# 수동 설치된 컴포넌트를 Helm 릴리스로 인수하고 GitOps에 넘기기

클러스터를 오래 운영하면 kubectl apply나 일회성 스크립트로 설치된 컴포넌트가 남습니다. CNI처럼 클러스터 부팅에 필요한 것들이 특히 그렇습니다. 이것을 Helm 릴리스로 바꾸고 다시 ArgoCD로 관리하려면 두 번의 소유권 이전이 일어나는데, 각 단계에 조용히 실패하거나 데이터플레인을 끊을 수 있는 함정이 있습니다.

이 글에서는 Helm의 --take-ownership, Cilium의 upgradeCompatibility처럼 인수 과정에서 반드시 이해해야 하는 옵션의 의미, values 병합에서 값이 사라지는 패턴, 그리고 GitOps로 넘긴 직후 실제로 드러나는 문제들을 정리합니다.

# 1. 두 단계의 소유권 이전

수동 설치 → GitOps는 한 번에 가지 않습니다.

kubectl apply 로 설치된 리소스
  │  ① Helm 릴리스로 인수 (helm install --take-ownership)
  ▼
Helm 이 소유한 리소스 (release secret 존재)
  │  ② ArgoCD Application 으로 인수 (첫 sync, field manager 부착)
  ▼
GitOps 로 관리되는 리소스

각 단계에서 확인해야 할 것이 다릅니다. ①은 Helm이 기존 객체를 자기 릴리스에 편입하는지, ②는 ArgoCD가 매니페스트를 재적용하며 무엇을 바꾸는지입니다.

# 2. Helm의 소유권 검사와 --take-ownership

Helm 3는 설치 대상 리소스가 이미 존재하면 소유권 애노테이션(meta.helm.sh/release-name 등)을 검사하고, 자기 릴리스 것이 아니면 실패합니다. Helm 3.17에서 추가된 --take-ownership은 이 검사를 건너뛰고 기존 리소스를 릴리스에 편입합니다.

helm install cilium cilium/cilium \
  --version 1.18.6 \
  --namespace kube-system \
  --values cilium-values.yaml \
  --take-ownership

주의할 점이 두 가지입니다.

  • values를 완벽히 복원해야 합니다. Cilium 문서는 업그레이드 명령이 기존 ConfigMap을 덮어쓴다고 경고합니다: "Running any of the previous commands will overwrite the existing cluster's ConfigMap so it is critical to preserve any existing options." 수동 설치 당시 지정한 옵션이 values에 빠져 있으면, 인수와 동시에 차트 기본값으로 되돌아갑니다.
  • CR 인스턴스는 별개입니다. 차트가 관리하는 것은 CRD 정의와 컨트롤러이며, 이미 클러스터에 있는 CR 인스턴스(정책, 라우트 등)는 릴리스에 포함되지 않습니다. --take-ownership이 커스텀 리소스를 편입하지 않는다는 이슈도 보고돼 있습니다. 인수 전후로 개수를 세어 비교하는 절차를 넣는 편이 안전합니다.
# 인수 전
kubectl get ciliumnetworkpolicies -A --no-headers | wc -l > before.count
kubectl get ciliumnetworkpolicies -A -o yaml > backup-cnp.yaml
# 인수 후 같은 명령으로 비교

# 3. upgradeCompatibility - 기본값의 기준 시점을 고정한다

Cilium을 마이너 버전 간 업그레이드할 때 문서가 권하는 옵션입니다.

To minimize datapath disruption during the upgrade, the upgradeCompatibility option should be set to the initial Cilium version which was installed in this cluster.

이름만 보면 호환 모드 스위치처럼 보이지만, 실제 동작은 차트 템플릿의 분기 기준 버전입니다. 차트는 이 값을 semverCompare로 비교해 명시되지 않은 value의 기본값을 결정합니다. 즉 upgradeCompatibility="1.17"로 두면, 내가 values에 적지 않은 옵션들의 기본값이 "차트 최신 버전 기준"이 아니라 "1.17 시점 기준" 으로 고정됩니다.

