ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Amazon Lightsail에 배포
    문제해결 2023. 12. 4. 21:49

    서론

    모두의 토론 백엔드를 아마존 라이트세일에 배포하는 과정을 정리했다.

    배포 과정

    모두의 토론 백엔드는 도커 위에 개발 환경을 구축했다. php-apache 이미지를 기반으로 라라벨 웹 서버를 구동시키고, redis 이미지를 활용하여 job을 처리하고, mysql 이미지를 활용하여 데이터베이스를 사용했다. 세 컨테이너를 관리하기 위하여 도커 컴포즈를 활용했다. 따라서 배포 역시 도커를 기반으로 하면 좋을 것이라고 생각하고 진행했다.

    0. 배포 방법 고민

    어떻게 배포를 할까 생각했다. AWS, Azure 사이에서 고민했다. Azure를 사용하면 새로운 경험을 할 수 있지만, 결제 정보 관리 포인트가 늘어나므로 AWS로 결정했다. AWS를 만지작 거리다 보니 Lightsail이란 게 눈에 띄었다. AWS의 여러 서비스를 모두 직접 관리하려면 불편하고 비용도 많이 드니까 관리가 쉽고 비교적 비용이 적게 들도록 나온 서비스였다. 레퍼런스도 충분해 보이고 처음 3달 동안 무료라 라이트 세일에서 해보기로 했다.

    1. 라이트 세일에 인스턴스 생성

    라이트 세일에서 os only로 우분투를 선택, 인스턴스 이름과 인스턴스 플랜을 정해주고 마무리

    2. 인스턴스에 접속하여 도커 설치

    공식 홈페이지의 Install Docker Engine on Ubuntu가 안되어 chatGpt의 도움을 받아 설치했다. 다음 명령어를 순차적으로 적용했다.

    sudo apt update
    sudo apt install apt-transport-https ca-certificates curl software-properties-common
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
    sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
    sudo apt update
    sudo apt install docker-ce
    sudo systemctl start docker
    sudo systemctl status docker

    3. 깃허브 저장소 클론

    인스턴스의 유저 디렉토리로 이동하여 저장소 클론하고 마무리

    4. env 파일 업로드

    env파일은 깃허브에 없으므로, 로컬 개발환경에 있던 env를 인스턴스에 업로드했다. 이를 위해 scp를 사용했고, 키를 연결했다. 그러나 다음과 같이 보호되지 않은 개인키 경고가 발생하였다.

    https://superuser.com/questions/1554696/windows-ssh-cant-ssh-into-ec2-account-permissions-for-key-pem-are-too-open

    연결에 사용된 개인 키 파일이 누구나 접근 가능한 상태여서 발생하는 에러라고 한다. 윈도우10에서 문제를 해결하려면 다음 과정을 거쳐야 한다.

    • 해당 pem파일을 우클릭하여 속성 ㅡ> 보안 ㅡ> 고급 탭으로 이동
    • 하단 상속 사용 안 함을 눌러서 상속된 사용 권한을 모두 제거한다.
    • 다음과 같이 사용 권한이 변해야 한다.

    상속 사용 안 함을 누르면 모든 권한이 사라진다.

    • 왼쪽 하단의 추가 ㅡ> 상단 보안주체 선택 ㅡ> 고급 선택
    • 오른쪽 중간의 지금 찾기 ㅡ> 하단의 검색결과에서 내가 로그인한 아이디를 찾아 확인을 눌러주고 저장

    다시 scp를 이용하여 환경 변수 파일을 업로드 하고 마무리

    5. 도커 컴포즈 실행

    다음 명령어로 실행한다. 별다른 문제는 없었다.

    sudo docker compose up -d --build

    6. 첫 번째 테스트 API 요청: 연결 시간 초과

    모든 컨테이너가 정상적으로 동작하는 것을 확인했다. 포스트맨을 이용하여 테스트용 API를 아무거나 요청했지만, 연결 시간이 초과되었다. 생각해 보니 방화벽에서 포트를 열어주지 않았다. 라이트 세일의 관리 페이지에서 네트워크를 선택하여 방화벽에 규칙을 추가해서 해결.

    7. 두 번째 테스트 API 요청 : 500 에러

    라라벨에서 log를 남기는 storage디렉터리에 permission denied에러가 발생하였다. 호스트 머신의 유저와 도커 컨테이너 안의 유저의 권한 설정이 달라 충돌이 발생한 것이다. 다음과 같은 커맨드로 해결하였다. 참고

    sudo chown www-data:www-data -R ./storage

    8. 세 번째 테스트 API 요청 : 테이블 없음

    도커 컴포즈를 이용하여 컨테이너를 올렸지만, 테이블을 생성하지 않아서 발생하였다. artisan 커맨드를 이용하여 마이그레이션 진행하였다. 이후 API 요청은 성공하였다.

    php artisan migrate:fresh --seed

    9. 회원가입 시도 실패

    회원가입 API를 호출했다. 요청 바디에 보낸 이메일로 인증메일이 제대로 왔다. 인증 확인 버튼을 누르자 제대로 페이지가 뜨지 않았다. 문제가 발생한 곳은 인증 완료를 위한 URL을 환경변수에서 가져오지 않은 지점이었다. 해당 코드를 환경변수에서 URL을 가져오도록 수정 완료.

    10. 환경 변수 파일 이원화

    개발용 환경 변수 파일과 배포용 환경 변수 파일을 분리하여 적용하였다. 라라벨의 설정 파일에 사용되는 mode변수나 URL 등이 변경되었다.

    11. 도메인 구매후 적용

    AWS에서 13달러에 도메인을 구매했다. 이메일 인증까지 끝낸 후 라이트 세일 관리 페이지에 들어가 보니 등록한 도메인을 볼 수 있었다. 그리고, 도메인 구매에서 끝나는 것이 아니라, 구매한 도메인을 네임서버에 등록하고 IP에 연결하도록 설정을 해줘야 한다. 예전엔 라우트 53에서 하나씩 해줬었다. 호스팅 영역을 지정해서 IP도 연결해 줬던 기억이 난다.

     

    지금은 라이트 세일에서 설정을 해줄 수 있었다. 따로 문서를 읽을 필요성을 못 느낄 정도로 직관적인 수준에서 연결할 수 있도록 유도한다. 그래서, DNS zone을 생성해서 도메인을 등록해 줬다. 그리고 Assignments에서 도메인을 이전에 생성했던 라이트 세일 인스턴스로 연결해 줬다. 마지막으로 DNS records에 등록이 된 것을 확인하고 마무리

    DNS zone을 만들었다. 문서를 참고할 필요가 없을 정도로 직관적으로 편했다.

    12. HTTP를 위해 증명서 연결

    현재는 self signed 증명서를 이용하고 있다. 그래서 포스트맨으로 요청을 하면 응답과 함께 다음과 같은 경고가 뜬다.

    자가 서명 증명서 경고

     

    이것을 해결하려면 도메인에 맞는 증명서를 생성하여 등록해줘야 한다. 예전에 로드 밸런서에 증명서를 연결하고 웹 서버에는 별다른 설정을 해주지 않았던 기억이 난다. 그래서 라이트 세일에서 로드밸런서를 할당하고 다시 연결을 해주려고 했다. 하지만 로드 밸런서당 18달러라는 압박에 잠시 물러났다.

     

    letsencrypt를 설치하여 증명서를 연결했다.

    sudo apt-get install letsencrypt
    sudo certbot certonly -d "*.every-discussion.com" --manual --preferred-challenges dns

    위 방식은 DNS 레코드에 TXT 레코드 타입을 추가하여 DNS의 소유자임을 증명하여 진행된다. 위 명령어를 치면 TXT 타입의 레코드에 지정된 이름과 값을 제시한다. 이것을 라이트 세일에서 설정해 주고 마무리

    증명서 생성 성공

    13. 증명서를 컨테이너의 웹 서버에 연결

    생성된 증명서를 컨테이너의 웹 서버에 연결해줘야 한다. 기존의 자가 서명 인증서를 대신해 준다. Dockerfile과 docker-compose.yml에서 경로를 수정하여 연결 마무리

    14. vue 프론트엔드 배포

    vue로 만든 프론트엔드를 배포한다. vue를 배포한다는 것도 원리적으론 웹서버를 올리고 vue의 빌드 결과를 웹서버가 서빙할 수 있도록 하는 것이다. 도커를 이용하여 진행하였다. 다음은 Dockerfile이다.

    FROM node:lts-alpine as build-stage
    WORKDIR /app
    COPY . . # .dockerignore 에서 node_modules와 같은 디렉토리를 제외하지 않으면 오래걸릴 수 있다.
    
    RUN npm install
    RUN npm run build
    
    FROM php:8.2-apache as production-stage
    COPY --from=build-stage /app/dist /var/www/html/public
    
    RUN a2enmod rewrite
    ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
    RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
    RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
    WORKDIR /var/www/html/public

    .dockerignore 파일을 추가하여 node_modules, dist 등 불필요한 파일들을 제거해 주도록 하자.

     

    그리고 앞서 증명서를 적용한 과정을 다시 반복했다.  빠뜨린 부분이 없는지 확인하기 위해 디렉토리 제거하고 저장소 클론부터 다시 반복하고 마무

    잘 연결된다!

    15. 개발 내용을 서버에 쉽게 반영하기

    개발한 내용을 서버에 반영하려면 다음과 같은 행동을 반복해야한다.

    1. 서버 접속
    2. 디렉토리에 이동하여 git pull
    3. 도커 컴포즈로 컨테이너들 다 내리고 다시 올리기

    로컬에선 그렇게 귀찮지 않았는데, 원격 서버에 하려니 너무 번거롭다. 방법이 없을까 하다가 예전에 강의에서 들었던 깃허브 액션을 이용하기로 했다.

    yml 파일을 작성하는 와중에 jobs안의 uses라는 키워드가 헷갈렸다. 뭘 어떻게 사용한다는 건지... 다행히 깃허브 공홈의 설명이 바로 이해가 갔다. An action is a custom application for the GitHub Actions platform that performs a complex but frequently repeated task. 즉, 반복적으로 사용되는 일련의 태스크를 자동으로 대신해 주는 것이다. 내가 사용한 건 appleboy/ssh-action으로 특정 서버에 접속하여 스크립트를 실행할 수 있는 액션이다. 액션을 등록하고 서버에 스크립트를 올리고 마무리

    16. 증명서 재발급

    증명서 발급을 위해 dns challenge 방식을 사용했다. 이 방식은 cert bot의 renew가 제대로 되지 않아, 수동으로 다시 재발급을 진행하였다.

    첫 번째 시도

    certbot --apache

     

    위 커맨드를 실행하면, let's encrypt에서 서버로 접속해 ".well-known/acme-challenge/난수문자열파일"을 받으려고 요청하지만, 80번 포트가 닫혀있어서 연결이 거부되어 실패. 80번 포트를 열고, 난수문자열파일을 만들어서 넣어줬으나, 다음 시도에서 난수문자열이 변경되는 문제발생

    두 번째 시도

    certbot certonly --manual

     

    실행하면, 난수문자열파일 이름을 알려주고 ".well-known/acme-challenge/난수문자열파일"에 놓으라고 한다. 그렇게 했지만 실패. 왜? apache 웹 서버의 .htaccess설정에서 모든 요청을 index.php로 rewrite하고 있으므로, /.well-known으로 가는 요청도 index.php에 가게된다. .htacess를 수정하여 .well-knonw으로 가는 요청은 제외시킴.

    N 번째 시도

    여전히 실패. 로그를 확인하였다.

    /var/log/letsencrypt/letsencrypt.log
    
    acme.messages.Error: urn:ietf:params:acme:error:rateLimited :: 
    There were too many requests of a given type :: Error creating new order :: 
    too many failed authorizations recently: 
    see https://letsencrypt.org/docs/failed-validation-limit/

    1시간 안에 동일한 도메인에 대하여 인증을 5번 시도하고 실패하면 인증을 더 이상 진행할 수 없다. 약 1시간 뒤에 다시 시도하여 남은 인증을 진행할 수 있었다.

     

    인증이 모두 끝나면, 다음 경로에 인증을 위한 파일들이 위치한다.

    /etc/letsencrypt/live/도메인이름

    파일들은 총 4개가 나온다. privkey.pem, fullchain.pem, chian.pem, cert.pem 각각의 파일에 대한 설명이 README.MD에 담겨있다. 우리가 사용할 것은 privkey.pem과 fullchain.pem이다. 이 파일을 다시 실서버에 옮겨서 인증을 마무리했다.

     

    하지만, 나중에 확인해 보니 이러한 방식은 좋지 않다고 한다. 

    // README.MD의 내용일부
    WARNING: DO NOT MOVE OR RENAME THESE FILES!
             Certbot expects these files to remain in this location in order
             to function properly!
    
    We recommend not moving these files. For more information, see the Certbot
    User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.

    즉, certbot이 참조하는 파일 이름과 그 위치가 정해져 있으므로 이름을 바꾸지도 말고 이동시키지도 말라는 것이다. 이것을 소홀하게 여겨, 인증을 다시 하는 과정이 매끄럽지 않은 게 아닐는지...

     

    당장의 구조에선 파일을 옮겨야 하긴 하는데, 다음에 할 땐 어떻게 해야 매끄러울 수 있을까 찾아보니, 그 사용방법 역시 certbot guide에 있었다. "복사하기보다는, 웹 서버 설정이 바로 파일들을 가르키거나, 심볼릭 링크를 생성해서 사용해라". 맞는 말이다. 읽어보니 좋은 내용들이 많아서 한 번 쭉 읽어봐야겠다.

    재밌다

    17. 증명서 재발급 자동화

    certbot을 실행하여 증명서 재발급을 시도하였다. 

    certbot renew --dry-run

    그러나, 증명서가 있음에도 불구하고 증명서를 인식하지 못하였다. 왜 그런가 찾아보니, 증명서를 발급할 땐, 증명서뿐만 아니라 재발급을 위한 파일(letsencrypt/renewal밑에)을 비롯하여 여러 파일들이 같이 나오는데, 그것들을 옮겨오지 않았기 때문이다. 고정된 웹 서버였다면 이런 일이 발생하지 않았겠지만, 도커를 이용하여 반복하여 서버를 올리고 내리는 와중에 16. 증명서 재발급처럼 증명서만 복사하여 넣어주기 때문에 발생한 것이다.

     

    재발급 등을 위한 파일들을 찾을 수 없기에 다시 증명서 자체를 발급하고 관련 파일들을 docker-compose의 볼륨 마운트를 이용하여 가지고 있기로 하였다. 그리고 재발급을 시도하였다.

     

    다시 --dry-run으로 시도하니 성공했다.

    certbot renew --dry-run 성공

    크론탭에 등록하여 실행해 보니 아직 갱신하기엔 너무 이르다고 한다.

    renew하기 위한 크론탭

    증명서 재발급을 위한 문서 참고: challenge types

    하다 보니 개선이 필요한 것.

    설정 파일을 잘 다루기

    서비스를 위한 설정 파일들이 있다.

    1. 아파치 웹 서버

    2. PHP

    3. 데이터베이스

    4. 라라벨

    5. ssl 파일

    6. vue 설정

    위 파일들의 설정을 어떻게 다룰지 결정해야한다. 여러 가지 방법을 시도해봤다.

     

    만족한 설정 방법은 다음과 같다.

    2. PHP: Docker 이미지의 PHP.ini 파일을 그대로 사용하되 설정을 추가하기 위해 ini파일을 수정하지 않고 conf.d 디렉토리에 설정파일을 추가하는 식이다. 개발 모드와 프러덕션 모드를 나누었다. 추가하고 수정하기 쉬워서 만족

    3. 데이터베이스: 기본 설정은 Docker 이미지의 설정을 그대로 따르되, docker-compose파일을 이용하여 설정 수정. 서비스와 관련된 테이블은 라라벨의 마이그레이션을 이용. 이것도 만족

    5. ssl파일: ssl디렉토리를 만들어서 해당 ssl파일을 volume mount를 진행. 특별한 설정은 없다.

    6. vue: 빌드한 파일을 아파치에서 내려주도록 설정. 특별한 설정은 없었다.

     

    수정했다가 약간 애매해진 설정은 다음과 같다.

    4. 라라벨: core.env를 만들어서 기본설정을 모아두고, 개발.env와 프러덕션.env을 따로 두고 필요에 따라 덮어쓰기를 할 수 있게 하였다. 쉘 스크립트를 적용하여 컨테이너를 실행할 때, .env에 core.env와 나머지를 합치는 식. 그럭저럭 만족. 개발 모드와 프러덕션에서 중복된 것을 해소하기 위한 방향성이었는데 이 정도보다 복잡해지면 파악하기 어려워질 것이라 예상.

     

    개선이 필요한 설정은 다음과 같다.

    1. 아파치 웹 서버: 기본 설정인 apache2.conf가 있고, 사이트별 site-available가 있고(site-enabled는 available를 기반으로 자동생성된다), .htaccess가 있다. 이 설정들을 수정하기 위해서 Dockerfile에서 sed 커맨드를 이용하여 교체를 하는 식으로 했다. 그 순간엔 괜찮았지만,  새로운 설정을 추가하기 위한 과정이 깔끔하지 않았다. 차라리 내 설정을 따로 만들고 그것을 로컬에서 수정한 다음 volume mount를 하는 식이 나았을 것이다.

    남은 배포 과정

    • self signed 증명서를 이용하여 https를 처리 중. 도메인을 하나 사서 https를 적용해야겠다.
    • 증명서 재발급 자동화
    • 개발 내용을 서버에 쉽게 반영하기
    • vue로 만든 프론트엔드 배포하기
      • 개발 소스를 컨테이너에 복사하여 빌드 후 웹서버로 서빙
      • 증명서 적용

    댓글

Designed by Tistory.