26M15g

Young-Kyoo Kim·2일 전

네. Jenkins만으로 충분히 만족할 수 있습니다. 다만 Jenkins를 “배포 도구”로 쓰면 안 되고, ArgoCD가 바라보는 Git 브랜치에 merge되기 전 PR 검증 게이트로 써야 합니다. 첨부 문서에서도 표준 흐름은 Jira → Confluence 계획서 → Bitbucket PR → Peer Review → Merge → ArgoCD 자동 배포이고, 브랜치 보호 규칙에는 helm lint, kubeconform, Kyverno dry-run, Ansible --check 같은 CI 통과가 필수로 정의되어 있습니다.

1. 결론: Jenkins로 가능한 범위

소스 빌드를 하지 않아도 Jenkins에서 다음 검증은 충분히 가능합니다.

검증 영역Jenkins에서 가능한 검증대표 도구
문법 검증YAML/JSON 문법, Helm chart 구조, Kustomize 렌더링 가능 여부yamllint, helm lint, kustomize build
의존성 검증Helm dependency, chart repo 접근, CRD 선행 여부, ArgoCD App 의존 순서helm dependency build, custom script
환경변수 적합성필수 env 누락, values schema 위반, Secret/ConfigMap 참조 누락values.schema.json, yq, cue, jsonschema
K8s API 적합성target Kubernetes 버전에 맞는 API/Kind/field 검증kubeconform
정책 적합성privileged, hostPath, latest image, resource limit, label, namespace, NetworkPolicy 등kyverno apply, conftest
적용 가능성실제 API Server 기준 admission/defaulting/server validationkubectl apply --dry-run=server
배포 전 변경 영향현재 live 상태와 Git 변경분 차이 확인argocd app diff

Helm 공식 문서상 helm lint는 chart가 올바른 형식인지 일련의 테스트를 수행하고, 설치 실패를 유발할 항목은 error로 표시합니다. Kubeconform은 Kubernetes manifest를 리소스 정의에 맞춰 검증하는 도구이고 CI에 통합할 수 있습니다. Kyverno CLI는 정책을 리소스에 적용해 클러스터에 넣기 전에 정책 동작을 테스트할 수 있습니다. (helm.sh)

2. 권장 전체 흐름

가장 중요한 구조는 아래입니다.

Developer / Operator
  │
  │ 1. Git branch 생성
  │ 2. Helm values / Kustomize overlay / Manifest 수정
  ▼
Bitbucket PR
  │
  │ 3. Webhook
  ▼
Jenkins GitOps Validation Pipeline
  │
  ├─ ① 변경 경로 분석
  ├─ ② 문법 / 렌더링 검증
  ├─ ③ Helm dependency / Kustomize build 검증
  ├─ ④ 환경변수 / values schema 검증
  ├─ ⑤ kubeconform API schema 검증
  ├─ ⑥ Kyverno / OPA 정책 검증
  ├─ ⑦ kubectl server dry-run
  ├─ ⑧ argocd app diff 리포트 생성
  └─ ⑨ Bitbucket PR status + Slack/Jira 결과 기록
  │
  │ 4. CI 통과 시에만 merge 허용
  ▼
Protected main / release branch
  │
  │ 5. ArgoCD가 Git 변경 감지
  ▼
ArgoCD Sync
  │
  ▼
Target Cluster

