순간을 성실히, 화려함보단 꾸준함을

[토이 프로젝트] 마무리 본문

나의 개발 메모장

[토이 프로젝트] 마무리

폭발토끼 2024. 5. 4. 22:07

안녕하세요.

오늘은 그동안 진행했었던 토이 프로젝트를 정리하면서 어떻게 배포했는지에 대해 적어보려고 합니다.

 

docker-compose 를 활용한 개발환경 편리하게 세팅하기

가장 처음으로 어떻게 개발환경을 좀 더 편리하게 세팅할 수 있을지에 대해 고민하였습니다. docker-compose 를 활용해서 명령어 하나로만 입력하면 따로 세팅해줄 필요없이 해주었는데 이 방법은 로컬 컴퓨터에 반드시 docker desktop 이 설치되어 있어야 한다는 단점이 존재하는 것 같습니다(mac, windows 기준)

그러나 이런 단점때문에 포기하기에는 너무나도 편리하여 결국 docker-compose 를 활용하기로 의견이 모아졌었습니다.

//Dockerfile
# Use the official OpenJDK base image
FROM openjdk:11-jdk

# Copy the jar file into the image
COPY ./build/libs/diray-0.0.1-SNAPSHOT.jar app.jar

# Set the entry point to start the application
ENTRYPOINT ["java","-jar","/app.jar"]
version: '3'
services:
  mysql:
    image: mysql:8.0
    container_name: mysql_db
    ports:
      - "3306:3306" # HOST:CONTAINER
    environment:
      MYSQL_DATABASE: diarydb
      MYSQL_ROOT_HOST: '%'
      MYSQL_ROOT_PASSWORD: admin
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    volumes:
      - D:/mysql/data:/var/lib/mysql
    networks:
      - app_network

  redis:
    image: redis
    container_name: redis
    hostname: redis
    ports:
      - "6379:6379"
    networks:
      - app_network

#  backend:
#    build:
#      dockerfile: Dockerfile
#      context: ./
#    container_name: app_backend
#    environment:
#      SPRING_DATASOURCE_URL: jdbc:mysql://mysql_db:3306/diarydb?useSSL=false&allowPublicKeyRetrieval=true
#      SPRING_DATASOURCE_USERNAME: root
#      SPRING_DATASOURCE_PASSWORD: admin
#    depends_on:
#      - mysql
#    ports:
#      - "8080:8080"
#    volumes:
#      - ./:/app
#    networks:
#      - app_network

networks:
  app_network:


#  docker-compose up -d : 실행 명령어
#  docker ps 명령어로 mysql 과 app.jar 가 실행 중 인지 확인
#  docker-compose logs 를 통해 로그를 확인할 수 있다

# docker-compose 명령어
#docker-compose up : 컨테이너를 생성하고 실행
#docker-compose down : 컨테이너와 네트워크를 종료하고 삭제
#docker-compose stop : 컨테이너를 종료

 

각각 Dockerfile 과 docker-compose.yml 입니다.

Dockerfile 이 하는 역할은 도커 이미지를 생성하기 위한 파일이고, 실제 로컬환경에서 환경세팅을 구축하는 부분은 docker-compose.yml 입니다.

 

해당 내용은 docker container 로 mysql database 와 redis cache memory 를 띄우겠다는 뜻입니다.

docker-compose up -d

 

위 명령어를 터미널에서 실행하면 해당 컨테이너들이 실행이 됩니다.

 

redis cache 는 어떻게 테스트 해야되는 걸까요?

테스트 코드를 작성하다 보니 redis cache 를 사용하는 기능들은 대체 어떻게 테스트를 해야되는지 고민이 있었습니다.

서치를 하다보니 여러가지 방법들이 있더라구요.

 

대표적인 방법 중 하나는 [Redis] SpringBoot Data Redis 로컬/통합 테스트 환경 구축하기 (tistory.com) 정말 유명하신 블로거님이 적어주신 embedded-redis 를 사용하는 방법이 있습니다.

 

