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 구조가 실제로 움직이기 시작함.