AWS EC2로 웹 서버 띄우기 전에 해야 할 일

필자는 AWS의 EC2 인스턴스를 제대로 활용해 볼 기회가 없었던, 그래서 AWS 초짜이다. EC2 인스턴스를 새로 생성해서 서비스해야 할 일이 생겨서 웹서버를 구축하다가 AWS 서비스의 구조를 잘 몰라서 잠시 고생을 했다. 웹서버를 띄웠는데 인스턴스와 IP는 어떻게 연결해 주어야 할 지, 도메인 세팅을 다 해 주었는데도 왜 접속이 되지 않았는지 고민하게 되었다.

결론은 AWS를 몰라서 생긴 이슈였다. 필자가 경험한 것을 바탕으로 AWS EC2 사용과정에서 꼭 필요한 과정들을 간단히 정리해 보았다. 혹여나 필자와 같은 처지에 있는 분들께 도움이 되길 바라는 마음으로 과정을 공유하고자 한다.

1. EC2 인스턴스를 생성한다.

2. ElasticIP를 추가한 후 생성한 인스턴스를 연결해 준다.

AWS의 인스턴스를 멈추고 다시 시작하면 새로운 내부 IP가 부여된다. 고정된 IP로 서비스하려면 엘라스틱 IP 서비스를 사용해야 한다.

3. 네임서버에 서비스하고자 하는 도메인에 대한 주소를 엘라스틱IP에서 할당받은 IP로 설정해 준다.

카페24나 도레지 등에서 네임서버를 설정할 수 있으면 굳이 라우트 53은 사용하지 않아도 된다.

4. 보안 그룹 페이지에 가서 서비스하고자 하는 포트를 열어준다.

기본적으로 EC2는 ssh 사용을 위해 22번 포트만 열어준다. 따라서 추가로 서비스를 제공하려면 해당하는 포트를 inbound에서 열어줘야 서비스가 가능하다.

docker-compose 사용하기 – 2부. mariadb 추가설정 및 wordpress, nginx 설정

지난 번에 작성하던 docker-compose.yml 파일을 가져왔다.

version: '2.1'
services:
  mariadb:
    container_name: mydb
    image: mariadb:10.2.10
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PW}
    volumes:
      - /Users/aaaa/con_volumes/mariadb/data:/var/lib/mysql
      - /Users/aaaa/con_volumes/mariadb/conf.d:/etc/mysql/conf.d

1부를 보셨던 분이라면 중요한 정보인 DB의 root 비밀번호를 MYSQL_PW라는 환경변수로 추출했던 것을 기억할 것이다.

환경변수로 추출할 부분이 더 있다. 바로 volumes에서 호스트의 경로 설정 부분이다. 컨테이너는 동일한 환경으로 구축해야 하지만, 로컬의 호스트는 사용자마다 다를 수 있다. 각자가 원하는 환경에서 도커를 구동하기 원할 것이다. 그래서 다음과 같이 환경 변수들을 추출하고, 설정파일 .env와 docker-compose.yml을 각각 다음과 같이 변경하였다.

MYSQL_PW=mypassword
MYSQL_DATA_PATH=/Users/aaaa/con_volumes/mariadb/data
MYSQL_CONFIG_PATH=/Users/aaaa/con_volumes/mariadb/conf.d
version: '2.1'
services:
  mariadb:
    container_name: mydb
    image: mariadb:10.2.10
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PW}
    volumes:
      - ${MYSQL_DATA_PATH}:/var/lib/mysql
      - ${MYSQL_CONFIG_PATH}:/etc/mysql/conf.d

이제 어느 개발자의 머신에서든 서버에서든 각자의 입맛에 맞는 설정이 가능하다.

WordPress를 컨테이너로 띄울 것이다. 필자는 wordpress를 php 7.1으로 구동하는 fpm으로 띄울 것이며 도커 이미지 경량화를 위해 alpine 리눅스 이미지를 사용할 것이다. 관련 사항을 docker-compose.yml에 추가하려 한다. mariadb의 환경변수를 추출했던 것처럼 .env에 환경 변수는 별도로 설정한다.

# MYSQL
MYSQL_PW=mypassword
MYSQL_DATA_PATH=/Users/aaaa/con_volumes/mariadb/data
MYSQL_CONFIG_PATH=/Users/aaaa/con_volumes/mariadb/conf.d

# WORDPRESS
WP_DB_USER=root
WP_DB_PASSWORD=mypassword
WP_DB_NAME=my_wp_db
WP_DATA_PATH=/Users/aaaa/con_volumes/wordpress
version: '2.1'
services:
  mariadb:
    container_name: mydb
    image: mariadb:10.2.10
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PW}
    volumes:
      - ${MYSQL_DATA_PATH}:/var/lib/mysql
      - ${MYSQL_CONFIG_PATH}:/etc/mysql/conf.d
  wordpress:
    container_name: wordpress
    image: wordpress:4.8.3-php7.1-fpm-alpine
    restart: always
    environment:
      - WORDPRESS_DB_HOST=mydb
      - WORDPRESS_DB_USER=${WP_DB_USER}
      - WORDPRESS_DB_PASSWORD=${WP_DB_PASSWORD}
      - WORDPRESS_DB_NAME=${WP_DB_NAME}
      - WORDPRESS_TABLE_PREFIX=wp_
    volumes:
      - ${WP_DATA_PATH}:/var/www/html

