Service discovery and distributed configuration with Amazon Elastic Kubernetes Service (EKS)

Service discovery and distributed configuration in a Micronaut application with Amazon Elastic Kubernetes Service (EKS)

Authors: Nemanja Mikic, Nemanja Mikic

Micronaut Version: 4.6.3

1. Getting Started

In this guide, we will deploy three microservices on the Amazon Elastic Kubernetes Service (EKS). We will use Kubernetes Service discovery and Distributed configuration to wire up our microservices.

You will discover how the Micronaut framework eases Kubernetes integration and deployment to ECR.

2. What you will need

To complete this guide, you will need the following:

Some of the following commands use jq

jq is a lightweight and flexible command-line JSON processor

3. The Application

Download the complete solution of the Kubernetes and the Micronaut Framework guide. You will use same three microservices (users, orders, and api) as a starting point.

4. Prepare and Deploy Microservices

We will define some environment variables to make deploying process easier. In AWS_ACCOUNT_ID store your AWS account id and in the AWS_REGION variable store your AWS region, both you can find by executing commands in aws cli. We will store our EKS cluster name in EKS_CLUSTER_NAME. For this guide we will use micronaut-k8s.

export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query \"Account\" --output text)"
export AWS_REGION="$(aws configure get region)"
export EKS_CLUSTER_NAME="micronaut-k8s"

Run the next command to log in to Amazon Elastic Container Registry (Amazon ECR):

aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com

4.1. Users Microservice

Edit k8s.yml inside users service.

/users/k8s.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: micronaut-k8s
  name: "users"
spec:
  selector:
    matchLabels:
      app: "users"
  template:
    metadata:
      labels:
        app: "users"
    spec:
      serviceAccountName: micronaut-service
      containers:
        - name: "users"
          image: <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com/users:latest (1)
          imagePullPolicy: Always (2)
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health/readiness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
          livenessProbe:
            httpGet:
              path: /health/liveness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
            failureThreshold: 10
---
apiVersion: v1
kind: Service
metadata:
  namespace: micronaut-k8s
  name: "users"
spec:
  selector:
    app: "users"
  type: NodePort
  ports:
    - protocol: "TCP"
      port: 8080
1 Repository URI that we created in ECR container registry. Change the <aws-region> to your region and change the <aws-account-id> to your aws account id. You can achieve this by running sed -i'' -e "s/<aws-region>/$AWS_REGION/" users/k8s.yml and sed -i'' -e "s/<aws-account-id>/$AWS_ACCOUNT_ID/" users/k8s.yml
2 Change imagePullPolicy to Always Create container repository in your AWS account.
export USERS_REPOSITORY=$(aws ecr create-repository --repository-name users --image-scanning-configuration scanOnPush=true --region us-east-1 --output json | jq -r .repository.repositoryUri)

Build a docker image of the users service with the name users.

Ensure that Docker images are constructed for the correct CPU architecture. For instance, if you’re utilizing Apple Silicon (aarch64), you can consider modifying the DOCKER_DEFAULT_PLATFORM environment variable to the value linux/amd64. Alternatively, you have the option to use ARM (arch64) instances within your Kubernetes cluster.

Tag an existing users microservice image.

docker tag users:latest ${USERS_REPOSITORY}:latest

Push tagged users microservice image to remote repository.

docker push ${USERS_REPOSITORY}:latest

4.2. Orders Microservice

Edit k8s.yml inside orders service.

/orders/k8s.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: micronaut-k8s
  name: "orders"
spec:
  selector:
    matchLabels:
      app: "orders"
  template:
    metadata:
      labels:
        app: "orders"
    spec:
      serviceAccountName: micronaut-service
      containers:
        - name: "orders"
          image: <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com/orders:latest (1)
          imagePullPolicy: Always (2)
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health/readiness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
          livenessProbe:
            httpGet:
              path: /health/liveness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
            failureThreshold: 10
---
apiVersion: v1
kind: Service
metadata:
  namespace: micronaut-k8s
  name: "orders"
