🤔 프로젝트가 1차적으로 끝난 이후에 배포 부분을 제대로 공부를 하고 싶었다고 생각하고 있었고...
그에 따라서 Docker와 Github Action에 대해서 깊이 있게 알아보면서 가지고 있던 의문들에 대한 답을 어느 정도 얻은 것 같습니다.
CI/CD 도구 - 1. Github Action 🔗
Docker에 대해서 알아보자 - 1. Docker의 개념과 생태계 🔗
Docker에 대해서 알아보자 - 2. Dockerfile 🔗
Docker에 대해서 알아보자 - 3. docker build & docker compose 🔗
어떻게든 돌아가던 설계에서 제대로 돌아가는 설계를 향해서... 리펙토링을 해보겠습니다.
현재 Clokey의 배포 프로세스는 Github Action과 Docker를 활용하며 Blue-Green 무중단 배포가 되도록 설계가 되어있습니다.
📌 Local 환경
✅ Dockerfile
FROM openjdk:17-jdk-slim
# Set the timezone to Asia/Seoul
ENV TZ=Asia/Seoul
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# Set the working directory
WORKDIR /app
# Copy the built JAR file into the container
COPY ./build/libs/*.jar app.jar
# Expose the port your application will run on
EXPOSE 8080
# Command to run the JAR file
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
✅ Docker-compose.yml
version: '3.8'
services:
app_blue:
image: (도커 유저명)/(도커 레포):1.0.0
container_name: app_blue
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=blue # 애플리케이션에서 'blue' 프로필로 설정
networks:
- app_network
app_green:
image: (도커 유저명)/(도커 레포):1.0.0
container_name: app_green
ports:
- "8081:8080"
environment:
- SPRING_PROFILES_ACTIVE=green # 애플리케이션에서 'green' 프로필로 설정
networks:
- app_network
networks:
app_network:
driver: bridge
✅ dev-deploy.yml (Github Action)
name: CI/CD Pipeline
on:
push:
branches: [ develop ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: make application-secret.yml
run: |
touch ./src/main/resources/application-secret.yml
echo "${{ secrets.APPLICATION_SECRET }}" > ./src/main/resources/application-secret.yml
shell: bash
- name: Build with Gradle Wrapper
run: |
./gradlew --stop && ./gradlew clean --refresh-dependencies
./gradlew clean build
ls -l build/libs
- name: Build Docker Image
run: |
docker build -t (도커 유저명)/(도커 레포):1.0.0 .
- name: Push Docker Image
run: |
echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
docker push (도커 유저명)/(도커 레포):1.0.0
- name: Copy docker-compose file to remote
uses: appleboy/scp-action@v0.1.3
with:
username: ubuntu
host: ${{ secrets.EC2_HOST }}
key: ${{ secrets.EC2_SSH_KEY }}
source: "./docker-compose.yml"
target: "/home/ubuntu/cicd/"
- name: Deploy Blue-Green
uses: appleboy/ssh-action@master
with:
username: ubuntu
host: ${{ secrets.EC2_HOST }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
docker pull (도커 유저명)/(도커 레포):1.0.0
sudo chmod +x /home/ubuntu/cicd/deploy.sh
sudo /home/ubuntu/cicd/deploy.sh
로컬 배포 프로세스 요약
- CI/CD 환경에서 github secrets에서 application-secret.yml을 가져와서 만듭니다.
- 로컬 환경에서에 존재하는 Dockerfile을 기반으로 이미지를 build 하고 docker hub에 있는 repository로 push 합니다.
- docker-compose.yml을 나중에 deploy.sh 스크립트가 사용할 수 있도록 배포 환경에 복사해 놓습니다.
- 배포 환경에 이미지를 pull 해서 가져온 후 deploy.sh 스크립트를 이용해서 배포를 진행합니다.
📌 AWS 서버
✅ green.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept onø;
}
http {
server {
listen 80;
server_name clokey.shop;
location / {
proxy_pass http://localhost:8081;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
client_max_body_size 100M;
}
✅ blue.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept onø;
}
http {
server {
listen 80;
server_name clokey.shop;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
##
# Basic Settings
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
client_max_body_size 100M;
}
✅ deploy.sh
#!/bin/bash
cd /home/ubuntu/cicd
APP_NAME="clokey"
# NGINX 설정 관련
NGINX_CONF_PATH="/etc/nginx"
BLUE_CONF="blue.conf"
GREEN_CONF="green.conf"
DEFAULT_CONF="nginx.conf"
MAX_RETRIES=3
# 활성화된 서비스 확인 및 스위칭 대상 결정
determine_target() {
if docker-compose -f docker-compose.yml ps | grep -q "app_blue.*Up"; then
TARGET="green"
OLD="blue"
elif docker-compose -f docker-compose.yml ps | grep -q "app_green.*Up"; then
TARGET="blue"
OLD="green"
else
TARGET="blue" # 첫 실행 시 기본값
OLD="none"
fi
echo "TARGET: $TARGET"
echo "OLD: $OLD"
}
# 헬스체크 실패 시 롤백 처리
health_check() {
local URL=$1
local RETRIES=0
local ORIGINAL_TARGET=$TARGET # 원래 TARGET 값을 저장
while [ $RETRIES -lt $MAX_RETRIES ]; do
echo "Checking service at $URL... (attempt: $((RETRIES + 1)))"
sleep 3
# 현재 실행 중인 컨테이너 확인
CONTAINER_RUNNING=$(docker ps --filter "name=app_$TARGET" --format '{{.Names}}')
if [ "$CONTAINER_RUNNING" = "app_$TARGET" ]; then
echo "$TARGET container is running."
return 0 # 컨테이너가 실행 중이라면 헬스체크 성공
else
echo "$TARGET container is not running."
fi
RETRIES=$((RETRIES + 1))
done
# 헬스체크 실패 시 롤백 처리
echo "Health check failed after $MAX_RETRIES attempts."
echo "Rolling back to the original target: $ORIGINAL_TARGET"
# TARGET을 원래 값으로 롤백
TARGET=$ORIGINAL_TARGET
echo "Rolled back TARGET: $TARGET"
# 로그 파일에 실패 기록
echo "Failed health check for $TARGET container" > /home/ubuntu/cicd/health_check_failure.log
# docker-compose down 대신 실패 기록 후 종료
exit 1
}
# NGINX 설정 스위칭 함수
switch_nginx_conf() {
if [ "$TARGET" = "blue" ]; then
sudo cp "${NGINX_CONF_PATH}/${BLUE_CONF}" "${NGINX_CONF_PATH}/${DEFAULT_CONF}"
else
sudo cp "${NGINX_CONF_PATH}/${GREEN_CONF}" "${NGINX_CONF_PATH}/${DEFAULT_CONF}"
fi
echo "Reloading NGINX configuration..."
nginx -s reload
}
# 이전 컨테이너 종료 함수
down_old_container() {
if [ "$OLD" != "none" ]; then
echo "Stopping old container: $OLD"
sudo docker stop "app_$OLD"
fi
}
# 메인 실행 로직
main() {
determine_target
# 대상 컨테이너 실행
echo "Starting $TARGET container..."
docker-compose -f docker-compose.yml up -d "app_$TARGET"
# 헬스체크
if [ "$TARGET" = "blue" ]; then
health_check "http://127.0.0.1:8080/actuator/health"
else
health_check "http://127.0.0.1:8081/actuator/health"
fi
# NGINX 설정 스위칭
switch_nginx_conf
# 이전 컨테이너 종료
down_old_container
echo "Deployment to $TARGET completed successfully!"
}
# 스크립트 실행
main
서버 환경 베포 요약
- 현재 실행되는 컨테이너를 바탕으로 다음으로 스위칭되어야 할 컨테이너 식별
- docker compose를 통해서 대상 컨테이너 실행
- 헬스 체크
- Nginx 설정 스위칭 후 이전 컨테이너 종료
📌 문제점 및 해결
1️⃣ 베포가 너무 오래 걸림 -> 최적화 필요
현재 CI/CD에 걸리는 시간은 3분 정도입니다.
그리고 그중에서도 build 하는 과정에서 절반 이상의 시간을 소모하고 있습니다.
buildx의 cache 기능과 멀티 스테이지 빌드를 통해서 최적화해보겠습니다.
Docker Image의 캐싱 기능 활용
# syntax=docker/dockerfile:1.4
FROM gradle:8.5-jdk17 AS dependencies
WORKDIR /build
COPY gradlew .
COPY gradle.properties /root/.gradle/gradle.properties
COPY gradle/wrapper/gradle-wrapper.jar gradle/wrapper/
COPY gradle/wrapper/gradle-wrapper.properties gradle/wrapper/
COPY build.gradle settings.gradle ./
RUN ./gradlew dependencies --no-daemon
FROM gradle:8.5-jdk17 AS builder
(메인 빌드 스테이지)
FROM openjdk:17-jdk-slim
(최종 이미지 스테이지)
다음과 같이 빌드를 3개의 스테이지로 분리하여 멀티 스테이지 빌드로 설계를 변경했습니다.
대부분의 업데이트의 경우에 dependency는 바뀌지 않습니다. 따라서, 이 부분을 캐싱하여 dependency에 대한 레이어는 보존한 채로 의존성과 관련된 사항이나 Dockerfile 자체가 바뀌지 않는다면 dependencies 레이어는 다시 만들 필요가 없어지게 됩니다.
- name: Check Dockerfile or Dependency Changes
id: check_changes
run: |
git fetch origin ${{ github.event.before }}
echo "Changed files between commits:"
changed_files=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }})
echo "$changed_files"
if echo "$changed_files" | grep -qE 'Dockerfile|build\.gradle|settings\.gradle|gradle\.properties|gradlew|gradle/wrapper/gradle-wrapper\.properties'; then
echo "changed=true" >> $GITHUB_ENV
else
echo "changed=false" >> $GITHUB_ENV
fi
- name: Build & Push Dependency Cache
if: env.changed == 'true'
run: |
docker buildx build \
--builder mybuilder \
--platform linux/amd64 \
--push \
--file Dockerfile \
--tag ${{ secrets.DOCKERHUB_USERNAME }}/clokey-docker:dependency-cache \
--cache-to type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/clokey-docker:dependency-cache,mode=max \
--secret type=env,id=GRADLE_BUILD_CACHE_URL,env=GRADLE_BUILD_CACHE_URL \
--secret type=env,id=GRADLE_BUILD_CACHE_USERNAME,env=GRADLE_BUILD_CACHE_USERNAME \
--secret type=env,id=GRADLE_BUILD_CACHE_PASSWORD,env=GRADLE_BUILD_CACHE_PASSWORD \
.
⚠️ 트러블 슈팅 : 이때 dependency 뿐만 아니라 Dockerfile이 바뀌는 것 자체도 검사해야 합니다 , Dockerfile이 조금이라도 바뀌면 레이어 정보가 바뀌기 때문입니다.
따라서, 의존성 정보 또는 Dockerfile 레이어가 바뀌는 순간 cache 정보를 갱신해서 docker repository에 cache를 갱신하게 됩니다.
cache는 원본 이미지와 별도로 관리됩니다.
- name: Build & Push Application Image
run: |
docker buildx build \
--builder mybuilder \
--platform linux/amd64 \
--push \
--file Dockerfile \
--tag ${{ secrets.DOCKERHUB_USERNAME }}/clokey-docker:1.0.0 \
--build-arg DEPENDENCY_IMAGE=${{ secrets.DOCKERHUB_USERNAME }}/clokey-docker:dependency-cache \
--cache-from type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/clokey-docker:dependency-cache \
--secret type=env,id=GRADLE_BUILD_CACHE_URL,env=GRADLE_BUILD_CACHE_URL \
--secret type=env,id=GRADLE_BUILD_CACHE_USERNAME,env=GRADLE_BUILD_CACHE_USERNAME \
--secret type=env,id=GRADLE_BUILD_CACHE_PASSWORD,env=GRADLE_BUILD_CACHE_PASSWORD \
.
그 이후에 원본 Application 이미지를 빌드하는 과정에서 --cache-from을 사용하여 dependency 레이어의 캐시를 활용하게 됩니다. 이때 캐시를 갱신하지 않는 이유는...
⭐ 어차피 1 스테이지(의존성 관련) 레이어만 재사용되기 때문입니다. 매번 코드가 업데이트되면 src의 소스 코드가 바뀌게 될 것이고.. 1단계 스테이지 말고는 이미지 캐시를 사용하기 힘들 것입니다. 따라서, 1 스테이지의 캐시 정보가 바뀌는 경우 (의존성이 바뀌거나 Dockerfile이 바뀌는 경우)를 제외하고는 캐시 업데이트를 하지 않습니다.
Gradle 캐시 적용
현재 CI/CD 과정은 clean 빌드를 사용하기 때문에 코드의 일부만 바뀌게 되어도 처음부터 소스 파일을 다시 컴파일하는 문제가 발생합니다. 이를 해결하기 위해서 gradle 캐시를 적용했습니다.
다만, CI/CD는 매번 새로운 환경에서 실행이 되기 때문에 gradle 캐시를 저장하기 위한 원격에 캐시 저장소 서버를 띄워줄 필요가 있습니다. 원래는 gradle enterprise에서 제공하는 Develocity 이미지가 있어서 사용하려 했으나, 유로였기 때문에 간단히 flask 스크립트로 EC2 서버에 캐시를 저장할 수 있는 컨테이너를 띄워줬습니다.
(수정) -> 20225.07.07 : gradle 캐시를 저장할 수 있는 깃허브 액션이 있어서 사용하면 편리합니다~!
✅ gradle-cache-server.py
from flask import Flask, request, Response, abort, send_file
import os
app = Flask(__name__)
USERNAME = (유저명)
PASSWORD = (비번)
CACHE_DIR = "/cache-data"
# Ensure the cache directory exists
os.makedirs(CACHE_DIR, exist_ok=True)
@app.before_request
def authenticate():
auth = request.authorization
if not auth or auth.username != USERNAME or auth.password != PASSWORD:
return Response(
"Unauthorized", 401,
{"WWW-Authenticate": "Basic realm='Login Required'"}
)
@app.route("/<cache_id>", methods=["GET", "PUT"])
def cache(cache_id):
file_path = os.path.join(CACHE_DIR, cache_id)
if request.method == "GET":
if not os.path.exists(file_path):
abort(404)
return send_file(file_path, as_attachment=True)
if request.method == "PUT":
# === 여기 추가됨 ===
for filename in os.listdir(CACHE_DIR):
file_path_to_delete = os.path.join(CACHE_DIR, filename)
try:
if os.path.isfile(file_path_to_delete):
os.remove(file_path_to_delete)
except Exception as e:
print(f"Failed to delete {file_path_to_delete}: {e}")
# === 여기까지 ===
with open(file_path, "wb") as f:
f.write(request.data)
return "Saved", 200
abort(405)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
ubuntu@ip-172-31-42-221:~/gradle-cache-server$
그리고 프로젝트에서 원격 서버의 캐시를 사용할 수 있도록, gradle 설정과 properties를 만들어 주었습니다.
#setting.gradle
rootProject.name = 'server'
def buildCacheUrl = System.getenv('GRADLE_BUILD_CACHE_URL')
def buildCacheUsername = System.getenv('GRADLE_BUILD_CACHE_USERNAME')
def buildCachePassword = System.getenv('GRADLE_BUILD_CACHE_PASSWORD')
buildCache {
local {
enabled = true
}
if (buildCacheUrl) {
remote(HttpBuildCache) {
url = buildCacheUrl
push = true
allowInsecureProtocol = true
credentials {
username = buildCacheUsername
password = buildCachePassword
}
}
}
}
#gradle.properties
org.gradle.caching=true # 로컬 + 원격 캐시 활성화
org.gradle.configureondemand=true
org.gradle.terms.of.use=true
캐시 서버에 대한 정보는 build시에만 필요하나, 민감한 정보기 때문에 ARG를 사용하는 대신 secret mount로 RUN에서 주입을 해주었습니다.
// deploy.yml 에서 주입해주는 부분
- name: Build & Push Application Image
run: |
docker buildx build \
--builder mybuilder \
--platform linux/amd64 \
--push \
--file Dockerfile \
--tag ${{ secrets.DOCKERHUB_USERNAME }}/clokey-docker:1.0.0 \
--build-arg DEPENDENCY_IMAGE=${{ secrets.DOCKERHUB_USERNAME }}/clokey-docker:dependency-cache \
--cache-from type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/clokey-docker:dependency-cache \
--secret type=env,id=GRADLE_BUILD_CACHE_URL,env=GRADLE_BUILD_CACHE_URL \
--secret type=env,id=GRADLE_BUILD_CACHE_USERNAME,env=GRADLE_BUILD_CACHE_USERNAME \
--secret type=env,id=GRADLE_BUILD_CACHE_PASSWORD,env=GRADLE_BUILD_CACHE_PASSWORD \
.
//Dockerfile 내부 부분
FROM gradle:8.5-jdk17 AS dependencies
WORKDIR /build
COPY gradlew .
COPY gradle.properties /root/.gradle/gradle.properties
COPY gradle/wrapper/gradle-wrapper.jar gradle/wrapper/
COPY gradle/wrapper/gradle-wrapper.properties gradle/wrapper/
COPY build.gradle settings.gradle ./
RUN ./gradlew dependencies --no-daemon
FROM gradle:8.5-jdk17 AS builder
WORKDIR /build
COPY --from=dependencies /build /build
COPY src src
RUN --mount=type=secret,id=GRADLE_BUILD_CACHE_URL \
--mount=type=secret,id=GRADLE_BUILD_CACHE_USERNAME \
--mount=type=secret,id=GRADLE_BUILD_CACHE_PASSWORD \
bash -euxo pipefail -c " \
echo '==== Mounted Secrets ====' && \
ls -l /run/secrets && \
export GRADLE_BUILD_CACHE_URL=\$(cat /run/secrets/GRADLE_BUILD_CACHE_URL | tr -d '\r\n') && \
export GRADLE_BUILD_CACHE_USERNAME=\$(cat /run/secrets/GRADLE_BUILD_CACHE_USERNAME | tr -d '\r\n') && \
export GRADLE_BUILD_CACHE_PASSWORD=\$(cat /run/secrets/GRADLE_BUILD_CACHE_PASSWORD | tr -d '\r\n') && \
./gradlew bootJar -x test --build-cache \
"
FROM openjdk:17-jdk-slim
ENV TZ=Asia/Seoul
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ >/etc/timezone
WORKDIR /app
COPY --from=builder /build/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
⚠️ 트러블 슈팅 : secret mount를 사용하게 되면 몇 가지 주의 사항들이 있습니다.
- buildx 세팅은 물론이고 builder를 꼭 따로 정의해 주어야 합니다 (default 사용 시 작동 X)
- Dokcerfile 내부에도 지시자를 통해서 최신 버전을 사용해야 합니다.
- secret mount는 한 번의 RUN에서 사용되면 사라지게 됩니다.
- 이외에도 secret mount를 사용하게 되면 docker에서 조금 더 내부적으로 test 코드라던지 Dockerfile의 문법을 엄격하게 보게 되기 때문에 몇 가지 부수적인 에러가 발생할 있습니다.
저는 특히 builder를 따로 정의해 주지 않았던 부분 때문에 오랜 시간 고생했습니다...
정리하면 다음과 같이 최적화되었습니다.
의존성 부분 → 도커 이미지 스테이지 단위로 캐시 됨 다시 만들지 않음
바뀐 소스 코드 → gradle 캐시를 통해서 바뀐 부분만 컴파일
빌드 시간이 평균적으로 1분 50초 정도로 36%의 시간이 절감되었습니다.
2️⃣ 보안적 문제
프로젝트를 할 당시에는 .dockerignore의 기능에 알지 못했고 결과적으로 적용하지 않았습니다. 또한 현재는 application-secret.yml을 gitIgnore로 관리하여 안전한 상태이지만(Github Action을 통해서 배포할 경우 안전함), 그냥 로컬에서 이미지를 만들 경우 포함될 수 있습니다.
⚠️ 즉, 현재 image에 민감한 정보가 담긴 appplication-secret.yml이 포함될 수도 있는 경로가 열려있기는 하다고 결론을 내렸습니다.
.dockerignore
# 기본적으로 무시할 것들
.git
.idea
.vscode
*.log
*.iml
# 빌드 결과물 무시
build/
target/
# 민감 파일 무시
src/main/resources/application-secret.yml
#도커 관련 파일
docker-compose.yml
Dockerfile
# 깃허브 파일
.github/
현재는 Dockerfile에서 src아래의 파일들과 gradle 관련 파일만 image로 만들지만, .dockerignore에 root에 존재하는 파일들을 명시해 주면 git에서는 추적하는 Dockerfile과 docker-compose.yml도 build context에서 제외하기 때문에 더 최적화할 수 있습니다.
따라서, 다음과 같이 .dockerignore을 완성해 주었습니다.
version: "3.8"
services:
app_blue:
image: (도커 사용자명)/(도커 레포):1.0.0
container_name: app_blue
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=blue
- SPRING_CONFIG_ADDITIONAL_LOCATION=file:/app/config/
volumes:
- /home/ubuntu/secret/application-secret.yml:/app/config/application-secret.yml:ro
networks:
- app_network
app_green:
image: (도커 사용자명)/(도커 레포):1.0.0
container_name: app_green
ports:
- "8081:8080"
environment:
- SPRING_PROFILES_ACTIVE=green
- SPRING_CONFIG_ADDITIONAL_LOCATION=file:/app/config/
volumes:
- /home/ubuntu/secret/application-secret.yml:/app/config/application-secret.yml:ro
networks:
- app_network
networks:
app_network:
driver: bridge
docker-compose.yml의 secret 기능은 swarm이 아니기 때문에 사용하지 못했습니다. 대신 volume mount를 통해서 이미지 레이어에 민감한 정보를 담고 있는 application-secret.yml을 포함시키지 않았고 이는 호스트의 volume이 연결된 것이기 때문에 컨테이너에서 commit을 통해서 새로운 이미지를 만들어 내는 경우에도 application-secret이 포함되지 않기 때문에 안전합니다.
3️⃣ ETC
마지막으로, 생각보다 EC2로 파일을 전송하는 행위 자체가 오래 걸리는 것 같아서 git으로 관리되는 해시값을 비교해서 수정되는 경우에만 전송하도록 수정했습니다 (application-secret.yml)은 gitignore 되어 있어서 불가능.
최종 수정 코드 :
Github Repository 🔗
'프로젝트' 카테고리의 다른 글
JPA 최적화는 어떻게 해야할까? (0) | 2025.05.28 |
---|---|
배포의 모든 것 - 5. Blue-Green 무중단 배포 적용 (2) | 2025.05.01 |
Docker에 대해서 알아보자 - 3. docker build & docker compose (0) | 2025.04.26 |
Docker에 대해서 알아보자 - 2.Dockerfile (1) | 2025.04.26 |
Docker에 대해서 알아보자 - 1.Docker의 개념과 생태계 (0) | 2025.04.18 |