docs: kubernetes tutorial

Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
Andrea Luzzardi 2021-06-03 18:18:32 -07:00 committed by Sam Alba
parent 24dfbc97fb
commit 4221011064
2 changed files with 427 additions and 0 deletions

View File

@ -0,0 +1,4 @@
{
"label": "Tutorials",
"position": 5
}

View File

@ -0,0 +1,423 @@
# Kubernetes
This example illustrates how to use `dagger` to build, push and deploy Docker
images to Kubernetes.
## Prerequisites
### Setup a local Kubernetes cluster
While dagger supports GKE and EKS, for the purpose of this example, we'll be
using [kind](https://kind.sigs.k8s.io/) to install a local Kubernetes cluster
in addition to a local container registry, no cloud account required.
1\. Install kind
Follow [these instructions](https://kind.sigs.k8s.io/docs/user/quick-start) to
install kind.
Alternatively, on macOS using [homebrew](https://brew.sh/):
```shell
brew install kind
```
2\. Start a local registry
```shell
docker run -d -p 5000:5000 --name registry registry:2
```
3\. Create a cluster with the local registry enabled in containerd
```bash
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"]
endpoint = ["http://registry:5000"]
EOF
```
4\. Connect the registry to the cluster network
```shell
docker network connect kind registry
```
## Initialize a Dagger Workspace and Environment
```shell
mkdir dagger-kubernetes && cd dagger-kubernetes
dagger init
dagger new default
```
## Create a basic plan
<!-- `.dagger/env/default/plan`
- `manifest.cue`:
- `deploy.cue`:
[Stateless Application](https://kubernetes.io/docs/tasks/run-application/run-stateless-application-deployment/) -->
Create a file named `.dagger/env/default/plan/manifest.cue` and add the
following configuration to it.
```cue title=".dagger/env/default/plan/manifest.cue"
package main
// inlined kubernetes manifest as a string
manifest: """
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
"""
```
This will define a `manifest` variable containing the inlined Kubernetes YAML
used to create a _nginx_ deployment.
Next, create `.dagger/env/default/plan/main.cue`.
```cue title=".dagger/env/default/plan/main.cue"
package main
import (
"dagger.io/kubernetes"
)
// input: ~/.kube/config file used for deployment
// set with `dagger input text kubeconfig -f ~/.kube/config`
kubeconfig: string @dagger(input)
// deploy uses the `dagger.io/kubernetes` package to apply a manifest to a
// Kubernetes cluster.
deploy: kubernetes.#Apply & {
// reference the `kubeconfig` input above
"kubeconfig": kubeconfig
// reference to the manifest defined in `manifest.cue`
"manifest": manifest
}
```
This defines:
- `kubeconfig` a _string_ **input**: kubernetes configuration (`~/.kube/config`)
used for `kubectl`
- `deploy`: Deployment step using the package `dagger.io/kubernetes`. It takes
the `manifest` defined earlier and deploys it to the Kubernetes cluster specified in `kubeconfig`.
### Configure the environment
Before we can bring up the deployment, we need to provide the `kubeconfig` input
declared in the configuration. Otherwise, dagger will complain about a missing input:
```shell
$ dagger up
6:53PM ERR system | required input is missing input=kubeconfig
```
You can inspect the list of inputs (both required and optional) using `dagger input list`:
```shell
$ dagger input list
Input Type Description
kubeconfig string ~/.kube/config file used for deployment
deploy.namespace string Kubernetes Namespace to deploy to
```
Let's provide the missing input:
```shell
# we'll use the ~/.kube/config created by `kind`
dagger input text kubeconfig -f ~/.kube/config
```
### Deploying
Now is time to deploy to kubernetes.
```shell
$ dagger up
deploy | computing
deploy | #26 0.700 deployment.apps/nginx created
deploy | completed duration=900ms
```
Let's verify the deployment worked:
```shell
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 1/1 1 1 1m
```
## CUE Kubernetes manifests
In this section we will convert the inlined YAML manifest to CUE to take advantage of the language features.
For a more advanced example, see the
[official CUE Kubernetes tutorial](https://github.com/cuelang/cue/blob/v0.4.0/doc/tutorial/kubernetes/README.md)
First, let's replace `manifest.cue` with the following configuration. This is a
straightforward one-to-one conversion from YAML to CUE, only the syntax has changed.
```cue title=".dagger/env/default/plan/manifest.cue"
package main
import (
"encoding/yaml"
)
nginx: {
apiVersion: "apps/v1"
kind: "Deployment"
metadata: {
"name": "nginx"
labels: app: "nginx"
}
spec: {
replicas: 1
selector: matchLabels: app: "nginx"
template: {
metadata: labels: app: "nginx"
spec: containers: [{
"name": "nginx"
"image": image
ports: [{
containerPort: port
}]
}]
}
}
}
manifest: yaml.Marshal(nginx)
```
We're using the built-in `yaml.Marshal` function to convert CUE back to YAML so
Kubernetes still receives the same manifest.
You can inspect the configuration using `dagger query` to verify it produces the
same manifest:
```shell
$ dagger query manifest -f text
apiVersion: apps/v1
kind: Deployment
...
```
Now that the manifest is defined in CUE, we can take advantage of the language
to remove a lot of boilerplate and repetition.
Let's define a re-usable `#Deployment` definition in `.dagger/env/default/plan/deployment.cue"`:
```cue title=".dagger/env/default/plan/deployment.cue"
package main
// Deployment template containing all the common boilerplate shared by
// deployments of this application.
#Deployment: {
// name of the deployment. This will be used to automatically label resouces
// and generate selectors.
name: string
// container image
image: string
// 80 is the default port
port: *80 | int
// 1 is the default, but we allow any number
replicas: *1 | int
// Deployment manifest. Uses the name, image, port and replicas above to
// generate the resource manifest.
manifest: {
apiVersion: "apps/v1"
kind: "Deployment"
metadata: {
"name": name
labels: app: name
}
spec: {
"replicas": replicas
selector: matchLabels: app: name
template: {
metadata: labels: app: name
spec: containers: [{
"name": name
"image": image
ports: [{
containerPort: port
}]
}]
}
}
}
}
```
`manifest.cue` can be rewritten as follows:
```cue title=".dagger/env/default/plan/manifest.cue"
import (
"encoding/yaml"
)
nginx: #Deployment & {
name: "nginx"
image: "nginx:1.14.2"
}
manifest: yaml.Marshal(nginx.manifest)
```
Let's make sure it yields the same result:
```shell
$ dagger query deploy.manifest -f text
apiVersion: apps/v1
kind: Deployment
...
```
And we can now deploy it:
```shell
$ dagger up
deploy | computing
deploy | #26 0.700 deployment.apps/nginx unchanged
deploy | completed duration=900ms
```
## Building, pushing and deploying Docker images
Rather than deploying an existing (`nginx`) image, we're going to build a Docker
image from source, push it to a registry and update the kubernetes configuration.
### Update the plan
The following configuration will:
- Declare a `repository` input as a `dagger.#Artifact`. This will be mapped to
the source code directory.
- Declare a `registry` input. This is the address used for docker push
- Use `dagger.io/docker` to build and push the image
- Use the registry image reference (`push.ref`) as the image for the deployment.
```cue title=".dagger/env/default/plan/manifest.cue"
package main
import (
"encoding/yaml"
"dagger.io/docker"
)
// input: source code repository, must contain a Dockerfile
// set with `dagger input dir repository ./app`
repository: dagger.#Artifact @dagger(input)
// registry to push images to
registry: string @dagger(input)
// docker build the `repository` directory
image: docker.#Build & {
source: repository
}
// push the `image` to the `registry`
push: docker.#Push & {
source: image
ref: registry
}
// use the `#Deployment` template to generate the kubernetes manifest
app: #Deployment & {
name: "test"
// use the reference of the image we just pushed
// this creates a dependency: `app` will only be deployed after the image is
// built and pushed.
"image": push.ref
}
manifest: yaml.Marshal(app.manifest)
```
### Connect the Inputs
Next, we'll provide the two new inputs, `repository` and `registry`.
For the purpose of this tutorial we'll be using
[hello-go](https://github.com/aluzzardi/hello-go) as example source code.
```shell
$ git clone https://github.com/aluzzardi/hello-go.git
dagger input dir repository ./hello-go
dagger input text registry "localhost:5000/image"
```
### Bring up the changes
```shell
$ dagger up
repository | computing
repository | completed duration=0s
image | computing
image | completed duration=1s
deploy | computing
deploy | #26 0.709 deployment.apps/hello created
deploy | completed duration=900ms
```
Let's verify the deployment worked:
```shell
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 1/1 1 1 1m
hello 1/1 1 1 1m
```
## Next Steps
Deploy on a hosted Kubernetes cluster:
- [GKE](https://github.com/dagger/dagger/tree/main/stdlib/gcp/gke)
- [EKS](https://github.com/dagger/dagger/tree/main/stdlib/aws/eks)
Authenticate to a remote registry:
- [ECR](https://github.com/dagger/dagger/tree/main/stdlib/aws/ecr)
- [GCR](https://github.com/dagger/dagger/tree/main/stdlib/gcp/gcr)
Integrate kubernetes tools with Dagger:
- [Helm](https://github.com/dagger/dagger/tree/main/stdlib/kubernetes/helm)
- [Kustomize](https://github.com/dagger/dagger/tree/main/stdlib/kubernetes/kustomize)