CI / CD
- CI (Continuous Integration): 지속적 통합
- 코드에 대한 통합
- 다수의 개발자들의 코드 베이스를 계속해서 통합한다는 의미
- 즉 여러 개발자들의 코드를 빠르게 배포함을 의미
- CD (Continuous Deployment): 지속적 배포
- 코드 베이스를 사용자가 사용하는 환경에 코드 베이스 배포를 자동화하는 것
- 즉 CI/CD는 여러명의 개발자들이 개발 환경을 통합하여 사용자가 사용하는 환경에 전달하는 모든 일련의 과정들을 자동화하는 것
젠킨스?
- Java Runtime Environment에서 동작
- 플러그인 사용하여 자동화 작업 파이프라인을 설계
- 파이프라인 마저도 플러그인의 일부
- Credentials 플러그인을 사용해서 민감정보를 보관
젠킨스 파이프라인
- Section - 가장 큰 개념
- Agent Section
- 오케스트레이션 처럼 slave node에 일 처리 지정
- Post Section
- 스테이지가 끝난 이후의 결과에 따른 후속 조치
- 즉 작업 결과에 따른 행동 조치
- Stage Section - 카테고리
- 어떠한 일을 처리할 것인지 stage를 정의
- Dockerfile의 스테이지를 정의하는 것과 같음
- Step Section - 카테고리 내부의 동작들
- 한 스테이지 안에서의 단계
- 여러 작업들 실행 가능
Declaratives
- Environment, Stage, Options, Parameters, Triggers, When
- 각 Stage 안에서 어떠한 일을 할 것인지 정의
- Environment
- Parameter
- Triggers
- 파이프라인이 실행되는 트리거 정의
- 예시: 특정 브랜치에 머지가 감지되었을 때
- When
- 수행되는 조건문
- 예시: 환경변수 BUILD_BRANCH가 dev일 때
예시
- 깃허브 특정 레포 dev(개발)/main(운영) 브랜치에 머지 인식
- 미리 Credentials에 등록해 둔 각각 환경에 따라 다른 환경 변수 로드
- 각각 환경에 따른 이미지 명으로 도커 이미지 빌드
- 개인 도커 레지스트리에 이미지 푸시
- 각각 개발/운영 서버에 배포할 서비스 경로 생성
- docker-compose.yml 파일 복사
- 빌드하여 푸시한 이미지 가져와서 컨테이너 구동
// 브랜치별 배포 위치
def getDeployTargets(envName) {
targets = [:]
// dev 브랜치
targets['prod'] = [[
COMPOSE_ENV: 'dev',
SSH_MODE: 'KEYONLY',
SSH_IP: '<Server IP>',
SSH_KEY_ID: 'Key Id From Jenkins Credentials',
COPY_DIR: '도커 컴포즈 파일 배포할 서버 상의 경로'
]]
return targets[envName]
}
// 브랜치별 환경 정보
def getBuildBranch(branchName) {
branches = [
'origin/main': 'prod',
]
return branches[branchName]
}
pipeline {
agent any
//환경변수
environment {
//브랜치 선택
BUILD_BRANCH = getBuildBranch(env.GIT_BRANCH)
// 도커 설정
DOCKER_IMAGE = ''
DOCKER_IMAGE_NAME = '빌드 할 도커 이미지 명'
// Git, Docker private 레지스트리 로그인 정보 설정
GIT_KEY_ID = '깃허브 인증 SSH 키 ID - Credentials에서 관리;'
REGISTRY_LOGIN_INFO_ID = 'donghquinn_registry'
}
stages {
stage('체크아웃') {
steps {
echo "작업 브랜치: ${env.GIT_BRANCH}"
git branch: BUILD_BRANCH, credentialsId: GIT_KEY_ID, url: 'git@github.com:donghquinn/<레포 명>.git'
}
}
stage('도커 이미지 빌드') {
steps {
script {
// 브랜치에 따라 이미지 이름 변경
DOCKER_IMAGE = docker.build(DOCKER_IMAGE_NAME + '-' + BUILD_BRANCH)
}
echo "Built: ${DOCKER_IMAGE_NAME}-${BUILD_BRANCH}"
}
}
stage('도커 이미지 Push') {
steps {
script {
// 개발서버 내부 Docker private 레지스트리에 업로드
docker.withRegistry('https://registry.donghyuns.com', REGISTRY_LOGIN_INFO_ID) {
DOCKER_IMAGE.push(env.BUILD_NUMBER)
DOCKER_IMAGE.push('latest')
}
}
echo "Pushed: ${DOCKER_IMAGE_NAME}-${BUILD_BRANCH}:${env.BUILD_NUMBER}"
}
}
stage('도커 컨테이너 배포') {
steps {
script {
def deployTargets = getDeployTargets(BUILD_BRANCH)
def deployments = [:]
// 배포 타깃별로 병렬 배포
for (item in deployTargets) {
def target = item
deployments["TARGET-${BUILD_BRANCH}"] = {
def remote = [:]
remote.name = target.SSH_IP
remote.host = target.SSH_IP
remote.allowAnyHosts = true
if (target.SSH_MODE == 'KEYONLY') {
withCredentials([
// DOTENV 파일과 SSH KEY를 가져옴
sshUserPrivateKey(credentialsId: target.SSH_KEY_ID, keyFileVariable: 'SSH_PRIVATE_KEY', usernameVariable: 'USERNAME')
]) {
// 가져온 키로 ssh 정보 설정
remote.user = USERNAME
remote.identityFile = SSH_PRIVATE_KEY
sshCommand remote: remote, command: """
mkdir -p ${target.COPY_DIR}-${BUILD_BRANCH}/
"""
// docker compose 파일 전송
sshPut remote: remote, from: "docker-compose.${BUILD_BRANCH}.yml", into: "${target.COPY_DIR}-${BUILD_BRANCH}/docker-compose.yml", failOnError: 'true'
// 각 상황에 맞는 .env.* 파일 전송
// 각 상황에 맞는 .env.* 파일 전송
sshPut remote: remote, from: DOTENV, into: "${target.COPY_DIR}/.env", failOnError: 'true'
// 도커 이미지 Pull 및 재시작
sshCommand remote: remote, command: """
cd ${target.COPY_DIR}-${BUILD_BRANCH}/
BUILD_BRANCH=${BUILD_BRANCH} docker compose pull
BUILD_BRANCH=${BUILD_BRANCH} docker compose up -d
"""
}
}
}
parallel deployments
}
}
}
}
}
}