Skip to content

๐Ÿ› ๏ธ CI๏ผCD

JaeWon_LEE edited this page Apr 22, 2024 · 1 revision

๋นŒ๋“œ ๋ฐ ๋ฐฐํฌ ๋ฌธ์„œ

0. ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜

image

1. ๊ฐœ๋ฐœ์ž๊ฐ€ ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ ๋ฐ ํ…Œ์ŠคํŠธํ•œ ํ›„ , ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ github๋กœ push
2. github๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์— push ๋˜๋ฉด webhook์ด ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด์„œ HTTP ์š”์ฒญ
3. webhook์—์„œ HTTP์š”์ฒญ์„ ํ•˜๊ฒŒ ๋˜๋ฉด JENKINS๋ฅผ ํ†ตํ•ด Gradle์„ ์‚ฌ์šฉํ•˜์—ฌ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๋นŒ๋“œํ•˜๊ณ  ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•œ jar ํŒŒ์ผ๋กœ ์ƒ์„ฑ
4. ๋นŒ๋“œ๋œ jar ํŒŒ์ผ์€ dockerfile๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Docker image๋กœ pakaging.  
5. Docker๋Š” image๋ฅผ DockerHub๋กœ push 
6. ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํด๋Ÿฌ์Šคํ„ฐ๋Š” Docker image๋ฅผ ๊ฐ€์ ธ์™€์„œ ๋ฐฐํฌํ•˜๊ณ  ๋ฐฑ์—”๋“œ์™€ ํ”„๋ก ํŠธ์—”๋“œ๋Š” ๊ฐ๊ฐ์˜ ํŒŒ๋“œ๋กœ ๋ฐฐํฌ๋จ
7. ubuntu ์„œ๋ฒ„์—์„œ MariaDb๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  backend์™€ ์—ฐ๋™ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•จ
8. ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•œ ๋ฒˆ์—ญ ์ž‘์—…์€ DeepL API๋กœ ์ „์†ก๋˜์–ด ์ฒ˜๋ฆฌ๋˜๋ฉฐ, ๋ฒˆ์—ญ๋œ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ˜ํ™˜๋จ

ํ™˜๊ฒฝ

โ—ฝ macOS Sonoma 14.2

๋„๊ตฌ ๋ฒ„์ „
JDK 17
Docker 25.0.3
Kubernetes 1.29.1
Jenkins 2.453

1. ๋นŒ๋“œ

[ Dockerfile ]

  • Spring boot Dockerfile
