티스토리 뷰
HBsmith 는 메인 백엔드 API 서버와 백오피스 어플리케이션을 aws 의 elastic beanstalk 서비스를 이용하여 배포하고 있다. 2011년에 출시된 서비스인 만큼 많은 레퍼런스와 자료들이 존재하고 있으며, 모든 조직/서비스가 항상 컨테이너라는 기술이 항상 답이 될 수 없기 때문에 아직도 많이 사용되고 있는 서비스이다.
우리는 “Amazon Linux 2018.03 v2.9.20 running Python 3.6” 환경을 이용하여 배포하고 있었다. 그리고 새로운 기능과 보안의 이유로 2020년 4월에 출시한 Amazon Linux2 Python3 로의 마이그레이션 작업을 진행한 후기를 소개하고자 한다. 현재 우리는 “64bit Amazon Linux 2 v3.2.1 running Python 3.8” 환경을 사용하고 있다.
Amazon Linux 2 Environment
Amazon Linux 1 AMI 환경보다 더 다듬어진 Amazon Linux 2 환경으로 업그레이드하기 위해 어떤 점이 달라졌는지 간단히 확인해보자. 또, 이런 변경점으로 인해 업그레이드 중에 어려웠던 점과 그 해결했던 방안을 공유하고자 한다.
.platform 도입 & 변화된 엔진
위 그림을 보면 elastic beanstalk 의 엔진이 어떤 과정을 통해 우리의 어플리케이션을 배포하는지 알 수 있으며, 어느 스텝에서 어떤 작업을 해야하는 지 쉽게 파악할 수 있다.
기존엔 .ebextensions 파일을 통해 배포 과정 중 원하는 작업을 지정할 수 있었다. (물론 지금도 가능하다.) 새로운 AL2 환경은 좀 더 심플한 방식으로 커스터마이징을 지원하기 위하여 .platform 훅을 지원한다.
이 .platform 방식에서는 새로운 훅을 제공한다. 위 다이어그램에서 [3.Deploy > c]에 해당하는 postdeploy 훅이다. 이를 통해 써드파티 모니터링 툴 세팅과 같은 작업을 배포 이후에도 수행할 수 있게 되었다.
다이어그램을 보면 확인할 수 있듯, Amazon Linux 1 AMI를 이용하고 있는 구형환경과의 하위호환성을 유지하기 위하여 .ebextensions 파일 안에 정의되어있는 commands, container_commands 명령어를 실행시킬 수 있다. 나는 이 .ebextensions 파일을 굳이 .platform 형식에 맡게 변경하지 않았다. .platform 을 사용하기 위해서는 아래의 구조를 가져야 한다.
├── .platform
│ ├── hooks
│ │ ├── prebuild
│ │ │ ├── 01\_set\_secrets.sh
│ │ │ └── 10\_install\_dependencies.sh
│ │ └── predeploy
│ │ └── 01\_configure\_corretto.sh
│ │ ├── postdeploy
│ │ │ └── 99\_log\_deployment\_complete.py
│ └── nginx
│ └── conf.d
│ └── custom.conf
├── Procfile
└── application.jar
그리고 우리의 .ebextensions 에는 아래와 같이 커맨드가 정의되어있다.
commands:
00-mysql:
command: yum -y install https://downloads.mysql.com/archives/get/p/23/file/MySQL-devel-5.5.62-1.el7.x86_64.rpm
01-mysql:
command: yum -y install https://downloads.mysql.com/archives/get/p/23/file/MySQL-shared-5.5.62-1.el7.x86_64.rpm
container_commands:
00-command:
command: ./provisioning.py ec2
나는 이 커맨드를 굳이 .sh 파일과 같은 거로 바꿀 필요성을 느끼지 못하였다. 오히려 이런 형태때문에 생기는 스크립트 관리의 어려움이 있을 것 같았다. 또한 우리의 provisioning.py 스크립트는 추가적인 argument가 필요한데, .platform의 훅을 사용하기 위해선 스크립트 수정이 불가피했다. 결론적으로 하위호환이 지켜짐을 확인하였기 때문에 문제없다고 판단했다.
잘 다듬어진 훅은 쓰지 않지만, `.platform` 폴더가 있는 이유가 있다.
.platform 폴더 안엔 nginx 관련 설정들만 존재하게 되었다. nginx의 설정파일을 오버라이딩시켜주기 때문이다. 저 위의 다이어그램으로 다시 돌아가면, [3. Deploy > b] 에 해당하는 이야기다.
Apache & mod_wsgi 에서 NGINX 와 GUNICORN 이 Default.
mod_wsgi 와 Apache 를 위한 설정 파일을 nginx 와 gunicorn 을 위한 설정파일로 바꿔야한다.
Procfile 지정
Amazon Linux 2 환경으로 넘어오면서 Procfile 을 설정하도록 했다. Procfile 을 따로 .platform/Procfile 로 저장하지 않으면 .ebextensions 에 지정된 템플릿 변수를 가지고 gunicorn 을 사용하는 Procfile을 만들어 사용하게 된다.
"aws:elasticbeanstalk:application:environment":
PYTHONPATH: "<YOUR-PATH>"
"aws:elasticbeanstalk:container:python":
WSGIPath: "<YOUR-WSGI-PATH>"
.ebextensions 에 위의 설정이 포함되었다면 결국 아래의 Procfile로 배포가 될 것이다.
web: gunicorn --bind 127.0.0.1:8000 --workers=1 --threads=15 --chdir <YOUR-PATH> <YOUR-WSGI-PATH>
8000번 포트를 사용하는 방식이므로, socket file 방식을 사용하고싶다면, Procfile 을 재정의할 필요가 있다.
uwsgi 는 쓸 수 없는 건가요?
우리의 백오피스 어플리케이션인 nerv는 uwsgi를 사용하고 있다. uwsgi 를 사용하기 위해선 몇가지 작업이 더 필요하다.
앱 루트 폴더에 존재하는 requirements.txt 에 UWSGI 를 포함시킨다. 우리는 uWSGI==2.0.19.1 를 포함시켰다. Elastic Beanstalk 엔진이 배포 초반에 requirements.txt 의 라이브러리를 다 설치해준다.
Procfile 도 새롭게 정의내려야 한다. 우리가 사용하는 파일은 아래와 같이 정의되어 있다.
web: uwsgi --chdir /var/app/current/nerv --master --max-requests 5000 --module nerv.wsgi:application --pidfile /run/nerv/nerv.pid --socket /run/nerv/nerv.sock --chmod-socket=777 --vacuum --workers 8권한 문제
기존 환경과는 달리 Procfile 을 정의하고 이 파일을 webapp 이라는 유저가 실행시키는 구조를 갖는다. 그러므로 갖게 되는 문제가 있다. .ebextensions 에서 지정한 명령어는 root 유저로 수행하는데, 이를 통해 커스터마이징한 부분이 있다면 파일 권한에 각별히 신경 써야 한다.
우리의 배포 과정 중 커스터마이징은 provisioning.py 가 많은 부분을 차지하고 있다. python을 이용하고 있으며, 높은 재사용성으로 local 환경에도 대응하고 있다. 아래는 그 파이썬 스크립트의 일부이다.
_run(['chown', '-R', 'webapp:', '/var/app/staging/static'])
_run(['chown', '-R', 'webapp:', '/var/log/nerv'])
_run(['chown', '-R', 'webapp:', '/var/log/uwsgi'])
_run(['chown', '-R', 'webapp:', '/run/nerv'])
기타 변경점
애플리케이션 저장 위치 변경
과거엔 /opt/python 디렉터리였지만, 지금은 /var/app이다. 이런 변경 점 때문에 커스터마이징 하는 스크립트를 많이 수정하게 되었다. 배포 중간단계의 커스터마이징에는 /var/app/staging 경로를 쓰게 되며, /var/app/current 경로로 옮긴 후 배포시킨다.
python 환경의 변경
기존과 달리 webapp유저의 도입과 함께, /var/app/venv/staging-LQM1lest 라는 python venv도 새롭게 들어왔다. 더이상 .ebextensions 에서의 pip 관련 작업이 배포에서 무의미하다. 심지어 gunicorn 이 설치된 상태로 시작한다. 위에서도 잠깐 언급했지만, staging 단계에서 앱 루트 경로의 requirements.txt를 그대로 설치한다.
로그를 까보자.
처절한 커밋 메세지. #55의 의미는 elastic beanstalk 를 55번 프로비저닝했다는 의미로밖에.. 🤭
55번의 디플로이를 통해서 지옥을 맛봤고, 여러 로그를 찢어 아래의 표로 정리해봤다. Elastic Beanstalk의 새로운 엔진이 어떻게 동작하는지 이해하는 데 도움이 될 거라 본다.
정리
로드밸런싱, 오토스케일링과 같은 설정부터 모니터링까지 모두 세팅되는 편한 서비스인 만큼, 시행착오에 많은 시간이 들었다. 이름과 구조가 전 환경과 비슷하지만, 전혀 다른 서비스를 이용하는 경험이었다. .platform 같은 새로운 훅 추가, 기본 프록시 서버 변경과 같은 알려진 변경점도 있지만, 공식 문서에서 찾을 수 없던 webapp 유저 구조 추가, python 가상환경 추가 등의 변경점이 마이그레이션을 어렵게 했다. ( ssh 접근을 통해 알아낸 것들이다.)
이 글을 통하여 Amazon Linux 1 환경을 쓰는 분들이 Amazon Linux 2 환경으로 업그레이드하는 데에 도움이 되었으면 한다.
ps. Amazon Linux 2 AMI 자체가 가져다주는 이점과 이에 대한 설명은 이 문서를 참고하기 바란다.
Reference
- Python platform history — https://docs.aws.amazon.com/elasticbeanstalk/latest/platforms/platform-history-python.html
- Elastic Beanstalk supported platforms — https://docs.aws.amazon.com/elasticbeanstalk/latest/platforms/platforms-supported.html
- Linux 서버에서 소프트웨어 사용자 지정 — https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/customize-containers-ec2.html#linux-container-commands
- Elastic Beanstalk Linux 애플리케이션을 Amazon Linux 2로 마이그레이션 — https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/using-features.migration-al.html