AWS Controllers for Kubernetes Hands-on

Abstract
In the world of Infrastructure as code (IaC) there are many tools that support us to create AWS resources quickly such as CDK, Pulumi, and Terraform. Today I introduce AWS Controllers for Kubernetes (ACK)
AWS Controllers for Kubernetes (ACK) lets you define and use AWS service resources directly from Kubernetes. With ACK, you can take advantage of AWS-managed services for your Kubernetes applications without needing to define resources outside of the cluster or run services that provide supporting capabilities like databases or message queues within the cluster.
This post provides step-by-step to create AWS RDS postgres in private VPC using ACK and then accessing the database to prove it works.
Table Of Contents
๐ Introduction of ACK
-

When to use ACK? Read here
๐ Install the ACK service controller for RDS
Pre-requisite:
EKS cluster
OIDC for IRSA
IAM for service account (IRSA) - Checkout Using IAM Service Account Instead Of Instance Profile For EKS Pods for how-to.
Install ACK for RDS
โก $ export HELM_EXPERIMENTAL_OCI=1 โก $ helm pull oci://public.ecr.aws/aws-controllers-k8s/rds-chart --version=v0.0.17 โก $ tar xf rds-chart-v0.0.17.tgz โก $ helm install rds-chart --generate-name --set=aws.region=ap-northeast-2
๐ Create ACK ServiceAccount base on IRSA
This step requires IAM role for service account here is ACK RDS. Note that the role needs permission to manage RDS resources. We can limit permission by restricting resources. And to protect the resource from incidents of deleteing the
DBInstance(describe later), we can setDenyaction ofrds:DeleteDBInstance{ "Version": "2012-10-17", "Statement": [ { "Condition": { "StringEquals": { "aws:RequestedRegion": "ap-northeast-2" } }, "Action": "rds:*", "Resource": "arn:aws:rds:ap-northeast-2:123456789012:*", "Effect": "Allow", "Sid": "CreateRds" }, { "Action": "rds:DeleteDBInstance", "Resource": "*", "Effect": "Deny", "Sid": "DenyDeleteRds" } ] }Generate SA yaml and update IRSA to apply, replace the IAM ARN role with yours.
ack-sa.yamlapiVersion: v1 kind: ServiceAccount metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/name: ack-rds-controller name: ack-rds-controller annotations: eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/ack-rds-controller-d1Apply the manifest
kf apply -f ack-sa.yaml
๐ Create RDS secret keys
The best practice is to protect the RDS user's password
export RDS_DB_USERNAME=rds_username export RDS_DB_PASSWORD=rds_password kubectl create secret generic rds-postgresql-user-creds \ --from-literal=username="${RDS_DB_USERNAME}" \ --from-literal=password="${RDS_DB_PASSWORD}"
๐ Create a subnet group
The subnet group contains all subnets of EKS private VPC
Get subnets from EKS VPC, replace the VPC ID and the region with yours
EKS_SUBNET_IDS=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-0eb6477bf2c8430cd" --query 'Subnets[*].SubnetId' --output text --region ap-northeast-2)Generate yaml file inherit ${EKS_SUBNET_IDS} above
cat <<-EOF > ack/ack-rds-subnet-groups.yaml apiVersion: rds.services.k8s.aws/v1alpha1 kind: DBSubnetGroup metadata: name: rds-postgresql-subnet-group spec: name: rds-postgresql-subnet-group description: RDS for app in EKS subnetIDs: $(printf " - %s\n" ${EKS_SUBNET_IDS}) tags: - key: stage value: development - key: owner value: dev EOFApply the manifest
kf apply -f ack/ack-rds-subnet-groups.yaml
๐ Create security group to allow traffic from EKS pods to the RDS
First, we create an SG in the EKS VPC to attach to the RDS, replace the VPC ID with your EKS VPC ID and the appropriate region
RDS_SECURITY_GROUP_ID=$(aws ec2 create-security-group \ --group-name rds-postgres-sg \ --description "SG to allow traffic from EKS pod to RDS" \ --vpc-id vpc-0eb6477bf2c8430cd \ --output text --region ap-northeast-2 )Then we need to allow traffic from EKS worker nodes. There are two ways
In RDS SG, allow traffic from CIDR range of the EKS VPC
Get CIDR range of EKS VCP using the command line
EKS_CIDR_RANGE=$(aws ec2 describe-vpcs --vpc-ids vpc-0eb6477bf2c8430cd --query 'Vpcs[].CidrBlock' --output text --region ap-northeast-2)Create ingress in the SG to allow traffic from the above CIDR
aws ec2 authorize-security-group-ingress \ --group-id "${RDS_SECURITY_GROUP_ID}" \ --protocol tcp \ --port 5432 \ --cidr "${EKS_CIDR_RANGE}" \ --region ap-northeast-2
In RDS SG, allow traffic from the security group which is attached to all EKS nodes (in general, traffic is allowed from the network interfaces that are associated with the source security group for the specified protocol and port, read more from Understand Pods communication at
Security groups for your VPCspecifying a security group as the source for a rule)