본 포스팅에서는 편의상 WP_DB_USER를 root로 설정해 주었다. 실제 상황에서는 maria db에 적절한 권한을 가진 사용자를 만들고 적어주면 되겠다.

이제 워드프레스 설정은 끝났다. 워드프레스 설정에 사용한 도커 이미지는 php-fpm으로 돌아가므로 nginx에서 proxy pass로 접근하게 할 것이다. nginx 컨테이너 설정도 추가로 해 준다.

# MYSQL
MYSQL_PW=mypassword
MYSQL_DATA_PATH=/Users/aaaa/con_volumes/mariadb/data
MYSQL_CONFIG_PATH=/Users/aaaa/con_volumes/mariadb/conf.d

# WORDPRESS
WP_DB_USER=root
WP_DB_PASSWORD=mypassword
WP_DB_NAME=my_wp_db
WP_WWW_PATH=/Users/aaaa/con_volumes/wordpress
version: '2.1'
services:
  mariadb:
    container_name: mydb
    image: mariadb:10.2.10
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PW}
    volumes:
      - ${MYSQL_DATA_PATH}:/var/lib/mysql
      - ${MYSQL_CONFIG_PATH}:/etc/mysql/conf.d
  wordpress:
    container_name: mywp
    image: wordpress:4.8.3-php7.1-fpm-alpine
    restart: always
    environment:
      - WORDPRESS_DB_HOST=mydb
      - WORDPRESS_DB_USER=${WP_DB_USER}
      - WORDPRESS_DB_PASSWORD=${WP_DB_PASSWORD}
      - WORDPRESS_DB_NAME=${WP_DB_NAME}
      - WORDPRESS_TABLE_PREFIX=wp_
    volumes:
      - ${WP_WWW_PATH}:/var/www/html
  nginx:
    container_name: mynginx
    image: nginx:1.12.2-alpine
    restart: always
    ports:
      - 80:80
    volumes:
      - ${WP_WWW_PATH}:/var/www/html

일단 위와 같은 설정이면 컨테이너 3개가 모두 정상적으로 뜬다. http://localhost에 접속하면 welcome to nginx! 화면을 볼 수 있다.

이제 nginx에 워드프레스 php-fpm에 대한 proxy_pass를 설정해주면 된다.

그리고 컨테이너 간의 통신을 위해서는 networks 옵션을 사용하면 된다. 사실 지금까지 네트워크 옵션을 주지 않았지만, 자동으로 aaaa_default 라는 네트워크가 생성되어 해당 네트워크를 통해 통신하고 있었던 것이다. 필자는 my-net이라는 네트워크 이름을 지정해서 사용해 보도록 하겠다. 먼저 docker network create my-net 명령으로 네트워크를 생성한 후, 아래와 같이 .env와 compose 파일을 각각 설정한다.

# MYSQL
MYSQL_PW=mypassword
MYSQL_DATA_PATH=/Users/aaaa/con_volumes/mariadb/data
MYSQL_CONFIG_PATH=/Users/aaaa/con_volumes/mariadb/conf.d

# WORDPRESS
WP_DB_USER=root
WP_DB_PASSWORD=mypassword
WP_DB_NAME=my_wp_db
WP_WWW_PATH=/Users/aaaa/con_volumes/wordpress

# NGINX
NGINX_VHOSTS=/Users/aaaa/con_volumes/nginx/vhosts.conf

# NETWORK
MY_NETWORK=my-net
version: '2.1'

services:
  mariadb:
    container_name: mydb
    image: mariadb:10.2.10
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PW}
    volumes:
      - ${MYSQL_DATA_PATH}:/var/lib/mysql
      - ${MYSQL_CONFIG_PATH}:/etc/mysql/conf.d
    networks:
      - mynet

  wordpress:
    container_name: mywp
    image: wordpress:4.8.3-php7.1-fpm-alpine
    restart: always
    environment:
      - WORDPRESS_DB_HOST=mydb
      - WORDPRESS_DB_USER=${WP_DB_USER}
      - WORDPRESS_DB_PASSWORD=${WP_DB_PASSWORD}
      - WORDPRESS_DB_NAME=${WP_DB_NAME}
      - WORDPRESS_TABLE_PREFIX=wp_
    volumes:
      - ${WP_WWW_PATH}:/var/www/html
    networks:
      - mynet

  nginx:
    container_name: mynginx
    image: nginx:1.12.2-alpine
    restart: always
    ports:
      - 80:8080
    volumes:
      - ${NGINX_VHOSTS}:/etc/nginx/conf.d/vhosts.conf
      - ${WP_WWW_PATH}:/var/www/html
    networks:
      - mynet

networks:
  mynet:
    external:
      name: ${MY_NETWORK}

