멀티 클러스터 #5 Kyverno

codelab·2023년 10월 21일
0

Multi Cloud

목록 보기
9/11

우리는 이제 총 세 개의 클러스터를 관리하게 되었습니다.
관리할 클러스터가 늘어난다는 것은 그때마다 아르고에 클러스터를 등록해 주는 작업을 해야 한다는 의미지요.

관리 클러스터가 알아서 등록해 줄 수는 없을까?

생각이 들겁니다.
자, 해봅시다.

Kyverno를 사용하여 ArgoCD 클러스터 자동 추가

우리의 목표는 새로 생성되는 Kubernetes cluster를 관리 클러스터 ArgoCD에 자동으로 추가하는 것입니다.
결론부터 말씀드리면, Kyverno를 이용하면 됩니다. Kyverno의 ClusterPolicy 는 리소스의 유효성을 검사할 수 있을 뿐만 아니라 리소스가 생성되거나 업데이트될 때 추가 리소스를 생성할 수 있습니다. 

ArgoCD는 Kubernetes 내부의 각 워크로드 클러스터에 대한 세부 정보를 argocd.argoproj.io/secret-type: cluster 라벨이 붙은 secret으로 저장합니다. 
반면에 Kubernetes는 특정 클러스터 전용 네임스페이스 내부 cluster에 클러스터 자격 증명을 저장합니다. Kubernetes 클러스터 정보가 등록될 때마다 ArgoCD가 인식할 수 있는 정보로 변환해 주면 됩니다.


Kyverno를 설치

시작하기 전에 Kubernetes에 Kyverno를 설치해야 합니다. 
먼저 Helm 저장소를 추가해 보겠습니다.

$ helm repo add kyverno https://kyverno.github.io/kyverno/

아래 명령을 사용하여 네임스페이스 kyverno에 설치할 수 있습니다.

$ helm install kyverno kyverno/kyverno -n kyverno --create-namespace

Argo Cluster Secret Generation From CAPI Secret

Non-Managed Workload Cluster

우선 일반 클러스터 유형에 대한 Kyverno Policy 정의를 위해 필요한 정보들을 살펴봅시다.

필요한 정보

# 클러스터 정보
❯ k get cluster prod -n cluster-prod -o yaml
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
    ...
  creationTimestamp: "2023-10-22T11:43:43Z"
  finalizers:
  - cluster.cluster.x-k8s.io
  generation: 3
  labels:
    cluster.x-k8s.io/cluster-name: prod
  name: prod
  namespace: cluster-prod
  resourceVersion: "569798"

# 시크릿
k get secrets prod-kubeconfig -n cluster-prod -o yaml

# 시크릿 중 데이터 부분은 base64로 인코딩되어 있기 때문에 디코드해 줍니다.
# kubernetes config 파일의 내용이 그대로 들어있는 걸 확인할 수 있습니다.
k get secrets prod-kubeconfig -n cluster-prod -o json | jq .data.value | tr -d '"' | base64 --decode

위 정보들을 바탕으로 Kyverno 정책을 작성해 봅니다.

Kyverno Policy 정의

cat <<EOT> argo-cluster-generation-from-capi-non-managed-cluster.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: argo-cluster-generation-from-capi-non-managed-cluster
  annotations:
    policies.kyverno.io/title: Argo Cluster Secret Generation From CAPI Secret
    policies.kyverno.io/category: Argo
    policies.kyverno.io/severity: medium
    policies.kyverno.io/subject: Secret
    kyverno.io/kyverno-version: 1.7.1
    policies.kyverno.io/minversion: 1.7.0
    kyverno.io/kubernetes-version: "1.23"
    policies.kyverno.io/description: >-
      This policy generates and synchronizes Argo CD cluster secrets from clusters.