즉, Jenkins가 통과시키지 않으면 ArgoCD가 바라보는 branch로 merge가 안 되게 해야 합니다. ArgoCD가 이미 추적 중인 branch에 merge된 뒤 Jenkins가 검사하는 구조라면, Auto Sync가 먼저 동작할 수 있으므로 “배포 전 CI”가 아닙니다. 따라서 ArgoCD는 main, release/*, prod/* 같은 보호 브랜치만 추적하게 두고, Jenkins 검증은 PR 단계에서 강제하는 게 맞습니다. ArgoCD는 Git을 desired state로 보고 target 환경에 자동 반영하는 GitOps CD 도구이며 Helm, Kustomize, plain YAML 등을 지원합니다. (Argo CD)

3. Jenkins 검증 단계 상세

Stage 1. 변경 대상 식별

PR에서 변경된 파일을 보고 어떤 cluster/app/env를 검증할지 계산합니다.

예를 들어 첨부 문서의 레포 구조 기준으로 보면 infra-gitops, infra-platform, infra-policy, infra-secrets, infra-base가 분리되어 있고, 각 레포별로 검증 방식이 달라져야 합니다.

git diff --name-only origin/main...HEAD > changed-files.txt

# 예시 결과
infra-platform/keycloak/Chart.yaml
infra-platform/keycloak/values/ic-p-data-01.yaml
infra-policy/kyverno/require-resources.yaml
infra-gitops/clusters/ic-p-data-01/apps/keycloak.yaml

Jenkins는 이 변경 목록을 보고 아래처럼 target matrix를 만듭니다.

[
  {
    "cluster": "ic-p-data-01",
    "tier": "production",
    "app": "keycloak",
    "type": "helm",
    "chart": "infra-platform/keycloak",
    "values": "infra-platform/keycloak/values/ic-p-data-01.yaml",
    "namespace": "keycloak",
    "kubeVersion": "1.31.0"
  }
]

이렇게 해야 전체 레포를 무식하게 검증하지 않고, 변경 영향 범위만 빠르게 검증할 수 있습니다.

Stage 2. Helm/Kustomize 렌더링 검증

Helm 기반이면:

helm dependency build infra-platform/keycloak
helm lint infra-platform/keycloak \
  -f infra-platform/keycloak/values/ic-p-data-01.yaml \
  --strict

helm template keycloak infra-platform/keycloak \
  -n keycloak \
  -f infra-platform/keycloak/values/ic-p-data-01.yaml \
  --include-crds \
  > rendered/keycloak-ic-p-data-01.yaml

Kustomize 기반이면:

kubectl kustomize infra-platform/cilium/overlays/ic-p-data-01 \
  > rendered/cilium-ic-p-data-01.yaml

Kubernetes 공식 문서에서도 Kustomize는 kustomization 파일을 통해 Kubernetes object를 customize하는 도구로 설명되며, kubectl kustomize <directory> 형태로 리소스를 출력할 수 있습니다. (Kubernetes)

Stage 3. 환경변수 / values 적합성 검증

여기가 실제 운영에서 가장 효과가 큽니다. “YAML은 맞는데 운영 환경에 필요한 값이 비어 있음” 같은 문제를 잡아야 합니다.

권장 방식은 Helm chart마다 values.schema.json을 두는 것입니다.

{
  "$schema": "http://json-schema.org/schema#",
  "type": "object",
  "required": ["global", "image", "resources", "env", "externalSecret"],
  "properties": {
    "global": {
      "type": "object",
      "required": ["clusterName", "environment", "region"],
      "properties": {
        "clusterName": {
          "type": "string",
          "pattern": "^[a-z]{2}-[psd]-[a-z]+-[0-9]{2}$"
        },
        "environment": {
          "enum": ["dev", "staging", "production"]
        },
        "region": {
          "enum": ["icheon", "yongin", "cheongju"]
        }
      }
    },
    "image": {
      "type": "object",
      "required": ["repository", "tag"],
      "properties": {
        "repository": { "type": "string" },
        "tag": {
          "type": "string",
          "not": { "const": "latest" }
        }
      }
    },
    "resources": {
      "type": "object",
      "required": ["requests", "limits"]
    },
    "externalSecret": {
      "type": "object",
      "required": ["enabled", "secretStoreRef"]
    }
  }
}

이렇게 해두면 Jenkins의 helm lint --strict 단계에서 필수 값 누락, 잘못된 cluster naming, latest tag 같은 문제를 조기에 차단할 수 있습니다. 첨부 문서에도 멀티클러스터 naming convention과 노드/클러스터 식별 표준이 정의되어 있으므로, schema에 그 표준을 반영하는 것이 좋습니다.

추가로 아래 같은 custom check를 넣을 수 있습니다.

#!/usr/bin/env bash
set -euo pipefail

MANIFEST="$1"

echo "[check] unresolved placeholders"
if grep -E '\$\{[A-Z0-9_]+\}' "$MANIFEST"; then
  echo "ERROR: unresolved placeholder exists"
  exit 1
fi

echo "[check] plain Secret data"
if yq 'select(.kind == "Secret" and .data != null)' "$MANIFEST" | grep -q .; then
  echo "ERROR: plain Kubernetes Secret data is not allowed. Use ExternalSecret or SealedSecret."
  exit 1
fi

echo "[check] required labels"
yq '
  select(.kind == "Deployment" or .kind == "StatefulSet" or .kind == "DaemonSet")
  | select(.metadata.labels."app.kubernetes.io/owner" == null)
  | .metadata.name
' "$MANIFEST" | grep -q . && {
  echo "ERROR: app.kubernetes.io/owner label is required"
  exit 1
}

echo "[check] passed"

첨부 문서에서도 평문 Secret 저장 금지, 개발/스테이징의 Sealed Secrets, 운영의 Vault + External Secrets 사용 원칙이 명시되어 있고, Jenkins는 Vault plugin 등을 통해 동적 secret을 사용하도록 제안되어 있습니다.

Stage 4. Kubernetes schema/API 검증

렌더링된 manifest에 대해 target Kubernetes 버전 기준으로 검증합니다.

kubeconform \
  -strict \
  -summary \
  -kubernetes-version "1.31.0" \
  -schema-location default \
  -schema-location "schemas/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json" \
  rendered/keycloak-ic-p-data-01.yaml

여기서 중요한 점은 CRD schema입니다. Cilium, ArgoCD, ExternalSecret, Kyverno, Longhorn, AIStor 관련 CRD는 기본 Kubernetes schema에 없기 때문에 내부 schemas/ 디렉터리에 CRD OpenAPI schema를 저장하거나, CRD를 먼저 렌더링해서 검증 흐름에 포함해야 합니다.

Stage 5. 정책 검증

예를 들어 운영 표준 정책을 infra-policy/kyverno에 둡니다.

kyverno apply infra-policy/kyverno \
  --resource rendered/keycloak-ic-p-data-01.yaml \
  --policy-report \
  > reports/kyverno-policy-report.txt

대표 정책은 아래 정도가 적합합니다.

정책목적
require-requests-limits모든 Pod에 CPU/Memory request/limit 강제
disallow-latest-taglatest 이미지 태그 금지
require-approved-registryHarbor/Nexus 등 승인 registry만 허용
disallow-privilegedprivileged container 차단
require-networkpolicy운영 namespace에 기본 NetworkPolicy 강제
require-pdb운영 Stateful/Deployment에 PDB 강제
require-owner-labelsowner, service, environment label 강제
require-external-secret평문 Secret 대신 ExternalSecret 사용 강제

첨부 문서의 보안·거버넌스 SOP도 이미지 보안은 Harbor 경유 및 Trivy 스캔, Kyverno 정책 강제, Secret 평문 저장 금지를 요구합니다.

Stage 6. 실제 API Server 기준 적용 가능성 검증

여기서 “적용 가능성”을 가장 현실적으로 검증합니다.

kubectl --context ic-p-data-01-validation \
  apply \
  --server-side \
  --dry-run=server \
  -f rendered/keycloak-ic-p-data-01.yaml

Kubernetes 문서상 --dry-run=server는 서버 측 요청을 보내지만 리소스를 persist하지 않는 방식입니다. 따라서 단순 YAML/schema 검증보다 실제 API server의 admission, defaulting, CRD 존재 여부, 일부 webhook 거부 조건을 더 잘 잡을 수 있습니다. (Kubernetes)

주의할 점은 이 검증용 ServiceAccount가 완전한 read-only 권한만으로는 충분하지 않을 수 있다는 점입니다. dry-run이라도 API server에는 create/update/patch 성격의 요청이 들어가므로, 운영에서는 다음처럼 제한하는 것이 좋습니다.

validation service account 권장 범위
- 대상 namespace 한정
- create/update/patch dry-run 검증에 필요한 리소스만 허용
- delete 금지
- secret get/list 금지 또는 최소화
- production은 우선 staging dry-run 필수
- prod dry-run은 변경 유형 “중/높음”에만 수행

Stage 7. ArgoCD diff 리포트

Jenkins가 실제 sync를 하면 안 되지만, ArgoCD CLI로 diff는 볼 수 있습니다.

set +e
argocd app diff keycloak-ic-p-data-01 \
  --local rendered \
  --local-repo-root . \
  --server-side-generate \
  > reports/argocd-diff-keycloak-ic-p-data-01.txt
RC=$?
set -e

# ArgoCD app diff는 차이가 있으면 1, 일반 오류는 2를 반환할 수 있음.
# 차이 자체는 PR에서 기대되는 결과이므로 실패로 보지 않고, 오류만 실패 처리.
if [ "$RC" -eq 2 ]; then
  echo "ERROR: argocd app diff failed"
  exit 1
fi

ArgoCD 공식 문서상 argocd app diff는 target과 live state의 차이를 보여주며, --local, --revision, --server-side-diff, --server-side-generate 같은 옵션을 제공합니다. 반환 코드는 일반 오류 2, diff 존재 1, 차이 없음 0으로 설명되어 있으므로, Jenkins에서는 “diff 존재”와 “명령 오류”를 구분해야 합니다. (Argo CD)

4. Jenkinsfile 예시

아래는 “소스 빌드 없이 GitOps manifest만 검증”하는 대표 Jenkinsfile입니다.

pipeline {
  agent {
    label 'jenkins-gitops-validator'
  }

  options {
    timestamps()
    disableConcurrentBuilds()
    buildDiscarder(logRotator(numToKeepStr: '30'))
  }

  environment {
    TARGET_BASE_BRANCH = 'origin/main'
    REPORT_DIR = 'reports'
    RENDER_DIR = 'rendered'
  }

  stages {
    stage('Checkout') {
      steps {
        checkout scm
        sh '''
          set -euo pipefail
          mkdir -p "${REPORT_DIR}" "${RENDER_DIR}"
          git fetch origin main
          git diff --name-only "${TARGET_BASE_BRANCH}"...HEAD | tee "${REPORT_DIR}/changed-files.txt"
        '''
      }
    }

    stage('Resolve Targets') {
      steps {
        sh '''
          set -euo pipefail
          ./ci/resolve-targets.sh "${REPORT_DIR}/changed-files.txt" \
            | tee "${REPORT_DIR}/targets.json"

          test -s "${REPORT_DIR}/targets.json"
        '''
      }
    }

    stage('Basic Lint') {
      steps {
        sh '''
          set -euo pipefail
          yamllint -c ci/yamllint.yaml .
          ./ci/check-required-pr-metadata.sh
          ./ci/check-no-plain-secret.sh .
          ./ci/check-no-unresolved-placeholders.sh .
        '''
      }
    }

    stage('Render Manifests') {
      steps {
        sh '''
          set -euo pipefail

          jq -c '.[]' "${REPORT_DIR}/targets.json" | while read -r target; do
            APP=$(echo "$target" | jq -r '.app')
            TYPE=$(echo "$target" | jq -r '.type')
            CHART=$(echo "$target" | jq -r '.chart // empty')
            VALUES=$(echo "$target" | jq -r '.values // empty')
            KUSTOMIZE_PATH=$(echo "$target" | jq -r '.kustomizePath // empty')
            NAMESPACE=$(echo "$target" | jq -r '.namespace')
            CLUSTER=$(echo "$target" | jq -r '.cluster')

            OUT="${RENDER_DIR}/${APP}-${CLUSTER}.yaml"

            if [ "$TYPE" = "helm" ]; then
              helm dependency build "$CHART"
              helm lint "$CHART" -f "$VALUES" --strict
              helm template "$APP" "$CHART" \
                -n "$NAMESPACE" \
                -f "$VALUES" \
                --include-crds \
                > "$OUT"
            elif [ "$TYPE" = "kustomize" ]; then
              kubectl kustomize "$KUSTOMIZE_PATH" > "$OUT"
            elif [ "$TYPE" = "plain" ]; then
              yq eval '.' "$(echo "$target" | jq -r '.path')" > "$OUT"
            else
              echo "Unsupported type: $TYPE"
              exit 1
            fi

            ./ci/check-rendered-manifest.sh "$OUT"
          done
        '''
      }
    }

    stage('Kubernetes Schema Validation') {
      steps {
        sh '''
          set -euo pipefail

          jq -c '.[]' "${REPORT_DIR}/targets.json" | while read -r target; do
            APP=$(echo "$target" | jq -r '.app')
            CLUSTER=$(echo "$target" | jq -r '.cluster')
            KUBE_VERSION=$(echo "$target" | jq -r '.kubeVersion')
            MANIFEST="${RENDER_DIR}/${APP}-${CLUSTER}.yaml"

            kubeconform \
              -strict \
              -summary \
              -kubernetes-version "$KUBE_VERSION" \
              -schema-location default \
              -schema-location "schemas/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json" \
              "$MANIFEST"
          done
        '''
      }
    }

    stage('Policy Validation') {
      steps {
        sh '''
          set -euo pipefail

          jq -c '.[]' "${REPORT_DIR}/targets.json" | while read -r target; do
            APP=$(echo "$target" | jq -r '.app')
            CLUSTER=$(echo "$target" | jq -r '.cluster')
            MANIFEST="${RENDER_DIR}/${APP}-${CLUSTER}.yaml"

            kyverno apply infra-policy/kyverno \
              --resource "$MANIFEST" \
              --policy-report \
              > "${REPORT_DIR}/kyverno-${APP}-${CLUSTER}.txt"
          done
        '''
      }
    }

    stage('Server Dry Run') {
      when {
        expression {
          return fileExists('ci/enable-cluster-dryrun')
        }
      }
      steps {
        withCredentials([
          file(credentialsId: 'kubeconfig-gitops-validation', variable: 'KUBECONFIG')
        ]) {
          sh '''
            set -euo pipefail

            jq -c '.[]' "${REPORT_DIR}/targets.json" | while read -r target; do
              APP=$(echo "$target" | jq -r '.app')
              CLUSTER=$(echo "$target" | jq -r '.cluster')
              CONTEXT=$(echo "$target" | jq -r '.kubeContext')
              MANIFEST="${RENDER_DIR}/${APP}-${CLUSTER}.yaml"

              kubectl --context "$CONTEXT" \
                apply \
                --server-side \
                --dry-run=server \
                -f "$MANIFEST" \
                | tee "${REPORT_DIR}/dryrun-${APP}-${CLUSTER}.txt"
            done
          '''
        }
      }
    }

    stage('ArgoCD Diff') {
      when {
        expression {
          return fileExists('ci/enable-argocd-diff')
        }
      }
      steps {
        withCredentials([
          string(credentialsId: 'argocd-validation-token', variable: 'ARGOCD_AUTH_TOKEN')
        ]) {
          sh '''
            set -euo pipefail

            jq -c '.[]' "${REPORT_DIR}/targets.json" | while read -r target; do
              APP=$(echo "$target" | jq -r '.app')
              CLUSTER=$(echo "$target" | jq -r '.cluster')
              ARGO_APP=$(echo "$target" | jq -r '.argoApp')
              MANIFEST_DIR="${RENDER_DIR}"

              set +e
              argocd app diff "$ARGO_APP" \
                --local "$MANIFEST_DIR" \
                --local-repo-root . \
                --server-side-generate \
                > "${REPORT_DIR}/argocd-diff-${APP}-${CLUSTER}.txt"
              RC=$?
              set -e

              if [ "$RC" -eq 2 ]; then
                echo "ERROR: ArgoCD diff failed for ${ARGO_APP}"
                exit 1
              fi
            done
          '''
        }
      }
    }
  }

  post {
    always {
      archiveArtifacts artifacts: 'reports/**/*, rendered/**/*', allowEmptyArchive: true
    }
    success {
      echo 'GitOps validation passed. PR can be merged if peer review is also completed.'
    }
    failure {
      echo 'GitOps validation failed. Fix manifest, values, policy, or dry-run errors before merge.'
    }
  }
}

