개요
`2023 오픈소스 컨트리뷰션`의 `Argo Workflows`에 합류하게 되었습니다. 여러가지 주제 중 `Argo Workflows`를 선택한 이유는 클라우드 엔지니어 또는 devops 엔지니어로 전향하는 것을 목표로 하고있는데, `CNCF`가 관리하는 프로젝트이기도 하고 희망하고자 하는 직무와 너무 잘 맞는다고 생각했기 때문입니다.
하지만 아직 `Argo Workflows`를 사용해 본적이 없기 때문에 클라우드 메이트 기술블로그 Argo를 사용하자 글을 참고하여 `CI/CD Pileline`을 구현해 보며 `Argo`와 친해져 보려 합니다!
Argo를 사용해보자
서문 Argo란 무엇인가? Argo는 2023년 2월 기준 Argo CD, Argo Workflows, Argo Rollouts, Argo Events 이렇게 4가지 제품이 있습니다. Argo CD는 Kubernetes를 위한 GitOps Continuos Delivery 도구
tech.cloudmt.co.kr
아키텍처
`GitHub Source Code Repository`: 배포할 Application 코드 및 컨테이너 이미지를 빌드하기 위한 Dockerfile이 존재합니다.
`Argo Events`: Source Repository의 Push 이벤트가 발생하면 Webhook을 통해 Argo Events를 호출합니다. 호출받은 Argo Events는 정의된 내용에 맞춰 Workflow를 Trigger 합니다.
`Argo Workflows` : Argo Events로부터 Trigger된 Wrokflows는 Source Repository로 부터 Code를 Clone하는 Pull 단계, 그리고 그 코드를 기반으로 새로운 컨테이너 이미지를 빌드하는 Build 단계, 마지막으로 Argo CD가 바라보고 있는 YAML의 컨테이너 이미지 버전을 업데이트 하는 Push 단계로 작동합니다.
`Argo CD` : GitOps Repository에 정의된 YAML 파일의 정보를 확인하며 Kubernetes 클러스터에 Applicaion을 배포합니다.
준비물
`Argo project`를 사용하기 위해 Kubernetes가 필요합니다. 저는 `GCP`(Google Cloud Platform)의 GKE 프리티어를 사용하여 쿠버네티스 환경을 구성했습니다.
설치하기
`kubectl`을 `GKE`와 연결한 상태에서 터미널에 아래 명령어를 붙여넣으면 각 컴포넌트가 설치 됩니다! 설치되는 컴포넌트에 대한 설명은 아래 표를 확인하시면 됩니다.
구분 | 설명 |
`argo-cd` | Kubernetes 애플리케이션 배포 및 관리 도구 |
`argo-workflows` | Kubernetes에서 워크플로우를 정의하고 실행하는 기능 |
`argo-event` | 다양한 소스(Web Hook, S3 등)로 부터 이벤트를 감지하고 트리거 하는 기능 |
설치 쉘 스크립트
설치 쉘 스크립트가 수행되면 각 컴토넌트 설치 및 로그인 할 수 있는 `ID`, `PWD`가 표시 됩니다.
제 환경은 GKE이기 때문에 Service Type을 LoadBalancer로 변경하면 외부 접근이 가능하기 때문에 `kubectl patch service -n argo argo-server -p '{"spec":{"type":"LoadBalancer"}}'`설정을 통해 LoadBalancer로 배포할 수 있습니다.
만약 Minikube, k3d등 Local 환경을 사용중이신 경우 Metallb 설정 혹은 주석 처리된 `kubectl -n argocd port-forward service/argocd-server 8080:443 &` 명령을 통해 서비스를 포트포워딩 해주시기 바랍니다.!
GREEN='\033[0;32m'
RESET='\033[0;37m'
echo -e "${GREEN}
┌───────────────────────┐
│ Install start argo-cd │
└───────────────────────┘
${RESET}"
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml
kubectl rollout status deployment -n argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/rollout-extension/v0.2.1/manifests/install.yaml
echo -e "${GREEN}
┌──────────────────────────────┐
│ Install start argo-workflows │
└──────────────────────────────┘
${RESET}"
kubectl create namespace argo
kubectl apply -n argo -f https://github.com/argoproj/argo-workflows/releases/download/v3.4.9/install.yaml
kubectl rollout status deployment -n argo
kubectl patch deployment \
argo-server \
--namespace argo \
--type='json' \
-p='[{"op": "replace", "path": "/spec/template/spec/containers/0/args", "value": [
"server",
"--auth-mode=server"
]}]'
echo -e "${GREEN}
┌───────────────────────────┐
│ Install start argo-events │
└───────────────────────────┘
${RESET}"
kubectl create namespace argo-events
kubectl apply -n argo-events -f https://github.com/argoproj/argo-events/releases/download/v1.8.0/install.yaml
kubectl apply -n argo-events -f https://github.com/argoproj/argo-events/releases/download/v1.8.0/install-validating-webhook.yaml
PASSWORD=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
echo -e "
USERNAME: admin
PASSWORD: ${PASSWORD}
Argo CD: https://localhost:8080
Argo Workflows: https://localhost:2746
"
kubectl patch service -n argo argo-server -p '{"spec":{"type":"LoadBalancer"}}'
kubectl patch service -n argocd argocd-server -p '{"spec":{"type":"LoadBalancer"}}'
#kubectl -n argocd port-forward service/argocd-server 8080:443 &
#kubectl -n argo port-forward deployment/argo-server 2746:2746 &
스크립트 실행 결과
스크립트가 정상적으로 실행되면 아래 코드가 콘솔에 출력됩니다. USERNAME과 PASSWORD는 `Argo CD` 접속 시 사용합니다. `Argo Workflows`는 별도의 계정 정보 없이 접속이 가능합니다.
USERNAME: admin
PASSWORD: zRtb3UQlKvdxV3Ry
접속 해보기
`Argo CD` : https://<loadbalancer-ip>:8080
![](https://blog.kakaocdn.net/dn/kD3xs/btso6qf0zk0/K2oR9FN4ymmqR9W85RYWC0/img.png)
`Argo Workflows` : https://<loadbalancer-ip>:2746
![](https://blog.kakaocdn.net/dn/3xM3J/btso22mE7lT/jE87QLHpRgO5KS5zXyxrk1/img.png)
코드 리포지토리
코드 리포지토리를 생성합니다. 저의 경우 github에 리포지토리를 생성했습니다. `main.go`의 코드는 `Hello, World!`를 출력하는 Go 언어로 된 웹 서비스 이고, 이를 컨테이너 이미지로 빌드하기 위해 `Dockerfile`도 포함되어 있습니다.
이 리포지토리는 `Argo Workflows`에서 CI를 위해 사용됩니다.
tree .
.
├── Dockerfile
└── main.go
main.go
package main
import (
"fmt"
"log"
"net/http"
)
func index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", index)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Dockerfile
FROM golang:1.20.1-alpine3.17 AS builder
WORKDIR /work
COPY . /work/
RUN go build -o server main.go
FROM alpine:3.14
COPY --from=builder /work/server /work/server
ENTRYPOINT ["/work/server"]
Image Build & Deploy(선택 사항)
# docker image build 하기
docker build -t godummyweb:1.0 .
# localhost 8080 port를 기반으로 배포하기
docker run -dp 8080:8080 --name goweb godummyweb:1.0
접속해보기
`Dockerfile`을 통해 `build`한 이미지로 배포 후 테스트 해봅니다.
GitOps 리포지토리
`GitOps`는 애플리케이션의 배포와 운영에 관련된 모든 요소를 코드화하여 깃(git)에서 관리합니다. 그리고 `Argo CD`는 코드화 된 배포 정보를 깃으로 부터 불러와 `k8s cluster`에 배포합니다. 따라서 이 리포지토리는 코드리포지토리의 소스를 기반으로 `Argo CD`에서 배포를 위해 사용됩니다.
`GitopsDummy`라는 이름의 github 리포지토리를 생성합니다. 그 후 `Argo CD`를 통한 배포를 위해 아래 `yaml`파일을 생성합니다. 해당 파일에는 코드리포지토리의 어플리케이션을 배포하는 `Deployment`와 `LoadBalancer` Type의 `Service`를 배포합니다.
deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-deployment
labels:
app: go
spec:
replicas: 2
selector:
matchLabels:
app: go
template:
metadata:
labels:
app: go
spec:
containers:
- name: go
image: kimhj4270/godummyweb:latest
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: go-service
spec:
selector:
app: go
type: LoadBalancer
ports:
- protocol: TCP
port: 8080
targetPort: 8080
nodePort: 30080
Argo CD Application 생성하기
GitOps 리포지토리를 대상으로 `Argo CD`를 통해 `minikube` 클러스터에 배포하도록 설정합니다.
우선 `localhost:8080`주소로 `Argo CD`에 접속합니다.
설치 스크립트실행에 표시 됐던 계정정보로 로그인 합니다.
우선 GitOps 리포지토리 정보를 등록합니다.
`Setting` → `Repositories` → `Connect Repo`
`Repository URL`은 GitOps 리포지토리의 주소이고, `Username`과 `Password`는 Github 계정정보이며 비밀번호는 토큰값을 입력합니다.
`Application`을 생성합니다.
`Applications` → `New App`
결과적으로 GitOps의 deploy.yaml을 참고하여 1개의 Deployment, 1개의 Service 객체가 생성됩니다.
Argo Events, Argo Workflows 정의를 통해 CI/CD Pipeline 구성하기
아래 YAML 파일을 기반으로 CI/CD Pipeline을 구성할 수 있습니다. 하지만 생성 전 변경해야할 부분이 있습니다.
(`더보기`에 코드가 있습니다.)
apiVersion: v1
kind: Namespace
metadata:
labels:
app.kubernetes.io/instance: ci
app.kubernetes.io/name: ci
name: ci
---
apiVersion: v1
data:
.dockerconfigjson: <your-docker-config-base64-encrypt>
kind: Secret
metadata:
name: dockerhub-registry
namespace: ci
type: kubernetes.io/dockerconfigjson
---
apiVersion: v1
data:
gcs-iam.json: <your-GCP-IAM-json-base64-encrypt>
kind: Secret
metadata:
name: argo-gcp
namespace: ci
type: Opaque
---
apiVersion: v1
kind: Secret
metadata:
name: ssh-key-secret
namespace: ci
type: Opaque
data:
ssh-private-key: <your-GIT-SSH-KEY-base64-encrypt>
---
apiVersion: v1
data:
argocd-server: YXJnb2NkLXNlcnZlci5hcmdvY2Quc3ZjLmNsdXN0ZXIubG9jYWw=
password: <your-argocd-admin-password-base64-encrypt>
username: YWRtaW4=
kind: Secret
metadata:
name: argocd-config
namespace: ci
---
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: ci
name: operate-workflow-sa
---
# Similarly you can use a ClusterRole and ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: operate-workflow-role
namespace: ci
rules:
- apiGroups:
- argoproj.io
verbs:
- "*"
resources:
- "*"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: operate-workflow-role-binding
namespace: ci
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: operate-workflow-role
subjects:
- kind: ServiceAccount
name: operate-workflow-sa
---
apiVersion: argoproj.io/v1alpha1
kind: EventBus
metadata:
name: default
namespace: ci
spec:
nats:
native:
replicas: 3
auth: none
---
apiVersion: v1
kind: Service
metadata:
name: ci
namespace: ci
spec:
ports:
- port: 12000
targetPort: 12000
type: LoadBalancer
selector:
eventsource-name: webhook
---
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
name: webhook
namespace: ci
labels:
eventsource-name: webhook
spec:
service:
ports:
- port: 12000
targetPort: 12000
webhook:
example:
port: "12000"
endpoint: /example
method: POST
---
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
name: cicd
namespace: ci
spec:
dependencies:
# 앞에서 정의한 웹훅 이벤트 소스가 발생하면 동작
- name: test-dep
eventSourceName: webhook
eventName: example
template:
serviceAccountName: operate-workflow-sa
triggers:
- template:
k8s:
operation: create
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: input-artifact-git-
namespace: ci
spec:
serviceAccountName: operate-workflow-sa
entrypoint: pipe
templates:
- name: pipe
steps:
- - name: checkout
template: checkout
- - name: delay
template: delay
- - name: image-build
template: image-build
arguments:
parameters:
- name: version
value: "{{steps.checkout.outputs.parameters.gitVersion}}"
artifacts:
- name: source
from: "{{steps.checkout.outputs.artifacts.source}}"
- - name: delay2
template: delay2
- - name: gitops
template: gitops
arguments:
parameters:
- name: version
value: "{{steps.checkout.outputs.parameters.gitVersion}}"
- - name: delay3
template: delay3
- - name: deployment
template: deployment
- name: checkout
inputs:
artifacts:
- name: source
path: /src
git:
repo: https://<your-source-code-git-url>.git
revision: "main"
outputs:
parameters:
- name: gitVersion
valueFrom:
default: "latest"
path: /version.txt
artifacts:
- name: source
path: /src
gcs:
bucket: <your-GCP-Bucket-name>
key: ci/
serviceAccountKeySecret:
name: argo-gcp
key: gcs-iam.json
container:
image: bitnami/git:latest
command: [sh, -c]
args: ["git rev-list --tags --max-count=1 | xargs git describe --tags > /version.txt"]
workingDir: /src
- name: delay
suspend: {}
- name: image-build
inputs:
parameters:
- name: version
artifacts:
- name: source
path: /src
gcs:
bucket: <your-GCP-Bucket-name>
key: ci/
serviceAccountKeySecret:
name: argo-gcp
key: gcs-iam.json
container:
name: kaniko
image: gcr.io/kaniko-project/executor:debug
command: [executor]
workingDir: /src
args:
- "--dockerfile=Dockerfile"
- "--context=./"
- "--destination=<your-image-repository>/<your-image-name>:{{inputs.parameters.version}}"
volumeMounts:
- name: kaniko-secret
mountPath: /kaniko/.docker/
volumes:
- name: kaniko-secret
secret:
secretName: dockerhub-registry
items:
- key: .dockerconfigjson
path: config.json
- name: delay2
suspend:
duration: "5"
- name: gitops
inputs:
parameters:
- name: version
container:
image: bitnami/git:latest
command: [sh, -c]
args:
- |
mkdir -p /root/.ssh
cp /mnt/workspace/ssh-key/id_rsa /root/.ssh/id_rsa
chmod 600 /root/.ssh/id_rsa
ssh-keyscan github.com >> /root/.ssh/known_hosts
git clone git@github.com:<your-git-name>/<your-git-repo>.git /src
cd /src
git config --global user.name "<your-git-name>"
git config --global user.email "<your-git-email>"
sed -E -i 's/(image: <your-image-repository>\/<your-image-name>:)[^ ]+/\1{{inputs.parameters.version}}/g' deploy.yaml
git add deploy.yaml
git commit -m "Update image tag to {{inputs.parameters.version}}"
git push origin main
env:
- name: GIT_SSH_COMMAND
value: ssh -i /root/.ssh/id_rsa -o StrictHostKeyChecking=no
volumeMounts:
- name: ssh-key-volume
mountPath: /mnt/workspace/ssh-key
readOnly: false
volumes:
- name: ssh-key-volume
secret:
secretName: ssh-key-secret
items:
- key: ssh-private-key
path: id_rsa
- name: delay3
suspend:
duration: "5"
- name: deployment
container:
args:
- |-
apk --no-cache add curl
TOKEN=$(curl -s -k $ARGOCD_SERVER/api/v1/session -d "{\"username\":\"admin\",\"password\":\"$PASSWORD\"}" | sed -e 's/{"token":"//' | sed -e 's/"}//')
curl -k -X POST $ARGOCD_SERVER/api/v1/applications/argocicd/sync -H "Authorization: Bearer $TOKEN"
command:
- sh
- -xuce
env:
- name: ARGOCD_SERVER
valueFrom:
secretKeyRef:
key: argocd-server
name: argocd-config
- name: PASSWORD
valueFrom:
secretKeyRef:
key: password
name: argocd-config
image: alpine:3.17
name: ci-workflow-trigger
Docker-Config Secret
.dockerconfigjson secret은 Build과정에서 Container Image 저장소인 Docker Hub에 Image를 Push할 수 있는 권한이 필요하기 때문에 설정해주어야 합니다. 아래 명령어의 $부분을 본인의 상황에 맞게 수정 후 입력하면 .dockerconfigjson을 자동 생성해줍니다.
`$REGISTRY_SERVER`의 주소는 사설 저장소의 경우 사설 저장소 주소를 입력하고, 저처럼 Docker Hub에서 작업하시는 경우 https://index.docker.io/v1/ 를 입력하시면 됩니다.
kubectl create secret \
docker-registry docker-registry \
--docker-server=$REGISTRY_SERVER \
--docker-username=$REGISTRY_USER \
--docker-password=$REGISTRY_PASS \
--docker-email=$REGISTRY_EMAIL
위 명령어를 통해 Secret생성 후 `kubectl edit secrets dockerhub-registry`명령을 실행하면 .dockerconfigjson의 내용을 확인하실 수 있습니다. 해당 내용을 복사하여 secret의 <your-docker-config-base64-encrypt>에 대입하시면 됩니다.
GCS Secret
Argo Workflows의 Job 사이의 데이터 전달과정에서 GCS Bucket Storage를 사용하기 때문에 파일을 생성할 수 있는 권한이 필요한데, GCS Secret에는 이 권한정보를 입력해야 합니다. 우선 GCP의 Strage > Bucket 메뉴에서 Public 저장소를 생성하고, 위 코드의 <your-GCP-Bucket-name>에 생성한 Bucket 이름을 입력합니다.
그 후 GCP의 IAM > Service Account 메뉴에서 Bucket 저장소에 대한 권한을 부여한 서비스 계정을 생성하고, 키 관리 메뉴를 통해 JSON 파일의 키 정보를 다운로드 합니다. 그 후 아래 코드를 입력하여 Secret을 생성합니다.
kubectl create secret generic argo-gcp -n argo --from-file=<your-service-account-auth-file>.json
위 명령어를 통해 Secret 생성 후 `kubectl edit secrets -n argo gcp-service-account` 명령을 실행하면 data 부분에서 <your-GCP-IAM-json-base64-encrypt>에 대입해야 할 내용을 확인할 수 있습니다.
Git SSH Secret
Build된 이미지 정보를 ArgoCD가 바라보는 GitOps Repository에 반영하기 위해 Git SSH를 사용하여 Git Push권한을 부여합니다. 이를 위해서 Github에 SSH Key를 등록하는 과정이 필요합니다. 잘 정리된 글이 있어 링크 첨부합니다.
GitHub SSH Key 생성 및 등록 방법
GitHub 저장소는 HTTPS 혹은 SSH 프로토콜을 통해 다양한 작업을 할 수 있습니다. SSH 프로토콜은 공개키 방식으로 안전하게 정보를 교환합니다. 이번 글에서는 GitHub 인증을 위해 SSH 키를 만드는 방법
www.lainyzine.com
Github에 SSH Public Key 등록을 완료했으면 아래 절차에 따라 Private Key Secert을 생성합니다. `cat ~/.ssh/<your-private-key-file-name> | base64` 명령을 통해 Private Key 정보를 base64로 인코딩 합니다.
출력된 내용을 복사하여 <your-GIT-SSH-KEY-base64-encrypt>에 붙여 넣습니다.
ArgoCD Secret
GitOps에 Push된 이미지 정보를 반영하기 위해 Sync 버튼을 누르는 POST API 요청을 전달해야 합니다. 이때 토큰 정보와 ArgoCD 주소를 Secret에 저장하여 주소 및 인증 정보를 사용합니다.
설치 스크립트가 완료될때 표기해 주었던 Argocd의 Password 주소를 <your-argocd-admin-password-base64-encrypt>에 대입합니다.
기타 설정값
`https://<your-source-code-git-url>.git` : Source Code Repository 값을 입력합니다. EX) https://github.com/junkmm/WebGoDummy.git
`<your-GCP-Bucket-name>` : GCP Bucket 이름을 입력합니다. EX) argo-proj-junkmm
`<your-image-repository>/<your-image-name>` : 컨테이너 이미지 정보를 입력합니다. EX) kimhj4270/webgodummy
`<your-git-name>/<your-git-repo>` : GitOps 레포 정보를 입력합니다. EX) junkmm/GitopsDummy.git
CI/CD Pipeline 생성하기
모든 설정을 완료했으면 `kubectl create -f <filename.yaml>`을 통해 리소스를 생성합니다.
Github WebHook 등록하기
Source Code Repository에 Push 이벤트가 들어오면 Argo Events의 Event Sensor가 감지하도록 WebHook을 설정해야 합니다. 즉 개발자는 본인의 Code를 Push하기만 하면 배포 과정까지 자동화 할 수 있는 것이죠, 우선 Github에 접속하여 WebHook을 등록해야 합니다.
`Github` -> `Source Code Repository` -> `Settings` -> `Webhooks` 메뉴로 이동하고. Add webhook을 클릭합니다.
아래와 같이 URL 정보를 입력해야 합니다.
저의 경우 Webhook을 감지하는 Service의 Type을 LoadBalancer로 배포하였기 때문에 공인IP로 접근 가능한 상황입니다. `kubectl get service -n ci`를 입력하면 Service의 정보를 확인할 수 있습니다.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ci LoadBalancer 10.60.5.221 34.92.75.33 12000:31398/TCP 4d1h
CI/CD 실행 예시
아래는 Hello, World!-`v5.2`를 표기하는 go web 서버를 Hello, World!-`v5.3`으로 수정 업데이트 하는 예시입니다.
'클라우드' 카테고리의 다른 글
[EKS] Amzaon EKS 설치 및 기본 사용 (0) | 2024.03.09 |
---|---|
[k8s] Pod의 안정적인 유지 - liveness probe (0) | 2023.10.22 |
[Docker] Docker-Compose (0) | 2023.07.14 |
[Docker] Centos이미지 기반 httpd 서비스 구성하기 (0) | 2023.07.14 |
[Docker] Docker로 컨테이너 배포하기 (0) | 2023.07.14 |