Circle CI에서 코드에서 실행된 결과물을 이미지로 저장해보자


환경

  • Python
  • Selenium
  • Circle CI and Project


Circle Artifact

Circle CI에서 원할 때 화면을 캡쳐해서 저장할 수 있다.

기본적으로 Circle CI에서 CIRCLE_ARTIFACTS라는 환경변수를 따로 저장해두는데 이 환경변수 값을 불러와서 저장이 가능하다. 사진을 저장하는 예제는 아래와 같다.

  • os.environ['CIRCLE_ARTIFACTS'] + '/name.png'의 형식에서 name.png에 다른 이름 형식을 넣어서 원하는 형식으로 저장 할 수 있다.

example.py

if 'CIRCLE_ARTIFACTS' in os.environ:
    # dirver = Selenium Web Browser
    driver.get_screenshot_as_file(os.environ['CIRCLE_ARTIFACTS'] + '/name.png')


참고자료

Nginx에서 sites-available 폴더에 .conf 파일을 추가해서 가상서버를 만들어보자.


환경 및 선수조건

  • Nginx(nginx/1.10.0 (Ubuntu))
  • Nginx의 기본 사용법 및 서버 블록에 대한 이해


Nginx의 기본 파일과 폴더들

Ubuntu에서 apt-get을 통해 nginx 설치하기 및 간단한 정리에서 간단하게 정리를 해두긴 했지만 설정을 하기에 앞서서 간단하게 몇가지 기본 폴더들과 파일들을 보고 가겠습니다.

  • sites-available : 가상 서버 환경들에 대한 설정 파일들이 위치하는 부분입니다. 가상 서버를 사용하거나 사용하지 않던간에 그에 대한 설정 파일들이 위치하는 곳입니다.
  • sites-enabled : sites-available에 있는 가상 서버 파일들중에서 실행시키고 싶은 파일을 symlink로 연결한 폴더입니다. 실제로 이 폴더에 위치한 가상서버 환경 파일들을 읽어서 서버를 세팅합니다.
  • nginx.conf : Nginx에 관한 설정파일로 Nginx 설정에 관한 블록들이 작성되어 있으며 이 파일에서 sites-enabled 폴더에 있는 파일들을 가져옵니다.


nginx 가성서버 파일 생성

위에서 말씀드린대로 sites-available에 가상 서버 환경 파일을 작성하시면 됩니다.

가상 서버 환경 파일을 만들고 vim으로 열어줍니다.

$ cd /etc/nginx/sites-available
$ sudo touch [virtual_server_conf_file_name]
$ sudo vim [virtual_server_conf_file_name]


Nginx 가상서버 파일 작성

열어둔 설정 파일에 아래처럼 기본적인 블록들을 작성합니다.

[domain name], [upstream name] 그리고 [web root directory path]는 원하는 서버 환경에 따라서 넣어주시면 됩니다.

간단하게 server block만 추가하면 아래와 같습니다.

virtual_server_conf_file_name(no SSL)

server {
        listen 80 default_server;
        listen [::]:80 default_server;
        root /var/www/html;

        server_name [domain name];

        location / {
            try_files $uri $uri/ =404;
        }
}


아래는 SSL을 적용한 저희 서비스의 모습입니다.

virtual_server_conf_file_name(SSL)

upstream [upstream name] {
    server unix://[unix socket file path];
}

server {
    listen 443 ssl;

    root [web root directory path];
    index index.html index.htm;

    server_name [domain name];

    # SSL Setting below
    ...
    # SSL Setting above

    location / {
        try_files $uri $uri/ =404;
    }

    location /api {
        ...
    }

    # SSL Setting (Let's Encrypt)
    location ~ /.well-known {
            allow all;
    }
}

server {
    listen 80;
    listen [::]:80;

    server_name [domain name]];
    return 301 https://$host$request_uri;
}


이제 sites-available에 있는 파일들에 대해서 sites-enabled에 symlink를 추가합시다.

