github.com/GoogleContainerTools/skaffold/v2@v2.13.2/docs-v1/content/en/docs/tutorials/ci_cd.md (about) 1 --- 2 title: "Using Skaffold for CI/CD with GitLab" 3 linkTitle: "Skaffold in CI/CD" 4 weight: 100 5 --- 6 Contributed by: [@tvvignesh](https://github.com/tvvignesh) 7 8 Skaffold is a tool which is non-opinionated about the CI/CD tool you should use and acts as a platform agnostic solution for both development and deploying to production. 9 10 To facilitate building and deploying images in CI/CD, skaffold offers docker images which you can build on top of. If that doesn’t work for your use-case, you can make your own Dockerfile and pull in skaffold and its dependencies using curl. 11 12 Let us have a look at how we can use Skaffold with GitLab CI. 13 14 ## Step 1: Getting the project and Dockerfile ready 15 16 The first step is to obviously have your project which you want to deploy ready with a Dockerfile setup for the same. 17 18 Skaffold supports multiple builders like Docker, Kaniko, Bazel and more and if the builder you are looking for is not supported out of the box, you can [script it out](https://skaffold.dev/docs/tutorials/custom-builder/) as well. We will use Docker in this demonstration. 19 20 For instance, if you would like to deploy nginx, your Dockerfile would look something like this: 21 22 23 ```Dockerfile 24 FROM nginxinc/nginx-unprivileged:1.19.3-alpine 25 WORKDIR /app/server 26 27 COPY ./web ./web 28 29 EXPOSE 8080 30 31 CMD [ "/bin/bash", "-c", "nginx -g 'daemon off;'" ] 32 ``` 33 34 35 Skaffold can use your dockerfile to automatically build, tag and push images to the registry when needed. 36 37 ## Step 2: Choosing the deployment method 38 39 Skaffold supports deployment through kubectl, helm and a lot of other mechanisms. You can have a look at the complete list of deployers available [here](https://skaffold.dev/docs/pipeline-stages/deployers/). 40 41 If you choose to deploy through kubectl, you must have all your yaml files ready, or if you would like to deploy through helm charts, you must have all your charts ready. 42 43 ## Step 3: Choosing the image and tag strategy 44 45 Skaffold automatically tags images based on different strategies as documented here: [https://skaffold.dev/docs/pipeline-stages/taggers/](https://skaffold.dev/docs/pipeline-stages/taggers/). The tagging strategy used is configurable, so choose the mechanism which is right for you. By default, skaffold uses the git sha tagger. 46 47 If you are using helm as your deployer, you might want to use the helm image strategy if you would like to follow helm specific conventions, as skaffold will pass in all the details for you. 48 49 ## Step 4: Plugins (optional) 50 51 If you want to do some kind of processing before deployment like decrypting the secrets in the CI/CD pipeline, skaffold also supports plugins like [Helm Secrets](https://github.com/zendesk/helm-secrets/). So, if you would like to deploy secrets that have been encrypted using KMS or any other mechanism supported by [SOPS](https://github.com/mozilla/sops) you can actually set **useHelmSecrets** option to true and skaffold will handle everything automatically for you. 52 53 ## Step 5: The `skaffold.yaml` file 54 55 Your `skaffold.yaml` file will change depending on the way you would like to build and deploy your project. It is recommended that you set up skaffold and run it manually before you setup your CI pipeline since the CI pipeline can just call skaffold to do all the builds and deployments which can be tested out locally via skaffold. 56 57 A sample skaffold file can look something like this: 58 59 60 ```yml 61 apiVersion: skaffold/v2beta8 62 kind: Config 63 profiles: 64 # Specify the profile name which you can run later using skaffold run -p <profilename> 65 - name: my-profile-name 66 build: 67 artifacts: 68 # Skaffold will use this as your image name and push it here after building 69 - image: asia.gcr.io/my-project/my-image 70 # We are using Docker as our builder here 71 docker: 72 # Pass the args we want to Docker during build 73 buildArgs: 74 NPM_REGISTRY: '{{.NPM_REGISTRY}}' 75 NPM_TOKEN: '{{.NPM_TOKEN}}' 76 deploy: 77 # Using Helm as the deployment strategy 78 helm: 79 # Pass the parameters according to https://skaffold.dev/docs/references/yaml/ 80 releases: 81 - name: my-release 82 namespace: default 83 # Using Helm secrets plugin to process secrets before deploying 84 useHelmSecrets: true 85 # Location of the chart - here, we use a local chart 86 chartPath: ./charts/my-chart 87 # Path to the values file 88 valuesFiles: 89 - ./deployment/dev/values.yaml 90 - ./deployment/dev/secrets.yaml 91 skipBuildDependencies: true 92 flags: 93 upgrade: 94 - --install 95 ``` 96 97 Please have a look at the comments above to understand what the yaml does. 98 99 Rather, if you want to deploy via kubectl, your skaffold file can look something like this: 100 101 102 ```yml 103 apiVersion: skaffold/v2beta8 104 kind: Config 105 profiles: 106 # Specify the profile name which you can run later using skaffold run -p <profilename> 107 - name: dev-svc 108 build: 109 artifacts: 110 # Skaffold will use this as your image name and push it here after building 111 - image: asia.gcr.io/my-project/my-image 112 # We are using Docker as our builder here 113 docker: 114 # Pass the args you want to Docker during build 115 buildArgs: 116 # Pass the args we want to Docker during build 117 NPM_REGISTRY: '{{.NPM_REGISTRY}}' 118 NPM_TOKEN: '{{.NPM_TOKEN}}' 119 deploy: 120 # In case we enable status check, this will be the timeout till which skaffold will wait 121 statusCheckDeadlineSeconds: 600 122 # Using kubectl as our deployer 123 kubectl: 124 # Location to our yaml files 125 # Refer https://skaffold.dev/docs/references/yaml/ for more options 126 manifests: 127 - k8/dev/*.yml 128 ``` 129 130 ## Step 6: Development & Testing locally 131 132 Before we move on to setting up the CI pipeline, we should test it out locally. Use `skaffold run` if you are deploying it to the cluster, `skaffold build` to just build and push the artifacts, `skaffold dev` if you are developing (this will enable auto reload on changes) or debug using `skaffold debug` 133 134 You can find docs about all these workflows [here](https://skaffold.dev/docs/workflows/) 135 136 Also, note that you can also set up file synchronization to enable faster development workflows. You can read more about that [here](https://skaffold.dev/docs/pipeline-stages/filesync/). 137 138 Enabling file synchronization would avoid the need to rebuild the images repeatedly during development and testing and this can accelerate the inner dev loop. 139 140 ## Step 7: Setting up authentication with the registry 141 142 As you may already know, there are a lot of places where you can host images namely [Docker Hub](https://hub.docker.com/), [GCR](https://cloud.google.com/container-registry), [GitLab Registry](https://docs.gitlab.com/ee/user/packages/container_registry/), [Quay](https://quay.io/) and so on. 143 144 The way you authenticate is specific to the registry provider of your choice. For eg. you may choose to do a `username-password` authentication with DockerHub or authenticate using service accounts in `GCR` and so on. 145 146 **NOTE:** Username-Password authentication is not recommended for CI/CD pipelines since they can be easily compromised and can sometimes be logged as well. 147 148 We will be using GCR in our example. You can refer [this page](https://cloud.google.com/container-registry/docs/advanced-authentication) to understand how to authenticate with it. 149 150 ## Step 8: Using DIND Service (Optional) 151 152 Docker images are typically not cached if you are running your builds/pipeline within Docker containers(i.e. [DIND](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker-workflow-with-docker-executor) or Docker-in-Docker) and this can be very slow since your CI runner has to download the images again and again for every build even if some layers are already available. 153 154 **NOTE:** If you are not looking to run your pipeline within Docker containers or are fine with slower pipelines/pipelines without caching, you can completely skip this step and proceed to Step 9. 155 156 To avoid this, we can set up a DIND service in our cluster to enable caching and storage of image layers for us. A sample dind deployment file as deployed in Kubernetes can look something like this: 157 158 159 ```yml 160 # Setup a DIND deployment within the Kubernetes cluster to cache images as we build images in our pipelines 161 apiVersion: apps/v1 162 kind: Deployment 163 metadata: 164 name: my-dind 165 spec: 166 selector: 167 matchLabels: 168 app: my-dind 169 strategy: 170 type: Recreate 171 template: 172 metadata: 173 labels: 174 app: my-dind 175 spec: 176 containers: 177 - image: docker:dind 178 imagePullPolicy: Always 179 name: my-dind 180 ports: 181 - containerPort: 2375 182 name: my-dind 183 env: 184 - name: DOCKER_HOST 185 value: tcp://localhost:2375 186 - name: DOCKER_TLS_CERTDIR 187 value: '' 188 securityContext: 189 privileged: true 190 volumeMounts: 191 - name: my-dind-storage 192 mountPath: /var/lib/docker 193 resources: 194 limits: 195 memory: '2Gi' 196 cpu: '1000m' 197 requests: 198 memory: '1Gi' 199 cpu: '250m' 200 volumes: 201 - name: my-dind-storage 202 persistentVolumeClaim: 203 claimName: my-dind-pv-claim 204 ``` 205 206 207 Service File: 208 209 210 ```yml 211 # Expose DIND as a service so that all the pipelines can access it 212 apiVersion: v1 213 kind: Service 214 metadata: 215 name: my-dind-svc 216 spec: 217 selector: 218 app: my-dind 219 type: LoadBalancer 220 ports: 221 - port: 2375 222 targetPort: 2375 223 protocol: TCP 224 name: http 225 ``` 226 227 228 **PV Claim:** 229 230 231 ```yml 232 # Provision a Persistent Volume Claim to be used to store the cached artifacts by DIND 233 apiVersion: v1 234 kind: PersistentVolumeClaim 235 metadata: 236 name: my-dind-pv-claim 237 labels: 238 app: my-dind 239 spec: 240 accessModes: 241 - ReadWriteOnce 242 resources: 243 requests: 244 storage: 200Gi 245 storageClassName: standard 246 ``` 247 248 249 And we would need to pass the URL to the DIND service when we do builds in the pipeline and all the caching will happen in the DIND storage speeding up the pipeline a lot. Make sure that you clean up the DIND storage periodically or setup cleanup scripts for the same else it might fill up soon. 250 251 ## Step 9: Getting the CI pipeline setup 252 253 Now that we have Skaffold ready and working locally, we look at the CI pipeline. 254 255 Here is a sample pipeline using GitLab CI and if you are new to it, you can refer the docs [here](https://docs.gitlab.com/ee/ci/quick_start/). Also, you might want to look at how to define GitLab CI/CD environment variables [here](https://docs.gitlab.com/ee/ci/variables/) 256 257 This pipeline might look complicated at first glance, but most of it related to configuring access for deploying to GCP and configure GitLab's Docker-in-Docker support to cache artifacts between builds. What is important to note is that we use Skaffold to do all the builds and deployments. 258 259 260 ```yml 261 stages: 262 # The name of the pipeline stage 263 - my-stage 264 265 # The name of the job 266 my-job: 267 # The image to be used as the base for this job 268 image: 269 name: gcr.io/k8s-skaffold/skaffold:v1.15.0 270 # Pipeline tags (if you have specified tags to your GitLab Runner) 271 tags: 272 - development 273 stage: my-stage 274 retry: 2 275 script: 276 # Logging in to our gcp account using the service account key (specified in GITLAB variables) 277 - echo "$GCP_DEV_SERVICE_KEY" > gcloud-service-key.json 278 - gcloud auth activate-service-account --key-file gcloud-service-key.json 279 # Specifying the project, zone, cluster to deploy our application in. 280 - gcloud config set project $PROJECT_DEV_NAME 281 - gcloud config set compute/zone $PROJECT_DEV_REGION 282 - gcloud container clusters get-credentials $CLUSTER_DEV_NAME 283 - kubectl config get-contexts 284 # Pass in all the environment variables to be passed to skaffold during build and deployment with all the args as documented in https://skaffold.dev/docs/references/cli/ (in our case our cluster context, env vars, namespace and some labels) 285 - DOCKER_HOST="$DIND_DEV" NPM_REGISTRY="$NPM_REGISTRY_DEV" NPM_TOKEN="$NPM_TOKEN_DEV" skaffold run --kube-context $CLUSTER_DEV_CONTEXT -n default -l skaffold.dev/run-id=deploydep -p dev-svc --status-check 286 only: 287 # Run this only for changes in the main branch 288 refs: 289 - main 290 ``` 291 292 So, ultimately our GitLab CI pipeline does not know anything about the helm charts or build strategy or any deployment methods and relies completely on skaffold to do the job for us. 293 294 This works equally well irrespective of whether you choose the [Docker Executor](https://docs.gitlab.com/runner/executors/docker.html) or the [Kubernetes Executor](https://docs.gitlab.com/runner/executors/kubernetes.html) and would require very little changes between the two. 295 296 While GitLab supports many executors (including Docker, Kubernetes, etc.) as documented [here](https://docs.gitlab.com/runner/executors/), we are working with Kubernetes executor here since it is vendor-agnostic, has the ability to auto-scale up/down, private (if you deploy the Runner in your Kubernetes cluster) and also is well supported for the forseeable future. 297 298 **NOTE:** If you are running Skaffold using the Kubernetes executor, make sure that you are running the runner with appropriate permissions or pod security policies. If you are running using DIND, it requires access to the Docker socket and being in privileged mode which might actually tend to be insecure. An alternative can be to use [kaniko](https://github.com/GoogleContainerTools/kaniko), [buildkit](https://github.com/moby/buildkit) or other custom builders like [Buildah](https://github.com/containers/buildah) for building the image. 299 300 ## Step 10: That’s all folks 301 302 If all went well so far, you can try pushing a commit and see your pipeline running, picking up the changes, building and pushing the image and also deploying the same. 303 304 Hope this post was informative. Good luck with your journey with Skaffold.