jupyter notebook에 scala 커널 올려보기

어제는 자바를 쥬피터에 붙여보았는데,
브루스 테이트의 세븐랭귀지를 공부하는 중이고
마침 공부해야하는 챕터가 스칼라라서 스칼라도 붙여보기로 했다.

찾아보니 스칼라는 제플린이나 암모나이트 같은 녀석들도 있었는데,
여러가지 툴을 사용하는 것도 귀찮고, 쥬피터 노트북으로도 공부는 충분히 하고도 남을 것 같아서
쥬피터 노트북에 설치해보기로 했다.

다운로드 및 설치

➜ git clone https://github.com/alexarchambault/jupyter-scala.git
➜ cd jupyter-scala
➜ sbt publishLocal

Getting org.scala-sbt sbt 0.13.15 (this may take some time)…
downloading https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt/0.13.15/jars/sbt.jar …
[SUCCESSFUL ] org.scala-sbt#sbt;0.13.15!sbt.jar (7448ms)
downloading https://repo1.maven.org/maven2/org/scala-lang/scala-library/2.10.6/scala-library-2.10.6.jar …
[SUCCESSFUL ] org.scala-lang#scala-library;2.10.6!scala-library.jar (30749ms)
downloading https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/main/0.13.15/jars/main.jar …
[SUCCESSFUL ] org.scala-sbt#main;0.13.15!main.jar (17395ms)

….한…참걸림 and 중략
[info] published scala-cli_2.12.2 to /Users/andy/.ivy2/local/org.jupyter-scala/scala-cli_2.12.2/0.4.3-SNAPSHOT/poms/scala-cli_2.12.2.pom
[info] published scala-cli_2.12.2 to /Users/andy/.ivy2/local/org.jupyter-scala/scala-cli_2.12.2/0.4.3-SNAPSHOT/jars/scala-cli_2.12.2.jar
[info] published scala-cli_2.12.2 to /Users/andy/.ivy2/local/org.jupyter-scala/scala-cli_2.12.2/0.4.3-SNAPSHOT/srcs/scala-cli_2.12.2-sources.jar
[info] published scala-cli_2.12.2 to /Users/andy/.ivy2/local/org.jupyter-scala/scala-cli_2.12.2/0.4.3-SNAPSHOT/docs/scala-cli_2.12.2-javadoc.jar
[info] published ivy to /Users/andy/.ivy2/local/org.jupyter-scala/scala-cli_2.12.2/0.4.3-SNAPSHOT/ivys/ivy.xml
[success] Total time: 160 s, completed Apr 20, 2018 10:36:21 PM

jupyter에서 사용할 scala 설치

➜ ./jupyter-scala –id scala-develop –name “Scala (develop)” –force

이것도 한참 걸림…

잘 되는지 확인

➜ jupyter kernelspec list

➜ jupyter kernelspec list
Available kernels:
scala-develop /Users/andy/Library/Jupyter/kernels/scala-develop
python3 /Users/andy/.pyenv/versions/py3/share/jupyter/kernels/python3

위와 같이 scala가 있으면 성공임

아래는 연습으로 끄적여 본 스크립트입니다~

jupyter notebook에 자바 커널 올려보기

1. java jdk 설치

아래 링크에서 받아서 설치
http://www.oracle.com/technetwork/java/javase/downloads/index.html

버전 확인 (jdk 9 버전 이상이 필요하다함)

<code>➜ ~ java -version
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
</code>

2. 자바 커널 설치

<code>➜ git clone https://github.com/SpencerPark/IJava.git --depth 1
➜ ~ chmod +x gradlew
✗ ./gradlew installKernel
... 중략
BUILD SUCCESSFUL in 42s
</code>

쥬피터 커널 확인하기

<code>➜ jupyter kernelspec list
Available kernels:
java /Users/gyus/.ipython/kernels/java
python3 /usr/local/opt/pyenv/versions/3.6.0/share/jupyter/kernels/python3
</code>

아래와 같이 테스트 해보았다.

pipenv 소개

Pipenv 뭐하는 녀석인가?

  • python.org 에서 공식적으로 추천하는 패키지 관리툴
  • 세계에서 제일가는 패키징툴을 만드는게 목표라고 한다…
  • virtualenv 환경을 자동으로 만들어 준다.
  • Pipfile 에 패키지를 더하거나 빼거나 하면 됨.
  • Pipfile이라는 녀석이 Pipfile.lock 이라는 겁나 중요한 파일을 생성하는데, 결정론적 빌드(deterministic builds)에 쓰인다.
  • 이거 만든사람은… requests 만든사람이다. requests를 만들었을 때처럼 인터페이스가 깔끔하다.
  • 지 블로그에는 주말에 만들었다고 올려놨다… https://www.kennethreitz.org/essays/announcing-pipenv
  • 주말에 만들었다고는 하지만, 케네스의 깃헙을 보면 비슷한 시도들을 많이 한것으로 보인다.
  • 즉, 능력자의 삽질의 결과물인 것이다.

Pipenv가 해결하려고 하는 문제들

  • pip 와 virtualenv 를 분리된 상태로 사용하는 것이 아니라, pipenv 하나만 사용하기
  • requirement.txt 파일의 관리를 별도의 코드가 아닌 Pipfile, Pipfile.lock 으로 할 수 있도록 해주는 것
  • 해쉬의 자동생성(보안관련)
  • pipenv graph 명령어로 의존성 그래프를 직관적으로 볼 수 있음
  • .env 파일들을 사용한 스트림라인 개발 워크플로우

기능들

  • 딱 필요한 것만 정의 하면서도, 레알 결정론적 빌드가 가능하다.
    • 결정론적 빌드라는게 뭐냐면 그냥 파일에 정해진대로 빌드를 할 수 있다는 말이고,
      빌드가 상황과 환경에 좌지우지 되지 않는 다는 것을 말한다.
  • 락이 걸린 의존성에 대한 해쉬 파일들을 만들어준다. (Pipenv.lock 파일)
  • pyenv가 설치되어 있으면, 파이썬도 자동으로 설치해줌
  • Pipfile 을 사용해서 자동으로 프로젝트 홈을 찾아줌
  • Pipfile이 없으면 자동으로 만들어줌
  • 자동으로 virtualenv 환경을 만들어줌
  • Pipfile 에서 추가/삭제하면 해당 패키지도 추가/삭제 됨. (오 좀 짱인듯)
  • .env 파일이 있으면 자동으로 인식 (환경변수를 자동으로 인식할 수 있음)
  • 주된 커맨드는 install, uninstall, lock 이고, 요넘들은 Pipfile.lock 이라는 파일을 만들어냄.

기본 컨셉

  • virtualenv 환경(이 없으면) 자동 생성
  • install 커맨드에 파라메터를 안넣으면 정의 되어 있는 의존성 몽땅 설치
  • pipenv --three 실행하면 python3 환경을 뚝딱 만들어줌
  • pipenv --two 실행하면 python2 환경을 만들어줌
  • 이러나 저러나 virtualenv 는 기본이자 기본이다.
  • 한마디로 줄이면 자동생성을 수동으로(커맨드는 날려야되니) 편리하게 해준다 정도

다른 명령어들

  • shell : virtualenv 환경 활성화
  • run : virtualenv 의 run 커맨드랑 동일
  • check : 현재의 환경이 PEP508 의 스펙 내용을 만족하는지 확인
  • graph : 의존성 트리를 보여줌

Shell Completion (뭐라해야될지 모르겠지만, 쉘에 넣어두면 pipenv가 활성화됨)

  • eval “$(pipenv –completion)”
  • fish shell 이 젤좋은  쉘이라고 쓰라고 약을 팔고 있으나 안쓸꺼임 ㅎㅎ
#### 프로젝트 위치 찾기
$ pipenv --where
#### virtualenv 위치 찾기
$ pipenv --venv
#### python 인터프리터 위치 찾기
$ pipenv --py

사용법

