Argo project는 Git-Ops 및 CI/CD Pipeline 구성을 위한 Kubernetes-native 도구입니다.
Argo project는 현재 CNCF(Cloud Native Computing Foundation)에서 Graduate 등급의 Maturity를 가지고 있으며, Argo Workflows / Argo CD / Argo Rollout / Argo Events 등의 프로젝트들로 이루어져 있습니다.
각 프로젝트들의 목적과 활용성은 다음과 같습니다.
- Argo Workflows : 직렬 및 병렬로 구성된 Job을 처리할 수 있는 Workflow Engine입니다. Kubernetes 환경에서 Workflow를 실행하는데 특화되어 있습니다.
- Argo CD : Git-Ops 기반의 CD(Continuous Delivery)를 위한 도구입니다. 애플리케이션의 Lifecycle, Version control, Deployment를 관리할 수 있습니다.
- Argo Rollouts : Kubernetes Deployment를 기반으로 Canary, Blue/Green 등의 Advanced한 배포 전략을 구현할 수 있는 도구입니다.
- Argo Events : Kubernetes 환경에서 Event 기반의 logic을 구현할 수 있게 해주는 Event dependency management 도구입니다.
이번 글에서는 위에서 소개한 Argo Project들을 활용해 Container Image Build부터 Kubernetes 환경의 배포까지 실행 가능한 CI/CD Pipeline을 GKE 클러스터에서 구현해보겠습니다.
1. Architecture
Argo Project로 구성된 CI/CD Pipeline의 아키텍쳐는 위와 같습니다.
CI/CD Pipeline은 아래와 같은 순서로 동작합니다.
1. Bitbucket 등의 Source repository에서 코드를 수정한 뒤 Tag Push 작업을 수행하면 Argo Events의 Eventsource로 노출한 Endpoint로 Webhook을 발송합니다.
2. Argo Events의 EventSource는 Source repository에서 발송한 Webhook payload를 받아 EventBus로 전달합니다.
3. Argo Events의 Sensor는 Eventbus에서 발송한 Webhook Payload를 받아 Parsing 작업을 수행합니다.
4. Argo Events의 Sensor가 Webhook payload를 Parsing해 얻은 Parameter와 함께 Argo Workflow의 Workflow를 생성합니다.
5. Argo Workflow의 Workflow는 지정된 Workflow Template의 Step을 넘겨받은 Parameter 값과 함께 실행합니다. 가장 먼저 Slack에 Notification을 발송하는 Step을 실행합니다.
6. 이후 User의 Manual approval을 위해 Pause를 수행하는 Step을 실행합니다. Dashboard UI에서 User가 resume 동작을 수행함으로써 다시 workflow가 진행됩니다.
7. Approval이 수행되면 Kaniko를 이용해 Source repository의 코드를 기반으로 Container Image Build를 수행하는 Step을 실행합니다. 이 Step은 빌드가 완료된 이후 Artifact registry로 빌드된 Image를 Push합니다.
8. 이전 Step에서 빌드된 Image를 Helm Chart에서 사용하도록 Helm Version 및 Application Version을 upgrade합니다. 이후 수정된 Helm repository를 각각 Git repository와 Artifact registry에 push합니다.
9. Argo Workflow의 작업이 종료되고 Helm Chart를 바라보고 있던 ArgoCD가 Helm Chart 버전의 변경을 감지합니다.
10. User의 Manual Sync를 통해 Kubernetes Cluster에 Helm Chart 리소스 배포 작업을 실행합니다.
리소스는 Argo Rollout이 포함되어 있어 advanced한 배포 전략을 사용할 수 있도록 합니다.
이렇게 Argo CI/CD Pipeline은 Argo Workflow, Argo CD, Argo Events, Argo Rollouts 4개의 Argo Project 컴포넌트로 구성되어 있습니다.
2. DEMO
Argo CI/CD Pipeline의 동작을 알아봤으니 이제 실제로 Pipeline이 구동되는 모습을 보도록 하겠습니다.
1. Source Repository에서 변경사항을 Commit한 뒤, Tag Push를 통해 해당 Commit에 태그를 attach합니다.
여기서 붙은 Tag는 이후 Container Image의 Tag가 됩니다.
2. Tag Push가 정상적으로 실행되면 Argo events의 Sensor가 Webhook payload를 기반으로 Workflow를 실행합니다.
실행되는 Workflow의 첫번째 Step은 Slack Notification을 보내는 작업입니다.
지정된 Slack Channel로 지정된 문구와 Dashboard로 진입할 수 있는 URL이 포함된 Message가 수신되는 것을 확인할 수 있습니다.
3."notification" Step이 실행됨과 동시에 User의 Manual Approval을 기다리기 위해 Pause 상태로 대기중인 "approval" Step이 실행됩니다.
Approval step은 User가 직접 "RESUME" 버튼을 누르기 전까지 다음 step으로 넘어가지 않기 때문에 CI Pipeline이 동작하기 전 Double Check의 용도로 사용됩니다.
4. "RESUME" 버튼을 클릭해 Approval 의사를 밝히면 "build image" Step이 실행됩니다.
"build image" Step은 Kaniko를 사용해 Container Image를 빌드하고, Artifact registry에 이미지를 푸시하는 작업으로 이루어져 있습니다.
"build-image" step이 정상적으로 실행 완료되면 지정된 artifact registry에 Container image가 푸시되어 있는 것을 확인할 수 있습니다.
본 CI/CD Pipeline에서는 Container Image build 도구로 Kaniko를 사용했습니다.
Container Image build 도구는 컨테이너 빌드 도구 선택을 위한 특성 및 성능 비교 (Kaniko, Buildah, Buildkit)에서 도출한 결론을 기반으로 결정했습니다.
Kaniko가 Caching을 위해 사용하는 Remote cache 용도의 레지스트리 또한 생성된 것을 볼 수 있습니다.
5. 다음으로 실행되는 "bump-helm" Step에서는 빌드한 Container Image를 포함하는 Helm Chart가 새로운 버전의 Image를 참조하도록 버전 값을 변경하는 작업을 수행합니다.
"bump-helm" Step은 두 가지의 버전 값을 변경합니다.
첫번째는 Helm Chart 자체의 version 값으로, Helm chart의 수정이 일어날때마다 패치 버전을 올립니다. CI/CD Pipeline이 실행될때마다 Helm Chart가 참조하는 Container Image의 버전이 달라지기 때문에 Pipeline이 실행될때마다 패치 버전을 올립니다.
두번째는 Application version 값으로, Helm Chart가 참조하는 Container Image의 버전 값을 빌드된 Image의 버전과 일치하도록 변경합니다. Container Image의 Version은 최초 Git Tag Push시 Attach한 태그 버전과 동일하기 때문에, 최종적으로 Webhook Payload에 포함된 태그 버전을 Application version 값에 할당합니다.
6. 다음으로 실행되는 "push-git-helm" Step에서는 이전 Step에서 실행된 Helm Chart 버전 올림을 반영하기 위해 Chart가 존재하는 Git repository로 Push하는 작업을 실행합니다.
Git repository에서 Helm Chart의 version 및 appVersion 버전올림이 반영된 것을 확인할 수 있습니다.
7. Argo Workflow의 마지막 Step인 "push-artifact-helm" Step을 실행합니다.
이 Step에서는 helm chart를 tar 압축해서 Artifact 형태로 Artifact registry에 Push하는 작업을 실행합니다.
해당 Step이 진행되고 나면 아래와 같이 Artifact Registry에서 Push된 Helm Chart의 패키지를 확인할 수 있습니다.
여기까지 CI(Continuous Integration) 단계가 모두 진행되었습니다.
8. 다음으로 Argo CD에서 CD(Continuous Delivery)를 진행합니다. Argo CD는 Artifact Registry에 존재하는 Helm Chart를 참조하여 Kubernetes 환경에 리소스 배포를 시도합니다.
Pipeline의 실행으로 Helm Chart의 Version이 바뀌었기 때문에 현재 상태와 Helm Chart의 상태가 불일치하는 OutOfSync 상태가 되는 것을 확인할 수 있습니다.
9.최신 Helm Chart의 값을 기반으로 Kubernetes 환경에 배포를 실행하기 위해 SYNC 버튼을 클릭합니다. Sync가 완료되면 최신 버전의 Image를 참조하는 Pod 및 관련 리소스들이 배포되는 것을 볼 수 있습니다.
Pod는 Argo Rollout을 통해 배포되기 때문에 Blue/Green 및 Canary 배포를 수행할 수 있습니다.
10. 이전 버전으로 Rollback을 원한다면 대시보드의 HISTROY AND ROLLBACK 버튼을 클릭하는 것으로 원하는 버전의 롤백을 손쉽게 수행할 수 있습니다.
여기까지 Argo CI/CD Pipeline가 실행되는 DEMO를 함께 보았습니다.
위에서 구성한 Argo CI/CD Pipeline은 Double Check를 위해 필요한 Manual Approval을 제외하고는 CI부터 CD까지 전 과정이 자동으로 이루어지도록 구성되어 있습니다.
이제 Argo CI/CD Pipeline을 구성하기 위해 어떤 컴포넌트들이 필요한지 살펴보겠습니다.
3. Components
이번 장에서는 본 포스팅의 Argo CI/CD Pipeline을 구성하기 위해 어떤 Kubernetes 오브젝트들이 필요한지 알아보겠습니다.
필요한 Kubernetes 오브젝트는 크게 Argo Events의 Eventsource 및 Sensor, Argo Workflow의 Workflow 및 WorkflowTemplate, Argo CD의 Application이 필요합니다.
이 중 핵심적인 CRD인 Eventsource , Sensor, WorkflowTemplate에 대한 명세를 살펴보도록 하겠습니다.
3-1. Eventsource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
name: gitlab
spec:
service:
ports:
- port: 12000
targetPort: 12000
gitlab:
example:
projects:
- "49"
webhook:
endpoint: /
port: "12000"
method: POST,GET
url: http://gitlab-eventsource-ingress.test-for-lswoo.kro.kr
events:
- PushEvents
- TagPushEvents
accessToken:
key: token
name: gitlab-app-token
enableSSLVerification: false
gitlabBaseURL: http://34.64.42.49
deleteHookOnFinish: true
|
cs |
Source Repository에서 보내는 Webhook을 감지하고 Consume하는 역할을 맡은 Eventsource입니다.
Eventsource는 Webhook을 지원하는 Git repository를 구성할 시 해당 Repository에 Webhook 관련 설정을 자동으로 생성합니다.
생성되는 Webhook 설정은 webhook 어트리뷰트의 값을 기반으로 구성합니다.
Webhook 인증을 위해 accessToken 어트리뷰트의 값에 Token 등의 인증 수단이 존재하는 Secret을 참조하도록 합니다.
Eventsource를 생성하기 전에, webhook을 수신할 엔드포인트 역할을 할 수 있는 Ingress 오브젝트를 생성해둬야 합니다.
3-2. BackendConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
name: gitlab-backendconfig
namespace: argo-events
spec:
healthCheck:
checkIntervalSec: 30
timeoutSec: 5
healthyThreshold: 1
unhealthyThreshold: 2
type: HTTP
requestPath: /health
port: 12000
customRequestHeaders:
headers:
- "Host: gitlab-eventsource-ingress.test-for-lswoo.kro.kr"
|
cs |
만약 GKE 클러스터에서 Eventsource의 엔드포인트 역할을 할 Ingress를 생성했을시, GCP Loadbalancer의 Health check 이슈로 Eventsource가 동작하지 않을 수 있습니다.
이는 기본적으로 GCP LB는 "/" path로 health check를 실행하기 떄문인데, eventsource controller가 200 응답을 발신하는 path는 "/health"이기 때문에 health check가 실패하기 때문입니다.
Health check path를 수정하는 작업은 GCP에서 제공하는 CRD인 BackendConfig로 수행 가능합니다.
BackendConfig CRD를 통해 GCP LB의 backend에 전달될 health check의 명세를 정의할 수 있습니다. 여기서 health check path를 변경하는 것으로 Eventsource를 정상 동작하도록 할 수 있습니다.
3-3. Sensor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
name: gitlab
spec:
template:
serviceAccountName: event-sa
dependencies:
- name: test-dep
eventSourceName: gitlab
eventName: example
filters:
data:
- path: body.ref
type: string
value:
- "prod"
comparator: "="
template: '{{ splitList "." .Input | last}}'
triggers:
- template:
name: gitlab-workflow-trigger
k8s:
operation: create
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
namespace: argo
generateName: kaniko-build-
spec:
volumeClaimTemplates:
- metadata:
name: workspace
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 64Mi
arguments:
parameters:
- name: REPO
value: .git
# the value will get prepended by event payload from test-dep
- name: HELM_REPO
value: .git
- name: IMAGE_NAME
value: asia-northeast3-docker.pkg.dev/prj-cc-sandbox-devops-0010/ar-mkt-sandbox-lsw/
- name: TAG
value: latest
- name: pvc-name
value: argo-workflow-pvc
- name: CONTEXT
value: /workspace/
- name: DOCKERFILE
value: Dockerfile
- name: COMMENT
value: Hello world
- name: HELM_REVESION
value: main
- name: USER
value: user
# the value will get prepended by event payload from test-dep
- name: SERVICE_NAME
value: service
ttlStrategy:
secondsAfterCompletion: 604800 # Time to live after workflow is completed, replaces ttlSecondsAfterFinished
secondsAfterSuccess: 604800 # Time to live after workflow is successful
secondsAfterFailure: 604800 # Time to live after workflow fails
entrypoint: main
serviceAccountName: build
volumes:
- name: git-auth
secret:
secretName: git-auth-secret
#- name: docker-auth
# secret:
# secretName: docker-config
# # Add host name per repository at config.json in docker-config Secret
templates:
- name: main
dag:
tasks:
- name: notification
templateRef:
name: slack
template: slack
- name: approval
template: approval
- name: build-image
dependencies:
- approval
templateRef:
name: kaniko
template: build
- name: bump-helm
dependencies:
- build-image
templateRef:
name: helm
template: bump
- name: push-git-helm
dependencies:
- bump-helm
templateRef:
name: helm
template: push-git
- name: push-artifact-helm
dependencies:
- push-git-helm
templateRef:
name: helm
template: push-artifact
- name: approval
suspend: {}
parameters:
- src:
dependencyName: test-dep
dataKey: body.project.web_url
dest: spec.arguments.parameters.0.value
operation: prepend
# Application Resistry URL
- src:
dependencyName: test-dep
dataTemplate: '{{ cat "http://34.64.42.49/argo-pipeline/helm-chart/application/" .Input.body.project.name | nospace }}'
dest: spec.arguments.parameters.1.value
operation: prepend
# Helm Repo URL
- src:
dependencyName: test-dep
dataKey: body.project.name
dest: spec.arguments.parameters.2.value
operation: append
# Image Name
- src:
dependencyName: test-dep
dataTemplate: '{{ splitList "/" .Input.body.ref | last }}'
# Use sprig template to manipulate parameter
dest: spec.arguments.parameters.3.value
# Image Version Tag
- src:
dependencyName: test-dep
dataKey: body.commits.0.message
dest: spec.arguments.parameters.7.value
# Commit Message
- src:
dependencyName: test-dep
dataKey: body.user_name
dest: spec.arguments.parameters.9.value
# Application USER NAME
- src:
dependencyName: test-dep
dataKey: body.project.namespace
dest: spec.arguments.parameters.10.value
# Namespace
|
cs |
Sensor는 Eventsource에서 넘어온 Webhook Payload를 기반으로 Argo Workflow의 Workflow를 실행하는 역할을 맡습니다.
Webhook Payload에서 필요한 정보를 추출한 뒤 원하는 Parameter에 할당하는 작업도 수행합니다.
여기서 할당된 Parameter 값을 기반으로 Workflow를 실행하게 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
|
dependencies:
- name: test-dep
eventSourceName: gitlab
eventName: example
filters:
data:
- path: body.ref
type: string
value:
- "prod"
comparator: "="
template: '{{ splitList "." .Input | last}}'
|
cs |
위 Block은 Sensor 리소스의 dependencies 어트리뷰트를 정의한 부분입니다.
dependencies 어트리뷰트를 통해 Workflow를 실행하기 위해 필요한 정보를 정의할 수 있습니다.
여기서 Workflow가 실행되기 위해 필요한 Eventsource를 지정할 수 있으며, filter 기능을 통해 특정 Payload 값과 일치했을 시에만 Workflow가 실행되게끔 구성할 수도 있습니다.
위 코드에서는 "gitlab" Eventsource가 이벤트를 감지했을 시 Workflow를 실행하게끔 설정했으며, Webhook payload의 tag 값 뒤에 ".prod"가 포함되어있을 시에만 Workflow가 실행되게끔 설정한 것을 볼 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
|
triggers:
- template:
name: gitlab-workflow-trigger
k8s:
operation: create
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
namespace: argo
generateName: kaniko-build-
|
cs |
위 Block은 Sensor 리소스의 trigger 어트리뷰트를 정의한 부분입니다.
triggers 어트리뷰트를 통해 이벤트를 감지했을시 실행할 작업을 정의할 수 있습니다.
Argo CI/CD Pipeline을 통해 Webhook을 감지했을 시 Argo Workflow의 Workflow를 실행하도록 구성합니다.
k8s.operation: create 와 k8s.source.resource.kind: Workflow 값을 통해 Workflow 오브젝트를 생성하도록 구성할 수 있습니다.
이 아래로 소개하는 어트리뷰트들은 Argo Workflow의 Workflow를 정의하는 역할을 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
arguments:
parameters:
- name: REPO
value: .git
# the value will get prepended by event payload from test-dep
- name: HELM_REPO
value: .git
- name: IMAGE_NAME
value: asia-northeast3-docker.pkg.dev/prj-cc-sandbox-devops-0010/ar-mkt-sandbox-lsw/
- name: TAG
value: latest
- name: pvc-name
value: argo-workflow-pvc
- name: CONTEXT
value: /workspace/
- name: DOCKERFILE
value: Dockerfile
- name: COMMENT
value: Hello world
- name: HELM_REVESION
value: main
- name: USER
value: user
# the value will get prepended by event payload from test-dep
- name: SERVICE_NAME
value: service
|
cs |
위 Block은 Sensor 명세 내에서 정의된 Workflow의 arguments 어트리뷰트를 정의한 부분입니다.
argument 어트리뷰트를 통해 Workflow가 실행할때 참조할 Parameter들을 정의할 수 있습니다.
Parameter의 Value는 직접 정의해서 고정된 값을 할당할 수 있지만, 아래에 나올 parameters 어트리뷰트를 통해 Webhook Payload 기반의 동적인 값을 할당할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
templates:
- name: main
dag:
tasks:
- name: notification
templateRef:
name: slack
template: slack
- name: approval
template: approval
- name: build-image
dependencies:
- approval
templateRef:
name: kaniko
template: build
- name: bump-helm
dependencies:
- build-image
templateRef:
name: helm
template: bump
- name: push-git-helm
dependencies:
- bump-helm
templateRef:
name: helm
template: push-git
- name: push-artifact-helm
dependencies:
- push-git-helm
templateRef:
name: helm
template: push-artifact
- name: approval
suspend: {}
|
cs |
위 Block은 Sensor 명세 내에서 정의된 Workflow의 templates 어트리뷰트를 정의한 부분입니다.
templates 어트리뷰트를 통해 Workflow가 실행하는 Job들을 순서에 맞게 구성할 수 있습니다.
Workflow가 실행할 Job을 구성하는 방법은 Step과 Dag 두 가지가 존재합니다.
두 구성 방법에서 Step은 Job의 실행 순서를 정의하지만,Dag는 Job 간의 Dependency를 정의한다는 차이점이 존재합니다.
본 포스팅의 Pipeline에서는 Dag를 정의해 각 Job간 의존성을 통해 순서가 지정된 것을 확인할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
parameters:
- src:
dependencyName: test-dep
dataKey: body.project.web_url
dest: spec.arguments.parameters.0.value
operation: prepend
# Application Resistry URL
- src:
dependencyName: test-dep
dataTemplate: '{{ cat "http://34.64.42.49/argo-pipeline/helm-chart/application/" .Input.body.project.name | nospace }}'
dest: spec.arguments.parameters.1.value
operation: prepend
# Helm Repo URL
- src:
dependencyName: test-dep
dataKey: body.project.name
dest: spec.arguments.parameters.2.value
operation: append
# Image Name
- src:
dependencyName: test-dep
dataTemplate: '{{ splitList "/" .Input.body.ref | last }}'
# Use sprig template to manipulate parameter
dest: spec.arguments.parameters.3.value
# Image Version Tag
- src:
dependencyName: test-dep
dataKey: body.commits.0.message
dest: spec.arguments.parameters.7.value
# Commit Message
- src:
dependencyName: test-dep
dataKey: body.user_name
dest: spec.arguments.parameters.9.value
# Application USER NAME
- src:
dependencyName: test-dep
dataKey: body.project.namespace
dest: spec.arguments.parameters.10.value
# Namespace
|
cs |
위 Block은 Sensor 리소스의 parameters 어트리뷰트를 정의한 부분입니다.
parameters 어트리뷰트를 통해 Workflow Parameter에 할당될 값을 동적으로 부여할 수 있습니다.
이는 "src.dataKey" 어트리뷰트의 값으로 Webhook Payload에서 어떤 정보를 가져올지, "src.dest" 어트리뷰트의 값으로 가져온 정보를 어떤 Parameter에 할당할지 정의하는 것으로 구현 가능합니다.
예를 들어 첫번째 "src" 요소는 "dataKey" 어트리뷰트의 "body.project.web_url" 값을 "spec.arguments.parameters.0.value" 위치에 존재하는 "REPO" parameter에 할당합니다.
Webhook Payload의 정보를 가공한 후 paramter에 할당하고 싶다면 dataKey 대신 dataTemplate 어트리뷰트를 사용할 수 있습니다.
dataTemplate 어트리뷰트는 Sprig 기반의 함수를 통해 가져온 정보를 가공할 수 있게끔 해줍니다.
예를 들어 dataTemplate의 '{{ splitList "/" .Input.body.ref | last }}' 값은 Webhook Payload의 body.ref 값을 "/" 문자열 기반으로 나눈 뒤, 마지막 요소를 가져옵니다.
3-4. WorkflowTemplate
Workflowtemplate은 Workflow가 실행할 Job들의 명세를 정의하는 리소스입니다.
본 글의 CICD Pipeline은 총 4개의 Workflowtemplate이 사용됩니다.
- Kaniko WorkflowTemplate : 소스 코드를 기반으로 Container Image 빌드 및 푸시를 담당합니다.
- Helm WorkflowTemplate : 빌드된 Container Image를 기반으로 Helm Chart 명세를 수정하고 git registry 및 Artifact registry에 결과물을 푸시합니다.
- Slack WorkflowTemplate : Slack Notification을 실행해 Slack Channel에 Approval을 위한 Notification을 발송합니다.
- Approval WorkflowTemaplte : User의 Manual Approval이 올때까지 Workflow의 진행을 중지합니다.
3-4-1. kaniko workflowTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: kaniko
spec:
templates:
- name: build
serviceAccountName: build
metadata:
inputs:
artifacts:
- name: repo
path: '{{ workflow.parameters.CONTEXT }}'
git:
repo: '{{ workflow.parameters.REPO }}'
revision: '{{ workflow.parameters.TAG }}'
container:
image: gcr.io/kaniko-project/executor:v1.9.1
workingDir: '{{ inputs.artifacts.repo.path }}'
command: ["/kaniko/executor"]
args:
- --cache
- --cache-repo={{ workflow.parameters.IMAGE_NAME }}-cache
- --reproducible
- --dockerfile={{ workflow.parameters.DOCKERFILE }}
- --destination={{ workflow.parameters.IMAGE_NAME }}:{{ workflow.parameters.TAG }}
|
cs |
위 코드는 위의 3개 Workflowtemplate 중 "kaniko" workflowTemplate입니다.
"kaniko" workflowTemplate은 Workflow에서 전달받은 Parameter 값과 git source를 기반으로 kaniko 빌드 명령어를 실행하는 1개의 Job으로 이루어져 있습니다.
git source는 Argo Workflow의 "artifact" 형태로 전달됩니다. artifact를 이용해 Workflow의 Step간 공유할 수 있는 아티팩트를 정의할 수 있습니다.
Artifact registry에 접근할 수 있는 권한은 GKE의 Workload Identity 기능을 사용해 얻도록 구성했습니다.
이를 위해 적절한 권한이 부여되어 있는 GCP Service account와 Binding되어 있는 Kubernetes Service account를 사용하도록 지정합니다.
3-4-2. helm workflowTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: helm
spec:
entrypoint: got
templates:
- name: bump
inputs:
artifacts:
- name: repo
path: '{{ workflow.parameters.CONTEXT }}/helm'
git:
repo: '{{ workflow.parameters.HELM_REPO }}'
revision: '{{ workflow.parameters.HELM_REVESION }}'
script:
volumeMounts:
- name: workspace
mountPath: '{{ workflow.parameters.CONTEXT }}'
workingDir: '{{ workflow.parameters.CONTEXT }}/helm'
image: mikefarah/yq:4
securityContext:
runAsUser: 0
command: [sh]
source: |
set -eu
# Change Application version
echo "Change Application tag version to {{ workflow.parameters.TAG }}"
yq '.base.rollouts.image.tag="{{ workflow.parameters.TAG }}"' values-test.yaml --inplace
yq '.appVersion="{{ workflow.parameters.TAG }}"' Chart.yaml --inplace
yq '.version="{{ workflow.parameters.TAG }}"' Chart.yaml
# Bump Helm chart version
result=$(yq '.version' Chart.yaml)
major=$(echo "$result" | cut -d '.' -f 1)
minor=$(echo "$result" | cut -d '.' -f 2)
patch=$(echo "$result" | cut -d '.' -f 3)
echo "Current Helm chart version is $major.$minor.$patch"
patch=$((patch + 1))
version="$major.$minor.$patch"
echo "Bump Helm chart version to $version"
yq '.version= "'"$version"'"' Chart.yaml
yq '.version= "'"$version"'"' Chart.yaml --inplace
- name: push-git
script:
securityContext:
runAsUser: 0
workingDir: '{{ workflow.parameters.CONTEXT }}/helm'
volumeMounts:
- name: workspace
mountPath: '{{ workflow.parameters.CONTEXT }}'
- name: git-auth
mountPath: /tmp
image: cgr.dev/chainguard/git:root-2.39
command: [sh] # Use "sh" instead "sh -c" to avoid permission error
source: |
set -eu
cp /tmp/.git-credentials /root/.git-credentials
cp /tmp/.gitconfig /root/.gitconfig
# As Secret volume mounted as read-only filesystem, Copy files from secret volume to modify files
chmod 400 /root/.git-credentials
chmod 400 /root/.gitconfig
cat /root/.git-credentials
cat /root/.gitconfig
git add -- . ':!version'
git commit -m "change image tag to {{ workflow.parameters.TAG }} ({{ workflow.parameters.COMMENT }})"
git push origin HEAD:main
RESULT_SHA="$(git rev-parse HEAD | tr -d '\n')"
printf "%s" "$RESULT_SHA"
- name: push-artifact
serviceAccount: build
script:
volumeMounts:
- name: workspace
mountPath: '{{ workflow.parameters.CONTEXT }}'
workingDir: '{{ workflow.parameters.CONTEXT }}/helm'
image: alpine/helm:3.11.2
command: [sh]
source: |
VERSION=2.1.8
OS=linux
ARCH=amd64 # or "arm64" for ARM64 OSs
URL=https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases/download/v${VERSION}/docker-credential-gcr_${OS}_${ARCH}-${VERSION}.tar.gz
echo "Download Helper from $URL...."
curl -fsSL "https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases/download/v${VERSION}/docker-credential-gcr_${OS}_${ARCH}-${VERSION}.tar.gz" \
| tar xz docker-credential-gcr \
&& chmod +x docker-credential-gcr && mv docker-credential-gcr /usr/bin/
docker-credential-gcr configure-docker --registries=asia-northeast3-docker.pkg.dev
echo "https://asia-northeast3-docker.pkg.dev" | docker-credential-gcr get
helm dependency update
helm package ../helm
chart=$(ls|grep .tgz)
echo $chart
helm push ${chart} oci://asia-northeast3-docker.pkg.dev/prj-cc-sandbox-devops-0010/ar-mkt-prod-application-helm
rm $chart
# Delete packaged chart to ensure idempotency
|
cs |
위 코드는 Helm Chart의 수정 및 푸시를 담당하는 "helm" workflowTemplate입니다.
"helm" workflowTemplate은 3개의 Job으로 이루어져 있습니다.
- bump: yaml processor인 yq를 이용해 helm chart의 version 및 appversion을 수정하는 작업을 실행합니다.
- push-git : 수정된 helm chart 소스를 git repoisitory에 푸시합니다.
- push-artifact : 수정된 helm chart 소스로 생성한 아티팩트를 artifact registry에 푸시합니다.
위 3개 Job을 순차적으로 실행하는 것으로 Argo CD가 배포하는데 사용할 Helm chart 아티팩트와 git source code를 갱신 및 저장할 수 있습니다.
각 job들이 동일한 아티팩트를 기반으로 작업을 수행할 수 있도록 helm chart의 source code는 artifact 형태로 가져와 사용했습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
image: alpine/helm:3.11.2
command: [sh]
source: |
VERSION=2.1.8
OS=linux
ARCH=amd64 # or "arm64" for ARM64 OSs
URL=https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases/download/v${VERSION}/docker-credential-gcr_${OS}_${ARCH}-${VERSION}.tar.gz
echo "Download Helper from $URL...."
curl -fsSL "https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases/download/v${VERSION}/docker-credential-gcr_${OS}_${ARCH}-${VERSION}.tar.gz" \
| tar xz docker-credential-gcr \
&& chmod +x docker-credential-gcr && mv docker-credential-gcr /usr/bin/
docker-credential-gcr configure-docker --registries=asia-northeast3-docker.pkg.dev
echo "https://asia-northeast3-docker.pkg.dev" | docker-credential-gcr get
helm dependency update
helm package ../helm
chart=$(ls|grep .tgz)
echo $chart
helm push ${chart} oci://asia-northeast3-docker.pkg.dev/prj-cc-sandbox-devops-0010/ar-mkt-prod-application-helm
rm $chart
# Delete packaged chart to ensure idempotency
|
cs |
"helm" workflowTemplate을 이루고있는 job들 중 "push-artifact" job의 명세입니다.
GCP의 아티팩트 저장소인 Artifact registry에 차트를 푸시하기 위해서는 적절한 인증이 필요합니다.
이 인증 절차를 수행하기 위해서는 GCP Service account key를 발급받아 key의 token을 제출하는 방법과, Workload identity를 사용해 metadata server에서 받아온 token을 제출하는 방법 2가지를 사용할 수 있습니다.
하지만 첫번째 방법인 Service account key를 발급받는 방법은 Key 파일의 유출과 관리에 대한 리스크가 존재하므로 2번쨰 방법인 Workload identity를 사용하는 방법이 Best practice입니다.
Workload Identity를 이용해 인증을 수행하기 위해서는 GKE 클러스터에서 Cloud SDK가 설치된 환경이 필요합니다. Cloud SDK가 ADC(Application Default Credential)을 수행해 인증 절차를 수행하기 떄문입니다.
또는 Cloud SDK가 설치되지 않은 환경이라면 Google에서 제공하는 "docker-credential-gcr" 헬퍼를 사용할 수 있습니다.
"push-artifact" job은 Cloud SDK가 설치되지 않은 alpine/helm 환경에서 실행되기 때문에 docker-credential-gcr 헬퍼를 사용해서 Workload identity를 활용해 인증을 수행하도록 되어 있습니다.
3-4-3. slack workflowTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: slack
spec:
templates:
- name: slack
inputs:
parameters:
- name: USER
default: "user"
- name: SERVICE_NAME
default: "service"
- name: COMMIT
default: "commit message"
container:
image: curlimages/curl
command: [sh, -c]
args: [
"curl -X POST --data-urlencode 'payload={
\"text\": \"[Argo Workflow] {{workflow.parameters.USER}} 님의 {{workflow.parameters.SERVICE_NAME}} 승인 요청\",
\"blocks\": [
{
\"type\": \"header\",
\"text\": {
\"type\": \"plain_text\",
\"text\": \"Approval Request\",
\"emoji\": true
}
},
{
\"type\": \"section\",
\"text\": {
\"type\": \"mrkdwn\",
\"text\": \" {{workflow.parameters.USER}} 님이 승인 요청을 보냈습니다.\n*Service Name:*
{{workflow.parameters.SERVICE_NAME}}\n*UserName:* {{workflow.parameters.USER}}\n*Commit Message:* {{workflow.parameters.COMMENT}}\"
}
},
{
\"type\": \"section\",
\"text\": {
\"type\": \"mrkdwn\",
\"text\": \"<http://argo.test-for-lswoo.kro.kr/workflows/{{workflow.namespace}}/{{workflow.name}}?tab=workflow|Check DashBoard>\"
}
}
]
}'
'https://hooks.slack.com/services/TOKEN'"
]
|
cs |
위 코드는 Notification을 담당하는 "slack" workflowTemplate입니다.
Workflow에서 가장 첫번째로 실행되는 WorkflowTemplate입니다.
이 WorkflowTemplate에서 실행되는 Job은 주로 Slack의 Webhook API 엔드포인트로 POST 요청을 보내는 것으로 이루어져 있습니다.
요청에 담는 Payload를 통해 Slack Message에 담을 문자열 및 형태를 지정할 수 있습니다.
3-4-4. approval workflowTemplate
1
2
3
4
5
6
7
8
|
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: approval
spec:
templates:
- name: approval
suspend: {}
|
cs |
User의 Manual Approval을 담당하는 "approval" workflowTemplate입니다.
suspend 어트리뷰트를 추가함으로써 승인 절차가 수행되기 전까지 Workflow의 진행을 중지할 수 있습니다.
Workflow의 진행이 중지되면 "RESUME" 버튼을 클릭하거나, 특정한 값을 수동으로 입력하는 것으로 Workflow 진행을 다시 재개할 수 있습니다.
4. 마무리
지금까지 Argo Project로 구성한 CI/CD Pipeline을 함께 살펴봤습니다.
Argo Project는 Events, Workflow, CD, Rollout 등 Kubernetes 환경에서 파이프라인을 구성하는데 최적화되어 있는 도구들로 이루어져 있습니다.
게다가 CNCF에서 Graduate 성숙도를 가진 프로젝트이기 때문에 도구의 완성도가 높고 레퍼런스가 많이 쌓여있다는 장점이 존재합니다.
이러한 점 덕분에 Argo Project를 사용해 쉽고 빠르게 완성도있는 파이프라인을 구축할 수 있었습니다.
다음에는 Argo CI/CD Pipeline을 통해 SSO를 구현하는 방법에 대해서 알아보겠습니다.
'Devops' 카테고리의 다른 글
Clean Code를 구현하기 위해 Sonarqube로 정적 코드 분석을 해보자 (2) | 2023.10.28 |
---|---|
Argo 사용해보기 (2) Standalone DEX로 Argo Workflow에서 SSO 구현하기 (0) | 2023.05.09 |
컨테이너 빌드 도구 선택을 위한 특성 및 성능 비교 (Kaniko, Buildah, Buildkit) (5) | 2023.03.26 |
Tekton 사용해보기 (5) Tekton에 Human Approval 기능을 추가해보자 (0) | 2023.03.18 |
Terraform으로 Replace없는 GCP Compute Instance 부트 디스크를 구성하는 방법 (0) | 2023.01.29 |