How to deploy Helm charts to an EKS cluster through AWS CloudFormation
- Oleksii Bebych
- Sep 15, 2021
- 3 min read
Problem statement
There are several ways to create an EKS cluster in AWS:
Web console or CLI
EKSctl tool
Terraform, CloudFormation or other IaC tools
Third-party products
In most cases an empty kubernetes cluster is not enough. We still may need an Ingress Controller, Cluster autoscaler, External DNS, Prometheus, etc. included in a default cluster set.
As you probably know, when an Amazon EKS cluster is created, the IAM entity (user or role) that creates the cluster is added to the Kubernetes RBAC authorization table as the administrator (with system:masters permissions). Initially, only that IAM user can make calls to the Kubernetes API server.
For example, in the Terraform we can use a Kubernetes/Helm provider or even “local-exec”, that will deploy required Kubernetes resources into the cluster, patch “aws-auth” ConfigMap in order to add extra IAM entities for a cluster administration. But, unfortunately, there is no such basic functionality in AWS CloudFormation. If we have a multi-account (multi-region) AWS environment, we have to use CloudFormation Stack Sets. In this case we can not use any things other than CloudFormation templates. We can create an EKS cluster itself, a managed node group, and the Fargate profile, but we can not manipulate with Kubernetes entities like pods, deployments, configmaps, services, etc. Potentially this can be solved via Lambda-backed Custom Resource, but there is another way.
The proposed solution
AWS CloudFormation has a registry, where we can find Amazon or Third-party public extensions. The AWS Quick Start team has developed and published several CloudFormation extensions related to Kubernetes:
AWSQS::EKS::Cluster - github link
AWSQS::Kubernetes::Get - link
AWSQS::Kubernetes::Helm - link
AWSQS::Kubernetes::Resource - link

Before using an extension you need to activate it in the relevant AWS region.

You have to choose an appropriate Execution role, created in advance. Every CloudFormation extension has a template for the Execution role with required policies (example here). You can also configure logging for the extension, it may be useful for debugging if anything goes wrong with Stack provisioning. You can enable or disable automatic extension updates.


As I mentioned before, we use CloudFormation Stack Sets for multi-account (multi-region) provisioning, so we’ve created a separate CloudFormation template that deploys needed IAM execution roles + activates needed extensions.
// omitted code
Resources:
EKSClusterExtensionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: [resources.cloudformation.amazonaws.com, cloudformation.amazonaws.com, lambda.amazonaws.com]
Action: sts:AssumeRole
Path: "/"
Policies:
- PolicyName: ResourceTypePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "eks:CreateCluster"
- "eks:DeleteCluster"
- "iam:PassRole"
- "sts:AssumeRole"
- "lambda:InvokeFunction"
- "lambda:CreateFunction"
// other required permissions
Resource: "*"
EKSClusterExtension:
Type: AWS::CloudFormation::TypeActivation
DependsOn: EKSClusterExtensionRole
Properties:
AutoUpdate: false
ExecutionRoleArn: !GetAtt EKSClusterExtensionRole.Arn
PublicTypeArn: !Sub "arn:aws:cloudformation:${AWS::Region}::type/resource/408988dff9e863704bcc72e7e13f8d645cee8311/AWSQS-EKS-Cluster"
// omitted code
The same procedure will be for other required extensions, e.g. AWSQS::Kubernetes::Helm. Once the extension is activated we can use it to create EKS cluster with many more options compared with default CloudFormation resource for EKS, e.g. extra IAM roles/users added to aws-auth configMap, control plane logging configuration and API endpoint configuration.
// omitted code
Resources:
AdminEKSRole:
Type: "AWS::IAM::Role" # Extra role added to aws-auth
Properties:
// omitted code
EksKMSKey:
Type: "AWS::KMS::Key" # KMS key for envelope encryption
// omitted code
EKSCluster:
Type: "AWSQS::EKS::Cluster"
Properties:
Name: !Sub "${ProjectName}-${ProductName}-${Env}-${ClusterName}"
Version: !Ref KubernetesVersion
RoleArn: !GetAtt serviceRole.Arn
LambdaRoleName: !ImportValue …… # extra role needed if EKS API endpoint is private (check docs)
ResourcesVpcConfig:
SubnetIds:
- !ImportValue ...
// omitted code
SecurityGroupIds:
- !Ref EKSSecurityGroup
EndpointPrivateAccess: true
EndpointPublicAccess: false
EnabledClusterLoggingTypes: !If [ LoggingEnabled, !Ref EKSClusterLoggingTypes, !Ref "AWS::NoValue" ]
KubernetesApiAccess: # Extra roles/users added to aws-auth configmap
Roles:
- Arn: !GetAtt AdminEKSRole.Arn
Username: "AdminRole"
Groups: ["system:masters"]
- Arn: !ImportValue ... # Execution role for Helm extension
Username: "DeployRole"
Group: ["system:masters"]
EncryptionConfig: !If
- EnableEncryption
- - Resources: [ secrets ]
Provider:
KeyArn: // omitted code
MetricsServer: # Example for applying kubernetes manifest
Type: "AWSQS::Kubernetes::Resource"
Properties:
ClusterName: !Ref EKSCluster
Namespace: kube-system
Url: https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.4.1/components.yaml
KubeStateMetrics: # Example for deploying Helm chart
Type: "AWSQS::Kubernetes::Helm"
Properties:
ClusterID: !Ref EKSCluster
Name: kube-state-metrics
Namespace: kube-state-metrics
Repository: https://prometheus-community.github.io/helm-charts
Chart: prometheus-community/kube-state-metrics
ValueYaml: |
prometheus:
monitor:
enabled: true
Conclusion
AWS CloudFormation extensions described in this post help us deploy EKS clusters with all necessary Kubernetes resources in a fully automated way across all AWS accounts/OUs within an AWS Landing Zone where EKS is required. Whole configuration is stored in the GIT repository and deployed by AWS CodePipeline + AWS CloudFormation Stack Sets.
留言