본문 바로가기

spring

Jlaner 개발기록 10 CI/CD 무중단 배포 - 완

이번 포스팅에서는 지난 포스팅에 이어 CI를 수정하고 CD를 구성하고 무중단 배포가 되는지 확인까지 해보도록 하겠다.

우선 현재 사용하고 있는 CI를 확인하자.

name: deploy

on:
  push:
    branches:
      - 'main'

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: ./gradlew clean build -x test

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}


      - name : Build Docker Image & Push to Docker Hub
        run: |
          docker build . -t hanjihoon0315/docker-springboot
          docker build ./nginx -t hanjihoon0315/docker-nginx
          docker push hanjihoon0315/docker-springboot
          docker push hanjihoon0315/docker-nginx

 

위 코드가 필자가 지금 사용하고 있던 CI이다.

수정해야 할 부분이 몇 군데 있다. 일단 지금 필자가 사용하고 있는 docker의 이미지는 3가지이다.

springboot, redis, mysql 위 코드는 springboot와 nginx만 있다.

이 부분을 도커 허브에 있는 redis, springboot, nginx로 수정할 것이다.

큰 수정점은 없이 build와 push 부분에 추가하면 된다.

springboot는 root에 있는 dockerfile로 nginx는 ./nginx 폴더 경로로 mysql은 도커에 있는 이미지를 사용할 것이고 설정을 건드리는 일은 없을 것 같아 허브에 올라가져 있는 이미지를 사용하도록 둘 것이다. redis는 root에 있는 dockerfile.redis를 사용할 것이다.

 

CI는 이전과 크게 다를 것은 없이 추가만 해줬다.

name: deploy

on:
  push:
    branches:
      - 'main'

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: ./gradlew clean build -x test

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}


      - name : Build Docker Image & Push to Docker Hub
        run: |
          docker build . -t hanjihoon0315/docker-springboot
          docker build ./nginx -t hanjihoon0315/docker-nginx
          docker build -f dockerfile.redis -t hanjihoon0315/docker-redis .
          docker push hanjihoon0315/docker-springboot
          docker push hanjihoon0315/docker-nginx
          docker push hanjihoon0315/docker-redis

 

초반에 이런식으로 구성해서 진행했었다. 하지만 Elastic Beanstalk는 기존 배포 옵션을 제공한다. 롤링이나 추가 배치 블루/ 그린에 대한 것도 제공한다. 이는 아래에서 확인할 수 있다.

https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/using-features.CNAMESwap.html

 

Elastic Beanstalk를 사용한 블루/그린 배포 - AWS Elastic Beanstalk

이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.

docs.aws.amazon.com

EB는 앱 인프라 스트럭처의 배포, 스케일링, 관리를 자동화해 준다. 로드밸런싱 처리, Nginx 설정, 오토 스케일링, EC2 생성을 간편하게 해결할 수 있다. 처음 EB에 대해서 사용하는 이유에서 여러 구성을 간편하게 제공한다. 환경을 구성하며 위 요소들을 구성할 수 있고 제공하기 때문에 지난 포스팅에서 필자가 ec2로 접속해서 nginx 구성을 하게 되는 것도 EB가 엔진엑스를 제공하기 때문에 별도의 설치 없이 사용할 수 있던 것이다.

EB를 설정할때 로드밸런스 부분을 설정할 수 있는데 이 설정이 로드밸런싱을 담당하는 부분이다.

EB에서 제공하기 때문에 별도의 엔진엑스 설치 없이 사용할 수 있던 것이다.

그러므로 엔진엑스를 따로 컨테이너를 생성한다던가 이미지를 생성하지 않고 CI/CD를 구성해 보았다.

docker-compose 파일은 nginx 없이 구성해서 그대로이고 이전에 CI로 구성했던 gitacion 파일만 새로 손을 보았다.

name: deploy

