- ArgoCD 설치 방법 : https://blog.wonizz.com/2020/06/08/kubernetes-deploy-tool-argocd/
- ArgoCD Slack Notification 방법 : https://blog.wonizz.com/2020/06/15/kubernetes-deploy-tool-notification/
1. Jenkinsfiles 를 통한 pipeline 구축
젠킨스 파이프 라인을 통해서 빌드잡을 구성했습니다. 이전의 젠킨스 파이프라인에 대해서 정리해놓은 포스팅이 있으니 참고 부탁드립니다.- Jenkins 파이프라인 사용법 : https://blog.wonizz.com/2019/08/04/jenkins-pipeline/
- 빌드가 시작되면, 운영자 / 개발자에게 시작 알림 메시지
- git에서의 소스코드 체크아웃
- Application Build ( gradle script를 통한 빌드 )
- Image Build ( Jar file Copy )
- Harbor push ( Image Repository )
- 빌드가 완료되면, 운영자/개발자에게 완료 알림 메시지
final HARBOR_USERNAME = '{harbor bot 계정}'
final HARBOR_PASSWORD = '{harbor pw 토큰}'
pipeline {
agent {
node {
label '{slave 고유의 이름}'
}
}
parameters {
string name: 'REVISION'
booleanParam name: 'ONLY_BUILD'
}
environment {
DOCKER_IMAGE_TAG = """${sh(
returnStdout: true,
script: 'echo ${REVISION#refs/tags/}'
)}"""
SLACK_CHANNEL = '{슬랙 채널명}'
SLACK_TOKEN = '{슬랙 채널 토큰}'
}
stages {
stage('Start Notification') {
steps {
slackSend(channel: SLACK_CHANNEL, color: '#ECB22E', message: "Build Start! (${env.BUILD_TAG})\n - phase : ${PHASE}\n- revision : ${DOCKER_IMAGE_TAG}", token: SLACK_TOKEN)
}
}
stage('Checkout') {
steps {
checkout([$class: 'GitSCM',
branches: [[name: "${REVISION}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
gitTool: 'Default',
submoduleCfg: [],
userRemoteConfigs: [[url: '{git 주소}']]
])
}
}
stage('Build Application') {
tools {
jdk 'JDK 8u121 + JCE'
}
steps {
sh './gradlew clean assemble'
}
}
stage('Application Test') {
tools {
jdk 'JDK 8u121 + JCE'
}
steps {
sh './gradlew check integrationTest'
}
}
stage('Harbor Log-in') {
steps {
sh 'docker logout {harbor 주소}'
sh "docker login {harbor 주소} --username ${HARBOR_USERNAME} --password ${HARBOR_PASSWORD}"
}
}
stage('Build Docker Image') {
parallel {
stage('Keystore2') {
steps {
sh 'docker build -t {harbor 주소}/{path}/{application name}:${DOCKER_IMAGE_TAG} --build-arg APPLICATION_PATH=apps/test --build-arg APPLICATION={application name} --build-arg "EXPOSE=29996 20082 20083" .'
sh 'docker push {harbor 주소}/{path}/{application name}:${DOCKER_IMAGE_TAG}'
}
}
}
}
stage ('Invoke_Helm Chart commit') {
steps {
build job: '{Invoked Job Name}',
parameters: [
string(name: 'PHASE', value: 'alpha'),
string(name: 'TARGET', value: 'link-framework'),
string(name: 'REVISION', value: String.valueOf(DOCKER_IMAGE_TAG)),
string(name: 'ONLY_BUILD', value: String.valueOf(ONLY_BUILD))
]
}
}
}
post {
success {
slackSend(channel: SLACK_CHANNEL, color: '#2EB67D', message: " Build Completed! (${env.BUILD_TAG})\n - phase : ${PHASE}\n- revision : ${DOCKER_IMAGE_TAG}", token: SLACK_TOKEN)
}
failure {
slackSend(channel: SLACK_CHANNEL, color: '#E01E5A', message: " Build Failed: @{slack menthion}\n - phase : ${PHASE}\n - build url : ${env.BUILD_URL}", token: SLACK_TOKEN)
}
}
}
최종적인 Jenkinsfiles의 구성은 위와 같습니다. 중요한 부분은 source를 체크아웃 받기 위해서 commit hash값을 인자로 받고 있습니다. 개발(커밋해시), 운영(태그)와 같이 2가지 방식으로 커밋 해시 값이 전달 됩니다.
parameters {
string name: 'REVISION'
booleanParam name: 'ONLY_BUILD'
}
environment {
DOCKER_IMAGE_TAG = """${sh(
returnStdout: true,
script: 'echo ${REVISION#refs/tags/}'
)}"""
SLACK_CHANNEL = '{슬랙 채널명}'
SLACK_TOKEN = '{슬랙 채널 토큰}'
}
위와 같이 파라미터로 REVISION값을 받게 되어있는데, 개발같은 경우는 commit hash 값이 그대로 와서 사용이 가능하지만, tag는 (ref/tags/v2.1.4)와 같은 형태로 오기 때문에 environment에서 한번 replace를 해주는 과정이 있습니다.
2. 젠킨스 Job 구성
위에서 간단하게 설명하긴 했지만, 파이프라인 이전 스텝에서 2가지 방식으로 파라미터를 전달 받습니다.- develop / release branch 푸시 : commit hash 값
Branch specifier부분에서 어떤 부분에 대해서 해당 Job이 감지를 할지 기입해 두면 됩니다.
Trigger parameterized build on the project의 사전 정의 파라미터에서 아래와 같이 입력을 하게 됩니다.
GIT_COMMIT은 해당 브랜치에서 최신의 커밋 해시 버전을 가져옵니다.
같은 방식으로 Tag 생성시에는 아래와 같이 입력을 합니다.
- tag 생성시 : tag 버전
GIT_BRANCH는 해당 태그버전의 브랜치명을 가져옵니다.
3. Dockfile 최적화 작업
빌드를 수행하면서 Dockerfile에 대해서도 최적화 작업을 진행했습니다. 기존에는 Multi-stage 방식으로 아래와 같이 굉장히 긴 포맷으로 빌드를 수행했습니다.# Build
FROM {harbor 주소}/{path}/{application name}/gradle:6.7.1-jdk8 AS build-env
ARG PROJECT
WORKDIR /{application name}
COPY . ./
RUN gradle clean :${PROJECT}:assemble
# Production
FROM {harbor 주소}/{path}/{application name}/openjdk:8-jre-alpine AS production-env
ARG PROJECT_PATH
ARG PROJECT
ARG EXPOSE
ENV JAR=${PROJECT}.jar
EXPOSE ${EXPOSE}
ENTRYPOINT /sbin/tini java \
${JAVA_OPTS} \
-jar ${JAR}
RUN addgroup -S -g 2001 www && adduser -S -u 1001 -G www www
RUN apk add --no-cache tini bash wget curl bind-tools && rm -rf /var/cache/apk/*
RUN sed -i s/.*networkaddress.cache.ttl.*/networkaddress.cache.ttl=0/g /usr/lib/jvm/java-1.8-openjdk/jre/lib/security/java.security
RUN sed -i s/.*networkaddress.cache.negative.ttl.*/networkaddress.cache.negative.ttl=0/g /usr/lib/jvm/java-1.8-openjdk/jre/lib/security/java.security
WORKDIR /home/www
USER www
# Copy test fils
COPY docker/test-custom.conf \
docker/test-env.conf \
docker/install_test_agent.sh \
./
# Install test agent
RUN sh install_test_agent.sh
# Copy kafka .jks files
COPY kafka ./kafka
# Copy jar
COPY --from=build-env --chown=www /{application 이름 }/${PROJECT_PATH}/${PROJECT}/build/libs/${PROJECT}.jar ./
파일의 원본은 밝힐수 없어, 내용을 가렸습니다. 결론적으로 위와 같이 Multi Stage에서의 가장큰 문제는 매번 빌드를 수행할때마다, Build-env안에서는 gradle스크립트를 통해서 모든 빌드를 수행한다는 것입니다.
아래의 과정은 jenkinsfiles에서 1번만 수행하도록 변경했습니다.
RUN gradle clean :${PROJECT}:assemble
추가적으로 아래와 같은 작업들을 더 진행했습니다.
- Build 영역 삭제
- COPY . ./ : COPY는 좀더 구체적으로 작성
- RUN : layer를 출이기 위해 하나의 작업 단위로 구성
- wget, curl : 불필요 의존성 제거 필요
# Production
FROM {harbor 주소}/{path}/{application name}/openjdk:8-jre-alpine-base
# Arguments & Environments
ARG PROJECT_PATH
ARG PROJECT
ARG EXPOSE
ENV JAR=${PROJECT}.jar
EXPOSE ${EXPOSE}
# Entrypoint for running docker
ENTRYPOINT /sbin/tini java \
${JAVA_OPTS} \
-jar ${JAR}
# Copy jar
COPY --chown=www /${PROJECT_PATH}/${PROJECT}/build/libs/${PROJECT}.jar ./
엄청 간결해진것도 있고, 빌드 시간도 약 10m이 소요된것에서 2m정도로 많이 단축이 되었습니다.
4. 빌드 / 배포 아키텍처
전체적인 빌드 / 배포에 대한 아키텍처는 다음과 같습니다.
- develop branch : 개발/릴리즈 브랜치로부터 커밋 해시 추출
- master branch : 태그 생성시 태그값 추출
- pipeline : 젠킨스 파일을 통한 빌드 파이프라인
- helm chart update : 최종적으로 빌드가 완료되면, values.yaml 파일의 docker image 버전 업데이트
5. 마치며…
빠른 개발과 안정적인 서비스를 위해서는 무엇보다도 빌드와 배포는 안정적으로 이루어져야 합니다. Jenkinsfiles와 Dockerfile의 최적화로 안정적인 CI를 구축했고, ArgoCD와 연계하기 위해 Helmchart를 중간에 업데이트 하도록 해두었습니다. 이로써, CI-CD간에 지속적인 통합과 배포가 이루어지도록 했습니다. 개발자들도 새로운 시스템에서 빠른 이해로 안정적으로 개발을 진행하고 있습니다. 다음시간에는 ArgoCD에 대해서 좀더 심화학습 하여 정리하도록 하겠습니다.함께 보면 좋은 글
- [DevOps] Nginx Rate Limit
- [DevOps] Hubot 적용기
- [DevOps] k8s monitoring with Datadog
- [DevOps] Istio virtualhost 사용법
새 글이 올라오면 Threads @wonizz.ai 에도 올립니다. 팔로우해 두면 피드에서 바로 볼 수 있습니다.