$ cd /etc/nginx/sites-enabled
$ sudo ln -s /etc/nginx/sites-available/[config file name] /etc/nginx/sites-enabled/
$ sudo ln -s /etc/nginx/sites-available/[config file name] /etc/nginx/sites-enabled/


Nginx 재시작하기

이제 Nginx에 문법이 제대로 되어있는지 확인하고 재시작을 합니다.

제대로 재시작이 완료가 되면 OK라고 나와야 합니다.

$ sudo nginx -t

// 재시작은 아래에 있는 명령어 중 하나로 실행하시면 됩니다.
$ sudo service nginx restart
$ sudo systemctl restart nginx
$ sudo /etc/init.d/nginx restart

업데이트(2018.04.29) : 내용을 조금 보완하였습니다.

python을 통한 서버를 돌릴 때 newrelic을 사용하는 방법을 알아보고 나아가 pyenv와 virtualenv를 사용하는 환경에서 나타나는 오류를 어떻게 해결했는지 알아보자


환경

  • python(with Django)
  • pyenv 및 virtualenv


newrelic 연동하기

먼저 newrelic 사이트에 들어가서 가입을 하고 로그인을 하면 아래와 같은 창이 나오며 아래에서 APM을 선택합니다.

New Relic Page


다음 메뉴에서 python을 선택합니다.

Choose Language


위에서 python을 선택하게 되면 1~5까지 어떻게 하면 실행하면 되는지 자세하게 나와있습니다. 상세하게 설명이 되어 있으니 여기서는 간단하게만 요약해서 보여드리겠습니다.


  • Reveal license key를 클릭해서 키를 발급 받습니다.

  • 사용하고자 하는 프로젝트 혹은 파이썬 가상환경에서 newrelic 파이썬 패키지를 설치합니다.

$ pip install newrelic
  • 아래 명령어를 통해서 위에서 만든 license key를 통해 newrelic.ini 파일을 생성합니다. 이는 프로젝트 폴더 안에 두는것이 좋습니다.
$ newrelic-admin generate-config <your-key-goes-here> newrelic.ini
  • NEW_RELIC_CONFIG_FILE 다음에는 위에서 만든 newrelic.ini이 오게하고 run-program 다음에는 원하는 서버 명령어를 입력하시면 됩니다. 저희 django 같은 경우 python manage.py runserver와 같은 명령어를 넣어주시면 됩니다.
$ NEW_RELIC_CONFIG_FILE=newrelic.ini newrelic-admin run-program YOUR_COMMAND_OPTIONS
  • 이제 서버에서 newrelic으로 통신이 오고가면 관리페이지로 이동합니다.


pyenv와 virtualenv에서 newrelic 연동하기

파이썬의 경우 가상환경을 만들어서 실행하는 경우가 많은데 pyenv와 virtualenv를 통해서 실행한 경우에 가끔 다음과 같이 django을 찾을 수 없다고 나올 때가 있습니다.

이 문제는 현재 django가 없다고 합니다. 분명히 설치했는데도 이런 오류가 생기는거면 sys.path를 통해서 저희가 사용하는 가상 환경과 패키지 파일들이 들어가 있는지 확인하는것이 좋습니다.

ImportError: No module named 'django'
unable to load app 0 (mountpoint='') (callable not found or import error)
*** no app loaded. going in full dynamic mode ***
Traceback (most recent call last):
  File "./linku/wsgi.py", line 12, in <module>
    from django.core.wsgi import get_wsgi_application
ImportError: No module named 'django'
unable to load app 0 (mountpoint='') (callable not found or import error)


위와 같은 경우에 sys.path/home/linku/.pyenv/versions/[virtualenv name]/lib/python3.5/site-packages에서 모듈들을 가져오지 않아서 생기는 문제인데 wsgi.py에 위의 경로를 sys.path에 추가해주면 일단 해결이 됩니다. 당연히 이 경로는 사용하시는 분에 따라서 다르며 virtualenv는 설정하신 이름으로 넣어주셔야합니다.

wsgi.py

import os
import sys
sys.path.append('/home/linku/.pyenv/versions/[virtualenv name]/lib/python3.5/site-packages')
from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "[your project name].settings")