Jenkins Declarative Pipeline은 pipeline { ... } 블록 안에 정의하는 방식이며, 이런 형태로 단계별 검증과 post 처리 구성이 가능합니다. (Jenkins)

5. 대표 사용 예시

예시 1. Keycloak Helm values 변경

변경 파일:

infra-platform/keycloak/values/ic-p-data-01.yaml

실수 예:

global:
  clusterName: ic-p-data-01
  environment: production

image:
  repository: harbor.company.io/platform/keycloak
  tag: latest

resources:
  requests:
    cpu: "500m"
    memory: "1Gi"

env:
  KC_HOSTNAME: ""

Jenkins에서 잡아야 할 문제:

- image.tag=latest 금지
- production인데 resources.limits 누락
- KC_HOSTNAME 빈 값
- externalSecret.secretStoreRef 누락
- PDB 없음
- NetworkPolicy 없음

검증 결과 예:

[helm lint] values.schema.json violation:
  - image.tag must not be "latest"
  - resources.limits is required
  - env.KC_HOSTNAME must not be empty

[kyverno] require-pdb failed:
  - production workload must define PodDisruptionBudget

[kyverno] require-networkpolicy failed:
  - namespace keycloak has no NetworkPolicy

이 경우 PR은 merge되지 않고, ArgoCD 배포도 발생하지 않습니다.

