우리는 이제 총 세 개의 클러스터를 관리하게 되었습니다.
관리할 클러스터가 늘어난다는 것은 그때마다 아르고에 클러스터를 등록해 주는 작업을 해야 한다는 의미지요.
관리 클러스터가 알아서 등록해 줄 수는 없을까?
생각이 들겁니다.
자, 해봅시다.
우리의 목표는 새로 생성되는 Kubernetes cluster를 관리 클러스터 ArgoCD에 자동으로 추가하는 것입니다.
결론부터 말씀드리면, Kyverno를 이용하면 됩니다. Kyverno의 ClusterPolicy
는 리소스의 유효성을 검사할 수 있을 뿐만 아니라 리소스가 생성되거나 업데이트될 때 추가 리소스를 생성할 수 있습니다.
ArgoCD는 Kubernetes 내부의 각 워크로드 클러스터에 대한 세부 정보를 argocd.argoproj.io/secret-type: cluster
라벨이 붙은 secret으로 저장합니다.
반면에 Kubernetes는 특정 클러스터 전용 네임스페이스 내부 cluster
에 클러스터 자격 증명을 저장합니다. Kubernetes 클러스터 정보가 등록될 때마다 ArgoCD가 인식할 수 있는 정보로 변환해 주면 됩니다.
시작하기 전에 Kubernetes에 Kyverno를 설치해야 합니다.
먼저 Helm 저장소를 추가해 보겠습니다.
$ helm repo add kyverno https://kyverno.github.io/kyverno/
아래 명령을 사용하여 네임스페이스 kyverno
에 설치할 수 있습니다.
$ helm install kyverno kyverno/kyverno -n kyverno --create-namespace
우선 일반 클러스터 유형에 대한 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 정책을 작성해 봅니다.
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)으로 구분하고 각각 다른 정책이 적용되도록 정책을 추가하였습니다.
❯ 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
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
❯ k apply -f argo-cluster-generation-from-capi-non-managed-cluster.yaml
❯ k apply -f argo-cluster-generation-from-capi-managed-cluster.yaml
❯ 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