on:
  push:
    branches:
      - 'main'

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: ./gradlew clean build -x test

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}


      - name : Build Docker Image & Push to Docker Hub
        run: |
          docker build . -t hanjihoon0315/docker-springboot
          docker build -f Dockerfile.redis -t hanjihoon0315/docker-redis .
          docker push hanjihoon0315/docker-springboot
          docker push hanjihoon0315/docker-redis
          
      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
         format: YYYY-MM-DDTHH-mm-ss
         utcOffset: "+09:00"

      - name: Deploy to EB
        uses: einaregilsson/beanstalk-deploy@v20
        with:
         aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
         aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
         application_name: jlaner
         environment_name: jlaner-env
         version_label: deploy-${{steps.current-time.outputs.formattedTime}}
         region: ap-northeast-2
         deployment_package: docker-compose.yml
         wait_for_environment_recovery: 300

 

EB에 업로드할 때 버전의 이름으로 현재 시간을 넣어주기 위해  Get current time 작업을 추가하고 아래에 EB에 배포할 수 있도록 구성했다.

aws_access_key와 secret_key는 이후에 발급받는 방법에 대해서 기술하도록 하겠다.

application_name과 enviroment_name은 EB에서 설정한 값을 기입하면 되고 version_label은 이전에 설정한 시간과 deploy를 포함한 이름으로 구성했다.

region은 사용하는 위치에 대한 설정이다. deployment_package는 zip이 아닌 docker-compose를 통해서 배포하기 때문에 docker-compose를 기입했다.

맨 아래에 wait_for_enviroment_recovery를 사용한 것은 더 뒤에서 기술할 것인데 지금은 EB는 애플리케이션에 health를 주기적으로 체크한다. 체크에 반환하는 값이 200으로 반환되어야 정상적으로 실행된다. 반환 값을 받을 때까지 대기하는 시간으로 300인 5분을 줬다.

 

이 코드로 인해 CI와 CD를 진행하게 되는데 로컬 환경에서 코드를 git repository main에 push 하면 위가 실행돼서 도커 허브에 이미지가 빌드되고 빌드된 이미지는 docker-compose를 통해 aws에 배포되는 것이다. 이전에 ec2 인스턴스를 자체적으로 생성해 배포했을 때에는 s3와 deploy를 각 구성해서 실행시켰었는데 EB는 자체적으로 ec2 자동 생성과 AutoScaling을 하기 때문에 별다른 구성이 더 필요 없는 것이다.

 

위에서 aws 키를 발급받은 것에 대해서 설명하고 실행해 보자.

이전에 설정해 사용하던 IAM이 있을 것이다.

IAM으로 이동해서 사용하고 있던 IAM을 들어가 주고 요약에 액세스키 만들기가 있을 것이다.

aws 컴퓨팅 서비스에서 실행되는 애플리케이션을 선택한 후 발급받는 csv 파일을 저장하고 발급되는 키를 따로 관리하면 좋다.

이렇게 발급 받는 키를 위 git에서 secret에 저장 후 사용하면 된다.

 

다음으로 실행을 해보았다.

 

오류가 발생했다. Error의 이유는 환경이 실행이 된 후 환경에서는 health를 체크하는 부분이 있다고 위에서 설명했었다. 이때 반환이 정상적으로 된다면 green을 반환해야 하지만 지금은 gray를 반환한다 이 이유를 알아보기 위해서 EB를 확인해 보자.

 

버전을 확인해 보면 정상적으로 배포 업로드는 되었지만 No Data를 반환한다. 이유를 확인해 보자.

Instance has not sent any data since launch.

인스턴스는 launch 되고 데이터를 전송하지 않았다는 것이다. 이 이유를 자세하게 알아보기 위해 안내서를 확인해 보았다.

 

데이터를 받고 있지 않다는 것이다. 그렇다면 왜인지 로그를 확인해 보자고 생각했다.

 

로그를 요청할 수 없다고 나오니 당황스러웠다.

인스턴스는 정상적으로 실행되고 있고 이유를 알 수 없었다.

그래서 인스턴스에 접속해 보았다.

