亚马逊AWS官方博客

使用 Kyverno 和 ArgoCD 实施 Kubernetes/EKS 最佳实践

随着容器化和 Kubernetes 容器编排工具的普及,在 AWS 云上,我们发现,使用 AWS EKS 来部署他们的关键业务系统工作负载的企业越来越多,对运维人员和开发人员的容器化日常使用提出来越来越多的需求。在企业内部,拥有越来越多的 EKS 集群,并制定了越来越多属于企业专有的策略和标准,比如在容器部署的过程中,强制每一个容器必须有它的资源配额。

在开始的时候,企业使用约定俗成或者标准操作文档的形式,让这些策略和标准得以保障。在自动化程度越来越高,比如 CICD/GitOps 等成为企业应用自动构建和部署的标准,在频率越来越高的自动化构建和部署生命周期内,如何在众多的业务线之间的越来越多的容器集群保证企业制定的策略和标准得以实施成为一个非常大的挑战。

为解决这些问题和挑战,出现了越来越多的通过自动化策略工具和框架,来让我们使用插件的形式,在 Kubernetes/EKS 集群中制定客制化策略,让组织的策略和标准在 DevOps/GitOps 的自动化流水线生命周期内得到保障。

本文将通过 ArgoCD 和 Kyverno 来演示,如何在 ArgoCD 的 GitOps 过程中,使用 Kyverno 的客制化策略,强制组织的策略在 EKS 部署应用的时候得到保证。

我们假设您已经熟悉一定的 Kubernetes 和 AWS EKS 的相关知识。本文将从使用 ArgoCD 结合 kyverno 在 Kubernetes(AWS EKS)集群在 GitOps 的 lifecycle 确保应用能够符合组织和公司的最佳实践,比如必须对 Kubernetes 的 deployment 进行资源申请的 quota 限制等策略得以保证。

我们假设已经创建一个 AWS EKS 集群,客户端用户具有足够的权限进行集群资源的操作。本文使用的 AWS EKS 集群版本为 1.22,Argo-CD 版本为 2.4.7, Kyverno 版本为 1.7.2。

基础概念

GitOps

GitOps 是一个软件开发框架,能够让我们基于 Git 代码仓库,比如 GitLab 和 GitHub,作为我们应用程序定义和声明的单一事实来源,管理我们的 IT 基础设施,持续交付软件应用程序。GitOps 是 DevOps 领域的一个子集,综合了基础架构即代码(IaC)和 DevOps 最佳实践,帮助我们管理架构,可以轻松地根据 Git 存储库的状态按需复制我们的云基础架构, 比如从UAT(User Acceptance Testing)环境复制提升到 Production 环境。

Amazon EKS

Amazon EKS 是一项托管 Kubernetes 服务,用于在 AWS Cloud 和本地数据中心上运行 Kubernetes。在云中,Amazon EKS 可自动管理负责安排容器、管理应用程序可用性、存储集群数据和其他关键任务的 Kubernetes 控制面板节点的可用性和可扩展性。通过 Amazon EKS,您可以利用 AWS 基础设施的所有性能、规模、可靠性和可用性,以及与 AWS 网络和安全服务的集成。EKS 在本地提供一个一致且完全支持的 Kubernetes 解决方案,其中集成了工具和 AWS Outposts、虚拟机或裸机服务器的简单部署。

Argo-CD

Argo CD 是 Kubernetes 生态中非常受欢迎的 GitOps 工具。Argo CD 可以整合我们的代码仓库,轻松定义一组应用程序,并在多 Kubernetes 集群中轻松部署我们定义的应用程序,部署完成之后 Argo CD 可以持续监控应用的状态,根据我们对应用的声明捕捉偏移,进行持续的部署。

Kyverno

Kyverno 是一个基于 Kubernetes 的策略引擎,让我们可以使用自定义资源来定义策略,比如 POD 的 resource request 和 limit,它通过使用 admission webhook,可以审计策略违规或甚至阻止对集群进行更改,比如应用程序发布的请求。

安装配置 ArgoCD

ArgoCD 的安装配置非常简单,我们只需要创建 ArgoCD 的命名空间,并通过 ArgoCD 官网提供的脚本进行安装,命令如下:

kubectl create namespace argocd 
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

部署完成之后,我们可以看到,在 ArgoCD 的命名空间下面,有一个 argocd-server 的 service,我们将通过该 service 进行 ArgoCD 的管理和后续的操作:

ArgoCD 默认情况下,不对外暴露 argocd-server 的服务,我们将通过 AWS ALB 进行 ArgoCD 服务的对外发布,因此我们需要更改 argocd-server 的服务定义:

argocd-server 修改够的定义文件如下:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/name: argocd-server
    app.kubernetes.io/part-of: argocd
  name: argocd-server
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  - name: https
    port: 443
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/name: argocd-server
  type: ClusterIP

我们定义一个 ingress 来暴露 argocd-server,因为 argocd-server 默认使用 443 端口,因此我们在 alb ingress 上需要使用一个 ssl 证书,该证书需要提前在 ACM 进行导入。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: argocd
  name: argocd-svc-ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/backend-protocol: HTTPS
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/certificate-arn: 'arn:aws:acm:ap-east-1:xxxxxxxxxxxx:certificate/01dbd1fe-c9e7-4eab-b99a-e9dd21452908'
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
    alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
