Deploying a Simple IRIS-Based Web Application Using Amazon EKS
Tags: docker, eks, intersystems, circleci
Published: 2020-08-04 07:12:42

We’re going to deploy a [simple IRIS application](https://openexchange.intersystems.com/package/objectscript-rest-docker-template) to Amazon Web Services using its Elastic Kubernetes Service ([EKS](https://docs.aws.amazon.com/eks/index.html)).

Please, fork the [IRIS project](https://github.com/intersystems-community/objectscript-rest-docker-template) to your own private repository. It’s called \<username\>/my-objectscript-rest-docker-template in this article. \<root_repo_dir\> is its root directory.

Before getting started, install the [AWS command-line tool](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) and, for Kubernetes cluster creation, [eksctl](https://eksctl.io/introduction/#installation), a simple CLI utility. If you choose aws2, you’ll need to set aws2 usage in kube config file as described [here](https://github.com/weaveworks/eksctl/issues/1562).

#### AWS EKS
Like AWS resources [in general](https://aws.amazon.com/pricing/), EKS is [not free](https://aws.amazon.com/eks/pricing/). But you can create a [free-tier account](https://aws.amazon.com/free/) to play with AWS features. Keep in mind, though, that not everything you want to play with is included in the free tier. So, to manage your current budget and understand the financial issues, read [What is AWS Billing and Cost Management?](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-what-is.html) and [Control your AWS costs](https://aws.amazon.com/getting-started/tutorials/control-your-costs-free-tier-budgets/?trk=gs_card).

We’ll assume you already have an AWS account and root access to it, and that you don’t use this root access but have created a user with admin permissions. You’ll need to put the access key and secret key of this user into the AWS credentials file under the [dev] profile (or whatever you choose to name the profile):

```
$ cat ~/.aws/credentials
[dev]
aws_access_key_id = ABCDEFGHIJKLMNOPQRST
aws_secret_access_key = 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234
```
We’re going to create resources in the AWS "eu-west-1" region, but you should choose the [region](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) closest to your location and replace “eu-west-1” by your region everywhere below in the text.

By the way, all needed files (.circleci/, eks/, k8s/) are also stored [here](https://github.com/intersystems-community/eks-circleci-objectscript-rest-docker-template) to simplify copying and pasting.

All required EKS resources will be created from scratch. You’ll find the [Amazon EKS Workshop](https://eksworkshop.com/) to be a good resource to get an initial impression.

Now let’s check our access to AWS (we’ve used a dummy account here):

```
$ export AWS_PROFILE=dev

$ aws sts get-caller-identity
{
  "Account": "012345678910",
  "UserId": " ABCDEFGHIJKLMNOPQRSTU",
  "Arn": "arn:aws:iam::012345678910:user/FirstName.LastName"
}

$ eksctl version
[ℹ] version.Info{BuiltAt:"", GitCommit:"", GitTag:"0.10.2"}
```
We could run *"eksctl create cluster <cluster_name> --region eu-west-1"* now, relying on the fact that all the default settings are good for us, or we can manage our own settings by creating a configuration file and using it.

The latter is preferable because it allows you to store such a file in a version control system (VCS). Examples of configurations can be found [here](https://github.com/weaveworks/eksctl/tree/master/examples). After reading about the different settings [here](https://eksctl.io/usage/schema/), let’s try to create our own configuration):

```
$ mkdir <root_repo_dir>/eks; cd <root_repo_dir>/eks

$ cat cluster.yaml

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: dev-cluster
  region: eu-west-1
  version: '1.14'

vpc:
  cidr: 10.42.0.0/16
  nat:
    gateway: Single
  clusterEndpoints:
    publicAccess: true
    privateAccess: false

nodeGroups:
  - name: ng-1
    amiFamily: AmazonLinux2
    ami: ami-059c6874350e63ca9  # AMI is specific for a region
    instanceType: t2.medium
    desiredCapacity: 1
    minSize: 1
    maxSize: 1

    # Worker nodes won't have an access FROM the Internet
    # But have an access TO the Internet through NAT-gateway
    privateNetworking: true

    # We don't need to SSH to nodes for demo
    ssh:
      allow: false

    # Labels are Kubernetes labels, shown when 'kubectl get nodes --show-labels'
    labels:
      role: eks-demo
    # Tags are AWS tags, shown in 'Tags' tab on AWS console'
    tags:
      role: eks-demo

# CloudWatch logging is disabled by default to save money
# Mentioned here just to show a way to manage it
#cloudWatch: 
#  clusterLogging:
#    enableTypes: []
```

Note that *"nodeGroups.desiredCapacity = 1"* would make no sense in a production environment, but it’s fine for our demo.
Also note that AMI images could differ between regions. Look for "amazon-eks-node-1.14" and choose one of the latest:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/su7o7u9cinrt7x99bkjm.png)

Now let’s create the cluster - the control plane and worker nodes:

```
$ eksctl create cluster -f cluster.yaml
```

By the way, when you no longer need a cluster, you can use the following to delete it:

```
$ eksctl delete cluster --name dev-cluster --region eu-west-1 --wait
```
Creating a cluster takes about 15 minutes. During this time you can look at the eksctl output:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/s7b1axlhxe17g8hrsi0h.png)

You can also view the [CloudFormation console](https://console.aws.amazon.com/cloudformation), which will have two stacks. You can drill down into each one and look at the Resources tab to see exactly what will be created, and at the Events tab to check the current state of the resources creation.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2dunqfw3imxjqygg398q.png)

The cluster was successfully created, although you can see in the eksctl output that we had difficulties connecting to it: "unable to use kubectl with the EKS cluster".
Let's fix this by installing the [aws-iam-authenticator](https://docs.aws.amazon.com/eks/latest/userguide/install-aws-iam-authenticator.html) and using it to create a kube context:

```
$ which aws-iam-authenticator
/usr/local/bin/aws-iam-authenticator
 
$ aws eks update-kubeconfig --name dev-cluster --region eu-west-1

$ kubectl get nodes
NAME                                       STATUS  ROLES   AGE   VERSION                                                                              
ip-10-42-140-98.eu-west-1.compute.internal Ready   <none>   1m    v1.14.7-eks-1861c5
```

It should work now, but we created a cluster with a user who has administrator rights. For the regular deployment process from CircleCI, it’s better to [create a special AWS user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html), named, in this case, CircleCI, with only programmatic access and the following policies attached:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jv85xaq0kilm9wjiwoha.png)

The first policy is built into AWS, so you only need to select it. The second one should be created on your own. [Here](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html#access_policies_create-json-editor) is a creation process description. The policy *"AmazonEKSDescribePolicy"* should look like:

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "eks:DescribeCluster",
                "eks:ListClusters"
            ],
            "Resource": "*"
        }
    ]
}
```
After user creation, save the user’s access key and secret access key — we’ll need them soon.

We also want to give this user rights within the Kubernetes cluster itself, as described in [How do I provide access to other users and roles after cluster creation in Amazon EKS?](https://aws.amazon.com/premiumsupport/knowledge-center/amazon-eks-cluster-access/). In short, after creating the EKS cluster, only the IAM user, creator, has access to it. To add our CircleCI user, we need to replace default empty "mapUsers" section in the cluster AWS authentication settings (configmap aws-auth, 'data' section) by the following lines using [kubectl edit](https://github.com/fabric8io/kansible/blob/master/vendor/k8s.io/kubernetes/docs/user-guide/kubectl/kubectl_edit.md) (use your own Account Id instead of ‘01234567890’):

```
$ kubectl -n kube-system edit configmap aws-auth
...
data:
...
  mapUsers: |
    - userarn: arn:aws:iam::01234567890:user/CircleCI
      username: circle-ci
      groups:
        - system:masters
```

We’ll use the Kubernetes manifests from [this article](https://community.intersystems.com/post/deploying-intersystems-iris-solution-gcp-kubernetes-cluster-gke-using-circleci) (see the "Google Cloud Prerequisite" section) with one change: in the deployment image field we use placeholders. We’ll store those manifests in the \<root_repo_dir\>/k8s directory. Note that the deployment file was renamed to deployment.tpl:

```
$ cat <root_repo_dir>/k8s/deployment.tpl
...
spec:
containers:
- image: DOCKER_REPO_NAME/iris-rest:DOCKER_IMAGE_TAG
...
```

#### CircleCI

The deployment process on the CircleCI side is similar to the [process used for GKE](https://community.intersystems.com/post/deploying-intersystems-iris-solution-gcp-kubernetes-cluster-gke-using-circleci):

* Pull the repository
* Build the Docker image
* Authenticate with Amazon Cloud
* Upload the image to Amazon Elastic Container Registry (ECR)
* Run the container based on this image in AWS EKS

We’ll take advantage of already created and tested CircleCI configuration templates: [orbs](https://circleci.com/docs/2.0/orb-intro/).

* [aws-ecr orb](https://circleci.com/orbs/registry/orb/circleci/aws-ecr) for building an image and pushing it to ECR
* [aws-eks orb](https://circleci.com/orbs/registry/orb/circleci/aws-eks) for AWS authentication
* [kubernetes orb](https://circleci.com/orbs/registry/orb/circleci/kubernetes) for Kubernetes manifests deployment

Our deployment configuration looks like this:

```
$ cat <root_repo_dir>/.circleci/config.yml
version: 2.1
orbs:
  aws-ecr: circleci/aws-ecr@6.5.0
  aws-eks: circleci/aws-eks@0.2.6
  kubernetes: circleci/kubernetes@0.10.1

jobs:
  deploy-application:
    executor: aws-eks/python3
    parameters:
      cluster-name:
        description: |
          Name of the EKS cluster
        type: string
      aws-region:
        description: |
          AWS region
        type: string
      account-url:
        description: |
          Docker AWS ECR repository url
        type: string
      tag:
        description: |
          Docker image tag
        type: string
    steps:
      - checkout
      - run:
          name: Replace placeholders with values in deployment template
          command: |
            cat k8s/deployment.tpl |\
            sed "s|DOCKER_REPO_NAME|<< parameters.account-url >>|" |\
            sed "s|DOCKER_IMAGE_TAG|<< parameters.tag >>|" > k8s/deployment.yaml; \
            cat k8s/deployment.yaml
      - aws-eks/update-kubeconfig-with-authenticator:
          cluster-name: << parameters.cluster-name >>
          install-kubectl: true
          aws-region: << parameters.aws-region >>
      - kubernetes/create-or-update-resource:
          action-type: apply
          resource-file-path: "k8s/namespace.yaml"
          show-kubectl-command: true
      - kubernetes/create-or-update-resource:
          action-type: apply
          resource-file-path: "k8s/deployment.yaml"
          show-kubectl-command: true
          get-rollout-status: true
          resource-name: deployment/iris-rest
          namespace: iris
      - kubernetes/create-or-update-resource:
          action-type: apply
          resource-file-path: "k8s/service.yaml"
          show-kubectl-command: true
          namespace: iris
workflows:
  main:
    jobs:
    - aws-ecr/build-and-push-image:
        aws-access-key-id: AWS_ACCESS_KEY_ID
        aws-secret-access-key: AWS_SECRET_ACCESS_KEY
        region: AWS_REGION
        account-url: AWS_ECR_ACCOUNT_URL
        repo: iris-rest
        create-repo: true
        dockerfile: Dockerfile-zpm
        path: .
        tag: ${CIRCLE_SHA1}
    - deploy-application:
        cluster-name: dev-cluster
        aws-region: eu-west-1
        account-url: ${AWS_ECR_ACCOUNT_URL}
        tag: ${CIRCLE_SHA1}
        requires:
          - aws-ecr/build-and-push-image

```

The [Workflows](https://circleci.com/docs/2.0/workflows/) section contains a list of jobs, each of which can be either called from an orb, such as [aws-ecr/build-and-push-image](https://circleci.com/orbs/registry/orb/circleci/aws-ecr#jobs-build-and-push-image), or defined directly in the configuration using "deploy-application". 

The following code means that the deploy-application job will be called only after the aws-ecr/build-and-push-image job finishes:

```
requires:
- aws-ecr/build-and-push-image
```

The Jobs section contains a description of the deploy-application job, with a list of steps defined, including:

* *checkout*, to pull from a Git repository
* *run*, to run a script that dynamically sets the Docker-image repository and tag
* *aws-eks/update-kubeconfig-with-authenticator*, which uses [aws-iam-authenticator](https://github.com/kubernetes-sigs/aws-iam-authenticator) to set up a connection to Kubernetes
* [kubernetes/create-or-update-resource](https://circleci.com/orbs/registry/orb/circleci/kubernetes#commands-create-or-update-resource), which is used several times as a way to run "kubectl apply" from CircleCI

We use variables and they, of course, should be defined in CircleCI on the "Environment variables" tab:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/hw1srecxuafw5m0txdeu.png)

The following table shows the meaning of the variables used:

| Variable | Meaning |
| --- | --- |
| AWS_ACCESS_KEY_ID |  Access key of CircleCI IAM user |
| AWS_SECRET_ACCESS_KEY | Secret key of CircleCI IAM user |
| AWS_REGION | *eu-west-1*, in this case |
| AWS_ECR_ACCOUNT_URL | URL of the [AWS ECR Docker Registry](https://docs.aws.amazon.com/AmazonECR/latest/userguide/Repositories.html), such as *01234567890.dkr.ecr.eu-west-1.amazonaws.com*, where ‘01234567890’ is the account ID |

Here’s how we trigger the deployment process:

```
$ git add .circleci/ eks/ k8s/
$ git commit -m “AWS EKS deployment”
$ git push
```

This will show the two jobs in this workflow:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dfywyrzazbbv01ut2epr.png)

Both jobs are clickable, and this allows you to see details of the steps taken. 
Deployment takes several minutes. Once it completes, we can check the status of the Kubernetes resources and of the IRIS application itself:

```
$ kubectl -n iris get pods -w    # Ctrl+C to stop

$ kubectl -n iris get service    
NAME       TYPE        CLUSTER-IP      EXTERNAL-IP              PORT(S)      AGE                                                                                                                                          
iris-rest LoadBalancer 172.20.190.211  a3de52988147a11eaaaff02ca6b647c2-663499201.eu-west-1.elb.amazonaws.com  52773:32573/TCP 15s
```

Allow several minutes to propagate the DNS-record. Until then you’ll receive a "Could not resolve host" error when running curl:

```
$ curl -XPOST -H "Content-Type: application/json" -u _system:SYS a3de52988147a11eaaaff02ca6b647c2-663499201.eu-west-1.elb.amazonaws.com:52773/person/ -d '{"Name":"John Dou"}'

$ curl -XGET -u _system:SYS a3de52988147a11eaaaff02ca6b647c2-663499201.eu-west-1.elb.amazonaws.com:52773/person/all
[{"Name":"John Dou"},]
```

#### Wrapping up

At first glance, deployment to AWS EKS looks more complex than to GKE, but it’s not really much different. If your organization uses AWS, you now know how to add Kubernetes to your stack.

Note that the EKS API was extended to support [managed groups](https://aws.amazon.com/blogs/containers/eks-managed-node-groups/). These allow you to deploy the control plane and the data plane as a whole, and they look promising. Moreover,  [Fargate](https://aws.amazon.com/blogs/aws/amazon-eks-on-aws-fargate-now-generally-available/), the AWS serverless compute engine for containers, is now available.

Finally, a quick note about AWS ECR: don’t forget to set up a [lifecycle policy](https://docs.aws.amazon.com/AmazonECR/latest/userguide/LifecyclePolicies.html) for your images.