기존에 접속이 잘 되던 ec2 인스턴스 ssh 접속이 되지 않았다.

201-196-26.ap-northeast-2.compute.amazonaws.com
ssh: connect to host ec2-43-201-196-26.ap-northeast-2.compute.amazonaws.com port 22: Operation timed out
hanjihoon@hanjihun-ui-MacBookAir jlaner %

이유는 time out이었다. 너무 당황해서 eb cli도 설치해서 커맨드를 통해 EB 상황을 확인하기로 했다.

eb cli 설치에 대한 부분은 

https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/eb-cli3-install.html

 

Elastic Beanstalk 명령줄 인터페이스 설치 - AWS Elastic Beanstalk

이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.

docs.aws.amazon.com

공식 페이지에서 제공하는 것에 따라 설치했다.

명령어를 통해서 eb 환경을 연결하는 것까지 하고 

 

health를 통해 보았지만 환경에서 보았듯 제대로 된 정보를 알 수 없었고 eb logs를 요청 시 아래와 같이 지속되는 것을 확인할 수 있었다.

hanjihoon@hanjihun-ui-MacBookAir aws-elastic-beanstalk-cli-setup % eb logs
Retrieving logs...

 

이후 막막함에 여러 방면으로 찾아보았지만 eb 트러블 슈팅에 대해서는 레퍼런스가 많지 않다 공식 페이지에서도 제대로 제공되지 않기에 차근차근 둘러보기로 한다.

우선 생각할 수 있는 것은 vpc와 보안 그룹 포트 설정이었다.

확인해 보자.

환경과 연결된 vpc는 활성화 상태로 이전에 사용하던 것과 다른 점은 찾을 수 없었다.

다음으로 보안 그룹 설정을 확인하자.

 

ec2 보안 그룹에서 연결된 보안 그룹을 가서 확인했을 때 잘 열려 있는 것을 확인할 수 있다. SSH는 내 IP만 열어두는 편이 보안상 좋지만 널리 서비스할 것은 아니니 모두 열어두었다.

여기까지 확인했을 땐 잘못된 점은 딱히 보이지 않았다.

다음으로 EB 환경 구성을 처음부터 확인했고 이 부분도 건든 점은 없으니 역시 특별한 점을 찾을 수 없었다.

배포된 변경 기록을 찾아보기로 했다.

 

변경 기록을 확인해 보니 CD로 배포한 기록에는 환경이 연결되어 있지 않았다. 이를 토대로 생각할 수 있던 것은 환경을 이전에 gitaction에 기입했었는데 이 부분이 잘못되었나 라는 생각을 할 수 있었다.

gitaction에는 Jlaner가 j 소문자였다 applicataion의 이름은 소문자지만 환경은 대문자이다.

배포되어 있는 애플리케이션 형식도 zip이었다. yml 방식의 docker-compose를 배포했어야 했다.

 

이 정보를 토대로 다시 gitacion을 수정하자.

수정하기 앞서 deployment_pakage에 docker-compose.yml을 설정했는데 zip으로 배포되는 이유에 대해 알아보고 넘어가야 할 것 같다.

이유로는 gitaction 워크 플로우에서 einaregilsson/beanstalk-deploy 액션을 사용할 때 배포 패키지로 특정 파일이나 디렉터리를 지정했기 때문이다. 일반적으로 EB에서는 애플리케이션 파일을 압축한 zip 파일을 사용하는 것이 표준이기 때문이다.

배포되는 디렉터리나 단일 파일은 S3 버킷에 저장이 된다. S3에 저장된 것을 확인해 보자.

확인해 보니 zip으로 배포가 되고 있었다.

필자 구성은 Docker running on 64bit Amazon Linux 2023/4.3.6 플랫폼을 사용하기 때문에 zip은 일반적인 방식이 아니다.

그러므로 배포되지 않았다고 생각한다.

 

EB에서 Docker를 배포할 땐 Dockerrun.aws.json을 사용할 수 있기도 하다. 이 방식의 사용법은 위 페이지 링크에서도 찾을 수 있다.