이제 해당 컨테이너들은 my-net이라는 docker network를 공동으로 사용하게 된다. 같은 네트워크를 사용하는 컨테이너끼리는 대상 호스트명을 컨테이너 이름으로 사용할 수 있게 된다. 그리고 기본 엔직엑스 경로가 아닌 워드프레스를 내보낼 것이므로 내부 컨테이너 포트를 8080으로 변경해 주었다. 이제 엔직엑스 설정 파일 vhosts.conf를 살펴보겠다.

server {
    listen 8080;
    server_name localhost;

    root /var/www/html;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?q=$uri&$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass mywp:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    }
}

컨테이너 내부의 서버 포트는 8080으로 설정해 주었다. 그리고 fastcgi_pass는 docker-compose에서 컨테이너 이름으로 설정해 주었던 mywp를 호스트 이름으로 사용하고 있는 것을 볼 수 있다.

모든 설정이 다 되었다면, docker-compose로 컨테이너를 띄운다. 이제 웹 브라우저를 실행하고 localhost에 접속해 보면, 워드프레스 설정 화면을 만나볼 수 있다.

간단히 도커 이미지를 사용해서 워드프레스를 띄우는 작업을 함께 해 보았다. 참, 도커 이미지의 버전을 지정하지 않으면 최신 버전이 설치될 수 있으므로 동일한 환경 설정을 위해서 필자는 도커 이미지의 버전을 모두 지정해 보았다. 그리고 docker-compose에는 build 옵션을 사용하여 Dockerfile을 빌드할 수도 있다. 필자는 docker-compose를 적용한 후 자질구레한 중복 쉘 스크립트들을 제거할 수 있었고, 서버 재시작 시에도 안심하고 서버를 띄울 수 있게 되었다. 컨테이너를 효율적으로 관리할 수 있는 도구들이 또 있다고 한다. 좀 더 학습해 보아야겠다.

혹시 아직도 docker run으로 컨테이너를 띄우고 계신 분이 있다면, docker-compose 적용을 시도해 볼 것을 권한다. 이것으로 2부로 docker-compose 활용에 대한 포스팅을 마친다.

docker-compose 사용하기 – 1부. mariadb 설정

서비스는 하나인데 사용해야 하는 컨테이너가 여러 개인 경우 매번 docker build, docker run, docker stop, docker rm, docker restart 등의 명령어를 사용하는 것은 말도 못하게 번거롭다. 필자는 바로 쉘 스크립트로 해당 명령어들을 작성해서 보관하기 시작했는데, 컨테이너 개수가 늘어나면서 관리할 쉘 스크립트도 덩달아서 늘어나고, 스크립트 내에서 사용하는 환경 변수들에 대한 처리 문제도 덩달아 발생하였다. docker-compose를 사용하면 build부터 컨테이너를 띄우는 순서까지 모두 결정할 수 있으며 docker-compose up -d 라는 명령어 한 줄이면 docker-compose.yml에 설정된 모든 컨테이너를 한 번에 바로 띄울 수 있다.

docker-compose 설정 파일은 YAML을 사용하여 서비스, 네트워크, 볼륨 등을 설정할 수 있다. 간단한 시나리오를 상정하고 docker-compose 파일을 설정해 보려한다. 워드프레스로 웹 사이트를 운영할 것이고, 데이터 베이스는 mariadb를 사용하고, 웹 서버는 nginx를 사용할 것이다. 본 포스팅에서는 우선 mariadb에 대한 docker-compose 파일 설정을 먼저 해 볼 것이다.

그에 앞서 버전 이야기를 먼저 하려고 한다. docker-compose는 docker 엔진 버전에 따라 docker-compose 파일 버전을 맞추어 설정해야 한다. 만일 도커 엔진 버전이 낮으면 해당 엔진 버전에 상응하는 docker-compose 파일 버전 이상을 사용할 수 없다. 따라서 docker-compose를 사용하려면 현재 자신이 사용하는 도커 엔진의 버전을 확인하고 적용할 수 있는 docker-compose 파일 버전을 확인한 후 사용해야 한다.

도커 버전은 다음의 명령어로 확인할 수 있다.

docker —version

일례로 필자가 사용중인 도커 엔진은 1.12 버전이다. 이 경우 docker-compose 파일 버전은 2.1까지 사용할 수 있다. 자신이 사용하는 도커 버전을 확인한 후, 아래 주소에서 자신이 사용할 수 있는 docker-compose 파일 버전을 확인한 후 활용하면 된다.

https://docs.docker.com/compose/compose-file

물론 버전에 따라 사용할 수 있는 문법의 지원 정도에 차이가 있는 것은 당연하며, 도커 엔진 버전에 상응하는 docker-compose 파일 버전보다 높은 버전으로 설정하면 오류가 발생한다.

docker-compose.yml 파일에 다음과 같이 적어주면 docker-compose 파일의 버전 설정이 끝난다.

version: '2.1'

그러면 이어서 mariadb 컨테이너 설정을 해보자. services는 띄울 컨테이너 서비스들을 모아서 설정하는 곳이다. 자신이 띄울 서비스의 이름을 적어준다. 필자는 mariadb라고 적어주었다.