๐ Create DBInstance
Create DBInstance manifest, replace security group ID in
vpcSecurityGroupIDswith the one you created in the previous stepRDS_SECURITY_GROUP_ID. Here we select RDS instance typedb.t3.microas it is free tier, master user and password maps with the RDS secret keys created in previous step,engine: postgresversion 10ack/ack-rds-postgresql.yamlapiVersion: rds.services.k8s.aws/v1alpha1 kind: DBInstance metadata: name: "rds-postgresql-dev" spec: allocatedStorage: 20 autoMinorVersionUpgrade: true backupRetentionPeriod: 7 dbInstanceClass: db.t3.micro dbInstanceIdentifier: "rds-postgresql-dev" dbSubnetGroupName: rds-postgresql-subnet-group engine: postgres engineVersion: "10" masterUsername: "rds_user" masterUserPassword: namespace: default name: rds-postgresql-user-creds key: password multiAZ: true publiclyAccessible: false storageEncrypted: true storageType: gp2 vpcSecurityGroupIDs: - sg-009c3a2658d1f7165 tags: - key: stage value: development - key: owner value: devApply the yaml
kf apply -f ack/ack-rds-postgresql.yamlCheck created
DBInstanceโก $ kf get DBInstance NAME AGE rds-postgresql-dev 1h
๐ Access RDS through EKS pod
This step proves RDS works well and EKS pods in private VPC can read/write to the RDS using master user
Build
postgresql-clientdocker image and push it to ECR or any container image repositoryFROM alpine:3 RUN apk add --no-cache postgresql-client CMD while true; do sleep 5; echo psql-client; doneCreate postgresql client deployment
psql-client.yamlapiVersion: apps/v1 kind: Deployment metadata: labels: app: psql-client name: psql-client spec: replicas: 1 selector: matchLabels: app: psql-client-deployment template: metadata: labels: app: psql-client-deployment spec: containers: - image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/psql/client:latest name: psql-clientApply the deployment and then get the pod
~ $ kf apply -f ack/psql-client.yaml deployment.apps/psql-client created ~ $ kf get pod NAME READY STATUS RESTARTS AGE psql-client-9d45f759-8swtr 1/1 Running 0 44mGo into the pod to access RDS, get RDS private endpoint in the console or use AWS CLI or using
kubectl~ $ aws rds describe-db-instances --db-instance-identifier rds-postgresql-dev --region ap-northeast-2 --query 'DBInstances[0].Endpoint.Address' "rds-postgresql-dev.xxxxxxxxxxxx.ap-northeast-2.rds.amazonaws.com" ~ $ kubectl get dbinstance rds-postgresql-dev -o jsonpath='{.status.endpoint.address}' "rds-postgresql-dev.xxxxxxxxxxxx.ap-northeast-2.rds.amazonaws.com" ~ $ kf exec -it psql-client-9d45f759-8swtr -- sh / # psql -h rds-postgresql-dev.xxxxxxxxxxxx.ap-northeast-2.rds.amazonaws.com -U rds_user -d postgres postgres=> \du List of roles Role name | Attributes | Member of --------------------+------------------------------------------------------------+------------------------------------------------------------- rds_user | Create role, Create DB +| {rds_superuser} | Password valid until infinity | rds_ad | Cannot login | {} rds_iam | Cannot login | {} rds_password | Cannot login | {} rds_replication | Cannot login | {} rds_superuser | Cannot login | {pg_monitor,pg_signal_backend,rds_replication,rds_password} rdsadmin | Superuser, Create role, Create DB, Replication, Bypass RLS+| {} | Password valid until infinity | rdsrepladmin | No inheritance, Cannot login, Replication | {} postgres=> CREATE DATABASE "rds-test"; CREATE DATABASE postgres=> \l rds-test List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ----------+--------------------+----------+-------------+-------------+------------------- rds-test | rds_user | UTF8 | en_US.UTF-8 | en_US.UTF-8 | (1 row)
๐ Clean-up workspace
Delete
DBInstance, note that if you denyrds:DeleteDBInstancein ACK role to protect your RDS then this step requires more action such as remove the deny or delete RDS manually then forcing deleteDBInstance. Here assume ACK RDS role has the permission of deleting the RDSโก $ kf delete DBInstance rds-postgresql-dev dbinstance.rds.services.k8s.aws "rds-postgresql-dev" deletedDelete
psql-clientdeploymentโก $ kf delete -f ack/psql-client.yaml deployment.apps "psql-client" deletedDelete RDS secret key
โก $ kf delete secret rds-postgresql-user-creds secret "rds-postgresql-user-creds" deletedDelete RDS security group
โก $ aws ec2 delete-security-group --group-id sg-009c3a2658d1f7165 --region ap-northeast-2Uninstall ACK RDS
โก $ helm list NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION rds-chart-1648053478 default 1 2022-03-23 23:38:11.397906502 +0700 +07 deployed rds-chart-v0.0.17 v0.0.17 โก $ helm uninstall rds-chart-1648053478 release "rds-chart-1648053478" uninstalledFinally, delete the IRSA for ACK RDS
โก $ cdk destroy AckControllerSA --profile vc-mfa Are you sure you want to delete: AckControllerSA (y/n)? y AckControllerSA: destroying... โ AckControllerSA: destroyed
๐ Conclusion
Using ACK to create AWS resources is a big plus for those ones love managing AWS resources through k8s manifests within the EKS cluster
We can combine cdk8s to create ACK yaml files
References:




