Introduction#
- Create a network stack
- Creat an amazon eks cluster
Network stack#
Let's create a network stack for launching a amazon eks cluster:
- at least two subnets in two availability zones
- security group for communication between control plane and data plane
- role for control plane to call aws api on-behalf of you
- role for nodegroup or instance with managed policies
AWSTemplateFormatVersion: '2010-09-09'#------------------------------------------------------# Mappings#------------------------------------------------------Mappings:CidrMappings:public-subnet-1:CIDR: 10.0.0.0/24public-subnet-2:CIDR: 10.0.1.0/24public-subnet-3:CIDR: 10.0.2.0/24#------------------------------------------------------# Parameters#------------------------------------------------------Parameters:CidrBlock:Type: StringDescription: CidrBlockDefault: 10.0.0.0/16#------------------------------------------------------# Resources: VPC, Subnets, NAT, Routes#------------------------------------------------------Resources:VPC:Type: AWS::EC2::VPCProperties:CidrBlock: !Ref CidrBlockEnableDnsSupport: trueEnableDnsHostnames: trueTags:- Key: NameValue: !Sub ${AWS::StackName}-vpcInternetGateway:Type: AWS::EC2::InternetGatewayProperties:Tags:- Key: NameValue: !Sub ${AWS::StackName}-igAttachGateway:Type: AWS::EC2::VPCGatewayAttachmentProperties:VpcId: !Ref VPCInternetGatewayId: !Ref InternetGatewayPublicRouteTable:Type: AWS::EC2::RouteTableProperties:VpcId: !Ref VPCTags:- Key: NameValue: !Sub ${AWS::StackName}-public-rtRouteInternetGateway:Type: AWS::EC2::RouteDependsOn: AttachGatewayProperties:RouteTableId: !Ref PublicRouteTableDestinationCidrBlock: 0.0.0.0/0GatewayId: !Ref InternetGatewayPublicSubnet1:Type: AWS::EC2::SubnetProperties:MapPublicIpOnLaunch: trueAvailabilityZone:Fn::Select:- 0- Fn::GetAZs:Ref: AWS::RegionVpcId: !Ref VPCCidrBlock:Fn::FindInMap:- CidrMappings- public-subnet-1- CIDRTags:- Key: NameValue: !Sub ${AWS::StackName}-public-subnet-1PublicSubnet2:Type: AWS::EC2::SubnetProperties:MapPublicIpOnLaunch: trueAvailabilityZone:Fn::Select:- 1- Fn::GetAZs:Ref: AWS::RegionVpcId: !Ref VPCCidrBlock:Fn::FindInMap:- CidrMappings- public-subnet-2- CIDRTags:- Key: NameValue: !Sub ${AWS::StackName}-public-subnet-2PublicSubnet3:Type: AWS::EC2::SubnetProperties:MapPublicIpOnLaunch: trueAvailabilityZone:Fn::Select:- 2- Fn::GetAZs:Ref: AWS::RegionVpcId: !Ref VPCCidrBlock:Fn::FindInMap:- CidrMappings- public-subnet-3- CIDRTags:- Key: NameValue: !Sub ${AWS::StackName}-public-subnet-3PublicSubnet1RouteTableAssociation:Type: AWS::EC2::SubnetRouteTableAssociationProperties:SubnetId: !Ref PublicSubnet1RouteTableId: !Ref PublicRouteTablePublicSubnet2RouteTableAssociation:Type: AWS::EC2::SubnetRouteTableAssociationProperties:SubnetId: !Ref PublicSubnet2RouteTableId: !Ref PublicRouteTablePublicSubnet3RouteTableAssociation:Type: AWS::EC2::SubnetRouteTableAssociationProperties:SubnetId: !Ref PublicSubnet3RouteTableId: !Ref PublicRouteTableControlPlaneSecurityGroup:Type: AWS::EC2::SecurityGroupProperties:GroupDescription: Communication between the control plane and worker nodegroupsVpcId: !Ref VPCGroupName: !Sub ${AWS::StackName}-eks-cluster-sgControlPlaneSecurityGroupIngress:Type: AWS::EC2::SecurityGroupIngressProperties:GroupId: !Ref ControlPlaneSecurityGroupIpProtocol: -1SourceSecurityGroupId: !Ref ControlPlaneSecurityGroupSourceSecurityGroupOwnerId: !Ref AWS::AccountId#------------------------------------------------------# Export:#------------------------------------------------------Outputs:VPC:Value: !Ref VPCExport:Name: !Sub ${AWS::StackName}-vpcPublicSubnet1:Value: !Ref PublicSubnet1Export:Name: !Sub ${AWS::StackName}-public-subnet-1PublicSubnet2:Value: !Ref PublicSubnet2Export:Name: !Sub ${AWS::StackName}-public-subnet-2PublicSubnet3:Value: !Ref PublicSubnet3Export:Name: !Sub ${AWS::StackName}-public-subnet-3PublicRouteTable:Value: !Ref PublicRouteTableExport:Name: !Sub ${AWS::StackName}-public-route-tableInternetGateway:Value: !Ref InternetGatewayExport:Name: !Sub ${AWS::StackName}-igControlPlaneSecurityGroup:Value: !Ref ControlPlaneSecurityGroupExport:Name: !Sub ${AWS::StackName}-eks-cluster-sg
EKS cluster#
Let's create a eks cluster:
- vervsion 1.30
- instance type t3.medium
AWSTemplateFormatVersion: '2010-09-09'#------------------------------------------------------# Parameters#------------------------------------------------------Parameters:NetworkStackName:Type: StringDefault: 'network-stack'EKSClusterVersion:Type: StringDefault: '1.30'NodeGroupInstanceType:Type: StringDefault: 't3.medium'#------------------------------------------------------# Resources: EKS Cluster#------------------------------------------------------Resources:EKSClusterRole:Type: AWS::IAM::RoleProperties:AssumeRolePolicyDocument:Version: '2012-10-17'Statement:- Effect: AllowPrincipal:Service:- eks.amazonaws.comAction:- sts:AssumeRoleManagedPolicyArns:- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonEKSClusterPolicy- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonEKSVPCResourceControllerControlPlane:Type: AWS::EKS::ClusterProperties:Name: !Sub ${AWS::StackName}-eks-clusterResourcesVpcConfig:SecurityGroupIds:- !ImportValueFn::Sub: ${NetworkStackName}-eks-cluster-sgSubnetIds:- !ImportValueFn::Sub: ${NetworkStackName}-public-subnet-1- !ImportValueFn::Sub: ${NetworkStackName}-public-subnet-2- !ImportValueFn::Sub: ${NetworkStackName}-public-subnet-3Version: !Ref EKSClusterVersionRoleArn: !GetAtt EKSClusterRole.ArnManagedNodeGroupRole:Type: AWS::IAM::RoleProperties:AssumeRolePolicyDocument:Version: '2012-10-17'Statement:- Effect: AllowPrincipal:Service:- ec2.amazonaws.comAction:- sts:AssumeRoleManagedPolicyArns:- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonEKSWorkerNodePolicy- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonEKS_CNI_Policy- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCoreManagedNodeGroup:Type: AWS::EKS::NodegroupProperties:AmiType: AL2_x86_64ClusterName: !Ref ControlPlaneInstanceTypes:- !Ref NodeGroupInstanceTypeLabels:alpha.eksctl.io/cluster-name: !Ref ControlPlaneNodeRole: !GetAtt ManagedNodeGroupRole.ArnScalingConfig:DesiredSize: 3MaxSize: 3MinSize: 3Subnets:- !ImportValueFn::Sub: ${NetworkStackName}-public-subnet-1- !ImportValueFn::Sub: ${NetworkStackName}-public-subnet-2- !ImportValueFn::Sub: ${NetworkStackName}-public-subnet-3Tags:Name: !Sub ${AWS::StackName}-eks-nodegroup
Deploy#
Here is a simple script to deploy CloudFormation stacks
aws cloudformation validate-template \--template-body file://1-network.yamlaws cloudformation create-stack \--stack-name network-stack \--template-body file://1-network.yaml \--capabilities CAPABILITY_NAMED_IAMaws cloudformation update-stack \--stack-name network-stack \--template-body file://1-network.yaml \--capabilities CAPABILITY_NAMED_IAMaws cloudformation create-stack \--stack-name eks-stack \--template-body file://2-eks.yaml \--capabilities CAPABILITY_NAMED_IAM# aws eks update-kubeconfig --name eks-stack-eks-cluster
Update Configure#
aws eks update-kubeconfig --name eks-stack-eks-cluster
Get service account
kubectl -n kube-system get serviceaccount/ebs-csi-controller-sa -o yaml
Create identity provider
eksctl utils associate-iam-oidc-provider \--cluster=eks-stack-eks-cluster \--approve
Create trusted policy
{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Federated": "arn:aws:iam::633688584000:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/9D29659801172AADAE5B5A48CA2FE5BA"},"Action": "sts:AssumeRoleWithWebIdentity","Condition": {"StringEquals": {"oidc.eks.us-west-2.amazonaws.com/id/9D29659801172AADAE5B5A48CA2FE5BA:aud": "sts.amazonaws.com","oidc.eks.us-west-2.amazonaws.com/id/9D29659801172AADAE5B5A48CA2FE5BA:sub": "system:serviceaccount:kube-system:ebs-csi-controller-sa"}}}]}
First App#
Let's run a busybox.
kubectl run busybox --image=busybox --rm -it --command -- bin/sh
Let's run a nginx image.
Let's run an aws cli image.
kubectl run awscli --image=public.ecr.aws/aws-cli/aws-cli:latest -it --command -- /bin/sh
We can run locally to check the image.
docker run -it --entrypoint /bin/sh public.ecr.aws/aws-cli/aws-cli:latest
AWS CLI#
Let's run an aws cli image.
kubectl run awscli --image=public.ecr.aws/aws-cli/aws-cli:latest -it --command -- /bin/sh
Then check caller identity.
aws sts get-caller-identity
Create a deployment for awscli.
Shell into a running pod.
kubectl exec --stdin --tty awscli -- /bin/sh
Pod Identity#
- install amazon eks pod identity agent eks add-on
- create an iam role named RoleForPodDemo
- create a service account name pod-identity-demo
- create a pod identity association
First, let's install the add-on from console.
Second, create a service account in eks.
kubectl create serviceaccount pod-identity-demo -n default
Deploy awscli pod with the service account.
kubectl apply -f yaml/hello.yaml
Here is the hello.yaml.
apiVersion: v1kind: Podmetadata:creationTimestamp: nulllabels:run: awscliname: awsclispec:serviceAccountName: pod-identity-democontainers:- image: public.ecr.aws/aws-cli/aws-cli:latestname: awscliresources: {}dnsPolicy: ClusterFirstrestartPolicy: Alwaysstatus: {}
Third, create a pod identity association from console
Finally, let's test it, shell into a running pod
kubectl exec --stdin --tty awscli -- /bin/sh
Create a bucket
aws s3api create-bucket --bucket haimtran-demo-02062024 --region us-west-2 --create-bucket-configuration LocationConstraint=us-west-2
Or using this command
aws s3 mb s3://haimtran-demo-03062024
Container Insight#
- create an iam role for service account used by cwagent
- install the amazon cw observability eks add-on
First, let create an iam role which will be used by the cwagent service account. It is possible to use eksctl or standard method.
eksctl utils associate-iam-oidc-provider --cluster eks-stack-eks-cluster --approve
eksctl create iamserviceaccount \--name cloudwatch-agent \--namespace amazon-cloudwatch --cluster eks-stack-eks-cluster \--role-name role-for-cw-agent-add-on \--attach-policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \--role-only \--approve
This eksctl command below will create a CloudFormation template under the hood. It add the aws managed policy arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy.
arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
And a trusted policy.
trusted-policy.json
{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Federated": "arn:aws:iam::094847457777:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/C048A79AB478F38A58EF0C5B4915934D"},"Action": "sts:AssumeRoleWithWebIdentity","Condition": {"StringEquals": {"oidc.eks.us-west-2.amazonaws.com/id/C048A79AB478F38A58EF0C5B4915934D:sub": "system:serviceaccount:amazon-cloudwatch:cloudwatch-agent","oidc.eks.us-west-2.amazonaws.com/id/C048A79AB478F38A58EF0C5B4915934D:aud": "sts.amazonaws.com"}}}]}
Second, add the amazon cw observability (add-on) from console, it will create a serviceaccount name cloudwatch-agent in namespace amazon-cloudwatch.
Describe the annoted role.
kubectl describe serviceaccounts amazon-cloudwatch-observability-controller-manager -n amazon-cloudwatch
And see role binding.
Name: amazon-cloudwatch-observability-controller-managerNamespace: amazon-cloudwatchLabels: app.kubernetes.io/instance=amazon-cloudwatch-observabilityapp.kubernetes.io/managed-by=EKSapp.kubernetes.io/name=amazon-cloudwatch-observabilityapp.kubernetes.io/version=1.0.0Annotations: eks.amazonaws.com/role-arn: arn:aws:iam::094847457777:role/role-for-cw-agent-add-on
Finally, go to cloudwatch container insights and filter by namespaces to see how pods running with metrics, and logs.
EBS CSI#
- create an iam role for the csi driver.
- install the ebs csi add-on.
Let's create a role which will be used by the drive and annotated with a service account. Add this aws managed policy arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy and the following trusted policy.
arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy
trusted-policy.json
{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Federated": "arn:aws:iam::094847457777:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/C048A79AB478F38A58EF0C5B4915934D"},"Action": "sts:AssumeRoleWithWebIdentity","Condition": {"StringEquals": {"oidc.eks.us-west-2.amazonaws.com/id/C048A79AB478F38A58EF0C5B4915934D:aud": "sts.amazonaws.com","oidc.eks.us-west-2.amazonaws.com/id/C048A79AB478F38A58EF0C5B4915934D:sub": "system:serviceaccount:kube-system:ebs-csi-controller-sa"}}}]}
Then let's install the add-on.
Describe the serviceaccount and see the annotated role.
kubectl describe serviceaccount ebs-csi-controller-sa -n kube-system
Annotated role.
Name: ebs-csi-controller-saNamespace: kube-systemLabels: app.kubernetes.io/component=csi-driverapp.kubernetes.io/managed-by=EKSapp.kubernetes.io/name=aws-ebs-csi-driverapp.kubernetes.io/version=1.31.0Annotations: eks.amazonaws.com/role-arn: arn:aws:iam::094847457777:role/AmazonEKS_EBS_CSI_DriverRole
Applications#
awscli.yaml
apiVersion: v1kind: Podmetadata:creationTimestamp: nulllabels:run: awscliname: awsclispec:serviceAccountName: pod-identity-democontainers:- image: public.ecr.aws/aws-cli/aws-cli:latestname: awsclicommand: ['/bin/sh']args: ['-c', 'while true; do echo hello; sleep 10;done']
book-service.yaml
apiVersion: v1kind: Servicemetadata:name: go-app-servicespec:ports:- port: 80targetPort: 3000name: httpselector:app: go-apptype: LoadBalancer---apiVersion: apps/v1kind: Deploymentmetadata:name: go-app-deploymentspec:replicas: 2selector:matchLabels:app: go-apptemplate:metadata:labels:app: go-appspec:containers:- image: 633688584000.dkr.ecr.us-west-2.amazonaws.com/go-app:latestname: go-appports:- containerPort: 3000resources:limits:cpu: 100mrequests:cpu: 100m---apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata:name: go-app-hpanamespace: defaultspec:maxReplicas: 10metrics:- resource:name: cputarget:averageUtilization: 15type: Utilizationtype: ResourceminReplicas: 2scaleTargetRef:apiVersion: apps/v1kind: Deploymentname: go-app-deployment
hello-service.yaml
apiVersion: v1kind: Servicemetadata:name: cdk8s-app-service-c8a84b3espec:ports:- port: 80targetPort: 8080selector:app: hello-cdk8stype: LoadBalancer---apiVersion: apps/v1kind: Deploymentmetadata:name: cdk8s-app-deployment-c8f953f2spec:replicas: 2selector:matchLabels:app: hello-cdk8stemplate:metadata:labels:app: hello-cdk8sspec:containers:- image: 'paulbouwer/hello-kubernetes:1.7'name: hello-kubernetesports:- containerPort: 8080resources:limits:cpu: 100mrequests:cpu: 100m
metric-server.yaml
apiVersion: v1kind: ServiceAccountmetadata:labels:k8s-app: metrics-servername: metrics-servernamespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:labels:k8s-app: metrics-serverrbac.authorization.k8s.io/aggregate-to-admin: 'true'rbac.authorization.k8s.io/aggregate-to-edit: 'true'rbac.authorization.k8s.io/aggregate-to-view: 'true'name: system:aggregated-metrics-readerrules:- apiGroups:- metrics.k8s.ioresources:- pods- nodesverbs:- get- list- watch---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:labels:k8s-app: metrics-servername: system:metrics-serverrules:- apiGroups:- ''resources:- nodes/metricsverbs:- get- apiGroups:- ''resources:- pods- nodesverbs:- get- list- watch---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:labels:k8s-app: metrics-servername: metrics-server-auth-readernamespace: kube-systemroleRef:apiGroup: rbac.authorization.k8s.iokind: Rolename: extension-apiserver-authentication-readersubjects:- kind: ServiceAccountname: metrics-servernamespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:labels:k8s-app: metrics-servername: metrics-server:system:auth-delegatorroleRef:apiGroup: rbac.authorization.k8s.iokind: ClusterRolename: system:auth-delegatorsubjects:- kind: ServiceAccountname: metrics-servernamespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:labels:k8s-app: metrics-servername: system:metrics-serverroleRef:apiGroup: rbac.authorization.k8s.iokind: ClusterRolename: system:metrics-serversubjects:- kind: ServiceAccountname: metrics-servernamespace: kube-system---apiVersion: v1kind: Servicemetadata:labels:k8s-app: metrics-servername: metrics-servernamespace: kube-systemspec:ports:- name: httpsport: 443protocol: TCPtargetPort: httpsselector:k8s-app: metrics-server---apiVersion: apps/v1kind: Deploymentmetadata:labels:k8s-app: metrics-servername: metrics-servernamespace: kube-systemspec:selector:matchLabels:k8s-app: metrics-serverstrategy:rollingUpdate:maxUnavailable: 0template:metadata:labels:k8s-app: metrics-serverspec:containers:- args:- --cert-dir=/tmp- --secure-port=4443- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname- --kubelet-use-node-status-port- --metric-resolution=15simage: registry.k8s.io/metrics-server/metrics-server:v0.6.3imagePullPolicy: IfNotPresentlivenessProbe:failureThreshold: 3httpGet:path: /livezport: httpsscheme: HTTPSperiodSeconds: 10name: metrics-serverports:- containerPort: 4443name: httpsprotocol: TCPreadinessProbe:failureThreshold: 3httpGet:path: /readyzport: httpsscheme: HTTPSinitialDelaySeconds: 20periodSeconds: 10resources:requests:cpu: 100mmemory: 200MisecurityContext:allowPrivilegeEscalation: falsereadOnlyRootFilesystem: truerunAsNonRoot: truerunAsUser: 1000volumeMounts:- mountPath: /tmpname: tmp-dirnodeSelector:kubernetes.io/os: linuxpriorityClassName: system-cluster-criticalserviceAccountName: metrics-servervolumes:- emptyDir: {}name: tmp-dir---apiVersion: apiregistration.k8s.io/v1kind: APIServicemetadata:labels:k8s-app: metrics-servername: v1beta1.metrics.k8s.iospec:group: metrics.k8s.iogroupPriorityMinimum: 100insecureSkipTLSVerify: trueservice:name: metrics-servernamespace: kube-systemversion: v1beta1versionPriority: 100