version: '2.1'
services:
  mariadb:
    image: mariadb:10.2.10
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=mypassword
    volumes:
      - /Users/aaaa/con_volumes/mariadb/data:/var/lib/mysql
      - /Users/aaaa/con_volumes/mariadb/conf.d:/etc/mysql/conf.d

위의 설정은 mariadb:10.2.10이라는 image를 사용할 것을 의미한다. 재시작 옵션을 always로 하겠다는 것은 컨테이너 종료 상태와 상관없이 항상 재시작하겠다는 것이다. 이 옵션을 설정해 두면, PC을 껐다 켠 후에 컨테이너가 자동으로 뜨는 것을 확인할 수 있을 것이다.

그 다음은 enviroment 옵션인데, MYSQL_ROOT_PASSWORD를 mypassword로 설정해 주었다.

그리고 volumes 옵션을 통해서 Host 경로를 Container 경로로 마운팅하게 해 놓았는데, 도커 컨테이너를 삭제하면 당연히 컨테이너의 내용은 모두 사라진다. DB을 보존하기 위해서는 호스트 경로로 저장되도록 설정해야 한다. 콜론(:)을 중심으로 왼쪽은 호스트 경로, 오른쪽은 컨테이너 경로이다. /var/lib/mysql 경로는 DB가 저장되는 경로이고, /etc/mysql/conf.d는 mysql 환경설정을 별도로 오버라이딩 하기 위한 경로이다.

도커를 띄우기 전에 /Users/aaaa/con_volumes 디렉토리를 생성해 줘야 한다. mariadb 이후의 경로는 도커가 알아서 생성해 준다.

mkdir con_volumes

여기에서 잠깐! MYSQL_ROOT_PASSWORD는 환경변수이다. git과 같은 버전관리 도구를 사용하면서 docker-compose.yml 파일을 커밋하고 푸시하면, DB의 root 패스워드가 공개된다. 이러면 안된다. 그래서 환경변수로 설정을 해 주어야 한다.

docker-compose는 기본으로 .env 파일을 환경변수 파일로 인식한다. 필요한 변수를 .env에 설정해 주고, .gitignore에 추가한다. 새로 생성한 .env 파일과 변경한 docker-compose 파일은 다음과 같다.

MYSQL_PW=mypassword
version: '2.1'
services:
  mariadb:
    image: mariadb:10.2.10
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PW}
    volumes:
      - /Users/aaaa/con_volumes/mariadb/data:/var/lib/mysql
      - /Users/aaaa/con_volumes/mariadb/conf.d:/etc/mysql/conf.d

이제 docker-compose.yml 파일을 안전하게 커밋해도 된다. 각자의 환경변수로 필요한 설정을 해서 사용하면 될테니 말이다.

이제 docker-compose 명령어로 컨테이너를 띄워보자.

docker-compose up -d

위와 같이 입력하면 기본적으로 .env 파일에서 환경변수를 읽어들이고, docker-compose.yml 파일을 찾아서 컨테이너를 띄우게 된다. 만약 docker-compose.yml 파일을 사용하지 않고 파일이름을 특정하고 싶다면 자신이 원하는 파일이름으로 저장한 후,  docker-compose -f FILENAME.yml up -d이라고 해 주면 된다.

docker-compose의 up 명령어는 컨테이너를 새로 생성하고 띄우라는 것이고, -d는 background 모드로 실행하겠다는 것이다. 이제 컨테이너가 작동하는지 확인해 보자.

docker-compose ps

aaaa_mariadb_1 라는 이름의 컨테이너가 떠 있는 것을 확인할 수 있다. 동일한 서비스이름을 사용할 수 있기 때문에 접두어와 접미어가 붙어있다. 접두어와 접미어를 붙이지 않고 그냥 mydb라고 간단히 명명하고 싶다면 다음과 같이 파일을 수정하면 된다.

version: '2.1'
services:
  mariadb:
    container_name: mydb
    image: mariadb:10.2.10
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PW}
    volumes:
      - /Users/aaaa/con_volumes/mariadb/data:/var/lib/mysql
      - /Users/aaaa/con_volumes/mariadb/conf.d:/etc/mysql/conf.d

일단 docker-compose 사용하기의 첫 포스팅은 여기까지이다. 글이 너무 길어지는 것 같아서 다음 포스팅에 wordpress와 nginx 설정을 차례로 하려한다.

SSL configuration generator & security test links

Mozilla에서 Apache, Nginx 등의 SSL 설정 내용을 생성해 주는 페이지가 있다.

Mozilla SSL configuration generator
https://mozilla.github.io/server-side-tls/ssl-config-generator

SSL 설정이 얼마나 잘 되었는지, 해당 도메인으로 접속했을 때 얼마나 안전한지, 접속 호환성이 있는지 확인할 수 있는 페이지가 있다. 테스트 결과를 등급과 점수로 제시하고 있어서, 현재 운용중인 사이트의 보안 수준을 확인하기에 좋다.

SSL Security Test
https://www.ssllabs.com/ssltest

SSL 보안 테스트를 한 후, XP에 IE 8인 경우 서비스를 제공할 수 없는 상태라는 것을 확인하였다. 그리고 해당 운영체제와 브라우저를 지원하려면 nginx를 해당 알고리즘을 지원하도록 다시 컴파일해서 설정하면 된다고 한다.

