Amazon Elastic Kubernetes Service (Amazon EKS) is a managed service that you can use to run Kubernetes on AWS without needing to install, operate, and maintain your own Kubernetes control plane or nodes. Kubernetes is an open-source system for automating the deployment, scaling, and management of containerized applications.
Amazon EKS:
- Runs and scales the Kubernetes control plane across multiple AWS Availability Zones to ensure high availability.
- Automatically scales control plane instances based on load, detects and replaces unhealthy control plane instances, and provides automated version updates and patching for them.
- Runs up-to-date versions of the open-source Kubernetes software, so you can use all of the existing plugins and tooling from the Kubernetes community.
Amazon EKS is integrated with many AWS services to provide scalability and security for your applications, including the following capabilities:
- Amazon ECR for container images
- Elastic Load Balancing for load distribution
- IAM for authentication
- Amazon VPC for isolation
Applications that are running on Amazon EKS are fully compatible with applications running on any standard Kubernetes environment, no matter whether they’re running in on-premises data centers or public clouds. This means that you can easily migrate any standard Kubernetes application to Amazon EKS without any code modification.
How does Amazon EKS work?
- Create an Amazon EKS cluster in the AWS Management Console or with the AWS CLI or one of the AWS SDKs.
- Launch managed or self-managed Amazon EC2 nodes, or deploy your workloads to AWS Fargate.
- When your cluster is ready, you can configure your favorite Kubernetes tools, such as kubectl, to communicate with your cluster.
- Deploy and manage workloads on your Amazon EKS cluster the same way that you would with any other Kubernetes environment. You can also view information about your workloads using the AWS Management Console.
Running the UnicornStore with Amazon EKS
Below you can find a visualization on how the UnicornStore application will be deployed and communicate with Amazon EKS:
Creating an Amazon EKS cluster
eksctl is a simple CLI tool for creating and managing clusters on EKS . It is written in Go, uses CloudFormation and was created by Weaveworks .
- Get the existing VPC and Subnet IDs to inform EKS where to create the new cluster
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Disable Temporary credentials in Cloud9
aws cloud9 update-environment --environment-id $C9_PID --managed-credentials-action DISABLE --region $AWS_REGION &> /dev/null
rm -vf ${HOME}/.aws/credentials &> /dev/null
export UNICORN_VPC_ID=$(aws cloudformation describe-stacks --stack-name UnicornStoreVpc --query 'Stacks[0].Outputs[?OutputKey==`idUnicornStoreVPC`].OutputValue' --output text)
export UNICORN_SUBNET_PRIVATE_1=$(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$UNICORN_VPC_ID" "Name=tag:Name,Values=UnicornStoreVpc/UnicornVpc/PrivateSubnet1" --query 'Subnets[0].SubnetId' --output text)
export UNICORN_SUBNET_PRIVATE_2=$(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$UNICORN_VPC_ID" "Name=tag:Name,Values=UnicornStoreVpc/UnicornVpc/PrivateSubnet2" --query 'Subnets[0].SubnetId' --output text)
export UNICORN_SUBNET_PUBLIC_1=$(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$UNICORN_VPC_ID" "Name=tag:Name,Values=UnicornStoreVpc/UnicornVpc/PublicSubnet1" --query 'Subnets[0].SubnetId' --output text)
export UNICORN_SUBNET_PUBLIC_2=$(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$UNICORN_VPC_ID" "Name=tag:Name,Values=UnicornStoreVpc/UnicornVpc/PublicSubnet2" --query 'Subnets[0].SubnetId' --output text)
aws ec2 create-tags --resources $UNICORN_SUBNET_PRIVATE_1 $UNICORN_SUBNET_PRIVATE_2 \
--tags Key=kubernetes.io/cluster/unicorn-store,Value=shared Key=kubernetes.io/role/internal-elb,Value=1
aws ec2 create-tags --resources $UNICORN_SUBNET_PUBLIC_1 $UNICORN_SUBNET_PUBLIC_2 \
--tags Key=kubernetes.io/cluster/unicorn-store,Value=shared Key=kubernetes.io/role/elb,Value=1
export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
export AWS_REGION=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region')
- Create the cluster with eksctl
1
2
3
4
5
6
7
8
cd ~/environment
eksctl create cluster \
--name unicorn-store \
--version 1.28 --region $AWS_REGION \
--nodegroup-name managed-node-group-x64 --managed --node-type m5.xlarge --nodes 2 --nodes-min 2 --nodes-max 4 \
--with-oidc --full-ecr-access --alb-ingress-access \
--vpc-private-subnets $UNICORN_SUBNET_PRIVATE_1,$UNICORN_SUBNET_PRIVATE_2 \
--vpc-public-subnets $UNICORN_SUBNET_PUBLIC_1,$UNICORN_SUBNET_PUBLIC_2
- Wait for the cluster creation
The creation of the cluster might take around 15 minutes.
- Add the workshop IAM roles to the list of the EKS cluster administrators to get access from the AWS Console.
1
2
3
4
5
6
7
8
9
10
11
eksctl create iamidentitymapping --cluster unicorn-store --region=$AWS_REGION \
--arn arn:aws:iam::$ACCOUNT_ID:role/WSParticipantRole --username admin --group system:masters \
--no-duplicate-arns
eksctl create iamidentitymapping --cluster unicorn-store --region=$AWS_REGION \
--arn arn:aws:iam::$ACCOUNT_ID:role/java-on-aws-workshop-user --username admin --group system:masters \
--no-duplicate-arns
eksctl create iamidentitymapping --cluster unicorn-store --region=$AWS_REGION \
--arn arn:aws:iam::$ACCOUNT_ID:role/java-on-aws-workshop-admin --username admin --group system:masters \
--no-duplicate-arns
- Get access to the cluster
1
2
aws eks --region $AWS_REGION update-kubeconfig --name unicorn-store
kubectl get nodes
Adding IAM permissions & service accounts
The Java application needs to push events to EventBridge, read parameters from Parameter Store and secrets from Secrets Manager. To achieve that and develop a secure application with the “Principle of least privilege” we need to create a Service Account and give it required permissions to access AWS services. We also want to create a Kubernetes namespace for the Java Application.
- Create a Kubernetes namespace for the application:
1
kubectl create namespace unicorn-store-spring
- Create an IAM-Policy with the proper permissions to publish to EventBridge, retrieve secrets & parameters and basic monitoring:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
cat <<EOF > service-account-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "xray:PutTraceSegments",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "events:PutEvents",
"Resource": "arn:aws:events:$AWS_REGION:$ACCOUNT_ID:event-bus/unicorns",
"Effect": "Allow"
},
{
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "$(aws cloudformation describe-stacks --stack-name UnicornStoreInfrastructure --query 'Stacks[0].Outputs[?OutputKey==`arnUnicornStoreDbSecret`].OutputValue' --output text)",
"Effect": "Allow"
},
{
"Action": [
"ssm:DescribeParameters",
"ssm:GetParameters",
"ssm:GetParameter",
"ssm:GetParameterHistory"
],
"Resource": "arn:aws:ssm:$AWS_REGION:$ACCOUNT_ID:parameter/databaseJDBCConnectionString",
"Effect": "Allow"
}
]
}
EOF
aws iam create-policy --policy-name unicorn-eks-service-account-policy --policy-document file://service-account-policy.json
- Create a Kubernetes Service Account with a reference to the previous created IAM policy:
1
2
3
eksctl create iamserviceaccount --cluster=unicorn-store --name=unicorn-store-spring --namespace=unicorn-store-spring \
--attach-policy-arn=$(aws iam list-policies --query 'Policies[?PolicyName==`unicorn-eks-service-account-policy`].Arn' --output text) --approve --region=$AWS_REGION
rm service-account-policy.json
Synchronizing parameters and secrets
We need to synchronize the database secret from AWS Secrets Manager to a Kubernetes secret to use it in a deployment. To achieve that, we will use External Secrets and install it via Helm :
- Install the External Secrets Operator:
1
2
3
4
5
6
7
8
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets \
--create-namespace \
--set installCRDs=true \
--set webhook.port=9443 \
--wait
- Create the Kubernetes External Secret resources:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
cat <<EOF | envsubst | kubectl create -f -
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: unicorn-store-spring-secret-store
namespace: unicorn-store-spring
spec:
provider:
aws:
service: SecretsManager
region: $AWS_REGION
auth:
jwt:
serviceAccountRef:
name: unicorn-store-spring
EOF
cat <<EOF | kubectl create -f -
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: unicorn-store-spring-external-secret
namespace: unicorn-store-spring
spec:
refreshInterval: 1h
secretStoreRef:
name: unicorn-store-spring-secret-store
kind: SecretStore
target:
name: unicornstore-db-secret
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: unicornstore-db-secret
property: password
EOF
Deploying the application
- Create a new directory k8s in the application folder:
1
2
mkdir ~/environment/unicorn-store-spring/k8s
cd ~/environment/unicorn-store-spring/k8s
- Create Kubernetes manifest files for the deployment and the service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
export ECR_URI=$(aws ecr describe-repositories --repository-names unicorn-store-spring \
| jq --raw-output '.repositories[0].repositoryUri')
export SPRING_DATASOURCE_URL=$(aws ssm get-parameter --name databaseJDBCConnectionString \
| jq --raw-output '.Parameter.Value')
cat <<EOF > ~/environment/unicorn-store-spring/k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: unicorn-store-spring
namespace: unicorn-store-spring
labels:
app: unicorn-store-spring
spec:
replicas: 1
selector:
matchLabels:
app: unicorn-store-spring
template:
metadata:
labels:
app: unicorn-store-spring
spec:
serviceAccountName: unicorn-store-spring
containers:
- name: unicorn-store-spring
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "1"
memory: "2Gi"
image: ${ECR_URI}:latest
env:
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: "unicornstore-db-secret"
key: "password"
optional: false
- name: SPRING_DATASOURCE_URL
value: ${SPRING_DATASOURCE_URL}
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
EOF
cat <<EOF > ~/environment/unicorn-store-spring/k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
name: unicorn-store-spring
namespace: unicorn-store-spring
labels:
app: unicorn-store-spring
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
protocol: TCP
selector:
app: unicorn-store-spring
EOF
- Deploy manifests to EKS cluster:
1
2
kubectl apply -f ~/environment/unicorn-store-spring/k8s/deployment.yaml
kubectl apply -f ~/environment/unicorn-store-spring/k8s/service.yaml
- Verify that the application is running:
1
2
3
4
5
6
kubectl wait deployment -n unicorn-store-spring unicorn-store-spring --for condition=Available=True --timeout=120s
kubectl get deploy -n unicorn-store-spring
export SVC_URL=http://$(kubectl get svc unicorn-store-spring -n unicorn-store-spring -o json | jq --raw-output '.status.loadBalancer.ingress[0].hostname')
while [[ $(curl -s -o /dev/null -w "%{http_code}" $SVC_URL/) != "200" ]]; do echo "Service not yet available ..." && sleep 5; done
echo $SVC_URL
echo Service is Ready!
The creation of the load balancer for the service might take around 2-5 minutes.
- Get the Load Balancer URL for the services and make an example API call:
1
2
3
4
5
6
7
8
echo $SVC_URL
curl --location $SVC_URL; echo
curl --location --request POST $SVC_URL'/unicorns' --header 'Content-Type: application/json' --data-raw '{
"name": "'"Something-$(date +%s)"'",
"age": "20",
"type": "Animal",
"size": "Very big"
}' | jq
Exploring Amazon EKS in AWS console
Go to the Amazon EKS console.
Accessing the application logs
To further inspect the application startup or runtime behavior you can navigate to the application logs with the following steps.
- Get the logs from the current running pod via kubectl:
1
kubectl logs $(kubectl get pods -n unicorn-store-spring -o json | jq --raw-output '.items[0].metadata.name') -n unicorn-store-spring
- You should see a similar result to:
Section finished
In this section you have learned how to create a new EKS cluster. You deployed externals secrets, permissions and the UnicornStore Java application.