• This email address is being protected from spambots. You need JavaScript enabled to view it.
    • +44 (0) 20374 57368
Monday, 17 September 2018 10:50

The kubernetes config is a file that kubernetes uses to store different configuration variables.

Information on things like clusters, contexts and users are all stored here. Having all of these stored in the config allows us to easily switch between any of them when working with multiple projects.

If you have already started using kubernetes, you can sneak a peek at what it looks like using the following command on your terminal:

kubectl config view

This should print out all of your kubernetes configs as a single file to the terminal.


One of the collections in the config file will be for the clusters. This contains block sequences for each of your clusters. Each block sequence will contain mapping for the name of the cluster, it will also contain mapping for the server and the certificate authority data of the cluster.


Each block sequence in the user collection contains information on the name of the user block, and then the user information. The user information contains the auth provider. Which then contains the name of the service provider, and other details like token information and cmd paths and args.


For the context collection, each block sequence contains the name of the context, the user it uses and the cluster it uses. The context collection basically pairs the users with the correct clusters.

The config file

When working with multiple clusturs, being able to quickly change between them is a time saver. This can be achieved by creating a context for each cluster in your kubernetes config.

Creating the config file

Start with creating a file named 'example-config'

apiVersion: v1
kind: Config
preferences: {}

- cluster: 
  name: client-A
- cluster:
  name: client-B

- name: developer
- name: maintainer

- context:
  name: dev-production
- context:
  name: dev-staging
- context:
  name: dev-testing
- context:
  name: dev-development
- context:
  name: maint-production

The configuration file now describes two clusters named client-A and client-B respectively. It also describes two users, developer and maintainer. Finally it describes five contexts, dev-production, dev-staging, dev-testing, dev-development, and maint-production.

For each of your cluster details, you will also need to set the server details and if your server is using SSL, the certificate. Don't worry if your server is not using SSL though, you can always set insecure-skip-tls-verify: true for your cluster.

You can do this using kubectl:

kubectl config --kubeconfig=example-config set-cluster client-A --server= --certificate-authority=/home/user/some-ca-file.crt

In the above command, we tell kubectl to edit the definition of the client-A cluser in the example-config file. We want to set the server to and we want to use the crt file at /home/user/some-ca-file.crt.

If you open up your example-config file again, you will find that your definition for cluster client-A now looks like this

- cluster:
    certificate-authority: /home/user/some-ca-file.crt
  name: client-A

To set cluster client-B, we use the same command while changing some of the variables

kubectl config --kubeconfig=example-config set-cluster client-B --server= --insecure-skip-tls-verify

The client-B cluster definition will now look like this,

    insecure-skip-tls-verify: true
  name: client-B

Now that we have successfully set the definition for our clusters, we will have to add user details to the configuration file.

The following two lines will add the details we require to our developer user and our maintainer user:

kubectl config --kubeconfig=example-config set-credentials developer --client-certificate=some-client.crt --client-key=some-client.key

kubectl config --kubeconfig=example-config set-credentials maintainer --username=user --password=some-password

The updated user collection in your example-config should now look like this:

- name: developer
    client-certificate: some-client.crt
    client-key: some-client.key
- name: maintainer
    password: some-password
    username: user

Now that we have properly defined our clusters and users, we can define the contexts. We initially created five contexts so we will have to run five commands to add all their details

kubectl config --kubeconfig=example-config set-context dev-development --cluster=client-A --namespace=development --user=developer

kubectl config --kubeconfig=example-config set-context dev-testing --cluster=client-A --namespace=testing --user=developer

kubectl config --kubeconfig=example-config set-context dev-staging --cluster=client-A --namespace=staging --user=developer

kubectl config --kubeconfig=example-config set-context dev-production --cluster=client-A --namespace=production --user=developer

kubectl config --kubeconfig=example-config set-context maint-production --cluster=client-B --namespace=production --user=maintainer

One last look into our example-config file andd we should see this:

- context:
    cluster: client-A
    namespace: development
    user: developer
  name: dev-development
- context:
    cluster: client-A
    namespace: testing
    user: developer
  name:: dev-testing
- context:
    cluster: client-A
    namespace: staging
    user: developer
  name:: dev-staging
- context:
    cluster: client-A
    namespace: production
    user: developer
  name:: dev-production
- context:
    cluster: client-B
    namespace: production
    user: maintainer
  name: maint-production