예시 2. Cilium Kustomize overlay 변경

변경 파일:

infra-platform/cilium/overlays/ic-p-data-01/kustomization.yaml
infra-platform/cilium/overlays/ic-p-data-01/cilium-values.yaml

검증 항목:

1. kubectl kustomize 렌더링 성공 여부
2. Cilium CRD schema 검증
3. CiliumNetworkPolicy 문법 검증
4. kubeconform target K8s version 검증
5. kubectl dry-run server로 실제 cluster admission 검증
6. ArgoCD diff로 live 대비 변경 영향 확인

특히 Cilium, Gateway API, ExternalSecret, Kyverno 같은 CRD 기반 리소스는 kubeconform 기본 schema만으로는 부족할 수 있으므로, CRD schema 저장소를 내부에 두는 게 좋습니다.

예시 3. ArgoCD ApplicationSet 변경

변경 파일:

infra-gitops/clusters/ic-p-data-01/applicationset.yaml

검증 항목:

- targetRevision이 승인된 branch인지
- repoURL이 허용된 Bitbucket/Nexus/Harbor 주소인지
- destination server/namespace가 target cluster와 일치하는지
- AppProject가 올바른지
- syncPolicy.prune/selfHeal/allowEmpty 설정이 표준에 맞는지
- sync-wave 순서가 CRD → Namespace/RBAC → Core Component → Workload 순서인지

