소프트웨어 마에스트로 프로젝트 배포 및 dev 서버 운영 방식에 대해 정리한 포스트이다.
팀원 한명이 이를 도맡아 하였기 때문에, 궁금한 부분은 물어가면서, 그리고 직접 확인해보면서 정리하였다.
지금은 "dev"용 서버만 다루고 있음을 명시한다.
추후 "prod" 인프라도 정리할 예정이다.
지금의 배포 상황이다.
현 배포 상황에서 깊게 다룰 부분은 두가지 이다.
Proxy역할을 해주는 CI/CD, Nginx + public/private subnet을 깊게 다루겠다.
github action으로 CI/CD 과정을 거친다.
이를 담은 workflow yml 파일을 첨부한다.
yml 을 보면서 이야기해보자.
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Java CI/CD with Gradle In Develope Branch
on:
push:
branches: [ "develope" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
- name: Remove previous snapshot.jar
run: rm -rf ./build/libs
- name: Add properties
run: echo "${{ secrets.APPLICATION_DEV }}" > ./src/main/resources/application.yml
- name: init with Gradle
uses: gradle/gradle-build-action@v2
- run: gradle init
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
gradle-version: 7.5.1
arguments: build -x test
- name: Docker build
run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKER_DEV_REPO }} .
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker push
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKER_DEV_REPO }}
- name: Deploy to dev
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST_DEV }}
username: ec2-user
key: ${{ secrets.PRIVATE_KEY }}
script: |
ssh spring sudo docker rm -f $(ssh spring sudo docker ps -qa)
ssh spring sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKER_DEV_REPO }}
ssh spring sudo docker run -dp 8080:8080 ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKER_DEV_REPO }}
- name: Notify Slack
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: '#00FF00'
SLACK_TITLE: 'Build and Deploy'
SLACK_TEXT: 'Backend repository build and deployment process has been completed successfully.'
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
현재 배포 branch 는 다음과 같다.
1. develope (dev 서버)
2. main (prod - 실 배포)
현재 포스트는 dev용 서버이므로, 위 github action workflow는
develope 브랜치에 push가 일어나면 자동으로 CI/CD의 과정을 거친다.
github action의 여러가지 스크립트 설명은 제외하고 jobs의 step 순서대로 그 과정을 설명하겠다.
우리 레포지토리 branch 코드를 가져온다.
본 프로젝트는 JAVA 17을 사용하므로 이를 맞춰준다.
처음에 이 과정이 왜 필요할까에 의문을 가졌다. 매번 새로운 VM에서 Github Actions가 작동하는게 아닐까 싶었다.
하지만 GitHub Actions는 컨테이너 기반으로 작동하기 때문에, 매번 새로운 가상 VM에서 작업하는 것이 아니다. 따라서 매 작업마다 완전히 새로운 VM을 생성하는 것이 아니라, 작업 컨테이너를 사용하여 작업을 수행한다. 그렇기에 여러 정보가 cache 되고, 빌드 파일이 남아있을 수 있다. 따라서 해당 작업을 필수로 진행한다.
Github Actions Secret 변수들을 build 전에 미리 저장한다.
우리 프로젝트는 gradle을 기반이므로 gradle을 활용하여 init 하는 과정을 거친다.
build 하고,
Docker image를 만든다.
이때, Docker Hub에 해당 image파일을 push하는 과정을 거치기 위한 과정이 필요하다.
Docker Hub에 push하기 위해서 login 하는 과정이다.
만들어진 docker image를 push 한다.
dev용 서버에 배포하는 과정이다.
AWS remote 서버 dev 서버에 배포과정을 거쳐야 하므로, ssh설정이 필요했다.
여기서 script파일이 나는 이해가 되지 않았었다.
ssh spring 이 뭐지?
script: |
ssh spring sudo docker rm -f $(ssh spring sudo docker ps -qa)
ssh spring sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKER_DEV_REPO }}
ssh spring sudo docker run -dp 8080:8080 ${{secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKER_DEV_REPO }}
ssh spring이 무엇을 의미하는지 알아보기 전에,
Nginx 구성 이유와, 설정 정보들을 알아야만 한다.
아무리 dev용 서버여도, 보안은 중요한 부분이다.
이를 위해, nginx용 컨테이너를 띄우고 있는 EC2 인스턴스를 구성하였다.
(참고)
아래 ecs-agent는 ecs optimized용 ami를 사용하였기 때문에 자동으로 띄워진 container이므로 무시해도 좋다.
위에 ~/nginx가 우리가 daemon으로 띄운 nginx용 컨테이너이다.
80으로 포트 매핑이 되어있다.
그러면
docker exec -it 컨테이너ID /bin/bash
해당 컨테이너ID로 bash로 접속해보자.
그 안에 nginx config 파일을 살펴보자.
이는 /etc/nginx 디렉토리에 존재한다.
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
upstream next {
server 10.~~;
}
upstream spring {
server 10.~~;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://next;
proxy_http_version 1.1;
}
location /api {
proxy_pass http://spring;
proxy_http_version 1.1;
}
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
spring 서버, next 서버 두가지 서버로 같은 vpc내에 존재하는 서버로 proxy역할을 한다.
기본적으로 nginx에서 여러 보안사항 및 cache역할을 담당하므로 (리버스 프록시), 실질적인 WAS서버 앞단에 이를 붙인다. 그래서 사람들이 우리 서비스를 이용하기 위해서는 인터넷 gateway와 연결되어 있는, nginx서버를 통해서 우리 서버와 통신하게끔 해야한다.
그렇기에, public subnet에 nginx용 ec2를, private subnet에 프론트, 백엔드 서버, RDS를 둔것이다.
그리고, server 블록에서는 80포트의 Nginx로 들어오는 요청 경로마다 다른 proxy를 거치도록 설정하였다. (아까 설정한 upstream 경로)
루트는 next를,
/api는 spring의 api서버를 거치게 한다.
그렇다면 private subnet에 존재하는 spring 서버에 자동배포를 하기 위해선 어떻게 해야할까?
답은 간단하다.
proxy 서버를 거치는 것이다.
서버에 deploy를 하기 위해서는, Nginx의 ec2에 접속하는 과정을 거쳐야 한다.
같은 VPC내에 존재하고, private subnet에 존재하는 서버의 인바운드 규칙이 nginx ec2와 통신이 가능케만 한다면, 문제 없을 것이다.
그렇다면 ssh spring
이 뜻하는 것은,
spring 서버를 접속하는 방식과 연관되있을 것이다.
Nginx용 ec2에 루트경로에 ssh폴더를 생성하고, config 파일을 생성하여,
다음과 같이 설정하였다.
위 private ip로 ssh접속을 ssh spring, ssh next로 할 수 있게 된다.
그리고, 해당 디렉토리 내에는 pem키의 키값들이 존재하는 것은 당연하겠다.
private ip는 그냥 찝찝해서 지웠다.
사실 public subnet에 존재하는 nginx에 접속만하면 털릴 수 있긴 한데, 결국 서버 key 값이 털리지 않게 하는 것이 중요하지 않을까?
dev서버 설정을 좀더 발전시켰습니다.
발전된 내용은 아래 포스트를 참고해주세요.
https://velog.io/@goat_hoon/%EB%B0%B0%ED%8F%AC-dev-%EC%84%9C%EB%B2%84-%EB%B3%80%EA%B2%BD-%EC%82%AC%ED%95%AD-%EC%A0%95%EB%A6%AC
많은 도움이 되었어요..!