yeonghoon.kim

  • 게시판
  • 갤러리

SOPS Secret 복구와 ArgoCD root Application 적용

김영훈 2026.06.18 12:22 조회 수 : 28

CoreDNS override까지 잡고 나니 ArgoCD가 GitLab 주소를 찾을 준비는 됨.

이제 GitLab repo 접근에 필요한 Secret을 복구해야 함.

Kubernetes Secret은 비밀번호, 토큰, SSH key 같은 민감한 값을 담는 리소스라서 일반 manifest처럼 막 다루면 안 됨.

특히 kubectl apply로 Secret을 적용하면 last-applied-configuration annotation에 원문이 남을 수 있음.

그래서 Secret은 SOPS로 암호화된 파일만 Git에 두고, 복구할 때만 age key로 임시 복호화해서 Kubernetes에 넣는 방식으로 정리함.

평문 Secret
= Git에 넣지 않음

SOPS encrypted Secret
= Git에 저장 가능

age private key
= 서버의 안전한 경로에 보관, 내용 출력 금지

복구 시점
= SOPS가 임시로 복호화하고 kubectl이 Secret 생성/교체/patch

cluster-apps repo의 SOPS 정책.

/home/ioniere/gitlab-project-clones/ioniere/cluster-apps/.sops.yaml

SOPS 정책은 secrets/ 아래의 *.enc.yaml 파일에 적용.

creation_rules:
  - path_regex: ^secrets/.*\.enc\.yaml$
    encrypted_regex: '^(data|stringData)$'
    age:
      - age10wt0r980kcthxs94e620uqudj4pqyy33hhupfu0e3wdjz5hr0g3qdu0rd8
      - age1yzqzf7gsctpuuwxjvxdugr9d7ldgwdzcjad2605clr7qdqa7pg7s9pk4cn

data, stringData만 암호화하는 방식.

Secret 이름이나 namespace는 Git에서 볼 수 있지만, 실제 값은 ENC[...] 형태로 남음.

age key는 Git에 올리지 않음.

/home/ioniere/.config/sops/age/dgx-sops-poc-daily.agekey
/home/ioniere/.config/sops/age/dgx-sops-poc-cold.agekey

먼저 SOPS 파일과 age key 상태 확인.

# cluster-apps repo의 SOPS Secret 파일 개수와 age key 권한을 확인한다. key 내용은 출력하지 않는다.
cd /home/ioniere/gitlab-project-clones/ioniere/cluster-apps
find secrets -type f -name "*.enc.yaml" | sort | wc -l
ls -ld \
  /home/ioniere/.config/sops \
  /home/ioniere/.config/sops/age
ls -l \
  /home/ioniere/.config/sops/age/dgx-sops-poc-daily.agekey \
  /home/ioniere/.config/sops/age/dgx-sops-poc-cold.agekey

ArgoCD 복구 대상 Secret.

repo-creds-gitlab-ssh
= ArgoCD가 GitLab repo를 SSH로 읽기 위한 credential

argocd-notifications-secret
= ArgoCD Notifications용 Secret

argocd-secret
= 전체 교체 금지
= webhook.gitlab.secret, admin.password, admin.passwordMtime만 patch

중요한 점.

repo-creds-gitlab-ssh, argocd-notifications-secret은 전체 Secret을 replace/create 가능.

argocd-secret은 전체 replace 금지.

argocd-secret에는 server.secretkey, TLS 관련 key 등 ArgoCD 동작에 필요한 값이 같이 들어갈 수 있어서 필요한 key만 patch함.

ArgoCD SOPS 파일 존재 확인.

# ArgoCD SOPS Secret 파일이 존재하는지 확인한다. Secret 값은 출력하지 않는다.
cd /home/ioniere/gitlab-project-clones/ioniere/cluster-apps
ls -l \
  secrets/argocd/repo-creds-gitlab-ssh.enc.yaml \
  secrets/argocd/argocd-notifications-secret.enc.yaml \
  secrets/argocd/argocd-webhook-gitlab-secret.enc.yaml \
  secrets/argocd/argocd-admin-password.enc.yaml