참고자료: https://ablagoev.github.io/ssl/nginx/ie8/winxp/cipher/2016/12/23/ie8-winxp-nginx-ssl.html

이런 정보를 알게된 후 XP 운영체제에서의 IE 8의 호환성을 제공하는 것이 중요한지 의문이 들었다. 게다가 해당 환경에서 사용할 알고리즘은 이미 보안 취약성을 가져서, 안전하지 않다. 진정으로 사용자를 위하는 길이 XP의 IE 8 호환성을 유지하는 것일까? 아니면 조금 위험하더라도 사용자가 접속할 수 있도록 길을 터 주어야 하는 것일까. 다시 말해서, 모든 사용자가 차별받지 않고 서비스를 이용할 수 있어야 하는가. 아니면 일부 소외된다 하더라도 안전한 서비스를 제공할 것인가의 문제다.

서비스를 제공하는 입장에서 XP 운영체제에 IE 8을 사용해야만 하는 사용자에게 미안한 마음이 들거나, 서비스를 이용하지 못하게 되는 것에 대한 아쉬움이 있을 수 있다. 하지만, 그 미안함과 아쉬움 때문에 사용자를 위험에 빠뜨려서는 안 될 것이다. 따라서 안전한 서비스를 제공하고 사용자에게 이용할 수 있는 안전한 환경을 제시하는 게 더 나은 길이라 생각한다.


간밤에 글을 쓰고 다시 고민해 보았다. 그럼에도 불구하고 어쩔 수 없는 타협점이 필요할 것이라는 생각이 들었다. 취약점이 모두 제거된 최신 브라우저나 단말기 사용자만 지원하는 게 보안상 더 안전할 수는 있겠지만, 과도하게 사용자의 접근성을 제한할 수도 있겠다는 생각이 들었다. 그런 지점에서 본다면, 서비스의 보안 담당자는 적절한 타협점을 찾고 의사결정을 하는 과정이 필요할 것이다.

Dockerfile –build-arg option

도커 컨테이너를 띄울 때 volume을 설정해서 persistence를 잡아 줄 때, 컨테이너에 마운팅되는 방식이 머신별로 차이가 있는 것을 발견하였다.

필자는 php:7.1.10-fpm-alpine 이미지를 이용하여 컨테이너를 띄웠다.

볼륨을 마운트하면, 마운팅 된 디렉토리의 UID와 GID가 계속 1000으로 나오는 것이었다.

컨테이너에서 php-fpm을 띄우는 사용자는 www-data로 세팅해 놓았고, www-data 사용자의 UID와 GID는 둘 다 기본으로 82로 잡혀 있었다.

www-data 사용자로는 마운팅된 디렉토리에 쓰기 permission이 주어지지 않았으니 기록을 못하는 것이 당연하다.

그래서 찾아낸 것이 Dockerfile을 빌드할 때 주는 옵션이 있다는 것을 알게 되었다. 바로 “–build-arg 변수명=변수값”이다. 컨테이너 내의 사용자인 www-data의 UID를 Host에서 현재 docker를 사용하는 사용자의 UID로 바꾸면 된다.

docker build \
  --build-arg HOST_UID=$(id -u) \
  --build-arg HOST_GID=$(id -g)

위와 같이 UID와 GID를 설정하고 Dockerfile에는 아래와 같은 내용을 추가하였다.

ARG HOST_UID
ARG HOST_GID

RUN usermod -u ${HOST_UID} www-data
RUN groupmod -u ${HOST_GID} www-data

도커 이미지를 생성한 후 컨테이너의 www-data 사용자의 UID가 바뀐 것을 확인하였다.

이렇게 하면, 개발자마다 로그인하여 사용하는 PC의 UID가 다르더라도 각자의 환경에 맞게 적용이 가능하다.

필자는 개발용 머신으로 우분투 17.10을 사용 중인데, 별 문제 없이 잘 되었다. 하지만, MacOS에서는 Group 이름이 staff이며 GID는 20이라서 다음과 같이 충돌이 발생한다.

groupmod: GID '20' already exists

테스트 해 본 결과 Mac에서는 GID를 변경하지 않아도, Host의 파일의 권한이 Host의 UID와 GID로 유지된다. 하지만 Host가 리눅스인 경우 GID를 변경하지 않으면 컨테이너의 www-data 그룹의 기본 id인 82로 세팅되는 것을 확인하였다.

따라서 Mac에서는 빌드 시에 다음과 같이 GID 세팅을 빼 주면 된다.

docker build \
  --build-arg HOST_UID=$(id -u)

Dockerfile에서는 GID 변경 부분을 제외한다.

ARG HOST_UID

RUN usermod -u ${HOST_UID} www-data

작성하고 보니, 이 부분들도 쉘 스크립트를 작성하면 중복을 제거하고 하나의 파일로 구동이 가능할 것 같다. 이 부분은 독자의 몫으로 남겨놓겠다.

Tutorial of automated Letsencrypt certificates with nginx