$ pipenv
Usage: pipenv [OPTIONS] COMMAND [ARGS]...
Options:
  --update         Update Pipenv & pip to latest.
  --where          Output project home information.
  --venv           Output virtualenv information.
  --py             Output Python interpreter information.
  --envs           Output Environment Variable options.
  --rm             Remove the virtualenv.
  --bare           Minimal output.
  --completion     Output completion (to be eval'd).
  --man            Display manpage.
  --three / --two  Use Python 3/2 when creating virtualenv.
  --python TEXT    Specify which version of Python virtualenv should use.
  --site-packages  Enable site-packages for the virtualenv.
  --jumbotron      An easter egg, effectively.
  --version        Show the version and exit.
  -h, --help       Show this message and exit.
Usage Examples:
   Create a new project using Python 3.6, specifically:
   $ pipenv --python 3.6
   Install all dependencies for a project (including dev):
   $ pipenv install --dev
   Create a lockfile containing pre-releases:
   $ pipenv lock --pre
   Show a graph of your installed dependencies:
   $ pipenv graph
   Check your installed dependencies for security vulnerabilities:
   $ pipenv check
   Install a local setup.py into your virtual environment/Pipfile:
   $ pipenv install -e .
Commands:
  check      Checks for security vulnerabilities and...
  graph      Displays currently–installed dependency graph...
  install    Installs provided packages and adds them to...
  lock       Generates Pipfile.lock.
  open       View a given module in your editor.
  run        Spawns a command installed into the...
  shell      Spawns a shell within the virtualenv.
  uninstall  Un-installs a provided package and removes it...
  update     Uninstalls all packages, and re-installs...

설치하기

$ pip install pipenv

의존성 패키지 설치

### 1개 설치
$ pipenv install pytest
$ pipenv install

개발환경일때의 의존성 패키지 설치

### 1개 설치
$ pipenv install pytest --dev
### 전부 설치
$ pipenv install --dev

파이썬 shell 사용하기

$ pipenv shell

프로젝트에 적용 해보기

django로 되어 있는  프로젝트에 적용해 보자

### 프로젝트 루트 디렉토리로 이동
$cd exid
### 아무것도 없지만, 몽땅 설치를 실행한다!
$ ppipenv install
Creating a virtualenv for this project…
⠋Using base prefix '/pyenv/versions/3.4.3'
New python executable in /
Installing setuptools, pip, wheel...done.
Virtualenv location: /
Requirements.txt found, instead of Pipfile! Converting…
Warning: Your Pipfile now contains pinned versions, if your requirements.txt did.
We recommend updating your Pipfile to specify the "*" version, instead.
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (489182)!
Installing dependencies from Pipfile.lock (489182)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 31/31 00:00:25
To activate this project's virtualenv, run the following:
 $ pipenv shell
Spawning environment shell (/usr/local/bin/zsh). Use 'exit' to leave.
source /activate
# Load pyenv-virtualenv automatically by adding
# the following to ~/.zshrc:
eval "$(pyenv virtualenv-init -)"
  • pipenv 가 실행된 다음의 로그를 읽어보자
  • virtualenv 환경이 없으니 pipenv가 pyenv의 파이썬 버전을 읽어서 virtualenv를 하나 만들어준다. virtualenv 이름이 마음에 안들긴 하는데, 딱히 잘 안쓰니 상관없나 싶기도 함
  • 놀랍게도 requirements.txt 파일이 있는 것을 찾아냈다.
  • Pipfile.lock 도 없어서 만들어주고, requirements.txt에 설정된 패키지들을 몽땅 설치하고, Pipfile.lock 도 업데이트를 한다.

pipenv 로 만든 virtualenv 환경 활성화 하기

#### pipenv shell 을 실행하면 virtualenv 환경으로 만들어줌
$ pipenv shell
➜  exid git:() ✗ source /~/exid-NTYXrrm5/bin/activate
(exid-NTYXrrm5) ➜  exid git: ✗
  • 역시나 랜덤하게 만들어지는 virtualenv 이름은 마음에 안든다.

pipenv run

virtualenv를 활성화 하지 않고 해당 환경에서 파이썬 파일을 실행하는 방법은 pipenv run 커맨드를 이용하는 것이다.

$ pipenv run python my_project.py

자주 사용하게될 녀석이니 앨리어스로 등록해두자. vim 사용자라면, .vimrc 에 추가하면 된다.

### 별칭은 알아서 잘 만들자
alias pipython="pipenv run python"
alias prp="pipenv run python"

Pipfile과 Pipfile.lock

Pipfile

  • 위에서 pipenv install 명령어 한방으로 생성한 파일이다.
  • dev 는 아직 없으나 나중에 필요하면 채워놓자.
[[source]]
name = "pypi"
verify_ssl = true
[packages]
amqp = "==2.1.4"
billiard = "==3.5.0.2"
celery = "==4.0.2"
"contextlib2" = "==0.5.4"
Django = "==1.8.5"
django-braces = "==1.9.0"
django-crispy-forms = "==1.6.1"
django-extensions = "==1.7.4"
django-floppyforms = "==1.7.0"
djangorestframework = "==3.3.1"
elasticsearch = "==2.4.0"
kombu = "==4.0.2"
mysqlclient = "==1.3.7"
olefile = "==0.44"
Pillow = "==4.0.0"
pycrypto = "==2.6.1"
pytz = "==2016.10"
raven = "==6.2.1"
redis = "==2.10.5"
redis-py-cluster = "==1.1.0"
requests = "==2.9.1"
serpy = "==0.1.0"
simplejson = "==3.10.0"
"urllib3" = "==1.19.1"
uWSGI = "==2.0.15"
vine = "==1.1.3"
xlwt = "==1.2.0"
mongoengine = "==0.13.0"
django-widget-tweaks = "==1.4.1"
[dev-packages]

Pipfile.lock

{
    "_meta": {
        "hash": {
            "sha256": "e0bb552e2ab575fa5141985b686ac5e49da21bdda3444cfd1273a5927f489182"
        },
        "host-environment-markers": {
            "implementation_name": "cpython",
            "implementation_version": "3.4.3",
            "os_name": "posix",
            "platform_machine": "x86_64",
            "platform_python_implementation": "CPython",
            "platform_release": "17.3.0",
            "platform_system": "Darwin",
            "platform_version": "Darwin Kernel Version 17.3.0: Thu Nov  9 18:09:22 PST 2017; root:xnu-4570.31.3~1/RELEASE_X86_64",
            "python_full_version": "3.4.3",
            "python_version": "3.4",
            "sys_platform": "darwin"
        },
        "pipfile-spec": 6,
        "requires": {},
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.python.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "amqp": {
            "hashes": [
                "sha256:5e0871a93433f941e444c2b859da095f05034d2ac1b7c084529cfd0b6f8eef18",
                "sha256:1378cc14afeb6c2850404f322d03dec0082d11d04bdcb0360e1b10d4e6e77ef9"
            ],
            "version": "==2.1.4"
        },
        "billiard": {
            "hashes": [
                "sha256:82478ebdd3cd4d357613cb4735eb828014364c5e02d3bba96ea6c1565a2c0172",
                "sha256:a93c90d59fca62ad63f92b3d2bf1d752c154dde90a3100dba4c8e439386e534c",
                "sha256:d8df4b276b11b3e2fe25652e411487bda6e5bac4f8fd236a278a2bfe300f7c43",
                "sha256:e740e352bbf7b6c8cc92a2596cc2da2bfb4ab1009aeb68bf844456af4e924278",
                "sha256:52c2e01c95c6edae9ca1f48d83503e6ceeafbf28c19929a1e917fe951a83adb1",
                "sha256:03f669755f1d6b7dbe528fe615a3164b0d8efca095382c514dd7026dfb79a9c6",
                "sha256:3eb01a8fe44116aa6d63d2010515ef1526e40caee5f766f75b2d28393332dcaa"
            ],
            "version": "==3.5.0.2"
        },
          ... 중략
    },
    "develop": {}
}
  • Pipfile.lock 은 의존성 패키지의 검증을 담당한다.
  • 의존성이 있는 패키지가 의존하고 있는 패키지들도 함께 검사를 해주게 되므로 Pipfile.lock 을 잘 사용해서 안심하고 쓸 수 있게 되는 것이다.

dev 에 패키지를 추가해보자

$ pipenv install mock --dev
Installing mock…
Collecting mock
  Downloading mock-2.0.0-py2.py3-none-any.whl (56kB)
Collecting pbr>=0.11 (from mock)
  Downloading pbr-3.1.1-py2.py3-none-any.whl (99kB)
Requirement already satisfied: six>=1.9 in //lib/python3.4/site-packages (from mock)
Installing collected packages: pbr, mock
Successfully installed mock-2.0.0 pbr-3.1.1
Adding mock to Pipfile's [dev-packages]…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (bd3f8c)!
  • Pipfile 에 추가하고
  • 각 환경에서의 의존성이 걸리는 부분을 확인 한다음
  • Pipfile.lock 을 업데이트한다.

Pipfile [dev-packages] 부분

[dev-packages]
mock = "*"

Pipfile.lock  “develop” 부분

"develop": {
    "mock": {
        "hashes": [
            "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
            "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
        ],
        "version": "==2.0.0"
    },
    "pbr": {
        "hashes": [
            "sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac",
            "sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1"
        ],
        "version": "==3.1.1"
    },
    "six": {
        "hashes": [
            "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb",
            "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
        ],
        "version": "==1.11.0"
    }
}

개발환경과 운영환경에서는 어떻게 적용할까?

개발환경

–dev 를 붙이면 [packages] 와 [dev-packages] 둘다 설치하므로 아래의 명령어를 그냥 날리면 된다.

pipenv install --dev

샌박, 베타, 실서버

운영환경에서는 [packages] 에 있는 것만 설치하면 되니 –dev 를 빼고 실행하면 되겠다.

$ pipenv install

참고

깃헙 : https://github.com/pypa/pipenv

매뉴얼 : https://media.readthedocs.org/pdf/pipenv/latest/pipenv.pdf

케네스 아저씨 블로그 : https://www.kennethreitz.org/essays/announcing-pipenv

curl을 사용한 분할 다운로드

오늘 알아볼 팁은 curl으로 분할 다운로드 하는 방법에 대한 것이다.
간단한 팁이긴 하지만, 경우에 따라서는 유용할 수도 있을것 같아서 글로 남긴다.

chef-server를 설치해야할 일이 생겼다.
chef-server의 설치파일은 551메가 가량 되는데, 그 녀석은 이링크에서 받아온다.

눌러보면 알겠지만, 겁~~~~~~~나 느리다.
저거 받는다고 30분씩 기다리는 경우가 태반인데,
여기서 생각한게 이런거다.

예전에 flashget이라는 윈도우 유틸을 사용했던 것처럼 분할해서 다운로드 한다음에 합치면 좋지 않을까?

위의 아이디어에서 출발했고, 30분을 3분정도로 줄이는데에 성공했다.

물론…여러가지 꼼수를 써서 가능하게 된것이다.

일단 분할해서 받아오는 방법을 알아보자.

curl의 --range 옵션을 사용하면 받아올 파일의 구간을 설정할 수 있다.

curl로 쪼개서 받아오는 코드는 아래와 같다.

#!/bin/bash
curl –range 0-99999999 -s -o chef-server_12.9.1-1.part1 https://packages.chef.io/files/stable/chef-server/12.9.1/ubuntu/14.04/chef-server-core_12.9.1-1_amd64.deb
curl -s –range 100000000-199999999 -s -o chef-server_12.9.1-1.part2 https://packages.chef.io/files/stable/chef-server/12.9.1/ubuntu/14.04/chef-server-core_12.9.1-1_amd64.deb && echo “done2” &
curl -s –range 200000000-299999999 -s -o chef-server_12.9.1-1.part3 https://packages.chef.io/files/stable/chef-server/12.9.1/ubuntu/14.04/chef-server-core_12.9.1-1_amd64.deb && echo “done3” &
curl -s –range 300000000-399999999 -s -o chef-server_12.9.1-1.part4 https://packages.chef.io/files/stable/chef-server/12.9.1/ubuntu/14.04/chef-server-core_12.9.1-1_amd64.deb && echo “done4” &
curl -s –range 400000000-499999999 -s -o chef-server_12.9.1-1.part5 https://packages.chef.io/files/stable/chef-server/12.9.1/ubuntu/14.04/chef-server-core_12.9.1-1_amd64.deb echo “done5” &
curl -s –range 500000000- -s -o chef-server_12.9.1-1.part6 https://packages.chef.io/files/stable/chef-server/12.9.1/ubuntu/14.04/chef-server-core_12.9.1-1_amd64.deb && echo “done6” &
wait
cat chef-server_12.9.1-1.part? > chef-server-core_12.9.1-1_amd64.deb
rm chef-server_12.9.1-1.part?

curl의 -s 옵션과 -o 옵션을 사용했는데, -s는 slient이고, -o는 분할해서 다운로드 받는 파일의 파일명을 지정해주는 옵션이다. 분할된 각 파일들을 보면 아래와 같다.

-rw-rw-r– 1 vagrant vagrant 100000000 Oct 24 05:48 chef-server_12.9.1-1.part1
-rw-rw-r– 1 vagrant vagrant 100000000 Oct 24 05:48 chef-server_12.9.1-1.part2
-rw-rw-r– 1 vagrant vagrant 100000000 Oct 24 05:48 chef-server_12.9.1-1.part3
-rw-rw-r– 1 vagrant vagrant 100000000 Oct 24 05:49 chef-server_12.9.1-1.part4
-rw-rw-r– 1 vagrant vagrant 100000000 Oct 24 05:48 chef-server_12.9.1-1.part5
-rw-rw-r– 1 vagrant vagrant 77422441 Oct 24 05:48 chef-server_12.9.1-1.part6

wait 명령으로 각 프로세스가 종료될때까지 기다리고 다 끝나면, chef-server-core_12.9.1-1_amd64.deb 파일로 합친다.

저 방법으로 30분 걸리던게 15분 정도로 줄어들었다. 1/6로 줄어야 될것 같긴한데, 코어수가 1개이기 때문인 것 같다.
프로세스 자체는 curl 6개가 열심히 다운로드를 받고 있다. 코어가 여러개인 머신에서 실행하면 훨씬 빠릿하게 받을 것 같다.

parallel_ing

& 로 프로세스를 백드라운드로 실행시키는 녀석을 제거해 보려고 parallel 이라는 녀석으로도 시도를 해보았다.

참고로 parallel의 –link라는 옵션을 사용하는데, 최신버전에서 사용할 수 있다. 기본설치버전에는 없다.

parallel을 추가로 사용한 분할 다운로드 코드를 보자

#!/bin/bash

URL=”https://s3.ap-northeast-2.amazonaws.com/chef-download/chef-server-core_12.9.1-1_amd64.deb”

parallel –jobs 6 –link curl –range {1}{2} -s -o chef-server_12.9.1-1.part{3} $URL ::: 0 100000000 200000000 300000000 400000000 500000000 ::: -99999999 -199999999 -299999999 -399999999 -499999999 – ::: 1 2 3 4 5 6
wait
cat chef-server_12.9.1-1.part? > chef-server-core_12.9.1-1_amd64.deb

위의 코드를 간단히 설명하면 :::뒤에 오는 숫자들이 파라메터들이다. 위의 코드가 어떻게 동작 하는지는 아래의 코드가 어떻게 실행되는지를 보는게 더욱 빠를 것 같다.

parallel –jobs 6 –link echo {1} {2} {3} ::: 1 2 3 ::: 4 5 6 ::: 7 8 9

[output]
1 4 7
2 5 8
3 6 9

{1} {2} {3}에 해당하는 곳에 각각의 :::으로 구분된 파라메터의 값들을 하나씩 치환해서 넣어주게 된다.

위의 parallel을 사용한 curl코드는 사실 curl로 처음 분할 다운로드를 하던 코드에 병렬실행 만 양념으로 넣어준것 뿐이다.
나머지는 같다. 위 코드를 사용해도 다운로드 속도가 그리 빠르지는 않았다.

다 받았으면, checksum으로 같은 파일인지 확인을 해보도록 하자.
md5나 sha1, sha256으로 체크하는데 sha1로 체크를 해보자.
하는 방법은 웹사이트에 올려져 있는 sha1 checksum과 분할해서 받았다가 합쳐진 녀석의 checksum이 일치하면, 같은 파일이라고 봐도 좋다는 것이다. 체크섬을 체크하는법(?!)은 아래와 같다.

sha1sum chef-server-core_12.9.1-1_amd64.deb

위의 코드를 실행시켜서 나오는 hash값이 원본파일의 sha1 hash와 동일한지 보면된다.

한가지 더 꼼수를 쓴 것은 그냥 국내의 클라우드 서비스 저장소에 chef-server-core_12.9.1-1_amd64.deb 올려놓고 그녀석을 분할 다운로드 하니, 속도가 많이 빨라졌다.

그냥 국내 클라우드에 올려놓으면 될 것을 이라는 마음이 들기도 하지만, 간단한 것도 고민해보고 만들어보고 하는건 참 재미있는것 같다.

아래는 위의 두가지를 모두 적용후 다운로드 시간을 측정해본 것이다. 2분 31초가 걸렸는데, 그전에는 30분정도 걸렸다.
적용전 split_download_before

 

 

적용후 parallel_download

vagrant의 sandbox 플러그인

너무 글을 안써서 글을 좀 써야겠다는 마음으로 짧지만 삶에 도움되는
vagrant sandbox 명령어를 사용할 수 있게 해주는
sahara라는 플러그인을 소개하려고 한다.

해당 플러그인의 소스 저장소는 다음의 링크를 참고하시라.

sahara

sahara가 뭐하는 녀석인가?

사하라 하면 사막!
사막하면 모래!
모래 하면 sandbox 이다! (말도안되지만..그냥 넘어가자..ㅎ)
vagrant에서 사용하는 virtualbox를 sandbox 처럼 사용할 수 있게 해주는 플러그인이다.

주의

  • sahara는 Vagrant 1.0 이하에서는 제대로 동작하지 않는다.

설치

리눅스 계열은 요렇게 하면된다. (윈도우즈는….ㅠㅠ)

[code lang=text]
$ git clone https://github.com/ryuzee/sahara.git
$ cd sahara
$ bundle install
$ vagrant plugin install sahara
[/code]

설치 잘됐는지 확인

아래와 같이 나오면 잘되는 것이다.

[code lang=text]
$vagrant plugin list
sahara (0.0.17)
[/code]

사용법

이름은 sahara 인데 설치하면 사용할 수 있는 명령어는 sandbox 이다. 그냥 이름이 sandbox 였으면 안헷갈렸을것 같다.
명령어는 달랑 5개 밖에 없긴 한데, 그걸로도 충분하다.

[code lang=text]
### 샌드박스 모드 활성화
$ vagrant sandbox on

### 스냅샷 저장하기
$ vagrant sandbox commit

### 롤백하기
$ vagrant sandbox rollback

### 샌드박스 모드 끄기
$ vagrant sandbox off

### 샌드박스 상태 확인
$ vagrant sandbox status
[/code]

sandbox commit은 겁나 느리다

이걸 빠르게 하려면 가상머신을 끄고 하면 빠르다
~~근데 다시 켜는 시간은..읍읍..~~

[code lang=text]
$ vagrant halt
$ vagrant sandbox commit
[/code]

이제 이걸로 좀 더 편하게 chef질을 할 수 있게 됐다~! 예이!

참고로 virtualbox 뿐 아니라 VMware fusion, libvirt, parallels 도 지원한다~!

golang의 sort패키지 사용해보기

[markdown]
golang에 sort라는 패키지가 있을 것이다라는 생각은 했지만, 잘 되어 있을거라는 생각은 못했는데,왠걸 내가 구현한것 보다 훨씬 잘 되어 있어서 내가만든거는 버리기로 했다.
내가 원하는 기능의 리스트는 아래와 같았다.

– 구조체 안에 있는 점수를 비교해서 구조체 리스트를 정렬하고 싶다.
– 점수가 같은 경우에는 나중에 업데이트된 녀석에게 상위 등급을 주고 싶다.
– 역순으로 정렬 하면 좋겠다.

결론적으로는 내가 원하는 기능보다 강력한 기능이 go의 sort패키지에 탑재 되어 있었다.
일단 정렬을 하려면 인터페이스를 만족시켜줘야 하는데, go의 인터페이스의 구현은 직접적으로 구현을 하는 것이 아니라,
그냥 해당 인터페이스가 제공하는 메서드를 모두 가지고 있으면, 그 인터페이스를 구현한것이다 라고 하기 때문에 sort패키지의 인터페이스를 단순히 구현을 하면 된다.
int, float, string은 go를 만드신 분들이 만들어 놨기 때문에, 그냥 쓸 수 있다. 그리고 내가 만든 구조체를 정렬하기 위해서는 구조체를 정렬하기 위한 타입을 만들어서 인터페이스의 메서드들을 구현해 주면 된다~!
말로하니깐 긴데 코드는 그렇게 길지 않다.

“`
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
“`

sort인터페이스코드인데 Len, Less, Swap 세가지 메서드를 구현하면 sort.Interface가 되는 것이되겠다.

### 숫자 정렬

일단 그냥 숫자를 정렬해보자.
import 관련된 코드는 그냥 생략 하도록 하겠다.

“`
func sortInt() {
// 램덤한 수를 세팅하기 위해 유닉스 시간으로 Seed를 저장
// 리스트의 숫자는 30개로 지정한다.
rand.Seed(time.Now().UTC().UnixNano())
count := 30
maxInt := 10000
arr := make([]int , count)

for i, _ := range arr {
arr[i] = rand.Int() % int(maxInt)
}

fmt.Println(“Sort this array : “, arr)
sort.Sort(sort.IntSlice(arr))
fmt.Println(“Sorted result : “, arr)
sort.Sort(sort.Reverse(sort.IntSlice(arr)))
fmt.Println(“Reversed result : “, arr)
}
“`

실행하면 이런 식으로 나올 것이다.

“`
Sort this array : [8298 6378 8489 5548 734 6301 6347 2620 2636 2569 3039 168 2130 8599 9316 723 4642 6531 147 42 188 264 5544 260 5101 5595 550 6594 9128 3430]
Sorted result : [42 147 168 188 260 264 550 723 734 2130 2569 2620 2636 3039 3430 4642 5101 5544 5548 5595 6301 6347 6378 6531 6594 8298 8489 8599 9128 9316]
Reversed result : [9316 9128 8599 8489 8298 6594 6531 6378 6347 6301 5595 5548 5544 5101 4642 3430 3039 2636 2620 2569 2130 734 723 550 264 260 188 168 147 42]
“`

### 구조체 정렬

숫자는 해봤으니, 이제 좀 더 실제적으로 사용할 만한 녀석으로 구조체를 정렬 해보자. 내가 하고자 하는것은 카카오 게임에서 랭킹을 만들어주는 역활을 하는 녀석이다.
구조체는 이런식으로 생겼다. 더 많은 데이터가 있지만, 공부에 필요없는 녀석은 생략했다.

“`
type Ranking struct {
kakaoId string
score int
written_date int // unix timestamp
}

// 테스트 결과를 이뿌게 보기위해 String 함수를 추가하였다.
func (ranking Ranking) String () string {
return fmt.Sprintf(“kakaoid %s score %d written_date %d\n”, ranking.kakaoid, ranking.score, ranking.written_date)
}
“`

그럼 이제부터 Ranking 구조체를 위한 예제 코드를 만들어보자.
아래 코드는 sort.interface를 구현 하기 위한 코드들이다. 소팅에 사용할 RankingSlice 타입을 새로 정의 하였다.
그리고 `Len, Less, Swap` 세개의 함수를 만들어 주었다.
점수가 큰녀석이 상위 랭크가 되도록 하였고, 점수가 같은 경우는 written_date가 큰녀석이 상위랭크가 되도록 작성했다.

“`
type RankingSlice []Ranking
func (arr RankingSlice) Len() int {
return len(arr)
}

// j 인덱스를 가진녀석이 i 앞으로 와야하는지 말아야하는지를 판단하는 함수
func (arr RankingSlice) Less(i, j int) bool {
if arr[i].score == arr[j].score {
return arr[i].written_date > arr[j].written_date
} else {
return arr[i].score > arr[j].score
}
}

func (arr RankingSlice) Swap(i, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
“`

소팅을 하기위한 준비는 이제 끝났다.
테스트 코드를 만들고 테스트 해보자.

“`
func sortRankings(){
rankings := []Ranking {
{“88154420822432544”, 1436520192, 13960},
{“91670176688136577”, 1440799941, 13080},
{“88180751521592112”, 1435637878, 14280},
{“88144324860977520”, 1437278775, 13560},
{“93797540915256451”, 1435889873, 14200},
{“88249875880704624”, 1438226613, 11960},
{“88192601275706464”, 1436694190, 13320},
{“88148115536237952”, 1436450939, 13600},
{“89047353485705664”, 1435654249, 12760},
{“88182263314045056”, 1436586635, 13320},
{“88193534078563601”, 1438176365, 16600},
{“88256711722920353”, 1437136853, 8920},
{“89852877819675984”, 1436684270, 12680},
{“93594773313085234”, 1437634370, 8480},
{“90289273133727025”, 1436351481, 10800},
{“92150085612088915”, 1440609977, 12720},
{“91211930152797040”, 1438076721, 10920},
{“88309448624594448”, 1438057506, 10440},
{“88406950494717233”, 1436508922, 11000},
{“93176928270204371”, 1437293064, 10440},
{“88056851903088048”, 1436338570, 10440},
{“93691000129391522”, 1436618165, 12560},
{“88675559501659840”, 1438230885, 9800},
}

sort.Sort(RankingSlice(rankings))
fmt.Println(rankings)
}
“`

크게 한것 없이 정렬이 끝났다. 아래와 같은 결과가 나올 것이다.

“`
kakaoid 88193534078563601 score 16600 written_date 1438176365
kakaoid 88180751521592112 score 14280 written_date 1435637878
kakaoid 93797540915256451 score 14200 written_date 1435889873
kakaoid 88154420822432544 score 13960 written_date 1436520192
kakaoid 88148115536237952 score 13600 written_date 1436450939
kakaoid 88144324860977520 score 13560 written_date 1437278775
kakaoid 88192601275706464 score 13320 written_date 1436694190
kakaoid 88182263314045056 score 13320 written_date 1436586635
kakaoid 91670176688136577 score 13080 written_date 1440799941
kakaoid 89047353485705664 score 12760 written_date 1435654249
kakaoid 92150085612088915 score 12720 written_date 1440609977
kakaoid 89852877819675984 score 12680 written_date 1436684270
kakaoid 93691000129391522 score 12560 written_date 1436618165
kakaoid 88249875880704624 score 11960 written_date 1438226613
kakaoid 88406950494717233 score 11000 written_date 1436508922
kakaoid 91211930152797040 score 10920 written_date 1438076721
kakaoid 90289273133727025 score 10800 written_date 1436351481
kakaoid 88309448624594448 score 10440 written_date 1438057506
kakaoid 93176928270204371 score 10440 written_date 1437293064
kakaoid 88056851903088048 score 10440 written_date 1436338570
kakaoid 88675559501659840 score 9800 written_date 1438230885
kakaoid 88256711722920353 score 8920 written_date 1437136853
kakaoid 93594773313085234 score 8480 written_date 1437634370
“`

처음에는 퀵소트를 구현해서 정렬하려고 퀵소트를 만들기 까지 했지만, 너무 잘 만들어놓은 패키지 때문에 큰 노력없이 좋은 결과를 얻을 수 있었다.
위에서 테스트로 만든 것 이외에도 소팅할 키를 지정하기, 멀티키로 소팅하기 같은 기능도 sort패키지를 사용하면 손쉽게(?!) 구현할 수 있다.
관련 정보는 golang.org의 sort 패키지를 확인해 보자.

[sort패키지](https://golang.org/pkg/sort/)

전체 소스코드는 아래의 링크에 있다.

[소스코드보기](https://gist.github.com/wapj/5b75ad16d32727eed61036fdb6c349e7)

[/markdown]

[install] powerdns 설치하기

[markdown]
## Private DNS? POWERDNS!

DB를 마스터/슬레이브로 운영하고 있을 경우, 마스터 디비가 죽으면 슬레이브 디비를 마스터로 승격시켜주는 녀석들이 있다.(MMM, MHA등등) 그런데, 보통 디비를 바라보고 있는 클라이언트는 보통 아이피나 도메인을 통해서 디비를 바라보고 있을 것이다. 그런 상황에서 슬레이브가 마스터로 승격되어 봤자 클라이언트들은 죽어있는 마스터를 바라보게 마련이다.

이런경우 슬레이브를 마스터로 승격 시켜주는 것과 별개로 아이피나 도메인도 같이 바꿔줘야 하는데,
아이피를 바꾸려면 가상아이피라는 녀석을 써야한다. 그런데 지금 사용하는 클라우드 서비스에서는 가상아이피를 지원하지 않는관계로,어쩔 수 없이(?!) 도메인을 써야하는 상황이었다.

어쨌든 클라이언트들이 모르게 도메인만 슬쩍 바꿔버리면, 클라이언트는 마스터가 죽었는지 살았는지 모를테니, dns서버라는 녀석을 도입하기로 했다. 그중에 제일 간편해 보이는 녀석이 powerdns 였는데..(설치는 간편하지 않다) web api도 지원하고 해서 실제 서비스에 도입해서 사용하고 있다.

Private DNS라고 쓰긴했지만, 내가 그렇게 쓴다는거지, Public DNS 서버로 못쓴다는 얘기는 아니다.

궁금한 사람은 [powerdns](https://doc.powerdns.com) 문서 페이지와 [github](https://github.com/PowerDNS/pdns)에서 더 많은 정보를 볼 수 있을 것이다.

### MariaDB설치
PowerDNS는 벡엔드로 Mysql을 사용할 수 있는데, 나는 MariaDB를 사용하기로 했다. (이유가 있었던것 같은데, 까먹었다.)
여튼 설치를 해보자. root 유저로 들어가서 실행해야한다.

“`
# apt-get update && apt-get upgrade -y
# apt-get install software-properties-common
# apt-key adv –recv-keys –keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db
# add-apt-repository ‘deb http://ftp.kaist.ac.kr/mariadb/repo/5.5/ubuntu trusty main’
# apt-get -y install libaio1 libdbd-mysql-perl libdbi-perl libmariadbclient18 libmysqlclient18 libnet-daemon-perl libplrpc-perl mariadb-client-5.5 mariadb-client-core-5.5 mariadb-common mysql-common mariadb-server mariadb-server-5.5 mariadb-server-core-5.5
“`

### mariadb 보안 적용
보안을 위해서 하는 건데, 테스트환경이라면 굳이 하지 않아도 된다.

“`
$ mysql_secure_installation
“`

위의 코드를 실행하면 아래와 같이 물어보는데, 읽어보고 yes를 해주면 된다.

“`
Change the root password? [Y/n] n
Remove anonymous users? [Y/n] y
Disallow root login remotely? [Y/n] y
Remove test database and access to it? [Y/n] y
Reload privilege tables now? [Y/n] y
Thanks for using MariaDB!
“`

### powerdns 유저 생성및 데이터베이스와 테이블 생성

powerdns_init.sql이라는 파일을 하나 생성하고 아래의 내용을 채워넣자. (*패스워드는 알아서 잘 바꿔주자. 복붙금지!*)

“`
CREATE DATABASE powerdns;
GRANT ALL ON powerdns.* TO ‘pdns’@’localhost’ IDENTIFIED BY ‘password’;
FLUSH PRIVILEGES;
USE powerdns;
CREATE TABLE domains (
id INT AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type VARCHAR(6) NOT NULL,
notified_serial INT DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (id)
) Engine=InnoDB;

CREATE UNIQUE INDEX name_index ON domains(name);
CREATE TABLE records (
id INT AUTO_INCREMENT,
domain_id INT DEFAULT NULL,
name VARCHAR(255) DEFAULT NULL,
type VARCHAR(10) DEFAULT NULL,
content VARCHAR(64000) DEFAULT NULL,
ttl INT DEFAULT NULL,
prio INT DEFAULT NULL,
change_date INT DEFAULT NULL,
disabled TINYINT(1) DEFAULT 0,
ordername VARCHAR(255) BINARY DEFAULT NULL,
auth TINYINT(1) DEFAULT 1,
PRIMARY KEY (id)
) Engine=InnoDB;

CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX recordorder ON records (domain_id, ordername);

CREATE TABLE supermasters (
ip VARCHAR(64) NOT NULL,
nameserver VARCHAR(255) NOT NULL,
account VARCHAR(40) NOT NULL,
PRIMARY KEY (ip, nameserver)
) Engine=InnoDB;
CREATE TABLE comments (
id INT AUTO_INCREMENT,
domain_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(10) NOT NULL,
modified_at INT NOT NULL,
account VARCHAR(40) NOT NULL,
comment VARCHAR(64000) NOT NULL,
PRIMARY KEY (id)
) Engine=InnoDB;

CREATE INDEX comments_domain_id_idx ON comments (domain_id);
CREATE INDEX comments_name_type_idx ON comments (name, type);
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
CREATE TABLE domainmetadata (
id INT AUTO_INCREMENT,
domain_id INT NOT NULL,
kind VARCHAR(32),
content TEXT,
PRIMARY KEY (id)
) Engine=InnoDB;

CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind);
CREATE TABLE cryptokeys (
id INT AUTO_INCREMENT,
domain_id INT NOT NULL,
flags INT NOT NULL,
active BOOL,
content TEXT,
PRIMARY KEY(id)
) Engine=InnoDB;

CREATE INDEX domainidindex ON cryptokeys(domain_id);
CREATE TABLE tsigkeys (
id INT AUTO_INCREMENT,
name VARCHAR(255),
algorithm VARCHAR(50),
secret VARCHAR(255),
PRIMARY KEY (id)
) Engine=InnoDB;

CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
“`

디비생성후에 다음의 커맨드로 mariadb에 반영해주자.

“`
$ mysql -u root -p < powerdns_init.sql ``` ### Powerdns설치 (4.0.X) /etc/apt/source.list에 아래한줄을 추가한다. ``` # add-apt-repository 'deb [arch=amd64] http://repo.powerdns.com/ubuntu trusty-auth-40 main' ``` 그리고 `/etc/apt/preferences.d/pdns` 파일을 생성하고 아래 내용을 추가한다. ``` # touch /etc/apt/preferences.d/pdns # echo -e "Package: pdns-*\nPin: origin repo.powerdns.com\nPin-Priority: 600" | sudo tee /etc/apt/preferences.d/pdns ``` 위의 것들을 하면 아래의 명령어로 실행할 수 있다. ``` # curl https://repo.powerdns.com/FD380FBB-pub.asc | sudo apt-key add - # apt-get update && apt-get upgrade # apt-get install -y pdns-server pdns-backend-mysql # rm /etc/powerdns/pdns.d/* ### 기존의 설정 삭제 꼭 해줘야한다. ``` 설치시에 아래와 같은 에러가 난다면, 아래의 명령을 실행해주자. `# apt-get -f purge -y mysql-client` ``` You might want to run 'apt-get -f install' to correct these. The following packages have unmet dependencies: mariadb-server : Depends: mariadb-server-5.5 (= 5.5.49+maria-1~trusty) but 5.5.49-1ubuntu0.14.04.1 is installed E: Unmet dependencies. Try using -f. ``` 설치시 아래와 같은 화면이 나오는데, No를 선택해주자. [![](http://gyus.me/wp-content/uploads/2016/05/pdns1-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns1-1-1.png) 그리고 /etc/powerdns/pdns.d/pdns.local.gmysql.conf 파일을 아래와같이 수정해 주어야 한다. 디비의 유저와 패스워드를 잘 바꿔서 넣어주자. ``` ### MySQL Configuration file launch=gmysql gmysql-host=localhost gmysql-dbname=디비명 gmysql-user=유저명 gmysql-password=

###수정후 mariadb를 재시작해준다.
$ service pdns restart
“`

### pdns가 잘동작하는지 테스트
netstat와 dig를 사용했을 때 아래와 비슷하게 나와야 한다.

“`
$ netstat -nlp |grep pdns
tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN 6793/pdns_server-in
udp 0 0 0.0.0.0:53 0.0.0.0:* 6793/pdns_server-in
unix 2 [ ACC ] STREAM LISTENING 46518 6789/pdns_server /var/run/pdns.controlsocket
$ dig @localhost
; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> @localhost
; (3 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28331 ;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 2800 ;; QUESTION SECTION: ;. IN NS ;; Query time: 2 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Thu Mar 10 20:06:16 KST 2016 ;; MSG SIZE rcvd: 29 ``` ### Poweradmin 설치 powerdns에는 Poweradmin 이라는 php관리 페이지가 따로 있다. 설치가 어렵지않고, 설치하면 많은 기능을 웹기반으로 쉽게사용할 수 있으니 설치하도록 하자. (커맨드라인으로 굳이하겠다면 말리지는 않음) ``` $ apt-get install -y unzip apache2 gettext libapache2-mod-php5 php5 php5-common php5-curl php5-dev php5-gd php-pear php5-imap php5-ming php5-mysql php5-xmlrpc php5-mhash php5-mcrypt $ pear upgrade pear/PEAR $ pear install DB $ pear install pear/MDB2#mysql ### php 모듈 활성화 $ php5enmod mcrypt $ service apache2 restart $ cd tmp $ wget https://github.com/poweradmin/poweradmin/archive/master.zip -O poweradmin.zip $ unzip poweradmin.zip $ mv poweradmin-master /var/www/html/poweradmin ``` ###Poweradmin 설정 설치가 끝났으니 아래의 주소로 들어가서 설정을 하도록하자. http://#{server_ip}/poweradmin/install/ [![](http://gyus.me/wp-content/uploads/2016/05/pdns2-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns2-1-1.png) 들어가면 위와 같은 언어 선택 화면이 나온다. 대부분 영어겠지만, 익숙한 언어를 선택하고 Go to step2로 가자. 나는 영어를 선택했다. [![](http://gyus.me/wp-content/uploads/2016/05/pdns3-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns3-1-1.png) 스텝2는 별거 없다. 바로 Go to step3 를 누르자. [![](http://gyus.me/wp-content/uploads/2016/05/pdns4-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns4-1-1.png) Step3에서는 DB와 Poweradmin에 대한 설정을 하게 된다. 적절한 값을 넣도록 하자. ``` Username : DB와 연결할 유저명을 적으면 된다. 우리는 위에서 pdns라는 유저를 생성했다. Password : 디비패스워드 Database type : MySQL Hostname : localhost DB Port : 3306 Database : powerdns Poweradmin administrator password : 파워애드민에서 사용할 관리자 패스워드 ``` [![](http://gyus.me/wp-content/uploads/2016/05/pdns5-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns5-1-1.png) Username : poweradmin의 관리자명 Password: poweradmin의 관리자 패스워드 hostmaster.unixmen.local – SOA레코드를 만들때 없을경우 기본값으로 들어가는 값. 다음과 같은 방식이 된다. “hostmaster.example.net”. ns1.unixmen.local – 템플릿을 만들때 주 네임서버로 설정되는 값. 요런식으로 적는다. “ns1.example.net”. ns2.unixmen.local – 템플릿을 만들때 주 네임서버로 설정되는 값. 요런식으로 적는다. “ns2.example.net”. 잘 적은뒤에 Step5로 가자. [![](http://gyus.me/wp-content/uploads/2016/05/pdns6-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns6-1-1.png) Step5에서는 poweradmin에서 powerdns의 유저가 디비에 접근할때의 권한을 SELECT, INSERT, UPDATE, DELETE 만주라고 권장하고 있다. 화면에 있는 내용을 mysql에서 실행해주면 된다. [![](http://gyus.me/wp-content/uploads/2016/05/pdns7-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns7-1-1.png) Step6에서는 poweradmin의 설정파일을 "../inc/config.inc.php" 경로에 만들어라고 말을하고 있다. 해당 내용을 복사해서 만들어주자. 설정파일에 db_user가 admin 이라고 되어있는데, 디비에 유저가 생성되지 않은 경우가 있으므로 그경우에는 기존의 'pdns'유저로 해당 파일의 설정을 변경해주자. 나 같은 경우는 "/var/www/html/poweradmin/inc/config.inc.php" 경로에 생성을 했다. 7단계로 가자. 이게 설치는 거의 다되어 간다! [![](http://gyus.me/wp-content/uploads/2016/05/pdns8-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns8-1-1.png) 7단계에서는 DDNS를 사용하고 싶으면 mod_rewrite를 활성화 하라고 하고 있다. 그리고 반드시 "install" 디렉토리를 삭제하라고 한다. 다음으로 poweradmin의 "install" 디렉토리를 반.드.시. 삭제해야한다. (나는 사실 이름만 바꿨다. 실서버에서는 이렇게 하면 안된다.) [![](http://gyus.me/wp-content/uploads/2016/05/pdns9-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns9-1-1.png) http://server_ip/poweradmin 으로 들어왔을 때 위와 같은 화면이 나오면 설치는 성공이다. Username과 Password에 위에서 설정한 값을 넣으면 Poweradmin에 접근이 가능하다. 이제 설치는 끝이다. ### DNS서버 설정 [![](http://gyus.me/wp-content/uploads/2016/05/pdns1-10-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns1-10-1-1.png) Add Master Zone을 추가하자. [![](http://gyus.me/wp-content/uploads/2016/05/pdns1-11-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns1-11-1-1.png) Zone name에 'pkpk.com' 등과 같은 사용하고 싶은 주도메인을 입력하고 Add zone 을 눌러서 추가해준다. [![](http://gyus.me/wp-content/uploads/2016/05/pdns1-12-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns1-12-1-1.png) List zones에 들어가면 위와같은 화면이 나오고 빨간 동그라미친 곳에 들어가면 A record등을 추가할 수 있다. [![](http://gyus.me/wp-content/uploads/2016/05/pdns1-13-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns1-13-1-1.png) `alpha.pkpk.com` 이라는 도메인을 요청하면 `192.168.0.8`로 응답을 주도록 세팅을 했다. ### DNS서버 테스트 이제 잘되는지 테스트를 해보자. 먼저는 powerdns가 설치된 서버에서 실행을 해보자. 다음과 같은 코드를 실행해보자 `$ dig alpha.pkpk.com A @127.0.0.1` 아래와 비슷한 결과가 나오면 성공이다. ``` ; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> alpha.pkpk.com A @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18817 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 2800 ;; QUESTION SECTION: ;alpha.pkpk.com. IN A ;; ANSWER SECTION: alpha.pkpk.com. 5 IN A 192.168.0.8 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Thu Mar 10 21:21:09 KST 2016 ;; MSG SIZE rcvd: 59 ``` 이제 다른 서버에 DNS서버를 연결해서 테스트해보자. ``` ### ubuntu14.04에서 실행했다. root권한으로 실행해야한다. $ echo "nameserver dnsserver의 아이피" >> /etc/resolvconf/resolv.conf.d/head
$ service networking restart
$ sudo resolvconf -u
“`

설정이 끝났으니 위에서 추가한 alpha.pkpk.com 으로 핑을 날려보자.

“`
$ ping alpha.pkpk.com
PING alpha.pkpk.com (192.168.0.8) 56(84) bytes of data.
64 bytes from 192.168.0.8: icmp_seq=1 ttl=64 time=0.398 ms
64 bytes from 192.168.0.8: icmp_seq=2 ttl=64 time=0.279 ms
64 bytes from 192.168.0.8: icmp_seq=3 ttl=64 time=0.282 ms
64 bytes from 192.168.0.8: icmp_seq=4 ttl=64 time=0.303 ms

— alpha.pkpk.com ping statistics —
4 packets transmitted, 4 received, 0% packet loss, time 3001ms
rtt min/avg/max/mdev = 0.279/0.315/0.398/0.051 ms
“`

위와 같이 핑테스트가 잘되면 성공이다.

### API

powerdns의 3.4버전부터 api를 사용가능하다.
우분투에서 `apt-get install`로 설치하는 녀석들은 버전이 낮은 경우가 많으니 주의하자.
설정파일에 아래와 같은 부분을 설정하고, powerdns를 재시작 해주면 활성화가 된다.

“`
설정을 업데이트하는 API를 사용하려면, mysql의 설정의 binlog_format을 row로 변경해주어야 한다.
“`

“`
api=yes
api-key=changeme
webserver=yes
“`

### API 사용예제

<`주의`> API를 사용해서 만든 zone 정보만 API를 사용해서 조작할 수 있다. 이점에 유의해야한다.

“`
# zone list
$ curl -H ‘X-API-Key: changeme’ http://192.168.0.44:8081/api/v1/servers/localhost/zones | jq .

# create zone
$ curl -X POST –data ‘{“name”:”example.org.”, “kind”: “Native”, “masters”: [], “nameservers”: [“ns1.example.org.”, “ns2.example.org.”]}’ -v -H ‘X-API-Key: changeme’ http://192.168.0.44:8081/api/v1/servers/localhost/zones | jq .

# show zone info
$ curl -H ‘X-API-Key: changeme’ http://192.168.0.44:8081/api/v1/servers/localhost/zones/pkpk.com | jq .

# change sub domain info
$curl -X PATCH –data ‘{“rrsets”: [ {“name”: “gamedb1.pkpk.com”, “type”: “A”, “ttl”: 0, “changetype”: “REPLACE”, “records”: [ {“content”: “192.168.0.29”, “disabled”: false } ] } ] }’ -H ‘X-API-Key: changeme’ http://192.168.0.44:8081/api/v1/servers/localhost/zones/pkpk.com | jq .

“`
[/markdown]

docker registry로 도커 컨테이너 관리하기

[markdown]
docker-registry

보통은 서버의 형상관리를 chef로 관리하지만,
소스를 배포할 때에 하나의 이미지로 뭉쳐서 배포를 해보고 싶어서
docker를 도입해보려고 공부중이다.

그런데 내가 생성한 이미지를 docker hub라는 열린 docker registry
올릴 수는 없어서, 찾아본것이 docker registry였다.

책이나 블로그에서 그다지 심도있게 다루고 있지 않아서, 쉬운가보다 했는데,
왠걸 설치가 생각보다 까다로웠다.

디지털오션의 글이 정말 많이 도움이 됐는데, 거기 있는 글을 보고
내용을 조금 추가, 수정 한것이라 보시면 될 것 같다.

모쪼록 docker registry를 도입하고자 하는 사람이 이 글을 보고 조금이나마 쉽게
구축 할 수 있으면 좋겠다.

시작하기 전에…

  • ubuntu를 사용 (14.04 면 좋음)
  • docker와 docker compose는 설치가 되어 있어야함
  • docker registry에서 사용할 도메인이 있어야함

docker 설치

설치전에 해야할 것들

뭔가 귀찮은게 많이 있는데, 그냥 빨리 설치하고 싶은 사람은 아래에 설.치. 부분으로 바로 이동하자.
시간이 있는 사람들은 봐두면 나중에 도움이 되는 부분은 있을 것 같다.

[code lang=text]
$ sudo apt-get update
$ sudo apt-get install apt-transport-https ca-certificates
$ sudo apt-key adv –keyserver hkp://p80.pool.sks-keyservers.net:80 –recv-keys 58118E89F3A912897C070ADBF76221572C52609D
$ sudo /etc/apt/sources.list.d/touch docker.list
[/code]

우분투의 다른 버전이라면 docker.list파일에 아래의 내용으로 변경하자

[code lang=text]
### ubuntu 12.04
deb https://apt.dockerproject.org/repo ubuntu-precise main

### ubuntu 14.04
deb https://apt.dockerproject.org/repo ubuntu-trusty main

### ubuntu 15.10
deb https://apt.dockerproject.org/repo ubuntu-wily main
[/code]

[code lang=text]
### `APT` 패키지의 인덱스를 업데이트
$ sudo apt-get update

### 오래된 버전 삭제
$ sudo apt-get purge lxc-docker

### `APT`확인하기
$ sudo apt-cache policy docker-engine
[/code]

linux-image-extra 설치

[code lang=text]
$ sudo apt-get install linux-image-extra-$(uname -r)
$ sudo apt-get install apparmor
$ sudo apt-get install docker-engine
[/code]

설.치.

위에 주저리 주저리 많은데, 보통은 이것만 해도 설치가 된다.

[code lang=text]
$ sudo apt-get update
$ sudo apt-get install docker-engine
$ sudo service docker start
$ sudo docker run hello-world
[/code]

요즘에 도커를 설치할 일이 많아서, 아래의 링크에 설치 스크리트를 정리해 두었다.

도커 설치 스크립트

docker compose 설치

[code lang=text]
$ sudo apt-get -y install python-pip
$ sudo pip install docker-compose
[/code]

docker compose 테스트

[code lang=text]
$ cd ~/docker-registry/
$ mkdir hello-world
$ cd hello-world
$ vi docker-compose.yml
[/code]

docker-compose.yml을 아래와 같이 만들자.

[code lang=text]
test-compose:
image: hello-world
[/code]

테스트

[code lang=text]
$ sudo docker-compose up
[/code]

아래와 같이 나오면 성공이다.

[code lang=text]
Creating helloworld_test-compose_1
Attaching to helloworld_test-compose_1
test-compose_1 |
test-compose_1 | Hello from Docker.
test-compose_1 | This message shows that your installation appears to be working
………. 블라블라……………..
test-compose_1 |
helloworld_test-compose_1 exited with code 0
[/code]

docker-registry 설치에 docker-compose가 이용되는 것이므로
docker-compose에 대해 더 알고 싶은 사람은 아래 링크를 참고하자.

Docker compose

서버와 클라이언트의 버전이 다르다고 나올 때

아래와 같은 메세지가 나오는 경우가 있는데,
아래의 경우 클라이언트가 서버보다 새로운 버전이라 나는 에러이니
설치된 오래된 docker를 삭제하고 새로운 버전으로 다시 설치하자.

[code lang=text]
ERROR: client and server don't have same version (client : 1.21, server: 1.18)
[/code]

아래의 메세지로 도커에 대한 정보를 알 수 있다.

[code lang=text]
$ docker version
$ docker info
[/code]

패스워드 생성을 위한 패키지 설치

apache2-utils에 있는 htpasswd 를 사용하기위한 패키지 다운로드

[code lang=text]
### kr.archive.ubuntu.com 에서는 패키지를 잘 못받아 오는 경우가 있으므로 변경해줌
$ sudo sed -i 's/kr.archive.ubuntu.com/ftp.daum.net/g' /etc/apt/sources.list
$ sudo apt-get update
$ sudo apt-get -y install apache2-utils
[/code]

Docker Registry 설치 및 설정하기

두개의 컨테이너를 연결할때 docker-compose를 사용할 것이다.
docker-compose.yml에 정의 된 내용을 설정 파일로 사용한다.

도커 레지스트리에서 사용할 디렉토리를 생성한다.

[code lang=text]
$ mkdir ~/docker-registry && cd $_
$ mkdir data
[/code]

docker-registry를 만들기위한 docker-compose.yml 파일을 아래와 같이 생성한다.

[code lang=text]
registry:
image: registry:2
ports:
– 127.0.0.1:5000:5000
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
volumes:
– ./data:/data
[/code]

아래의 명령어로 docker-registry 를 띄우자.

[code lang=text]
$ cd ~/docker-registry
$ sudo docker-compose up
[/code]

아래와 같은 메세지들이 나온다.

[code lang=text]
Attaching to dockerregistry_registry_1
registry_1 | time="2016-02-19T02:34:50Z" level=warning msg="No HTTP secret provided – generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.5.3 instance.id=840ceb15-6c68-41b2-a793-967ee3974a20 version=v2.3.0
registry_1 | time="2016-02-19T02:34:50Z" level=info msg="redis not configured" go.version=go1.5.3 instance.id=840ceb15-6c68-41b2-a793-967ee3974a20 version=v2.3.0
registry_1 | time="2016-02-19T02:34:50Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.5.3 instance.id=840ceb15-6c68-41b2-a793-967ee3974a20 version=v2.3.0
registry_1 | time="2016-02-19T02:34:50Z" level=info msg="listening on [::]:5000" go.version=go1.5.3 instance.id=840ceb15-6c68-41b2-a793-967ee3974a20 version=v2.3.0
registry_1 | time="2016-02-19T02:34:50Z" level=info msg="Starting upload purge in 55m0s" go.version=go1.5.3 instance.id=840ceb15-6c68-41b2-a793-967ee3974a20 version=v2.3.0
[/code]

No HTTP secret provided라는 문구에 놀라지 말라고 한다. 정상이라고~

아무튼 저 메세지가 나오면 잘 된것이니 Ctrl + C를 눌러서 빠져 나가자.
registry 하나 올리려고 docker-compose를 깐게 아니다.

Nginx 설정하기

[code lang=text]
$ mkdir ~/docker-registry/nginx
[/code]

docker-compose.yml파일을 다시 열어서 아래의nginx 관련 설정을 추가하자.

[code lang=text]
nginx:
image: "nginx:1.9"
ports:
– 5443:443
links:
– registry:registry
volumes:
– ./nginx/:/etc/nginx/conf.d
[/code]

결과적으로 docker-compose.yml은 아래와 같은 형태가 된다.

[code lang=text]
nginx:
image: "nginx:1.9"
ports:
– 5443:443
links:
– registry:registry
volumes:
– ./nginx/:/etc/nginx/conf.d
registry:
image: registry:2
ports:
– 127.0.0.1:5000:5000
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
volumes:
– ./data:/data
[/code]

그런데 nginx의 설정파일이 빠져있으므로 추가하자.

nginx 설정파일 추가하기

아래의 경로에 설정파일을 추가하자.

[code lang=text]
$ vi ~/docker-registry/nginx/registry.conf
[/code]

설정은 아래와 같다.

[code lang=text]
### registry.conf

upstream docker-registry {
server registry:5000;
}

server {
listen 443;
server_name docker.gyus.me;

client_max_body_size 0;
chunked_transfer_encoding on;

location /v2/ {
### docker 1.5 버전 이하에서 접속 안되게 함.
if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
return 404;
}

proxy_pass http://docker-registry;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900;
}
}
[/code]

$ docker-compose up으로 서버를 올릴 때 아래와 같은 에러가 났는데,

[code lang=text]
ERROR: failed to register layer: Untar re-exec error: unexpected EOF: output:
[/code]

sudo로 권한을 높여주니 해결 됐다. 압축을 풀 권한이 없는 곳에 압축을 풀려다가 에러가 난것 같다.

[code lang=text]
$ sodo docker-compose up
[/code]

nginx 랑 잘 연동이 되었는지 curl로 확인해보자.

[code lang=text]
$ curl http://localhost:5000/v2/
[/code]

{} 로 결과 값이 나오면 성공이다.

https가 바인딩되는 포트로 사용될 5443 포트로도 잘 되는지 확인해 보자.

[code lang=text]
$ curl http://localhost:5443/v2/
[/code]

역시나 {} 로 결과 값이 나오면 성공이다.

인증 설정하기

SSL만으로 불안한 사람은 이걸 하는게 좋은 것 같다.
나는 귀찮아서 사실 패스했다.

htpasswd 파일을 생성해보자.

[code lang=text]
$ cd ~/docker-registry/nginx
$ htpasswd -c registry.password <유저명>

### 패스워드를 입력해야한다. (안입력해도 되긴된다.)
New password:
Re-type new password:
[/code]

nginx설정파일인 registry.conf 파일에 인증관련 설정을 추가해주자.
location 항목 아래에 설정해주어야한다.

[code lang=text]
location /v2/ {
#### docker 1.5 버전 이하에서 접속 못하도록 막기.
if ($http_user_agent ~ "^(docker\1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
return 404;
}

#### 인증 설정
auth_basic "registry.localhost";
auth_basic_user_file /etc/nginx/conf.d/registry.password;
add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always;

proxy_pass http://docker-registry;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900;
}
[/code]

SSL 세팅하기

nginx 설정파일인 ~/docker-registry/nginx/registry.conf 파일에 SSL 인증 관련 설정을 추가하자.
SSL인증을 위해서는 해당도메인의 인증서 파일인 crt파일과 key파일이 필요하다.

[code lang=text]
server {
listen 443;
server_name deploy.gyus.me;

# SSL
ssl on;
ssl_certificate /etc/nginx/conf.d/domain.crt;
ssl_certificate_key /etc/nginx/conf.d/domain.key;
[/code]

인증서와 키파일은 startssl 사이트에서 공짜로 얻을 수도 있고,
다른 인증기관에서 구매도 가능하다.

임시적으로 사용하기 위해서 자가 인증서를 발급 하는 방법도 있는데, 아래에서 알아보자.

자가 인증서 발급하기

도커에서는 자가인증서만 가지고는, SSL를 사용할 수가 없고
자가 인증서를 인증해주는 자가 인증기관도 우리 서버에 세팅을 해야한다;;

자가인증기관에서 인증해줄수 있도록 루트 인증키를 만들자.

[code lang=text]
$ cd ~/docker-registry/nginx
### 루트 키를 만들자.
$ openssl genrsa -out rootCA.key 2048
### 루트 인증키를 만들자. (물어보는거는 대충 알아서 잘 적도록하자.)
$ openssl req -x509 -new -nodes -key rootCA.key -days 10000 -out rootCA.crt
[/code]

이제 우리 서버의 인증서를 만들어보자.

[code lang=text]
### 키파일을 만든다.
$ openssl genrsa -out gyusme.key 2048
### 인증서 서명 요청 파일을 만듬 (csr)
$ openssl req -new -key gyusme.key -out gyusme.csr
### 인증서를 생성
$ openssl x509 -req -in gyusme.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out gyusme.crt -days 10000
[/code]

인증서 서명 요청 파일 (csr)을 만들때에 이것 저것 물어보는데, 다른 건 몰라도 도메인은 잘 입력해주도록 하자.
그리고 패스워드를 물어보면 그냥 엔터 를 치자

[code lang=text]
Country Name (2 letter code) [AU]:KO
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:Busan
Organization Name (eg, company) [Internet Widgits Pty Ltd]:gyus,inc.
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:docker.gyus.me
Email Address []:
A challenge password []:
An optional company name []:
[/code]

여기까지 했다면 nginx디렉토리에는 아래와 같은 파일들이 있을 것이다.

[code lang=text]
-rw-rw-r– 1 ubuntu ubuntu 1229 Feb 19 17:29 gyusme.crt
-rw-rw-r– 1 ubuntu ubuntu 1054 Feb 19 17:28 gyusme.csr
-rw-rw-r– 1 ubuntu ubuntu 1679 Feb 19 17:26 gyusme.key
-rw-rw-r– 1 ubuntu ubuntu 831 Feb 19 17:10 registry.conf
-rw-rw-r– 1 ubuntu ubuntu 1269 Feb 19 17:25 rootCA.crt
-rw-rw-r– 1 ubuntu ubuntu 1675 Feb 19 17:25 rootCA.key
-rw-rw-r– 1 ubuntu ubuntu 17 Feb 19 17:29 rootCA.srl
[/code]

우리의 서버가 인증기관인것 처럼 동작하게 하는 세팅을 해주자.

[code lang=text]
$ sudo mkdir /usr/local/share/ca-certificates/docker-cert
$ sudo cp rootCA.crt /usr/local/share/ca-certificates/docker-cert
$ sudo update-ca-certificates
[/code]

SSL 테스트하기

테스트를 해보려면, SSL 설정까지 마치고 에러없이 docker registrynginx가 올라가야 한다.

[code lang=text]
$ cd ~/docker-registry
$ docker-compose up
[/code]

[code lang=text]
### 기본인증 사용X, 자가 인증서 X 인 경우
$ curl https://domain:5443/v2/

### 기본인증 사용 O, 자가 인증서 X 인 경우
$ curl https://USERNAME:PASSWORD@domain:5443/v2/

### 기본인증 사용 O, 자가 인증서 O 인 경우
$ curl https://USERNAME:PASSWORD@domain:5443/v2/ -k
[/code]

도커 레지스트리 서비스로 등록하기

도커 레지스트리를 운영하는 서버를 재시작 할 경우가 생길 수 있는데,
이때에 필요한 프로세스를 일일이 찾아서 올리는거는 많은 정신력을 필요로 한다.

그런일을 당하지 않도록 서비스로 등록해보자.

Upstart 스크립트를 작성해서, 시스템이 부팅될 때 시작되도록 해보자.

[code lang=text]
$ cd ~/docker-registry
### 지금 올라간 컨테이너를 삭제
$ docker-compose rm
$ sudo mv ~/docker-registry /docker-registry
$ sudo chown -R root: /docker-registry
[/code]

Upstart스크립트를 작성해보자.

[code lang=text]
$ sudo vi /etc/init/docker-registry.conf
[/code]

[code lang=text]
### docker-registry.conf
description "Docker Registry"

start on runlevel [2345]
stop on runlevel [016]

respawn
respawn limit 10 5

chdir /docker-registry

exec /usr/local/bin/docker-compose up
[/code]

이제 docker-registry를 실행 시켜보자.

[code lang=text]
$ sudo service docker-registry start
[/code]

아래와 같이 나오면 성공이다.

[code lang=text]
docker-registry start/running, process 27241
[/code]

아래의 명령어로 또한 확인이 가능하다.

[code lang=text]
$ docker ps
[/code]

로그는 아래의 명령어로 확인이 가능하다.

[code lang=text]
$ sudo tail -f /var/log/upstart/docker-registry.log
[/code]

curl 명령어로 확인을 하면 nginx_1이 앞에 붙어서 로그가 찍힐 것이다.

[code lang=text]
$ curl https://USERNAME:PASSWORD@domain:5443/v2/
[/code]

클라이언트 머신에서 docker registry 접근하기

다른 머신에서 도커 레지스트리에 접속하려면 루트인증서 파일을 해당 서버에 등록해주어야 한다.

[code lang=text]
$sudo cat /docker-registry/nginx/rootCA.crt
[/code]

위의 명령어를 실행하면 아래와 같은 형식의 암호화 문자열이 나올것이다.

[code lang=text]
—–BEGIN CERTIFICATE—–
MIIDfTCCAmWgAwIBAgIJAKJf+hKlYzotMA0GCSqGSIb3DQEBCwUAMFUxCzAJBgNV
…. 중략…
ShmxzUJQAcQwdI2Wrivv9QG4kcRPw73lrMTNU8FfYnfQ
—–END CERTIFICATE—–
[/code]

이걸 docker registry서버의 머신에서 해주었던 것 처럼 등록해주어야 한다.

우분투의 경우

[code lang=text]
$ sudo mkdir /usr/local/share/ca-certificates/docker-dev-cert
$ sudo cp rootCA.crt /usr/local/share/ca-certificates/docker-dev-cert/
$ sudo update-ca-certificates
[/code]

Centos의 경우

[code lang=text]
$ sudo cp rootCA.crt /etc/pki/ca-trust/source/anchors/
$ sudo update-ca-trust enable
$ sudo update-ca-trust extract
[/code]

루트 인증서를 추가후 도커를 재시작 해야한다.

[code lang=text]
$ sudo service docker restart
[/code]

인증을 사용하는 경우 docker login을 사용해야 하는데, 사용법은 아래와 같다.

[code lang=text]
$ docker login https://<domain>
[/code]

UsernamePassword Email을 물어보는데, Email은 그냥 아무것도 입력 하지 않으면 된다.

성공인 경우 아래와 같은 메세지가 뜬다.

[code lang=text]
Login Succeeded
[/code]

이미지를 올려보자.

가장 쉬운 hello-world로 해보자.

[code lang=text]
$ sudo docker run hello-world
$ sudo docker tag hello-world deploy.gyus.me:5443/hello-world
$ sudo docker push deploy.gyus.me:5443/hello-world
[/code]

위와 같은 스크립트를 실행시키면 성공시에 아래와 같은 메세지들이 나온다.

[code lang=text]
The push refers to a repository [deploy.gyus.me:5443/hello-world]
5f70bf18a086: Pushed
b652ec3a27e7: Pushed
latest: digest: sha256:fea8895f450959fa676bcc1df0611ea93823a735a01205fd8622846041d0c7cf size: 708
[/code]

다른 머신에서 이미지를 땡겨와보자.

땡겨오는 법은 그냥 push 대신 pull을 쓰면 된다.
물론 인증서 작업은 완료된 상태이어야 한다.

[code lang=text]
$ docker pull deploy.gyus.me:5443/hello-world
[/code]

[code lang=text]
$ docker run deploy.gyus.me:5443/hello-world
[/code]

잘 실행된다!

[code lang=text]
Hello from Docker.
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
https://hub.docker.com

For more examples and ideas, visit:
https://docs.docker.com/userguide/
[/code]

느낀점

처음에는 좀 어렵다 귀찮다라는 느낌이 강했는데,
차근 차근 설치해보니, 크게 어려운 점은 없었다.

https로 통신을 해야하니 자가인증기관과 자가인증서를 만들어 주는 부분을
처음 해보는거라서 좀 이상하긴 했지만, 이 부분은 나중에 또 공부를 해야되는 부분으로
남겨두기로 한다.

참고 링크

아래의 링크를 대부분 참고했고, 따라하면서 내용을 조금씩 추가하거나 생략 하거나 했다.
디지탈 오션

추가 (인증서 인증 실패 관련)

자가인증서를 사용하게 되면, 아래와 같은 에러가 나는 경우가 있다.

[code lang=text]
tls: oversized record received with length 20527
[/code]

이런경우 /etc/docker/certs.d/domain:5443 의 경로에
위에서 만든 rootCA.crt 파일을 복사한후 도커를 재시작 해주면 된다.
자세한 것은 아래의 링크를 참고하자.

https://docs.docker.com/registry/insecure/

[/markdown]

Chef Server 12으로 인프라 자동화하기

Chef Server 12를 사용해보기

 

2년전에 chef-server로 게임서버들을 잘 구성해서 사용했었다.
knife-solo로 하면 10대 정도까지는 그냥 관리가 가능하긴 한데,
role이나 databag같은 메타데이터를 다루게 되면,
그때부터는 chef-server를 사용하는 것도 좋은 것 같다.

2년 전에도 쉐프 서버 구축할 때 이리저리 고생을 했던 기억이 나는데,
정리를 제대로 안해놨더니, 했던 삽질을 다시하면서 나의 멍청함을 뇌로 되새기고 있다.

아무튼 다시는 그런일이 벌어지지 않게 하기 위해서,
좀 정성들여서 정리를 하는중이고, 특히 에러가 나는 포인트를 계속해서 정리중이다.

chef서버와 client는 virtual box를 사용해서 2대를 실행했고,
workstation은 mac에 chefdk를 설치해서 돌리고 있다.

사실 chef server 이외에는 chef client & knife가 깔리면 되는거라서, 뭘 어떻게 하든 크게 관계는 없을 것 같다.

기본지식

  • chef-server-core만 인스톨한 후에는 chef-server-ctl커맨드로 reconfigure을 실행해주어야한다. 설정파일이 변경되면 reconfigure를 실행해야한다.
  • chef-server-core관련 파일은 /etc/opscode에 있다.
  • 로그는 /var/log/opscode에 있다
  • chef서버의 로그를 실시간으로 보려면 chef-server-ctl tail 명령어를 사용하면 된다.

chef server 12에서는 여러가지 기능이 추가된 것 같긴한데, 기본만 설치하자.
– chef-server-core
– manage ui

chef-server-core 설치

아래 링크에서 chef-server-core_12.x.x-x_amd64.deb를 받는다. (버전은 그때 그때 다르다. RHEL은 패스)
2016년 10월의 최신버전은 12.9.1이다.

우분투 16.04
https://packages.chef.io/stable/ubuntu/16.04/chef-server-core_12.9.1-1_amd64.deb

우분투 14.04
https://packages.chef.io/stable/ubuntu/14.04/chef-server-core_12.9.1-1_amd64.deb

우분투 12.04
https://packages.chef.io/stable/ubuntu/12.04/chef-server-core_12.9.1-1_amd64.deb

아래 명령어를 실행

[code lang=text]
$ sudo dpkg -i chef-server-core_12.x.x-x_amd64.deb
$ sudo chef-server-ctl reconfigure
[/code]

reconfigure 실행시의 에러 처리

reconfigure를 실행시에 아래와 같은 에러가 날 수 있는데, 이는 nginx cookbook의 이슈이다.

[code lang=text]
Chef::Exceptions::ValidationFailed
———————————-
common_name is required
[/code]

이를 해결하려면 /etc/hosts에 유효한 호스트명을 뭐라도 넣어줘야된다.
에는 hostname 이라는 명령어를 치면 나오는 녀석으로 변경해야한다.

sudo echo "127.0.0.1 localhost localhost.localdomain <hostname>" | sudo tee -a /etc/hosts

이게 오픈소스가 되서그런지 버그가 은근 있는데, reconfigure 시에 아래와 같은 에러가 나올 수도 있다.

[code lang=text]
ubuntu@chef-server:~$ sudo chef-server-ctl reconfigure
Starting Chef Client, version 12.14.98
resolving cookbooks for run list: [“private-chef::default”]
Synchronizing Cookbooks:
– enterprise (0.10.1)
– yum (3.13.0)
– runit (1.6.0)
– packagecloud (0.2.5)
– apt (2.9.2)
– openssl (6.0.0)
– private-chef (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks…
[2016-10-21T03:00:43+00:00] WARN: Chef::Provider::AptRepository already exists! Cannot create deprecation class for LWRP provider apt_repository from cookbook apt
[2016-10-21T03:00:43+00:00] WARN: AptRepository already exists! Deprecation class overwrites Custom resource apt_repository from cookbook apt
[2016-10-21T03:00:43+00:00] WARN: Chef::Provider::YumRepository already exists! Cannot create deprecation class for LWRP provider yum_repository from cookbook yum
[2016-10-21T03:00:43+00:00] WARN: YumRepository already exists! Deprecation class overwrites Custom resource yum_repository from cookbook yum
Recipe: private-chef::default
* directory[/etc/opscode] action create (up to date)
* directory[/etc/opscode/logrotate.d] action create (up to date)
[2016-10-21T03:00:43+00:00] FATAL:

———————————————————————–
BOOT007: The secrets file (/etc/opscode/private-chef-secrets.json) is present
but the file /etc/opscode/pivotal.pem is missing.

Ensure that private-chef-secrets.json is copied into /etc/opscode from the
first Chef Server node that you brought online, then run
‘chef-server-ctl reconfigure’ again.
———————————————————————–
[/code]

이 경우에는 /etc/opscode/private-chef-secrets.json 파일을 삭제하고
다시 sudo chef-server-ctl reconfigure를 해주면 된다.

user/org를 생성

[code lang=text]
### user생성
chef-server-ctl user-create user_name first_name last_name email password –filename ADMIN_PEM_FILE_NAME

### org생성
chef-server-ctl org-create short_name full_organization_name –association_user user_name –filename ORG_VALIDATION_KEY_FILE_NAME
[/code]

/etc/opscode/admin.pem
/etc/opscode/orgname-validator.pem
등의 이름이 뭘하는 파일인지 알아보기 쉽다.

chef manage ui 설치

이번 버전부터 ui가 엄청 이뿌게 변했다.
소스를 대충 보니 bootstrap을 사용한것 같은데, 개발자스럽긴하지만, 이쁜 UI이다.

[code lang=text]
$ sudo chef-server-ctl install opscode-manage
$ sudo opscode-manage-ctl reconfigure
[/code]

12.9 버전에서 opscode-manage가 chef-manage로 변경되었다. 최신버전을 쓰는 사람은 chef-manage로 설치하자

[code lang=text]
$ sudo chef-server-ctl install chef-manage
$ sudo chef-server-ctl reconfigure
$ sudo chef-manage-ctl reconfigure
[/code]

workstation 구축

chef를 설치

[code lang=text]
$ sudo curl -L https://www.opscode.com/chef/install.sh | sudo bash
[/code]

chefdk(chef development kit) 라는 녀석을 설치해도 된다.

chefdk에는 chef, Test Kitchen, ChefSpec, Foodcritic, Chef Client, Knife, Ohai, Chef Zero 등이 함께 들어 있어서 쉐프 레시피개발 및 테스트에 좋다.

knife 설정

[code lang=text]
$ knife configure
Where should I put the config file? [/home/USER/.chef/knife.rb]
Please enter the chef server URL: [https://xxx:443] https://chef-server/organizations/org
Please enter an existing username or clientname for the API: [user] <USER>
Please enter the validation clientname: [chef-validator] <ORG>-validator
Please enter the location of the validation key: [/etc/chef-server/chef-validator.pem] /Users/wapj/chef/.chef/<ORG>-validator.pem
Please enter the path to a chef repository (or leave blank): <CHEF-REPO-PATH>
[/code]

위와 같이 하면 .chef 디렉토리 아래에 knife.rb 파일이 생긴다.
해당 파일은 아래와 같이 생겼다.

[code lang=text]
### knife.rb
log_level :info
log_location STDOUT
node_name ‘wapj’
client_key ‘/Users/wapj/chef/.chef/admin.pem’
validation_client_name ‘company-validator’
validation_key ‘/Users/wapj/chef/.chef/org-validator.pem’
chef_server_url ‘https://wapj-VirtualBox/organizations/wapj’
syntax_check_cache_path ‘/Users/wapj/chef/.chef/syntax_check_cache’
cookbook_path [“/Users/wapj/chef/site-cookbooks”]
[/code]

admin.pem파일과 org-validator.pem 파일은 쉐프 서버에서 scp 명령어 등으로 잘 가져와서 해당디렉토리에 복사하자.

chef서버와 잘 통신되는지 확인

아래와 같은 명령어로 확인이 가능하다.

[code lang=text]
$ knife ssl check
[/code]

trouble shooting

knife.rb의 chef_server_url설정에 아이피를 넣은 경우

아래와 같은 에러가 난다.

[code lang=text]
Connecting to host 192.168.10.111:443
ERROR: The SSL cert is signed by a trusted authority but is not valid for the given hostname
ERROR: You are attempting to connect to: ‘192.168.10.111’
ERROR: The server’s certificate belongs to ‘wapj-VirtualBox’

TO FIX THIS ERROR:

The solution for this issue depends on your networking configuration. If you
are able to connect to this server using the hostname wapj-VirtualBox
instead of 192.168.10.111, then you can resolve this issue by updating chef_server_url
in your configuration file.

If you are not able to connect to the server using the hostname wapj-VirtualBox
you will have to update the certificate on the server to use the correct hostname.
[/code]

이 경우 /etc/hosts에 server의 domain혹은 QFDN을 아래와 같은 식으로 넣어주어야 한다.

[code lang=text]
# hosts
192.168.10.111 wapj-VirtualBox
[/code]

chef server에서 받은 인증서가 localhost로 되어 있을 때

로컬에서만 테스트하다가 실제로 서버를 올리면 이런 일이 생기곤 한다.
workstation 에서 아래의 명령어를 실행했는데

[code lang=text]
$ knife ssl check
[/code]

아래와 같은 에러가 나는경우

[code lang=text]
Connecting to host chef.gyus.me:443
ERROR: The SSL cert is signed by a trusted authority but is not valid for the given hostname
ERROR: You are attempting to connect to: ‘chef.gyus.me’
ERROR: The server’s certificate belongs to ‘localhost’

TO FIX THIS ERROR:

The solution for this issue depends on your networking configuration. If you
are able to connect to this server using the hostname localhost
instead of chef.gyus.me, then you can resolve this issue by updating chef_server_url
in your configuration file.

If you are not able to connect to the server using the hostname localhost
you will have to update the certificate on the server to use the correct hostname.
[/code]

이 경우는 chef server의 /etc/opscode/chef-server.rb 파일에 설정을 추가해주어야한다.
chef server의 도메인이 chef.gyus.me라고 한다면, 아래와 같이 수정해 주면 된다.

[code lang=text]
### /etc/opscode/chef-server.rb
api_fqdn “chef.gyus.me”
[/code]

설정 파일이 수정되었으므로 서버에서 chef-server-ctl reconfigure를 한번더 실행해준다.

그리고 workstation으로 돌아와서 인증서를 받아오는 작업을 다시 해주자.

[code lang=text]
$ knife ssl fetch
$ knife ssl check
[/code]

아래와 같이 나오면 성공이다.

[code lang=text]
Connecting to host chef.gyus.me:443
Successfully verified certificates from `chef.gyus.me’
[/code]

아래와 같은 에러가 날 때

[code lang=text]
$knife status
ERROR: The object you are looking for could not be found
Response: <!DOCTYPE html>
<html xmlns=”http://www.w3.org/1999/xhtml”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<meta http-equiv=”X-UA-Compatible” content=”IE=EmulateIE7″ />
<title>Chef – 404 Not Found</title>
<link media=”all” rel=”stylesheet” type=”text/css” href=”/css/all.css” />
<!–[if lt IE 7]><link rel=”stylesheet” type=”text/css” href=”/css/lt7.css” /><![endif]–>
</head>
<body>
<div class=”header-block”>
<div id=”header”>
<strong class=”logo”><a href=”http://www.getchef.com”>Chef</a></strong>
</div>
</div>
<div id=”wrapper”>
<div id=”main”>
<div class=”mybox”>
<div id=”content”>
<h1>404 – Not Found</h1>
<p>Sorry, I can’t find what you are looking for.</p>
</div>
</div>
</div>
</div>
<div class=”footer-block”>
<div id=”footer”>
<div class=”mybox”>
</div>
<div class=”footer-bottom”>
<span>© 2010 – 2014 Chef Software, Inc. All Rights Reserved</span>
</div>
</div>
</div>
</body>
</html>
[/code]

knife.rb 설정의 chef_server_url에 정말로 serverURL만 넣은 경우에 나는 에러이다.

아래와 같은 식으로 되어있다면,

[code lang=text]
chef_server_url https://wapj-VirtualBox/
[/code]

뒤에 organizations/<ORGANIGATION>을 붙여주자.

[code lang=text]
chef_server_url https://wapj-VirtualBox/organizations/wapj
[/code]

쿡북 만들기

아래 명령어로 hello라는 쿡북을 site-cookbooks에 만들었다.
레시피

[code lang=text]
$ knife cookbook create hello -o site-cookbooks
[/code]

간단히 아래 파일을 변경하자.

[code lang=text]
site-cookbooks/hello/recipes/defau1t.rb
[/code]

[code lang=text]
### default.rb
log “Hello , Chef!”
[/code]

쿡북을 서버로 업로드하기

아래의 커맨드를 사용하면 모든 쿡북이 서버로 업로드 된다.

[code lang=text]
$ knife cookbook upload -a
[/code]

하나의 쿡북만 업로드 하고 싶은 경우는 아래의 커맨드를 사용하자.

[code lang=text]
$ knife cookbook upload <쿡북이름> -o <해당쿡북의 폴더가 있는 경로>
$ knife cookbook upload hello -o ./site-cookbooks/
[/code]

쿡북 업로드시에 SSL 에러가 나는 경우

[code lang=text]
WARN: Found a directory hello in the cookbook path, but it contains no cookbook files. skipping.
ERROR: SSL Validation failure connecting to host: 192.168.10.111 – hostname “192.168.10.111” does not match the server certificate
[/code]

자가 인증서로 검증하기 때문에 나는 당연한 에러(?!) 라서, 검증하지 말라고 하면 된다..;;

아래 한줄을 knife.rb에 추가해주자

[code lang=text]
ssl_verify_mode :verify_none
[/code]

chef-client

chef 설치

  • 서버와의 인증에는 비밀키를 사용한다.
  • chef server는 client별로 비밀키를 발행하고, 공개키를 chef-server에 등록한다.
  • client별로 비밀키를 발행해야 하기 때문에, 미리 준비된 validator-key를 사용한다.
  • client는 처음에는 validator-key로 통신해서 chef server에 client로 등록한후 이후의 통신에 필요한 비밀키가 발행된다.
  • workstation에서 삽질했던것 처럼 hosts에 chef server의 ip를 등록해야한다.

knife bootstrap으로 client 등록하기

전제 조건
1. chef server에서 client서버로 비밀번호 없이 root계정으로 로그인을 할 수 있어야한다.
2. bootstrap 을 실행할 서버의 /etc/hosts에 chef server의 ip를 등록한다.

1번 조건은 root의 홈디렉토리에 chef server의 퍼블릭키를 authorized_keys파일에 등록하면 된다.
2번은 위의 workstation 설정시에 보았던, /etc/hostschef server의 ip를 등록하면 된다.

chef server가 만약에 해킹당하게 되면, 모든 서버에 접속 할 수 있으므로 이 방법을 사용할 때에는,
chef server의 보안에 특히 주의 해야한다.
나 같은 경우는 chef server를 사용할 때만 켰다가 끄는 방식으로 사용했다.

위의 조건이 만족되면, 아래의 커맨드로 바로 노드를 등록할 수 있다.

[code lang=text]
$ knife bootstrap <client server ip> -x <user> -N <node name>
[/code]

성공하게 되면 아래와 같은 메세지가 나오게 된다.

[code lang=text]
### workstation의 chef repo 디렉토리에서 실행하면 된다.

$knife bootstrap 192.168.10.101 -x root -N node1
Doing old-style registration with the validation key at /Users/wapj/chef/my-chef/.chef/company-validator.pem…
Delete your validation key in order to use your user credentials instead

Connecting to 192.168.10.101
192.168.10.101 —–> Existing Chef installation detected
192.168.10.101 Starting the first Chef Client run…
192.168.10.101 Starting Chef Client, version 12.7.2
192.168.10.101 Creating a new client identity for node1 using the validator key.
192.168.10.101 resolving cookbooks for run list: []
192.168.10.101 Synchronizing Cookbooks:
192.168.10.101 Compiling Cookbooks…
192.168.10.101 [2016-02-16T21:02:32+09:00] WARN: Node node1 has an empty run list.
192.168.10.101 Converging 0 resources
192.168.10.101
192.168.10.101 Running handlers:
192.168.10.101 Running handlers complete
192.168.10.101 Chef Client finished, 0/0 resources updated in 02 seconds
[/code]

node가 등록되었는지 확인

[code lang=text]
### workstation의 chef repo에서 실행
$ knife node list
[/code]

잘 등록되었다면 아래와 같이 나올 것이다.

[code lang=text]
node1
[/code]

node에 레시피 등록 하기

노드는 등록되었지만, 레시피가 아무것도 없으므로 실행해봐야 아무것도 안나온다.
레시피를 등록해보자.

해당 작업은 chef server의 WEB UI에서도 가능하므로, 웹사이트에 들어가서 해도 된다.

[code lang=text]
### workstation의 chef repo에서 실행
$ knife node run_list add <노드명> ‘recipe[레시피명]’
$ knife node run_list add node1 ‘recipe[hello]’
[/code]

node에서 레시피 실행하기

레시피의 실행은 각 노드 서버에 들어가서 $ sudo chef-client 를 실행해주면 되지만,
귀찮게 일일이 들어가면 굳이 서버를 설치한 이유가 없으니 서버에서 실행할 수 있는 방법을 찾아보자.

바로 knife search node를 이용하는 방법인데,

아래의 명령어를 실행하면, 조건을 줘서 chef client를 찾을 수 있다.

[code lang=text]
$ knife search node “fqdn:*”
[/code]

그리고 조건은 knife ssh에도 동일하게 사용가능 한데, 조건과 조합하여 여러서버에 커맨드를 날릴수있다.
예를 들어 아래와 같이 실행하게 되면, 모든 서버에 echo "hello" 명령을 날리게 된다.

[code lang=text]
$ knife ssh “fqdn:*” ‘echo “hello”‘
[/code]

이를 응용하여 아래와 같이 실행하게 되면, 모든 클라이언트에 sudo chef-client를 실행 할 수 있게 된다.

[code lang=text]
$ knife ssh “fqdn:*” “sudo chef-client”
[/code]

hosts에 정보가 없는 경우

아래와 같은 에러가 난다.

[code lang=text]
WARNING: Failed to connect to node1 — SocketError: getaddrinfo: nodename nor servname provided, or not known
[/code]

이 경우는 workstation의 /etc/hosts에 node1의 ip를 등록해야한다.

[code lang=text]
### workstaions의 /etc/hosts
192.168.10.101 node1
[/code]

다시 실행해보면~

[code lang=text]
$ knife ssh “fqdn:*” “sudo chef-client”
[/code]

아직도 에러가 난다;;;

[code lang=text]
node1
node1 Starting Chef Client, version 12.7.2
node1 resolving cookbooks for run list: [“hello”]
node1 Synchronizing Cookbooks:
node1 [2016-02-16T21:45:15+09:00] ERROR: SSL Validation failure connecting to host: wapj-virtualbox – hostname “wapj-virtualbox” does not match the server certificate
node1 [2016-02-16T21:45:15+09:00] ERROR: SSL Validation failure connecting to host: wapj-virtualbox – hostname “wapj-virtualbox” does not match the server certificate
node1 [2016-02-16T21:45:15+09:00] ERROR: SSL Validation failure connecting to host: wapj-virtualbox – hostname “wapj-virtualbox” does not match the server certificate
node1 ================================================================================
node1 Error Syncing Cookbooks:
node1 ================================================================================
node1
node1 Unexpected Error:
node1 —————–
node1 OpenSSL::SSL::SSLError: SSL Error connecting to https://wapj-virtualbox/bookshelf/organization-7479a4a25da1226f99983095c54052ba/checksum-40dcaffc3a2cdc8968a18f61672dab63?AWSAccessKeyId=49472f43a12b78c05ea083a9e82ac20de4ca4463&Expires=1455655515&Signature=ftp5IYQKkyvjyXYPG%2BlPltfh/18%3D – hostname “wapj-virtualbox” does not match the server certificate
node1 Running handlers complete
node1 [2016-02-16T21:45:40+09:00] ERROR: Exception handlers complete
node1 Chef Client failed. 0 resources updated in 27 seconds
node1 [2016-02-16T21:45:40+09:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
node1 [2016-02-16T21:45:40+09:00] ERROR: SSL Validation failure connecting to host: wapj-virtualbox – hostname “wapj-virtualbox” does not match the server certificate
node1 [2016-02-16T21:45:40+09:00] FATAL: Please provide the contents of the stacktrace.out file if you file a bug report
node1 [2016-02-16T21:45:40+09:00] ERROR: SSL Error connecting to https://wapj-virtualbox/bookshelf/organization-7479a4a25da1226f99983095c54052ba/checksum-40dcaffc3a2cdc8968a18f61672dab63?AWSAccessKeyId=49472f43a12b78c05ea083a9e82ac20de4ca4463&Expires=1455655515&Signature=ftp5IYQKkyvjyXYPG%2BlPltfh/18%3D – hostname “wapj-virtualbox” does not match the server certificate
node1 [2016-02-16T21:45:40+09:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
[/code]

에러는 인증서가 매치가 안된다는 말인데, 자가 인증서이기 때문에 안되는 문제이다.

/etc/chef/client.rbssl_verify_mode :verify_none을 또 추가해주자.

아래와 같은 형태로 client.rb가 되어 있으면 된다.

[code lang=text]
### node1 서버 client.rb
log_location STDOUT
chef_server_url “https://chef-server/organizations/company”
validation_client_name “company-validator”
node_name “node1”
trusted_certs_dir “/etc/chef/trusted_certs”
ssl_verify_mode :verify_none
[/code]

다시 실행해보자

[code lang=text]
$ knife ssh “fqdn:*” “sudo chef-client”
[/code]

아래와 같이 실행된다.

[code lang=text]
node1
node1 Starting Chef Client, version 12.7.2
node1 resolving cookbooks for run list: [“hello”]
node1 Synchronizing Cookbooks:
node1 – hello (0.1.0)
node1 Compiling Cookbooks…
node1 Converging 1 resources
node1 Recipe: hello::default
node1 * log[Hello Chef!] action write
node1
node1
node1 Running handlers:
node1 Running handlers complete
node1 Chef Client finished, 1/1 resources updated in 01 seconds
[/code]

개발PC에서 cookbook을 만들어서 올리고, chef의 API를 사용해서 실행하는 것을 보여주기위해
workstation이라는 것을 예로 들었지만, workstation을 굳이 만들지 않아도 되고, chef serverchef를 설치해서 하는 것이 더 간단한 방법일 수도 있다.
개념을 파악하고 구조를 파악해서, 각자의 환경에 맞게 잘 사용하면 될 것 같다.

좀 길었지만, chef server 설치중에 내가 만난 문제들은 다 정리가 된것 같다.
몇가지 수동으로 했던 부분들을 자동화하게 되면, 훌륭한 인프라 툴이 될 것이다.

TIPS

여기서 부터는 꼭 안봐도 되고, 약간씩 도움이 되는 팁을 정리하려고 한다.
알면 좋고 모르면, 그냥 손이 수고하면 되는(?) 그런 부분이다. 귀찮으면 과감히 패스~

knife.rb 설정에 변수 사용하기

팁이라고 하기도 뭐한 팁인데,
쉐프의 소스가 git 같은 소스 관리 프로그램으로 관리되는 경우,
키의 경로를 절대 경로로 잡아주게 되면,
다른 pc나 인스턴스에서 쉐프 소스를 받게 되면, 매번 수정해야 하는 번거로움이 생긴다.
이 때에 각 키의 경로를 그냥 knife.rb와 같은 경로로 변수를 사용하는 팁이 되시겠다.

knife.rb 파일은 말 그대로 루비 스크립트 이므로 루비의 문법을 사용하면 되는데,
File.dirname(__FILE__) 이라는 녀석이랑 erb의 문법을 사용할 것이다.

[code lang=text]
### knife.rb
urrent_dir = File.dirname(__FILE__)
log_level :info
log_location STDOUT
node_name “company”
client_key “#{current_dir}/admin.pem”
validation_client_name “company-validator”
validation_key “#{current_dir}/gyus-validator.pem”
chef_server_url “https://deploy.gyus.me/organizations/gyus”
syntax_check_cache_path “#{current_dir}/syntax_check_cache”
ssl_verify_mode :verify_mode
[/code]

위의 파일에서 중요한 것은 홑따옴표 ' 가 아니라 쌍따옴표 "를 써야 변수를 루비가 인식하고 변환해준다는 점이다.

업데이트 (2016.10.21)

자가 인증서가 아닌 comodo등의 기관에서 발급받은 인증서를 적용하고 싶은경우.

/var/opt/opscode/nginx/ca 아래에 있는 chef-server.crt, chef-server.key 를 자신이 가지고 있는 인증서로 바꿔치기 하면된다.

파일명을 같게한다음(확장자는 맞춰줘야함) 덮어쓰자.

그 후 설정을 변경하기위해 sudo chef-server-ctl reconfigure 를 실행하고 sudo chef-server-ctl restart를 실행해주자.

이렇게 하면 끝난다. 파일명을 다르게 적용하고 싶다면, /var/opt/opscode/nginx/etc/chef_https_lb.conf 안에 있는 설정을 변경해야한다.

아래와 같은 설정이 있는데, 파일명도 바꿔주면 된다.

[code lang=text]
ssl_certificate /var/opt/opscode/nginx/ca/chef-server.crt;
ssl_certificate_key /var/opt/opscode/nginx/ca/chef-server.key;
[/code]

어려울 줄 알았는데, 생각보다 너무 쉬웠음.

docker 명령어들

[markdown]

# docker 명령어들

도커로 apache php mysql환경을 구성중이다.
apache와 php를 하나의 환경으로 묶기로 했고, mysql은 따로 하나더 묶을 예정이다.

이 글은 위의 환경을 구축중에 공부하고 있는 것들을 정리하는 글인데, 위의 환경 구축이 완료되면
관련해서 글을 하나 더 쓸 수 있을 것 같다.

도커가 간단하네 심플하네 빠르네, 이런 것들로 홍보를 하고 있지만,
알아야 될 것 들이 생각보다 많다.
명령어를 도저히 다 외울 수가 없어서 그냥 정리하기로 했다.

도커를 공부하면서 좋은 것 하나는 bash와 자동화에 대한 지식이 뽀너스로 늘어나는 것이 좋은 것 같다.
물론 알아야되는게 많아서 짜증나는 점이기도 하다.

오픈소스 홈페이지들에 올라가 있는 정보는 내가 보기에는 너무 상세하고 스펙적인게 대부분이기 때문에,
그냥 이렇게 케이스별로 정리를 하는게 나에게는 도움이 많이 되었던것 같다.

찾아보면 대부분 나오는 것들이겠지만,
도커를 하면서 조금이라도 삽질을 줄이려면, 자신만의 커맨드라인 리스트 같은게 있으면
개발할 때 좋을 것 같다.

아직 공부중이기 때문에 해당 포스트는 추후에 좀 더 내용이 추가될 가능성이 있다.

### 도커설치(우분투)

“`
$ sudo apt-get update
$ sudo apt-get install docker.io
“`

### 도커설치(Centos)

“`
$ sudo yum update
$ curl -fsSL https://get.docker.com/ | sh
$ sudo service docker start
“`

### 도커설치 윈도우, OSX

### docker 실행시 관리자 권한으로 실행되게 하기
매번 실행할때마다 sudo 넣는게 귀찮은 사람은 실행하면 좋다. (ubuntu에서는 잘되는데 cent에서는 잘 안됨!)

“`
$ sudo usermod -aG docker ${USER}
$ sudo service docker restart
“`

아래와 같은 에러가 나는 경우. 재부팅을 해주면 된다.
“`
FATA[0000] Post http:///var/run/docker.sock/v1.18/images/create?fromImage=registry%3Alatest: dial unix /var/run/docker.sock: permission denied. Are you trying to connect to a TLS-enabled daemon without TLS?
“`

### docker 프로세스 모두 죽이기

“`
$ docker stop $(docker ps -qa)
“`

### docker 컨테이너 모두 삭제하기

가상머신에서 도커이미지를 만들게 되면, 용량이 부족하다고 가끔 뜨는데 그때 필요하다.

“`
$ docker rm $(docker ps -qa)
“`

### docker 이미지 모두 삭제하기
요것도 가상머신에서 용량이 부족하다고 뜰때 필요하다.
“`
$ docker rmi $(docker images -qa)
“`

### 도커 컨테이너 데몬모드로 시작시키기

“`
docker run -d image명
“`

### 도커 컨테이너에 배쉬연결

“`
docker run -it image명
“`

### 기존 ENTRYPOINT를 무시하고 entrypoint설정하기

“`
docker run –entrypoint=”/bin/bash”
“`

### 컨테이너끼리 연결하기

“`
docker run –link=”db:db” image명
“`

### 데몬모드로 띄우면서 포트, 볼륨, 환경설정까지 해보기

“`
docker run -d -p 8080:80 -v /home/user/www:/var/www -e PHP_ERROR_REPORTING=’E_ALL & ~E_STRICT’
“`
[/markdown]