복호화 가능 여부 확인.

값을 보는 게 아니라 age key로 복호화가 되는지만 확인.

# SOPS age key로 ArgoCD Secret 파일을 복호화할 수 있는지만 확인한다. Secret 값은 출력하지 않는다.
cd /home/ioniere/gitlab-project-clones/ioniere/cluster-apps
sops_bin=/home/ioniere/.local/bin/sops
key=/home/ioniere/.config/sops/age/dgx-sops-poc-daily.agekey
for file in \
  secrets/argocd/repo-creds-gitlab-ssh.enc.yaml \
  secrets/argocd/argocd-notifications-secret.enc.yaml \
  secrets/argocd/argocd-webhook-gitlab-secret.enc.yaml \
  secrets/argocd/argocd-admin-password.enc.yaml; do
  SOPS_AGE_KEY_FILE="$key" "$sops_bin" -d "$file" >/dev/null
  echo "decrypt ok: $file"
done

ArgoCD Secret 복구.

# ArgoCD용 SOPS Secret을 복구한다. Secret 값은 출력하지 않는다.
set -euo pipefail
cd /home/ioniere/gitlab-project-clones/ioniere/cluster-apps
K=/home/ioniere/.local/bin/kubectl
sops_bin=/home/ioniere/.local/bin/sops
key=/home/ioniere/.config/sops/age/dgx-sops-poc-daily.agekey
command -v python3 >/dev/null
workdir="$(mktemp -d)"
chmod 700 "$workdir"

cleanup() {
  rm -rf "$workdir"
}
trap cleanup EXIT

restore_secret() {
  ns="$1"
  file="$2"
  out="$workdir/$(basename "$file" .enc.yaml).yaml"

  SOPS_AGE_KEY_FILE="$key" "$sops_bin" -d "$file" > "$out"
  chmod 600 "$out"
  secret_name=$("$K" create --dry-run=client -f "$out" -o jsonpath='{.metadata.name}')

  if "$K" -n "$ns" get secret "$secret_name" >/dev/null 2>&1; then
    "$K" -n "$ns" replace -f "$out" >/dev/null
    echo "replaced $file"
  else
    "$K" -n "$ns" create -f "$out" >/dev/null
    echo "created $file"
  fi
}

restore_secret argocd secrets/argocd/repo-creds-gitlab-ssh.enc.yaml
restore_secret argocd secrets/argocd/argocd-notifications-secret.enc.yaml

webhook_yaml="$workdir/argocd-webhook-gitlab-secret.yaml"
admin_yaml="$workdir/argocd-admin-password.yaml"
webhook_json="$workdir/argocd-webhook-gitlab-secret.json"
admin_json="$workdir/argocd-admin-password.json"
patch_json="$workdir/argocd-secret-patch.json"

SOPS_AGE_KEY_FILE="$key" "$sops_bin" -d \
  secrets/argocd/argocd-webhook-gitlab-secret.enc.yaml > "$webhook_yaml"
SOPS_AGE_KEY_FILE="$key" "$sops_bin" -d \
  secrets/argocd/argocd-admin-password.enc.yaml > "$admin_yaml"
chmod 600 "$webhook_yaml" "$admin_yaml"

"$K" create --dry-run=client -f "$webhook_yaml" -o json > "$webhook_json"
"$K" create --dry-run=client -f "$admin_yaml" -o json > "$admin_json"
chmod 600 "$webhook_json" "$admin_json"

python3 - "$webhook_json" "$admin_json" "$patch_json" <<'PY'
import base64
import json
import sys

webhook_path, admin_path, patch_path = sys.argv[1:]

with open(webhook_path) as f:
    webhook = json.load(f)