spec:
  selector:
    app: "orders"
  type: NodePort
  ports:
    - protocol: "TCP"
      port: 8080
1 Repository URI that we created in ECR container registry. Change the <aws-region> to your region and change the <aws-account-id> to your aws account id. You can achieve this by running sed -i'' -e "s/<aws-region>/$AWS_REGION/" orders/k8s.yml and sed -i'' -e "s/<aws-account-id>/$AWS_ACCOUNT_ID/" orders/k8s.yml
2 Change imagePullPolicy to Always Create container repository in your AWS account.
export ORDERS_REPOSITORY=$(aws ecr create-repository --repository-name orders --image-scanning-configuration scanOnPush=true --region us-east-1 --output json | jq -r .repository.repositoryUri)

Build a docker image of the orders service with the name orders.

Ensure that Docker images are constructed for the correct CPU architecture. For instance, if you’re utilizing Apple Silicon (aarch64), you can consider modifying the DOCKER_DEFAULT_PLATFORM environment variable to the value linux/amd64. Alternatively, you have the option to use ARM (arch64) instances within your Kubernetes cluster.

Tag an existing orders microservice image.

docker tag orders:latest ${ORDERS_REPOSITORY}:latest

Push tagged orders microservice image to remote repository.

docker push ${ORDERS_REPOSITORY}:latest

4.3. API Microservice

Edit k8s.yml inside api service.

/api/k8s.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: micronaut-k8s
  name: "api"
spec:
  selector:
    matchLabels:
      app: "api"
  template:
    metadata:
      labels:
        app: "api"
    spec:
      serviceAccountName: micronaut-service
      containers:
        - name: "api"
          image: <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com/api:latest (1)
          imagePullPolicy: Always (2)
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health/readiness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
          livenessProbe:
            httpGet:
              path: /health/liveness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 3
            failureThreshold: 10
---
apiVersion: v1
kind: Service
metadata:
  namespace: micronaut-k8s
  name: "api"
spec:
  selector:
    app: "api"
  type: LoadBalancer
  ports:
    - protocol: "TCP"
      port: 8080
1 Repository URI that we created in ECR container registry. Change the <aws-region> to your region and change the <aws-account-id> to your aws account id. You can achieve this by running sed -i'' -e "s/<aws-region>/$AWS_REGION/" api/k8s.yml and sed -i'' -e "s/<aws-account-id>/$AWS_ACCOUNT_ID/" api/k8s.yml
2 Change imagePullPolicy to Always Create container repository in your AWS account.
export API_REPOSITORY=$(aws ecr create-repository --repository-name api --image-scanning-configuration scanOnPush=true --region us-east-1 --output json | jq -r .repository.repositoryUri)

Build a docker image of the api service with the name api.

Ensure that Docker images are constructed for the correct CPU architecture. For instance, if you’re utilizing Apple Silicon (aarch64), you can consider modifying the DOCKER_DEFAULT_PLATFORM environment variable to the value linux/amd64. Alternatively, you have the option to use ARM (arch64) instances within your Kubernetes cluster.

Tag an existing api microservice image.

docker tag api:latest ${API_REPOSITORY}:latest

Push tagged api microservice image to remote repository.

docker push ${API_REPOSITORY}:latest

4.4. Deploy Services to EKS

Create a directory for kubectl configuration.

mkdir -p $HOME/.kube

Generate kubectl configuration for authentication to EKS.

aws eks update-kubeconfig --region $AWS_REGION --name $EKS_CLUSTER_NAME

Set KUBECONFIG to the created config file. This variable is consumed by kubectl.

export KUBECONFIG=$HOME/.kube/config

Deploy the auth.yml file that we created in the Kubernetes and the Micronaut Framework guide.

kubectl apply -f auth.yml

Run the next command to deploy for users microservice:

kubectl apply -f users/k8s.yml

Run the next command to deploy for orders microservice:

kubectl apply -f orders/k8s.yml

