요구사항
- 도커 컨테이너(Spring 프로젝트 2개)
- MongoDB(MongoDB Atlas)
- MySQL(RDS)
- Redis(Elastic Cache)
위와 같이 스프링 프로젝트를 컨테이너화 시킨것을 CI/CD 구축할것이다.
먼저 위와 같이 많은 리소스들의 파이프라인 관리와 배포를 위해서 많은 도구들이 제공되어 있다.
ECS, ECR, Jenkins 등등... 아직 나는 그 정도까지는 못할것 같고, 비교적 간단하게(?) 도커, EC2, Nginx, Github Actions를 이용해 보려고한다.
먼저 배포를 위해서는 docker 파일에 대한 정보를 알려주기 위해 Dockerfile을 직접 작성하고, push하고, container Image에 build하고, registry에 등록 하는 방식은 정말 벌써부터 하기가 싫다... -> 이를 자동화 시켜준 도구가 바로 Jib 이다!
Jib
구글에서 만든 Java Image Container Service로, Java에 최적화된 방식으로 이미지를 만들어주고, 위와 같이 복잡한 flow를 Layer Caching으로 세분화 시켜서 이를 자동화, 변경사항만 다시 빌드시켜준다.
이 명령어 하나면 모든 작업이 끝난다!!!
./gradlew jib
프로젝트 세팅
기존 작업했던 프로젝트를 들고 오고 Dockerfile을 세팅해주자.
# jdk 17사용
FROM openjdk:17
# 컨테이너속 저장 위치
WORKDIR /app
# 빌드된 JAR 파일 경로, 복사할 위치
COPY ./build/libs/helloworld-mvc-0.0.1-SNAPSHOT.jar /app/helloworld-mvc.jar
EXPOSE 8080
# 복사한 JAR 파일을 실행하도록 설정
CMD ["java", "-jar", "helloworld-mvc.jar"]
# jdk 17사용
FROM openjdk:17
# 컨테이너속 저장 위치
WORKDIR /app
# 빌드된 JAR 파일 경로, 복사할 위치
COPY ./build/libs/helloworld-webflux-0.0.1-SNAPSHOT.jar /app/helloworld-webflux.jar
EXPOSE 8082
# 복사한 JAR 파일을 실행하도록 설정
CMD ["java", "-jar", "helloworld-webflux.jar"]
- docker 이미지를 기반으로 CI/CD를 진행하기 위해 이미지를 만들어줄 Docker Hub 계정이 필요하다.
여기서 로그인/회원가입 진행하면된다.
VPC, EC2, RDS, ElasticCache, MongoDB Atlas
2024.08.15 - [DevOps/AWS] - [AWS] Amazon 네트워크 운영하기
2024.08.15 - [DevOps/AWS] - [AWS] Amazon EC2 인스턴스 만들기
2024.08.17 - [DevOps/AWS] - [AWS] Amazon RDS 사용해보기
이미 만드는 방법에 대한 포스팅을 진행했기에 위 글들을 보고 오면 좋을것같다.
ElasticCache, MongoDB Atlas 사용법은 구글링을 통해 자세히 알아보길 추천한다(본인은 아직 잘 못하기에)
Github Action 스크립트 파일생성
** 꼭 root 디렉토리에 .github 이름의 폴더에 작성해줘야한다.
name: Hello-World Dev CI/CD
on:
pull_request:
types: [ closed ]
workflow_dispatch: # (2).수동 실행도 가능하도록
jobs:
build:
runs-on: ubuntu-latest # (3).OS환경
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop'
steps:
- name: Checkout
uses: actions/checkout@v2 # (4).코드 check out
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17 # (5).자바 설치
distribution: 'adopt'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
shell: bash # (6).권한 부여
- name: Build with Gradle
run: ./gradlew clean build -x test
shell: bash # (7).build시작
- name: Set Version
id: set_current_date
run: echo "version=$(date '+%Y-%m-%d-%H-%M-%S')" >> $GITHUB_OUTPUT
- name: Docker Image Build
run: |
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/helloworld-mvc:${{ steps.set_current_date.outputs.version }} .
docker tag ${{ secrets.DOCKERHUB_USERNAME }}/helloworld-mvc:${{ steps.set_current_date.outputs.version }} ${{ secrets.DOCKERHUB_USERNAME }}/helloworld-mvc:latest
- name: Docker Login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Docker Hub Push
run: |
docker push ${{ secrets.DOCKERHUB_USERNAME }}/helloworld-mvc:${{ steps.set_current_date.outputs.version }}
docker push ${{ secrets.DOCKERHUB_USERNAME }}/helloworld-mvc:latest
#ssh 접속 후 docker-compose
- name: Deploy in EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.SSH_KEY }}
script: |
cd app
sudo docker rm -f $(docker ps -qa)
sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/helloworld-mvc
docker-compose up -d
sudo docker logs -f helloworld-mvc &> helloworld-mvc.log &
docker image prune -f
실행흐름도
- develop 브랜치로 checkout
- 자바 설치
- gradle 실행 권한 부여
- .jar파일 생성
- .jar파일 docker image로 빌드
- docker-compose up -d 명령어로 도커 컨테이너 빌드
docker-compose.yml
services:
helloworld-mvc:
image: swlee1999/helloworld-mvc:latest
container_name: helloworld-mvc
ports:
- "8080:8080"
environment:
- 환경 변수 설정
helloworld-webflux:
image: swlee1999/helloworld-webflux:latest
container_name: helloworld-webflux
ports:
- "8082:8082"
environment:
- 환경 변수 설정
나의 경우 EC2 인스턴스 내부에 파일을 직접 작성해주었다.
문제상황
- develop 브랜치가 merge 되었을때 CI/CD가 작동되게 한줄 알았으나, 터미널에서 원격(ssh)접속을 할때 무한 로딩되는 에러가 발생했다. 문제 원인을 살펴보니, AWS EC2모니터링에서 메모리 부족이슈로 인스턴스를 t3.micro -> t3.small 로 스펙업을 해주니 해결되었다.
- 현재 진행하고있는 사이드 프로젝트에 구글 로그인 구현 부분을 맡았는데, 구글 API에 redirect_url을 설정해줘야한다. 이 부분에서 구글은 비교적 최근부터 HTTPS즉, SSL(Secure Socket Layer)을 적용한 도메인을 배포된 url로 허용해준다.
** SSL : 웹 통신을 할때 적용하는 보안프로토콜
클라이언트 <-> 서버 보안이 향상된 통신 가능
도메인 등록
HTTPS를 적용하기 위해서는 우선 도메인을 먼저 등록해줘야한다.
이 과정 역시 이전 포스팅에서 방법을 다뤘으므로 찾아가서 보길 바란다.
2024.09.04 - [DevOps/AWS] - [AWS] Route 53으로 도메인 서비스 구축하기
SSL 인증서 발급
- 발급 방법
- EC2에 직접 인증서를 설치
- AWS의 인증서 관리서비스인 ACM(AWS Certificate Manager) 사용
나는 AWS 클라우드 서비스를 사용하는김에 2번 방법을 선택해서 진행했다.
> AWS Certificate Manager -> 인증서 -> 인증서 요청 클릭
> 퍼블릭 인증서 요청
> 완전히 정규화된 도메인 이름 : 가비아에서 구매한 도메인 이름(와일드 카드 인증서 사용가능)
> 검증 방법 : DNS 검증
**와일드 카드 인증서 : HTTPS 프로토콜을 사용하여 안전하게 통신할 수 있도록 하는 디지털 인증서, 특정 도메인 이름의 하위 도메인에 얼마든지 사용할 수 있다.
ex. 도메인 이름 : naver.com
www.naver.com, hello.naver.com, bye.naver.com ... 등등 *.naver.com 즉 인증서 관리 최소화 및 비용 절감의 효과를 가져온다!
> 인증서 발급 확인
> CNAME 레코드 (도메인 이름 -> 다른 도메인 이름에 매핑) 확인
> 인증서 <-> Route53 연결
> Route53에서 레코드 생성 클릭
로드 밸런서 생성
- AWS에서 HTTPS, 즉 SSL을 적용하기 위해선 서버 인스턴스가 하나더라도 ELB(로드 밸런서)를 적용해야한다.
로드 밸런서란?
들어오는 트래픽을 여러 타깃에 분산하고, 애플리케이션의 가용성, 확장성, 보안, 성능을 개선하며, 전송 중인 민감한 데이터를 HTTPS로 암호화, 트래픽 관리 및 모니터링을 위한 고급 기능을 제공하는 역할
> EC2 -> 로드 밸런서 -> Application Load Balancer 선택 (https 적용을 원하기 때문)
> 로드 밸런서 이름 설정
> 인터넷 경계 체계 선택
> IPv4 선택
> EC2가 있는 VPC 선택
> EC2가 있는 서브넷 하나 이상 선택
> 기존 EC2에 설정한 보안그룹 설정
> 보안그룹 인바운드 규칙 : HTTP(80 포트), HTTPS(443 포트) 추가
> 리스너 및 라우팅 2개 등록 (HTTP, HTTPS)
> HTTPS의 경우 인증된 ACM을 등록해주자.
> 80, 443 포트에서 들어오는 트래픽을 수신 대기하고 등록된 대상으로 전달하여 처리
대상 그룹을 만들어줘야한다!! 만들러가자
대상 그룹 생성
> EC2 -> 대상 그룹 -> 대상 그룹 생성
> 대상 유형 = 인스턴스 선택
> 대상 그룹 이름 입력
> 프로토콜 = HTTP, 포트 = 80 -> EC2 인스턴스가 80포트 이용하기 때문
> 로드 밸런서를 적용할 인스턴스 선택
> 대상 그룹 생성
로드 밸런서 생성 (다시 돌아와서)
위에서 하다 만 로드 밸런서 생성과정중 생성한 대상 그룹을 설정해주자.
> 최종적으로 로드 밸런서 생성!
HTTP -> HTTPS 리다이렉션
> 리스너 -> HTTP:80 편집
> 도메인 80포트로 들어왔을때 조건 추가
> HTTPS(443포트)로 URI 영구 리다이렉션
- 이제 HTTP로 접속해도 자동으로 SSL 접속이 된다!
Route53 설정
- 만든 도메인으로 접속하면 바로 EC2로 접속하는게 아닌, 로드 밸런서로 접근하게 해줘야한다. Route53에서 설정해주자.
> A레코드 선택
> 트래픽 라우팅 대상
> 리전 선택, 라우팅할 로드 밸런서 선택
** 맨앞에 내가 생성한 기억이 없는 dualstack이라는것이 붙어있는데 주소 체계를 IPv4, IPv6 모두 사용하는 경우에 해당하는 것이라고 하니 나 같이 IPv4 주소에 대해서 설정해줄때는 앞에 dualstack을 빼주자
EC2에 NGINX 설치
NGINX란?
- 대표적인 웹 서버로, Apache보다 높은 성능과 안정성이 특징인 웹 서버이다.
- 스프링에 띄운 서버 2개에 접근하기 위한 리버스 프록시 용도로 사용했다.
nginx에 대해서는 내용이 많으므로 다른 글에 따로 정리할 예정이다. 우선 리버스 프록시를 위해서 사용한다는 정도만 알고 있자.
sudo ssh -i <pem 키 파일 위치> ubuntu@<퍼블릭IP>
> bash에서 ssh 명령어로 EC2에 원격접속
sudo apt update
sudo apt-get install nginx
> nginx 설치
sudo service nginx start
> 설치 확인
cd /etc/nginx/sites-available/default
> nginx 설정파일 경로 이동
sudo vi default
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name gotoend.store;
# HTTP 요청 받을시 Header에 _(언더바) 인식
underscores_in_headers on;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
location /mvc {
# nginx에서 spring으로 요청할때 /mvc 제외하고 uri 요청
rewrite ^/mvc(/.*)$ $1 break;
# swagger 요청 prefix
proxy_set_header X-Forwarded-Prefix /mvc;
proxy_pass http://localhost:8080;
# CORS 관련 헤더 추가
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; # 요청 클라이언트 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 클라이언트의 IP와 거쳐온 프록시 서버들의 IP들
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass_request_headers on; # 클라이언트의 모든 요청 헤더를 백엔드로 전달
}
location /webflux {
# nginx에서 spring으로 요청할때 /webflux 제외하고 uri 요청
rewrite ^/webflux(/.*)$ $1 break;
# swagger 요청 prefix
proxy_set_header X-Forwarded-Prefix /webflux;
proxy_pass http://localhost:8082;
# CORS 관련 헤더 추가
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass_request_headers on; # 클라이언트의 모든 요청 헤더를 백엔드로 전달
}
}
> defalut 파일에 리버스 프록시 구성 설정
sudo nginx -t # 설정 파일 테스트
sudo systemctl restart nginx # Nginx 재시작
> nginx 설정 파일 적용 테스트
forward-headers-strategy: FRAMEWORK
> 스프링 .yml 파일 수정
- X- : 비표준 헤더를 의미
- 스프링에서 디폴트 설정(none)은 표준헤더값만 가져올수있다.
최초 Client의 IP로 부터 header 정보를 함께 가져오는데
1. Nginx에서 remoteAddr로 최초 IP를 가져온다.
2. Nginx에서 설정한 proxy_set_header는 비표준 헤더이므로 스프링에 .yml 파일에 framework 설정을 추가해준다.
3. X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Prefix 등등 표준 헤더 외에 비표준 헤더를 가져올 수 있다.
도커 + EC2 + Nginx +Github Actions를 이용한 다중 컨테이너 배포하기 완료!!
클라이언트 접속 플로우
- Route53에 있는 도메인 주소
- ELB로 이동
- ELB (80포트)로 이동
- ELB(443포트)로 이동
- EC2(80포트)로 이동
- Nginx가 리버스 프록시 역할
- /mvc(8080 포트) 로 이동
- /webflux(8082 포트) 로 이동
'DevOps > AWS' 카테고리의 다른 글
[AWS] AWS 리소스 삭제하기! (0) | 2024.09.04 |
---|---|
[AWS] Route 53으로 도메인 서비스 구축하기 (0) | 2024.09.04 |
[AWS] EC2에 스프링 프로젝트 띄우기 (0) | 2024.08.18 |
[AWS] Amazon RDS 사용해보기 (0) | 2024.08.17 |
[AWS] Amazon EC2 원격 로그인 (0) | 2024.08.16 |