application = get_wsgi_application()


Nginx에 gzip 설정을 추가하자


환경

  • Nginx


웹 사이트 압축?

우리가 흔히 사용하는 웹브라우저 및 통신은 HTTP에 기반한 통신이 많습니다.

그러한 웹 통신에서 속도를 개선하기 위해 많은 노력들이 이루어져 왔고 그러한 노력의 결과로 gzip, 브라우저 캐싱, js 및 css 압축 그리고 CDN(contents delivery network)과 같은 여러 방식들이 나왔습니다.

위에 나온 여러 방법들이 존재하지만 nginx에서 가장 쉽게 설정 할 수 있고 웹서비스를 할 때 기본중에 기본인 gzip을 설정하는 방법에 대해서 알아보겠습니다.


gzip이란?

위키피디아에서 가져온 내용을 발췌하면 아래와 같습니다.

  • gzip은 파일 압축에 쓰이는 응용 소프트웨어이다. gzip은 GNU zip의 준말이며, 초기 유닉스 시스템에 쓰이던 압축 프로그램을 대체하기 위한 자유 소프트웨어이다. gzip은 Jean-loup Gailly와 마크 애들러가 만들었다. 버전 0.1은 1992년 10월 31일에 처음 공개되었으며, 버전 1.0이 1993년 2월에 뒤따라 나왔다. 오픈BSD의 gzip 버전은 더 오래된 압축 프로그램을 기반으로 하고 있으며, 오픈BSD 3.4에 추가되었다.

즉, 쉽게 정리하면 gzip은 압축 프로그램 및 방식이라고 할 수 있습니다. 구체적으로 어떠한 방식과 원리로 압축을 하는지에 대해서는 포스팅하지 않겠으나 LZ77허프만 코딩을 조합한 DEFLATE알고리즘에 기반하여 구현한다고 합니다.


Nginx에서 gzip설정하기

Nginx에서 gzip 설정하는 부분

Nginx를 apt-get을 통해서 패키지로 설치를 했다면 /etc/nginx경로에 있는 nginx.conf에서 설정이 가능합니다.

또한 패키지를 통해 설치를 하셨다면 다음처럼 nginx.confhttp블록 안에 Gzip Settings라고 나와있습니다.

nginx.conf

http {

    ...

    ##
    # Gzip Settings
    ##

    // 이 아래 설정들이 주석처리 되어있거나 설정되어 있을 수도 있습니다.

    gzip on;
    gzip_disable "msie6";

    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ...
}


Nginx에서 gzip 설정하기

간단하게 위에 있는대로 주석이 nginx.conf에 나와있고 주석을 제거해주시면 되고 만약 해당 부분이 없다면 아래부분을 nginx.confhttp블록에 그대로 추가해주시면 됩니다.

nginx.conf

...

http{

    ...

    ##
    # Gzip Settings
    ##

    gzip on;
    gzip_disable "msie6";

    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    ...

}

...

각각에 대한 내용은 nginx 공식문서를 참고하면 다음과 같습니다.

  • gzip [on/orr] : gzip의 설정을 on/off하는 명령어입니다.
  • gzip_disable [regex] : 해당 명령어 다음에 오는 문장이 요청 헤더에 User-Agent와 일치하면 gzip을 하지 않습니다.
  • gzip_vary [on/off] : gzip, gzip_static, or gunzip의 설정들이 on으로 되어있을 때 응답 헤더에 “Vary: Accept-Encoding”를 넣을지 말지에 대한 명령어 입니다.
  • gzip_proxied : 요청과 응답에 따라서 프록시된 요청에 대해서 gzipping을 할지 말지에 대한 설정입니다. “any”로 설정하면 모든 응답에 대해서 gzipping을 수행합니다.
  • gzip_comp_level [level] : 응답에 대한 압축의 정도를 의미하며 level의 값은 1부터 9까지 가능합니다.
  • gzip_buffers [num] [size] : num은 버퍼의 갯수를 의미하며 size는 그 버퍼의 크기를 의미합니다.
  • gzip_http_version [versino] : minimum HTTP 통신의 버전을 정합니다.
  • gzip_types [types in array] : “text/html”을 제외하고 MIME 타입에서 또 어떠한 응답 형태들을 gzipping 할지 정할 수 있습니다.


