Setting up EFS on EKS

The following is part of a series of posts called "Building a complete Kubernetes backed infrastructure".

This series of posts describes my approach to standing up a scalable infrastructure for hosting both internal and external software in a company setting.

This series focuses on AWS specifically as I have found EKS to be the most complicated Kubernetes provider to set up, but the principles and workflow should be easily applied to any other provider or on-prem situation.

If you’ve used Kubernetes for a least a little while you’ll be aware of the issues that can be caused when using PersistentVolumes that only support ReadWriteOnce claims and not ReadWriteMany.

With a ReadWriteOnce claim, only one pod can be assigned a given volume claim and have it mounted at any given time.

This might not sound like an issue if you’re only planning to run a single replica of a service, and not scale horizontally, which is often the case especially for personal projects, but there are a number of unseen issues in this case.

One of the biggest problem with ReadWriteOnce volume claims is when you are using a Deployment and you want to do rollouts. A rollout will typically create a new ReplicationController, which will create the require Pod, check things have started correctly, then switch traffic over to the new Pod before deleting the old ReplicationController.

The issue here is the new Pod will not be able to start as it will not be able to mount the PersistentVolumeClaim which will currently be bound to the exiting Pod. Basically, ReadWriteOnce stops you from doing Deployment based roll-outs and introduce the need for service downtime as the existing Pod must be removed before starting the new Pod.

To get around this, instead of using the default gp2 storage class which is made available when creating the cluster, a new storage class can be created which is backed by AWS EFS. EFS is basically a NFS offering with a different name.

Using EFS/NFS allows multiple pods to access a given PersistentVolumeClaim at the same time, avoiding the issue outlined previously.

To set up, I used this repo. I’ve found that there is a lot of conflicting information regarding getting EFS working in EKS, but this was the best source to follow. It is installed via the following:

kubectl apply -k "github.com/kubernetes-sigs/aws-efs-csi-driver/deploy/kubernetes/overlays/stable/?ref=release-1.0"

You’ll also need to create a new EFS drive. It is pretty easy to create a new EFS volume in the AWS console and you can just select the defaults when creating. You also need to create a new security policy to allow your EC2 instances within your Kubernetes cluster to access EFS. To do this, go to the EC2 dashboard in the AWS console and navigate to the Security Groups section. From here create a New Security Group. Add a name and description for the policy and ensure that you select the VPC of your cluster. Add an inbound rule with the type of NFS and for the source. In my case I added 172.31.0.0/16 allowing anything from the shared VPC (you can check this within your VPC, it is just what is listed as the IPv4 CIDR).

Adding this security policy to the EFS drive you just created is done via the Network tab in the EFS view. ‘Manage’ the mount targets which were automatically created and change the security policy to the one you just created. Without this, the EFS mounts will fail with some very unhelpful error messages, so if things don’t work, this might be the first thing that you check is set up correctly.

Once complete, take a note of the file system id for the new drive which is in the form of fs-XXXXXXX. This is required in the Kubernetes configuration.

Next, it is a case of creating a new storage class for EFS, which can be done by applying a configuration such as the following.

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: aws-efs
provisioner: efs.csi.aws.com

After the storage class is available the next step is to create a PersistentVolume using the filesystem id of the EFS volume that was just created, like the below:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: VOLUME_NAME
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: aws-efs
  claimRef:
    name: VOLUME_PVC
  csi:
    driver: efs.csi.aws.com
    volumeHandle: FILE_SYSTEM_ID

After running the above you should now have a provisioned PersistentVolume. To claim that volume, need to create a PersistentVolumeClaim like the below.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: VOLUME_PVC
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: aws-efs
  resources:
    requests:
      storage: 1Gi

Any number of pods can now use this PersistentVolumeClaim and mount it as they want. Apart from the benefit explained above of now being able to do Deployment rollouts, there is also another great benefit that different services can share a common file system. This is obviously an anti-pattern if not done correctly, as services should be built around a well designed and defined API but if done with limited scope with proper guardrails this can be beneficial.

One such example is a Hadoop / MapReduce style pattern where different services can take directories as input and output into other directories for other services to pick up. On a previous project this worked fantastic, where we had a good number of integrations with external parties that would extract data and store it within a directory in EFS, another worker service would pick up these files and transform them into a number of outputs, which in turn was processed by dedicated loading services. This workflow made things much more scalable than doing a full ETL system for each integration.

The efs driver does have the ability as well to load an EFS directory as the mount as well. This can be leveraged so that you only need one EFS instance and then each service can have it’s own directory on the one EFS. This is done via the volumeHandle property and will look something like fs-xxxxxxxx:/service-name, but beware, if that directory doesn’t exist on the EFS when you try to mount it the mount will fail. You will need to create these directories on the EFS volume manually prior to using them.

Posted in: