LabHub
배우기 러닝패스 코스

GitOpsとArgo CD

調整ループを手で一周回す

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

리컨실 루프가 한 바퀴 도는 동안 무슨 일이 벌어지는지를 직접 만들어 봅니다. 원하는 상태를 소유 표시와 함께 적용하고, 흔들리는 필드를 걷어 내고, 무시하기로 한 필드를 빼고 비교하고, 삭제 후보를 골라내고, 빈 결과를 거부하는 데까지 갑니다.

왜 중요한가

gitops-manifest 실습에서 여러분은 손으로 드리프트를 만들고 손으로 되돌렸습니다. 여기서 질문 하나만 하면 GitOps 의 마지막 조각이 나옵니다. 그 적용을 누가 치는가. 사람이 친다면 그것은 잘 정리된 배포 스크립트이지 GitOps 가 아닙니다.

컨트롤러가 대신 치게 만들려면 판단 기준이 코드로 있어야 합니다. 무엇을 흔들리는 값으로 볼 것인지, 어떤 필드는 남이 소유하도록 둘 것인지, 무엇을 이 앱의 소유로 표시할 것인지, 그리고 결과가 비었을 때 어떻게 할 것인지입니다. selfHeal 은 최악의 경우 수동 변경이 사라지는 것이고 prune 은 최악의 경우 데이터가 사라지는 것이라, 위험의 급이 다릅니다. 그 판단을 스크립트로 적어 보면 스위치 이름 뒤에 무엇이 있는지가 분명해집니다.

환경

이 파드에는 ArgoCD 컨트롤러가 없습니다. Application 오브젝트는 만들 수 있지만 스스로 Synced 로 바뀌지는 않습니다. 그래서 이 실습은 선언을 적어 두고 그 선언대로 움직이는 도구를 직접 만드는 방식으로 갑니다. 필드 소유권과 서버 사이드 적용, 스케줄링 판정은 kwok 이 띄운 진짜 apiserver 가 처리하므로 그 부분은 실물입니다. 작업 디렉터리는 /root/gitops-sh 이고 그 아래 k8s/·desired/· bin/·out/ 을 씁니다.

단계

  1. Application shop 에 스위치와 무시할 필드를 선언합니다.
  2. 선언 상태를 소유 표시와 함께 서버 사이드로 적용합니다.
  3. bin/normalize.sh 로 흔들리는 필드를 걷어 냅니다.
  4. bin/drift.sh 로 무시할 필드를 빼고 비교합니다.
  5. 드리프트를 만들고 한쪽만 되돌린 뒤 out/selfheal.txt 에 적습니다.
  6. bin/prune.sh 로 삭제 후보를 골라냅니다.
  7. bin/sync.sh 가 빈 결과를 거부하게 만듭니다.
  8. Synced 인데 정상이 아닌 상태를 만들고 out/status.txt 에 정리합니다.

참고

루프를 어떻게 돌릴지 선언한다

/root/gitops-sh/k8s/application.yamlargocd 네임스페이스의 Application shop 을 적어 적용하세요. syncPolicy.automatedprune: true·selfHeal: true·allowEmpty: false, syncOptionsPruneLast=trueServerSideApply=true, 그리고 ignoreDifferencesapps/Deployment/spec/replicas 를 무시하게 하세요. 대상 네임스페이스는 sh-lab 입니다.

이 파드에는 ArgoCD 컨트롤러가 없으므로 이 오브젝트는 스스로 Synced 가 되지 않습니다. 대신 루프를 어떻게 돌릴지 적어 두는 자리로 씁니다. 뒤 단계에서 만드는 스크립트들이 여기 적힌 대로 움직이면 됩니다. allowEmpty 는 기본값이 false 지만 이 실습에서는 의도를 드러내기 위해 명시합니다. ignoreDifferences 는 HPA 처럼 다른 컨트롤러가 정당하게 소유한 필드를 되돌리지 않게 하는 장치입니다.

원하는 상태를 소유 표시와 함께 적용한다

/root/gitops-sh/desired/deployment.yaml(이름 orders)과 configmap.yaml(이름 orders-config)을 만드세요. 둘 다 네임스페이스는 sh-lab 이고 argocd.argoproj.io/tracking-id 어노테이션이 shop: 으로 시작해야 합니다. Deployment 에는 spec.replicas 를 적지 마세요. 그다음 --server-side --field-manager=argocd-controller 로 디렉터리를 통째로 적용하세요.

무시하기로 한 필드는 아예 선언하지 않는 것이 정석입니다. 선언해 두면 그 필드의 소유권을 이쪽이 가져가고, 나중에 HPA 나 사람이 바꾼 값과 매번 부딪힙니다. tracking-id 는 ArgoCD 가 자기 소유를 표시하는 어노테이션이고 <앱>:<그룹>/<종류>:<네임스페이스>/<이름> 꼴입니다. 소유 표시가 없으면 6단계의 삭제 후보 계산에서 그 오브젝트가 보이지 않습니다. 적용한 뒤 kubectl get deploy orders -o yaml --show-managed-fields 로 누가 어느 필드를 소유했는지 보세요.

흔들리는 필드를 걷어 내는 전처리를 만든다

/root/gitops-sh/bin/normalize.sh <매니페스트> 를 만드세요. metadataresourceVersion·uid·generation·creationTimestamp·managedFields 와 최상위 status 를 걷어 낸 YAML 을 표준 출력으로 내보냅니다. 사람이 선언한 것(kind·이름·네임스페이스·어노테이션·spec)은 그대로 남겨야 합니다.