첨부 문서에도 ArgoCD App-of-Apps 패턴과 배포 순서가 CRD/Namespace/RBAC → Storage/Networking → Auth → Workload 순으로 정리되어 있으므로, ApplicationSet 검증에서 이 순서를 정책화할 수 있습니다.

간단한 검사 예:

#!/usr/bin/env bash
set -euo pipefail

APPSET="$1"

echo "[check] targetRevision must not be HEAD in production"
if yq '
  select(.kind == "ApplicationSet")
  | .spec.template.spec.source.targetRevision == "HEAD"
' "$APPSET" | grep -q true; then
  echo "ERROR: production ApplicationSet must use main, release/*, or pinned commit, not HEAD"
  exit 1
fi

echo "[check] allowEmpty must be false"
if yq '
  select(.kind == "ApplicationSet")
  | .spec.template.spec.syncPolicy.automated.allowEmpty == true
' "$APPSET" | grep -q true; then
  echo "ERROR: allowEmpty=true is not allowed"
  exit 1
fi

예시 4. Kubespray / Ansible inventory 변경

변경 파일:

infra-base/inventory/ic-p-data-01/hosts.yaml
infra-base/group_vars/ic-p-data-01/k8s-cluster.yml

검증 항목:

ansible-lint infra-base/playbooks
ansible-playbook \
  -i infra-base/inventory/ic-p-data-01/hosts.yaml \
  infra-base/playbooks/cluster.yml \
  --syntax-check