하지만 직접 배포를 사용하기 때문에 docker-compose.yml을 인식하고 배포할 수 있도록 구성을 바꾸어 보자.

- name: Deploy to EB
  uses: einaregilsson/beanstalk-deploy@v20
  with:
   aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
   aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
   application_name: jlaner
   environment_name: Jlaner-env
   version_label: deploy-${{steps.current-time.outputs.formattedTime}}
   region: ap-northeast-2
   deployment_package: .
   wait_for_environment_recovery: 300

위와 같이 수정해서 배포하려는 docker-compose.yml의 위치 디렉터리를 지정했고 EB는 파일을 직접 인식하고 배포하게 될 것이다.

이를 수정했으니 다시 CD를 진행해 보자.

하지만 

Deployment failed: Error: EISDIR: illegal operation on a directory, read

 

오류가 발생했다.

이는 .으로 수정한 것이 현재 디렉터리 전체를 지정했기 때문에 문제가 발생했다.

보통 다른 사람들이 한 것을 확인하니 필자와 같게 한다. 패키지 부분에 docker-compose의 경로를 주고 docker-compose.yml을 기입하서나 루트 디렉터리에 있다면 docker-compose.yml을 기입하면 되는 분들이 많다. 필자는 zip으로 생성되는데 s3에서 zip을 열어보려 시도했지만 압축을 해제할 수 없다고 떴다. 이 오류는 zip에 오류가 있거나 내부 파일이 깨지게 되면 발생하는 오류라 아래와 같이 구성했다.

name: deploy

on:
  push:
    branches:
      - 'main'

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: ./gradlew clean build -x test

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}


      - name : Build Docker Image & Push to Docker Hub
        run: |
          docker build . -t hanjihoon0315/docker-springboot
          docker build -f Dockerfile.redis -t hanjihoon0315/docker-redis .
          docker push hanjihoon0315/docker-springboot
          docker push hanjihoon0315/docker-redis
          
      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
         format: YYYY-MM-DDTHH-mm-ss
         utcOffset: "+09:00"

      - name: Create deployment package
        run: |
          mkdir -p deploy-package
          cp docker-compose.yml deploy-package/
          cd deploy-package
          zip -r ../deploy-package.zip .

      - name: Deploy to EB
        uses: einaregilsson/beanstalk-deploy@v20
        with:
         aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
         aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
         application_name: jlaner
         environment_name: Jlaner-env
         version_label: deploy-${{steps.current-time.outputs.formattedTime}}
         region: ap-northeast-2
         deployment_package: deploy-package.zip

 

이처럼 구성하면 파일 경로를 생성해 yml을 복사해서 생성한다.

이렇게 구성하고 다시 실행해 보자.

 

health는 green으로 수행되었다.

다시 EB로 이동해 확인해 보자.

 

확인하면 아직도 No Data이다.

엄청 답답했다.

아래와 보이는 것 같이 s3에서 zip 파일을 다운로드하여서 압축을 해제하면 docker-compose가 필자가 구성한 docker-compose인 것을 확인할 수 있다.


변경 기록에 내 환경 이름도 들어가지 않는다.

솔직히 이유를 잘 모르겠다. 그래서 일단 EB 환경을 삭제하고 다시 만들어보기로 했다. 

플랫폼 한 Linux2로 바꾸고 다시 시도해 보았지만 역시 nodata에 로그도 확인할 수 없었다.

시도해 볼 것은 Dockerrun.aws.json을 구성해서 진행해 볼 것이다.

docker-compose.yml을 올려보려 했지만 실패하고 포함해서 zip을 올려도 실행이 안되고 있으니 Dockerrun.aws.json을 구성해서 진행해 보자.