그러나 저는 embedded-redis 방법은 can not start for mac, · Issue #127 · kstyrc/embedded-redis (github.com) Mac M1 에서 이슈가 있었다는 점과 해당 방법을 통해 테스트 해보려고 했는데 설정이 뭔가 잘못되었는지 잘 안되더라구요.

그래서 TestContainer 를 사용하기로 하였습니다.

 

package share_diary.diray.apiTest;

import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;

@ActiveProfiles("test")
@Configuration
public class RedisTestContainers {

    private static final String REDIS_DOCKER_IMAGE = "redis:5.0.3-alpine";
    private static final int REDIS_PORT = 6379;

    static {    // (1)
        GenericContainer<?> REDIS_CONTAINER =
                new GenericContainer<>(DockerImageName.parse(REDIS_DOCKER_IMAGE))
                        .withExposedPorts(REDIS_PORT)
                        .withReuse(true);

        REDIS_CONTAINER.start();    // (2)

        // (3)
        System.setProperty("spring.redis.host", REDIS_CONTAINER.getHost());
        System.setProperty("spring.redis.port", REDIS_CONTAINER.getMappedPort(REDIS_PORT).toString());
    }
}

 

 

 

스프링 Redis 테스트 환경 구축하기 (Embedded Redis, TestContainer) — devoong2 (tistory.com)

 

스프링 Redis 테스트 환경 구축하기 (Embedded Redis, TestContainer)

예제 및 테스트 코드는 github 에서 확인 가능합니다. Spring 에서 Redis를 테스트 하는 방법 이번엔 Spring 에서 Redis를 테스트 하는 방법에 대해 알아보려 합니다. Embedded Redis Test-Containers Redis를 테스트

devoong2.tistory.com

해당 블로그 글을 참고하여 작성했습니다.

 

 

위와 같이 docker desktop 에 testcontainer 가 실행되는 것을 확인할 수 있습니다.

이렇게 redis cache 를 테스트 할 수 있었습니다.

 

Github Action 자동배포

개발까지 해보았으니 직접 배포까지 해봐야겠다는 목표로 시작했었습니다. AWS EC2 에 배포를 직접해보았습니다. ㅎㅎ

처음에는 프로젝트를 build 하여 jar 파일을 수동으로 EC2 에 옮겼습니다.

 

scp 명령어를 사용했습니다. scp 명령어에 대해서는 [리눅스] scp 명령어 사용법, 이 글 하나면 충분해 (tistory.com) 

 

[리눅스] scp 명령어 사용법, 이 글 하나면 충분해

SCP 란? Secure Copy의 약자로 ssh 프로토콜을 기반으로 파일이나 디렉토리를 전송하거나 가져올 때 사용합니다. 당연히 네트워크 통신이 가능한 환경에서 22번 Port와 Identify File을 이용해 파일을 안전

lifegoesonme.tistory.com

이 블로그를 참고해주세요! 알아두시면 유용하게 잘 사용하실 수 있으실 겁니다.

scp [옵션] [파일명] [원격지_id]@[원격지_ip]:[받는 위치]

ex)scp /d/diary/itsdiary-0.0.1-SNAPSHOT.jar ec2-user@43.xxx.xx.xx:/home/diary/diary

이렇게 직접 AWS EC2 에 jar 파일을 전송할 수 있습니다.

 

그러나 위와 같은 방법으로 한다면 코드가 변경이 발생할때 매번 수동으로 배포를 할 수는 없을겁니다. 편리하게 코드 변경만 하고 깃 브랜치에 머지를 하고 나면 자동으로 배포되게끔 어떻게 할까요?

 

대표적인 방법으로는 젠킨슨을 사용하는 방법이 있습니다. 그러나 젠킨슨을 사용하려면 EC2 서버에 젠킨슨을 또 설치하고 환경설정을 하고 그게 아니라면 도커 컨테이너로 설정을 해주어야 하는데 해당 과정이 또 테스크로 잡히게 되니깐 선뜻 손이 가질 않더라구요.

 