Run the next command to deploy for api microservice:

kubectl apply -f api/k8s.yml

5. Test integration between applications deployed on EKS

Run the next command to check status of the pods and make sure that all of them have the status "Running":

kubectl get pods -n=micronaut-k8s
NAME                      READY   STATUS    RESTARTS   AGE
api-6fb4cd949f-kxxx8      1/1     Running   0          2d1h
orders-595887ddd6-6lzp4   1/1     Running   0          2d1h
users-df6f78cd7-lgnzx     1/1     Running   0          2d1h

Run the next command to check the status of the microservices:

kubectl get services -n=micronaut-k8s
NAME     TYPE           CLUSTER-IP       EXTERNAL-IP                                                               PORT(S)          AGE
api      LoadBalancer   10.100.208.154   <redacted>   8080:31171/TCP   30s
orders   NodePort       10.100.157.155   <none>                                                                    8080:30742/TCP   20m
users    NodePort       10.100.126.97    <none>                                                                    8080:31580/TCP   20m
If EXTERNAL-IP is in <pending> state wait a couple of seconds and then run command again. AWS will provide you a hostname instead of ip address. To access your application you will have to wait that DNS can resolve hostname that was created.

Run the next command to retrieve the URL of the api microservice:

export API_URL=http://$(kubectl get svc api -n=micronaut-k8s -o json | jq -r '.status.loadBalancer.ingress[0].hostname'):8080

Run a cURL command to create a new user via the api microservice:

curl -X "POST" "$API_URL/api/users" -H 'Content-Type: application/json; charset=utf-8' -d '{ "first_name": "Nemanja", "last_name": "Mikic", "username": "nmikic" }'
{"id":1,"username":"nmikic","first_name":"Nemanja","last_name":"Mikic"}

Run a cURL command to a new order via the api microservice:

curl -X "POST" "$API_URL/api/orders" -H 'Content-Type: application/json; charset=utf-8' -d '{ "user_id": 1, "item_ids": [1,2] }'
{"id":1,"user":{"first_name":"Nemanja","last_name":"Mikic","id":1,"username":"nmikic"},"items":[{"id":1,"name":"Banana","price":1.5},{"id":2,"name":"Kiwi","price":2.5}],"total":4.0}

Run a cURL command to list created orders:

curl "$API_URL/api/orders" -H 'Content-Type: application/json; charset=utf-8'
[{"id":1,"user":{"first_name":"Nemanja","last_name":"Mikic","id":1,"username":"nmikic"},"items":[{"id":1,"name":"Banana","price":1.5},{"id":2,"name":"Kiwi","price":2.5}],"total":4.0}]

We can try to place an order for a user who doesn’t exist (with id 100). Run a cURL command:

curl -X "POST" "$API_URL/api/orders" -H 'Content-Type: application/json; charset=utf-8' -d '{ "user_id": 100, "item_ids": [1,2] }'
{"message":"Bad Request","_links":{"self":[{"href":"/api/orders","templated":false}]},"_embedded":{"errors":[{"message":"User with id 100 doesn't exist"}]}}

6. Cleaning Up

To delete all resources that were created in this guide run next command.

kubectl delete namespaces micronaut-k8s

Run the next command to delete micronaut-k8s/users artifacts container repository:

aws ecr delete-repository --repository-name users --force

Run the next command to delete micronaut-k8s/orders artifacts container repository:

aws ecr delete-repository --repository-name orders --force

Run the next command to delete micronaut-k8s/api artifacts container repository:

aws ecr delete-repository --repository-name api --force

NOTE: If you were following the Getting started with Amazon EKS – AWS Management Console and AWS CLI and you want to clean up everything, don’t forget to follow Step 5: Delete resources chapter.

7. Next Steps

Explore more features with Micronaut Guides.

Read more about Micronaut Kubernetes module.

8. License

All guides are released with an Apache license 2.0 license for the code and a Creative Commons Attribution 4.0 license for the writing and media (images…​).