spec:
  rules:
    - http:
        paths:
        - pathType: ImplementationSpecific
          backend:
            service:
              name: argocd-server
              port:
                number: 443
          pathType: ImplementationSpecific   

ingress 部署完成之后,我们可以通过 alb 进行 argocd server 的访问,argocd-server 的用户名为 admin,默认的密码可以通过如下的命令找到,登录之后,我们可以对密码进行更改:

export ARGO_PWD=kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d         

我们在客户端使用 argocd cli 进行集群的绑定,在绑定之前,我们需要登录 argocd server。

登录命令为 argocd login:

登录完成之后我们在 argocd 中加入 EKS 集群 — argocd cluster add:

集群加入之后,我们可以在 argocd 上看到加入的集群信息,包括集群的 Server URL,arn 等:

待集群加入成功之后,我们可以在该集群上创建一个简单的 application,在本文中我们使用 github 应用作为示例。

我们在默认工程创建一个示范应用:

并指定我们的 github 分支信息和路径信息,并选择应用对应的 EKS 的集群:

示例项目的 git 结构如下,eks 文件夹是我们存放应用部署声明文件(yaml)的位置:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: eks-sample-linux-deployment
  namespace: test
  labels:
    app: eks-sample-linux-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: eks-sample-linux-app
  template:
    metadata:
      labels:
        app: eks-sample-linux-app
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/arch
                operator: In
                values:
                - amd64
                - arm64
      containers:
      - name: nginx
        image: public.ecr.aws/nginx/nginx:1.21
        ports:
        - name: http
          containerPort: 80
        imagePullPolicy: IfNotPresent
      nodeSelector:
        kubernetes.io/os: linux

我们在 EKS 文件夹内部存放 nginx 示范应用的 deployment 和 service:

部署完成之后,可以在 ArgoCD 的 UI 和 EKS 集群观察到该应用:

通过 kubectl 客户端查看容器 test namespace 内部的应用状态:

安装配置 Kyverno

我们根据 EKS 版本选择对应的 Kyverno,本文中我们采用 1.7.2

需要注意,Kyverno 的版本和 helm 版本的对应关系,我们后续将通过 helm 来安装 Kyverno:

使用 helm 进行 kyverno 的安装:

helm install kyverno kyverno/kyverno -n kyverno --create-namespace --set replicaCount=3 --version v2.5.2

安装完成之后我们可以看到多了一个 kyverno 的 namespace 及其对应的应用资源:

Kyverno 的应用信息:

紧接着我们创建一个 ClusterPolicy,用于检测 test 这个 namespace 部署的应用是否加上了资源请求限额:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-requests
  annotations:
    policies.kyverno.io/title: Require Resource Requests
    policies.kyverno.io/category: Best Practices
    policies.kyverno.io/severity: medium
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/minversion: 1.6.0
    policies.kyverno.io/description: >-
      As application workloads share cluster resources, it is important to specify resources
      requested and consumed by each Pod. Requiring resource requests per Pod is recommended,
      especially for memory and CPU. If a Namespace level request is specified, defaults will
      automatically be applied to each Pod based on the LimitRange configuration. This policy
      validates that all containers have something specified for memory and CPU requests.
spec:
  validationFailureAction: enforce
  background: true
  rules:
  - name: validate-resource-requests
    match:
      any:
      - resources:
          kinds:
          - Pod
          namespaces:
          - test
    validate:
      message: "CPU and memory resource requests are required."
      pattern:
        spec:
          containers:
          - resources:
              requests:
                memory: "?*"
                cpu: "?*"

该策略部署之后我们可以看到已经部署的应用,因为没有资源请求的限额,策略已经出示 warning:

查看 ClusterPolicy 的状态信息:

应用部署

将应用 resync,我们可以在 ArgoCD 里面看到该应用已经 degraded,并可以看到应用部署不成功的错误信息提示因为没有为应用加上资源请求限额,违反了策略。

ArgoCD 发现应用 degraded:

在 argo 的应用事件信息上,发现有违反策略应用部署失败的信息:

调整应用部署声明文件,加上 resource 的 requests 和 limits,重新部署应用之后,我们可以看到应用成功发布。

修改应用声明文件,加上资源的请求和限制额度:

应用成功部署:

查看 argocd 控制台应用整体状态:

通过 kubectl 客户端查看应用的部署后状态:

总结

本文中的示例演示了策略注入控制功能,Kyverno 作为策略引擎,通过自动更新资源来强制执行策略,使其符合进入集群的标准,让开发和测试变得更加灵活和便捷。

Kyverno 和 Argo CD 联合在一起,可以根据我们的实际需要进行扩展,在很大程度上让 Kubernetes 的集群管理,应用维护和变更变得更加简单。

参考链接

ArgoCD:https://argo-cd.readthedocs.io/en/stable/getting_started/

Kyverno:https://kyverno.io/docs/introduction/

AWS Blog:https://thinkwithwp.com/blogs/opensource/connecting-aws-managed-services-to-your-argo-cd-pipeline-with-open-source-crossplane/

使用 Argo CD 和 Kyverno 的 3 个基本技巧 :  https://nirmata.com/2023/03/09/3-essential-tips-for-using-argo-cd-and-kyverno/

本篇作者

李俊杰

AWS 解决方案架构师,负责基于 AWS 的云计算方案的咨询与架构设计,同时致力于容器方面研究和推广。在加入 AWS 之前曾在金融行业 IT 部门负责传统金融系统的现代化改造,对传统应用的改造,容器化具有丰富经验。