Nextjs Springboot Nginx Docker-compose

devty·2023년 6월 8일
1

DevOps

목록 보기
9/11

서론

  • 우리 프로젝트의 front는 Nextjs로 되어있고, back은 Springboot로 되어있다. 앞에 proxy server로 Nginx를 두었다.
  • 각 어플리케이션마다 도커 이미지로 만들어 하나의 명령어로 관리하고 싶었다.
  • 그 과정에서 참 많은 일들이 있었다.
  • 순 개발시간은 50시간이 넘어갔고, 기간으로는 2.5주 정도 걸린 것 같다.
  • 여태까지의 실패하고 좌절하고 다시 시작하고 여러 사람들의 도움으로 완성하는 과정을 설명하겠다.

본론

  • 사실 처음에는 Nginx가 무엇인지, 왜 붙어야하는지 이해하지 못했는데 같은 회사 동료분께서 보안은 절대적으로 중요하며 내부포트(3000, 8080)를 개방하는 것이 아니라 외부포트(80) 하나로만 통신을 해야한다고 말씀해주셔서 도입하게 되었다.
  • 일단 지금 상태로는 back-end(Springboot)는 Jenkins를 이용하여 CICD를 끝내둔 상태이다.
    • 간략하게 설명하자면
      1. Code를 Github에 Push
      2. Jenkins Webhook으로 Github에 Push됨을 감지하여 Commit 내역을 가져온다.
      3. Jenkins 안에서 도커 이미지를 빌드하고 도커 허브에 푸쉬해둔다.
      4. EC2 접속 정보를 받아서 서버에 도커 허브에 푸쉬한 도커 이미지를 Pull 받아온다.
      5. 그 후 docker run 명령어를 이용하여 도커 컨테이너를 띄운 상태이다.
  • back-end는 이미 CICD가 완료된 상태이므로 잠시 냅두고 front-end에 대한 CICD를 작업할 예정이다.