spec:
  # 정책은 기본적으로 기존 리소스(우리는 이미 워크로드 클러스터를 생성하였습니다.)에 적용되지 않습니다.
  # 기존 리소스에 정책을 적용하기 위해 매개변수의 값을 true로 설정합니다.
  generateExistingOnPolicyUpdate: true 
  rules:
  - name: source-non-local-cluster-and-capi-secret
    # 정책은 kind가 cluster.x-k8s.io/v1beta1/Cluster 와 일치하는 항목에 대해 트리거 됩니다.
    match:
      all:
      - resources:
          kinds:
          - cluster.x-k8s.io/v1beta1/Cluster
    exclude:
      any:
      - resources:
          names:
          - "*managed*"
    # context의 필드를 사용하여 변수를 설정합니다.      
    context:
    - name: namespace
      variable:
        value: "{{request.object.metadata.namespace}}"
        jmesPath: 'to_string(@)'
    - name: clusterName
      variable:
        value: "{{request.object.metadata.name}}"
        jmesPath: 'to_string(@)'
    - name: clusterPrefixedName
      variable:
        value: "{{ join('-', ['cluster', clusterName]) }}"
        jmesPath: 'to_string(@)'
    - name: kubeconfigName
      variable:
        value: "{{ join('-', [clusterName, 'kubeconfig']) }}"
        jmesPath: 'to_string(@)'
    - name: extraLabels
      variable:
        value:
          argocd.argoproj.io/secret-type: cluster
          clusterId: "{{ clusterName }}"
    - name: metadataLabels
      variable:
        jmesPath: request.object.metadata.labels
        default: {}
    - name: metadataLabels
      variable:
        jmesPath: merge(metadataLabels, extraLabels)
    # 정책에는 소스 Secret 필드에 대한 액세스 권한이 있으므로 API 데이터를 얻을 수 있습니다.
    - name: kubeconfigData
      apiCall:
        urlPath: "/api/v1/namespaces/{{request.object.metadata.namespace}}/secrets/{{kubeconfigName}}"
        jmesPath: 'data | to_string(@)'
    - name: caData
      variable:
        value: "{{ kubeconfigData | parse_yaml(@).value | base64_decode(@) | parse_yaml(@).clusters[0].cluster.\"certificate-authority-data\" }}"
        jmesPath: 'to_string(@)'
    - name: server
      variable:
        value: "{{ kubeconfigData | parse_yaml(@).value | base64_decode(@) | parse_yaml(@).clusters[0].cluster.server }}"
        jmesPath: 'to_string(@)'
    - name: cert
      variable:
        value: "{{ kubeconfigData | parse_yaml(@).value | base64_decode(@) | parse_yaml(@).users[0].user.\"client-certificate-data\" }}"
        jmesPath: 'to_string(@)'
    - name: key
      variable:
        value: "{{ kubeconfigData | parse_yaml(@).value | base64_decode(@) | parse_yaml(@).users[0].user.\"client-key-data\" }}"
        jmesPath: 'to_string(@)'
    generate:
      synchronize: true
      apiVersion: v1
      kind: Secret
      name: "{{ clusterPrefixedName }}"
      namespace: argocd
      data:
        kind: Secret
        metadata:
          labels:
            "{{ metadataLabels }}"
        type: Opaque
        stringData:
          name: "{{ clusterPrefixedName }}"
          server: "{{ server }}"
          config: |
            {
              "tlsClientConfig": {
                "insecure": false,
                "caData": "{{ caData }}",
                "certData": "{{ cert }}",
                "keyData": "{{ key }}"
              }
            }  

EOT

소스 Secret의 네임스페이스에서 클러스터 이름을 검색할 수 있습니다. 생성된 보안 비밀은 라벨 argocd.argoproj.io/secret-type: cluster를 포함해야 하며 argocd 네임스페이스에 배치되어야 합니다. Secret 변수에 필요한 모든 필드를 채웁니다. ArgoCD는 cluster의 이름과 동일한 sectet으로 Kubernetes에 내부적으로 접근할 수 있습니다.


정책 작성을 하다보니 일반 Workload Cluster와 Managed Workload Cluster의 kubeconfig의 구성이 다르다는 것을 발견했습니다. 일반 Workload Cluster는 ca, cert, key로 구성되어 있는 반면, Managed Workload Cluster는 ca, token 으로 구성되어 있습니다. 따라서 정책이 적용되는 대상을 이름(managed)으로 구분하고 각각 다른 정책이 적용되도록 정책을 추가하였습니다.

Managed Workload Cluster

Kyverno Policy 정의 소스 확인

❯ k get cluster managed -n cluster-managed -o yaml
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
    ...
  creationTimestamp: "2023-10-22T11:43:43Z"
  finalizers:
  - cluster.cluster.x-k8s.io
  generation: 3
  labels:
    cluster.x-k8s.io/cluster-name: managed
  name: managed
  namespace: cluster-managed
  resourceVersion: "690094"

k get secrets managed-kubeconfig -n cluster-managed -o yaml

k get secrets managed-kubeconfig -n cluster-managed -o json | jq .data.value | tr -d '"' | base64 --decode