ansible-playbook \
  -i infra-base/inventory/ic-p-data-01/hosts.yaml \
  infra-base/playbooks/cluster.yml \
  --check \
  --diff \
  --limit staging-validation

첨부 문서의 CI 통과 항목에도 Ansible --check가 포함되어 있으므로, 클러스터 프로비저닝/노드 관리 영역은 Jenkins에서 이 방식으로 사전 검증하는 게 적합합니다.

6. 운영 기준으로는 이렇게 나누는 게 좋습니다

변경 유형Jenkins 검증 수준ArgoCD 동작
Dev/Testlint + render + kubeconform + policyAuto Sync 허용
Staging위 항목 + server dry-run + ArgoCD diffAuto Sync 허용 또는 manual
Production 낮은 변경위 항목 + prod dry-run + PR 승인Auto Sync 가능
Production 중/높은 변경위 항목 + staging 선검증 + prod dry-run + ArgoCD diff + 변경 계획서/롤백 확인Sync Window 또는 Manual Sync 권장

첨부 문서에서도 변경 유형에 따라 높은 변경은 파트장 승인과 Staging 선행이 필수이고, 모든 작업 계획서에 롤백 절차가 있어야 한다고 되어 있습니다.

7. 적용 시 주의할 점

첫째, Jenkins 검증이 끝나기 전 ArgoCD가 배포하지 못하게 branch 전략을 정리해야 합니다. ArgoCD는 보호 브랜치만 바라보고, Jenkins CI status가 성공해야만 해당 브랜치로 merge되게 해야 합니다.

