Skip to content

Commit

Permalink
Merge pull request #26 from garden-io/update-config
Browse files Browse the repository at this point in the history
Use K8s actions + various improvements
  • Loading branch information
eysi09 authored Nov 30, 2023
2 parents 48b00c7 + bdd51f2 commit 6c7a825
Show file tree
Hide file tree
Showing 26 changed files with 635 additions and 256 deletions.
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,68 @@
Please see Garden's official [Quickstart Guide](https://docs.garden.io/basics/quickstart) for the up-to-date instructions on how to use this repository.
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github-production-user-asset-6210df.s3.amazonaws.com/658727/272340510-34957be5-7318-4473-8141-2751ca571c4f.png">
<source media="(prefers-color-scheme: light)" srcset="https://github-production-user-asset-6210df.s3.amazonaws.com/658727/272340472-ad8d7a46-ef85-47ea-9129-d815206ed2f6.png">
<img alt="Garden" src="https://github-production-user-asset-6210df.s3.amazonaws.com/658727/272340472-ad8d7a46-ef85-47ea-9129-d815206ed2f6.png">
</picture>
</p>
<div align="center">
<a href="https://garden.io/?utm_source=github-quikstart">Website</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="https://docs.garden.io/?utm_source=github-quickstart">Docs</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="https://github.com/garden-io/garden/tree/0.13.21/examples">Examples</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="https://garden.io/blog/?utm_source=github-quickstart">Blog</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="https://go.garden.io/discord">Discord</a>
</div>

## Welcome to Garden's Quickstart Example 👋