실행과정

  1. front-end(Nextjs)도 back-end에서 작업했던 것과 마찬가지로 root directory에 Dockerfile을 생성한다.

  2. 생성한 Dockerfile 안에는 밑과 같이 작성을 하였다.

    # Node.js 이미지로 사용
    FROM node:14-alpine as build
    
    # 작업 디렉토리를 /app으로 설정한다
    WORKDIR /app
    
    # 컨테이너에 package.json와 package-lock.json 파일을 복사합니다.
    COPY package*.json ./
    
    # npm 설치
    RUN npm install
    
    COPY . .
    
    # Next.js를 빌드한다
    RUN npm run build
    
    # 새로운 단독의 nginx 이미지 생성
    FROM nginx
    
    # 오픈할 포트를 적어둔다
    EXPOSE 3000
    
    # default.conf을 /etc/nginx/conf.d/ 경로에 있는 default.conf에 복사한다.
    COPY ./default.conf /etc/nginx/conf.d/default.conf
    
    # nextjs build한 결과물을 /usr/share/nginx/html에 복사한다.
    COPY --from=build app/out  /usr/share/nginx/html
    • 한줄한줄 주석처리는 해두었지만 내가 처음 경험했을 때 어려웠던 부분만 디테일하게 설명을 하겠다.
    • 지금 위 dockerfile을 보면 FROM이 두개 있는 것을 확인할 수 있다.
      • FROM node → node 이미지를 가져와서 사용한다는 뜻
      • FROM nginx → nginx 이미지를 가져와서 사용한다는 뜻
      • 위 두가지가 있는데 이미지를 두개를 만드는 것이 아니고 FROM 기준으로 마지막에 있는 stage작업이 도커 이미지로 생성이 된다. → Multi stage
      • 이것에 이점으로는 도커 이미지를 줄일 수 있다.
      • 사진 → todo
    • COPY ./default.conf /etc/nginx/conf.d/default.conf
      • 해당 부분은 dockerfile이 위치한 경로 기준으로 동일한 계층에 있는 default.conf 파일을 빌드 될 이미지안에 nginx 설정 중 /etc/nginx/conf.d/default.conf에 복사한다.
      • 처음에 나는 당연하게도 nginx 이미지 파일 받아오니까 nginx 이미지에 들어있는 줄 알았다…당연히 아니고 이건 nextjs를 이미지로 만들기위한 도커파일 이므로 nextjs 이미지에 복사되는 부분이다! 도커에 대한 지식이 부족하여 며칠을 허비하였다…
      • default.conf → nextjs application안에 있는 파일
        server {
            listen 3000; // 내부 포트 개방
        
            location / {
        
                root /usr/share/nginx/html; // 위에서 빌드한 파일 위치 지정
                index  main.html;
                try_files $uri $uri/ /main.html;
        
            }
        }
    • COPY --from=build app/out /usr/share/nginx/html
      • 위 코드중 --from=build는 첫번째 줄 코드인 node에서 별칭을 둔 build와 동일하게 사용하면 된다.
      • 코드 중간에 npm run build를 하게 되면 nextjs는 .next 라는 폴더 하나를 생성하는데 out은 어디서 나왔을까? 라는 생각을 했었다. 알고보니 package.json에 scripts 중 build에 next export를 추가 하게 되면 out이라는 파일이 생성되고 바로 위 COPY와 같이 nextjs 이미지에 복사된다.
        "scripts": {
            "dev": "next dev",
            "build": "next build && next export",
            "start": "next start",
            "lint": "next lint"
        }
  3. Nextjs CICD

    • Nextjs CICD는 Github Action으로 작성하였다.
      • 그 이유는 Jenkins를 사용해본 결과 접근성이 어려움점이 있었다. 하지만 Github Action은 좀 더 직관적이며 가격적인 측면에서도 메리트가 있었다.
    name: Docker
    on:
      push:
        branches: [main]
    
    jobs:
      build:
        runs-on: ubuntu-latest
        
        strategy: 
          matrix: 
            node-version: [16.14.x] 
    
        steps:
          - uses: actions/checkout@v2
          - name: Use Node.js ${{ matrix.node-version }}
            uses: actions/setup-node@v1
            with: 
              node-version: ${{ matrix.node-version }} 
          
          - name: web docker build and push
            run: |
              docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
              docker build -t ${{ secrets.DOCKER_REPO }}/yeh_front .
              docker push ${{ secrets.DOCKER_REPO }}/yeh_front
                
          - name: aws ec2 docker image pull
            uses: fifsky/ssh-action@master
            with:
              command: |
                docker pull ${{ secrets.DOCKER_REPO }}/yeh_front
              host: ${{ secrets.HOST }}
              user: ${{ secrets.USERNAME }}
              key: ${{ secrets.KEY }}
    • Github Action을 한번이라도 써본 사람들이라면 위 내용이 그렇게 어렵지 않을 것이다.
    • 간략하게 설명하자면 :
      1. ubuntu서버에서 실행한다.
      2. node-version을 지정하여 사용하겠다.
      3. 도커 로그인, 빌드, 푸쉬를 진행하겠다.
      4. ec2 서버에 접속하여 푸쉬된 도커 이미지를 pull 받겠다.
    • 이제 github에 code를 푸쉬하면 ec2 서버에 front-end 이미지가 생성된걸 볼 수 있다.
  4. EC2 서버에 nginx 이미지 pull 받기.

    • EC2 서버에서 docker pull nginx 명령어를 입력한다.
    • 입력하게 되면 가장 최신버전에 nginx 이미지를 가져오게 된다.
  5. docker-compose.yaml 파일을 작성한다.

    • 아래 명령어를 입력해서 docker-compose를 다운한다.
    sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  • 아래 명령어를 입력해서 docker-compose.yaml 파일을 만든다.
    vi docker-compose.yaml
  • 아래와 같이 docker-compose.yaml 파일을 작성한다.
    version: '3'
    services:
    
      web:
        container_name: web
        image: {docker ID}/web
        expose:
          - 8080
        ports:
          - 8080:8080
    
      next:
        container_name: front
        image: {docker ID}/front
        expose:
          - 3000
    
      nginx:
        container_name: nginx
        image: nginx:latest
        restart: always
        volumes:
          - ./conf/nginx.conf:/etc/nginx/conf.d/nginx.conf
        ports:
          - 80:80
          - 443:443
        depends_on:
          - web
          - next
    • 위 docker-compose로 띄우는 컨테이너는 총 3개로 web(springboot), front(nextjs), nginx(proxy server)이다.
    • 특이한 점은 next 컨테이너를 보면 port가 열려있지 않은 걸 볼수 있다.
    • 왜냐하면 이미 nextjs는 dockerfile에서 빌드할 때 nginx 설정에 3000포트를 개방해줬기 때문이다.
    • 그리고 nginx 컨테이너에 보면 volumes로 기존 ec2 서버에 있는 nginx.conf 파일을 nginx 실행 시에 /etc/nginx/conf.d/nginx.conf에 복사해온다.
    • 처음에 헷갈렸던 부분은 ./conf/nginx.conf가 nextjs 어플리케이션 안에 들어가있어야하는 파일인 줄 알았다..내가 생각해도 너무 무지했다..
    • conf/nginx.conf → ec2에서 docker-compose.yaml파일과 같은 계층에 conf 디렉토리를 만들고 그 안에 nginx파일을 생성한다. 위치는 자기 마음대로 가능하다.
      user nginx;
      worker_processes auto;
      error_log /var/log/nginx/error.log;
      pid /run/nginx.pid;
      
      # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
      include /usr/share/nginx/modules/*.conf;
      
      events {
          worker_connections 1024;
      }
      
      http {
          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;
          tcp_nopush          on;
          tcp_nodelay         on;
          keepalive_timeout   65;
          types_hash_max_size 4096;
      
          include             /etc/nginx/mime.types;
          default_type        application/octet-stream;
      
          include /etc/nginx/conf.d/*.conf;
      
      		server {
              listen       80;
              server_name devyeh.com www.devyeh.com;
      
              # redirect https setting
              if ($http_x_forwarded_proto != 'https') {
                      return 301 https://$host$request_uri;
              }
      
             location / {
                     proxy_pass http://front:3000;
      
                     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_read_timeout 300;
                     proxy_connect_timeout 300;
                     proxy_send_timeout 300;
              }
      
              location /api {
                      proxy_set_header X-Real-IP $remote_addr;
                      proxy_set_header HOST $http_host;
                      proxy_set_header X-NginX-Proxy true;
                      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      
                      proxy_pass http://web:8080;
                      proxy_redirect off;
              }
      
          }
      }
      • 아마 처음보면 1도 이해가 안 될 것이다. 제가 그랬거든요 머쓱;;
      • 다 볼 필요는 없고 중간에 server하고 중괄호가 열리는 부분 부터 보면 된다.
      • listen → 내가 사용할 포트이다. 우린 80포트를 열거니까 80만 적어둔다. 여기서 잠깐 443 포트를 안 여는 이유는 이미 aws acm에서 80포트를 443포트로 리다이렉션 해주기에 따른 설정을 안 해줘도 된다.
      • server_name → 사용하고 있는 도메인이나 도메인이 없다면 퍼블릭 ip4를 입력하면 된다.
      • location → 이건 우리가 흔히 생각하는 url이라고 생각하면 편하다.
        • location / → 이거는 url에 뭐가오든 front로 통신하라는 뜻이다.
        • location /api → 이거는 url에 /api/~~~ 로 시작하는 건 back으로 통신하라는 뜻이다.
  1. 이제 설정할 파일은 다 끝났다.
    • 아래 명령어로 docker image들을 실행시키자.
      docker-compose up -d

결론

  • 막상 회고를 작성하니 별게 없다고 느껴진다.
  • 그만큼 내가 발전했다는 거라 생각하고 위안을 하는중이다..
  • 중간마다 안 될 때 처음부터 다시 시작하거나 블로그에 나와있는 방법이 아닌 새로운 방법도 많이 시도했다.
  • 새로운 도전에는 :
    • nginx 이미지에 설정파일이 안 담겨지니(nextjs 이미지에 담겨지는게 맞는데 몰랐을 땐) nginx 이미지를 사용하지 않고 ec2 서버내에 직접 설치해서 하는 방식을 채택하였다.
      • 근데 여기서 또 문제는 nginx 설정파일들 중 최상위에 위치한 nginx.conf 파일을 수정할 때 생긴 일이다.
      • 기존에는 aws route53에서 도메인을 산 뒤 aws acm(ssl 인증방식)을 사용하여 https 인증과 동시에 로드밸런싱을 통해 80 포트로 접속시에 443 포트로 redirect 되도록 만들었다.
      • 근데 나는 또 다시 ssl 인증은 nginx에서 해줘야하는 걸로 인지하여 nginx.conf 파일에 listen 443을 만들어서 letsencrypt를 이용하여 ssl 인증을 받아주었다……
      • 이미 aws acm에서 인증을 받은 터라 굳이 두번 받을 이유도 없을 뿐더러 인증이 중복되어 https 인증이 되지 않았다..앞으로는 내가 어떤 기술을 도입했는지 계속 기억하고 이 기술에 대한 목적성을 갖고 있는게 중요하다 생각했다…
    • 위 방법 ec2에 nginx를 설치하여 하는 방법이 성공했으니 이제 nginx 이미지를 받아서 오는 걸 다시 도전하였다.
      • 그 전에 비해서 dockerfile에 들어가는 명령어 docker-compose에 사용되는 기능들을 이해하고 시작하니 그렇게 어렵지 않게 해결을 하였다. (3일 소요)
      • 다시금 3개의 이미지를 만들어서 docker-compose up으로 한번에 띄우는 걸 성공했다.
  • 여태 하면서 프론트의 이해와 백엔드의 이해 그리고 웹 통신이 돌아가는 것에 대해 이해하였다.
  • 많은 시간을 소비하였지만 전체적인 틀을 이해하는데 이만한 경험이 없다고 생각한다.
profile
지나가는 개발자

0개의 댓글