Using the config file to pick a context

Now that we have everything set up, we can set a current context to be able to switch between clusters, users and namespaces as we have previously defined. Use the following command to set the current context:

kubectl config --kubeconfig=example-config use-context dev-development

This command will set all the user details, cluster details and the namespace defined in our example-config file.

Viewing a config file in the terminal

It is also possible to view the config file in the terminal using the view command:

kubectl config --kubeconfig=example-config view

or if you only want to see the details of the current context you can use the --minify flag with the view command:

kubectl config --kubeconfig=example-config view --minify
Wednesday, 08 August 2018 11:08

Kubernetes running on Google Cloud is our go-to deployment architecture, also known as the Google Container Engine (or GKE for short). We have helped many of our clients set up their application infrastructure using this solution with excellent results. If you’re already here then you probably know all the great features and benefits Kubernetes offers.

One question that we found ourselves asking was ‘What’s is the best ingress for Kubernetes?’. Now the answer isn’t that straightforward because of cause it entirely depends what your setup and what you're trying to achieve. So here we will take a look through four of the most popular ingresses solutions that are available.

For people that don’t know, there are basically two out of the box natively supported solutions:

We will also take a look at two other solutions on offer:

A Quick Load Balance Primer

Before we delve into the different ingress options we have on Kubernetes let’s quickly review the two types of LB we might want to use; Layer 4 (L4) and Layer 7(L7) these layers correspond to the OSI model.

Layer 4

At this layer, load balancing is relatively simple. Normally is just a service which is able to forward traffic over TCP/UDP ports to a number of consuming services. This is generally done is a round robin fashion, and employs some basic health check to determine if the service should be sent request. This layer tends to be routed by simple high-performance applications.

Layer 7

This is where the magic happens. At Layer 7 load balancing offers the ability to intelligently redirect traffic by inspecting its content. This then allows the service to optimize the way it handles traffic. Not only that but it can also manipulate the content, perform security inspection and implement access controls. With the requirement to inspect traffic content and apply rules, Layer 7 load balancing require much more resources than at Layer 4 and the applications use are highly tuned to carry out these operations.

GKE Ingress

GKEFirst, we will take a look at the GKE ingress. We found this service to work great for simple deployments and as it available straight out of the box and required very little configuration. It worked well up until the point we need to do something other than the simple host and path routing.

 Features  Limitations
  •  Supports layer 4 and 7 load balancing
  • Available straight out of the box. No additional config required.
  • Hosted outside your cluster in Google architecture.
  • Makes use of Google’s global IP system. If you need to move your cluster to a new zone you can keep your IP.
  • Only available if your deploying on Google Cloud
  • No cross namespace support – We found it would be useful to have a single LB that was able to access all our services regardless of their namespace.
  • No access control – Being able to restrict access to certain resources would be great
  • No ability to enforce SSL.
  • Limited SSL options – No ability to refine SSL config
  • Letsencrypt not natively supported by available by using Kube-Lego

NGINX Ingress

The other out of the box solution is a NGINX reverse proxy setup. This offers a lot more flexibility in configuration that the GCE ingress.


  • Deep configuration though configmap
  • SSL Enforcement via redirect
  • ModSecurity Web Application Firewall – This can stop penetration attempt fore they they even reach your application.


  • Letsencrypt not natively supported by available by using Kube-Lego


The voyager ingress is backed by the well respected HAProxy, which is known as one of the best open source load balancers available. It also come with built in support for Prometheus another great piece of soft that can provide powerful metrics on your traffic. This sounds like it could be very promising.


  • Supports layer 4 and 7 load balancing
  • Cross Namespace traffic routing
  • Has semi-native Letsencrypt which can offer
  • Managed SSL certificates resource
  • Allows for monitoring using Prometheus


Traefik is the new kid on the block. It bills itself as a modern HTTP reverse proxy and load balancer for made for deploying microservices. It’s designed to complete with the likes of NGINX and HAProxy but more lightweight and focused towards container deployments. This seems very promising and it gathering quite a community around it. Support for main backends is already on offer including Kubernetes.


  • Web UI based on AngularJS
  • Let’s Encrypt via Lego
  • Choice of monitoring options nativily supported: Prometheus, DataDog, StatsD and InfluxDB


  • While Traefik offers very rich set of feature this are not all easily accessible via the ingress config

Web Application Firewall   

 GCE IngressNGINX IngressVoyagerTræfik
