ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React+Vite CI/CD with Infisical
    클라우드 2025. 5. 23. 20:59

    그동안 열심히 해뒀던 Infisical에 환경변수 통합 저장과 React+Vite nginx로 수동배포하는 걸 합쳐서

    Infisical로 React+Vite 프로젝트 CI/CD 파이프라인을 구축해보겠습니다.

    CI/CD 파이프라인의 순서는 다음과 같습니다.

     

    우선 CI 파이프라인의 순서입니다.

    1. pull_request가 올라왔을 때, 파이프라인이 돌게 한다.
    2. 레포지토리에서 소스코드를 가져온다.
    3. Infisical CLI를 설치한다.
    4. Infisical에서 환경변수를 가져온다.
    5. 호환되는 Node 버전을 설정한다.
    6. 의존성을 설치한다.
    7. 환경변수를 담아 React를 빌드하고 결과물을 dist 경로에 올린다.

     

    그다음 CD 파이프라인의 순서입니다.

    1. dist 경로에 있는 빌드 결과물을 다운로드 받는다.
    2. 키를 저장하고 VM의 IP를 known_hosts에 등록한다.
    3. 기존 빌드 파일을 삭제하고 새로 경로를 만든 후에, dist에 새로 빌드한 파일을 새로운 경로에 넣습니다.
    4. 그 다음 nginx가 새로운 경로를 참조하도록 myapp.conf 파일에 써두고, 미리 만들어둔 인증서를 통해 HTTPS 설정을 합니다.
    name: Frontend CI/CD
    
    # 풀리퀘 할때 트리거
    on:
      pull_request:
        branches: [ dev ]
    
    jobs:
    # 빌드 과정
      build:
        runs-on: ubuntu-latest
        steps:
    # 레포에서 Github Runner 서버로 코드 전송
          - uses: actions/checkout@v3
    			
    # Infisical CLI 설치
          - name: Install Infisical CLI
            run: |
              curl -1sLf '<https://artifacts-cli.infisical.com/setup.deb.sh>' | sudo -E bash
              sudo apt-get update && sudo apt-get install -y infisical
              
    # Infisical에서 .env 가져오기
          - name: Fetch .env from Infisical
            run: |
              infisical export \\
                --env=${{ secrets.INFISICAL_ENV_DEV }} \\
                --projectId=${{ secrets.INFISICAL_PROJECT_ID }} \\
                --token=${{ secrets.INFISICAL_DEV_TOKEN }} \\
                --format=dotenv \\
                --domain=${{ secrets.INFISICAL_API_URL }} \\
                > $GITHUB_WORKSPACE/.env
                
    # 프로젝트에서 사용하는 노드 버전 설치
          - name: Setup Node.js
            uses: actions/setup-node@v3
            with:
              node-version: '23.11.0'
              cache: 'npm'
    				
    # 의존성 설치
          - run: npm ci
          
    # 빌드
          - run: npm run build
    
    # 빌드 결과를 다른 job(deploy)에서 사용하기 위해 아티팩트(임시 스토리지)에 업로드
          - name: Upload dist
            uses: actions/upload-artifact@v4
            with:
              name: dist
              path: dist/
    
      # test:
      #   runs-on: ubuntu-latest
      #   steps:
      #     - uses: actions/checkout@v3
      #     - uses: actions/setup-node@v3
      #       with:
      #         node-version: '18'
      #         cache: 'npm'
      #     - run: npm ci
      #     - run: npm test
    
      deploy:
        needs: [build] # test
        runs-on: ubuntu-latest
        steps:
    # build에서 업로드 했던 빌드 결과 아티팩트로부터 다운로드
          - name: Download dist
            uses: actions/download-artifact@v4
            with:
              name: dist
              path: dist/
              
    # SSH 접속 설정
          - name: Setup SSH
            run: |
              mkdir -p ~/.ssh
              echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
              chmod 600 ~/.ssh/id_rsa
              ssh-keyscan -H ${{ secrets.GCP_VM_IP }} >> ~/.ssh/known_hosts
    
    # build 결과 SCP로 VM에 업로드
          - name: Deploy build files
            run: |
              ssh ${{ secrets.GCP_VM_USER }}@${{ secrets.GCP_VM_IP }} "rm -rf /home/${{ secrets.GCP_VM_USER }}/fe && mkdir -p /home/${{ secrets.GCP_VM_USER }}/fe"
              scp -r dist/* ${{ secrets.GCP_VM_USER }}@${{ secrets.GCP_VM_IP }}:/home/${{ secrets.GCP_VM_USER }}/fe
    
    # Nginx 설치 후 필요한 작업 수행
    # 1. 기본 설정파일 삭제
    # 2. www-data가 접근할 수 있게 o+x 명령 수행
    # 3. sites-available/myapp.conf에 설정파일 생성(빌드 결과 위치 참조, SSL 인증서 설정)
          - name: Setup Nginx and Configure Site
            run: |
              ssh ${{ secrets.GCP_VM_USER }}@${{ secrets.GCP_VM_IP }} 'sudo apt update && sudo apt install -y nginx && \\
              sudo rm -f /etc/nginx/sites-available/default && \\
              sudo rm -f /etc/nginx/sites-enabled/default && \\
              sudo chmod o+x /home/${{ secrets.GCP_VM_USER }} && \\
              sudo chmod o+x /home/${{ secrets.GCP_VM_USER }}/fe && \\
              echo "server {" | sudo tee /etc/nginx/sites-available/myapp.conf && \\
              echo "  listen 443 ssl;" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "  server_name {server_name};" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "  ssl_certificate /etc/letsencrypt/live/{server_name}/fullchain.pem;" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "  ssl_certificate_key /etc/letsencrypt/live/{server_name}/privkey.pem;" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "  location / {" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "    root /home/${{ secrets.GCP_VM_USER }}/fe;" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "    index index.html index.htm;" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "    try_files \\$uri \\$uri/ /index.html;" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "  }" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "}" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "server {" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "  listen 80;" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "  server_name {server_name};" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "  return 301 https://\\$host\\$request_uri;" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              echo "}" | sudo tee -a /etc/nginx/sites-available/myapp.conf && \\
              sudo ln -sf /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/myapp.conf && \\
              sudo nginx -t && sudo systemctl restart nginx'

    이런 파이프라인입니다.

    제가 애먹었던 부분만을 정리해서 설명하겠습니다.

     

    첫번째는, Infisical에서 환경변수 불러오는 법 이였습니다.

    우선 infisical에서 환경변수 가져오는 방법은

    secrets.INFISICAL_ENV_DEV 이거는 그냥 프로젝트 SLUG 가져오는 것인데 이건 infisical login 하고 Organization과 Project 설정하고 그다음에 환경 설정해서 가져올 때 쓰이는 것 같습니다.

    그래서 빼도 되는 항목인 듯 보입니다.

    그리고 secrets.INFISICAL_PROJECT_ID 이거는 브라우저에서 해당 프로젝트를 들어갔을 때 보이는 부분인데

    {server_domian_name}/secret-manager/{PROJECT_ID}/overview 여기서 보이는 PROJECT_ID를 가져오면 됩니다.

    그리고 제일 중요한 secrets.INFISICAL_DEV_TOKEN인데요, 이건 Project - Access Control - Service Tokens 로 들어가서 Create-Token을 누르고

    이름, 환경 설정하고 Secret Path는 그대로 놔두셔도 됩니다. 아마 프로젝트에 폴더를 만드셨으면 그 경로로 해야할 겁니다. 그리고 만료일을 설정하고 만들면 Token이 발급되는데 이건 한번만 보이니 안전한 곳에 잘 저장해두고 두고두고 쓸겁니다.

     

    두번째로 중요한 부분이 secrets.INFISICAL_API_URL 인데요, 이 부분을 빼먹고 했다가 계속 서비스 토큰을 찾을 수 없다는 오류가 나왔습니다.

    그 이유는 저는 셀프호스팅을 하였는데요, 이 부분을 설정하지 않는 다면 Infisical Cloud로 서비스 토큰을 찾으러 가는겁니다. 그래서 찾을 수 없다는 오류가 나왔던 것이고, —domain 옵션을 통해 <domain_name>/api 경로를 지정해주면 서비스 토큰이 잘 적용됩니다.

    그리고 환경변수를 $GITHUB_WORKSPACE 경로에 저장하는데 이는 첫번째에 프론트 레포에 있는 소스코드를 체크아웃 해간 경로에 저장한다는 것입니다.

    그 이유는 프론트엔드 루트 디렉토리 (package.json과 같은 높이의 경로)에 .env파일이 있어야 빌드할 때 환경변수가 잘 적용되기 때문입니다.

     

     

    두번째는, 이번에는 애먹지 않았고 이전에 애먹었던 내용인데요

    chmod 600 ~/.ssh/id_rsa
              ssh-keyscan -H ${{ secrets.GCP_VM_IP }} >> ~/.ssh/known_hosts
    

    이 부분이였습니다. 여기서 known_hosts에 VM의 IP를 등록해두지 않으면

    yes/no의 입력 받는 부분이 뜹니다. 이런 부분은 파이프라인 내에서 입력받을 수 없기 때문에 미리 등록을 해줘서 yes/no가 뜨지 않게 해주어야합니다.

    예전에는 Github Runner 개념이 없어서 VM 내부에 들어가서 known_hosts 등록해두고, 로컬에 등록해두고 왜 안되지? 라는 생각을 했었지만 Github Runner → VM으로 ssh 접근한다는 것을 안 이후에는 러너에 known_hosts 등록을 해주어 해결하였습니다.

     

     

    세번째는, 권한 설정 문제였습니다.

    저는 /home/user/fe 디렉토리를 만들고 그 아래에 빌드 파일들을 넣었는데 자꾸 500에러가 나왔던 것이였습니다.

    저는 500에러라길래 환경변수가 잘 적용이 안되어 백엔드랑 연결할 수 없어서 환경변수 문제인가 싶어서 환경변수쪽을 엄청 건드리고 있었는데, 수동으로 할 때, 환경변수가 없어도 화면이 잘 나와서 무슨 문제지? 하고 한참을 헤맸습니다..

    문제가 뭐였냐면, nginx는 기본적으로 www-data 사용자로 실행됩니다. 그래서 /var/www/data 같은 nginx 전용 디렉토리에 빌드 파일들을 넣었던 것이였습니다.

    근데 저희 팀은 구분하기 좋게 사용자의 홈 디렉토리에 빌드파일을 넣어서 관리하자고 얘기가 되었고, www-data가 /home/user/fe 디렉토리에 접근하고 그 내부에 파일들을 볼 수 있게 user 디렉토리, fe 디렉토리에 실행권한을 주어(+x) 빌드 파일을 읽을 수 있게 만들었습니다.

     

    느낀 점

    이렇게 Infisical에서 환경변수를 로드하고 적용해서 CI/CD 파이프라인을 완성했습니다.

    그동안 Infisical 셀프 호스팅을 하느라 엄청 고생했는데, 뭔가 결과를 하나 만든거 같아 뿌듯합니다.

    이후 CI/CD 파이프라인 전부가 PR 때 도는게 맞지 않는 거 같다는 생각이 들어서 CI 파이프라인과 CD 파이프라인을 분리해서 PR 때는 CI만 돌고 Merge 될때는 CD만 도는 방식으로 리팩토링 할 예정입니다.

     

     

Designed by Tistory.