--- slug: /learn/107-kubernetes --- # Dagger 107: deploy to Kubernetes This tutorial illustrates how to use dagger to build, push and deploy Docker images to Kubernetes. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; ## Prerequisites For this tutorial, you will need a Kubernetes cluster. [Kind](https://kind.sigs.k8s.io/docs/user/quick-start) is a tool for running local Kubernetes clusters using Docker. 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 < This tutorial can be run against a [GCP GKE](https://cloud.google.com/kubernetes-engine) cluster and [GCR](https://cloud.google.com/container-registry) This tutorial can be run against a [AWS EKS](https://aws.amazon.com/eks/) cluster and [ECR](https://aws.amazon.com/ecr/) ## Initialize a Dagger Workspace and Environment ```shell mkdir dagger-kubernetes && cd dagger-kubernetes dagger init dagger new default ``` ## Create a basic plan 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/dagger" "dagger.io/kubernetes" ) // input: ~/.kube/config file used for deployment // set with `dagger input secret kubeconfig -f ~/.kube/config` kubeconfig: dagger.#Secret @dagger(input) // deploy uses the `dagger.io/kubernetes` package to apply a manifest to a // Kubernetes cluster. deploy: kubernetes.#Resources & { // reference the `kubeconfig` input above "kubeconfig": kubeconfig // reference to the manifest defined in `manifest.cue` "manifest": manifest } ``` ```cue title=".dagger/env/default/plan/main.cue" package main import ( "dagger.io/kubernetes" "dagger.io/gcp/gke" ) // gkeConfig used for deployment gkeConfig: gke.#KubeConfig @dagger(input) kubeconfig: gkeConfig.kubeconfig // deploy uses the `dagger.io/kubernetes` package to apply a manifest to a // Kubernetes cluster. deploy: kubernetes.#Resources & { // reference the `kubeconfig` input above "kubeconfig": kubeconfig // reference to the manifest defined in `manifest.cue` "manifest": manifest } ``` ```cue title=".dagger/env/default/plan/main.cue" package main import ( "dagger.io/kubernetes" "dagger.io/aws/eks" ) // eksConfig used for deployment eksConfig: eks.#KubeConfig @dagger(input) kubeconfig: eksConfig.kubeconfig // deploy uses the `dagger.io/kubernetes` package to apply a manifest to a // Kubernetes cluster. deploy: kubernetes.#Resources & { // 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 ``` ```shell $ dagger input list Input Type Description deploy.namespace string Kubernetes Namespace to deploy to gkeConfig.config.region string GCP region gkeConfig.config.project string GCP project gkeConfig.config.serviceKey dagger.#Secret GCP service key gkeConfig.clusterName string GKE cluster name ``` ```shell $ dagger input list Input Type Description deploy.namespace string Kubernetes Namespace to deploy to eksConfig.config.region string AWS region eksConfig.config.accessKey dagger.#Secret AWS access key eksConfig.config.secretKey dagger.#Secret AWS secret key eksConfig.clusterName string EKS cluster name ``` Let's provide the missing inputs: ```shell # we'll use the ~/.kube/config created by `kind` dagger input text kubeconfig -f ~/.kube/config ``` ```shell dagger input text gkeConfig.config.project dagger input text gkeConfig.config.region dagger input text gkeConfig.clusterName dagger input secret gkeConfig.config.serviceKey -f ``` ```shell dagger input text eksConfig.config.region dagger input text eksConfig.clusterName dagger input secret eksConfig.config.accessKey dagger input secret eksConfig.config.secretKey ``` ### 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/dagger" "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)