Layer 7 Support
Layer 4 Support    
Native To K8s  
SSL Enforcement  
Lets Encrypt Support  
Basic Authentication  
Access Control    
Built-in Monitoring UI  

(HAProxy stats)
Prometheus support    


If your using Google Cloud and all you need is a reliable, super simple entry point then the GCE Ingress will give you everything you need with minimal config.

Traefik has a great selection of features and for a young project shows great promise. However it’s integration with kubernetes is not as tight as its competitors and this makes configuring and managing it a bit more cumbersome.

On the other hand it offers a lot of integration options with different deployment, management and monitoring platforms so If you are already using one of these then this might be the best choice for you. We will certainly be keeping a close eye on this project.

This then leaves us with NGINX and Voyager, both of these platforms are backed by industry tried and tested load balancers and offer a similar set of features. Choosing between these two is likely to come down to a mixture of the features offered being the best match for your environment and a personal preference between the two load balance technologies.

One feature that Voyager offered that appealed to us was the cross Namespace traffic routing which worked well with the way we were employing namespaces. The allowed us to limit the amount of total ingresses we had and in turn simplified our deployment.

If none of the features offered by Voyager as of any consequence to you then you most likely better of sticking with NGINX ingress as this it supported under the Kubernetes umbrella.

We have heard about some other ingresses out there if you want to explore the subject further:

Tuesday, 07 August 2018 13:49

The main difference between a virtual machine and a Docker container is in how they interact with the physical machine that they are running on. These differences affect how much resources running a virtual machine or a Docker container consumes. These differences also affect the portability of an application meant to be used with a virtual machine or a docker container.

Virtual Machines

A virtual machine is a software that allows you to run an operating system (OS) inside of another OS called the host OS. Below is a diagram of what it would look like to run applications on a virtual machine.

While a virtual machine can only emulate one OS at a time, a single physical machine can run multiple operating systems at a time. The number of which, is only limited to the amount of resources on that physical machine.

A virtual machine is able to run its own OS on top of the host OS with the help of a process called a hypervisor. The hypervisor acts as a mediator between the OS in the virtual machine, and the resources in the physical machine. Since each virtual machine is running a full OS, it will take a lot to be able to run any number of them.

Knowing that running multiple VMs may be resource heavy, why then are they still used when setting up an application on the web?

One of the problem software developers face is that inconsistency in the specifications of physical machines, whether or not they are running the same OS, sometimes breaks applications. “It works on that machine, but not on this one.” is a phrase you hear a lot and you might think that the problem can be easily fixed just by making sure that all the physical machines, and all the operating systems that are running on them are exactly the same. While this seems like a viable solution, you have little to no control over these two these when putting up an application on the cloud.

Since making sure that each physical machine and operating is exactly the same, software developers use virtual machines. It is far easier to set up multiple virtual machines that are perfect mirrors of each other than to do the same with physical machines. The increased use of resources is a fair price to pay for the assurance that no matter where an application is put up, as long as the virtual machine is configured a set way, it will always work.

Unique Attributes:

  • Hypervisor
  • Guest Operating System
  • Uses application code as is


  • Easy to set up


A Docker container though still a form of virtualization, does so differently than a virtual machine. Where virtual machines need their own OS to run an application and a hypervisor to allow the OS to use the physical machines resources, a Docker container does not need its own OS or a hypervisor. The difference can be seen in the diagram below.

The hypervisor and the guest OS’ that can be found in a system that uses virtual machines are no longer present when using Docker. This still works because Docker takes stand-alone, executable package of the software and running it on the hosts OS.

This stand-alone package is called a Docker image. The application in the Docker image still needs an OS to run, however instead of a Docker container having its own OS, the host OS isolates some of its resources and allocates them to the Docker container. To make sure that the application has the correct files it needs to run, a Docker image contains all of the code, runtime system tools, system libraries and settings. As far as the application is concerned it is running on its own machine and it has everything it needs.

Allowing the Docker container to use the physical machines resources is the reason that Docker containers no longer need their own OS and a hypervisor.

Running a Docker container consumes less resources than running a virtual machine. This means that on the same machine, it is possible to run more Docker containers than virtual machines. Using Docker containers also allows for better portability. To get a Docker image up and running on a docker container, you need only use Docker to start the container and you’re good to go. Sure you will have to do some set up, but compared to having to start by installing an OS on a virtual machine its little trouble.

