myeonjeobeottae / client

0 stars 0 forks source link

CI/CD 구현 #22

Open tangjinlog opened 6 months ago

tangjinlog commented 6 months ago

Feature

Description

To do list

Troubleshooting

tangjinlog commented 6 months ago

Nginx-proxy-manager 폴더의 permission denied

 구분  ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ설명 ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
현상 github-actions 중, nginx-proxy-manager가 권한없음으로 열리지 않음
원인 nginx-proxy-manager 폴더의 권한이 현재 사용자와 일치하지 않을 때 발생
해결책 ls -ld nginx-proxy-manager로 폴더의 권한정보를 먼저 확인한다.
당시 사용자와 사용자 그룹이 root로 설정되어 있었음.
현재 EC2 접속 사용자는 ubuntu이기 때문에, 권한설정을 바꿔줘야 한다.

chown -R ubuntu:ubuntu nginx-proxy-manager
image
tangjinlog commented 6 months ago

Docker Image를 Hub에 Push하고 Pull 받았는데, 최신 코드가 배포 반영이 안되는 현상

github repository actions 페이지에 나타날 이름

name: CI/CD using github actions & docker

event trigger

main 브랜치에 push가 되었을 때 실행

on: push: branches: ['main']

permissions: contents: read

jobs: CI-CD: runs-on: ubuntu-latest steps:

github 가상환경에서 작업할 수 있도록 repository 코드 복제

  - uses: actions/checkout@v3

  # docker build & push to production
  - name: Docker build & push to prodction
    if: contains(github.ref, 'main')
    run: |
      docker login -u ${{ secrets.DOCKER_USERNAME}} -p ${{ secrets.DOCKER_PASSWORD}}
      docker-compose build --platform linux/amd64 -t ${{ secrets.DOCKER_USERNAME}}/myeonjeobeottae .
      docker push ${{ secrets.DOCKER_USERNAME}}/myeonjeobeottae

  # deploy to production
  - name: Deploy to production
    uses: appleboy/ssh-action@master
    id: deploy-prod
    if: contains(github.ref, 'main')
    with:
      host: ${{ secrets.EC2_HOST}}
      username: ${{ secrets.EC2_USERNAME}}
      key: ${{ secrets.EC2_KEY}}
      envs: GITHUB_SHA
      script: |
        cd client/
        sudo su
        docker-compose down
        docker-compose up -d --build
        docker system prune -f