이것이 중요한 이유는, 마이너 버전이 올라갈 때 데이터플레인 동작의 기본값이 바뀌는 항목들이 있기 때문입니다. kube-proxy 대체 방식, Envoy 활성화 여부, TLS 시크릿 동기화, 클라우드 환경의 masquerade 방식 등이 그런 예입니다. 값을 명시하지 않은 채 업그레이드하면 이런 기본값이 한꺼번에 바뀌면서 트래픽이 끊길 수 있습니다.

정리하면 두 가지 선택지가 있습니다.

전략 방법 특징
점진적 upgradeCompatibility를 기존 버전으로 고정 데이터플레인 동작 유지, 새 기본값은 나중에 개별 검토
일괄 옵션 미지정, 새 기본값 수용 최신 권장 설정으로 정렬되지만 변경 폭이 큼

운영 클러스터라면 전자로 인수를 먼저 끝내고, 기본값 변경은 별도 작업으로 분리하는 편이 안전합니다.

# 4. values 병합에서 값이 조용히 사라지는 두 패턴

# 4-1. YAML 중복 키

한 파일에 같은 키가 두 번 나오면 YAML 파서는 마지막 것을 채택하고 앞의 것을 버립니다. 오류가 아니라 정상 동작이므로 아무 경고가 없습니다.

gatewayAPI:
  enabled: true
  secretsNamespace:
    name: kube-system   # ← 아래 블록에 의해 통째로 사라진다

# ... 수십 줄 뒤 ...

gatewayAPI:
  enabled: true         # ← 이 블록만 살아남음

수동 설치 당시의 values를 이어받아 편집하다 보면 쉽게 생깁니다. 에디터 린트가 "Map keys must be unique"로 잡아 주기도 하지만, CI에서 확인하는 편이 확실합니다.

# 4-2. 추가 설정과 차트 기본 키의 충돌

많은 차트는 임의의 ConfigMap 키를 넣는 통로(extraConfig 등)를 제공합니다. 여기에 차트가 이미 전용 value로 렌더하는 키를 넣으면 같은 키가 두 번 렌더되어 ConfigMap이 예측 불가한 상태가 됩니다.

# ❌ 차트가 전용 value로 관리하는 키를 extraConfig 로 중복 주입
extraConfig:
  nodeport-addresses: "10.0.0.0/8"

# ✅ 차트가 제공하는 전용 value 사용
nodePort:
  addresses:
    - 10.0.0.0/8

두 패턴 모두 helm template으로 잡을 수 있습니다. 인수 전 검증을 한 번 돌리는 것이 비용이 가장 적습니다.

helm template cilium cilium/cilium --version 1.18.6 -f cilium-values.yaml \
  | yq 'select(.metadata.name == "cilium-config") | .data | keys' | sort | uniq -d
# 출력이 있으면 중복 키 존재

# 5. ArgoCD Application 설계 - CNI를 인수할 때

②단계에서 Application을 만들 때 CNI 같은 기반 컴포넌트는 일반 워크로드와 다르게 설정해야 합니다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cilium
  namespace: argocd
  annotations:
    # 다른 앱보다 먼저 sync (네트워크가 없으면 나머지가 뜨지 않음)
    argocd.argoproj.io/sync-wave: "-100"
spec:
  project: infra
  source:
    repoURL: https://helm.cilium.io/
    chart: cilium
    targetRevision: 1.18.6
    helm:
      values: |
        upgradeCompatibility: "1.17"
        # ...
  destination:
    server: https://kubernetes.default.svc
    namespace: kube-system
  syncPolicy:
    automated:
      prune: false      # 인수 초기에는 삭제를 절대 자동화하지 않는다
      selfHeal: true

prune: false가 핵심입니다. Git에 없는 리소스를 자동 삭제하는 설정을, 아직 Git과 클러스터 상태가 완전히 일치하는지 확인되지 않은 인수 초기에 켜 두면 안 됩니다. 수동 설치 시절의 부수 리소스가 한꺼번에 지워질 수 있습니다.

또한 AppProject의 sourceRepos에 차트 저장소를 추가해야 합니다. 부트스트랩 Application이 디렉터리를 recurse로 훑는 구성이라면 파일을 두는 것만으로 앱이 자동 발견됩니다.