Unique Attributes:

  • Uses application code in built Docker Images


  • Portable
  • Fast
  • Cheaper

Learn more about Docker here

Tuesday, 28 August 2018 08:13

A code integration and delivery (CI/CD) pipeline is essential to a software project.  Not only does it help us ship code faster but it also allows less room for mistakes. This tutorial will teach you how to set up a simple CI/CD pipeline for your GKE cluster with Codeship.  


We no longer had any time maintaining our self-hosted instance of Jenkins. Codeship works well for us because it has native Docker support. It has been extremely reliable, and their support is always quick to respond. As a bonus, adding Slack notifications is easy. It is also great for personal projects because it offers 100 free builds per month without requiring a credit card.


For this tutorial, we will be setting up a pipeline for a demo application that you can find at https://gitlab.com/neso-io/cat-api. It is a simple API that returns a random image of a cat.

At Neso, the build starts when a pull/merge request is accepted. On top of Codeship, we also make use of Gitlab CI so that we only merge the PR if the automated tests pass. We will be making something similar. A git push or PR merge to the master branch will trigger the chain of events in our pipeline.

Codeship will be responsible for running the automated tests, as well as building and pushing our Docker image to Google Container Registry. It then lets Kubernetes (k8s) know that there is a new image to be rolled out. At this point, we will let Kubernetes do its magic.

At Neso, a Kubernetes namespace serves as an environment. We'll be following that convention here. The master branch goes into our staging environment/namespace; git tags matching the Semantic Versioning Specification will go into our production environment/namespace.

environment namespace


Similar to other managed CI/CD platforms, a bit of configuration is required.  At the time of writing, Codeship only supports Github, Bitbucket, and Gitlab. When you add a new project, you'll need to pick where you host your code between the three.  I've selected Gitlab for this tutorial since that's where the code is on.

When you're done selecting your SCM provider, it's time to connect Codeship to your repository. You'll need raised privileges in your repository before you can add it to Codeship.

select scm

After you add your project on Codeship, you'll need a few files to get started:

  1. codeship-services.yml - Contains the defined Docker images required for the pipeline

  1. codeship-steps.yml - Configuration for the commands that will be executed in the Docker images specified in the services configuration

  1. codeship.aes - Every project on Codeship Pro has an AES key found in the General Project Settings. Go ahead and download it to the project's root directory. You'll need this key to encrypt the credentials required to access resources in your project on Google Cloud Platform (GCP).

  1. secrets.env - This is a plain text file that contains GCP service account credentials, GPC project ID and etc. Everything specified here will be accessible via environment variables. More on this in next section.

  1. secrets.env.encrypted - This is the encrypted version of the secrets.env file. This is the encrypted version of the secrets.env file which I will show you how to generate later.

Only the files codeship.aes and secrets.env shouldn't be committed so go ahead and add those to your .gitignore file.


We'll be setting up Codeship to run the unit tests and build the Docker image. The built Docker image will need to be pushed somewhere that our Kubernetes cluster has access to. There are many registries available like Gitlab and DockerHub. Since we're already on GKE, we might as well use Google Container Registry (GCR).  

GCR isn't just going to allow anyone to push to the registry unless they're authenticated. For that, we're going to need to create a service account on GCP. The service account will need the roles:

  1. Storage Admin - required for read-write access to GRC
  2. Kubernetes Engine Admin - required for read-write access to GKE

Download the JSON and add it to the secrets.env file. For a more detailed guide for encrypting sensitive data Codeship provides excellent documentation for this at https://documentation.codeship.com/pro/builds-and-configuration/environment-variables/#encrypted-environment-variables.

Before encryption, my secrets file looked like:

encryption google


I mentioned earlier that every project on Codeship is provided with an AES key that will be used for encrypting anything sensitive that will be needed in our pipeline. The key can be found in the project's General Project Settings page.

aes key

If you haven't already downloaded the project's AES key go ahead and add it to the root of the project as codeship.aes.

codeship aes

Before you can encrypt the secrets file, you'll need to install a CLI tool created by Codeship called Jet.  Jet CLI is a valuable tool that can not only encrypt the secrets files, but it can help you test your Codeship configuration by being able to run the steps on your machine .

Once you've installed Jet we'll be able to encrypt the secrets file with a single command:

$ jet encrypt secrets.env secrets.env.encrypted

