131 lines
16 KiB
Markdown
131 lines
16 KiB
Markdown
---
|
|
title: DevOps Baseline (CI + Supply chain + DX)
|
|
source_type: llm-generated
|
|
status: draft
|
|
confidence: medium
|
|
tags: [devops, ci-cd, supply-chain, sigstore, developer-experience]
|
|
related_projects: [ca-skeleton]
|
|
last_reviewed: 2026-05-22
|
|
---
|
|
|
|
# DevOps Baseline (CI + Supply chain + DX)
|
|
|
|
> Layer: `wiki/concepts/` — 일반 개념. 내 프로젝트 사실은 `project-template` / project 문서 사용.
|
|
|
|
## Summary
|
|
|
|
운영 가능한 백엔드 skeleton의 DevOps baseline은 세 축으로 구성된다.
|
|
**(1) CI quality gate** — GitHub Actions `needs:` + `if: success()`로 contract test ↔ release-blocking 의존성을 단일 yaml에서 강제하고, flaky test는 14일 sunset 기한이 붙은 quarantine bucket으로 분리한다.
|
|
**(2) Build / release supply chain** — Cosign keyless signing (Sigstore Fulcio + Rekor transparency log)으로 artifact를 서명하고, SLSA provenance attestation으로 build 출처를 검증하며, Gradle dependency-locking으로 transitive 버전 drift를 차단한다.
|
|
**(3) Developer experience** — `./gradlew bootstrap` 같은 단일 진입점 + Testcontainers `@ServiceConnection` 기반 integration test + `.tool-versions`로 핀된 JDK LTS로 새 개발자가 clean clone 직후 smoke까지 5단계로 도달한다.
|
|
|
|
## Standard (공식 정의)
|
|
|
|
### CI quality gate
|
|
|
|
- **GitHub Actions** (`docs.github.com/en/actions/`): YAML workflow의 `jobs.<id>.needs` 의존성과 `if: success() | failure()` 조건으로 단계별 gate를 표현. job status가 `failure`이면 workflow status도 `failure`.
|
|
- **GitLab CI/CD** (`docs.gitlab.com/ee/ci/pipelines/`): `stages` + `jobs` + `needs:` + `rules:` 키워드로 같은 모델을 구성. `parallel: matrix:` 키워드로 matrix job.
|
|
- **Jenkins Declarative Pipeline** (`jenkins.io/doc/book/pipeline/syntax/`): `agent` 디렉티브 + `post { failure { ... } }` block으로 실패 처리.
|
|
- **CircleCI configuration reference** (`circleci.com/docs/configuration-reference/`): orbs + workflow + job 모델.
|
|
- **Tekton Pipelines** (`tekton.dev/docs/pipelines/`): `Pipeline` = `Tasks`의 모음, 각 `Task`는 Kubernetes Pod로 실행.
|
|
|
|
### Supply chain
|
|
|
|
- **Sigstore Cosign** (`docs.sigstore.dev/cosign/signing/overview/`): OIDC identity token으로 Fulcio가 단명(10분) 서명 cert 발급, 서명 직후 private key 파기. 서명 이벤트는 **Rekor transparency log**에 immutable 기록. 검증 측은 `cosign verify --certificate-identity=... --certificate-oidc-issuer=...`로 issuer와 identity를 함께 강제.
|
|
- **SLSA v1.0 spec** (`slsa.dev/spec/v1.0/`): "Supply-chain Levels for Software Artifacts". provenance는 build platform, top-level build invocation, materials(sources + dependencies)를 최소 식별. Build L1 = provenance 존재, L2 = hosted build platform, L3 = hardened/hermetic build.
|
|
- **in-toto attestation** (`github.com/in-toto/attestation`): 인증된 statement = subject(artifact digest 목록) + predicate(예: SLSA Provenance). DSSE envelope으로 서명되며 Cosign이 같은 envelope을 서명한다.
|
|
- **Gradle dependency locking** (`docs.gradle.org/current/userguide/dependency_locking.html`): `dependencyLocking { lockAllConfigurations() }` + `--write-locks`로 lockfile 생성. `lockMode = STRICT`일 때 lock state와 다른 해석은 build fail.
|
|
- **Maven Enforcer Plugin** `dependencyConvergence` 룰: transitive lockfile은 부재. 부분 대응만 가능.
|
|
|
|
### Developer experience
|
|
|
|
- **Testcontainers for Java** (`java.testcontainers.org/`): Docker container 기반 throwaway dependency. Spring Boot 3.1+ `@ServiceConnection` annotation으로 JDBC URL, credentials, host, port가 ApplicationContext에 자동 주입. reuse 옵션은 CI 금지, 로컬만.
|
|
- **Devcontainer spec** (`containers.dev/implementors/spec/`): `.devcontainer/devcontainer.json`이 VSCode/Codespaces용 dev container 정의. tool version과 OS-level dep을 통일하지만 첫 진입점/smoke/migration 순서는 별도 필요.
|
|
- **mise / asdf** (`mise.jdx.dev/`, `asdf-vm.com/`) — `.tool-versions` 형식이 사실상 표준. **SDKMAN!** (`sdkman.io/`)은 별도 `.sdkmanrc` 사용.
|
|
- **Eclipse Temurin 21 LTS** (`adoptium.net/temurin/releases/?version=21`): 2028-09까지 무료 LTS 보안 패치.
|
|
|
|
## 한계 / 주의점
|
|
|
|
### CI
|
|
|
|
- **GitHub Actions**는 vendor lock-in(workflow yaml 문법, OIDC issuer URL, marketplace action 등)과 hosted runner 비용 모델이 다른 provider와 다르다. provider-agnostic하게 gate를 정의하지 않으면 이식 비용이 크다.
|
|
- **Jenkins / Tekton**은 인프라(k8s cluster, plugin ecosystem)에 대한 의존도가 커서 skeleton 단계에서는 과한 선택일 수 있다.
|
|
- **Flaky test quarantine**은 Spotify/Google/Microsoft가 운영 도구로 인정한 반면 Martin Fowler는 *"Eradicating Non-Determinism in Tests"*에서 quarantine 자체를 anti-pattern으로 본다. "Spotify가 한다 = 공식 best practice"로 표현 금지. 14일 sunset 같은 절충은 *어느 한쪽도 공식이 아니라는 인정*이다.
|
|
- **OpenAPI snapshot diff** (springdoc + openapi-diff/oasdiff)는 controller annotation을 정적 추출하므로 dynamic routing(예: webflux functional routes)이 있으면 누락된다. "ground truth"는 이 범위 안에서만 참.
|
|
|
|
### Supply chain
|
|
|
|
- **Cosign keyless**의 "signature 누락 시 deploy block"만으로는 부족하다. `--certificate-identity` + `--certificate-oidc-issuer`로 **identity 매칭 정책**을 별도로 명시해야 임의의 OIDC identity가 만든 서명도 통과되는 사고를 막을 수 있다. Sigstore 공식은 키리스 모드에서 두 flag를 **검증 진입 전제 조건**으로 강제하며(`--certificate-identity ... is required for verification in keyless mode`), GitHub Actions OIDC 환경의 expected identity는 `https://github.com/<ORG>/<REPO>/.github/workflows/<file>@refs/heads/<branch>` 형식, issuer는 `https://token.actions.githubusercontent.com`이다. 클러스터 측 강제는 policy-controller / Kyverno `verifyImages` 등 admission controller에서 expected identity/issuer를 정책으로 선언. — `needs-confirmation`: 정책 표현 형식은 조직별로 다름.
|
|
- **SLSA v1.0 spec**의 실제 필드명은 두 최상위 객체로 구성된다. `buildDefinition.{buildType, externalParameters, internalParameters, resolvedDependencies}` + `runDetails.{builder.id, builder.version, builder.builderDependencies, metadata.invocationId, metadata.startedOn, metadata.finishedOn, byproducts}`. in-toto Statement 래퍼는 `_type`(`https://in-toto.io/Statement/v1`) + `subject[*].digest` + `predicateType`(`https://slsa.dev/provenance/v1`) + `predicate`. 약식 표현(`build.config.source`, `build.invocation`, `materials`)은 spec 필드명과 다르므로 slsa-verifier가 `--builder-id` ↔ `runDetails.builder.id` 등의 필드를 찾지 못해 검증이 실패한다. provenance 생성 단계에서 spec 필드명을 그대로 사용해야 한다. — 출처: [[raw/official-docs/slsa-v1-provenance-schema]].
|
|
- **SLSA Build L3** (hardened build, hermetic, tamper-resistant builder)는 GitHub Actions hosted runner만으로는 도달 불가. 실무적으로는 L2(hosted build platform)가 현실적 목표지점.
|
|
- **Gradle dependency-locking**이 있어도 plugin 버전과 toolchain(JDK)은 별도 핀이 필요. `.tool-versions` / `gradle/wrapper/gradle-wrapper.properties` 핀과 함께 봐야 reproducible build가 완성된다.
|
|
- **Maven**에는 transitive lockfile이 1급 시민으로 존재하지 않는다. Maven 기반 프로젝트에서 같은 수준의 reproducibility를 요구하면 추가 도구가 필요.
|
|
|
|
### Developer experience
|
|
|
|
- **`.tool-versions`(asdf/mise) vs `.sdkmanrc`(SDKMAN)** 포맷 차이. 두 파일을 동시에 두면 drift 위험. 단일 source로 좁히는 편이 안전하다.
|
|
- **Devcontainer**는 VSCode/Codespaces에 의존한다. IntelliJ + 로컬 JDK 사용자에게는 중복 환경이 되며 bootstrap 단일 진입점/smoke는 devcontainer 안에서도 별도로 정의되어야 한다.
|
|
- **Testcontainers**는 Apple Silicon(arm64) 환경에서 일부 image가 emulation(amd64) 위에서 동작해 bootstrap 시간이 늘어날 수 있다.
|
|
- **Testcontainers reuse 옵션**은 CI에서는 반드시 비활성화. test 간 isolation을 깬다.
|
|
- **Bootstrap 한 줄 명령**은 ergonomic 강점이 있으나 단계가 합쳐져 있어 *어느 단계에서 실패했는지* 추적이 어려울 수 있다. 실패 단계별 exit code 또는 step 출력 분리가 필요.
|
|
|
|
## Project Application
|
|
|
|
- [[wiki/projects/ca-tmpl/devops-ci-supply-chain-dx]] — ca-tmpl 의사결정 기록 (현재 `documented-only`, Phase C2 미진입). 실제 구현 여부는 project 문서 참조.
|
|
내 프로젝트(ca-skeleton)에서 이 개념과 관련된 문서로 **링크**. 실제 구현 여부·검증 등급은 해당 project / branch 문서에서 판정 (concept 문서는 등급을 직접 매기지 않음).
|
|
|
|
- [[raw/project-notes/ca-skeleton-operational-contract]] — §29 G-E (외부 근거 / 대안 조사 인덱스, DevOps / CI).
|
|
- [[raw/branch-notes/feature-ci-quality-gates-contract]] — Gate ↔ Branch Contract Test 소유권 매트릭스 20행, flaky quarantine 14d sunset SSOT, OpenAPI snapshot diff.
|
|
- [[raw/branch-notes/feature-build-release-supply-chain-contract]] — Cosign keyless 의무, SLSA provenance attestation 의무, Gradle dependency-locking, SemVer + git sha suffix, reproducibility.
|
|
- [[raw/branch-notes/feature-developer-experience-contract]] — `./gradlew bootstrap` 5단계, Temurin 21 LTS, Testcontainers integration, markdown-link-check.
|
|
|
|
## Interview Questions
|
|
|
|
- "CI에서 Gate ↔ Branch Contract Test 소유권 매트릭스란 무엇이고 왜 필요한가? 누가 어떤 gate를 깨질 때 책임지는지 어떻게 표현하는가?"
|
|
- "Flaky test quarantine bucket에 sunset deadline을 14일로 두는 근거는 무엇인가? quarantine 자체를 반대하는 입장(Fowler)과 어떻게 절충하는가?"
|
|
- "Cosign keyless signing이 GPG signing과 비교해 어떤 운영 비용을 제거하고, 어떤 새 의존성(OIDC IdP, Rekor 가용성)을 추가하는가?"
|
|
- "SLSA build level L1/L2/L3가 각각 무엇을 보장하는가? skeleton 단계에서 현실적으로 도달 가능한 level은 어디까지인가?"
|
|
- "Gradle dependency-locking이 필요한 이유는 무엇이고, Maven에는 왜 같은 수준의 lockfile이 없으며 어떻게 대체하는가?"
|
|
- "Integration test backend로 Testcontainers를 H2 같은 in-memory DB 대신 선택하는 이유는 무엇인가? 그 비용은 무엇인가?"
|
|
|
|
## Do Not Overclaim
|
|
|
|
- "Cosign signature 누락만 차단하면 supply chain이 안전하다"고 단정 금지. **identity 매칭 정책**(`--certificate-identity` + `--certificate-oidc-issuer`)이 없으면 임의 OIDC identity가 만든 서명도 통과될 수 있다.
|
|
- "SLSA Build L3를 달성했다"고 단정 금지. ca-skeleton 단계에서 L3는 hermetic build / tamper-resistant builder를 요구하며 GitHub Actions hosted runner만으로는 도달 어렵다. branch note의 약식 매핑(`build.config.source` 등)은 spec 실제 필드명(`buildDefinition.externalParameters`)과 다르므로 정정 필요.
|
|
- "Google/Spotify/Microsoft가 flaky test quarantine을 운영하므로 공식 best practice다"라고 표현 금지. 이들은 *company-tech-blog* 등급이며 Fowler의 반대 입장이 함께 존재한다.
|
|
- "GitHub Actions가 CI provider의 정답이다"로 단정 금지. ca-skeleton은 `needs:` + `if: success()` 모델이 contract gate에 맞물려 채택된 것이며, gate 정의 자체는 provider-agnostic하게 작성되어야 이식 가능하다.
|
|
- "`./gradlew bootstrap` 한 줄이 끝났다 = 모든 게 정상이다"로 표현 금지. 5단계(compileTestJava → docker compose up → Flyway migrate → sample profile seed → smoke) 중 어디서 실패했는지 step 단위 검증이 필요.
|
|
- "Devcontainer가 있으면 bootstrap이 필요 없다"로 표현 금지. devcontainer는 tool version과 OS-level dep만 통일하며, 진입점/smoke/migration 순서는 별도로 정의되어야 한다.
|
|
- LLM 생성 문서이므로 본 concept 문서의 모든 진술은 `confidence: medium`. 검증 전 high confidence로 분류 금지.
|
|
|
|
## Sources
|
|
|
|
### 공식 문서 / spec
|
|
|
|
- [GitHub Actions — Migrating from GitLab CI/CD](https://docs.github.com/en/actions/learn-github-actions/migrating-from-gitlab-cicd-to-github-actions) / [GitLab CI/CD pipelines](https://docs.gitlab.com/ee/ci/pipelines/) / [Jenkins Declarative Pipeline](https://www.jenkins.io/doc/book/pipeline/syntax/) / [CircleCI configuration reference](https://circleci.com/docs/configuration-reference/) / [Tekton Pipelines overview](https://tekton.dev/docs/pipelines/) — CI provider 모델 비교.
|
|
- [Sigstore Cosign overview](https://docs.sigstore.dev/cosign/signing/overview/) + [Fulcio](https://docs.sigstore.dev/certificate_authority/overview/) + [Rekor](https://docs.sigstore.dev/logging/overview/) — keyless signing 체인.
|
|
- [SLSA v1.0 spec](https://slsa.dev/spec/v1.0/) + [Build levels](https://slsa.dev/spec/v1.0/levels) + [Provenance schema](https://slsa.dev/spec/v1.0/provenance) + [in-toto attestation](https://github.com/in-toto/attestation) — supply chain provenance.
|
|
- [Gradle dependency locking](https://docs.gradle.org/current/userguide/dependency_locking.html) + [Maven Enforcer dependencyConvergence](https://maven.apache.org/enforcer/enforcer-rules/dependencyConvergence.html) — dependency lockfile 정책.
|
|
- [Testcontainers for Java](https://java.testcontainers.org/) + [reuse](https://java.testcontainers.org/features/reuse/) + [Spring Boot Testcontainers support](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers) — integration test backend.
|
|
- [Devcontainer spec](https://containers.dev/implementors/spec/) + [VS Code Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) + [GitHub Codespaces](https://docs.github.com/en/codespaces/overview) — dev environment 통일.
|
|
- [mise](https://mise.jdx.dev/) + [asdf](https://asdf-vm.com/) + [SDKMAN!](https://sdkman.io/usage#env) + [Adoptium Temurin 21](https://adoptium.net/temurin/releases/?version=21) — tool versioning + JDK LTS.
|
|
- [springdoc-openapi](https://springdoc.org/) + [OpenAPITools/openapi-diff](https://github.com/OpenAPITools/openapi-diff) + [Tufin/oasdiff](https://github.com/Tufin/oasdiff) + [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) — OpenAPI snapshot diff.
|
|
|
|
### Raw 원본 (저장소 내 발췌)
|
|
|
|
- [[raw/official-docs/ci-github-actions-vs-gitlab-comparison]] — GitHub Actions `needs:` + `if: success()`가 contract gate 매트릭스에 맞물리는 근거, Jenkins/Tekton의 k8s 인프라 부담.
|
|
- [[raw/official-docs/ci-openapi-snapshot-diff-tooling]] — springdoc 런타임 추출 + openapi-diff/oasdiff CI 실패 조건, dynamic routing 함정.
|
|
- [[raw/company-tech-blogs/ci-flaky-test-quarantine-spotify-google]] — Spotify/Google/MS quarantine 인정 vs Fowler 반대 양립, 14d sunset은 절충.
|
|
- [[raw/official-docs/supply-chain-cosign-keyless-sigstore]] — Fulcio 단명 cert + Rekor transparency log + identity 매칭 정책 필요성.
|
|
- [[raw/official-docs/cosign-keyless-identity-verification-policy]] — `--certificate-identity` + `--certificate-oidc-issuer` 키리스 검증 강제 (Sigstore docs / cosign issue #3671), GitHub Actions OIDC identity 포맷.
|
|
- [[raw/official-docs/supply-chain-slsa-provenance-framework]] — SLSA v1.0 build levels, provenance 최소 필드, in-toto attestation, 약식 매핑 정정 필요.
|
|
- [[raw/official-docs/slsa-v1-provenance-schema]] — SLSA v1.0 provenance 실제 필드명 표(`buildDefinition.*` / `runDetails.*`) + in-toto Statement v1 래퍼 + slsa-verifier 검사 동작. ca-tmpl 약식 명명 정정 근거.
|
|
- [[raw/official-docs/supply-chain-gradle-vs-maven-dependency-locking]] — Gradle `lockMode = STRICT`, Maven transitive lockfile 부재.
|
|
- [[raw/official-docs/dx-testcontainers-java-best-practices]] — Spring Boot 3.1+ `@ServiceConnection`, singleton 패턴, CI에서 reuse 금지.
|
|
- [[raw/official-docs/dx-mise-asdf-tool-versioning]] — `.tool-versions` 사실상 표준, `.sdkmanrc`와의 drift 위험, Temurin 21 LTS.
|
|
- [[raw/official-docs/dx-devcontainer-spring-boot]] — devcontainer가 보장하는 것/보장하지 않는 것, IDE 종속성.
|
|
|
|
### Canonical 참조
|
|
|
|
- [[raw/project-notes/ca-skeleton-operational-contract]] §29 Group G-E — DevOps / CI / Supply chain / DX 대안 조사 인덱스.
|