今天给大家分享一个实战例子,如何在EKS上创建容器化应用并通过ALB来发布。先介绍一下几个基本概念:
-
IAM, OpenID Connect (OIDC)
2014 年,AWS Identity and Access Management 增加了使用 OpenID Connect (OIDC) 的联合身份支持。此功能允许您使用支持的身份提供商对 AWS API 调用进行身份验证,并接收有效的 OIDC JSON Web 令牌 (JWT)。您可以将此令牌传递给 AWS STS AssumeRoleWithWebIdentity API 操作并接收 IAM 临时角色凭证。您可以使用这些凭证与任何 AWS 服务进行交互,包括 Amazon S3 和 DynamoDB。
每个 JWT 令牌都由签名密钥对签名。密钥由 Amazon EKS 管理的 OIDC 提供商提供,私钥每 7 天轮换一次。Amazon EKS 会保留公钥,直到它们过期。如果您连接外部 OIDC 客户端,请注意,您需要在公钥过期之前刷新签名密钥。了解如何获取签名密钥以验证 OIDC 令牌。
Kubernetes 长期以来一直使用服务账户作为自己的内部身份系统。Pod 可以使用自动挂载的令牌(非 OIDC JWT)向 Kubernetes API 服务器进行身份验证,只有 Kubernetes API 服务器才能验证该令牌。这些旧式服务账户令牌不会过期,并且轮换签名密钥是一个困难的过程。在 Kubernetes 1.12 版中,添加了对新 ProjectedServiceAccountToken 功能的支持。此功能是 OIDC JSON Web 令牌,还包含服务账户身份并支持可配置的受众。
Amazon EKS 为每个集群托管一个公共 OIDC 发现终端节点,其中包含 ProjectedServiceAccountToken JSON Web 令牌的签名密钥,因此外部系统(例如 IAM)可以验证和接受 Kubernetes 颁发的 OIDC 令牌。
通过下面的架构图能清晰地说明它们之间的关系:
创建EKS cluster
TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
export AWS_DEFAULT_REGION=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" --silent http://169.254.169.254/latest/meta-data/placement/region)
echo $AWS_DEFAULT_REGION
eksctl create cluster \
--name eks-lab-cluster \
--nodegroup-name worknodes-1 \
--node-type t3.medium \
--nodes 2 \
--nodes-min 1 \
--nodes-max 4 \
--managed \
--version 1.29 \
--region ${AWS_DEFAULT_REGION}
生成eks kubeconfig 文件
aws eks update-kubeconfig --name eks-lab-cluster --region ${AWS_DEFAULT_REGION}
检查EKS cluster 状态
ec2-user:~/environment $ kubectl get node
NAME STATUS ROLES AGE VERSION
ip-192-168-51-133.ap-southeast-2.compute.internal Ready <none> 5m26s v1.29.3-eks-ae9a62a
ip-192-168-92-105.ap-southeast-2.compute.internal Ready <none> 5m25s v1.29.3-eks-ae9a62a
ec2-user:~/environment $ kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system aws-node-mm6xr 2/2 Running 0 6m
kube-system aws-node-w6v9b 2/2 Running 0 6m1s
kube-system coredns-57d946db4c-hjgb9 1/1 Running 0 9m38s
kube-system coredns-57d946db4c-x5t68 1/1 Running 0 9m38s
kube-system kube-proxy-6ffsn 1/1 Running 0 6m1s
kube-system kube-proxy-sj5k2 1/1 Running 0 6m
创建docker 容器
创建website 的Dockerfile文件
FROM public.ecr.aws/docker/library/httpd:2.4
RUN apt-get update && apt-get -y install cron && apt-get install vim -y
COPY cron /etc/cron.d/
COPY index.html /usr/local/apache2/htdocs/
COPY metadata.sh /usr/local/apache2/htdocs/
COPY copy-metadata-file.sh /
COPY font /usr/local/apache2/htdocs/font
COPY images /usr/local/apache2/htdocs/images
RUN mkdir /var/metadata
RUN chmod -R 0777 /var/metadata/
RUN chmod +x /usr/local/apache2/htdocs/metadata.sh
RUN chmod +x /copy-metadata-file.sh
RUN chmod 644 /etc/cron.d/cron
RUN crontab /etc/cron.d/cron
EXPOSE 80
WORKDIR /usr/local/apache2/htdocs/
CMD ./metadata.sh && crontab && crontab /etc/cron.d/cron && service cron restart && apachectl -D FOREGROUND
创建sidecar容器的Dockerfile
ROM public.ecr.aws/docker/library/python:alpine3.16
RUN python3 --version
RUN pip3 --version
RUN apk add --no-cache aws-cli
RUN aws --version
RUN mkdir /var/metadata/
COPY metadata2.sh /
COPY metadata2.json /
RUN chmod +x /metadata2.sh
RUN chmod -R 0777 /var/metadata/
CMD ./metadata2.sh
创建镜像文件
docker build -t website .
docker build -t sidecar .
创建ecr
$ aws ecr create-repository --repository-name website --region ${AWS_DEFAULT_REGION}
{
"repository": {
"repositoryArn": "arn:aws:ecr:ap-southeast-2:654654314383:repository/website",
"registryId": "654654314383",
"repositoryName": "website",
"repositoryUri": "654654314383.dkr.ecr.ap-southeast-2.amazonaws.com/website",
"createdAt": "2024-07-18T01:31:42.098000+00:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}
ec2-user:~/environment/environment/eksLabRepo (main) $ aws ecr create-repository \
> --repository-name sidecar \
> --region ${AWS_DEFAULT_REGION}
{
"repository": {
"repositoryArn": "arn:aws:ecr:ap-southeast-2:654654314383:repository/sidecar",
"registryId": "654654314383",
"repositoryName": "sidecar",
"repositoryUri": "654654314383.dkr.ecr.ap-southeast-2.amazonaws.com/sidecar",
"createdAt": "2024-07-18T01:33:09.150000+00:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}
给变量赋值
ec2-user:~/environment/environment/eksLabRepo (main) $ export ECR_REPO_URI_WEBSITE=$(aws ecr describe-repositories \
> --repository-names website \
> --region ${AWS_DEFAULT_REGION} \
> --query 'repositories[*].repositoryUri' \
> --output text)
ec2-user:~/environment/environment/eksLabRepo (main) $ export ECR_REPO_URI_SIDECAR=$(aws ecr describe-repositories \
> --repository-names sidecar \
> --region ${AWS_DEFAULT_REGION} \
> --query 'repositories[*].repositoryUri' \
> --output text)
ec2-user:~/environment/environment/eksLabRepo (main) $ echo ECR_REPO_URI_WEBSITE=$ECR_REPO_URI_WEBSITE && echo ECR_REPO_URI_SIDECAR=$ECR_REPO_URI_SIDECAR
ECR_REPO_URI_WEBSITE=654654314383.dkr.ecr.ap-southeast-2.amazonaws.com/website
ECR_REPO_URI_SIDECAR=654654314383.dkr.ecr.ap-southeast-2.amazonaws.com/sidecar
登录ECR
ec2-user:~/environment/environment/eksLabRepo (main) $ aws ecr get-login-password \
> --region ${AWS_DEFAULT_REGION} \
> | docker login \
> --username AWS \
> --password-stdin $ACCOUNT_NUMBER.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
上传镜像到ECR
c2-user:~/environment/environment/eksLabRepo (main) $ docker tag website:latest $ECR_REPO_URI_WEBSITE:latest
ec2-user:~/environment/environment/eksLabRepo (main) $ docker push $ECR_REPO_URI_WEBSITE:latest
The push refers to repository [654654314383.dkr.ecr.ap-southeast-2.amazonaws.com/website]
5f70bf18a086: Pushed
latest: digest: sha256:35d429413b45d69f42da254cb875bc77774a0df50f1327888b071b6038b886da size: 4480
ec2-user:~/environment/environment/eksLabRepo (main) $ docker tag sidecar:latest $ECR_REPO_URI_SIDECAR:latest
ec2-user:~/environment/environment/eksLabRepo (main) $ docker push $ECR_REPO_URI_SIDECAR:latest
The push refers to repository [654654314383.dkr.ecr.ap-southeast-2.amazonaws.com/sidecar]
254e6068aa48: Pushed
latest: digest: sha256:da1998c5b425dc986463ca94578c11f32ec10fcab0f9711f07f0c9185e4e8e86 size: 3242
检查eks cluster 状态
ec2-user:~/environment/environment/eksLabRepo (main) $ aws eks describe-cluster \
> --name eks-lab-cluster \
> --query 'cluster.status' \
> --output text
ACTIVE
在EKS上创建AWS Load Balancer Controller
export ACCOUNT_NUMBER=$(aws sts get-caller-identity --query 'Account' --output text)
eksctl utils associate-iam-oidc-provider --region ap-southeast-2 --cluster eks-lab-cluster --approve
eksctl create iamserviceaccount --cluster=eks-lab-cluster --namespace=kube-system --name=aws-load-balancer-controller --role-name "AmazonEKSLoadBalancerControllerRole" --attach-policy-arn=arn:aws:iam::$ACCOUNT_NUMBER:policy/AWSLoadBalancerControllerIAMPolicy --approve
sleep 5
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=eks-lab-cluster --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
分析每条命令的作用
下面的命令将 oidc provider和 eks 关联起来
eksctl utils associate-iam-oidc-provider --region ap-southeast-2 --cluster eks-lab-cluster --approve
下面的命令在eks中创建服务账号并和aws role , policy 关联起来。
eksctl create iamserviceaccount --cluster=eks-lab-cluster --namespace=kube-system --name=aws-load-balancer-controller --role-name "AmazonEKSLoadBalancerControllerRole" --attach-policy-arn=arn:aws:iam::$ACCOUNT_NUMBER:policy/AWSLoadBalancerControllerIAMPolicy --approve
检查aws load balancer创建成功
kubectl get pods \ -n kube-system \ --selector=app.kubernetes.io/name=aws-load-balancer-controller
部署应用
-
创建命令空间
kubectl create namespace containers-lab
-
准备yaml文件
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: containers-lab
name: eks-lab-deploy
labels:
app: eks-app
spec:
replicas: 3
selector:
matchLabels:
app: lab-app
template:
metadata:
labels:
app: lab-app
spec:
containers:
- name: website
image: $ECR_REPO_URI_WEBSITE:latest ## <-- Placeholder replaced with environment variable
ports:
- containerPort: 80
volumeMounts:
- mountPath: /var/metadata
name: metadata-vol
- name: sidecar
image: $ECR_REPO_URI_SIDECAR:latest ## <-- Placeholder replaced with environment variable
volumeMounts:
- mountPath: /var/metadata
name: metadata-vol
volumes:
- name: metadata-vol
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: lab-service
namespace: containers-lab
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app: lab-app
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: containers-lab
name: lab-ingress
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
kubernetes.io/ingress.class: alb
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: lab-service
port:
number: 80
-
部署应用
kubectl apply -f k8s-all.yaml
kubectl get all -n containers-lab
kubectl get ingress -n containers-lab
通过浏览器访问该地址
Amazon EKS 支持服务账户的 IAM 角色 (IRSA),允许集群操作员将 AWS IAM 角色映射到 Kubernetes 服务账户。
这为在 EKS 上运行并使用其他 AWS 服务的应用程序提供了细粒度的权限管理。这些应用程序可能是使用 S3、任何其他数据服务(RDS、MQ、STS、DynamoDB)或 Kubernetes 组件(如 AWS 负载均衡器控制器或 ExternalDNS)的应用程序。
您可以使用 eksctl 轻松创建 IAM 角色和服务账户对。
工作原理
它通过 EKS 公开的 IAM OpenID Connect 提供程序 (OIDC) 工作,并且必须参考 IAM OIDC 提供程序(特定于给定的 EKS 集群)以及对其将绑定到的 Kubernetes 服务帐户的引用来构建 IAM 角色。创建 IAM 角色后,服务帐户应将该角色的 ARN 作为注释 (eks.amazonaws.com/role-arn)。默认情况下,将创建或更新服务帐户以包含角色注释,可以使用标志 --role-only 禁用此功能。
在 EKS 内部,有一个准入控制器,它根据 pod 使用的服务帐户上的注释将 AWS 会话凭据分别注入角色的 pod 中。凭据将由 AWS_ROLE_ARN 和 AWS_WEB_IDENTITY_TOKEN_FILE 环境变量公开。
在 eksctl 中,资源的名称是 iamserviceaccount,它代表 IAM 角色和服务帐户对。
在开始之前,我先介绍一下IAM 角色和策略:
角色
您可以在账户中创建具有特定权限的 IAM 身份。IAM 角色与 IAM 用户有一些相似之处。角色和用户都是具有权限策略的 AWS 身份,这些权限策略决定了身份在 AWS 中可以做什么和不能做什么。但是,角色并非唯一地与一个人相关联,而是由任何需要它的人担任。此外,角色没有与之关联的标准长期凭证(例如密码或访问密钥)。相反,当您担任角色时,它会为您的角色会话提供临时安全凭证。
角色可供以下人员使用:
-
与角色位于同一 AWS 账户中的 IAM 用户
-
与角色位于不同 AWS 账户中的 IAM 用户
-
AWS 提供的 Web 服务,例如 Amazon Elastic Compute Cloud (Amazon EC2)
-
由与 SAML 2.0 或 OpenID Connect 兼容的外部身份提供商 (IdP) 服务或定制的身份代理进行身份验证的外部用户。
策略
您可以通过创建策略并将其附加到 IAM 身份(用户、用户组或角色)或 AWS 资源来管理 AWS 中的访问权限。策略是 AWS 中的对象,当与身份或资源关联时,它定义其权限。当 IAM 主体(用户或角色)发出请求时,AWS 会评估这些策略。策略中的权限决定是允许还是拒绝请求。大多数策略都以 JSON 文档的形式存储在 AWS 中。AWS 支持六种类型的策略:基于身份的策略、基于资源的策略、权限边界、组织 SCP、ACL 和会话策略。
IAM 策略定义操作的权限,无论您使用何种方法执行操作。例如,如果策略允许 GetUser 操作,则具有该策略的用户可以从 AWS 管理控制台、AWS CLI 或 AWS API 获取用户信息。创建 IAM 用户时,您可以选择允许控制台或编程访问。如果允许控制台访问,则 IAM 用户可以使用其登录凭证登录控制台。如果允许编程访问,用户可以使用访问密钥来使用 CLI 或 API。
默认情况下,当 Amazon EKS 集群具有 EC2 实例工作节点时,Pod 调用 AWS API 的权限有限。它们继承了工作节点的 EC2 实例配置文件。实例配置文件附加了以下 AWS 托管策略:
-
AmazonEKSWorkerNodePolicy
-
AmazonEC2ContainerRegistryReadOnly
-
AmazonSSMManagedInstanceCore
-
AmazonEKS_CNI_Policy
在这种情况下,已部署的应用程序会调用 API 来检索数据,但没有所需的权限。最佳实践是使用服务账户的 IAM 角色功能为在 Kubernetes pod 上运行的应用程序提供所需的权限。服务账户的 IAM 角色功能提供以下好处:
-
最小权限:通过使用服务账户的 IAM 角色功能,您无需为该节点上的 pod 提供节点 IAM 角色的扩展权限来调用 AWS API。您可以将 IAM 权限范围限定到服务账户,只有使用该服务账户的 pod 才能访问这些权限。此功能还意味着您不需要第三方解决方案,例如 kiam 或 kube2iam。
-
凭证隔离:容器只能检索与其所属服务账户关联的 IAM 角色的凭证。容器永远无法访问属于另一个 pod 的另一个容器的凭证。
-
可审计性:可通过 AWS CloudTrail 提供访问和事件日志记录,以帮助确保追溯审计。
需要为EKS pod创建IAM role.
提前创建一个policy eks-lab-read-policy,这里我用了默认的管理员权限,实际生产环境中不建议这样做。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
然后运行下面的命令来创建 iamserviceaccount
ec2-user:~/environment/environment/eksLabRepo/eks-lab-app (main) $ eksctl create iamserviceaccount --name iampolicy-sa-3 --namespace containers-lab --cluster eks-lab-cluster --role-name "eksRole4serviceaccount3" --attach-policy-arn arn:aws:iam::$ACCOUNT_NUMBER:policy/eks-lab-read-policy --approve --override-existing-serviceaccounts
2024-07-19 04:51:43 [ℹ] 2 existing iamserviceaccount(s) (default/s3-read-only,kube-system/aws-load-balancer-controller) will be excluded
2024-07-19 04:51:43 [ℹ] 1 iamserviceaccount (containers-lab/iampolicy-sa-3) was included (based on the include/exclude rules)
2024-07-19 04:51:43 [!] metadata of serviceaccounts that exist in Kubernetes will be updated, as --override-existing-serviceaccounts was set
2024-07-19 04:51:43 [ℹ] 1 task: {
2 sequential sub-tasks: {
create IAM role for serviceaccount "containers-lab/iampolicy-sa-3",
create serviceaccount "containers-lab/iampolicy-sa-3",
} }2024-07-19 04:51:43 [ℹ] building iamserviceaccount stack "eksctl-eks-lab-cluster-addon-iamserviceaccount-containers-lab-iampolicy-sa-3"
2024-07-19 04:51:44 [ℹ] deploying stack "eksctl-eks-lab-cluster-addon-iamserviceaccount-containers-lab-iampolicy-sa-3"
2024-07-19 04:51:44 [ℹ] waiting for CloudFormation stack "eksctl-eks-lab-cluster-addon-iamserviceaccount-containers-lab-iampolicy-sa-3"
2024-07-19 04:52:14 [ℹ] waiting for CloudFormation stack "eksctl-eks-lab-cluster-addon-iamserviceaccount-containers-lab-iampolicy-sa-3"
2024-07-19 04:52:14 [ℹ] created serviceaccount "containers-lab/iampolicy-sa-3"
验证服务账号的状态
ec2-user:~/environment/environment/eksLabRepo/eks-lab-app (main) $ kubectl get sa iampolicy-sa-3 -n containers-lab -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::654654314383:role/eksRole4serviceaccount3
creationTimestamp: "2024-07-19T04:52:14Z"
labels:
app.kubernetes.io/managed-by: eksctl
name: iampolicy-sa-3
namespace: containers-lab
resourceVersion: "316598"
uid: 6d8b23ce-b9ca-4c67-be54-fb58eca29a82
更新deployment 的服务账号
ec2-user:~/environment/environment/eksLabRepo/eks-lab-app (main) $ kubectl set serviceaccount \
> deployment eks-lab-deploy \
> iampolicy-sa-3 -n containers-lab
deployment.apps/eks-lab-deploy serviceaccount updated
验证deployment服务账号已经更新
ec2-user:~/environment/environment/eksLabRepo/eks-lab-app (main) $ kubectl describe deployment.apps/eks-lab-deploy \
> -n containers-lab | grep 'Service Account'
Service Account: iampolicy-sa-3
获得 ingress的ALB地址
ec2-user:~/environment/environment/eksLabRepo/eks-lab-app (main) $ kubectl get ingress -n containers-lab
NAME CLASS HOSTS ADDRESS PORTS AGE
lab-ingress <none> * k8s-containe-labingre-3207ffb4ea-1472706054.ap-southeast-2.elb.amazonaws.com 80 17m
打开浏览器访问该地址,可以看到 aws account, cluster name kubernetes version 信息已经可以正常显示。