FROM openjdk:17-alpine
COPY build/libs/*.jar app.jar

# ARG๋กœ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
ARG JASYPT_KEY
ENV JASYPT_KEY=$JASYPT_KEY

ENTRYPOINT ["java", "-jar", "app.jar", "--jasypt.encryptor.password=${JASYPT_KEY}"]

๐Ÿ”‘ jasypt๋ณตํ˜ธํ™” ํŒจ์Šค์›Œ๋“œ(JASYPT_KEY)๋Š” Jenkins Credentials๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌํ•˜๋ฉฐ, ์‹คํ–‰ ์‹œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ์„œ ์ž…๋ ฅ๋˜์–ด ์‹คํ–‰๋จ.



  • Vue Dockerfile
FROM node:lts-alpine

RUN apk add --no-cache curl

WORKDIR /app
COPY . ./
RUN npm install

CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

  • Jenkins Dockerfile
FROM jenkins/jenkins:jdk17

USER root

RUN apt-get update && \
    apt-get install -y apt-transport-https ca-certificates curl software-properties-common && \
    curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && \
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
 
 # docker-compose ์„ค์น˜
 RUN curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && \
    chmod +x /usr/local/bin/docker-compose && \
    ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

  • Jenkins docker-compose.yml
version: '3.7'

services:
  jenkins:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: 'jenkins_docker'
    
    # mac ์‹ค๋ฆฌ์ฝ˜ ์นฉ์˜ ๊ฒฝ์šฐ ์„ค์ •
    platform: linux/arm64
    
    restart: always
    user: root
    ports:
      - '8080:8080'
      - '50000:50000'
      
    volumes:
      - './jenkins_home:/var/jenkins_home'
      - '/var/run/docker.sock:/var/run/docker.sock'


โš ๏ธ ์  ํ‚จ์Šค ๋นŒ๋“œ ์ค‘ docker: not found ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ

docker exec -it jenkins_docker /bin/bash : container ์ ‘์†
curl -fsSL https://get.docker.com -o get-docker.sh : docker ์„ค์น˜
sh get-docker.sh



Github Webhook & Jenkins๋ฅผ ์ด์šฉํ•œ Pipeline ๊ตฌ์ถ•

โ—ฝ Jenkins Credentials

  1. ์  ํ‚จ์Šค ๋„์ปค ์ปจํ…Œ์ด๋„ˆ์— ์ ‘์†ํ•ด RSA Key ์ƒ์„ฑ -> Github ์ ‘์†์„ ์œ„ํ•ด private key ๋“ฑ๋ก

  2. Dockerhub ์ ‘์†์„ ์œ„ํ•œ dockerhub ๊ณ„์ • ์ •๋ณด ๋“ฑ๋ก

  3. jasypt ๋ณตํ˜ธํ™”์— ํ•„์š”ํ•œ password ๋“ฑ๋ก

image



- Github

Repository Settings์— ์ ‘๊ทผํ•˜์—ฌ Deploy keys์— public key ๋“ฑ๋ก

image



- ngrok์œผ๋กœ port๋ฅผ ๊ฐœ๋ฐฉํ•˜์—ฌ Webhook ์—ฐ๊ฒฐ
ngrok http 8080

image



- Jenkins Tools์—์„œ Java(OpenJDK 17) , Gradle(8.7) ์„ค์ •

image



- ์ƒˆ๋กœ์šด Item -> Pipeline ์ƒ์„ฑ -> Build Triggers -> Github hook trigger for GITScm polling ์ฒดํฌ

  • Jenkins Pipeline Script
pipeline {
    agent any

    tools {
        gradle 'gradle'
        jdk 'openJDK17'
    }

    environment {
        DOCKERHUB_USERNAME = 'orlzll'
        GITHUB_URL = 'https://github.com/OmokNoonE/OnionHotSayYo-backend.git'
    }

    stages {
        stage('Preparation') {
            steps {
                script {
                    sh 'docker --version'
                }
            }
        }
        stage('Source Build') {
            steps {
                // ์†Œ์ŠคํŒŒ์ผ ์ฒดํฌ์•„์›ƒ
                git branch: 'main', url: 'https://github.com/OmokNoonE/OnionHotSayYo-backend.git'

                // ์†Œ์Šค ๋นŒ๋“œ
                // 755๊ถŒํ•œ ํ•„์š” (์œˆ๋„์šฐ์—์„œ Git์œผ๋กœ ์†Œ์Šค ์—…๋กœ๋“œ์‹œ ๊ถŒํ•œ์€ 644)
                // JASYPT_KEY Credentials
                sh "chmod +x ./gradlew"
                 withCredentials([string(credentialsId: 'JASYPT_KEY', variable: 'JASYPT_KEY')]) { //set SECRET with the credential content
                    sh "./gradlew clean build -P jasypt.encryptor.password=${JASYPT_KEY}"               
                 }
                
            }
        }
        stage('Container Build') {
            steps {	
    
                // jar ํŒŒ์ผ ๋ณต์‚ฌ
                sh "cp ./build/libs/*.jar ."
    
                // ์ปจํ…Œ์ด๋„ˆ ๋นŒ๋“œ ๋ฐ ์—…๋กœ๋“œ
                withCredentials([string(credentialsId: 'JASYPT_KEY', variable: 'JASYPT_KEY')]) { //set SECRET with the credential content
                    sh "docker build --build-arg JASYPT_KEY=${JASYPT_KEY} -t ${DOCKERHUB_USERNAME}/onion-back2:latest . --platform linux/x86_64"
                }
                
                
                // docker hub๋กœ push
                withCredentials([usernamePassword(credentialsId: 'DOCKERHUB_PASSWORD', usernameVariable: 'DOCKERHUB_USER', passwordVariable: 'DOCKERHUB_PASS')]) {
                    sh "echo $DOCKERHUB_PASS | docker login --username $DOCKERHUB_USER --password-stdin"
                    sh "docker push ${DOCKERHUB_USERNAME}/onion-back2:latest"
                }
            }
        }
    }
}

๐Ÿ”จ ์ตœ์ดˆ๋กœ pipeline ๊ตฌ์ถ• ํ›„์—๋Š” โ–ถ๏ธ์ง€๊ธˆ ๋นŒ๋“œ ์‹คํ–‰



2. ๋ฐฐํฌ

Note

์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ†ตํ•ด ์‹คํ–‰ํ•˜๋ ค๋ฉด ์•„๋ž˜ ๋งค๋‹ˆํŽ˜์ŠคํŠธ ํŒŒ์ผ๋“ค์„ ํ•˜๋‚˜์˜ ํด๋”์—์„œ ๊ด€๋ฆฌ

โ—ฝSpring boot deployments & services

  • boot001dep.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: boot001dep
spec:
  selector:
    matchLabels:
      app: boot001kube
  replicas: 1
  template:
    metadata:
      labels:
        app: boot001kube
    spec:
      containers:
      - name: boot-container
        image: orlzll/onion-back2:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 8888

  • boot001ser.yml
apiVersion: v1
kind: Service
metadata:
  name: boot001ser
spec:
  type: NodePort
  ports:
  - port: 8888 #์„œ๋น„์ŠคํฌํŠธ
    targetPort: 8888
    protocol: TCP
    nodePort: 30001
  selector:
    app: boot001kube

โ—ฝ Vue deployments & Services

  • vue001dep.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vue001dep

spec:
  selector:
    matchLabels:
      app: vue001kube
  template:
    metadata:
      labels:
        app: vue001kube

    spec:
      containers:
      - name: vue-container
        image: orlzll/onion-front2:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 5173

  • vue001ser.yml
apiVersion: v1
kind: Service
metadata:
  name: vue001ser

spec:
  type: NodePort
  ports:
  - port: 5173
    targetPort: 5173
    protocol: TCP 
    
    nodePort: 30000
  selector:
    app: vue001kube


โ—ฝ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ ์‹คํ–‰

# ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ ์„ค์ •
KUBE_DIR=../../infra
VUE_DIR=../frontend/OnionHotSayYo_frontend          # frontend ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ
PASSWORD='0000'                                                         # sudo password

# vue project build
cd $VUE_DIR
echo $PASSWORD | sudo -S docker build -t orlzll/onion-front2 .
echo $PASSWORD | sudo -S docker push orlzll/onion-front2

# manifest
cd $KUBE_DIR
kubectl apply -f vue001dep.yml && kubectl apply -f vue001ser.yml
kubectl apply -f boot001dep.yml && kubectl apply -f boot001ser.yml

Note

ํ˜„์žฌ frontend project๋Š” Jenkins๋ฅผ ํ†ตํ•œ build ์ž๋™ํ™”๊ฐ€ ๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฏ€๋กœ ์ด ๊ณผ์ •์—์„œ build ๋ฐ docker hub๋กœ image push ํ•˜๋„๋ก ์„ค์ •ํ•จ



[ ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ ์‹œ ๋””ํ”Œ๋กœ์ด๋จผํŠธ ์—…๋ฐ์ดํŠธ ]
  • ๋ฐฑ์—”๋“œ
kubectl rollout restart deployments boot001dep

์  ํ‚จ์Šค๋ฅผ ํ†ตํ•ด dockerhub image๊ฐ€ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ ๋˜๋ฏ€๋กœ deployments restart


VUE_DIR=../frontend/OnionHotSayYo_frontend      # frontend ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ
PASSWORD='0000'                                 # sudo password

# vue project build
cd $VUE_DIR
echo $PASSWORD | sudo -S docker build -t orlzll/onion-front2 .
echo $PASSWORD | sudo -S docker push orlzll/onion-front2

# deployment restart
kubectl rollout restart deployments vue001dep

๋นŒ๋“œ -> ๋„์ปค ํ—ˆ๋ธŒ์— ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ -> ์žฌ์‹œ์ž‘๋จ


Note

์ถ”ํ›„ ํ•ด๋‹น ๊ณผ์ •์„ ArgoCD๋ฅผ ํ†ตํ•ด ๋„์ปค ํ—ˆ๋ธŒ ์ด๋ฏธ์ง€์˜ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๊ณ  Kubernetes ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ๊ณ ๋„ํ™” ์˜ˆ์ •


Redis Pods
Refresh Token ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด Redies๋ฅผ Kubernetes pods๋กœ์„œ ๋ฐฐํฌํ•จ
redis-pod.yml
redis-svc.yml
redis-configmap.yml