{
  "AWSEBDockerrunVersion": 3,
  "services": [
    {
      "name": "spring-app",
      "image": "hanjihoon0315/docker-springboot",
      "essential": true,
      "memory": 400,
      "portMappings": [
        {
          "hostPort": 8080,
          "containerPort": 8080
        }
      ],
      "environment": [
        { "name": "SPRING_DATA_REDIS_HOST", "value": "redis" },
        { "name": "SPRING_DATASOURCE_URL", "value": "jdbc:mysql://host.docker.internal:3306/jlaner?serverTimezone=Asia/Seoul" },
        { "name": "SPRING_DATASOURCE_USERNAME", "value": "root" },
        { "name": "SPRING_DATASOURCE_PASSWORD", "value": "raik1353" }
      ],
      "dependsOn": [
        {
          "containerName": "redis",
          "condition": "START"
        },
        {
          "containerName": "mysql",
          "condition": "START"
        }
      ]
    },
    {
      "name": "redis",
      "image": "hanjihoon0315/docker-redis",
      "essential": true,
      "memory": 128,
      "portMappings": [
        {
          "hostPort": 6379,
          "containerPort": 6379
        }
      ]
    },
    {
      "name": "mysql",
      "image": "hanjihoon0315/docker-mysql",
      "essential": true,
      "memory": 496,
      "environment": [
        { "name": "MYSQL_ROOT_PASSWORD", "value": "raik1353" },
        { "name": "MYSQL_DATABASE", "value": "jlaner" },
        { "name": "TZ", "value": "Asia/Seoul" }
      ],
      "mountPoints": [
        {
          "sourceVolume": "mysql-data",
          "containerPath": "/var/lib/mysql"
        }
      ],
      "portMappings": [
        {
          "hostPort": 3306,
          "containerPort": 3306
        }
      ]
    }
  ],
  "volumes": [
    {
      "name": "mysql-data",
      "host": {
        "sourcePath": "/var/lib/mysql"
      }
    }
  ]
}

 

프로젝트 루트에 생성해서 아래와 같이 배포해 보았다.

name: deploy

on:
  push:
    branches:
      - 'main'

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: ./gradlew clean build -x test

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}


      - name : Build Docker Image & Push to Docker Hub
        run: |
          docker build . -t hanjihoon0315/docker-springboot
          docker build -f Dockerfile.redis -t hanjihoon0315/docker-redis .
          docker push hanjihoon0315/docker-springboot
          docker push hanjihoon0315/docker-redis
          
      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
         format: YYYY-MM-DDTHH-mm-ss
         utcOffset: "+09:00"

      - name: Create deployment package
        run: |
          mkdir -p deploy-package
          cp Dockerrun.aws.json deploy-package/
          cd deploy-package
          zip -r ../deploy-package.zip .
      - name: Deploy to EB
        uses: einaregilsson/beanstalk-deploy@v20
        with:
         aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
         aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
         application_name: Jlaner
         environment_name: Jlaner-env
         version_label: deploy-${{steps.current-time.outputs.formattedTime}}
         region: ap-northeast-2
         deployment_package: deploy-package.zip

 

위 버전은 3을 사용했는데 이미 2도 시도했었다.

1은 단일 컨테이너 환경에서 사용하는 버전이고 2는 멀티 컨테이너 3은 멀티 컨테이너이고 docker-compose 파일을 사용하는 애플리케이션을 위한 버전이다.

 

버전의 문제인 것 같은데 2 버전도 안 되고 3도 안 되고 1은 멀티 컨테이너를 실행해야 하기 때문에 1은 아니다.

문제를 찾아보았지만 찾아지지 않았다.

몇 번의 시도와 다시 docker-compose를 사용을 해보았지만 역시 Nodata로 로그도 확인이 되지 않고 이해가 되지 않았다.

이 부분은 더 깊게 공부하도록 하고 다시 EB를 구성해서 실행해 보았다.

name: deploy