There should now be a file named secrets.env.encrypted containing the encrypted contents of the secrets.env file. With all of that done, we can now start designing our pipeline.

STEP 1 - Test runner

Automated tests are a requirement in Code Integration. For our test runner, we'll use Neso's image with PHP and composer built in. Before the build starts, Codeship is going to pull the code from the repo. We will need to mount this code to the image used in our test runner.

This image doesn't need too much set up on our end so we'll only need to run two commands for our test runner: installing the dependencies and running the test itself.

This should execute no matter what branch we push to.

STEP 2 - Docker Image builder

We won't have anything to run in our cluster without a Docker image. The next step we'll define is how to build the Docker image. Thankfully as I mentioned earlier, Codeship Pro has native Docker support. One important thing to take note of is the name of the image. If you're using Google Container Registry like us, then you'll need to follow the convention for the image's tag: [HOSTNAME]/[PROJECT-ID]/[IMAGE].

The step is pretty straightforward too. We only need to reference the name of the service we defined.

The Docker image should only be built when we push to the master branch as indicated by the tag field.

STEP 3 - Push the image to the registry

Before we push the Docker image, we have to decrypt the sensitive data needed to be authenticated. Fortunately, Codeship has a Docker image for that; it will only need to know the name of the encrypted file.

The image that will be pushed will be having two tags: the branch that triggered the build (in this case: master) and the first 8 characters of the git commit hash.

The image last tagged as master will serve as the release candidate in this pipeline. The tag for the commit hash will be used when we update the image to run in our k8s deployment.

STEP 4 - Deploying the image

The final step in our pipeline will be rolling out the new Docker image. We need Google Cloud SDK for this. Since it involves a couple of commands, we'll write a bash script.

The command in line 5 is where the authentication happens using the Google service account JSON we encrypted. That is the only part of the script that is Codeship specific. You probably remember running something similar to the commands in lines 8 to 11 after you installed the Gcloud SDK on your machine. The one on line 11 allows you to run kubectl commands from your terminal. Lines 13 to 17 allow us to specify which environment to roll out the image to. If we change anything in the k8s manifest, Line 21 applies that change as part of the pipeline.

The last bit of code in the script sets the image to be rolled out to the environment or k8s namespace. For the staging environment, we'll be using the Docker image tagged with the current commit hash. The only tags matching the Semantic Versioning specification will be rolled out to the production environment.

Since we'll need to authenticate Codeship to be able to execute commands on our k8s cluster, a Docker image with Gcloud SDK will be required. Codeship is there to save us once again with their own Image built for google cloud deployments. Same as the image we used for authenticating, we’ll pass the file name with the encrypted data to it.

The image will need a copy of the deploy script so let’s mount our code to it (lines 6 to 7). Now the only thing left to do is to execute the script.

This step will only execute during a push or PR merge to the master branch. The command tells it to roll out the image to the staging environment.

When we trigger the build, we should get all green.

run test suite green

STEP 5 - Promote the master image for release

At Neso, we create a Git tag following the Semantic Versioning specification from the staging branch to trigger a release. We can copy that procedure by adding a service using the Docker image we tagged as master.

The idea here is that Codeship will pull the Image we last tagged as master, tag it again with the semver tag and push it to the registry.

The last step in our pipeline is rolling out the image to production.

This looks pretty much the same as the step for deploying to staging except it will only be triggered with a semver tag. It should still run the test suite then promote and roll out the Docker image from the staging environment.

Now, if I tag the master branch as 1.0.0. It our pipeline look like:

run test suite released


Congratulations! You now have a simple CI/CD pipeline. There's still plenty of room for improving this pipeline: like having the test suite and building of the Image concurrently. But I think that will serve best as an exercise for you, dear reader.


Wednesday, 26 September 2018 13:11

We desided for a number of business reasons to move one of our existing Kubernetes cluster new a geographic region.

The thing we were most worried about is that we had persistent volumes attached to MySQL instances for our test environments running in k8s.

There isn’t a straightforward way for this. One common way is to create a snapshot of etcd but we’re on GKE so that’s out of the question. Luckily we found Ark.

Ark is a disaster recovery tool for Kubernetes clusters. It can take backups of the whole cluster with the ability to restore it using a single command. We can even have it run on a schedule. Persistent volumes are also taken care of. It has good documentation so setting it up was almost a breeze if not because of a bug with RBAC in GKE.


