์ฐธ๊ณ
Amazon Linux 2023 Red Hat Linux ๊ธฐ์ค.
0. ์์
Spring Cloud ๊ธฐ๋ฐ ํ๋ก์ ํธ์ CI/CD ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ์๋ค.
๊ธฐ์กด CI/CD์ ๋ค๋ฅธ ์ ์ ์ฌ๋ฌ ์๋น์ค๋ฅผ ํ๊บผ๋ฒ์ ๋ฐฐํฌํด์ผ๋๋ค๋ ๊ฒ์ด์๋ค.
๋ณธ ํ๋ก์ ํธ์ ์๋น์ค๋ ์๋ ์ธ๊ฐ์ง์๋ค.
- noti-service
- weather-service
- user-service
๋๋จธ์ง ํ๊ฒฝ์ ์ํ ์๋น์ค๋ค์ ๋ค์๊ณผ ๊ฐ์๋ค.
- Eureka
- api-Gateway
- config-service
- Kakfa & Zookeper
ํด๋น ์๋ฒ๋ค ๋ชจ๋ ์ง์์ ์ธ ๋ฐฐํฌ ํ๊ฒฝ์ ๊ตฌ์ถํ๋ค.
์ฐ์ ๋ค์๊ณผ ๊ฐ์ด ์ ํ๋ค.
- Eureka, config-service, kafka, zookeper ๋ค ๊ฐ์ง๋ ์๋น์ค ๋ก์ง์ด ์ ๋ณ๊ฒฝ๋์ง ์์ผ๋ฏ๋ก ์ปจํ ์ด๋ ์ต์ ํ๋ฅผ ํ์ง ์์๋ค.
- ๋ฐ๋ฉด ๋ค๋ฅธ ์๋น์ค๋ค์ ์๋น์ค๋ก์ง์ด ๋ณ๊ฒฝ๋๊ธฐ ๋๋ฌธ์ ์ต์ ํ๋ฅผ ์งํํ๋ค.
- ๋น์ง๋์ค ๋ก์ง ๋ณ๊ฒฝ๋ง๋ค ๋ชจ๋ ์๋น์ค ์ปจํ ์ด๋๋ฅผ ๋ค์ ์คํํ ํ์๋ ์๋ค๊ณ ์๊ฐํด์, input์ ๋ฐ์ ํ์ํ ์๋น์ค๋ง ์ต์ ํ๋ฅผ ํ๊ธฐ๋ก ํ๋ค.
1. ์ ์คํฌ๋ฆฝํธ
์ ์คํฌ๋ฆฝํธ๋ 3๊ฐ์ง๋ก ๋๋ด๋ค.
1-1. docker.sh
#!/bin/bash
#์
๋ฑ
(shebang) : ์ด ์คํฌ๋ฆฝํธ๊ฐ Bash Shell ์์ ์คํ๋์ด์ผ ํจ์ ๋ํ๋.
#Docker ๋ช
๋ น์ด๊ฐ ์กด์ฌํ๋์ง ํ์ธํจ.
# -v ์ต์
: ๋ช
๋ น์ด ์คํ ์ ๋ช
๋ น์ด์ ์ธ์ ์ถ๋ ฅ.
# &> ๋ช
๋ น์ด ์ถ๋ ฅ๊ณผ ์ค๋ฅ ๋ฉ์ธ์ง๋ฅผ ๋ฆฌ๋ค์ด๋ ์
. /dev/null ๋๋ฐ์ด์ค๋ก ๋ฆฌ๋ค์ด๋ ์
ํจ.
# docker ๋ช
๋ น์ด์ ๊ฒฝ๋ก๋ฅผ ์ถ๋ ฅํ๊ณ (stdout), ๋ฐ์ํ ์ ์๋ ์ค๋ฅ ๋ฉ์ธ์ง(stderr)์ ํจ๊ป /dev/null ๋๋ฐ์ด์ค๋ก ๋ฆฌ๋ค์ด๋ ์
.
# /dev/null ์ ํน์ํ ๋๋ฐ์ด์ค ํ์ผ. ์ฐ๊ธฐ ์์
์ ์ํํ ์ ์์ง๋ง ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฒ๋ฆผ.
# ์ฆ, ๋ช
๋ น์ด์ ์ถ๋ ฅ๊ณผ ์ค๋ฅ ๋ฉ์ธ์ง๋ฅผ ๋ชจ๋ ์จ๊ธธ ์ ์์.
# ๊ฒฐ๋ก ์ ์ผ๋ก ๋ช
๋ น์ด์ ์กด์ฌ ์ฌ๋ถ๋ฅผ ํ์ธ. ๋๋ ๋ช
๋ น์ด ์คํ ๊ฒฐ๊ด๋ฅด ์ฌ์ฉํ์ง ์๊ณ ์ฑ๊ณต/์คํจ ์ฌ๋ถ๋ง ํ์ธํ๊ฒ ๋จ.
if ! command -v docker &> /dev/null; then
# docker ๋ช
๋ น์ด๊ฐ ์คํจํ์ ๊ฒฝ์ฐ -> docker ๊ฐ ์ค์น๋์ด ์์ง ์์. docker ์ค์น๋ฅผ ์งํํจ.
echo "Docker is not installed..."
echo "Docker install start..."
sudo yum update -y
# docker ์ค์น
sudo yum install -y docker
# docker ๊ถํ ์ค์
sudo usermod -aG docker ec2-user
sudo systemctl enable --now docker
echo "Docker install complete"
else
# Docker ๋ช
๋ น์ด๊ฐ ์ฑ๊ณตํ์ ๊ฒฝ์ฐ. Docker ๊ฐ ์ด๋ฏธ ์ค์น๋จ์ ์ถ๋ ฅ
echo "Docker is already installed"
fi
# Docker-compose install
# Docker-compose ์ค์น
if ! command -v docker-compose &> /dev/null; then
echo "Docker-compose is not installed..."
echo "Docker-compose install start..."
#Docker compose plugin ์ค์น - Amazon Linux 2023
sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose version
echo "Docker-compose install complete!"
else
echo "Docker-compose is already installed"
fi
ํด๋น CI/CD๊ฐ ์๋ก์ด ํ๊ฒฝ์์๋ ๋์ํ ์ ์๋๋ก Docker์ Docker-compose๋ฅผ ์ค์นํ๋ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ๋ค.
Amazon Linux 2023 (Red Hat Linux) ๊ธฐ์ค์ด๊ณ ,
ubuntu ์๋ฒ์ผ ์ yum๋์ apt-get ๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค.
1-2. common.sh
์์์ ์ธ๊ธํ๋ "์๋น์ค ๋ก์ง์ด ๋ณ๊ฒฝ๋์ง ์๋" ์๋น์ค ์ปจํ ์ด๋๋ค์ ์คํํ๋ ์คํฌ๋ฆฝํธ์ด๋ค.
# ํ์ฌ ์คํ์ค์ธ ๋์ปค ์ปจํ
์ด๋์ค ๋จ์ด๊ฐ ํฌํจ ์ปจํ
์ด๋๋ฅผ ๊ฒ์
RUNNING_EUREKA=$(docker ps | grep eureka)
RUNNING_CONFIG=$(docker ps | grep config)
RUNNING_ZOOKEEPER=$(docker ps | grep zookeeper)
RUNNING_KAFKA=$(docker ps | grep kafka)
# Eureka ๊ฒ์
if [ -z "$RUNNING_EUREKA" ]; then
echo "Starting Eureka ..."
docker-compose -f /home/docker-compose.yml up -d eureka
sleep 5 #Eureka ์คํ 5์ด ๋๊ธฐ
else
echo "Eureka is already running"
fi
# Config ๊ฒ์
if [ -z "$RUNNING_CONFIG" ]; then
echo "Starting Config Service ..."
docker-compose -f /home/docker-compose.yml up -d config
sleep 5 #Config ์คํ 5์ด ๋๊ธฐ
else
echo "Config Service is already running"
fi
# Zookeeper ๊ฒ์
if [ -z "$RUNNING_ZOOKEEPER" ]; then
echo "Starting Zookeeper ..."
docker-compose -f /home/docker-compose.yml up -d zookeeper
else
echo "Zookeeper is already running"
fi
# Kafka ๊ฒ์
if [ -z "$RUNNING_ZOOKEEPER" ]; then
echo "Starting Kafka ..."
docker-compose -f /home/docker-compose.yml up -d kafka
else
echo "Kafka is already running"
fi
bash ์ -z ๋ฅผ ์ด์ฉํ์ฌ ์ปจํ ์ด๋๊ฐ ์คํ์ค์ธ์ง ํ์ธํ๊ณ , ์คํํ์ง ์๊ณ ์๋ค๋ฉด docker-compose๋ฅผ ์ด์ฉํ์ฌ
ํด๋น ์ปจํ ์ด๋๋ฅผ ์คํ์ํจ๋ค.
1-3. service.sh
#!/bin/bash
# ์
๋ ฅ๋ฐ์ ์๋น์ค ์ด๋ฆ
SERVICE_NAME=$1
# ์๋น์ค ์ด๋ฆ์ ๋ฐ๋ผ ๋ฐฐํฌํ ์ปจํ
์ด๋ ์ด๋ฆ ์ค์
case "$SERVICE_NAME" in
"api-gateway")
TARGET_SERVICE="api-gateway"
;;
"noti-service")
TARGET_SERVICE="noti-service"
;;
"user-service")
TARGET_SERVICE="user-service"
;;
"weather-service")
TARGET_SERVICE="weather-service"
;;
"all")
TARGET_SERVICE="api-gateway noti-service user-service weather-service"
;;
*)
echo "Invalid service name: $SERVICE_NAME"
exit 1
;;
esac
echo "$SERVICE_NAME service start..."
docker-compose -f /home/docker-compose.yml up -d $SERVICE_NAME
Github Action Input์ผ๋ก ๋ฐ์ ํ๋ผ๋ฏธํฐ๋ก, ์ด๋ค ์๋น์ค ์ปจํ ์ด๋๋ฅผ ์ต์ ํํ ์ง ์ ํํ๋ค.
2. docker-compose.yml
version: '3'
services:
api-gateway:
container_name: api-gateway
image: ${DOCKERHUB_USERNAME}/waither-gateway
expose:
- "8000"
restart: always
volumes:
- /home/ec2-user/logs/api-gateway:/logs
config:
container_name: config
image: ${DOCKERHUB_USERNAME}/waither-config
expose:
- "8888"
environment:
CONFIG_GIT_URI : ${CONFIG_GIT_URI}
CONFIG_PASSPHRASE : ${CONFIG_PASSPHRASE}
CONFIG_PRIVATE_KEY : ${CONFIG_PRIVATE_KEY}
restart: always
volumes:
- /home/ec2-user/logs/config:/logs
eureka:
container_name: eureka
image: ${DOCKERHUB_USERNAME}/waither-eureka
expose:
- "8761"
restart: always
volumes:
- /home/ec2-user/logs/eureka:/logs
user-service:
container_name: user-service
image: ${DOCKERHUB_USERNAME}/waither-user
expose:
- "8080"
restart: unless-stopped #์๋์ผ๋ก ์ค์ง๋์ง ์๋ ์ด์ ํญ์ ์ฌ์คํ
volumes:
- /home/ec2-user/logs/user-service:/logs
weather-service:
container_name: weather-service
image: ${DOCKERHUB_USERNAME}/waither-weather
expose:
- "8081"
restart: unless-stopped
volumes:
- /home/ec2-user/logs/weather-service:/logs
noti-service:
container_name: noti-service
image: ${DOCKERHUB_USERNAME}/waither-noti
expose:
- "8082"
restart: unless-stopped
volumes:
- /home/ec2-user/logs/noti-service:/logs
zookeeper:
image: wurstmeister/zookeeper:latest
container_name: zookeeper
ports:
- "2181:2181"
kafka:
image: wurstmeister/kafka:latest
container_name: kafka
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ํ์ํ ์๋น์ค๋ค์ ๋ชจ๋ ๊ธฐ์ ํ๋ค.
ํ์ํ ํ๊ฒฝ๋ณ์๋ค์ Github Action Runner์์
export ๋ฅผ ์ด์ฉํ์ฌ ํ๊ฒฝ๋ณ์๋ฅผ ์ค์ ํ๊ณ ์คํ๋๋ค.
3. ci.yml
Continuous Integration (์ง์์ ํตํฉ)์ ์ํ ๊นํ๋ธ ์ก์ ์ํฌํ๋ก์ด๋ค.
name: Backend CI
on:
pull_request:
branches:
- prod
- develop
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- name: action checkout
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: |
chmod +x ./config-service/gradlew
chmod +x ./noti-service/gradlew
chmod +x ./user-service/gradlew
chmod +x ./weather-service/gradlew
- name: Build with Gradle - config
run: |
cd config-service
./gradlew clean build -x test
env:
CONFIG_GIT_URI: ${{ secrets.CONFIG_GIT_URI }}
CONFIG_PASSPHRASE: ${{ secrets.CONFIG_PASSPHRASE }}
CONFIG_PRIVATE_KEY: ${{ secrets.CONFIG_PRIVATE_KEY }}
- name: Run config-service
run: |
./gradlew bootRun &
cd ..
- name: Wait for config-service to start
run: sleep 5
- name: Build with Gradle - noti-service
run: |
cd noti-service
./gradlew clean build -x test
cd ..
- name: Build with Gradle - user-service
run: |
cd user-service
./gradlew clean build -x test
cd ..
- name: Build with Gradle - weather-service
run: |
cd weather-service
./gradlew clean build -x test
cd ..
๋ค๋ฅธ ์๋น์ค๋ค์ด ๋น๋๋๊ธฐ ์ํด์ Config-Service๊ฐ ๋จผ์ ์คํ๋์ด์ผ ํ๊ธฐ ๋๋ฌธ์
Config-Service๋ฅผ ๋จผ์ ๋น๋ & ์คํํ๊ฒ๋๋ค.
Config-Service ๋ private key๋ก ์ค์ ํ์ผ๋ค์ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ 5์ด๋งํผ ๊ธฐ๋ค๋ ธ๋ค๊ฐ ๋ค์ ์๋น์ค๋ค์ ๋น๋ํ๋ค.
4. cd.yml
์ต์ข ์ ์ผ๋ก Continuous Deploy ๋ฅผ ์ํ ๊นํ๋ธ ์ก์ ์ํฌํ๋ก์ด๋ค.
name: Backend CI/CD with Gradle
on:
workflow_dispatch:
inputs:
service:
description: 'Choose Service to deploy. Default is all.'
required: true
default: 'all'
type: choice
options:
- all
- api-gateway
- noti-service
- user-service
- weather-service
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
#Setting JDK
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'temurin'
#gradlew chmod
- name: Grant execute permission for gradlew
run: |
chmod +x ./apiGateway-service/gradlew
chmod +x ./config-service/gradlew
chmod +x ./Eureka/gradlew
chmod +x ./noti-service/gradlew
chmod +x ./user-service/gradlew
chmod +x ./weather-service/gradlew
# DockerHub Login
- name: Docker Hub Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
#Config-Service Build & Docker Push
- name: Build with Gradle - config
run: |
cd config-service
./gradlew clean build -x test
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/waither-config .
docker push ${{ secrets.DOCKERHUB_USERNAME }}/waither-config
env:
CONFIG_GIT_URI: ${{ secrets.CONFIG_GIT_URI }}
CONFIG_PASSPHRASE: ${{ secrets.CONFIG_PASSPHRASE }}
CONFIG_PRIVATE_KEY: ${{ secrets.CONFIG_PRIVATE_KEY }}
#Config-Service Run
- name: Run config-service
run: |
./gradlew bootRun &
cd ..
#Wait 5 sec
- name: Wait for config-service to start
run: sleep 5
#apiGateway-Service Build & Docker Push
- name: Build with Gradle - apiGateway
run: |
cd apiGateway-service
./gradlew clean build -x test
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/waither-gateway .
docker push ${{ secrets.DOCKERHUB_USERNAME }}/waither-gateway
cd ..
#Eureka Build & Docker Push
- name: Build with Gradle - Eureka
run: |
cd Eureka
./gradlew clean build -x test
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/waither-eureka .
docker push ${{ secrets.DOCKERHUB_USERNAME }}/waither-eureka
cd ..
#Noti-Service Build & Docker Push
- name: Build with Gradle - noti-service
run: |
cd noti-service
./gradlew clean build -x test
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/waither-noti .
docker push ${{ secrets.DOCKERHUB_USERNAME }}/waither-noti
cd ..
#User-Service Build & Docker Push
- name: Build with Gradle - user-service
run: |
cd user-service
./gradlew clean build -x test
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/waither-user .
docker push ${{ secrets.DOCKERHUB_USERNAME }}/waither-user
cd ..
#Weather-Service Build & Docker Push
- name: Build with Gradle - weather-service
run: |
cd weather-service
./gradlew clean build -x test
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/waither-weather .
docker push ${{ secrets.DOCKERHUB_USERNAME }}/waither-weather
cd ..
deploy:
name : Connect to EC2 and re-run container
needs : build
runs-on : ubuntu-latest
steps:
- name: Deploy to Server
uses: appleboy/ssh-action@v0.1.6
env:
SERVICE_NAME: ${{ github.event.inputs.service }}
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
password: ${{ secrets.EC2_PASSWORD }}
port: ${{ secrets.EC2_SSH_PORT }}
timeout: 60s
script: |
export CONFIG_GIT_URI=${{ secrets.CONFIG_GIT_URI }}
export CONFIG_PASSPHRASE=${{ secrets.CONFIG_PASSPHRASE }}
export DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME }}
export CONFIG_PRIVATE_KEY=${{ secrets.CONFIG_PRIVATE_KEY }}
bash /home/docker.sh
bash /home/common.sh
bash /home/service.sh
์ฒ์์ workflow_dispatch๋ฅผ ์ด์ฉํ์ฌ ์ฌ๋ฐฐํฌํ ์๋น์ค๋ฅผ ์ ๋ ฅ๋ฐ๋๋ค.
workflow_dispatch๋ฅผ ์ด์ฉํ๋ ค๋ฉด ํด๋น ๋ธ๋์น๊ฐ ๊ธฐ๋ณธ ๋ธ๋์น์ ์์ด์ผ ํ๋ค.
- build ์์
ํ๋ก์ ํธ์ ๋ํ gradle ๋น๋๋ฅผ ์งํํ๊ณ , docker image ๋น๋ ํ pushํ๋ ๊ณผ์ ์ด๋ค.
- deploy ์์
AWS EC2์ ์ ์ ํ, ํ์ํ ํ๊ฒฝ๋ณ์๋ค์ ์ค์ ํ๊ณ
๋ฏธ๋ฆฌ ์์ฑํด ๋์๋ ์คํฌ๋ฆฝํธ ์ธ๊ฐ์ง๋ฅผ ์คํํ๋ค.
docker -> common -> service ์์ผ๋ก ์คํํด์ผ์ง ์๋น์ค๊ฐ ์ค๋ฅ ์์ด ๋์ํ๋ค.
์คํ ๊ฒฐ๊ณผ ์ปจํ ์ด๋ 7๊ฐ๊ฐ ์คํ๋๋๋ฐ,
์ฒ์์ EC2 ํ๋ฆฌํฐ์ด ์ฌ์ฉ ๊ฐ๋ฅํ CPU 1๊ฐ์ธ t2.micro๋ก ์ฒ์์ ์คํํ์ ๋ ์๋ฒ๊ฐ ๊ณผ๋ถํ๋๊ณ ๋จนํต์ด ๋๋ค....
t2.medium๊น์ง๋ ๋์ํ๋ค.
'DevOps' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
์ํํธ์จ์ด ํ๋ก์ธ์ค ์ ๋ฆฌ - ์ํํธ์จ์ด ๊ฐ๋ฐ ์๋ช ์ฃผ๊ธฐ (0) | 2023.09.01 |
---|