Kubernetes, GitLab and DigitalOcean
Using Kubernetes, GitLab and DigitalOcean can be a challenge. Even when you’re familiar with all three.
Versions tend to change over time. Although they are up to date at the time of writing, make sure to check for updates when you’re reading this later on.
Prerequisites
Before you can link your cluster to GitLab, you need to have a DigitalOcean account and access to GitLab. It doesn’t matter what version of GitLab you are using. It can be your own installation or gitlab.com.
Kubernetes
Getting Kubernetes on DigitalOcean is fairly straight forward. You can add a new cluster in the cloud interface. Click on Create and head for Clusters. The wizard will guide you through the process. If you plan to use Knative, make sure your cluster has at least 3 nodes, 6 vCPUs and 22.5GB memory.
Install doctl
doctl
is the command line interface to interact with DigitalOcean, similar to cli-tools from other cloud providers. The installation instructions are available on GitHub. You need this to get the kubectl
configuration for Kubernetes.
When doctl
is installed you can authenticate with DigitalOcean.
Run
doctl auth init
. It will ask you for the token you’ve created.
You now have access to your project at DigitalOcean.
Install kubectl
kubectl
is used to interact with Kubernetes. You can find the installation instructions for all operating systems on the kubernetes.io website.
When you’ve got kubectl
installed, you can use doctl
to get and save the Kubernetes configuration from DigitalOcean.
doctl kubernetes cluster kubeconfig save <cluster>
doctl kubernetes cluster kubeconfig save <cluster>
DigitalOcean rotates all cluster certificates every 7 days. And although this is a good thing, it means you will have to update your configuration occasionally too.
Add Kubernetes to GitLab
Credits to Stepan Kuzmin for his gist and GitLab for the video, most of which you can find below.
Add an existing Kubernetes cluster
When you’re adding a Kubernetes cluster to your project or installation, add an existing one. The steps below will help you get an API URL
, CA Certificate
and Service Token
.
Create a service account:
kubectl create -f - <<EOFapiVersion: v1kind: ServiceAccountmetadata:name: gitlabnamespace: defaultEOF
kubectl create -f - <<EOFapiVersion: v1kind: ServiceAccountmetadata:name: gitlabnamespace: defaultEOF
Create the cluster role giving the gitlab
-account cluster-admin privileges.
kubectl create -f - <<EOFkind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:name: gitlab-cluster-adminsubjects:- kind: ServiceAccountname: gitlabnamespace: defaultroleRef:kind: ClusterRolename: cluster-adminapiGroup: rbac.authorization.k8s.ioEOF
kubectl create -f - <<EOFkind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:name: gitlab-cluster-adminsubjects:- kind: ServiceAccountname: gitlabnamespace: defaultroleRef:kind: ClusterRolename: cluster-adminapiGroup: rbac.authorization.k8s.ioEOF
Get the API URL
.
kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'
kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'
Get the CA Certificate
.
kubectl get secret $(kubectl get secrets | awk '/gitlab/ {print $1}') -o jsonpath="{['data']['ca\.crt']}" | base64 -D
kubectl get secret $(kubectl get secrets | awk '/gitlab/ {print $1}') -o jsonpath="{['data']['ca\.crt']}" | base64 -D
Get the Service Token
.
kubectl get secret $(kubectl get secrets | awk '/gitlab/ {print $1}') -o jsonpath="{['data']['token']}" | base64 -D
kubectl get secret $(kubectl get secrets | awk '/gitlab/ {print $1}') -o jsonpath="{['data']['token']}" | base64 -D
Make sure you keep RBAC-enabled cluster and GitLab-managed enabled.
If you keep GitLab-managed enabled GitLab will expose your Kubernetes configuration to deployments with the environment set. Your jobs will depend on it, so leave this enabled.
Install helper applications
As soon as the cluster is added you will be able to add a number of helper applications that GitLab provides.
Install Helm Tiller
Install Ingress*
Install Cert-Manager
Install Prometheus
If you want to run your CI jobs on Kubernetes you can install the runner. I won’t cover this, as there are a few caveats when you do, Docker in Docker being one of them, but it’s still pretty cool.
* When you add the Ingress application a load balancer is added to your DigitalOcean project, you will be billed for it.
Once the helper applications are installed you should see the cluster health in your dashboard. This means Prometheus is running and things should be in good health.
Setting up your app in GitLab
It’s time to deploy our application. Let’s start with a basic .gitlab-ci.yml
.
variables:K8S_NAMESPACE: $CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUGstages:- deploydeploy-k8s:image: dtzar/helm-kubectlstage: deploybefore_script:- apk add -u gettext- kubectl create secret docker-registry regcred --docker-server=$CI_REGISTRY --docker-username="$CI_DEPLOY_USER" --docker-password="$CI_DEPLOY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL" --dry-run=true -o yaml | kubectl apply -f -environment:name: testurl: test.yourapp.comscript:- VERSION="${CI_COMMIT_TAG:-none}" envsubst < k8s.yml | kubectl apply -f -
variables:K8S_NAMESPACE: $CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUGstages:- deploydeploy-k8s:image: dtzar/helm-kubectlstage: deploybefore_script:- apk add -u gettext- kubectl create secret docker-registry regcred --docker-server=$CI_REGISTRY --docker-username="$CI_DEPLOY_USER" --docker-password="$CI_DEPLOY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL" --dry-run=true -o yaml | kubectl apply -f -environment:name: testurl: test.yourapp.comscript:- VERSION="${CI_COMMIT_TAG:-none}" envsubst < k8s.yml | kubectl apply -f -
In this deployment we’re using a few predefined variables.
$KUBE_NAMESPACE
— is the namespace we get from GitLab.$GITLAB_USER_EMAIL
— is your e-mail address.$CI_DEPLOY_USER
— is the user name of the read only deploy token.$CI_DEPLOY_PASSWORD
— is the key of the deploy token.
These last two variables need some further explanation.
Deploy tokens
In most environments your app sits in a private registry. GitLab creates tokens that can be used for temporary access. Although Kubernetes could use these, it would break whenever you start scaling your nodes. It wouldn’t be able to use the expired token and Kubernetes will not be able to launch new pods.
This is why we’re using Deploy Tokens. A deploy token is persistent. To use them, you need to create a secret in Kubernetes.
Create a read only Deploy Token, use
gitlab-deploy-token
as its name. It only needs access to read the registry. The username is irrelevant.If you have named the token
gitlab-deploy-token
, your CI job will find the username and token automatically.
Our before_script
creates a secret for you with the name regcred
. In most cases there is no need to change this name as we’re deploying each app in its own namespace, but feel free to do. You will need it in your deployment. It should be added to the spec of your deployment template like so.
...imagePullSecrets:- name: regcred...
...imagePullSecrets:- name: regcred...
Preparing for deployment
Once the deploy token is configured and the actual job is kicked off, there are a few things happening that need an explanation. You may want to keep a copy of the gist open while reading the below.
The kubectl
commands are different from what you would expect if you have worked with Kubernetes before. We’re using --dry-run=true
to get the YAML output and apply this instead.
... --dry-run=true -o yaml | kubectl apply -f -
... --dry-run=true -o yaml | kubectl apply -f -
You may wonder why. Running kubectl create
would have created the same resource, right? Although strictly true, Kubernetes will throw errors when you try to create that same resource. This is why you need apply
instead of create
.
Deploying your app
The script
to deploy your application is pretty straight forward:
envsubst < k8s.yml | kubectl apply -f -
envsubst < k8s.yml | kubectl apply -f -
We have prefixed it in the gist with a VERSION
variable as we use this to label our objects. It helps to quickly track what version a deployment is running.
More on envsubst
below.
Your deployment
I won’t go into the details of setting up a deployment, service or ingress. It’s up to you how you configure your deployments. There are some things that may help you get up to speed faster though. I will cover these below.
Variables
envsubst
replaces environment variables in standard input. This means we can create YAML-files like this:
...labels:app: your-appcommit: ${CI_COMMIT_SHORT_SHA}version: ${VERSION}...
...labels:app: your-appcommit: ${CI_COMMIT_SHORT_SHA}version: ${VERSION}...
And pass them to envsubst
with envsubst < k8s.yml
. This will be copied to standard output and can then be used to create your resources.
We’re using a single file for all environments. We’re keeping it DRY.
Ingress load balancing
Remember the applications you have deployed?
It includes an ingress controller that is linked to a load balancer. To use it, you have to annotate the ingress object you’re creating.
...metadata:name: ingress-your-appannotations:kubernetes.io/ingress.class: "nginx"...
...metadata:name: ingress-your-appannotations:kubernetes.io/ingress.class: "nginx"...
This will route traffic from the ingress controller to your ingress object, to your service and your pods.
Other useful annotations can be found here.
SSL and Let's Encrypt
Remember the Cert-Manager too?
It can automatically assign and renew certificates from Let’s Encrypt. To use it, you have to annotate the ingress object and add tls
to your spec
.
...metadata:name: ingress-your-appannotations:...certmanager.k8s.io/cluster-issuer: letsencrypt-prod......spec:tls:- hosts:- test.yourapp.comsecretName: test-yourapp-com
...metadata:name: ingress-your-appannotations:...certmanager.k8s.io/cluster-issuer: letsencrypt-prod......spec:tls:- hosts:- test.yourapp.comsecretName: test-yourapp-com
It can take a while for your certificate to be available. Be patient and make sure your DNS has been set — use a wildcard if you’re using review apps in GitLab to make things easier.
Other useful annotations can be found here.
Monitoring
Last but not least, Prometheus.
Make sure your deployment, service and ingress are prefixed with the environment slug like so:
...metadata:name: ${CI_ENVIRONMENT_SLUG}-your-app...
...metadata:name: ${CI_ENVIRONMENT_SLUG}-your-app...
This makes sure GitLab knows where to pick up data from Prometheus.
If you’re on the silver or equivalent plans and up and you would like to use the deploy boards feature, make sure your annotations are set up on the deployment itself AND the spec template:
...metadata:...annotations:app.gitlab.com/env: $CI_ENVIRONMENT_SLUGapp.gitlab.com/app: $CI_PROJECT_PATH_SLUG...
...metadata:...annotations:app.gitlab.com/env: $CI_ENVIRONMENT_SLUGapp.gitlab.com/app: $CI_PROJECT_PATH_SLUG...
The 'magic' namespace
If you’re a little familiar with Kubernetes you might have noticed .gitlab-ci.yml
has not been configured to create a namespace. Its creation is hidden from your CI job.
On the first deployment of your app, GitLab will create the $KUBE_NAMESPACE
, service accounts and access tokens. Subsequent jobs will use these credentials to deploy your application.
There’s a downside to this.
If you decide to delete this namespace, your jobs will fail. GitLab stores the namespace in a database and once it’s there, it is assumed that it is never deleted. There is no reality check — and that’s a mistake.
In case you do decide to delete the namespace and want to start over, you need to remove the entry from GitLab’s database.
# gitlab-rails dbgitlabhq_production=> select * from clusters_kubernetes_namespaces;gitlabhq_production=> delete from clusters_kubernetes_namespaces where namespace = '<namespace>';
# gitlab-rails dbgitlabhq_production=> select * from clusters_kubernetes_namespaces;gitlabhq_production=> delete from clusters_kubernetes_namespaces where namespace = '<namespace>';
Wrapping up: the Kubernetes dashboard
The Kubernetes dashboard is a web-based user interface. It makes it easier to troubleshoot your application(s). You can install the dashboard in the kube-system
namespace with the following command:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml
Read more on how to connect to it on the Web UI documentation pages.
You can reuse the token you’ve created for GitLab to login or create a new token, this is up to you.
Closing words
Thanks for reading this. I hope this article was useful and helps you get the most out of your GitLab installation together with DigitalOcean.