on:
  push:
    branches:
      - 'main'

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: ./gradlew clean build -x test

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}


      - name : Build Docker Image & Push to Docker Hub
        run: |
          docker build . -t hanjihoon0315/docker-springboot
          docker build -f Dockerfile.redis -t hanjihoon0315/docker-redis .
          docker push hanjihoon0315/docker-springboot
          docker push hanjihoon0315/docker-redis

      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
          format: YYYY-MM-DDTHH-mm-ss
          utcOffset: "+09:00"

      - name: Create deployment package
        run: |
          mkdir -p deploy-package
          cp docker-compose.yml deploy-package/
          cd deploy-package
          zip -r ../deploy-package.zip .

      - name: Deploy to EB
        uses: einaregilsson/beanstalk-deploy@v20
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: jlaner
          environment_name: Jlaner-env-1
          version_label: deploy-${{steps.current-time.outputs.formattedTime}}
          region: ap-northeast-2
          deployment_package: deploy-package.zip

gitaction은 이처럼 구성하고 실행했다.

결과는 실패이고 다시 NoData이다.

 

답답하던 와중 아래와 같이 애플리케이션 버전을 확인하고 작업에 배포를 눌러서 실행해보았다.

 

위처럼 실행하니 아래와 같이 Ok가 떴다.

위와 같이 ec2도 접속이 되고 ebcli의 health에도 정상적으로 된다. 도메인에 접속은 안된다. nginx와 swap 설정이 되지 않아서 접속이 되지 않는 것이다. 저번과 같이 먼저 nginx와 스왑을 설정해 보자.

https://jh315.tistory.com/99

 

Jlaner 개발기록 8 배포 AWS (DOCKER)

이번 포스팅은 저번에 이어서 배포를 진행하도록 하겠다.이번 포스팅은 넘어갈까 싶었다. 글을 쓰던 도중 중간까지 글을 작성했는데 글이 날아갔다... 자동 임시저장이 있지만 페이지가 나가지

jh315.tistory.com

https://jh315.tistory.com/100?category=1251728

 

Jlaner 개발기록 9 AWS SWAP

이번 포스팅은 저번 포스팅에서 mysql이 메모리 문제로 OOM 됐기 때문에 해결법을 고민하다 이왕 힘들게 배포한 거 SWAP 사용해보자 싶었다. Swap(스왑)스왑이란 컴퓨터에서 주메모리가 부족할 때

jh315.tistory.com

위 포스팅에서 스왑과 nginx 설정하는 것을 확인할 수 있다.

이후 접속해 보았다.

 

접속이 잘 된다.

다른 pc와 휴대폰으로도 접속이 잘 된다.

엔진엑스 설정과 스왑도 주어서 컨테이너가 실행할 메모리도 늘려주었으니 이제 다시 CD를 진행해보려 한다.

메모리 사용량이 스왑으로 늘려주었지만 기본 인스턴스 사용량이 많아서 warning이 떴다.

하지만 실행이 잘 된다. 이렇게 CD는 어느 정도 진행이 되었다.

하지만 배포되어서 환경이 바뀔 때 502 Bad GateWay가 발생한다. 이를 해결하기 위해서 무중단 배포를 해보자.

이전에 redis와 mysql은 중지시켰다. 이유는 배포를 확인하기 위함이기 때문에 메모리에 부담되는 다른 컨테이너는 종료하고 springboot만 실행해서 무중단 배포를 해보려 한다. redis와 mysql을 종료하니 상태가 OK로 바뀌었다.

기존 생각하던 무중단 배포 프로세스는 엔진엑스로 로드밸런스하고 블루와 그린 컨테이너를 띄워서 진행하려 했지만 EB에서 제공하는 블루 그린 배포가 있다.

https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/using-features.CNAMESwap.html

 

Elastic Beanstalk를 사용한 블루/그린 배포 - AWS Elastic Beanstalk

이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.

docs.aws.amazon.com

 

위 페이지에서는 블루 그린 배포에 대해서 알 수 있다. 위에서도 한 번 링크를 걸었지만 한참 아래이므로 다시 걸었다.

위 가이드대로 진행해 보도록 하겠다.

 

복제를 따라가다 보면 이전과 같은 환경으로 구성해 준다.

기다리며 인스턴스를 실행되기 기다렸고 환경이 실행된 것을 확인하니 No Data가 발생했다. 

 