A simple git clone This email address is being protected from spambots. You need JavaScript enabled to view it.:heptio/ark.git was all I did to download Ark. Its master branch is frequently updated and is not stable. The maintainers recommend checking out the latest tagged version. At this time, the latest release is v0.9.5.

Setting it up

Ark works by creating custom resources in k8s for its operations conveniently defined in a single yaml file.
I had to kubectl apply the yaml file to the American cluster and the shiny new European cluster where we’re moving into.

This is where the RBAC bug on GKE appears:
User "This email address is being protected from spambots. You need JavaScript enabled to view it." cannot create clusterrolebindings.rbac.authorization.k8s.io at the cluster scope: No policy matched.

To work around this I had to have my Google account granted with the cluster-admin role in both clusters:
kubectl create clusterrolebinding paul-cluster-admin-binding --clusterrole=cluster-admin --user=This email address is being protected from spambots. You need JavaScript enabled to view it.

Ironically, it spits out the same error unless you’re an account with the Owner IAM Role.

Apparently, this is a known issue on GKE:

Because of the way Container Engine checks permissions when you create a Role or ClusterRole, you must first create a RoleBinding that grants you all of the permissions included in the role you want to create. An example workaround is to create a RoleBinding that gives your Google identity a cluster-admin role before attempting to create additional Role or ClusterRole permissions. This is a known issue in the Beta release of Role-Based Access Control in Kubernetes and Container Engine version 1.6.

Cloud Storage Bucket

Apart from persistent volumes, Ark stores its backups in a cloud storage bucket. This bucket should be exclusive to Ark because each backup is stored in its own subdirectory in the bucket’s root. A service account will be needed to authorize Ark to upload files into the bucket.

Service account

I created a service account just for Ark to use. It will need read and write access to the bucket. In GKE, persistent volumes are just disks attached to the nodes so I had to give it permissions for those too. These are permissions given to the service account:


The Ark server config

At this point the bucket has been created and Ark has been allowed upload to it. Now it will need to know which bucket to use by setting the Ark Config (a custom resource defined by Ark):

# examples/gcp/00-ark-config.yaml
  name: gcp
bucket: neso-cluster-backup

The Ark server Deployment

To hand off the service account to Ark a k8s secret named cloud-credentials containing the service account key will have to be created.

# download service account key
gcloud iam service-accounts keys create ark-svc-account \
     --iam-account $SERVICE_ACCOUNT_EMAIL

# create secret
kubectl create secret generic cloud-credentials \
    --namespace heptio-ark \
    --from-file cloud=ark-svc-account

In the Ark Deployment yaml file, there wasn’t anything that needed to be changed. All that’s left to start the server is to kubectl apply the Config and the Deployment.

Generating a backup

After everything’s been set up on both clusters and the Ark client install locally. It’s time to put Ark to the test. Making sure kubectl's context was set to the US cluster, with fingers crossed we generated the backup:

$ ark backup create us-cluster --exclude-namespaces kube-system,kube-public,heptio-ark

Gave it a few minutes and then:

$ ark backup get
NAME                           STATUS      CREATED                         EXPIRES   SELECTOR
us-cluster                     Completed   2018-09-21 15:59:35 +0800 +08   30d       <none>

Restoring the backup

The backup includes all the resources from pods to ingresses. We wanted to keep the IP addresses we used in the old cluster. To free up the IP addresses, down go the ingresses in the old cluster.

Now setting the kubectl context to the new cluster in Europe. It took a while for the cluster to see the backup but it did appear eventually:

$ ark backup get
NAME                           STATUS      CREATED                         EXPIRES   SELECTOR
us-cluster                     Completed   2018-09-21 15:59:35 +0800 +08   30d       <none>

$ ark restore create --from-backup us-cluster

Ark was able to restore everything except for the persistent volumes. Our applications could not connect to the databases. Taking a closer look, it appears that Ark created the disks but they were in the region where the backups were created. The maintainers are aware of this issue and added the fix for this in V0.10.0.

We can’t wait for that release, though. We had no choice but to move the databases out of k8s. We ultimately decided to spin up a CloudSQL instance and stick our test environments’ databases there.


Ark is an awesome tool. Although the migration did not go as smooth as it should have, some good came out of it. It forced us to move our database outside of kubernetes which we shouldn’t be doing in the first place. Also, we now have regular backups of our new cluster.