둘째, server dry-run은 안전하지만 권한 설계가 필요합니다. 실제 저장은 하지 않지만 API server에 요청을 보내므로, 운영 cluster에는 전용 validation ServiceAccount를 두고 namespace/resource 범위를 제한해야 합니다.

셋째, CRD schema 관리가 중요합니다. 대규모 플랫폼에서는 Cilium, ArgoCD, ExternalSecret, Kyverno, Longhorn, AIStor 등 CRD가 많기 때문에, CRD schema를 infra-policy 또는 infra-gitops/schemas에 저장하고 버전별로 관리해야 합니다.

넷째, Secret은 절대 렌더링 로그에 노출하지 않아야 합니다. Jenkins artifact에 rendered manifest를 저장할 때도 Secret 값이 들어가지 않도록 ExternalSecret/SealedSecret 중심으로 설계해야 합니다.

다섯째, diff는 실패가 아니라 리뷰 자료입니다. argocd app diff에서 diff가 있다는 것은 PR 변경이 있다는 뜻일 수 있으므로, 오류와 변경 차이를 구분해서 처리해야 합니다.

8. 추천 시작안

처음부터 모든 것을 넣기보다 아래 순서로 단계화하는 게 좋습니다.

1단계: PR 필수 + Jenkins 기본 검증
  - yamllint
  - helm lint
  - kustomize build
  - kubeconform

2단계: 환경 적합성 검증
  - values.schema.json
  - 필수 env 검사
  - Secret/ConfigMap 참조 검사
  - cluster naming 검사

3단계: 정책 검증
  - Kyverno CLI
  - resource limit/request
  - image registry/tag
  - securityContext
  - namespace/network policy

4단계: cluster-aware 검증
  - staging server dry-run
  - production server dry-run
  - ArgoCD diff report

5단계: 운영 자동화 연계
  - Bitbucket PR status
  - Jira ticket comment
  - Slack notification
  - Confluence 변경 결과 링크

현재 문서상의 운영 원칙과 도구 구성을 보면, Jenkins를 GitOps 배포 전 CI 게이트로 두고 ArgoCD는 CD 전담으로 유지하는 구조가 가장 잘 맞습니다. S4가 CI/CD/GitOps를 담당하고 J3가 ArgoCD·Jenkins 운영과 파이프라인 모니터링을 맡는 역할 구조도 문서에 이미 잡혀 있으므로, 이 검증 체계를 Jenkins Shared Library로 표준화하면 팀 전체에 재사용하기 쉽습니다.

0개의 댓글