- 기존 EC2 docker-compose.yml 코드
```yml
# docker 컨테이너 버전을 명시
version: '3.8'

services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    container_name: nginx-proxy-manager
    restart: always
    ports:
      - 81:81 #관리포트
      - 80:80 #http
      - 443:443 #https
    volumes:
      - ./nginx-proxy-manager/data:/data
      - ./nginx-proxy-manager/letsencrypt:/etc/letsencrypt
    environment:
      DISABLE_IPV6: 'true'
    depends_on:
      - myeonjeobeottae

  myeonjeobeottae:
    container_name: myeonjeobeottae
    # 현재 경로에 이미지 빌드
    build:
      context: .
      dockerfile: Dockerfile.yml
    ports:
      - '3000:3000'
    image: tanglog/test:0.0.1

github-actions에서 EC2에 접속한 후, docker-compose up -d --build로 컨테이너를 실행시키고 있는데,
EC2의 docker-compose.yml에서는 다시 이미지 build를 하고있다.

그래서 github-actions의 과정에서 이미지를 build하고 DockerHub에 push했음에도 만들어진 이미지를 사용하지 않고 있었던 것.
그래서 만들어진 image를 pull하는 코드로 변경하면서 문제를 해결할 수 있었다.

docker build의 개념을 잘못 이해해서 일어난 일이었다. docker-compose.yml에서 nginx이미지는 Hub에서 받아오고, 면접어때 app 소스코드는 build하고 있었기 때문에, docker-compose build 명령어로 이미지를 만들면 두가지 컨테이너가 하나의 이미지에 담기는 줄 알았는데 아니였다. docker 이미지는 단일 컨테이너만 담을 수 있다고 한다. 결과적으로 nginx 와 면접어때 두 가지 이미지를 DockerHub에서 pull 받아오고 그걸 docker-compose up으로 한 번에 실행시켜야 하는 것이다.

CI/CD 구축하고 난 후 EC2안에서 clone을 안받아도 되니, 구조가 매우 깔끔해져서 만족한다!

image

기존 src를 비롯한 각종 config 파일들을 제거하고, nginx 폴더와 docker-compose.yml, env 파일만으로 배포할 수 있게 되었다.

tangjinlog commented 6 months ago

github-actions 중, DockerFile 못 찾는 현상

github-actions 진행중에 DockerFile을 못 찾아 CI/CD가 실패했다.

찾아보니 현재 DockerFile.yml로 yml확장자를 달면 DockerFile이 아니기 때문에, 감지를 못한다고 한다.

DockerFile.yml -> DockerFile로 변경해결 해결

tangjinlog commented 6 months ago
image

무한 실패

tangjinlog commented 6 months ago

첫 action 이후 여전히 최신 코드가 배포 반영이 안되는 현상

저번 트러블슈팅에서 docker-compose.yml에서 DockerHub의 이미지를 pull 받는 것으로 최신 코드를 반영할 수 있게 되었다. 하지만 처음 한 번만 최신 코드가 반영되었고 그 다음 부터는 최신 코드가 반영되지 않은채 그냥 CI/CD가 성공했다.

// 최신 코드가 반영되는지 테스트
<Modal>
    <Modal.Overlay />
    {/* <Modal.Title>라우터 감지 모달</Modal.Title> */}
    <Modal.Title>제목 바꾼다</Modal.Title> // 최신 코드가 반영되는지 확인차 바꾼 제목
    <Modal.CancelButton>취소</Modal.CancelButton>
    <Modal.ExecuteButton unBlockingWithCallback={unBlockingWithCallback}>
    나가기
    </Modal.ExecuteButton>
</Modal>

충격적인 결과

image
타이틀을 임의로 바꾼다음 CI/CD에 성공했는데 배포된 앱에서 안바뀌었다. 지금까지 바꾼 코드가 하나도 반영이 안됬다는 뜻.. CI/CD 성공만보고 착각했다!

어쨌든 문제의 원인 파악을 위해 또 의식의 흐름을 따라가본다.

  1. 현재 최신 코드로 Docker 이미지를 만들고 DockerHubpush후 그 이미지를 pull받아서 docker-compose로 컨테이너를 실행중이다.
  2. 그런데 최신 코드가 반영이 안된다.
  3. 그럼 이미지를 만들 때 최신 코드로 이미지가 만들어지는게 아닌가? -> 그럴리 없다. github의 커밋 기록과 DockerHub의 Image updated가 최신이다.
  4. 그렇다면 DockerHub에서 이미지를 pull 받는게 문제인가? -> 관련 코드를 살펴본다.
//github.actions.yml
...
# deploy to production
- name: Deploy to production
  uses: appleboy/ssh-action@master
  id: deploy-prod
  if: contains(github.ref, 'test')
  with:
    host: ${{ secrets.EC2_HOST}}
    username: ${{ secrets.EC2_USERNAME}}
    key: ${{ secrets.EC2_KEY}}
    envs: GITHUB_SHA
    script: |
      sudo su
      docker-compose down
      docker-compose up -d --build // 컨테이너를 올리는 부분
      docker system prune -f

github-actions.yml에서 EC2에 접속 후 docker-compose up -d --build로 컨테이너를 한번에 올리고있다. 의미는 다음과 같다.

docker-compose.yml에 정의된 해당 서비스 이미지를 허브에서 해당 이미지의 항상 최신버전으로 업데이트한다(pull받는다). 이미지의 태그가 같아도 pull한다.

docker-compose pull과 마찬가지로 이미지를 새로 받는다. 만약 로컬에 이미지가 없거나 이미지의 Tag가 다를 때만 새로운 이미지를 다운로드 한다. 즉, 로컬에 이미지가 있고, 태그가 같으면 새로 다운로드하지 않는다. pull 커맨드와의 차이점은 pull은 이미지를 다운 받는게 역할의 전부이고, docker-compose up은 다운받음과 동시에 컨테이너를 실행하는 역할까지 한다. 즉, 이 명령어를 쓴다면 docker-compose pull은 생략이 가능하다.

[!IMPORTANT] 따라서, 나는 계속해서 myeonjeobeottae:latest로 이미지를 생성했기때문에, 같은 이미지로 판단하고 이미지를 새롭게 받지 않았던 것이다!

원인은 알았지만, 나는 태그를 latest로 계속 유지하고싶었고, nginx 이미지를 추가로 사용하고있었기 때문에, 다음과 같이 코드를 수정했다.

// github-actions.yml
...
script: |
  sudo su
  docker system prune -f
  docker-compose down
  docker-compose pull
  docker-compose up -d --build

명시적으로 docker-compose pull을 해줌으로써 허브에서 이미지를 무조건 다운받고, docker-compose up -d --builddocker-compose.yml에 명시된 서비스 전부를 한번에 올린다. 이렇게 해결!

tangjinlog commented 6 months ago

Docker Container 내부에서 환경변수를 참조하지 못하는 문제

보안을위해 카카오 OAuth 로그인할 때, client_idredirect_uri를 환경변수에 담아 참조해서 사용하고있다.

NEXT_PUBLIC_REST_API_KEY=클라이언트 아이디
NEXT_PUBLIC_REDIRECT_URI=리다이렉트 콜백 uri

브라우저에서 환경변수를 참조하기위해 NEXT_PUBLIC 접두사를 달아줬다.

그런데, 로그인 버튼 클릭 시, 해당 값이 undefined로 나타났다.

현재 배포는 EC2컨테이너에서 DockerHub의 이미지를 pull받아서 컨테이너를 올리고있는데, docker-compose.yml과 같은 위치에 .env.production 환경변수 파일이 있다. 나는 Docker Image를 기반으로 컨테이너를 올리더라도 같은 depth에 있는 .env.production을 참조할 수 있다고 생각했는데, 실제로는 참조가 안되고있었던 것이다.

검색해보니, docker 이미지 빌드시에 동적으로 환경변수를 할당할 수 있는 방법이 있다고한다.

docker build --build-arg [환경변수 key]=[환경변수 value]

위 방법으로 사용할 환경변수 두 개를 github action secret으로 만들어 적용해봤다.

//github-actions.yml
...
- name: Docker build & push to prodction
  if: contains(github.ref, 'test')
  run: |
    docker login -u ${{ secrets.DOCKER_USERNAME}} -p ${{ secrets.DOCKER_PASSWORD}}
    docker build --build-arg NEXT_PUBLIC_REST_API_KEY=${{ secrets.REST_API_KEY }} --build-arg NEXT_PUBLIC_REDIRECT_URI=${{ secrets.REDIRECT_URI }} --platform linux/amd64 -t ${{ secrets.DOCKER_USERNAME}}/myeonjeobeottae .
    docker push ${{ secrets.DOCKER_USERNAME}}/myeonjeobeottae

이렇게 docker 이미지 build시에 환경변수를 넣어주고,DockerFile에서 ARG 커맨드로 해당 환경변수를 가져와서 사용하면 된다고한다.

//DockerFile
...
ARG NEXT_PUBLIC_REST_API_KEY
ENV NEXT_PUBLIC_REST_API_KEY=${NEXT_PUBLIC_REST_API_KEY}
ARG NEXT_PUBLIC_REDIRECT_URI
ENV NEXT_PUBLIC_REDIRECT_URI=${NEXT_PUBLIC_REDIRECT_URI}

그런데 여전히 배포 앱에서 환경변수 참조값은 undefined로 변함이 없었다.. 왜일까 생각하다, build시에 환경변수를 넣어주니, DockerFile에서 yarn build전에 환경변수를 세팅해줘야하나 싶었다. 그래서 DockerFile을 다시보니,

기존 코드

//DockerFile
...
FROM base AS builder
WORKDIR /usr/src/app
COPY --from=deps /usr/src/app/node_modules ./node_modules
COPY . .

RUN yarn build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /usr/src/app

ARG NEXT_PUBLIC_REST_API_KEY
ENV NEXT_PUBLIC_REST_API_KEY=${NEXT_PUBLIC_REST_API_KEY}
ARG NEXT_PUBLIC_REDIRECT_URI
ENV NEXT_PUBLIC_REDIRECT_URI=${NEXT_PUBLIC_REDIRECT_URI}
...

멀티스테이징 빌드로 builderrunner를 구분해서 사용하고있었는데, 지금 build가 끝난 후에 runner에서 환경변수를 세팅하고 있었다!

개선 코드

//DockerFile
...
FROM base AS builder
WORKDIR /usr/src/app
COPY --from=deps /usr/src/app/node_modules ./node_modules
COPY . .

// build 전에 환경변수 세팅
ARG NEXT_PUBLIC_KAKAO_LOGIN_URI
RUN touch .env.production
RUN echo "NEXT_PUBLIC_KAKAO_LOGIN_URI=${NEXT_PUBLIC_KAKAO_LOGIN_URI}" > .env.production
ENV NODE_ENV production

RUN yarn build
...

[!important] yarn build로 앱을 빌드하기전에, 환경변수를 세팅하는 걸로 코드를 수정했다. 추가로, docker container안에 배포시에 참조할 수 있도록 .env.production 환경변수 파일을 만들고, 거기에 github-actions.yml에서 넘겨줬던 환경변수를 덮어씌워줬다.

이렇게 docker 이미지가 빌드되면서 동적으로 할당된 action secret이 환경변수의 역할을 하면서 production 환경에서 브라우저가 환경변수를 참조할 수 있게 되었고, 실제 이 방법으로 문제를 해결할 수 있었다!

image