배경
기존에는 애플리케이션만 Docker 이미지로 EC2 서버에서 실행하고, Redis는 EC2 서버에 인메모리로 설치하였다.
하지만 Redis도 컨테이너로 관리하는게 환경 구성에 편리할 것 같다고 생각했다.
이에 Docker-Compose를 사용하여 두 컨테이너들이 통합 관리되도록 변경하였다.
추후에도 배포 자동화시 이 방법을 계속 사용할 거라 잊어버리기 전에 기록해두려고 한다.
cd.yml 파일 수정
더보기
name: CD Backend
on:
push:
branches: [ "main", "dev" ]
permissions:
contents: read
jobs:
build-and-push-image:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
# 자바 버전 설정
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# yml 파일 생성
- name: Generate application.yml
run: |
mkdir -p ./src/main/resources
echo "${{ secrets.APPLICATION_YML }}" | base64 -d > ./src/main/resources/application.yml
# gradle 권한 부여
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
shell: bash
# 빌드 시 캐시 적용
- name: Gradle Caching
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
# 빌드
- name: Build with Gradle
run: ./gradlew build -x test
# 도커 허브 로그인
- name: Docker Hub Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# 도커 이미지 빌드 및 푸시
- name: docker image build and push
run: |
docker build -f Dockerfile -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_APP_NAME }} .
docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_APP_NAME }}
deploy:
needs: build-and-push-image
runs-on: ubuntu-latest
steps:
# 서버 백그라운드 실행
- name: pull image and run container
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_KEY }}
port: ${{ secrets.EC2_PORT }}
script: |
cd compose
docker rm -f $(docker ps -qa)
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_APP_NAME }}
docker-compose up -d
docker system prune -f
기존 cd.yml과 나머지 부분은 동일하고 deploy 단계만 다르다.
deploy:
needs: build-and-push-image
runs-on: ubuntu-latest
steps:
# 서버 백그라운드 실행
- name: pull image and run container
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_KEY }}
port: ${{ secrets.EC2_PORT }}
script: |
docker rm -f $(docker ps -qa)
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_APP_NAME }}
docker-compose up -d
docker system prune -f
docker compose로 한 번에 띄우니 스크립트 명령어가 확연히 짧아졌다.
script: |
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_APP_NAME }}
docker stop ${{ secrets.DOCKERHUB_APP_NAME }}
docker rm ${{ secrets.DOCKERHUB_APP_NAME }}
docker run -d \
-p 8080:8080 \
-e TZ=Asia/Seoul \
--name ${{ secrets.DOCKERHUB_APP_NAME }} \
${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_APP_NAME }}
Docker-compose 설치
docker가 설치되어 있던 EC2 서버에 추가로 docker-compose를 설치해 준다.
#docker-compose 설치 명령어
sudo curl -L "https://github.com/docker/compose/releases/download/v2.7.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
더보기
도커 설치 명령어
# 패키지 업데이트
sudo apt-get update -y
# 기존에 있던 도커 삭제
sudo apt-get remove docker docker-engine docker.io -y
# 도커 설치
sudo apt-get install docker.io -y
# docker 서비스 실행
sudo service docker start
# /var/run/docker.sock 파일의 권한을 666으로 변경하여 그룹 내 다른 사용자도 접근 가능하게 변경
sudo chmod 666 /var/run/docker.sock
# ubuntu 유저를 docker 그룹에 추가
sudo usermod -a -G docker ubuntu
Docker-compose.yml 작성
서버 내에 docker-compose.yml 파일을 생성하고 아래와 같이 적어준다.
version: "3"
services:
redis:
image: redis
container_name: {설정할 컨테이너 이름}
ports:
- "6379:6379"
restart: always
volumes:
- redis_data:/data
app:
image: {dockerhub 사용자명}/{dockerhub repo명}
container_name: {설정할 컨테이너 이름}
ports:
- "8080:8080"
environment:
- TZ=Asia/Seoul
restart: always
depends_on:
- redis
volumes:
redis_data:
redis는 도커에서 제공하는 컨테이너로, volumes를 사용해 컨테이너가 재시작되었을 때 기존에 저장된 값을 불러온다.
app은 dockerhub에 올린 애플리케이션 jar 파일 컨테이너로, redis 실행 후에 실행된다.
트러블 슈팅
문제: Ubuntu 서버 인증 실패
github actions 배포 프로세스 수행 시 아래와 같은 오류가 발생하였다.
Connection closed by authenticating user ubuntu ...[preauth]
해결
비밀번호 인증 방식을 EC2에서 제공하는 key 인증 방식으로 변경하였다.
- name: pull image and run container
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
#password: ${{ secrets.EC2_PASSWORD }}
key: ${{ secrets.EC2_KEY }}
port: ${{ secrets.EC2_PORT }}
1. 키 파일 확인하기
키 페어 저장 경로에 cat 키페어파일이름.pem을 입력하면 키가 보이는데 그대로 복사하면 된다.
이때 주의할 게 % 전까지 복사해야 한다. (--BEGIN KEY-- ~ --END KEY--)
2. EC2에 키 페어 등록하기
vi .ssh/authorized_keys 명령어로 키 권한 파일을 수정한다.
하단에 키 파일을 붙여넣어준다.
3. github actions secret에 추가하기
문제: Redis 해킹
Redis에 저장된 데이터를 조회해 보니 기존에 저장됐던 값들 대신 알 수 없는 값들이 들어있었다.
설마 해서 검색해 보니 해커가 악의적으로 코인을 채굴할 때 아래처럼 보인다고 한다.
해결
찾아보니 포트를 6379가 아닌 다른 포트로 접근하는 방법도 있었지만, Redis에 비밀번호를 부여하는 게 보안성이 더 높아 보였다.
Redis 설정 파일을 수정하여 비밀번호로 접속할 수 있도록 하였다.
@Value("${spring.data.redis.password:}")
private String password;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(host);
config.setPort(port);
if (!password.isEmpty()) {
config.setPassword(password);
}
return new LettuceConnectionFactory(config);
}
아래처럼 redis-cli에 명령어를 입력하려고 하면 인증이 필요하다는 문구가 나온다.
비밀번호를 입력하고 나서야 명령어를 수행할 수 있다.