스프링 서버를 jar파일로 만든 뒤에 EB에 푸쉬하는 CI/CD의 과정을 알고 있었기 때문에 이번에는 도커를 이용해서 만들고 싶었습니다.
도커를 공부하면서 docker run
이나 docker-compose
명령어로 설정한 환경으로 서버를 활성화 하는 과정이 신기해서 docker-compose up
을 이용해서 프론트 + DB + 서버를 한번에 실행했습니다.
결론은 단점이 더 많은 시도였습니다.
모률을 분리하는 이유를 여기서 또 알게 되네요..
서론은 이만하고 지금까지 시도해봤던 방법들을 기록하겠습니다.
처음 시도한 방법입니다.
name: my-app
on:
push:
branches:
- dev
jobs:
image-build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: create env file
run: |
touch .env
echo "HS512_SECRET=${{ secrets.HS512_SECRET }}" >> .env
shell: bash
- name: create remote directory
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
script: |
sudo mkdir -p /home/ubuntu/srv/ubuntu
sudo chown -R ubuntu:ubuntu /home/ubuntu/srv/ubuntu
- name: copy source via ssh key
uses: burnett01/rsync-deployments@4.1
with:
switches: -avzr --delete -a --include=".*"
remote_path: /home/ubuntu/srv/ubuntu/ /var/lib/docker
remote_host: ${{ secrets.HOST }}
remote_user: ubuntu
remote_key: ${{ secrets.KEY }}
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@master
env:
HS512_SECRET: ${{ secrets.HS512_SECRET }}
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
envs: HS512_SECRET
script: |
sudo sh /home/ubuntu/srv/ubuntu/deploy.sh
jobs
을 여러개 둬서 병렬로 실행되게 할 수 있습니다.
runs-on
에는 작업이 실행될 환경을 지정합니다. 여기서는 깃헙액션이 제공해주는 도커를 이용합니다.
sudo
로 권한을 변경하면 소유자가 root
가 되므로 다시 ubuntu
로 변경합니다.
rsync
를 이용하면 로컬 프로젝트 전체를 복사합니다.
avzr
복사 옵션에서 -a
로 숨겨진 파일까지 복사합니다.
마지막에 복사된 deploy.sh
를 실행합니다.
ec2에서 이미지를 빌드하기 위해 도커를 설치하는 스크립트를 실행합니다.
buildx가 없을수도 있을것 같아서 추가했지만 안 쓰인거 같습니다.
#!/bin/bash
if ! type docker > /dev/null
then
echo "================================ docker does not exist ==============================="
echo "Start installing docker"
sudo DEBIAN_FRONTEND=noninteractive apt-get -y update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
fi
if ! docker buildx version > /dev/null
then
echo "=============================== Docker Buildx does not exist ==========================="
echo "Start installing Docker Buildx"
BUILDX_VERSION="v0.6.3"
wget https://github.com/docker/buildx/releases/download/$BUILDX_VERSION/buildx-$BUILDX_VERSION.linux-amd64 -O docker-buildx
chmod +x docker-buildx
mkdir -p ~/.docker/cli-plugins
mv docker-buildx ~/.docker/cli-plugins/docker-buildx
fi
if ! type docker-compose > /dev/null
then
echo "============================ docker-compose does not exist ==============================="
echo "Start installing docker-compose"
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
fi
echo "============================== start docker-compose up: ubuntu =============================="
sudo COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f /home/ubuntu/srv/ubuntu/docker-compose-prod.yml up --build
gnupg-agent
는 개인키를 캐싱해줍니다.
gpg
옵션으로 도커 공식 키를 이용해서 이미지와 소프트웨어 무결성을 검증합니다.
COMPOSE_DOCKER_CLI_BUILD=1
으로 /usr/local/bin/docker-compose
를 찾게 했습니다.
위 방법으로 시도했을때 도커 이미지가 커질경우 ec2에서 빌드하는 시간이 너무 오래걸렸습니다.
그래서 다른 방법을 시도하기로 했습니다.
로컬에서 도커 이미지를 빌드한 후에 도커 허브에 올린 뒤 ec2에서 이미지를 다운받아 실행하는 순서입니다.
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: DockerHub Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME}}
password: ${{ secrets.DOCKERHUB_TOKEN}}
- name: Image Build and Push
run: |
DOCKER_BUILDKIT=1 docker-compose -f docker-compose-prod.yml build
docker tag my-app-db ${{ secrets.DOCKERHUB_USERNAME }}/my-app-db:0.0.1
docker tag my-app-server ${{ secrets.DOCKERHUB_USERNAME }}/my-app-server:0.0.1
docker push ${{ secrets.DOCKERHUB_USERNAME }}/my-app-db:0.0.1
docker push ${{ secrets.DOCKERHUB_USERNAME }}/my-app-server:0.0.1
도커허브에 로그인을 한 뒤에 docker-compose
로 빌드한 이미지를 업로드합니다.
needs:
로 이미지 업로드가 완료된 후 아래 워크플로우가 실행됩니다.
deploy-server:
needs: image-build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: DockerHub Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME}}
password: ${{ secrets.DOCKERHUB_TOKEN}}
- name: Install Docker on EC2
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.HOST2 }}
username: ubuntu
key: ${{ secrets.KEY }}
script: |
if ! apt list --upgradable 2>/dev/null | grep -q "upgradable"; then
sudo apt update -y
fi
if ! command -v docker &> /dev/null; then
sudo apt install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker ubuntu
else
echo "Docker is already installed"
fi
if ! command -v docker-compose &> /dev/null; then
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
fi
- name: EC2 docker remove
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.HOST2 }}
username: ubuntu
key: ${{ secrets.KEY }}
script: |
if [ "$(sudo docker ps -aq)" ]; then
sudo docker stop $(sudo docker ps -aq)
sudo docker rm $(sudo docker ps -aq)
fi
if [ "$(sudo docker images -aq)" ]; then
sudo docker rmi $(sudo docker images -aq)
fi
- name: Copy file to EC2
uses: appleboy/scp-action@master
with:
host: ${{ secrets.HOST2 }}
username: ubuntu
key: ${{ secrets.KEY }}
source: ./docker-compose-ec2.yml
target: /home/ubuntu/
- name: EC2 docker-compose
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.HOST2 }}
username: ubuntu
key: ${{ secrets.KEY }}
script: |
echo "HS512_SECRET=${{ secrets.HS512_SECRET }}" > .env
echo "DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME}}" >> .env
echo "TAG_VERSION=0.0.1" >> .env
sudo COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -p my-app -f docker-compose-ec2.yml up -d
EC2에 도커가 없다면 설치를 해주고 실행중인 컨테이너가 있다면 종료합니다.
appleboy/scp
를 이용해서 복사를 했습니다. 이때 기존 파일이 존재하면 덮어쓰게 됩니다.
.env
에 환경변수를 넣고 docker-compose
를 실행하면 이미지를 다운 받고 실행합니다.
실행되는 docker-compose.yml
입니다.
image
에 다운받을 도커허브의 이미지를 넣습니다.
version: '3.9'
services:
database:
image: ${DOCKERHUB_USERNAME}/my-app-db:${TAG_VERSION}
restart: always
volumes:
- mysql-volume:/var/lib/mysql
hostname: ${HS512_SECRET}
environment:
- MYSQL_USER=${HS512_SECRET}
- MYSQL_PASSWORD=${HS512_SECRET}
- MYSQL_ROOT_PASSWORD=${HS512_SECRET}
- MYSQL_DATABASE=${HS512_SECRET}
expose:
- 3306
ports:
- "3306:3306"
springboot-server:
depends_on:
- database
image: ${DOCKERHUB_USERNAME}/my-app-server:${TAG_VERSION}
restart: always
volumes:
- ./springboot-server:/build/app
expose:
- 8080
ports:
- "8080:8080"
environment:
- MYSQL_HOST=${HS512_SECRET}
- MYSQL_USER=${HS512_SECRET}
- MYSQL_PASSWORD=${HS512_SECRET}
- MYSQL_DATABASE=${HS512_SECRET}
- MYSQL_PORT=3306
- HS512_SECRET=${HS512_SECRET}
volumes:
mysql-volume:
하지만 배포했을때 워크 플로우에서 아래 문제가 발생했습니다.
ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain
더 많은 정보가 필요해 터미널에 직접 연결해서 로그를 확인했습니다.
ssh -i "F:\s-2405.pem" ubuntu@ec2-52-78-209-35.ap-northeast-2.compute.amazonaws.com
# 직접 복사
scp -i "F:\s-2405.pem" "F:\docker-compose-prod.yml" ubuntu@ec2-43-202-44-171.ap-northeast-2.compute.amazonaws.com:/home/ubuntu/
sudo COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose-prod.yml up;
다른 터미널에서도 EC2에 ssh로 접속후 로그를 확인했습니다.
tail -f /var/log/syslog
# 또는
cat /var/log/syslog
로그를 확인하니 인증이 필요하다고 나옵니다.
level=error msg="Do not continue eject after error: Error:\nRejected: Requested access to resource denied.\nRequires authentication\n"
level=infomsg="ignore returned by registry: unauthorized: authentication required"
그래서 패스워드를 토큰으로 변경해보기로 했습니다.
- name: DockerHub Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME}}
password: ${{ secrets.DOCKERHUB_TOKEN}}
그럼에도 해결되지가 않았습니다. 원인이 다른곳에 있는것 같아서 찾아보니까
https://github.com/appleboy/ssh-action#if-you-are-using-openssh 여기서 도움을 받았습니다.
그래서 EC2 인스턴스를 만들때 새로운 키를 ED25519방식으로 생성했습니다.
생성한 키를 다시 깃헙에 넣습니다.
키를 추가할 때는 키를 열었을때 나오는 데이터를 모두 복사해서 넣습니다. ( ---- 포함 )
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
// 중간 생략
404fEa8r62pos21ej+L5c1RZXAICB/M3kL9cans+JBRpAAAAAAECAwQF
-----END OPENSSH PRIVATE KEY-----
이후 다시 시도하니 워크플로우에서 ssh 가 정상적으로 수행되었습니다.
도커허브의 토큰, 패스워드는 상관없었고 ssh 인증시 RSA 인증방식의 문제였습니다.
이후 배포가 성공적으로 완료됩니다.
깃헙의 워크플로우에서 다음 단계로 넘어가지 않고 멈춰있을 경우에는 백그라운드에서 배포가 되도록 설정합니다.
# 스프링 서버
script: |
echo "HS512_SECRET=${{ secrets.HS512_SECRET }}" > .env
echo "DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME}}" >> .env
echo "TAG_VERSION=0.0.1" >> .env
sudo COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -p my-app -f docker-compose-ec2.yml up -d
# 리액트 서버
script: |
sudo nohup docker run --name my-app-react-1 -p 80:3000 ${{ secrets.DOCKERHUB_USERNAME }}/my-app-react:0.0.1 > /dev/null 2>&1 &
script: |
echo "HS512_SECRET=${{ secrets.HS512_SECRET }}" > .env
echo "DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME}}" >> .env
echo "TAG_VERSION=0.0.1" >> .env
sudo COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -p my-app -f docker-compose-ec2.yml up -d
위 코드에서 ec2에 입력한 환경변수를 확인하려면 ec2에 접속해서 .env 파일을 확인합니다.
ssh -i "F:\s-2405.pem" ubuntu@ec2-52-78-209-35.ap-northeast-2.compute.amazonaws.com
ls -a
를 입력해 숨겨진 파일을 확인합니다.
cat .env
커맨드로 환경변수를 확인합니다.
한 번 ec2에 저장된 환경변수는 ec2가 유지되고 환경변수가 변경되지 않는 한 .env
파일에서 환경변수를 가져오게 됩니다.