지난 포스팅에서는 certbot을 사용하여 gitlab의 인증서를 설치하는 법에 대해서 작성하였고, 자동으로 인증서를 재발행하도록 해 보아야겠다고 생각했었는데, 오늘은 엔진엑스에 인증서를 설정하고 자동으로 갱신되도록 하는 부분에 대해 작성해 보려 한다.

지난 포스팅 이후에 인증서 재발행을 간단히 실행해 보고, 문제가 될 수 있는 지점을 하나 발견하였다. –manual 옵션으로 인증서를 발행한 경우에는 certbot renew 명령으로 인증서 재발행이 불가능하다는 것이다.

기본적으로 certbot은 인증서를 생성하는 과정에서 해당 도메인에 접속가능한 것을 확인한 후에 인증서를 발행해 준다. 인증서를 수동으로 발행할 때에도 정해진 경로에 해당 파일을 생성하고 certbot에서 생성한 키를 파일 내용으로 적어서 저장한 후, 이후 과정을 진행했었다.

–manual을 쓰지 않아야 한다면, 명령줄 하나만으로도 가능하지 않을까라는 생각이 들었고, 어렵지 않게 파악한 명령줄은 다음과 같다.

certbot certonly \
  --webroot -w /var/www/blog.osg.kr \
  -d blog.osg.kr \
  --agree-tos

위에 입력한 옵션들에 대한 설명은 다음과 같다.

certonly(부명령어)는 인증서를 발행까지만 하고 자동 설치는 하지 않을 때 사용한다.
–webroot는 위에서 이야기한 인증 파일을 둘 경로를 지정할 때 사용하며, -w 옵션으로 해당 경로를 설정한다. 이 옵션을 사용할 때는 해당 경로를 미리 생성해 놓아야 한다.
-d는 SSL 인증서를 발행할 대상 도메인 이름을 지정할 때 사용한다.
–agree-tos는 이용약관에 동의한다는 것을 옵션으로 설정하는 것이다.

위와 같이 입력하고 실행하면 되는데, 뭔가 빠진 느낌이 들지 않는가. 그렇다. 위의 명령어를 실행하기 전에 먼저 해 주어야 할 것이 있다. 그건 바로 nginx에 .well-known 디렉토리에 대한 경로를 설정해 주어야 하는 것이다.

server {
    listen 80;
    server_name blog.osg.kr;

    ...
    
    location /.well-known/acme-challenge {
        root /var/www/blog.osg.kr/;
    }
}

파랗게 적은 부분은 .well-known/acme-challenge의 경로의 root를 설정해 주는 것이다. root로 설정했기 때문에 실제 파일의 경로는 아래와 같은 경로가 된다. 이제 nginx를 재시작 해 준다.

/var/www/blog.osg.kr/.well-known/acme-challenge/인증파일명

위에서 언급했던 certbot 명령줄을 입력해 주면, certbot이 위의 파일을 엑세스 한 후 해당 파일을 삭제하고, 최종적으로 인증서를 발행해준다. 인증서 발행 경로를 따로 지정하지 않았으므로 /etc/letsencrypt/live/blog.osg.org에 인증서가 발행되게 된다.

인증서 발행 후에는 nginx의 설정 파일에 아래와 같이 추가로 HTTPS 접속에 대한 설정을 해 주어야 한다. 인증서 적용에 필요한 부분만 적었기에 다른 부분들은 생략하였다.

server {
    listen 443 ssl;
    server_name blog.osg.kr;

    ...

    ssl_certificate /etc/letsencrypt/live/blog.osg.kr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.osg.kr/privkey.pem;
}

SSL은 공개키 알고리즘을 사용하므로 ssl_certificate은 full chaining된 인증서의 경로를 적어주면 되고, ssl_certificate_key는 해당 인증서에 대한 개인키 경로를 적어주면 된다.