제가 선택한 방법은 Github Action 을 사용하는 방법입니다.

https://lucas-owner.tistory.com/49

 

[Docker] GitHub Actions - Docker Image 자동 빌드, push(CI) (1)

GitHub Actions 를 사용한 Docker Image 자동 빌드, push - Info Docker를 사용하여 EC2에 Spring Boot를 배포하는 방법들을 포스팅해왔다, 실제로 해봤다면 알겠지만 해당 작업은 상당히 복잡한 프로세스를 갖고

lucas-owner.tistory.com

해당 블로그를 참고하였습니다.

 

github setting 은 제가 레포지토리의 관리자가 아니라서 해당 사항을 볼 수는 없는데 아래 스크립트에서 키값으로 정의한 부분들을 github settings > Secretes and variables > Actions 에 정의해주면 됩니다.

 

name: share-diary

on:
  push:
    branches: [ "master" ]

jobs:
  deploy: 
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v3
    - name: Grant execute permission for gradlew
      run: chmod +x ./gradlew
    
    # Spring Boot 어플리케이션 Build (1)
    - name: Spring Boot Build
      run: ./gradlew clean build
    
    # Docker 이미지 Build (2)
    - name: docker image build
      run: docker build -t shareddiary/master .
    
    # DockerHub Login (3)
    - name: docker login 
      uses: docker/login-action@v2
      with: 
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}

    # Docker Hub push (4)
    - name: docker Hub push
      run: docker push shareddiary/master
      
    # GET GitHub IP (5)
    - name: get GitHub IP 
      id: ip
      uses: haythem/public-ip@v1.2
      
    # Configure AWS Credentials (6) - AWS 접근 권한 취득(IAM)
    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with: 
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-2
    
    # Add github ip to AWS (7)
    - name: Add GitHub IP to AWS
      run: |
        aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
    
    # AWS EC2 Server Connect & Docker 명령어 실행 (8)
    - name: AWS EC2 Connection
      uses: appleboy/ssh-action@v0.1.6
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ec2-user
        password: ${{ secrets.EC2_PASSWORD }} 
        port: ${{ secrets.EC2_SSH_PORT }}
        timeout: 60s
        script: |
          sudo docker stop master
          sudo docker rm master
          sudo docker rmi shareddiary/master
          sudo docker run -it -d -p 8080:8080 --name master --network compose_app_network -e TZ=Asia/Seoul shareddiary/master
    
    # REMOVE Github IP FROM security group (9)
    - name: Remove IP FROM security group
      run: |
        aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32

 

대략적인 flow 는 아래와 같습니다.

1. 프로젝트를 build 해줍니다.(이때 build 하기 전에 권한을 줘야지만 정상적으로 build 가 되더라구요. 그래서 chmod +x 옵션으로 권한을 부여해주었습니다)

2. docker hub 에 로그인 하여 image 를 등록해줍니다.

3. github ip 에 대해서 보안그룹을 추가

- github action 에서 자제적인 서버에서 aws ec2 에 접근을 해주어야 하는데 이때 보안그룹이 추가가 되어있지 않다면 아에 접근조차 불가하기 때문에 보안그룹에 github ip 를 추가해주고 작업이 끝나면 삭제시켜 주는 겁니다.

4. 만약 실행중인 docker container 가 있다면 중단시켜주고 image 도 함께 삭제시켜 줍니다. 그리고 docker hub 에서 방금 push 한 image 를 새롭게 받아와 실행시켜 줍니다.

 

그러면 소스를 변경하고 github main branch 로 merge 시켜주면 위의 script 가 실행되면서 자동으로 배포가 진행됩니다.

 

Domain 과 SSL 인증서 입히기

자동배포까지 끝냈으니 실제 사용자에게 서비스를 제공해주기 위한 작업을 끝맺음 해야겠죠?

