개발환경을 한 방에! 쉘 스크립트의 힘
들어가며…
안녕하세요. 우아한형제들 서비스개발실 주문시스템개발팀의 라태웅입니다.
어느 개발 조직이건 새로운 사람이 들어오면 그 사람의 PC에 새롭게 개발 환경을 셋팅해야 합니다.
이 때, 저는 세 가지 경우를 경험했는데요.
- 문서에 정리된 개발 환경 설정 방법을 받았다. 따라했는데 안된다.
- 구두로 설명해주시는데 잘 모르겠다.
- 쉘 스크립트로 한 방에 셋팅이 된다.
1번의 문제는 문서가 노후화되고 사람이 이해하기 쉽게, 따라하기 쉽게 작성하기가 어렵다는 단점이 있습니다.
2번의 문제는 전달자가 헷갈리거나 오래되어 잊어버린 경우 굉장한 혼선이 온다는 것, 거기에 신규 입사자 입장에선 이 조직이 과연 이대로 괜찮은가 하는 생각이 들 수도 있겠죠.
하지만 3번처럼 쉘 스크립트 한 방으로 셋팅이 된다면?!
신규 입사자의 감탄사가 들리시나요!? (와아아아아아아아아아!!!!!!!!!!)
이 글에서 해볼 것
이 글에서는 두 가지 쉘 스크립트를 작성할건데요.
첫 번째,
- PATH에 Working Directory 추가하기.
- AWS CLI, AWS ElasticBeanstalk CLI 설치하기.
- AWS Credential 추가하기.
보너스로 PHP Codeigniter를 AWS ElasticBeanstalk에 Deploy 해보기.
두 번째,
- 로컬 환경에서 Docker에 웹서버(PHP Codeigniter) 올려보기.
이 말인즉슨, 신규 입사자는 첫 번째 쉘 스크립트로 기본적인 AWS 기반의 개발 환경이 갖춰지고, 두 번째 쉘 스크립트로 로컬 환경에서 개발이 바로 가능해진다는 것이죠!
하지만 이 글에서는 쉘 스크립트의 문법에 대해서는 다루지 않습니다! 이곳을 참고해주세요! 이 글에서는 쉘 스크립트로 꽤 많은 것을 편하게 할 수 있다는 것을 알려드리는데에 초점이 맞춰져 있습니다.
첫 번째 쉘 스크립트
먼저 프로젝트 폴더를 생성하고, 내부에 디렉토리 구조를 만들어봅니다.
$ cd ~
$ mkdir sspj
$ cd sspj
$ mkdir utilities
$ cd utilities
$ mkdir bin
$ mkdir tmp
$ cd ..
$ mkdir webserver
만들어진 폴더 구조는 아래와 같습니다.
-- sspj
-- utilities
-- bin
-- tmp
-- webserver
이제 쉘 스크립트를 작성해봅니다.
$ vi ~/sspj/utilities/bin/sspj_setup.sh
#! /bin/bash
# 윗 줄은 이 프로그램은 bash를 기반으로 실행된다는 뜻입니다.
# 실행된 쉘 스크립트의 절대 경로를 가져옵니다.
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do
TARGET="$(readlink "$SOURCE")"
if [[ $SOURCE == /* ]]; then
SOURCE="$TARGET"
else
DIR="$( dirname "$SOURCE" )"
SOURCE="$DIR/$TARGET"
fi
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
BASEDIR=$DIR
IFS="/"
BASEDIR_SPLIT=($BASEDIR)
IFS=""
# Root 디렉토리를 구합니다.
SSPJ_HOME=""
for key in "${!BASEDIR_SPLIT[@]}";
do {
if [[ $key > 0 && $key < $(( ${#BASEDIR_SPLIT[@]} - 2 )) ]]; then
SSPJ_HOME="$SSPJ_HOME""/""${BASEDIR_SPLIT[$key]}"
fi;
};
done
# bash_profile에 PATH를 추가해줍니다.
echo "export SSPJ_HOME=$SSPJ_HOME" >> ~/.bash_profile
echo "export PATH=$PATH:$SSPJ_HOME" >> ~/.bash_profile
echo "Root directory is $SSPJ_HOME"
# AWS CLI를 설치하기 위해 pip를 먼저 설치합니다.
echo "Installing pip ..."
sudo curl https://bootstrap.pypa.io/get-pip.py -o $SSPJ_HOME/utilities/tmp/get-pip.py
echo "Running get-pip.py ..."
sudo python $SSPJ_HOME/utilities/tmp/get-pip.py
# AWS CLI를 설치합니다.
echo "Instailling awscli ..."
sudo pip install --ignore-installed awscli
# AWS ElasticBeanstalk CLI를 설치합니다.
echo "Instailling awsebcli ..."
sudo pip install --ignore-installed awsebcli
# AWS Credential을 만듭니다.
echo "Input AWS Access Key : "
read AWS_ACCESS_KEY
echo "Input AWS Secret Access Key : "
read AWS_SECRET_ACCESS_KEY
sudo mkdir ~/.aws
sudo chmod -R 777 ~/.aws
sudo touch ~/.aws/credentials
sudo cat > ~/.aws/credentials << EOF
[default]
aws_access_key=$AWS_ACCESS_KEY
aws_secret_access_key=$AWS_SECRET_ACCESS_KEY
EOF
sudo touch ~/.aws/config
sudo cat > ~/.aws/config << EOF
[profile eb-cli]
aws_access_key_id=$AWS_ACCESS_KEY
aws_secret_access_key=$AWS_SECRET_ACCESS_KEY
EOF
# PATH를 터미널에 적용하기 위해 source를 해줍니다.
source ~/.bash_profile
# 끝!
echo "Done!"
이제 이 쉘 스크립트를 실행 가능한 상태로 만들어줍니다.
$ chmod +x ~/sspj/utilities/bin/sspj_setup.sh
이제 만든 쉘 스크립트를 실행해 볼까요?
$ source ~/sspj/utilities/bin/sspj_setup.sh
잠깐! 혹시 AWS Access Key가 없으신가요?
이제 다시 돌아와서…
AWS ElasticBeanstalk에 CLI로 배포하기
먼저, webserver.zip 파일을 다운로드 받고, ~/sspj/webserver
폴더에 압축을 풉니다.
AWS ElasticBeanstalk에 관한 설명은 Elastic Beanstalk Configuration files(.ebextensions)에서도 보실 수 있습니다. 나는 쉘 스크립트만 궁금하다 하시는 분은 이 파트를 건너뛰셔도 됩니다!
PHP Codeigniter 기반으로 작성된 뼈대 웹서버 인데요. 이를 AWS ElasticBeanstalk CLI로 간편하게 배포해보겠습니다.
$ cd ~/sspj/webserver
$ eb init
위의 eb
는 AWS ElasticBeanstalk CLI 인데요. 아까 첫 번째 쉘 스크립트가 자동으로 설치해준 녀석입니다. init
명령어를 통해 최초 설정을 해줍니다.
ElasticBeanstalk Application이 만들어졌습니다. 이제 Environment를 만듭니다.
$ eb create
이제 다음 배포부터는 정말 간단합니다.
$ eb deploy
정말 쉽죠?
그런데 개발할 때 테스트를 위해서 기능을 수정할 때마다 deploy를 하고 수정하고 하는 식은 너무 불편하겠죠. 그래서 이를 Docker로 로컬 환경에서 실행해보겠습니다.
두 번째 쉘 스크립트
두 번째 쉘 스크립트에 작성하기 전에, webserver.zip 파일을 다운로드 받고, ~/sspj/webserver
폴더에 압축을 풉니다. 이미 앞선 파트에서 받으셨다면 받지않으셔도 됩니다.
저희는 Docker를 통해 개발 환경을 설정할 것이기 때문에 먼저 Docker를 설치해야 합니다. (이곳(Docker for Mac)에서 설치할 수 있습니다.)
먼저 Dockerfile을 만듭니다.
Dockerfile에 대한 자세한 설명은 진행하지 않습니다! 이런식으로 쉘 스크립트를 사용할 수 있다고만 봐주세요!
$ vi ~/sspj/Dockerfile_ws
FROM php:7.0.12-apache
ENV DEBIAN_FRONTEND=noninteractive
# Install the PHP extensions I need for my personal project (gd, mbstring, opcache)
RUN apt-get update && apt-get install -y libpng12-dev libjpeg-dev libpq-dev git mysql-client-5.5 wget \
&& rm -rf /var/lib/apt/lists/* \
&& docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \
&& docker-php-ext-install gd mbstring opcache pdo zip
RUN apt-get update && apt-get install -y apt-utils adduser curl nano debconf-utils bzip2 dialog locales-all zlib1g-dev libicu-dev g++ gcc locales make build-essential
# Install mysql extension
RUN apt-get update && apt-get install -y --force-yes \
freetds-dev \
&& rm -r /var/lib/apt/lists/* \
&& cp -s /usr/lib/x86_64-linux-gnu/libsybdb.so /usr/lib/ \
&& docker-php-ext-configure pdo_mysql --with-pdo-mysql=mysqlnd \
&& docker-php-ext-install \
pdo_dblib \
pdo_pgsql
# APC
RUN pear config-set php_ini /usr/local/etc/php/php.ini
RUN pecl config-set php_ini /usr/local/etc/php/php.ini
RUN pecl install apc
RUN a2enmod rewrite
RUN a2enmod expires
RUN a2enmod mime
RUN a2enmod filter
RUN a2enmod deflate
RUN a2enmod proxy_http
RUN a2enmod headers
RUN a2enmod php7
# Edit PHP INI
RUN echo "memory_limit = 1G" > /usr/local/etc/php/php.ini
RUN echo "upload_max_filesize = 1000M" >> /usr/local/etc/php/php.ini
RUN echo "post_max_size = 1000M" >> /usr/local/etc/php/php.ini
RUN echo "max_input_vars = 10000" >> /usr/local/etc/php/php.ini
RUN echo "max_input_time = 60" >> /usr/local/etc/php/php.ini
RUN echo "file_uploads = On" >> /usr/local/etc/php/php.ini
RUN echo "max_execution_time = 300" >> /usr/local/etc/php/php.ini
RUN echo "LimitRequestBody = 100000000" >> /usr/local/etc/php/php.ini
# Clean after install
RUN apt-get autoremove -y && apt-get clean all
# Configuration for Apache
RUN rm -rf /etc/apache2/sites-enabled/000-default.conf
ADD utilities/bin/ws-config/000-default.conf /etc/apache2/sites-available/
RUN ln -s /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-enabled/
RUN a2enmod rewrite
EXPOSE 80
# Change website folder rights and upload your website
RUN chown -R www-data:www-data /var/www
# Change working directory
WORKDIR /var/www/webserver
# Change your local - here it's in french
RUN echo "locales locales/default_environment_locale select ko_KR.UTF-8" | debconf-set-selections \
&& echo "locales locales/locales_to_be_generated multiselect 'ko_KR.UTF-8 UTF-8'" | debconf-set-selections
RUN echo "Asia/Seoul" > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata
# Change folder permission
RUN chmod -R 0777 /var/www/webserver
# RUN service apache2 restart
CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
Docker에서 바라볼 config 파일을 생성합니다.
$ cd ~/sspj/utilities/bin
$ mkdir ws-config
$ cd ws-config
$ vi 000-default.conf
<VirtualHost *:80>
ServerAdmin webmaster@localhost
ServerName localhost
DocumentRoot /var/www/webserver
<Directory />
Options FollowSymLinks
AllowOverride All
</Directory>
<Directory /var/www/webserver >
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
이제 이 Docker를 자동으로 빌드하고 실행하는 스크립트를 작성합니다.
$ vi ~/sspj/utilities/bin/run_ws_local.sh
#! /bin/bash
# 윗 줄은 이 프로그램은 bash를 기반으로 실행된다는 뜻입니다.
# 이미 실행중인 컨테이너가 있다면 멈추고
docker stop sspj-ws-container
# 제거합니다.
docker rm sspj-ws-container
# Dockerfile을 빌드하고
docker build -f $SSPJ_HOME/Dockerfile_ws . -t sspj-ws
# 실행합니다.
docker run -it -d -p 80:80 --name sspj-ws-container -v $SSPJ_HOME/webserver:/var/www/webserver sspj-ws
쉘 스크립트 자체는 정말 짧지요?
실행해보겠습니다. (일단 실행 권한을 먼저 줘야겠죠?)
$ chmod +x ~/sspj/utilities/bin/run_ws_local.sh
$ ~/sspj/utilities/bin/run_ws_local.sh
마치며…
이번 글에서는 쉘 스크립트를 한 번 만들어두면 개발 환경을 쉽게 구성할 수 있습니다! 라는 것을 전달드리고 싶었는데요… 다 적고보니 뭔가 뒤죽박죽이 된 것 같습니다 -0-…
결론은! 이렇게 누군가가 셋업을 해두면, 다음 사람들은 Docker를 설치하기만 하면
$ source ~/sspj/utilities/bin/quest_setup.sh
$ . ~/sspj/utilities/bin/run_ws_local.sh
이 명령어로 바로 개발이 가능하다는 것이죠.
여러분의 개발 환경에도 적용해보시는 것은 어떨까요?
감사합니다!