이제 엔진엑스를 재시작한 후, 자신의 도메인에 접속(필자의 경우 https://blog.osg.kr)하면 다음과 같이 자물쇠 표시가 된다.

이제 이 경로를 통해서 주고 받는 패킷들은 공개키 알고리즘을 사용해서 암/복호화가 이루어진다.

이제 앞서 이야기한 것처럼 인증서를 자동으로 재발행하는 게 가능하다.

certbot renew

라는 명령어 한 줄이면, 발행한 모든 인증서를 갱신해 준다. 하지만 발행한 지 일정 시간이 지나지 않으면 아직 발행할 때가 아니라며 인증서를 갱신해 주지 않는다.

이럴 때 강제로 인증서를 갱신하는 옵션이 있다.

certbot renew --force-renewal

–force-renewal 옵션을 추가로 적어주기만 하면, 인증서가 자동으로 갱신된다.

이제 해당 명령을 쉘스크립트로 생성하고, crontab에 원하는 주기에 한 번씩 실행되도록 등록해 주면 모든 준비가 끝난다. 인증서 갱신 때문에 인증서 설정을 새로한다든가 하지 않아도 된다. 단, 갱신된 인증서를 적용하려면 웹서버를 재시작해 주어야 한다. 웹서버 재시작까지 스크립트로 미리 준비해 둔다면, 별도로 웹서버를 재시작할 필요도 없을 것이다.

아참, 아직 crontab에 등록하는 것까지는 하지 않았다. 조만간 실행해 보고 잘 되면 추가로 포스팅 하도록 하겠다.

이제 인증서 발행과 설정 때문에 리소스를 과도하게 투입하지 않아도 된다. 사람이 손으로 일일이 설정하는 것들은 자동화해야 한다. 이렇게 자동화하면 아무리 많은 인증서도 자동으로 갱신되니 신경쓸 필요가 없다. 모든 설정이 끝났다면 인증서 갱신에 대한 걱정은 내려놓고, 집중해야 할 개발업무로 달려가면 된다.

docker –link option is deprecated

도커가 컨테이너를 띄울 때 내부 아이피를 사용하는데, 그 아이피가 고정되어 있지 않기 때문에, 컨테이너 간에 접근할 때 아이피를 사용하면 그 때 당시에는 되겠지만, 추후에 컨테이너를 다시 띄우면 IP 주소가 새로 할당되기 때문에 컨테이너 이름을 호스트 이름으로 사용해야 한다.

과거에는 –link 옵션을 사용해서 컨테이너를 서로 연결해 주었다. 이제는 docker network가 그 기능을 대신하고 있고, –link 옵션은 deprecated 상태이다. 언젠가 완전히 사라질 것이다.

네트워크를 설정하는 방법은 우선 도커 네트워크를 생성한다. 그 후에 컨테이너를 띄울 때 해당 네트워크를 사용하도록 설정해 주면 된다. 해당 네트워크를 사용하는 컨테이너에서 접근하고자 하는 대상 컨테이너 이름을 호스트 이름으로 사용할 수 있게 된다.

우선 도커 네트워크를 생성할 이름과 함께 아래와 같이 입력한다.

docker network create NETWORK-NAME

ls 명령어를 사용하여 도커 네트워크가 추가된 것을 확인한다.

docker network ls

이제는 특정 컨테이너를 실행할 때 사용할 네트워크를 지정해 준다. mysql과 php-fpm을 사용한다고 가정하고 아래와 같이 –network 옵션을 설정해 준다.

docker run ..... --name mysql --network NETWORK-NAME mysql:5.x.x
docker run ..... --name php --network NETWORK-NAME php:5.6-fpm

이제 호스트이름을 그냥 사용하면 된다. 위의 경우와 같이 컨테이너를 띄웠다면, 각 컨테이너의 이름인 mysql, php로 상호간에 접근할 수 있게 되었다.

그리고 이렇게 하면 해당 컨테이너의 해당 포트로 바로 접근 가능하므로 굳이 port를 publish 하면서 사용하지 않아도 된다.

만일 NGINX 컨테이너를 하나 더 띄워서 nginx.conf 에서 fastcgi를 설정하는 경우라면 php-fpm은 보통 9000번 포트를 사용하니 아래와 같이 설정해 주면 된다. 해당 서버 설정 구간에 다음과 같이 호스트명으로 잡아주면 된다.

fastcgi_pass php:9000;

 

참고자료: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/

https://docs.docker.com/engine/userguide/networking/

Vultr 서비스 사용기

국내 호스팅으로 CAFE24를 아주 작게 사용해왔었다. 월 500원의 비용으로 매우 저렴하게 이용했었다. 워드프레스로 블로그를 시작하면서부터 SSL 인증서가 적용되지 않는 점이 마음에 걸렸다. Letsencrypt를 사용하여 발급받은 인증서는 CAFE24에 자동으로 업로드가 되지 않아서 고객센터를 통해서 요청해야 했고, CAFE24의 응대는 깔끔한 느낌이 아니었다. 그리고 결정적으로 SSL을 443 포트로 서비스 받을 수 없었다.

“클라우드 서비스 중에 좀 저렴하고 쓸만한 게 없을까? 혹시 지금쯤이면 좀 쓸만할 게 있을까?” 하는 마음으로 반신반의하며 구글링한 결과 해외 서비스로 VULTR가 있다는 것을 알게 되었다. 여러 서비스들이 있었지만, 가격대비 스펙이 매우 훌륭하여 선택하였다.

현재 VC2 서비스로 5불 짜리를 이용하고 있다. 가장 저렴한 요금제는 2.5불 짜리가 있는데, 1 CPU, SSD 20G, 메모리 512MB, 트래픽 500GB인데, 운영체제까지 입맛에 맞게 골라서 쓸 수 있다는 점에 확 넘어가 버렸다. 카페24의 최저 요금 서비스가 월 500원으로 SSD 200MB에 트래픽 400MB을 제공하는 것에 비하면 가성비는 Vultr의 압승이라는 생각이 들었다.

참고로 Windows OS는 월 16불의 추가비용을 내고 사용할 수 있다. 하지만, ISO 파일을 업로드 하면 추가 비용 없이 사용할 수 있다. 해당 ISO 파일은 VirtIO 세팅이 된 파일이 아니면 설치가 제대로 되지 않는다고 한다.

처음에는 가격부터 따져보았다. 어차피 큰 요금 차이가 아니니 써보고 로케이션이 맘에 안 들면 바꾸자고 생각했다.

월 2.5불의 최저 요금제를 지원하는 Location은 미국 마이애미와 뉴저지였다. ping을 쳐 보니 300~400ms 정도가 나왔다. 그래도 쓸만하지 않을까 하는 마음으로 일단 뉴저지 Location으로 신청해 보았다. 터미널을 사용하는데, 역시 반응속도 300~400ms는 답답한 감이 많았다. 싸게 쓰려 했으나, 지리적으로 우리나라에 사는 한 뉴저지는 불편하겠다고 판단했다.

그래서 고민을 다시 시작했다. 커피 한잔에 5~6천원 한다고 보면 그냥 5불짜리를 사용하는 것도 나쁘지 않겠다는 생각이 들었다.

5불짜리는 1 CPU, SSD 25G, 메모리 1GB, 트래픽 1TB 이었다. 이참에 한 달에 하루는 날을 정해서 음료고 뭐고 먹지 말자고 다짐하고, 그 돈으로 서버를 굴리자고 생각하고 다시 로케이션을 탐색했다.

가장 가까운 곳으로 도쿄와 싱가폴이 있었고, 그 다음으로 미국의 로스엔젤레스와 시애틀이 눈에 들어왔다. VULTR에서는 핑 테스트 할 수 있는 정보를 다음 경로에서 제공하고 있다.

https://www.vultr.com/faq/#downloadspeedtests

집에서는 KT 0.5G 인터넷을 사용중인데, ping을 날려보면 도쿄는 무슨 이유인지 반응속도가 30~100ms 에 이르기까지 널을 뛰는 모습을 보여주었고, 싱가폴은 90~100ms, 시애틀은 140~160 정도를 오가며 안정적인 모습을 보여주었다. 마지막으로 LA를 찍어보았는데, 134~136ms 정도를 꾸준히 안정적으로 유지하는 모습을 보여줬다.

도쿄는 가깝고 빠를 땐 빠르지만 안정적이지 못하다는 느낌을 받았다. 사실 일본은 자연 재해가 발생할 경우 그렇게 안전하지 않을 수도 있다는 생각이 들어서 선택지에서 제외했다. 싱가폴도 가깝기는 하지만, 동남아 쪽이 아닌이상 국제 서비스를 하기에는 반응속도 때문에 이용자들이 너무 느리다고 생각할 수 있겠다는 생각이 들었다.

LA를 선택하면 필자도 크게 불편을 느끼지 않을 것 같았고, 미국 전역에서 접속해도 느리다는 생각을 하지 않을 것 같았다. 뭐 아직 미국을 대상으로 무슨 서비스를 할 생각은 없지만, 마음은 늘 원대하게 품어보는 것이니ㅎㅎ

결국 LA에 VC2 인스턴스를 생성하였으며, 필자는 도커 기반으로 서버를 운영할 것이라 운영체제는 CoreOS를 선택해서 설치하였다.

스냅샷 기능을 사용하면 현재의 상태를 보존할 수 있다. 다른 인스턴스로 이관할 필요가 있을 때 사용하면 된다. 고스트 이미지 떠 놓은 느낌이랄까. 일단 오늘 워드프레스를 이전한 기념으로 VULTR 사용기를 작성해 보았다.

참, VULTR은 회원에게 보상해 주는 제도가 있다. 회원의 소개로 가입해서 결제하면, 회원에게 보상을 해 준다. 보상 비용은 Paypal 계좌로 받을 수도 있고, 서버 운영비 계좌로 받을 수도 있다. 아래와 같은 배너도 제공해 준다. 원하는 배너를 선택해서 활용하면 되겠다.

그럼 VULTR 서비스에 대해 자세히 알아보려면, 클릭↓ 꾹~*

phinx 실행시 timezone 문제 발생하는 경우

사실 phinx의 문제라기보다는 php 환경변수로 timezone이 설정되지 않아서 발생하는 경고 메시지이다.

필자는 phinx를 사용하여 마이그레이션하려할 때 아래와 같은 경고 메시지를 받았다.

Warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone.

설명에도 나와있듯 php.ini에 date.timezone 값을 설정해 주거나, 관련 코드 실행시에 date_default_timezone_set()을 사용하면 된다.

우리나라의 경우에는 ‘Asia/Seoul’을 다음과 같이 적어주면 된다.

date.timezone = Asia/Seoul

date_default_timezone_set(‘Asia/Seoul’);

필자는 서버 환경 설정과 관계 없이 코드를 통해 동일한 결과를 만들어 내기 위해 phinx.php에 코드로 작성하여 문제를 해결하였다.

phinx db migration tool setting

phinx.yml 에서

host를 localhost로 할 때엔 unix_socket을 정해줘야 한다.

host가 localhost인데 unix_socket이 설정되어 있지 않으면, 다음과 같은 에러를 보게 된다.

[InvalidArgumentException]
There was a problem connecting to the database: SQLSTATE[HY000] [2002] No such file or directory

unix_socket을 사용하지 않으려면 host를 127.0.0.1로 하면 된다.

참고자료: https://stackoverflow.com/questions/22188026/sqlstatehy000-2002-no-such-file-or-directory/25782795#25782795