인스턴스 접속도 안 되고 이전과 같은 상황이 벌어졌다.

이벤트 로그에는 아래와 같은 로그가 발생했다.

The EC2 instances failed to communicate with AWS Elastic Beanstalk, either because of configuration problems with the VPC or a failed EC2 instance. Check your VPC configuration and try launching the environment again.

VPC 설정에 문제가 있다는 것인데 이미 배포 중인 환경과 같은 설정의 환경인데 왜 문제인지를 모르겠다.

다른 환경은 물론 잘 배포되고 있다.

복제 환경이 문제이니 다시 환경을 구성해 보도록 하겠다. 위 공식 페이지에서도 새 환경으로 시작해도 된다고 되어 있기에 복제 환경은 삭제하고 새로운 환경을 구성하도록 하겠다.

새로운 환경에서도 VPC에 문제가 생긴다. 

AWS에 전반적인 공부가 더 필요할 것 같다고 느낀다. 이번 프로젝트는 끝내도록 하고 끝내고 나서 차차 알아보도록 하겠다.

우선 VPC를 설정하지 않은 새로운 환경을 생성했다. 이번엔 정상적으로 동작할 수 있었다.

 

이후 인스턴스에 접속해 스왑 메모리와 nginx를 설정했다. 먼저 생성환 환경과 같은 환경으로 구성했다.

- name: Deploy to EB
  uses: einaregilsson/beanstalk-deploy@v20
  with:
    aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    application_name: jlaner
    environment_name: Jlaner-env
    version_label: deploy-${{steps.current-time.outputs.formattedTime}}
    region: ap-northeast-2
    deployment_package: deploy-package.zip

CD에 환경 이름을 새로운 환경으로 바꾸고 CD를 진행하도록 하겠다.

실행 후 

 

환경 도메인을 전환하면 무중단 배포가 진행된다.

 

스왑이 진행이 잘 되었다는 것을 로그로 확인할 수 있다.

이후 접속해 보면

이렇게 중간에 중단되지 않고 배포가 잘 되는 것을 확인할 수 있다.

 

 

이렇게 길고 길었던 배포가 끝이 났다.

기존에 하고자 했던 방향과 틀어지긴 했다. 이는 엘라스틱 빈스토크를 제대로 사용해 본 적이 없기 때문에 기존에 배포하려고 한 시나리오와 달라졌다. 블루 그린의 방식으로 배포한다는 점은 같지만 기존에 계획한 블루 그린 배포는 하나의 인스턴스에 2개의 블루 그린의 컨테이너를 올려 각 새 버전을 배포해 로드 밸런싱을 통해 무중단 배포를 계획했으나 생각해 보면 환경이 업데이트되는 동안 중단되기 때문에 필자의 원래 목표는 애초에 틀린 생각이었다. 공식 페이지에서 제공하는 블루 그린 배포를 통해서 진행해 성공적이었으니 정말 다행이라고 생각한다.

엘라스틱 빈스토크를 사용해 보면서 드는 생각은 정말 편리한 부분이 많다는 것이다. 여러 가지 구성도 자동으로 해주고 필자는 스왑 메모리를 수동으로 생성해 주었지만 넉넉한 용량을 사용한다면 스왑 메모리를 생성하지 않아도 되고 동일 인스턴스 환경을 사용한다면 한 번만 생성해 주면 되니 간편하다고 생각이 든다. 그리고 프록시도 별개의 설치나 컨테이너를 구성하지 않아도 되고 인스턴스 내에서 제공하기 때문에 이를 사용해주면 되기에 간편했다. 엘라스틱 빈스토크에서 프록시 구성과 로드 밸런싱도 조정할 수 있기에 이 부분은 더 공부하고 알게 되면 더 간편하게 사용할 수 있을 것 같다. 만약 간단한 애플리케이션을 배포하게 된다고 한다면 엘라스틱 빈스토크를 사용할 것 같고 여러 설정과 MSA 구성의 애플리케이션이라면 파이프라인을 구성하고 직접 핸들링하는 것이 세밀한 구성을 할 수 있을 것 같아 그 편이 좋다고 생각이 든다. 물론 세밀한 설정을 할 정도의 지식이 필요할 것이고 공부해 볼 생각이다. aws에 대한 자격증도 시험이 있으니 자격증도 따볼 생각을 하고 있다. 별개로 DB에 관련된 것이나 클라우드에 관련된 자격증도 요새 관심이 커지고 있다. 공부를 한다고 해도 증명할 길이 있다면 좋을 것이라 생각하기 때문이다.

