CI/CD 컴포넌트 예시
CI/CD 컴포넌트 예시 (CI/CD component examples)
컴포넌트를 실제로 어떻게 테스트하고, 공통 패턴을 어떻게 구현하는지, 기존 CI/CD 템플릿을 어떻게 재사용 컴포넌트로 옮기는지를 예시로 확인해 볼게요. 코드와 YAML을 그대로 놓고 단계를 하나씩 따라가면 컴포넌트 감이 금방 잡힙니다.
출처: 문서
본문
컴포넌트 테스트 (Test a component)
컴포넌트의 기능에 따라 컴포넌트 테스트에는 저장소의 추가 파일이 필요할 수 있어요. 예를 들어 특정 프로그래밍 언어의 소프트웨어를 린트·빌드·테스트하는 컴포넌트는 실제 소스 코드 샘플이 필요합니다. 소스 코드 예시, 구성 파일 등을 같은 저장소에 둘 수 있어요. 예를 들어 Code Quality CI/CD 컴포넌트에는 테스트용 코드 샘플이 여러 개 있습니다.
예시: Rust 언어 CI/CD 컴포넌트 테스트 (Example: Test a Rust language CI/CD component)
다음은 Rust 프로그래밍 언어의 “hello world” 예시로, 단순함을 위해 cargo 툴체인을 사용해요.
- CI/CD 컴포넌트 루트 디렉터리로 이동합니다.
cargo init명령으로 새 Rust 프로젝트를 초기화합니다.
cargo init
이 명령은 src/main.rs “hello world” 예시를 포함한 모든 필요한 프로젝트 파일을 만들어요. 이 단계만으로도 컴포넌트 job에서 cargo build로 Rust 소스를 빌드할 수 있어요.
tree
.
├── Cargo.toml
├── LICENSE.md
├── README.md
├── src
│ └── main.rs
└── templates
└── build.yml
- 예를 들어
templates/build.yml에서 컴포넌트가 Rust 소스를 빌드하는 job을 갖도록 합니다.
spec:
inputs:
stage:
default: build
description: 'Defines the build stage'
rust_version:
default: latest
description: 'Specify the Rust version, use values from https://hub.docker.com/_/rust/tags Defaults to latest'
---
"build-$[[ inputs.rust_version ]]":
stage: $[[ inputs.stage ]]
image: rust:$[[ inputs.rust_version ]]
script:
- cargo build --verbose
이 예시에서:
stage와rust_versioninputs는 기본값에서 수정할 수 있어요. CI/CD job은build-접두사로 시작하고rust_versioninputs에 따라 이름을 동적으로 만듭니다.cargo build --verbose명령이 Rust 소스를 컴파일해요.
- 프로젝트의
.gitlab-ci.yml구성 파일에서 컴포넌트의build템플릿을 테스트합니다.
include:
# include the component located in the current project from the current SHA
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/build@$CI_COMMIT_SHA
inputs:
stage: build
stages: [build, test, release]
- 테스트 실행 등을 위해 Rust 코드에 추가 함수·테스트를 넣고,
templates/test.yml에cargo test를 실행하는 컴포넌트 템플릿과 job을 추가합니다.
spec:
inputs:
stage:
default: test
description: 'Defines the test stage'
rust_version:
default: latest
description: 'Specify the Rust version, use values from https://hub.docker.com/_/rust/tags Defaults to latest'
---
"test-$[[ inputs.rust_version ]]":
stage: $[[ inputs.stage ]]
image: rust:$[[ inputs.rust_version ]]
script:
- cargo test --verbose
test컴포넌트 템플릿을 include해서 파이프라인에서 추가 job을 테스트합니다.
include:
# include the component located in the current project from the current SHA
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/build@$CI_COMMIT_SHA
inputs:
stage: build
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/test@$CI_COMMIT_SHA
inputs:
stage: test
stages: [build, test, release]
CI/CD 컴포넌트 패턴 (CI/CD component patterns)
이 섹션은 CI/CD 컴포넌트에서 공통 패턴을 구현하는 실제 예시를 제공해요.
boolean inputs로 조건부로 job 구성 (Use boolean inputs to conditionally configure jobs)
boolean 타입 inputs와 extends 기능을 조합하면 두 가지 조건으로 job을 구성할 수 있어요. 예를 들어 boolean input으로 복잡한 캐싱 동작을 구성하려면 이렇게 합니다.
spec:
inputs:
enable_special_caching:
description: 'If set to `true` configures a complex caching behavior'
type: boolean
---
.my-component:enable_special_caching:false:
extends: null
.my-component:enable_special_caching:true:
cache:
policy: pull-push
key: $CI_COMMIT_SHA
paths: [...]
my-job:
extends: '.my-component:enable_special_caching:$[[ inputs.enable_special_caching ]]'
script: ... # run some fancy tooling
이 패턴은 enable_special_caching input을 job의 extends 키워드로 전달해서 동작해요. enable_special_caching이 true인지 false인지에 따라 사전 정의된 숨은 job(.my-component:enable_special_caching:true 또는 .my-component:enable_special_caching:false)에서 적절한 구성이 선택됩니다.
options로 조건부로 job 구성 (Use options to conditionally configure jobs)
여러 옵션으로 job을 구성하면 if와 elseif 조건과 비슷하게 동작하게 만들 수 있어요. string 타입과 여러 options를 가진 extends를 사용해서 원하는 만큼 조건을 만들 수 있습니다. 예를 들어 3가지 옵션으로 복잡한 캐싱 동작을 구성하려면 이렇게 해요.
spec:
inputs:
cache_mode:
description: Defines the caching mode to use for this component
type: string
options:
- default
- aggressive
- relaxed
---
.my-component:cache_mode:default:
extends: null
.my-component:cache_mode:aggressive:
cache:
policy: push
key: $CI_COMMIT_SHA
paths: ['*/**']
.my-component:cache_mode:relaxed:
cache:
policy: pull-push
key: $CI_COMMIT_BRANCH
paths: ['bin/*']
my-job:
extends: '.my-component:cache_mode:$[[ inputs.cache_mode ]]'
script: ... # run some fancy tooling
이 예시에서 cache_mode input은 default, aggressive, relaxed 옵션을 제공하고, 각각 다른 숨은 job에 대응합니다. extends: '.my-component:cache_mode:$[[ inputs.cache_mode ]]'로 컴포넌트 job을 확장하면 선택한 옵션에 따라 적절한 캐싱 구성을 동적으로 상속합니다.
컴포넌트 컨텍스트로 버전 자원 참조 (Use component context to reference versioned resources)
- GitLab 18.6에서
ci_component_context_interpolation이라는 기능 플래그와 함께 beta로 도입. 기본으로 활성화되어 있어요. - GitLab 18.7에서 일반 공개. 기능 플래그
ci_component_context_interpolation은 제거됐어요.
CI/CD 표현식의 컴포넌트 컨텍스트로 버전·커밋 SHA 같은 컴포넌트 메타데이터를 참조할 수 있어요. 한 가지 사용 사례는 버전 자원(예: Docker 이미지)을 컴포넌트로 빌드·게시하고, 컴포넌트가 일치하는 버전을 사용하도록 하는 거예요. 예를 들어 이렇게 할 수 있습니다.
- 컴포넌트의 릴리스 파이프라인에서 컴포넌트 버전과 일치하는 태그로 Docker 이미지를 빌드.
- 컴포넌트가 같은 이미지 버전을 참조하게 하기.
컴포넌트 프로젝트의 릴리스 파이프라인(.gitlab-ci.yml)에서:
build-image:
stage: build
image: docker:latest
script:
- docker build -t $CI_REGISTRY_IMAGE/my-tool:$CI_COMMIT_TAG .
- docker push $CI_REGISTRY_IMAGE/my-tool:$CI_COMMIT_TAG
create-release:
stage: release
image: registry.gitlab.com/gitlab-org/cli:latest
script: echo "Creating release $CI_COMMIT_TAG"
rules:
- if: $CI_COMMIT_TAG
release:
tag_name: $CI_COMMIT_TAG
description: "Release $CI_COMMIT_TAG"
컴포넌트 템플릿(templates/my-component/template.yml)에서:
spec:
component: [version, reference]
inputs:
stage:
default: test
---
run-tool:
stage: $[[ inputs.stage ]]
image: $CI_REGISTRY_IMAGE/my-tool:$[[ component.version ]]
script:
- echo "Running tool version $[[ component.version ]]"
- echo "Component was included using reference: $[[ component.reference ]]"
- my-tool --version
이 예시에서:
- 컴포넌트를
@1.0.0으로 include하면 job은my-tool:1.0.0이미지를 사용해요. @1.0으로 include하면 최신1.0.x버전(예:1.0.3)으로 해석되어my-tool:1.0.3을 씁니다.@~latest로 include하면 최신 릴리스 버전을 사용해요.component.reference필드는 지정한 정확한 참조(1.0,~latest, SHA 등)를 보여줍니다. 로깅이나 디버깅에 참조를 쓸 수 있어요.
CI/CD 컴포넌트 마이그레이션 예시 (CI/CD component migration examples)
이 섹션은 CI/CD 템플릿과 파이프라인 구성을 재사용 CI/CD 컴포넌트로 옮기는 실제 예시를 보여줘요.
CI/CD 컴포넌트 마이그레이션 예시: Go (CI/CD component migration example: Go)
소프트웨어 개발 생명주기의 완전한 파이프라인은 여러 job과 스테이지로 구성할 수 있어요. 프로그래밍 언어용 CI/CD 템플릿은 단일 템플릿 파일에 여러 job을 제공할 수 있습니다. 연습 삼아 다음 Go CI/CD 템플릿을 마이그레이션해 볼게요.
default:
image: golang:latest
stages:
- test
- build
- deploy
format:
stage: test
script:
- go fmt $(go list ./... | grep -v /vendor/)
- go vet $(go list ./... | grep -v /vendor/)
- go test -race $(go list ./... | grep -v /vendor/)
compile:
stage: build
script:
- mkdir -p mybinaries
- go build -o mybinaries ./...
artifacts:
paths:
- mybinaries
[!NOTE] 더 점진적인 접근을 원한다면 job을 한 번에 하나씩 마이그레이션하세요.
buildjob부터 시작하고format,testjob에 같은 단계를 반복하세요.
CI/CD 템플릿 마이그레이션은 다음 단계를 포함합니다.
- CI/CD job과 의존성을 분석하고 마이그레이션 작업을 정의합니다.
image구성은 전역이라서 job 정의 안으로 옮겨야 해요.formatjob은 한 job에서 여러go명령을 실행해요. 파이프라인 효율을 높이려면go test명령을 별도 job으로 옮겨야 합니다.compilejob은go build를 실행하며build로 이름을 바꿔야 해요.
- 더 나은 파이프라인 효율을 위한 최적화 전략을 정의합니다.
stagejob 속성은 서로 다른 CI/CD 파이프라인 소비자들을 허용하도록 구성 가능해야 해요.image키는 하드코딩된 이미지 태그latest를 써요. 더 유연·재사용 가능한 파이프라인을 위해golang_version을 input으로 추가하고 기본값은latest로 둡니다. input은 Docker Hub 이미지 태그 값과 일치해야 해요.compilejob은 하드코딩된 대상 디렉터리mybinaries에 바이너리를 빌드하는데, 이를 동적 input과 기본값mybinaries로 개선할 수 있어요.
- job마다 템플릿 하나씩으로 새 컴포넌트의 디렉터리 구조를 만듭니다.
- 템플릿 이름은
go명령을 따라야 해요. 예:format.yml,build.yml,test.yml. - 새 프로젝트를 만들고, Git 저장소를 초기화하고, 변경을 추가·커밋하고, 원격 origin을 설정하고, 푸시합니다. URL을 여러분의 CI/CD 컴포넌트 프로젝트 경로에 맞게 수정하세요.
- 컴포넌트 작성 가이드에 따라 추가 파일(
README.md,LICENSE.md,.gitlab-ci.yml,.gitignore)을 만듭니다. 다음 셸 명령으로 Go 컴포넌트 구조를 초기화합니다.
- 템플릿 이름은
git init
mkdir templates
touch templates/{format,build,test}.yml
touch README.md LICENSE.md .gitlab-ci.yml .gitignore
git add -A
git commit -avm "Initial component structure"
git remote add origin https://gitlab.example.com/components/golang.git
git push
- CI/CD job을 템플릿으로 만듭니다.
buildjob부터 시작하세요.spec섹션에stage,golang_version,binary_directoryinputs를 정의합니다.inputs.golang_version에 접근하는 동적 job 이름 정의를 추가합니다.inputs.golang_version에 접근하는 동적 Go 이미지 버전에도 비슷한 패턴을 사용합니다.- 스테이지를
inputs.stage값으로 지정합니다. inputs.binary_directory로 바이너리 디렉터리를 만들고go build의 매개변수로 추가합니다.- artifact 경로를
inputs.binary_directory로 정의합니다.
spec:
inputs:
stage:
default: 'build'
description: 'Defines the build stage'
golang_version:
default: 'latest'
description: 'Go image version tag'
binary_directory:
default: 'mybinaries'
description: 'Output directory for created binary artifacts'
---
"build-$[[ inputs.golang_version ]]":
image: golang:$[[ inputs.golang_version ]]
stage: $[[ inputs.stage ]]
script:
- mkdir -p $[[ inputs.binary_directory ]]
- go build -o $[[ inputs.binary_directory ]] ./...
artifacts:
paths:
- $[[ inputs.binary_directory ]]
```
- `format` job 템플릿은 같은 패턴을 따르지만 `stage`와 `golang_version` inputs만 필요해요.
```yaml
spec:
inputs:
stage:
default: 'format'
description: 'Defines the format stage'
golang_version:
default: 'latest'
description: 'Golang image version tag'
---
"format-$[[ inputs.golang_version ]]":
image: golang:$[[ inputs.golang_version ]]
stage: $[[ inputs.stage ]]
script:
- go fmt $(go list ./... | grep -v /vendor/)
- go vet $(go list ./... | grep -v /vendor/)
```
- `test` job 템플릿은 같은 패턴을 따르지만 `stage`와 `golang_version` inputs만 필요해요.
```yaml
spec:
inputs:
stage:
default: 'test'
description: 'Defines the format stage'
golang_version:
default: 'latest'
description: 'Golang image version tag'
---
"test-$[[ inputs.golang_version ]]":
image: golang:$[[ inputs.golang_version ]]
stage: $[[ inputs.stage ]]
script:
- go test -race $(go list ./... | grep -v /vendor/)
```
5. 컴포넌트를 테스트하려면 `.gitlab-ci.yml` 구성 파일을 수정하고 [테스트](/ci/components/#test-the-component)를 추가합니다.
- `build` job의 input으로 `golang_version`의 다른 값을 지정합니다.
- URL을 여러분의 CI/CD 컴포넌트 경로에 맞게 수정하세요.
```yaml
stages: [format, build, test]
include:
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/format@$CI_COMMIT_SHA
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/build@$CI_COMMIT_SHA
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/build@$CI_COMMIT_SHA
inputs:
golang_version: "1.21"
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/test@$CI_COMMIT_SHA
inputs:
golang_version: latest
```
6. CI/CD 컴포넌트를 테스트할 Go 소스를 추가합니다. `go` 명령은 루트 디렉터리에 `go.mod`와 `main.go`가 있는 Go 프로젝트를 기대해요.
- Go 모듈을 초기화합니다. URL을 여러분의 CI/CD 컴포넌트 경로에 맞게 수정하세요.
```bash
go mod init example.gitlab.com/components/golang
```
- `main.go` 파일에 `Hello, CI/CD component`를 출력하는 main 함수를 만듭니다. GitLab Duo Code Suggestions로 Go 코드를 생성하려면 코드 주석을 사용할 수 있어요.
```go
// Specify the package, import required packages
// Create a main function
// Inside the main function, print "Hello, CI/CD Component"
package main
import "fmt"
func main() {
fmt.Println("Hello, CI/CD Component")
}
```
- 디렉터리 트리는 이렇게 보여야 해요.
tree
.
├── LICENSE.md
├── README.md
├── go.mod
├── main.go
└── templates
├── build.yml
├── format.yml
└── test.yml
```
CI/CD 템플릿을 컴포넌트로 변환 섹션의 나머지 단계를 따라 마이그레이션을 완료합니다.
- 변경을 커밋·푸시하고 CI/CD 파이프라인 결과를 확인합니다.
- 컴포넌트 작성 가이드에 따라
README.md와LICENSE.md파일을 업데이트합니다. - 컴포넌트 릴리스하고 CI/CD 카탈로그에서 확인합니다.
- CI/CD 컴포넌트를 스테이징·프로덕션 환경에 추가합니다.
GitLab이 관리하는 Go 컴포넌트는 inputs와 컴포넌트 모범 사례로 개선된 Go CI/CD 템플릿의 성공적인 마이그레이션 예시를 제공해요. Git 이력을 살펴보며 더 배울 수 있습니다.
더 알아보기
컴포넌트는 입력을 extends의 이름에 넣어 조건부 구성을 만들고, 컴포넌트 컨텍스트로 버전 자원을 참조할 수 있다는 게 가장 강력한 포인트예요. 템플릿을 컴포넌트로 옮길 때는 전역 키워드를 job 정의 안으로 이동하고, 하드코딩된 값을 inputs로 바꾸는 작업부터 하면 깔끔하게 시작할 수 있어요. 자체 테스트 데이터를 저장소에 두고 job마다 템플릿을 나눠 만드는 구조가 유지보수에 좋습니다.