쿠버네티스가 스스로 채우고 계속 바뀌는 값을 그대로 비교하면 아무것도 바꾸지 않아도 매번 차이가 있다고 보고됩니다. 그러면 리컨실 루프가 영원히 멈추지 않습니다. 반대로 너무 많이 지우면 진짜 차이도 못 보게 되니, 무엇을 남길지가 무엇을 지울지만큼 중요합니다. python3yaml 모듈로 읽고 yaml.safe_dump_all 로 내보내면 됩니다. 채점기가 픽스처 하나로 이 스크립트를 실제로 돌려 봅니다.

무시하기로 한 필드를 빼고 비교한다

/root/gitops-sh/bin/drift.sh <선언파일> <실제파일> 을 만드세요. 정규화한 뒤 apps/Deploymentspec.replicas 를 빼고 비교해, 같으면 SYNCED 를 찍고 0으로, 다르면 OUTOFSYNC 를 찍고 1로 끝냅니다.

비교는 선언한 것이 실제에 그대로 들어 있는가 를 보는 방향입니다. 실제 쪽에는 쿠버네티스가 채운 필드가 잔뜩 더 있는데 그것까지 차이로 세면 아무것도 통과하지 못합니다. 그래서 선언 쪽 키를 하나씩 따라가며 실제 쪽에 같은 값이 있는지 확인하는 방식이 편합니다. 리스트는 길이와 순서까지 맞춰 보세요. 채점기가 세 가지 짝으로 이 스크립트를 실제로 돌려 봅니다.

드리프트를 만들고 한쪽만 되돌린다

kubectl scaleorders 의 replicas 를 5로 올리고, kubectl set image 로 이미지 태그를 다른 값으로 바꾸세요. 그다음 선언 디렉터리를 --server-side --field-manager=argocd-controller --force-conflicts 로 다시 적용하고, 결과를 /root/gitops-sh/out/selfheal.txt 에 다섯 줄로 적으세요. DRIFT_FIELD, IGNORED, LIVE_REPLICAS, IMAGE_RESTORED, CONFLICT_RESOLUTION 입니다.

여기서 두 가지가 동시에 보입니다. 선언하지 않은 spec.replicas 는 소유자가 다르니 그대로 남고, 선언한 이미지는 되돌아옵니다. --force-conflicts 를 빼면 이미지도 되돌아오지 않습니다. kubectl set image 가 그 필드의 소유권을 가져갔기 때문입니다. 실제 ArgoCD 도 ServerSideApply=true 로 동기화할 때 같은 방식으로 소유권을 되찾습니다. LIVE_REPLICAS 는 클러스터에서 읽어 적으세요.

삭제 후보를 소유 표시로 골라낸다

선언 파일 없이 sh-lab 에 ConfigMap 을 하나 만들고 argocd.argoproj.io/tracking-id 를 붙이세요. 그다음 /root/gitops-sh/bin/prune.sh 를 만들어, 이 앱이 소유한 것으로 표시돼 있으면서 선언 디렉터리에는 없는 오브젝트마다 PRUNE=<종류>/<이름> 을 한 줄씩 찍게 하세요. 실제로 지우지는 않습니다.

판정 기준은 셋입니다. 선언 디렉터리를 읽어 있어야 할 목록을 만들고, 클러스터에서 이 앱이 소유한 오브젝트를 찾고, 둘째에는 있는데 첫째에 없는 것을 고릅니다. 소유 표시가 없는 오브젝트는 언제 만들었든 대상이 아닙니다. kube-root-ca.crt 처럼 클러스터가 스스로 만든 것까지 잡으면 안 됩니다. 채점기가 같은 계산을 스스로 해서 여러분의 출력과 대조합니다.

렌더 결과가 비면 멈춘다

/root/gitops-sh/bin/sync.sh 를 만드세요. 환경변수 DESIRED_DIR(기본값은 선언 디렉터리)를 읽어, 매니페스트가 하나도 없으면 REFUSED 가 들어간 메시지를 찍고 0이 아닌 코드로 끝냅니다. 있으면 --server-side --field-manager=argocd-controller --force-conflicts 로 적용하고 APPLIED=<개수> 를 찍은 뒤 0으로 끝냅니다.

source.path 를 빈 디렉터리로 잘못 바꾼 커밋 하나면 있어야 할 목록이 0개가 되고, 그 앱이 관리하던 모든 오브젝트가 삭제 대상이 됩니다. 리뷰어가 한 글자 오타를 놓치는 것으로 충분합니다. 그래서 빈 결과 자체를 거부하는 것이 가장 직접적인 방어입니다. 거부할 때는 아무것도 지우지 않고 멈춰야 합니다. 채점기는 빈 디렉터리로 한 번, 정상 디렉터리로 한 번 돌려 보고, 그 사이에 오브젝트가 사라지지 않았는지도 확인합니다.

Synced 인데 정상이 아닌 상태를 만든다

/root/gitops-sh/desired/broken.yaml 에 스케줄될 수 없는 nodeSelector 를 가진 Deployment broken 을 선언하고(소유 표시 포함) sync.sh 로 적용하세요. 그다음 /root/gitops-sh/out/status.txt 에 다섯 줄로 정리합니다. SYNC, HEALTH, REASON, PRUNE, ALLOW_EMPTY 입니다.

Sync 상태는 저장소와 같은가이고 Health 상태는 잘 돌고 있는가라 서로 다른 축입니다. 그래서 저장소가 시킨 대로 배포됐는데도 파드가 못 뜨는 조합이 정상적으로 나옵니다. 실무에서는 이미지 태그를 잘못 적은 경우가 정확히 이 상태이고, 두 축을 섞어 읽으면 장애 원인을 엉뚱한 곳에서 찾게 됩니다. 클러스터에 없는 라벨을 nodeSelector 에 적으면 같은 상태를 만들 수 있습니다. PRUNEALLOW_EMPTY 는 1단계에서 선언한 값을 그대로 적으세요.