[Docker] Github Actions + DockerHub CI/CD 구축

merci·2023년 6월 18일
0

Docker

목록 보기
4/4


스프링 서버를 jar파일로 만든 뒤에 EB에 푸쉬하는 CI/CD의 과정을 알고 있었기 때문에 이번에는 도커를 이용해서 만들고 싶었습니다.

도커를 공부하면서 docker run 이나 docker-compose 명령어로 설정한 환경으로 서버를 활성화 하는 과정이 신기해서 docker-compose up을 이용해서 프론트 + DB + 서버를 한번에 실행했습니다.

결론은 단점이 더 많은 시도였습니다.
모률을 분리하는 이유를 여기서 또 알게 되네요..

서론은 이만하고 지금까지 시도해봤던 방법들을 기록하겠습니다.

appleboy/ssh-action 으로 배포

처음 시도한 방법입니다.

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에서 실행될 스크립트

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에서 빌드하는 시간이 너무 오래걸렸습니다.
그래서 다른 방법을 시도하기로 했습니다.


DockerHub 로 배포

로컬에서 도커 이미지를 빌드한 후에 도커 허브에 올린 뒤 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

실행되는 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 인증문제

더 많은 정보가 필요해 터미널에 직접 연결해서 로그를 확인했습니다.

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 파일에서 환경변수를 가져오게 됩니다.


profile
작은것부터

0개의 댓글