# 6. 인수 직후에 드러나는 문제들

②단계에서 ArgoCD가 매니페스트를 재적용하면, 수동 설치 시절에는 드러나지 않았던 차이가 한꺼번에 나타납니다. 두 가지가 특히 자주 걸립니다.

# 6-1. CNI 바이너리 디렉터리 권한

CNI 플러그인은 호스트의 /opt/cni/bin에 바이너리를 복사합니다. 이 디렉터리의 소유자·권한이 컨테이너가 쓸 수 없는 상태이면 init 컨테이너가 Permission denied로 실패합니다. 노드 프로비저닝 방식이 클러스터마다 달라 일부 노드에서만 재현되는 일도 있습니다.

# 전 노드 권한 확인
kubectl get nodes -o name | while read n; do
  echo "== $n"; kubectl debug "$n" -it --image=busybox -- ls -ld /host/opt/cni/bin
done

임시로 권한을 열어 복구하더라도, 노드 프로비저닝 단계에서 보장하도록 되돌려 놓아야 합니다. 수동 조치는 노드가 재생성되면 사라집니다.

# 6-2. 노드별 오버라이드와 전역 설정의 충돌

Cilium은 특정 노드에만 설정을 덮어쓰는 CiliumNodeConfig를 제공합니다. 예전에 넣어 둔 노드별 설정이 새 전역 설정과 충돌하면 해당 노드의 에이전트만 초기화에 실패합니다.

대표적인 조합이 masquerade 관련 설정입니다. 인터페이스를 지정하는 egress-masquerade-interfaces는 iptables 기반 masquerade 문서에만 있는 옵션이고, eBPF 기반 masquerade(bpf.masquerade=true)에서는 지원되지 않습니다. eBPF 모드에서는 NodePort 디바이스 자동 탐지 결과나 devices value로 인터페이스가 결정됩니다. 두 방식을 섞으면 에이전트가 masquerade 초기화 단계에서 멈춥니다.

# 노드별 오버라이드가 남아 있는지 먼저 조사
kubectl get ciliumnodeconfigs -A -o yaml | yq '.items[].spec.defaults'

지금 쓰지 않는 오버라이드라면 인수 전에 정리하는 것이 가장 깔끔합니다.

# 7. 인수 절차 체크리스트

순서 항목 확인 방법
1 기존 설정 전수 추출 live ConfigMap/DaemonSet spec 백업
2 values ↔ 기존 설정 1:1 대조 helm template 결과와 live diff
3 중복 키·중복 렌더 검사 렌더된 ConfigMap 키 중복 확인
4 CR 인스턴스 백업·개수 기록 인수 후 동일 개수 확인
5 기본값 기준 시점 고정 upgradeCompatibility
6 Helm 인수 --take-ownership, 릴리스 시크릿 생성 확인
7 GitOps 인수 prune: false, sync-wave, field manager 부착 확인
8 노드별 오버라이드 정리 충돌 설정 삭제
9 데이터플레인 검증 Pod 간 통신, 정책 적용, 외부 egress

DaemonSet 기반 컴포넌트는 ArgoCD에서 Health가 Progressing으로 오래 남을 수 있습니다. 노드 수와 롤링 전략에 따라 생기는 정상 동작이므로 인수 성공 여부는 Health 대신 리소스 diff와 실제 통신으로 판단하는 것이 맞습니다.

# 8. 마무리

  • 수동 설치 → GitOps는 Helm 인수ArgoCD 인수 두 단계이고, 각각 다른 것을 검증해야 합니다.
  • --take-ownership은 소유권 검사만 건너뛸 뿐, values 복원과 CR 인스턴스 보존은 사람이 책임져야 합니다.
  • upgradeCompatibility는 명시하지 않은 옵션의 기본값 기준 시점을 고정하는 장치입니다. 인수와 기본값 변경을 같은 작업에 섞지 않는 것이 안전합니다.
  • 인수 초기에는 prune: false가 기본이어야 합니다. 자동 삭제는 Git과 클러스터가 일치한다고 확인된 뒤에 켭니다.

# 참고