Nginx에서 gzip 설정 적용하기

이제 바뀐 설정을 적용해보도록 하겠습니다. 반드시 재시작 했다는 문구가 나와야합니다!

$ sudo /etc/init.d/nginx restart

or

$ sudo service nginx restart

or

$ sudo systemctl restart nginx


적용결과

이제 gzip을 적용하면 제대로된 응답 요청에 대해서(HTTP STATUS CODE가 200) 다음 처럼 응답 헤더에 Content-Encoding:gzip가 표시됨을 알 수 있습니다.

Response Header


참고자료

Circle CI에서 TEST를 다 통과하면 원하는 서버에 배포하도록 해보자


환경

  • Cricle CI
  • Github Repository
  • Remote Server(Ubuntu 14.04) with SSH key


사전 준비사항

  • yml 설정을 하고 Circle CI를 통해 빌드한 경험이 있어야 합니다.
  • Github를 통해서 Merge하고 Deploy까지 할 예정이기에 Github에 프로젝트가 있어야 합니다.
  • 배포할 서버가 있어야하며 서버에 접속할 수 있는 SSH Key가 있어야 합니다.


배포과정

배포하는 과정은 크게 나누면 아래와 같습니다. 위에 있는 사전 준비사항에 있는 과정들을 다 진행해본 경험이 있어야합니다.

  1. 원하는 프로젝트를 특정 branch에 merge(이 글에서는 develop branch에 merge함을 통해서 진행 할 예정)
  2. CI에서 빌드
  3. CI에서 yml코드에 있는 쉘 스크립트를 통해서 서버에 접속 및 배포 코드 실행
  • Merge(in Github) -> Build(in Circle CI) -> Deploy(in Circle CI and Server)


배포 세팅 과정

위에서는 배포 세팅이 완료되었을 때 어떠한 순서로 배포가 이루어졌는지에 대한 과정이었다면, 이제는 우리가 어떠한 순서로 배포 세팅을 할지에 대해서 입니다.

  1. Github와 연결된 Circle CI project setting에서 PERMISSIONS에 서버 SSH Key를 복사해서 추가합니다.
  2. circle.yml에 deployment를 추가하고 관련된 코드들을 입력합니다.
  3. 원격 서버에 설치 스크립트를 추가합니다.


배포 세팅 과정

Circle CI에 연결된 프로젝트에 SSH Key를 추가

서버에 SSH 접속을 할 때 사용하는 Key를 Circle CI에 추가하도록 합시다.

Circle CI에 들어가서 로그인 하고 대시보드에서 좌측에 보면 프로젝트 목록이 있는데 옆에 있는 톱니바퀴 모양을 클릭해 설정에 들어가도록 합니다.

Circle CI Project Setting Icon


프로젝트에 들어가면 아래 보이는 바와 같이 SSH Permissions를 클릭합니다.

SSH Permissions


그러면 메인 화면에 아래와 같이 SSH Key를 추가 할 수 있는 창이 나오며 여기서 가장 우측에 파란 버튼인 Add SSH Key를 선택합니다.

Add SSH Key


아래와 같은 다이얼로그 창이 나오면 Hostname에 해당서버 이름을 Private Key에 해당 서버에 대한 개인키를 넣습니다.

여기서 개인키를 넣을 때 -----BEGIN RSA PRIVATE KEY-----부터 -----END RSA PRIVATE KEY-----까지 모두 복사해서 붙여넣으셔야 인식을 합니다!

Add SSH Dialog


이제 Circle CI에 Key를 추가하는 부분은 완료 되었습니다. 위의 과정을 제대로 추가하셨다면 Circle CI에서 자동으로 ~/.ssh폴더에 id_[Hostname]의 형태로 키를 저장해줍니다.