Kyverno Policy 정의

cat <<EOT> argo-cluster-generation-from-capi-managed-cluster.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: argo-cluster-generation-from-capi-managed-cluster
  annotations:
    policies.kyverno.io/title: Argo Cluster Secret Generation From Managed CAPI Secret
    policies.kyverno.io/category: Argo
    policies.kyverno.io/severity: medium
    policies.kyverno.io/subject: Secret
    kyverno.io/kyverno-version: 1.7.1
    policies.kyverno.io/minversion: 1.7.0
    kyverno.io/kubernetes-version: "1.23"
    policies.kyverno.io/description: >-
      This policy generates and synchronizes Argo CD cluster secrets from managed cluster.
spec:
  generateExistingOnPolicyUpdate: true
  rules:
  - name: sourcet-non-local-cluster-and-managed-capi-secret
    match:
      all:
      - resources:
          kinds:
          - cluster.x-k8s.io/v1beta1/Cluster
          names:
          - "*managed*"
    context:
    - name: namespace
      variable:
        value: "{{request.object.metadata.namespace}}"
        jmesPath: 'to_string(@)'
    - name: clusterName
      variable:
        value: "{{request.object.metadata.name}}"
        jmesPath: 'to_string(@)'
    - name: clusterPrefixedName
      variable:
        value: "{{ join('-', ['cluster', clusterName]) }}"
        jmesPath: 'to_string(@)'
    - name: kubeconfigName
      variable:
        value: "{{ join('-', [clusterName, 'kubeconfig']) }}"
        jmesPath: 'to_string(@)'
    - name: extraLabels
      variable:
        value:
          argocd.argoproj.io/secret-type: cluster
          clusterId: "{{ clusterName }}"
    - name: metadataLabels
      variable:
        jmesPath: request.object.metadata.labels
        default: {}
    - name: metadataLabels
      variable:
        jmesPath: merge(metadataLabels, extraLabels)
    - name: kubeconfigData
      apiCall:
        urlPath: "/api/v1/namespaces/{{request.object.metadata.namespace}}/secrets/{{kubeconfigName}}"
        jmesPath: 'data | to_string(@)'
    - name: caData
      variable:
        value: "{{ kubeconfigData | parse_yaml(@).value | base64_decode(@) | parse_yaml(@).clusters[0].cluster.\"certificate-authority-data\" }}"
        jmesPath: 'to_string(@)'
    - name: server
      variable:
        value: "{{ kubeconfigData | parse_yaml(@).value | base64_decode(@) | parse_yaml(@).clusters[0].cluster.server }}"
        jmesPath: 'to_string(@)'
    - name: token
      variable:
        value: "{{ kubeconfigData | parse_yaml(@).value | base64_decode(@) | parse_yaml(@).users[0].user.token }}"
        jmesPath: 'to_string(@)'
    generate:
      synchronize: true
      apiVersion: v1
      kind: Secret
      name: "{{ clusterPrefixedName }}"
      namespace: argocd
      data:
        kind: Secret
        metadata:
          labels:
            "{{ metadataLabels }}"
        type: Opaque
        stringData:
          name: "{{ clusterPrefixedName }}"
          server: "{{ server }}"
          config: |
            {
              "bearerToken": "{{ token }}",
              "tlsClientConfig": {
                "insecure": false,
                "caData": "{{ caData }}"
              }
            }
EOT

Kyverno Policy 적용

❯ k apply -f argo-cluster-generation-from-capi-non-managed-cluster.yaml
❯ k apply -f argo-cluster-generation-from-capi-managed-cluster.yaml

Kyverno Policy 적용 확인

❯ k get secrets -n argocd
NAME                                        TYPE                 DATA   AGE
argo-prod-tls                               kubernetes.io/tls    2      14h
argocd-initial-admin-secret                 Opaque               1      14h
argocd-notifications-secret                 Opaque               0      14h
argocd-secret                               Opaque               5      14h
cluster-kubernetes.default.svc-3396314289   Opaque               3      13h
cluster-managed                             Opaque               3      76s # 추가
cluster-prod                                Opaque               3      12h # 추가
sh.helm.release.v1.argocd.v1                helm.sh/release.v1   1      14


참고

https://piotrminkowski.com/2022/12/09/manage-multiple-kubernetes-clusters-with-argocd/
/argo/argo-cluster-generation-from-rancher-capi/argo-cluster-generation-from-rancher-capi.yaml

profile
Think about a better architecture

0개의 댓글