--- id: devops-external-secrets-atlantis title: External Secrets / Atlantis / GitHub Actions deep category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [devops, secrets, gitops, vibe-coding] tech_stack: { language: "YAML", applicable_to: ["DevOps"] } applied_in: [] aliases: [external secrets operator, Atlantis, Terraform PR, GitHub Actions, OIDC, reusable workflow] --- # External Secrets / Atlantis / GHA > K8s + Vault + Terraform + CI 의 modern integration. **External Secrets Operator (vault β†’ K8s), Atlantis (Terraform PR), GHA (advanced)**. ## πŸ“– 핡심 κ°œλ… - ESO: μ™ΈλΆ€ secret store β†’ K8s Secret μžλ™ sync. - Atlantis: Terraform 의 GitOps. - GHA OIDC: cloud auth κ°€ token 없이. - Reusable workflow / matrix. ## πŸ’» μ½”λ“œ νŒ¨ν„΄ ### External Secrets Operator ```yaml # SecretStore (Vault μ—°κ²°) apiVersion: external-secrets.io/v1beta1 kind: SecretStore metadata: name: vault-store spec: provider: vault: server: https://vault.example.com path: secret auth: kubernetes: mountPath: kubernetes role: my-role ``` ```yaml # ExternalSecret (sync) apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: db-credentials spec: refreshInterval: 1m secretStoreRef: name: vault-store kind: SecretStore target: name: db-secret # β†’ K8s Secret data: - secretKey: password remoteRef: key: secret/myapp/db property: password ``` β†’ App κ°€ K8s Secret 만 read. Vault κ°€ source. ### AWS Secrets Manager 도 ```yaml spec: provider: aws: service: SecretsManager region: us-east-1 auth: jwt: serviceAccountRef: name: external-secrets-sa ``` β†’ AWS IRSA (IAM Role for SA). ### Auto rotation ```yaml # AWS Secrets Manager κ°€ λ§€ 30 days rotate # ESO κ°€ λ§€ 1 min check + sync # K8s pod κ°€ secret mount = μžλ™ update (eventually) ``` ### Pod restart ```yaml # Stakater Reloader κ°€ secret λ³€κ²½ μ‹œ pod restart metadata: annotations: secret.reloader.stakater.com/reload: 'db-secret' ``` β†’ Secret λ³€κ²½ β†’ pod restart β†’ μƒˆ credential. ### Atlantis (Terraform 의 GitOps) ```yaml # atlantis.yaml version: 3 projects: - name: prod-vpc dir: terraform/prod/vpc autoplan: when_modified: ['*.tf', '*.tfvars'] apply_requirements: [approved] ``` ### Workflow ``` 1. Engineer κ°€ PR κ°€ terraform λ³€κ²½. 2. Atlantis κ°€ μžλ™ `terraform plan` β†’ PR comment 에 κ²°κ³Ό. 3. Reviewer κ°€ plan κ²€ν† . 4. Approved β†’ engineer κ°€ `atlantis apply`. 5. Atlantis κ°€ `terraform apply`. 6. PR merge. ``` β†’ "Apply ν›„ merge" β€” drift μ•ˆ. ### Atlantis 의 lock ``` λ§€ project κ°€ 1 PR 만 apply κ°€λŠ₯. - A κ°€ apply 쀑 β†’ B κ°€ wait. - "Apply" PR comment κ°€ lock κ°€μ Έ. - μ’…λ£Œ μ‹œ release. ``` β†’ Concurrent apply κ°€ conflict λ°©μ§€. ### terraform plan output ```diff $ atlantis plan + aws_instance.web ami = "ami-12345" instance_type = "t3.micro" - aws_security_group.old ~ aws_db_instance.main ~ instance_class: "db.t3.micro" -> "db.t3.small" ``` β†’ PR 의 visual plan. ### Atlantis 의 alternative ``` - Spacelift (managed, 더 κ°•λ ₯) - Terraform Cloud / HCP Terraform (HashiCorp) - env0 (managed) - Terragrunt + custom CI ``` β†’ Atlantis κ°€ OSS + 자체 host. ### GitHub Actions OIDC (cloud auth) ```yaml # .github/workflows/deploy.yml permissions: id-token: write # OIDC token contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789:role/GHA-Deploy aws-region: us-east-1 - run: aws s3 sync ./build s3://my-bucket ``` β†’ Long-lived AWS key X. OIDC token κ°€ short-lived. β†’ AWS / GCP / Azure κ°€ OIDC trust μ„€μ •. ### Reusable workflow ```yaml # .github/workflows/test.yml (reusable) on: workflow_call: inputs: node-version: type: string default: '20' jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: ${{ inputs.node-version }} } - run: npm ci && npm test ``` ```yaml # Caller jobs: test: uses: ./.github/workflows/test.yml with: node-version: '22' ``` ### Matrix ```yaml strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] node: [18, 20, 22] runs-on: ${{ matrix.os }} steps: - uses: actions/setup-node@v4 with: { node-version: ${{ matrix.node }} } ``` β†’ 9 jobs κ°€ parallel. ### Conditional matrix ```yaml strategy: matrix: os: [ubuntu-latest, windows-latest] include: - os: macos-latest special: true exclude: - os: windows-latest node: 18 ``` ### Environment + secrets ```yaml jobs: deploy: environment: production # β†’ manual approval gate steps: - run: ./deploy.sh env: API_KEY: ${{ secrets.API_KEY }} # environment-scoped ``` β†’ Prod environment κ°€ 별 secrets + manual approval. ### Composite action ```yaml # .github/actions/setup/action.yml name: 'Setup' runs: using: 'composite' steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' } - shell: bash run: npm ci ``` ```yaml # Use - uses: ./.github/actions/setup ``` β†’ Reusable inline. ### Concurrency ```yaml concurrency: group: ${{ github.ref }} cancel-in-progress: true ``` β†’ 같은 branch 의 μƒˆ push = μ˜› cancel. ### Cache ```yaml - uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- ``` β†’ Build 빠름. ### Dependabot ```yaml # .github/dependabot.yml version: 2 updates: - package-ecosystem: 'github-actions' directory: '/' schedule: interval: 'weekly' ``` β†’ Action 도 μžλ™ update. ### Self-hosted runner ```yaml runs-on: self-hosted # 자체 server κ°€ runner. ``` β†’ Cost ↓ (큰 traffic). Setup overhead. ### Action security ``` - Pin to SHA (version κ°€ mutable). - uses: actions/checkout@v4.1.0 - uses: actions/checkout@b32f140b0c872d58512e0a66172253c302617b90 # SHA β†’ Supply chain μ•ˆμ „. ``` ### GitHub Actions 의 secret 주의 ``` Secret value κ°€ log mask (μžλ™). ν•˜μ§€λ§Œ: - λΆ€λΆ„ echo κ°€ κ°€λŠ₯. - Forked PR κ°€ secret access X. - μ™ΈλΆ€ action κ°€ secret access (μ•…μ„± κ°€λŠ₯). β†’ Pin SHA. Secret μ΅œμ†Œν™”. ``` ### Workflow_dispatch (manual) ```yaml on: workflow_dispatch: inputs: env: description: 'Deploy environment' required: true type: choice options: [dev, staging, prod] ``` β†’ UI μ—μ„œ click κ°€ trigger. ### Schedule ```yaml on: schedule: - cron: '0 9 * * 1-5' # 평일 9 AM UTC ``` β†’ Cron κ°€ GHA μ•ˆ. ### Workflow run 의 cost ``` Public repo: 무료. Private repo: - 2000 min / month free. - $0.008 / minute (Linux). - $0.04 / minute (Windows / macOS). β†’ Optimization (cache, matrix exclude) κ°€ λ§€ cost. ``` ## πŸ€” μ˜μ‚¬κ²°μ • κΈ°μ€€ | μž‘μ—… | μΆ”μ²œ | |---|---| | K8s + Vault | External Secrets Operator | | Terraform GitOps | Atlantis | | Cloud auth | GHA OIDC | | 반볡 logic | Reusable workflow / composite action | | Multi-env | Environment + protection | | Cost-sensitive | Self-hosted runner | | Security | Pin SHA | ## ❌ μ•ˆν‹°νŒ¨ν„΄ - **K8s Secret 직접 commit (encrypted SealedSecret 도)**: rotation 어렀움. - **Long-lived AWS key**: OIDC. - **λͺ¨λ“  κ±° inline workflow**: reuse μ•ˆ 됨. - **Secret echo**: leak. - **No environment**: prod κ°€ dev 와 같은 gate. - **No concurrency**: 쀑볡 deploy. - **Action @main**: supply chain. ## πŸ€– LLM ν™œμš© 힌트 - ESO κ°€ K8s + μ™ΈλΆ€ secret store 의 λ‹΅. - Atlantis κ°€ Terraform 의 GitOps. - GHA OIDC κ°€ modern cloud auth. - Reusable workflow κ°€ DRY. ## πŸ”— κ΄€λ ¨ λ¬Έμ„œ - [[DevOps_Vault_Secrets]] - [[DevOps_Terraform_Patterns]] - [[DevOps_CI_CD_Pipeline_Patterns]]