๐ง ATMS - CICD ๊ตฌ์ฑ
์ด ํ๋ก์ ํธ๋ ์ํ ์๋ํ๊ธฐ๊ธฐ(ATM)์ ์ด์ ํํฉ์ ์ค์๊ฐ์ผ๋ก ๋ชจ๋ํฐ๋งํ๊ณ , ์ฅ์ ๋ฅผ ๊ฐ์งยท๋์ํ๋ฉฐ, ๊ธฐ๊ธฐ ์ ๋ณด์ ์ด์์ ์ ๋ฌด๋ฅผ ๊ด๋ฆฌํ๋ ATM ํตํฉ ๊ด๋ฆฌ ์์คํ (ATMS) ์ด๋ค.
Spring Boot ๋ฉํฐ๋ชจ๋ ๋ฐฑ์๋ 6๊ฐ(ํ๋ฉด API, ๊ฑฐ๋์ ๋ ์์ , ๋ฐฐ์น/์ ์ฐ, ํ์ผ ์ฐ๋, ๋๋ดยท๋์ธ ์ธํฐํ์ด์ค)์ React ํ๋ก ํธ์๋ 1๊ฐ, ์ด 7๊ฐ ์๋น์ค๊ฐ ๋ง๋ฌผ๋ ค ์ด์๋๋ค.
๋ฐฐํฌ ๋์์ด 7๊ฐ๋ ๋๋ค ๋ณด๋, ์ฒ์์๋ ๋ฐฐํฌํ ๋๋ง๋ค ์๋ฒ์ ์ง์ ์ ์ํด git pull โ ๋ชจ๋๋ณ ์ฌ๋น๋ โ ์์๋๋ก ์ฌ๊ธฐ๋์ ์์ผ๋ก ๋ฐ๋ณตํด์ผ ํ๋ค. ์ด ๋ฐฉ์์๋ ๋ ๊ฐ์ง ๋ฌธ์ ๊ฐ ์์๋ค.
๊ทธ๋์ git push ํ ๋ฒ์ผ๋ก ์ฒดํฌ์์๋ถํฐ ๋น๋, ์ฌ๊ธฐ๋๊น์ง ํญ์ ๋์ผํ ์์๋ก ์คํ๋๋ ํ์ดํ๋ผ์ธ์ด ํ์ํ๋ค๊ณ ํ๋จํ๋ค.
์ด๋ฒ ๊ตฌ์ถ ์ ๊ณผ์ ์์ Claude Code๋ฅผ ํธ๋ฌ๋ธ์ํ ํํธ๋๋ก ์ ๊ทน ํ์ฉํ๋ค. ํนํ ํจ๊ณผ๋ฅผ ๋ณธ ์ง์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
graph TD
A[๊ฐ๋ฐ์ git push origin main]
B[GitLab Webhook]
C[Jenkins Checkout]
D[Jenkins โ ๋ฐฐํฌ์๋ฒ SSH ์ ์]
E[์๋ฒ์์ git pull]
F[docker compose build]
G[docker compose up -d]
| ์๋น์ค | ํฌํธ | ๋น๊ณ |
|---|---|---|
| frontend | 19080:80 | React + nginx |
| atms-web | 19081:8080 | ๋ฉ์ธ API |
| atms-journal | 19082:8081 | ๊ฑฐ๋์ ๋ |
| atms-batch | 19083:8082 | ๋ฐฐ์น/ํต๊ณ |
| atms-file | 19084:8083 | ํ์ผ/์ด๋ฏธ์ง |
| atms-if-internal | 19085:8084 | ๋๋ด ์ฐ๊ณ |
| atms-if-external | 19086:8085 | ๋์ธ ์ฐ๊ณ |
์๋ฒ์ ์ด๋ฏธ ๋ค๋ฅธ ์๋น์ค๊ฐ ์ฌ๋ฟ ๋ ์์ด์, ๊ธฐ๋ณธ ํฌํธ(80/8080...) ๋์ 19000๋ฒ๋๋ก ์ ๋ถ ์ฎ๊ฒผ๋ค. (๊ธฐ์กด ์๋น์ค์ ํฌํธ๊ฐ ๊ฒน์ณ ํ ๋ฒ ์ถฉ๋์ ๊ฒช์ ๋ค ์ ์ฐฉํ ๊ท์น์ด๋ค.)
Jenkins๋ ๊ทธ๋ฅ ์ค์นํ์ง ์๊ณ ์ปค์คํ ์ด๋ฏธ์ง๋ก ๋์ ๋ค. ์ด์ ๋ ๋ ๊ฐ์ง๋ค.
docker compose build/up์ ์คํํด์ผ ํ๋ฏ๋ก) โ ์ด๋ฏธ์ง ์์ Docker CLI๊ฐ ์์ด์ผ ํ๋ค.FROM jenkins/jenkins:lts-jdk21
USER root
# Docker CLI ์ค์น โ Jenkins๊ฐ ํธ์คํธ Docker๋ฅผ ์ ์ดํ ์ ์๋๋ก
RUN apt-get update && apt-get install -y ca-certificates curl gnupg lsb-release && \
install -m 0755 -d /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
> /etc/apt/sources.list.d/docker.list && \
apt-get update && apt-get install -y docker-ce-cli && rm -rf /var/lib/apt/lists/*
RUN groupadd -f docker && usermod -aG docker jenkins
USER jenkins
COPY plugins.txt /usr/share/jenkins/ref/plugins.txt
RUN jenkins-plugin-cli --plugin-file /usr/share/jenkins/ref/plugins.txt
COPY casc/jenkins.yaml /var/jenkins_casc/jenkins.yaml
ENV CASC_JENKINS_CONFIG=/var/jenkins_casc/jenkins.yaml
plugins.txt์ ์๋ ํ๋ฌ๊ทธ์ธ๋ค, ํนํ ssh-agent ๋ฅผ ๋ฐ๋์ ๋ฃ์ด์ผ ํ๋ค.
workflow-aggregator
git
gitlab-plugin
credentials-binding
ssh-agent # โ Jenkinsfile์ sshagent() ์คํ
์ ํ์
ws-cleanup
configuration-as-code
์ด๊ฑธ ๋นผ๋จน์ผ๋ฉด ๋์ค์ Deploy ๋จ๊ณ๊ฐ ๋ก๊ทธ ํ ์ค ์์ด ์กฐ์ฉํ ์คํจํ๋ค. (๋ค์ ํธ์์ ๋ค๋ฃฐ ์ด์ผ๊ธฐ์ ๋ณต์ ์ด๋ค.)
JCasC ์ค์ ์ ์ต๋ํ ๋จ์ํ๊ฒ ์ ์งํ๋ค. ์ฒ์์ local๊ณผ gitLab ์ธ์ฆ ๋ฐฉ์์ ๋์์ ์ ์ธํ๋ค๊ฐ Jenkins๊ฐ ๋ฌดํ ์ฌ์์ ๋ฃจํ์ ๋น ์ง ์ ์ด ์๋๋ฐ, ์ธ์ฆ ๋ฐฉ์์ ํ๋๋ง ๋จ๊ธฐ๊ณ ๋์์ผ ์ ์ ๊ธฐ๋ํ๋ค.
jenkins:
securityRealm:
local:
allowsSignup: false
users:
- id: "${JENKINS_ADMIN_ID}"
password: "${JENKINS_ADMIN_PASSWORD}"
authorizationStrategy:
loggedInUsersCanDoAnything:
allowAnonymousRead: false
์ด๋ฒ ๊ตฌ์ถ์์ Jenkins๋ ์๋ก ๋ค๋ฅธ ๋ ๊ณณ์ ์ ์ํ๊ณ , ๊ฐ๊ฐ ์์ ํ ๋ค๋ฅธ ์ธ์ฆ ์๋จ์ ์ด๋ค. ์ฌ๊ธฐ์ ํ ๋ฒ ํท๊ฐ๋ ธ๋ค๊ฐ ๋ค์์ ํฌ๊ฒ ๊ณ ์ํ๋ค.
โ Jenkins โ GitLab (์์ค ์ฝ๋ ๋ฐ๊ธฐ)
์ธ์ฆ์๋จ: GitLab Deploy Token (์์ด๋/๋น๋ฐ๋ฒํธ ํํ)
โก Jenkins โ ๋ฐฐํฌ ์๋ฒ (์ค์ ๋ฐฐํฌ ์คํ)
์ธ์ฆ์๋จ: SSH ๊ฐ์ธํค (์ด์ ํํ)
์ ๋๋ค. Deploy Token์ GitLab ์ ์ฅ์ ์ฝ๊ธฐ ์ ์ฉ ๊ถํ์ผ ๋ฟ, ์๋ฒ SSH ์ ์๊ณผ๋ ์์ ํ ๋ณ๊ฐ์ ์ธ์ฆ ์ฒด๊ณ๋ค. ์ด๊ฑธ ๊ฐ์ ๊ฒ์ผ๋ก ์ฐฉ๊ฐํ๋ฉด ๋ฐ๋์ ๋งํ๋ค.
GitLab ์ชฝ์ ๊ฐ์ธ ๊ณ์ ์ ์ข ์๋์ง ์๋ Deploy Token(์ฝ๊ธฐ ์ ์ฉ)์ ๋ฐ๊ธํด์ ์ผ๋ค. Personal Access Token์ ๊ณ์ ์ด ์ญ์ ๋๊ฑฐ๋ ๋น๋ฐ๋ฒํธ๊ฐ ๋ฐ๋๋ฉด CI๊ฐ ํต์งธ๋ก ๋ฉ์ถ๊ธฐ ๋๋ฌธ์ ์ฒ์๋ถํฐ ํ๋ณด์์ ์ ์ธํ๋ค.
์ต์ข ์ ์ผ๋ก ์์ฑํ Jenkinsfile์ ๋ค์๊ณผ ๊ฐ๋ค. Checkout๊ณผ Deploy, ๋ฑ ๋ ์คํ ์ด์ง๋ฟ์ด๋ค.
pipeline {
agent any
options {
timeout(time: 30, unit: 'MINUTES')
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '10'))
}
environment {
DEPLOY_HOST = '10.0.20.15'
DEPLOY_USER = 'deploy'
DEPLOY_DIR = '/home/deploy/atms'
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(
script: 'git rev-parse --short HEAD',
returnStdout: true
).trim()
}
}
}
stage('Deploy') {
steps {
sshagent(credentials: ['atms-server-ssh-key']) {
sh """
ssh -o StrictHostKeyChecking=no \\
-o ConnectTimeout=10 \\
${DEPLOY_USER}@${DEPLOY_HOST} '
cd ${DEPLOY_DIR}
git pull origin main
docker compose build
docker compose up -d --remove-orphans
'
"""
}
}
}
}
post {
success { echo "โ
๋ฐฐํฌ ์ฑ๊ณต | ์ปค๋ฐ: ${env.GIT_COMMIT_SHORT}" }
failure { echo "โ ๋ฐฐํฌ ์คํจ | Jenkins ์ฝ์ ๋ก๊ทธ๋ฅผ ํ์ธํ์ธ์" }
always { cleanWs() }
}
}
disableConcurrentBuilds() : ๋ฐฐํฌ ๋์ค ๋ ๋ค๋ฅธ ๋ฐฐํฌ๊ฐ ๊ฒน์ณ ์๋น์ค๊ฐ ๋ถ์์ ํด์ง๋ ๊ฑธ ๋ง๋๋ค.sshagent(credentials: [...]) ๋ธ๋ก ์์์๋ง SSH ๊ฐ์ธํค๊ฐ ์ด์์๊ณ , ๋ธ๋ก์ ๋ฒ์ด๋๋ฉด ์๋์ผ๋ก ํด์ ๋๋ค.anonymous is missing the Job/Build permission ์ด๋ผ๋ 403์ ๋ฐ๊ฒ ๋๋ค.์ฌ๊ธฐ๊น์ง ๊ตฌ์ฑํ๊ณ ๋๋, ํ์ดํ๋ผ์ธ ์์ฒด๋ ๊ทธ๋ด๋ฏํ๊ฒ ์์ฑ๋ ๊ฒ์ฒ๋ผ ๋ณด์๋ค.
์์ ์ ์๋ฏ, ์ฆ์์ ๊ทธ๋๋ก AI์๊ฒ ๋์ง๊ณ ๊ฐ์ค์ ์ธ์ฐ๊ณ โ ๊ฒ์ฆํ๊ณ โ ๋ค์ ๊ฐ์ค๋ก ๋์ด๊ฐ๋ ๊ณผ์ ์ ํจ๊ป ์งํํ๋ค. ๋ค์ ๋ ํธ์ ๊ทธ ๊ณผ์ ์ ๊ทธ๋๋ก ๊ธฐ๋กํ ๊ธ์ด๋ค.
ํ์ง๋ง ์ค์ ๋ก git push๋ฅผ ๋ ๋ ค๋ณด์, ํ์ดํ๋ผ์ธ์ ๊ณง๋ฐ๋ก ๋ ๊ฐ์ ๋ฒฝ์ ๋ถ๋ชํ๋ค.