이렇게 Jlaner의 구성부터 배포 완료까지 생각보다 더 긴 시간이 걸렸다. 시작할 때에는 8월 초로 5주 생각을 하고 있었는데 1주일이나 더 걸려버렸다.

spring security와 docker, aws 부분에서 가장 크게 시간이 할애되었다. 기능적으로 다양하거나 큰 애플리케이션은 아니지만 시큐리티 구성에 있어 흐름과 docker에 대해서와 파이프라인에 대해서 오류가 많아 이해하고 사용하는데 시간이 들었다. 물론 사전에 어느 정도 기초 지식은 가지고 사용했지만 직접 깊게 사용한데 있어 사전 기초 지식은 사용할 때 틀을 잡는 것에 대한 기본일 뿐 직접 사용에 있어서 연계되는 부분과 사용되는 부분은 더 많은 지식을 요구하기에 깊게 공부하며 막막할 때도 많았지만 좋은 애플리케이션을 배포까지 할 수 있게 되어서 좋았다.

이번 개인 프로젝트를 진행하며 배워서 좋았던 부분은 시큐리티를 사용함에 있어 어떤 방식과 접근을 가지고 하면 좋은지 HTML에 시큐리티를 적용해 사용할 때 헤더에 대한 이해와 HTTP가 제공하는 네트워크 응답, 클라이언트 측에서 사용가능한 로컬 스토리지나 쿠키 사용 등을 사용해 보며 익힐 수 있었다. 이전 프로젝트에서는 타임리프와 css만 사용해서 페이지를 구성했었는데 이번에는 자바 스크립트를 사용하며 더 좋은 애플리케이션을 구성할 수 있어서와 스크립트를 작성하는 법을 익힐 수 있어 좋았다. 아직 미숙하고 다룰 수 있는 함수는 많이 없지만 더 익혀서 좋은 애플리케이션을 구성하게 되면 좋을 것 같고 aws에 대해서 더 알 수 있었다. 이전에 사용할 땐 레퍼런스들을 보며 이렇게 구성하면 작동되는구나 라는 생각을 했다면 이번에 구성할 땐 인스턴스나 엔진엑스 등 문제가 생겼을 때 로그를 확인하며 어떤 파이프 라인과 어떤 부분에서 구성이 잘못되거나 구성이 잘못되었는지 무엇이 문제인지 와닿으며 수정해 배포할 수 있었던 점에서 성장했다고 느꼈다.

이 개인 프로젝트의 시작은 단순히 스크립트를 동반한 애플리케이션을 구성하고 싶다는 생각에서 이어진 프로젝트로 필자의 부족한 점을 채우기 위해서 진행했던 프로젝트로 대단한 기능이나 예쁜 페이지를 제공하지는 않지만 이 프로젝트로 스펙트럼이 넓어질 수 있는 것을 느꼈고 필자가 원하기도 하고 대부분 기업에서도 원하는 기능의 최적화와 대용량 데이터를 다루고 최적화에 사용되는 기술은 알지만 혼자 이기에 직접 사용해 볼 기회는 없었다. 그러므로 더 공부하며 차후 포스팅에서는 이전에 말 한 대용량 기술이나 최적화에 관련된 기술들에 대해 사용해 보고 깊은 설명을 할 수 있는 날이 오면 좋겠다 생각하며 이만 포스팅을 마치도록 하겠다.