최근 CICD POC 준비와 Ceph 사용중인 K8s 클러스터 재배치 및 업그레이드 준비로 포스팅이 많이 밀렸습니다.
POC준비를 하며 구상하고 실제로 구축한 파이프라인 작업 과정을 남기기 위함.
클러스터 구성
마스터 노드 3개, 워커 노드 2개로 총 5개의 노드로 구성된 쿠버네티스 클러스터 환경입니다.
Ingress Controller : Nginx
CICD Engine : Tekton
Source Repo : Gitlab
Image Repo : Harbor ( 구성도 상에는 나오지 않았지만 pod 형태로 떠 있는 상태)
PPT를 통해 직접 아키텍쳐를 그려보았습니다. IP는 이해를 위해 가상의 IP로 적었습니다.
100% 완벽한 그림은 아니지만 (틀린게 있다면 알려주세요 ㅠ.ㅠ) 그림을 통해 Pipeline구성을 진행했던 내용을 설명하겠습니다.
가용성 보장을 위해 마스터 노드 삼중화를 진행했지만 상단에 L4가 없는 환경이라 KeepAlived를 설치하여 하나의 VIP로 세개의 마스터 노드에서 가상의 NIC을 통해 통신하는 구조를 만들었습니다. Active Stand-by 구조로 동작하며 Priority설정을 통해 Master로 설정된 노드와 통신이 되지 않을 시 차순위 노드로 VIP가 넘어가 가용성이 보장됩니다. (KeepAlived 설정 방법에 대한 가이드는 별도 포스팅 진행)
마스터 1번에 공인 IP를 할당 받고 해당 노드 내부에 HA proxy를 설치하여 port fowarding을 수행합니다. 8081 포트를 통해 들어오는 패킷은 Worker1번의 8080포트로 포워딩해주고, 8082포트를 통해 들어오는 패킷은 Worker2번의 8080포트로 포워딩 해주도록 설정했습니다. (HA Proxy 설정 방법에 대한 가이드는 별도 포스팅 진행)
이를 통해 추가 공인 IP 할당 없이 하나의 IP로 두 개의 개별 WAS 서버로 접근할 수 있습니다.
Worker1번과 Worker2번에 각각 Tomcat 서버를 구축하였습니다. (구축 방법에 대한 자세한 가이드는 별도 포스팅 진행)
Worker1번에는 DevOps포탈을 개발하여 배포하였고, Worker2번에는 파이프라인을 통해 Service APP이 배포될 예정입니다.
Gitlab과 Tekton을 REST API로 연동시킨 간단한 포탈을 개발하였습니다.
Source 정보
Springboot 2.7.13
Gradle 8.1.1
JAVA 11
주요 기능을 간단히 소개하겠습니다.
MR 승인 버튼을 눌러 API를 통해 Gitlab에서 생성된 MergeRequest를 Approve 합니다.Merge가 되면 배포 버튼이 활성화되고 배포 버튼을 눌러 API를 통해 Tekton Pipeline을 실행시킵니다.
포탈을 만들면서 아쉬운 부분이 많았는데 시간적이 충분했다면 MergeRequest를 조회하고 선택하여 Approve하고 Merge하는 기능을 추가하고 싶었고, 실패했을 경우의 예외처리 기능까지 추가했으면 더 좋았을 것 같다는 생각이 듭니다.
개인적으로 해당 기능들을 추가하여 포탈을 고도화를 진행해 볼 생각이고 고도화 시킨 후 해당 내용에 대해 더 자세히 리뷰해보도록 하겠습니다.
기본적으로 모 금융권 프로젝트를 진행하며 3-branch 전략으로 진행을 했었습니다. 이번 시나리오는 그 중 dev 브랜치에서 feature 브랜치를 따서 단위 기능 개발을 마치고 merge하여 개발 환경에 배포하는 과정을 Pipeline으로 생성하였습니다.
시나리오는 Pod로 K8s 클러스터에 배포하기와 WAS 서버에 배포하기 두 가지로 준비했습니다.
파이프라인 구조
Git-Clone => SonarQube(시큐어코딩) => Gradle-Build => Image-Build & Push => Deploy
총 6개의 Task로 구성되어 있습니다.
Tekton hub로 부터 제공받은 task를 필요에 맞게 수정한 것과 직접 작성한 task를 사용하여 구성했습니다.
각 Task에 대해 간단히 소개하겠습니다.
Git-Clone
git image로 실행되고 gitlab으로부터 배포할 서비스의 source를 clone해옵니다.
SonarQube
소나큐브 image로 실행되고 clone한 source의 시큐어코딩 체크를 진행합니다.
Gradle-Build
Gradle image로 실행되고 clone한 source의 build 작업을 수행합니다. 그 결과로 jar 파일이 생성됩니다.
Image-build
Buildah image로 실행되고 Dockerfile을 기반으로 생성된 jar파일과 base image로 배포할 image를 생성하고 생성된 image를 harbor(or 다른 이미지 레지스트리)에 push합니다.
Deploy
kubectl image로 실행되고 configMap을 통해 만들어둔 deployment template에 필요한 값들을 sed로 변경하여 yaml파일을 만들고 kubectl 커맨드를 통해 배포를합니다.
실제로 구성한 파이프라인런 yaml을 살펴보면
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: poc-container-00000-44p7m
namespace: ######
spec:
pipelineSpec:
tasks:
- name: git-clone
params:
- name: url
value: 'https://gitlab.#######.com/root/cicd-springboot-app'
- name: revision
value: container
- name: sslVerify
value: 'false'
- name: deleteExisting
value: 'true'
- name: gitInitImage
value: >-
gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.40.2
taskRef:
kind: ClusterTask
name: git-clone-dw
workspaces:
- name: output
workspace: s2i
- name: gradle-build
params:
- name: tls
value: 'false'
runAfter:
- git-clone
taskRef:
kind: ClusterTask
name: gradle-build-poc
workspaces:
- name: source
workspace: s2i
- name: sonarqube-scanner
params:
- name: SONAR_HOST_URL
value: 'http://sonarqube.#######.com/'
- name: SONAR_PROJECT_KEY
value: cicd
- name: PROJECT_VERSION
value: '1.0'
- name: SOURCE_TO_SCAN
value: ./src/main/java/com/example/demo/main/
- name: SONAR_ORGANIZATION
value: master
runAfter:
- git-clone
taskRef:
kind: ClusterTask
name: sonarqube-scanner
workspaces:
- name: source
workspace: s2i
- name: image-build
params:
- name: IMAGE
value: poc
runAfter:
- gradle-build
taskRef:
kind: ClusterTask
name: buildah-poc
workspaces:
- name: source
workspace: s2i
- name: app-deploy
params:
- name: appName
value: ######-poc
- name: svcName
value: ######-main
- name: nameSpace
value: ######-deploy
runAfter:
- image-build
taskRef:
kind: ClusterTask
name: deploy-poc
workspaces:
- name: source
workspace: s2i
workspaces:
- name: s2i
serviceAccountName: ######-poc-container-sa
timeout: 120h0m0s
workspaces:
- name: s2i
volumeClaimTemplate:
metadata:
creationTimestamp: null
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1000Mi
storageClassName: nfs
status: {}
다음과 같습니다.
파이프라인 구조
Git-Clone => SonarQube(시큐어코딩) => Gradle-Build => Deploy
총 4개의 Task로 구성되어 있습니다.
Tekton hub로 부터 제공받은 task를 필요에 맞게 수정한 것과 직접 작성한 task를 사용하여 구성했습니다.
각 Task에 대해 간단히 소개하겠습니다.
Git-Clone
git image로 실행되고 gitlab으로부터 배포할 서비스의 source를 clone해옵니다.
SonarQube
소나큐브 image로 실행되고 clone한 source의 시큐어코딩 체크를 진행합니다.
Gradle-Build
Gradle image로 실행되고 clone한 source의 build 작업을 수행합니다. 그 결과로 war 파일이 생성됩니다.
Deploy
ansible image로 실행되고 ansible로 WAS서버(Worker2번)에 접근하기 위해 필요한 정보는 secret으로 생성하고, ansible task와 inventory정보는 configmap으로 생성합니다. 이후 ansible-playbook 커맨드를 통해 WAS 서버로 war파일을 옮기고 Tomcat서버를 재기동합니다.
실제로 구성한 파이프라인런 yaml을 살펴보면
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: ######-poc-vm-00000-1251v
namespace: ######
spec:
pipelineSpec:
tasks:
- name: git-clone
params:
- name: url
value: 'https://gitlab.######.com/root/cicd-springboot-app'
- name: revision
value: vm
- name: sslVerify
value: 'false'
- name: deleteExisting
value: 'true'
- name: gitInitImage
value: >-
gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.40.2
taskRef:
kind: ClusterTask
name: git-clone-dw
workspaces:
- name: output
workspace: s2i
- name: gradle-build
params:
- name: tls
value: 'false'
runAfter:
- git-clone
taskRef:
kind: ClusterTask
name: gradle-build-poc
workspaces:
- name: source
workspace: s2i
- name: sonarqube-scanner
params:
- name: SONAR_HOST_URL
value: 'http://sonarqube.######.com/'
- name: SONAR_PROJECT_KEY
value: cicd
- name: PROJECT_VERSION
value: '1.0'
- name: SOURCE_TO_SCAN
value: ./src/main/java/com/example/demo/main/
- name: SONAR_ORGANIZATION
value: master
runAfter:
- git-clone
taskRef:
kind: ClusterTask
name: sonarqube-scanner
workspaces:
- name: source
workspace: s2i
- name: ansible
runAfter:
- gradle-build
taskRef:
kind: ClusterTask
name: ansible-runner-poc
workspaces:
- name: source
workspace: s2i
workspaces:
- name: s2i
serviceAccountName: ######-poc-vm-sa
timeout: 120h0m0s
workspaces:
- name: s2i
volumeClaimTemplate:
metadata:
creationTimestamp: null
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1000Mi
storageClassName: nfs
status: {}
다음과 같습니다.
일반적으로 Jenkins를 사용하여 CICD를 많이 진행합니다.
하지만 CICD 플랫폼으로 클라우드 환경을 사용할 경우 Tekton을 사용하는것이 더 유리합니다.
Jenkins의 경우 하나의 엔진에서 Pipeline들을 처리하기 때문에 Pipeline이 동작하지 않을 때에도 계속해서 떠 있어야하고, 이는 Resource적 측면과 비용(요금)적 측면에서 불필요한 낭비가 발생하게 됩니다.
또한 Jenkins 서버에 문제가 생길 겨우 모든 Pipeline에 영향이 미칩니다.
하지만 Tekton의 경우 Pipeline의 task들이 개별 Pod로 생성되어 진행되기 때문에 Pipeline이 돌지 않을 경우 Resource가 별도로 사용되지 않고 Pipeline들이 독립적으로 생성되어 진행되기 때문에 클라우드 환경에서 Jenkins보다 이점이 많습니다.
정리가 잘 된 글이네요. 도움이 됐습니다.