http 로 배포를 할 수도 있지만, frontend 소스는 vercel 로 배포를 해주기 때문에 자동으로 https 로 배포가 됩니다. 그래서 backend 에서도 https 로 배포를 하기 위해 ssl 인증서를 입혀야 하는데요.

해당 과정을 어떻게 진행했는지 설명해드리도록 하겠습니다.

 

EC2 HTTPS로 연결하기 (1) - 도메인 구매하고 ACM 인증서 발급하기 :: 마음대로 개발 LIFE (tistory.com)

 

EC2 HTTPS로 연결하기 (1) - 도메인 구매하고 ACM 인증서 발급하기

> 1편) 도메인 구매하고 ACM 인증서 발급하기 도메인 인증 --> ACM 인증서 발급 --> Target Group 생성 --> Load Balancer 생성 --> 규칙 수정 --> Health check 성공) 2편) 로드 밸런서 사용하고 Health check 통과하기 (

woojin.tistory.com

먼저 위 블로그를 참고하였음을 알려드립니다.

 

1. 가비아에서 도메인 구입

백엔드 domain 을 구입하였습니다. 가비에서는 처음 결제를 하면 1년기간을 100원대에 구입할 수 있습니다.

 

2. Route53 도메인 등록

Route53 에 해당 도메인을 입려해 준뒤 블로그에 설명한대로 레코드를 추가해줍니다.

 

3. AWS ACM 을 통해 SSL 인증서 발급받기

SSL 인증서를 발급받기 위해서 ACM 에서 인증서를 발급 받았습니다. 이때 구매한 도메인명을 기준으로 SSL 인증서가 생성되기 때문에 도메인명을 입력하여 SSL 인증서를 발급해주시면 됩니다.

 

4. ALB(Application Load Balancer) 를 이용하여 https 요청으로 통합하기

타겟그룹을 생성한 후 (여기서는 EC2 1개만 해당되겠죠?) 로드밸런서에 등록을 해줍니다.

로드 밸런서는 2개의 리스너를 가지게 됩니다(http,https)

그리고 http 로 요청이 온다면 https 로 리다이렉트 하게끔 설정을 해주었습니다.

그럼 실제로 http 요청을 보냈을때 https 로 리다이렉트 하는지 확인해볼까요?

테스트는 restDocs 로 개발했던 API 문서 페이지로 하겠습니다.

위와 같이 요청을 보내면?

https 로 정상적으로 리다이렉트 된 것을 확인하실 수 있습니다.

 

5. 웹서비스 정상 동작 확인

vercel 에 적용한 도메인에 정상적으로 프론트소스가 배포된 것을 확인할 수 있습니다.

그럼 API 또한 정상적으로 요청이 들어가는지 사용자 테스트를 통해 확인해보겠습니다.

네트워크 창을 통해서 정상적인 요청이 들어간 것을 확인 할 수 있습니다.

 

직접 서버에 들어가서 로그도 확인해볼까요?

master 라는 jar container 가 정상적으로 동작하고 있는 것을 확인할 수 있고

docker logs [컨테이너명]

위 명령어를 입력하면

위와 같이 로그에 잘 남아있는 것을 확인할 수 있습니다.

 

마무리

정말 길었습니다. 기획/개발/배포까지 전부 해볼 수 있었던 너무나도 값진 경험이었고 열심히 달려왔던 것 같습니다.

현재시점 기준으로 프론트개발자분과 백엔드 개발자분들이 전부 운영까지는 뜻이 없어서 프로젝트를 여기서 마무리할 계획입니다.

배포까지 해주었던 모든 것들을 전부 원상태로 돌려야겠죠 ㅎㅎㅎ

 

비록 운영을 못해봐서 아쉬움이 많이 남지만 다음 프로젝트에서는 운영까지 해보면서 어떤 이슈들을 만나고 해결할지 기대가 됩니다.

 

별거 아닌 글이지만 읽어주셔서 너무 감사드립니다!