circle.yml에 branch 추가 및 커맨드 추가

이제 circle.yml에 특정 branch에 Merge되면 Deplyment를 진행하는 코드를 추가하겠습니다. 코드는 아래와 같이 매우 간단합니다.

circle.yml

...

deployment:
    [name]:
        branch: [branch name]
        commands:
            - [command for deployment]

...
  • [name] : 아무 이름이나 붙이시면 됩니다.
  • [branch] : 변화가 있는 브랜치에 대해서 배포를 할 때 해당 브랜치를 적으시면 됩니다.
  • [commands] : 실행 할 명령어나 스크립트를 추가하시면 됩니다.


LinkU 서비스의 같은 경우 개발환경에 대한 배포는(branch도 develop 브랜치) 아래처럼 만들었습니다.

circle.yml


...

deployment:
    development:
        branch: develop
        commands:
            - ./deploy_to_development_server.sh

...


배포 스크립트 작성

이제 스크립트를 실행시켜서 원격 서버에 접속해 배포하는 과정을 해봅시다.

원격 서버에 접속하고 명령어들을 실행하는 법부터 보면 아래와 같습니다.

#!/bin/bash
ssh [user]@[domain] -i [ssh private key file] -o StrictHostKeyChecking=no <<'ENDSSH'
...
commands
...
ENDSSH
  • <<'ENDSSH' 부터 아래 있는 ENDSSH까지 있는 명령어들을 원격서버에서 실행합니다. 이때 << 다음에 오는건 꼭 ENDSSH일 필요는 없고 원하시는대로 입력해도 되지만 마지막에는 꼭 같아야합니다.
  • StrictHostKeyChecking=no는 ssh key fingerprint를 무시하고(서버에 키를 무조건 저장하고) 진행한다는 의미이며, ssh로 원격 서버에 접속할 때 yes or no룰 묻지 않습니다.


이제 원격서버의 유저 Root Directory에(cd ~) ./set_up_server.sh라는 스크립트가 있다면 아래처럼 실행 할 수 있습니다.

#!/bin/bash
ssh -p [port number] [user]@[domain] -i [ssh private key file] -o StrictHostKeyChecking=no <<'ENDSSH'
./set_up_server.sh
exit
ENDSSH


링쿠 서비스의 경우 다음처럼 스크립트를 작성하였습니다. 다만 보안상 포트 번호만 가리겠습니다. Circle CI에서 저장한 SSH key Host 이름이 linku-dev라면 ~/.ssh/폴더에 접미사 id_가 붙어서 id_linku-dev로 저장되어있습니다!

#!/bin/bash
ssh -p [port number] linku@dev.linkuniversity.me -i ~/.ssh/id_linku-dev -o StrictHostKeyChecking=no <<'ENDSSH'
./set_up_server.sh
exit
ENDSSH


원격 서버에 배포 스크립트 추가

위의 글에서 ./set_up_server.sh라는 쉘 스크립트 파일을 통해서 서버에 서버를 세팅합니다. 해당 sh파일에 원하는 서버 세팅 코드를 추가해주시면 됩니다.

예제로 저희 프로젝트 코드를 보여드리겠습니다.

set_up_server.sh

#!/bin/bash
cd /home/linku
./set_up_frontend.sh
./set_up_backend.sh

다음은 set_up_backend.sh의 코드입니다. 불필요한 부분도 있기에 중간은 생략하겠습니다.

set_up_backend.sh

#!/bin/bash
source ~/.bash_profile
cd /home/linku/LinkU;
git pull origin master;
cd /home/linku/LinkU/linku_backend;
pyenv activate LinkU;
pip install -r requirements.txt;
python manage.py makemigrations;
python manage.py migrate;
UWSGI_COUNT=$(pgrep -c uwsgi)
if [ $UWSGI_COUNT -ge 1 ]
then
        ...
else
        ...
fi
pyenv deactivate;


마무리

이제 develop 브랜치에 merge가 되면 자동으로 서버에 배포가 되게 됩니다! :)