이번 포스팅은 ArgoCD에 HPA를 포함하는 Sample Deployment 를 배포해보고 App 생성 시 추가 설정에 대해서 알아보도록 한다.
ArgoCD에서 주로 다루는 것이 Diff 인데, 즉 Git 저장소에 선언한 코드와 실제 쿠버네티스 클러스터의 현재의 상태가 달라질 때 Diff가 발생한다. 일반적으로 ArgoCD는 이러한 차이를 지속적으로 발견하고 동기화하면서 관리한다. 그러나 모든 차이점을 동기화하는 것이 항상 바람직한 것은 아니다.
특히 쿠버네티스 환경에서는 시스템이 자동으로 관리하거나 런타임에 동적으로 변경되는 필드들이 존재한다. 바로 이러한 상황에서 ignoreDifferences를 사용한다.
https://argo-cd.readthedocs.io/en/stable/user-guide/diffing/
ignoreDifferences의 필요성
쿠버네티스 리소스에는 여러 이유로 Git에 정의된 상태와 클러스터의 실제 상태 간에 차이가 발생할 수 있는 필드들이 있다.
- 쿠버네티스 컨트롤러가 자동으로 관리하는 필드
- HPA(Horizontal Pod Autoscaler)에 의해 조정되는 Deployment의 replicas 필드
- StatefulSet이나 Deployment의 revision 관련 주석
- 쿠버네티스 컨트롤러가 자동으로 추가하는 레이블이나 선택자
- 런타임에 생성되는 동적 데이터
- Pod의 상태 정보나 IP 주소
- Service의 클러스터 IP
- 자동 생성된 타임스탬프나 UUID 값
- 외부 도구나 운영자가 필요에 따라 수정하는 필드
- 긴급 상황에서 수동으로 조정된 replicas 값
- 특정 주석이나 레이블 업데이트
이러한 필드들의 차이를 모두 감지하고 동기화하게 된다면 다음과 같은 문제들이 생긴다.
- 지속적인 Out-of-sync 상태: 동적으로 변하는 필드로 인해 애플리케이션이 항상 동기화되지 않은 상태로 표시됨
- 불필요한 재배포: 중요하지 않은 필드 변경으로 인한 빈번한 동기화 발생
- 운영 중인 시스템의 불안정: HPA가 조정한 replicas 값이 지속적으로 초기 값으로 되돌아가는 등의 문제
- 자동화된 워크플로우 방해: CI/CD 파이프라인이 불필요한 변경 감지로 인해 지속적으로 트리거됨
조금 더 쉽게 예를 들자면 HPA 설정이 있는 pod의 경우 지속적으로 replicas 수가 달라지게 된다.
yaml에서는 2개로 선언된 pod가 HPA 설정으로 인해 5개가 된다면 ArgoCD 입장에서는 2개의 pod가 있어야 하는데 5개가 있는 것이므로 OutofSync 상태를 표시한다.
운영자 입장에서 굉장히 정상적인 상황이고, 문제가 없는 상황인데 OutofSync 표시로 확인되게 되고 이에 대해서 확인이 필요하게 된다. 만약 이 상태에서 불필요하게 Sync작업을 진행한다면 pod의 숫자가 줄어들 수 도 있는 문제가 있다. 바로 이럴 때 ignoreDifference 설정을 통해서 이러한 문제는 사전에 방지하게 되는 것이다.
ignoreDifferences 설정 방법
ArgoCD에서는 ignoreDifferences 설정을 통해 특정 필드의 차이를 무시할 수 있다. 다음 예시는 HPA 설정에 대해서 차이를 무시하는 것으로 아까 설명한 예시(도전과제의 내용) HPA로 인해 Pod의 수가 늘어났으나 git 저장소에서는 작게 선언된 경우 해결방안이다.
spec:
ignoreDifferences:
- group: apps
jsonPointers:
- /spec/replicas
- /metadata/annotations/deployment.kubernetes.io/revision
kind: Deployment
- group: autoscaling
jsonPointers:
- /status
kind: HorizontalPodAutoscaler
먼저 선언부는 spec.ignoreDifferences이다.
그리고 group 필드로 나눠지게 되는데 여기서는 쿠버네티스의 api group의 이름을 따른다.
그리고 jsonPointers 를 통해서 json의 위치를 지정할 수 있다.
kind의 경우 실제 리소스의 kind를 적으면 된다.
여기서 group을 중심으로 자세히 보자.
spec:
ignoreDifferences:
- group: apps
jsonPointers:
- /spec/replicas
- /metadata/annotations/deployment.kubernetes.io/revision
kind: Deployment
- apps 그룹은 Deployment 리소스에서 replicas 필드와 revision 주석의 차이를 무시한다.
spec:
ignoreDifferences:
- group: autoscaling
jsonPointers:
- /status
kind: HorizontalPodAutoscaler
- autoscaling 그룹은 HorizontalPodAutoscaler 리소스에서 모든 status 필드의 차이를 무시한다.
jsonPointer 말고, jq표현식을 써도된다.
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jqPathExpressions:
- .spec.template.spec.initContainers[] | select(.name == "injected-init-container")
"*" 도 가능하기 때문에 특정 manager의 모든 리소스도 이런식으로 표시할 수 있다.
spec:
ignoreDifferences:
- group: "*"
kind: "*"
managedFieldsManagers:
- kube-controller-manager
실습
지금부터 실습을 해보자. 실습코드는 https://devops-james.tistory.com/517 쪽에서 실습했던 ops-deploy 코드를 그대로 활용한다.
거기서 2가지 작업을 먼저 진행한다.
일단 HPA가 작동하기 위해서는 반드시 metrics-server가 동작해야한다.
따라서 metrics-server를 설치한다.
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl get deployment metrics-server -n kube-system
또한 hpa 작동을 위해서는 deployment에 리소스 limit과 request 설정을 해야한다.
따라서 deployment를 다음과 같이 수정한다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: nginx
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
ports:
- containerPort: 80
resources:
requests:
cpu: "200m" # 최소 0.2 코어 요청
memory: "256Mi" # 최소 256MB 메모리 요청
limits:
cpu: "500m" # 최대 0.5 코어 사용 가능
memory: "512Mi" # 최대 512MB 메모리 사용 가능
volumeMounts:
- name: index-html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
volumes:
- name: index-html
configMap:
name: {{ .Release.Name }}
그리고 마지막으로 hpa 설정을 한다.
hpa.yaml
해당 코드는 dev-app 디플로이먼트의 cpu 값에 의해 자동으로 증가되는 것으로 cpu가 50% 이상 사용될 경우 최대 10개까지 추가되도록 설정한다.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: dev-app
namespace: example
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: dev-app
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
자 이제 이 상태를 맞추고 cpu에 부하를 주자.
cpu에 부하를 주는 방법은 다양하게 있는데 일단 pod에 들어가서 다음 명령어를 설치 후 실행한다.
apt update && apt install -y stress-ng # Debian/Ubuntu 계열
stress-ng --cpu 4 --timeout 60s # 4개 CPU 사용, 60초 동안 실행
hpa를 조회하면 다음과 같이 지속적으로 pod가 증가하는 것을 확인할 수 있다.
또한 argocd에서도 OutOfSync가 지속해서 발생되고 있었다. 아무래도 실제로는 1개의 replicas를 코드상 설정하였는데, 2개 이상의 pod가 띄워져있기 때문에 문제가 발생한 것으로 판단한다.
이제 ignoreDifferences 설정을 진행해보자. 기존에 app이 존재하는 상태기 때문에 patch를 통해서 간단하게 집어넣었다.
argocd app patch dev-app --patch '{"spec": {"ignoreDifferences": [{"group": "apps", "kind": "Deployment", "jsonPointers": ["/spec/replicas"]}, {"group": "autoscaling", "kind": "HorizontalPodAutoscaler", "jsonPointers": ["/status"]}]}}' --type merge\n
해당 설정은 결국 Deployment의 replicas 숫자와 hpa의 status에 대해서 무시하라는 설정이다.
잘 들어간 것을 UI상에서도 확인할 수 있었다.
해당 내용이 적용되니 다음과 같이 replicas의 수에 상관없이 Synced 표시가 되는 것을 확인할 수 있다.
다음은 동기화 중에 ignoreDifferences 설정을 강제하는 옵션이다.
RespectIgnoreDifferences 옵션의 중요성
syncPolicy.syncOptions에 RespectIgnoreDifferences=true를 추가하면 동기화 프로세스 중에도 ignoreDifferences 설정을 강제하도록 지정할 수 있다.
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- RespectIgnoreDifferences=true
이 설정을 통해 수동 동기화와 자동 동기화 모두에서 ignoreDifferences 설정이 일관되게 적용된다. 특히 자동 동기화(automated sync)와 자가 복구(selfHeal) 기능을 사용할 때, 무시하기로 한 필드로 인해 지속적인 동기화 시도가 발생하는 것을 방지할 수 있다.
사용 사례
이 외에도 다음과 같이 다양한 활용사례로 사용할 수 있다.
- 상태 정보 무시 대부분의 리소스에서 /status 경로를 무시하면 불필요한 동기화 작업을 줄일 수 있다.
- 시스템 생성 메타데이터 처리 자동으로 생성되는 UUID, 타임스탬프, 리소스 버전 등을 무시하여 불필요한 변경 감지를 방지한다.
- 특정 환경에 맞춘 구성 개발, 테스트, 프로덕션 환경에서 특정 필드가 다르게 설정되어야 할 때 이러한 차이를 무시할 수 있다.
- 긴급 운영 변경 허용 운영 중 긴급하게 변경한 설정이 자동으로 Git의 상태로 되돌아가는 것을 방지한다.