This repository contains the Garden Quickstart example. Please see our [Quickstart Guide](https://docs.garden.io/basics/quickstart) for step-by-step instructions on how to deploy this project. If you see any issues or bugs, kindly report them
to the [main Garden repo](https://github.com/garden-io/garden/issues/new).

![Deploying the quickstart example](https://github.com/garden-io/quickstart-example/assets/5373776/5bde4656-0c6f-4ace-ad17-7f5feb4d9c23)

## About the Project

This project is a voting application that's meant to resemble a typical (if simplified) microservice architecture that runs on Kubernetes.
The goal is to demonstrate how you can use Garden to build, develop, and test applications like this.

The project also doubles as an interactive guide that walks you through some common Garden commands and workflows. We encourage you to give it a spin!

It's a good reference for how to _configure_ a Garden project but please don't take the application source code too seriously,
it's of mixed quality :)

### Garden Plugins

In this example we use the `ephmeral-kubernetes` plugin to deploy the project to a zero-config, ephemeral, Garden managed cluster that's spun up on-demand.
It's the quickest way to get started with Garden and this is the "quickstart" example after all.

If you'd rather deploy it to your own cluster, you can update the values in
the [`project.garden.yml`](https://github.com/garden-io/quickstart-example/blob/main/project.garden.yml) file.
To learn more about our different K8s plugins, check out [our documentation](https://docs.garden.io/kubernetes-plugins/about).

### Garden Actions

The project has the following micro services:

- `vote`—a frontend Vue application
- `api`—a Python server that receives votes from the `vote` frontend and pushes them to a message queue
- `redis`—a Redis deployment that's used as a message queue
- `worker`—a Java worker service that reads votes from the queue and pushes them to a database
- `db`—a Postgres database for storing the votes
- `result`—a Node.js websocket server that reads messages from the database and sends back to the `vote` client

These services are built, deployed, and tested with [Garden actions](https://docs.garden.io/overview/core-concepts#action).

Specifically, the `vote`, `api`, and `result` services all have their own Kubernetes manifests so we use the `container` Build action to build them
and the `kubernetes` Deploy action to deploy them.

The `redis` and `db` services are "off the shelf" Helm charts that are deployed via the `helm` Deploy action.

Finally the `worker` service is built and deployed via the `container` Build and Deploy actions respectively. Garden will generate the Kubernetes
manifests for you when using the `container` Deploy action which is useful for getting started quickly if you don't have those already—
but in general we recommend using your existing charts or manifests with Garden.

You can learn more about the Kubernetes action types [in our docs](https://docs.garden.io/kubernetes-plugins/action-types).
6 changes: 3 additions & 3 deletions api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

def get_redis():
if not hasattr(g, 'redis'):
g.redis = Redis(host="redis", db=0, socket_timeout=5)
g.redis = Redis(host="redis-master", db=0, socket_timeout=5)
return g.redis

@app.route("/health", methods=['GET'])
Expand All @@ -46,7 +46,7 @@ def vote():
redis = get_redis()
vote = request.form['vote']
data = json.dumps({'voter_id': voter_id, 'vote': vote})
print("received vote request for '%s' from voter id: '%s'" % (vote, voter_id))
print("Received vote request for '%s', pushing to Redis queue with ID '%s'" % (vote, voter_id))
sys.stdout.flush()

redis.rpush('votes', data)
Expand All @@ -56,7 +56,7 @@ def vote():
mimetype='application/json'
)
else:
print("received invalid request")
print("Received invalid request")
sys.stdout.flush()
return app.response_class(
response=json.dumps({}),
Expand Down
56 changes: 29 additions & 27 deletions api/garden.yml
Original file line number Diff line number Diff line change
@@ -1,44 +1,46 @@
---
kind: Build
name: api
type: container
name: api-build
description: The backend build for the voting UI
description: Build the vote API

---
kind: Deploy
type: container
name: api
build: api-build
description: The backend deploy for the voting UI
type: kubernetes
description: Deploy the vote API
dependencies: [build.api, deploy.redis]

variables:
hostname: api.${var.baseHostname || providers.ephemeral-kubernetes.outputs.default-hostname}

spec:
args: [python, app.py]
# Variables such as container image are set via Garden template strings
# in the manifests themselves. Variables can also be set in the Garden config to ensure the manifests
# remain valid K8s manifests. Learn more at: https://docs.garden.io/kubernetes-plugins/action-types/kubernetes
files: [./manifests/*]

# This tells Garden what "target" to use for logs, code syncing and more
defaultTarget:
kind: Deployment
name: api

sync:
args: ["/bin/sh", "-c", "ls /app/app.py | entr -n -r python /app/app.py"]
paths:
- target: /app
- sourcePath: .
containerPath: /app
mode: "one-way-replica"
exclude: [.venv]
ports:
- name: http
protocol: TCP
containerPort: 8080
servicePort: 80
ingresses:
- path: /api
port: http
hostname: "api.${var.base-hostname || providers.ephemeral-kubernetes.outputs.default-hostname}"
healthCheck:
httpGet:
path: /health
port: http
dependencies:
- deploy.redis
overrides:
# Use entr to restart server on file changes in sync mode
- args: ["/bin/sh", "-c", "ls /app/app.py | entr -n -r python /app/app.py"]

---
kind: Test
type: container
name: unit
description: Unit test for backend API
build: api-build
type: container
description: Unit test the vote API
dependencies: [build.api]

spec:
image: ${actions.build.api.outputs.deploymentImageId}
args: ["echo", "ok"]
57 changes: 57 additions & 0 deletions api/manifests/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- args:
- python
- app.py
env:
image: ${actions.build.api.outputs.deploymentImageId}
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 90
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 3
name: api
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 90
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 2
periodSeconds: 1
successThreshold: 2
timeoutSeconds: 3
resources:
limits:
memory: 300Mi
requests:
cpu: 10m
memory: 90Mi
securityContext:
allowPrivilegeEscalation: false
imagePullSecrets:
- name: ${var.imagePullSecretName || '<empty>'}
restartPolicy: Always
17 changes: 17 additions & 0 deletions api/manifests/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api
spec:
ingressClassName: nginx
rules:
- host: ${var.hostname}
http:
paths:
- backend:
service:
name: api
port:
number: 80
path: /
pathType: Prefix
12 changes: 12 additions & 0 deletions api/manifests/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: api
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
selector:
app: api
87 changes: 49 additions & 38 deletions postgres/garden.yml
Original file line number Diff line number Diff line change
@@ -1,50 +1,61 @@
kind: Deploy
description: Postgres container for storing voting results
type: container
name: postgres
name: db
type: helm
description: Deploy a Postgres database for storing voting results

spec:
image: postgres:11.7-alpine
volumes:
- name: data
containerPath: /db-data
ports:
- name: postgres
containerPort: 5432
env:
POSTGRES_DATABASE: ${var.postgres-database}
POSTGRES_USERNAME: ${var.postgres-username}
POSTGRES_PASSWORD: ${var.postgres-password}
healthCheck:
command: [psql, -w, -U, "${var.postgres-username}", -d, "${var.postgres-database}", -c, "SELECT 1"]
chart:
name: postgresql
repo: https://charts.bitnami.com/bitnami
version: 12.6.6
values:
# This is a more digestable name than the default one in the template
fullnameOverride: postgres
auth:
username: ${var.postgresUsername}
database: ${var.postgresDatabase}
postgresPassword: ${var.postgresPassword}
# Avoid some late startup flakiness
primary:
readinessProbe:
successThreshold: 3 # Raised from a default of 1
persistence:
enabled: false

---
kind: Run
name: db-init
type: container
dependencies: [deploy.postgres]
type: kubernetes-exec

dependencies:
- deploy.db

spec:
image: postgres:11.7-alpine
command: [/bin/sh, -c]
# The postgres health check appears to go through before the server accepts remote connections, so we need to
# sleep for a while.
# https://github.com/CrunchyData/crunchy-containers/issues/653
args:
resource:
kind: "StatefulSet"
name: "postgres"
command:
[
"sleep 15 && psql -w -U ${var.postgres-username} --host=postgres --port=5432 -d ${var.postgres-database} -c 'CREATE TABLE IF NOT EXISTS votes (id VARCHAR(255) NOT NULL UNIQUE, vote VARCHAR(255) NOT NULL, created_at timestamp default NULL)'",
"bin/sh",
"-c",
"sleep 15 && PGPASSWORD=${var.postgresPassword} psql -w -U ${var.postgresUsername} --host=postgres --port=5432 -d ${var.postgresDatabase} -c 'CREATE TABLE IF NOT EXISTS votes (id VARCHAR(255) NOT NULL UNIQUE, vote VARCHAR(255) NOT NULL, created_at timestamp default NULL)'",
]
env:
PGDATABASE: ${var.postgres-database}
PGUSER: ${var.postgres-username}
PGPASSWORD: ${var.postgres-password}

---
kind: Run
name: db-clear
type: container
dependencies: [deploy.postgres]
type: kubernetes-exec

dependencies:
- deploy.db

spec:
image: postgres:11.7-alpine
command: [/bin/sh, -c]
args: ["psql -w -U ${var.postgres-username} --host=postgres --port=5432 -d ${var.postgres-database} -c 'TRUNCATE votes'"]
env:
PGDATABASE: ${var.postgres-database}
PGUSER: ${var.postgres-username}
PGPASSWORD: ${var.postgres-password}
resource:
kind: "StatefulSet"
name: "postgres"
command:
[
"bin/sh",
"-c",
"PGPASSWORD=${var.postgresPassword} psql -w -U ${var.postgresUsername} --host postgres --port=5432 -d ${var.postgresDatabase} -c 'TRUNCATE votes'",
]
Loading

0 comments on commit 6c7a825

Please sign in to comment.