with open(admin_path) as f:
    admin = json.load(f)

patch_data = {}

for key, value in webhook.get("data", {}).items():
    patch_data[key] = value
for key, value in webhook.get("stringData", {}).items():
    patch_data[key] = base64.b64encode(value.encode()).decode()

for key, value in admin.get("data", {}).items():
    patch_data[key] = value
for key, value in admin.get("stringData", {}).items():
    patch_data[key] = base64.b64encode(value.encode()).decode()

required = [
    "webhook.gitlab.secret",
    "admin.password",
    "admin.passwordMtime",
]
missing = [key for key in required if key not in patch_data]
if missing:
    raise SystemExit("missing required keys: " + ", ".join(missing))

with open(patch_path, "w") as f:
    json.dump({"data": {key: patch_data[key] for key in required}}, f)
PY

chmod 600 "$patch_json"
"$K" -n argocd patch secret argocd-secret \
  --type merge \
  --patch-file "$patch_json" >/dev/null
echo "patched argocd-secret keys"

복구 후 annotation 확인.

# ArgoCD Secret에 last-applied annotation이 남아 있지 않은지 확인한다. Secret 값은 출력하지 않는다.
kubectl -n argocd get secret repo-creds-gitlab-ssh argocd-notifications-secret argocd-secret \
  -o custom-columns=NAME:.metadata.name,LAST_APPLIED:.metadata.annotations.kubectl\\.kubernetes\\.io/last-applied-configuration

annotation이 남아 있으면 제거.

# ArgoCD 관련 Secret에 last-applied annotation이 남아 있으면 제거한다. Secret 값은 출력하지 않는다.
for secret in repo-creds-gitlab-ssh argocd-notifications-secret argocd-secret; do
  kubectl -n argocd annotate secret "$secret" kubectl.kubernetes.io/last-applied-configuration- || true
done

# ArgoCD 관련 Secret의 annotation 이름만 확인한다. Secret 값은 출력하지 않는다.
for secret in repo-creds-gitlab-ssh argocd-notifications-secret argocd-secret; do
  kubectl -n argocd get secret "$secret" \
    -o go-template='{{.metadata.name}} annotations={{range $k, $_ := .metadata.annotations}}{{$k}} {{end}}{{"\n"}}'
done

Secret 존재 여부와 key 이름만 확인.

# ArgoCD Secret 존재 여부와 주요 controller 상태를 확인한다. Secret 값은 출력하지 않는다.
kubectl -n argocd get secret repo-creds-gitlab-ssh argocd-notifications-secret argocd-secret
kubectl -n argocd get pods -o wide
# ArgoCD Secret의 key 이름만 확인한다. Secret 값은 출력하지 않는다.
kubectl -n argocd get secret repo-creds-gitlab-ssh \
  -o go-template='repo-creds keys={{range $k, $_ := .data}}{{$k}} {{end}}{{"\n"}}'
kubectl -n argocd get secret argocd-notifications-secret \
  -o go-template='notifications keys={{range $k, $_ := .data}}{{$k}} {{end}}{{"\n"}}'
kubectl -n argocd get secret argocd-secret \
  -o go-template='argocd-secret keys={{range $k, $_ := .data}}{{$k}} {{end}}{{"\n"}}annotations={{range $k, $_ := .metadata.annotations}}{{$k}} {{end}}{{"\n"}}'

Secret 복구 후 ArgoCD가 새 repo credential을 다시 읽도록 재시작.

# ArgoCD가 새 repo credential을 다시 읽도록 repo-server와 application-controller를 재시작한다.
kubectl -n argocd rollout restart deployment/argocd-repo-server
kubectl -n argocd rollout status deployment/argocd-repo-server --timeout=180s
kubectl -n argocd rollout restart statefulset/argocd-application-controller
kubectl -n argocd rollout status statefulset/argocd-application-controller --timeout=180s

이제 8장에서 보류했던 root Application 적용.

# GitLab 접근 Secret 복구 후 cluster-apps root Application을 생성하고 hard refresh한다.
cd /home/ioniere/gitlab-project-clones/ioniere/cluster-apps
kubectl -n argocd apply -f bootstrap/root-application.yaml
kubectl -n argocd annotate application cluster-apps argocd.argoproj.io/refresh=hard --overwrite

root Application 상태 확인.

# cluster-apps root Application의 sync/health 상태를 확인한다.
kubectl -n argocd get application cluster-apps \
  -o jsonpath='sync={.status.sync.status}{"\n"}health={.status.health.status}{"\n"}phase={.status.operationState.phase}{"\n"}message={.status.operationState.message}{"\n"}'

관리 대상으로 잡힌 리소스 확인.

# cluster-apps root Application이 관리 대상으로 인식한 리소스 목록과 상태를 요약한다. Secret 값은 출력하지 않는다.
kubectl -n argocd get application cluster-apps \
  -o jsonpath='{range .status.resources[*]}{.kind}{"\t"}{.namespace}{"\t"}{.name}{"\t"}{.status}{"\t"}{.health.status}{"\n"}{end}'

이 단계의 목표는 모든 앱을 완전히 정상화하는 게 아니라, ArgoCD가 GitLab repo를 읽고 child Application 목록을 만들 수 있는 상태까지 확인하는 것.

처음에는 OutOfSync나 Missing이 남을 수 있음.

그건 monitoring, namespace, 앱 Secret, PVC 같은 다음 단계 준비가 덜 된 상태일 수 있음.

중요한 건 repo credential 오류가 사라지고, ArgoCD가 cluster-apps repo를 읽기 시작했다는 것.

이제 GitOps 구조가 실제로 움직이기 시작함.


  • 추천 0

  • 비추천 0
이 게시물을
목록

댓글 0

번호 제목 글쓴이 날짜 조회 수
19 DGX Kubernetes 구성 계획 김영훈 2026.06.08 25
18 Kubernetes 기본 클러스터, ingress-nginx 설치 김영훈 2026.06.09 15
17 Docker Compose 서비스를 Kubernetes로 이전(깃랩 도커 저장소 필요) [2] 김영훈 2026.06.10 20
16 yeonghoon.kim 배포 파일을 Helm Chart로 전환 [1] 김영훈 2026.06.12 18
15 SOPS age로 Kubernetes Secret 암복호화 김영훈 2026.06.15 23
14 ArgoCD와 GitOps 구조 준비 김영훈 2026.06.16 16
13 CoreDNS로 GitLab Registry 내부 주소 고정 김영훈 2026.06.17 19
12 복구 런북 초반 누락 사항 정리 김영훈 2026.06.17 15
» SOPS Secret 복구와 ArgoCD root Application 적용 김영훈 2026.06.18 28
10 관측 스택과 앱 배포 저장소 구조 정리 김영훈 2026.06.19 23
9 ArgoCD 복구 중 GitLab 접근 문제 정리 김영훈 2026.06.22 26
8 ImagePullBackOff와 registry pull secret 누락 김영훈 2026.06.22 17
7 worker 재부팅 전 drain 절차 정리 김영훈 2026.06.26 20
6 control-plane 장애 시나리오 관찰 김영훈 2026.06.27 12
5 OutOfSync와 Degraded 구분하기 김영훈 2026.06.28 25
4 Git revert는 기록을 지우는 게 아니라 되돌리는 기록을 남기는 것 김영훈 2026.06.29 19
3 Stateful과 Stateless 구분해보기 김영훈 2026.07.02 23
2 GitLab Registry PAT 만료로 이미지 Pull 실패 김영훈 2026.07.08 14
1 yeonghoon.kim php 업그레이드 김영훈 2026.07.09 0
쓰기 태그
 첫 페이지 8 9 10 11 12 13 14 15 16 17 끝 페이지