2019年7月31日水曜日

docker で dnsmasq を構築する

概要

前回 Ubuntu 上に dnsmasq を構築してみました
今回は docker コンテナとして dnsmasq を構築してみたいと思います

環境

  • Ubuntu 16.04
  • docker 18.09.8
  • dnsmasq

イメージ

andyshinn/dnsmasq を使います
おそらくこれが一番無難なはず、、

コンテナ起動

基本的な使い方はすべて引数でコントロールする点です
/etc/dnsmasq.conf に記載できる内容はすべて引数で指定可能です
例えば前回と同じ内容で起動する場合は

  • docker run -p 53:53/tcp -p 53:53/udp --cap-add=NET_ADMIN andyshinn/dnsmasq -D -b --local=/vagrant.local/ -E --domain=vagrant.local

という感じで起動します

  • -D・・・domain-needed
  • -b・・・bogus-priv
  • --local・・・local=/vagrant.local/
  • -E・・・expand-hosts
  • --domain・・・domain=vagrant.local

になります
そして A レコードを登録する場合は更に引数に -A を使います

  • docker run -d -p 53:53/tcp -p 53:53/udp --cap-add=NET_ADMIN --name dns andyshinn/dnsmasq -D -b --local=/vagrant.local/ -E --domain=vagrant.local -A /mac/172.28.128.1 -A /mac.vagrant.local/172.28.128.1

DNS クライアントから動作確認

  • dig mac @172.28.128.3
  • dig mac.vagrant.local @172.28.128.3

どちらでも引けるはずです

CNAME は

これもオプションを追加します
--cname を使います
また cname の場合は /etc/hosts への記載が必要になるので -A ではなく docker の --add-host オプションを使います

  • docker run -d -p 53:53/tcp -p 53:53/udp --cap-add=NET_ADMIN --name dns --add-host=mac:172.28.128.1 --add-host=mac.vagrant.local:172.28.128.1 andyshinn/dnsmasq -D -b --local=/vagrant.local/ -E --domain=vagrant.local --cname=alt-mac,mac

これで CNAME も引けるようになります

  • dig alt-mac @172.28.128.3

冷静に考えると A, AAAA レコードは --add-host オプションを使うのが良いと思います

最後に

docker で dnsmasq を動かしてみました
ポイントは設定ファイルではなくすべてオプションで指定するという点です
オプションがわからない場合は dnsmasq --help あたりで調べられます
help で表示されるオプションはすべて docker でも指定可能です

設定ファイルも当然あるのですがそれよりもオプションで変更できるほうを推奨しているようです

  • docker exec dns ls /etc/dnsmasq.conf

参考サイト

2019年7月30日火曜日

Ubuntu で dnsmasq に入門してみた

概要

dnsmasq は軽量な DNS サーバです
他の有名な DNS ソフトウェアに bind などがありますが bind のような複雑な知識がなくても簡単に導入できます
今回は Ubuntu 上で dnsmasq を構築してみました

環境

  • Ubuntu 16.04 LTS
  • dnsmasq 2.75

インストール

  • sudo apt -y install dnqmasq

Ubuntu の場合は systemctl でデーモンを制御できるようになります
インストールされたファイルの一覧は以下の通りです

  • dpkg -L dnsmasq
/. /usr /usr/lib /usr/lib/resolvconf /usr/lib/resolvconf/dpkg-event.d /usr/lib/resolvconf/dpkg-event.d/dnsmasq /usr/share /usr/share/doc /lib /lib/systemd /lib/systemd/system /lib/systemd/system/dnsmasq.service /etc /etc/insserv.conf.d /etc/insserv.conf.d/dnsmasq /etc/resolvconf /etc/resolvconf/update.d /etc/resolvconf/update.d/dnsmasq /etc/default /etc/default/dnsmasq /etc/init.d /etc/init.d/dnsmasq /etc/dnsmasq.d /etc/dnsmasq.d/README /etc/dnsmasq.conf /usr/share/doc/dnsmasq

設定

/etc/dnsmasq.conf を変更します
とりあえず最低限の設定だけ記載します

  • sudo vim /etc/dnsmasq.conf
domain-needed
bogus-priv
local=/vagrant.local/
expand-hosts
domain=vagrant.local

内部で使用するドメインは「vagrant.local」にしたいと思います

A レコードを登録する

dnsmasq は /etc/hosts に IP アドレスとホスト名の組み合わせを記載することで A レコードの登録になります
また IPv6 用の AAAA レコードも /etc/hosts に記載します

  • sudo vim /etc/hosts
127.0.1.1 vm03
172.28.128.2 vm02.vagrant.local vm02
172.28.128.3 vm03.vagrant.local vm03
172.28.128.4 vm04.vagrant.local vm04
172.28.128.5 vm05.vagrant.local vm05

ちなみに dnsmasq をインストールしたのは vm03 になります
今回は Vagrant 上で動作させており .1 は mac になるのでそれも登録しました
あとは必要に応じて適当に登録してください

DNS をクライアントで確認

今回は dig コマンド実行時に構築した DNS サーバを指定して名前から IP アドレスが引けるか確認します
dig の場合は @ のあとに DNS サーバを指定すれば OK です

  • dig mac @172.28.128.3
; <<>> DiG 9.10.6 <<>> mac @172.28.128.3 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2261 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1   ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1280 ;; QUESTION SECTION: ;mac. IN A   ;; ANSWER SECTION: mac. 0 IN A 172.28.128.1   ;; Query time: 0 msec ;; SERVER: 172.28.128.3#53(172.28.128.3) ;; WHEN: Tue Jul 30 11:48:38 JST 2019 ;; MSG SIZE rcvd: 48

ちなみに今回はローカルドメイン以外の検索はしないので例えば www.google.com のようなレコードは引けません

  • dig www.google.com @172.28.128.3
; <<>> DiG 9.10.6 <<>> www.google.com @172.28.128.3 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: FORMERR, id: 7891 ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0   ;; WARNING: EDNS query returned status FORMERR - retry with '+noedns'   ;; QUESTION SECTION: ;www.google.com. IN A   ;; Query time: 2 msec ;; SERVER: 172.28.128.3#53(172.28.128.3) ;; WHEN: Tue Jul 30 11:50:59 JST 2019 ;; MSG SIZE rcvd: 32

いちいちアットマークで指定するのも面倒なので普通は /etc/resolv.conf やネットワークの設定ファイルに記載します

PTR (逆引き) もできる

特に何も PTR 用のレコードは設定しませんでしたが引けました

  • dig -x 172.28.128.1 @172.28.128.3
; <<>> DiG 9.10.6 <<>> -x 172.28.128.1 @172.28.128.3 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16109 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1   ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1280 ;; QUESTION SECTION: ;1.128.28.172.in-addr.arpa. IN PTR   ;; ANSWER SECTION: 1.128.28.172.in-addr.arpa. 0 IN PTR mac.vagrant.local.   ;; Query time: 0 msec ;; SERVER: 172.28.128.3#53(172.28.128.3) ;; WHEN: Tue Jul 30 11:54:49 JST 2019 ;; MSG SIZE rcvd: 85

おまけ: CNAME を登録してみる

CNAME や MX レコードも登録できるようです
CNAME の場合は以下のような設定を追記してあげれば OK です

  • sudo vim /etc/dnsmasq.conf
domain-needed
bogus-priv
local=/vagrant.local/
expand-hosts
domain=vagrant.local
cname=alt-vm04,vm04

設定ファイルや hosts ファイルを変更した場合はプロセスを再起動してあげる必要があります

  • sudo systemctl restart dnsmasq

確認すると以下のような感じで返ってきました

  • dig alt-vm04 @172.28.128.3
; <<>> DiG 9.10.6 <<>> alt-vm04 @172.28.128.3 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21908 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1   ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1280 ;; QUESTION SECTION: ;alt-vm04. IN A   ;; ANSWER SECTION: alt-vm04. 0 IN CNAME vm04. vm04. 0 IN A 172.28.128.4   ;; Query time: 0 msec ;; SERVER: 172.28.128.3#53(172.28.128.3) ;; WHEN: Tue Jul 30 11:57:46 JST 2019 ;; MSG SIZE rcvd: 71

最後に

Ubuntu で dnsmasq に入門してみました
導入自体はかなり簡単に行えます
あとはこの DNS サーバを外部向けに公開する作業が必要になるかと思います
ルータの DNS 設定に登録したり DHCP サーバの配布設定に入れるのが良いかと思います
また今回は主に内部向けの DNS サーバを構築しましたが外部 DNS として公開する場合はドメインを取得したサービスに DNS を登録したりするのが必要になります

参考サイト

2019年7月29日月曜日

heroku config の環境変数情報を別のコンテナに渡す方法

概要

heroku 上で動作しているコンテナには普通に渡ります
が、そうではなく heroku config コマンドで表示される環境変数の情報を別のコンテナに渡す方法を紹介します

環境

  • macOS 10.14.5
  • docker 18.09.2

やり方

  • eval $(heroku config -s); docker run --rm -e REDIS_URL=$REDIS_URL ruby echo $REDIS_URL

heroku config -s では以下の情報を返却されることにします

REDIS_URL='redis://h:xxxxxxxxx@redis:6379'

その情報を eval して一旦シェルの環境変数に設定しそれを -e オプションで渡す感じです

docker-compose の場合も同様で一旦シェルに渡してそこから YAML ファイルないでその環境変数を読み込みます
コンテナに渡す場合には environment を使います

version: '3.7'
services:
  batch:
    build:
      context: .
      dockerfile: Dockerfile_batch
    environment:
      - REDIS_URL=$REDIS_URL

heroku で動かさないコンテナに渡した場合などに使えます

参考サイト

2019年7月27日土曜日

ruby の標準 logger で JSON 形式のログを出力する方法

概要

タイトルの通りです
いつもやり方を忘れるのでメモしておきます

環境

  • macOS 10.14.5
  • Ruby 2.6.2p47

サンプルコード

require 'logger'

logger = Logger.new(STDOUT)
logger.progname = "app.rb"
logger.formatter = proc do |severity, datetime, progname, msg|
  %Q|{"severity": "#{severity}", "datetime": "#{datetime.to_s}", "progname": "#{progname}", "message": "#{msg}"}\n|
end

logger.debug("hello debug")
logger.info("hello info")
logger.warn("hello warn")
logger.error("hello error")

結果

{"severity": "DEBUG", "datetime": "2019-07-25 08:37:22 +0900", "progname": "app.rb", "message": "hello debug"} {"severity": "INFO", "datetime": "2019-07-25 08:37:22 +0900", "progname": "app.rb", "message": "hello info"} {"severity": "WARN", "datetime": "2019-07-25 08:37:22 +0900", "progname": "app.rb", "message": "hello warn"} {"severity": "ERROR", "datetime": "2019-07-25 08:37:22 +0900", "progname": "app.rb", "message": "hello error"}

参考サイト

2019年7月26日金曜日

docker で nfs サーバを構築してみた

概要

nfs サーバを docker の nfs ボリュームとして使う方法はいろいろと紹介記事がありますが docker 上で nfs サーバ自体を動作させる記事が中々なかったので紹介します

環境

  • Ubuntu 16.04 (Vagrant)
  • docker 18.09.8

nfs サーバの構築

Dockerhub でいろいろと公開されているイメージがありますが今回は nfs-server-alpine を使ってみました
まずは nfs で共有するディレクトリを作成します
今回は vagrant ユーザで作成するのでサーバ側とクライアント側のマウントポイントは両方とも vagrant ユーザを所有者にする必要があります

  • sudo /nfsshare
  • sudo chown vagrant:vagrant /nfsshare

あとはそのディレクトリをマウントする nfs コンテナを起動するだけです

  • docker run -d -p 2049:2049 --name nfs --privileged -v /nfsshare:/nfsshare -e SHARED_DIRECTORY=/nfsshare itsthenetwork/nfs-server-alpine:latest

これで OK です
2049 を publish することで外部からも接続できるようになります

マウントできるか確認

確認も Ubuntu から行います
nfs-client が必要なのでインストールします

  • sudo apt -y install nfs-client

あとはマウントポイントの作成をしてマウントするだけです

  • sudo mkdir /mnt
  • sudo chown vagrant:vagrant /mnt
  • sudo mount.nfs4 -v 172.28.128.4:/ /mnt/

nfs サーバ側のマウントポイントは / になるのでご注意ください

mount.nfs4: timeout set for Wed Jul 24 10:17:36 2019 mount.nfs4: trying text-based options 'addr=172.28.128.4,clientaddr=172.28.128.4'

マウントが完了したらファイルが配置できるか確認します

  • touch /mnt/hoge

アンマウントする場合は umount コマンドを使います

  • sudo umount /mnt

Tips: docker for mac の場合は File Sharing を有効にする

  • sudo mkdir /nfsshare/
  • sudo chown -R hawksnowlog:staff /nfsshare/

Preferences -> File sharing からマウントするパスを追加する必要があります
今回は /nfsshare というパスを追加しました

Tips: macOS Mojave でマウントする場合

Mojave から nfs をマウントする場合は少しコマンドが特殊です

  • sudo mkdir /Volumes/mnt
  • sudo mount_nfs -v -o vers=4 172.28.128.4:/ /Volumes/mnt/

デフォルトだと v3 になっているようなので -o でバージョンを指定しましょう
マウントコマンドは mount_nfs になります
また上記でマウントすると所有者が Ubuntu 上の vagrant:vagrant になっているので macOS 側に存在しているユーザ ID とグループ ID に変更してあげましょう

最後に

nfs サーバを docker コンテナとして起動してみました
今回マウントした /nfsshare の容量を増やせば nfs サーバの容量も増やせます
他のコンテナからマウントする場合は root 権限でも問題ないかもしれませんが基本的にはどのユーザがマウントするのかは意識したほうが良いと思います

参考サイト

*https://hub.docker.com/r/itsthenetwork/nfs-server-alpine

2019年7月25日木曜日

stack deploy 時に ulimits が使えない場合の対処方法

概要

docker の stack deploy で docker-compose を使う場合にいくつか使えないパラメータがあります
その中でコンテナの ulimits を制限するパラメータがあるのですがこれが stack deploy では使えません
例えば ElasticSearch のコンテナを使う場合に ulimit の memory lock を設定する必要がありこのような場合に docker-compose がそのまま使えません
そんな場合には dockerd に対して直接設定することができます

環境

  • boot2docker
  • docker 18.09.7

dockerd のパラメータ設定

今回は boot2docker を想定しています
dockerd のインストール方法によって設定方法が異なるので注意してください
apt や yum でインストールした場合は daemon.json を編集します

  • vim /var/lib/boot2docker/profile
EXTRA_ARGS='
--label provider=vmwarevsphere
--default-ulimit memlock=-1

'
CACERT=/var/lib/boot2docker/ca.pem
DOCKER_HOST='-H tcp://0.0.0.0:2376'
DOCKER_STORAGE=overlay2
DOCKER_TLS=auto
SERVERKEY=/var/lib/boot2docker/server-key.pem
SERVERCERT=/var/lib/boot2docker/server.pem

--default-ulimit memlock=-1 を追加します
これで memory lock が制御できます

反映

  • docker-machine restart default

参考サイト

2019年7月24日水曜日

Gitlab CI で docker build したイメージを Gitlab Container Registry に push してみた

概要

前回は Gitlab + Container Registry を試してみました
今回は CI と組み合わせて Container Registry を使ってみたいと思います

環境

  • macOS 10.14.5
  • docker 18.09.2
  • Gitlab-ce 12.0.3
  • gitlab-runner 12.0.1 (Ubuntu 16.04)

gitlab-runner のインストール

今回は Ubuntu を Runner にします
Shared Runner でも可能ですが Specified Runner のほうが確実です

  • curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
  • apt -y install gitlab-runner

gitlab-runner の登録

  • sudo gitlab-runner register -n --url http://gitlab.example.com/ --registration-token e47zbJPKPwroKWF-nu1V --executor shell

token は Gitlab 上で確認できます
Settings -> CI/CD -> Runners -> Expand

「Runner registered successfully」と表示されれば OK です
Gitlab 上のプロジェクトで確認しても Runner が登録されているのがわかると思います

gitlab-runner ユーザに権限を付与

  • sudo usermod -aG docker gitlab-runner

Insecure Registry の登録

  • sudo vim /etc/docker/daemon.json
{
  "insecure-registries" : ["gitlab.example.com:4567"]
}
  • sudo service docker restart

/etc/hosts の編集

  • sudo vim /etc/hosts
172.28.128.1 gitlab.example.com

IP は適宜変更してください
この IP は Vagrant が mac に DHCP で付与している IP になります

Tips: mac 版 gitlab-runner だとうまく動作しなかった

Homebrew で簡単に導入できたので始めは macOS を gitlab-runner にしようとしていたのですがどうもうまく登録できなかったのでやめました
おそらく gitlab-runner として登録する mac の IP がうまく取得/アクセスできないのが原因かなと思います

macOS の場合 moby がいて Gitlab コンテナから見ると送信元の IP が必ず 172.17.0.1 になります
この辺りが怪しそうですが Ubuntu でも同じ IP になったのでよくわかりませんでした

.gitlab-ci.yml 作成

では CI のルールを作成してみます
今回は shell executor なので素直に docker コマンドを並べれば OK です
今回の目的は build してできたイメージを Gitlab Container Registry に push するのが目的なのでそれができるところまでやります

  • vim .gitlab-ci.yml
before_script:
  - env
  - echo $CI_BUILD_TOKEN
  - docker login -u $CI_REGISTRY_USER -p $CI_BUILD_TOKEN gitlab.example.com:4567
  - docker info

build_image:
   stage: build
   script:
     - docker build -t gitlab.example.com:4567/root/test:latest .
     - docker push gitlab.example.com:4567/root/test:latest

env はデバッグ用なので不要であれば削除してください
ポイントは before_scriptdocker login している部分です
ログインするユーザはこれまでのように root ではなく $CI_REGISTRY_USER/$CI_BUILD_TOKEN を使います
CI 用に Container Registry にログインできる専用のユーザとパスワードが環境変数で渡ってくるのでそれを使いましょう
単純にそれらを使ったほうがセキュアで root のパスワードを平文で .gitlab-ci.yml に書くのはよろしくないためです

今回は docker build もするので適当に Dockerfile を作成しましょう

  • vim Dockerfile
FROM alpine

CMD ["date"]

あとはプロジェクトに push すれば OK です
push 後は自動で CI の内容が Runner で実行されます

  • git add .
  • git commit -m "add .gitlab-ci.yml"
  • git push -u origin master

動作確認

まずは CI が成功したか確認しましょう
CI/CI -> Pipelines を確認すると CI の結果が一覧で見れます

また Pipeline の番号を選択すると詳細が確認できます

最後に Registry を確認するとちゃんとイメージが Gitlab Container Registry に push されているのが確認できると思います

最後に

Gitlab CI と Container Registry を組み合わせて自動で docker イメージをリポジトリに push するところまでやってみました
今回は shell executor の Runner を用意しましたが docker in docker を使った Runner を立ち上げることもできるのでその辺りは環境に応じて変えてもらえればと思います
Runner を準備するのが面倒な場合は管理者であれば Shared Runner を作ってしまえば OK です

参考サイト

2019年7月23日火曜日

Gitlab Container Registry を試してみた

概要

前回は localhost に Gitlab を立ち上げるところまでやってみました
今回は Container Registry を試してみます

環境

  • macOS 10.14.5
  • docker 18.09.2
  • Gitlab-ce 12.0.3

Gitlab Container Registry を有効にする

Gitlab 12.0.3 にはデフォルトで Registry の機能があります
ただデフォルトでは有効になっていないので有効にします

  • docker exec -it gitlab editor /etc/gitlab/gitlab.rb

これで nano が開くので以下の行をコメントアウト/追記します
もしくはマウントしているのでローカルのファイルを編集しても OK です

  • vim gitlab/config/gitlab.rb
registry_external_url 'https://gitlab.example.com:4567'
registry_nginx['ssl_certificate'] = "/var/opt/gitlab/registry/gitlab-registry.crt"
registry_nginx['ssl_certificate_key'] = "/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key"

他の紹介記事を見ると gitlab/data/gitlab-rails/etc/gitlab.yml を編集している記事を見かけますが docker 上で動作させている場合 gitlab.yml は gitlab.rb を元に自動生成される設定ファイルなので編集しても上書きされてしまいます
編集できたらコンテナを再起動しましょう

  • docker restart gitlab

もし 4567 ポートを LISTEN していない場合はコンテナを再作成しましょう

  • docker stop gitlab
  • docker run -d -h gitlab.example.com -p 443:443 -p 80:80 -p 22:22 -p 4567:4567 --name gitlab -v $(pwd)/gitlab/config:/etc/gitlab -v $(pwd)/gitlab/logs:/var/log/gitlab -v $(pwd)/gitlab/data:/var/opt/gitlab gitlab/gitlab-ce:latest

これでプロジェクトで Container Registry が使えます

mac と docker for mac の設定

動作確認する mac 上の docker の設定をしましょう
今回はドメインや証明書が偽物なのでそれを使えるための設定を入れます

まずは名前で解決できるように hosts ファイルを編集しましょう

  • sudo vim /etc/hosts
127.0.0.1       gitlab.example.com

そして docker for mac の Insecure Registry に追加し「Apply & Restart」しましょう

docker info で確認して Insecure Registries に含まれていれば OK です

プロジェクトに Registry を確認する

これでプロジェクトに「Registry」が追加になっています
右メニューにあるので確認しましょう

また選択するとレジストリのエンドポイントなどが確認できます

docker login

docker コマンドで実際にログインできるか確認します

  • docker login gitlab.example.com:4567

ID/PW は Gitlab にログインする root/設定したパスワードでログインしましょう

docker push

あとはタグ付けして push するだけです
今回の場合 localhost に投げているだけなので push も早いです

  • docker tag redis gitlab.example.com:4567/root/test:v1
  • docker push gitlab.example.com:4567/root/test:v1

Gitlab の UI を見てもちゃんとイメージが push されているのが確認できました

ちなみに gitlab.example.com:4567/root/test2 みたいなタグを付与して push してみましたが拒否されました
なのでタグはすべて同一である必要があります
もし別アプリなどのイメージを push したい場合はバージョンの部分を使うしかないかなと思います

  • docker tag nginx gitlab.example.com:4567/root/test:nginx
  • docker push gitlab.example.com:4567/root/test:nginx

docker pull

ついでに pull も試してみました
一旦ローカルでタグ付けしたイメージを削除してから試しましょう

  • docker rmi gitlab.example.com:4567/root/test:v1
  • docker pull gitlab.example.com:4567/root/test:v1

はまったポイント

レジストリに独自のドメインを適当に入れていたのですがそれだと 503 エラー + error authorizing context: authorization token required エラーで docker login できません
今回の構成の場合 Gitlab とコンテナレジストリは同一ホスト、同一ドメイン配下にあるので同じドメイン上で動作するようにしないとダメでした

最後に

Gitlab の Container Registry を試してみました
インストールした方法や環境に応じて設定方法が異なるので注意が必要です
docker を使っている場合は gitlab.rb を編集すれば OK です
ただレジストリ用のドメインの設定に注意しましょう
次回は CI で Container Registry を使ってみようと思います

参考サイト

2019年7月22日月曜日

localhost で Gitlab を試してみた

概要

docker のイメージが公開されているのでそれを使えば簡単に localhost でも試せます
今回は macOS 上で立ち上げてリポジトリを作成して git push するところまで試してみました

環境

  • macOS 10.14.5
  • docker 18.09.2
  • Gitlab-ce 12.0.3

Gitlab を起動する

docker コマンド一発で立ち上がります

  • docker run -d -h gitlab.example.com -p 443:443 -p 80:80 -p 22:22 --name gitlab -v $(pwd)/gitlab/config:/etc/gitlab -v $(pwd)/gitlab/logs:/var/log/gitlab -v $(pwd)/gitlab/data:/var/opt/gitlab gitlab/gitlab-ce:latest

起動すると chef でプロビジョニングが走るので起動までに少し時間がかかります
今回はドメインがないので基本は 80 ポート + IP アドレスを使いますがドメインがあれば -h でホスト名を指定してドメイン + SSL でアクセスできます

  • docker logs --tail 1 gitlab

Prometheus のメトリックを取得しているログが流れ始めれば起動完了です

[2019-07-22 03:41:18] 127.0.0.1 - - [22/Jul/2019:03:41:18 UTC] "GET /metrics HTTP/1.1" 200 775 "-" "Prometheus/2.8.1"

localhost または IP アドレスにアクセスすると新規アカウントを作成する画面になっていると思います

アカウント作成

root ユーザのパスワードを設定する画面になるのでそのまま素直に設定しましょう
設定後 root ユーザでログインできることを確認しましょう

プロジェクト (リポジトリ) 作成

ログインが完了するといろいろと作成する画面になります
とりあえず「Create a project」でプロジェクトを作成しましょう
また Gitlab はプロジェクト名=リポジトリ名になります

今回は「test」という名前のプロジェクトにしました

ユーザ名の下にプロジェクトが作成されるので http://localhost/root/test という URL でアクセスすることになります

push してみる

適当にディレクトリとファイルを作成して作成したプロジェクトに push してみましょう

  • mkdir test
  • cd test
  • git init
  • git remote add origin http://localhost/root/test.git
  • git config user.name "Administrator"
  • git config user.email "admin@example.com"
  • echo '# test' > README.md
  • git add .
  • git commit -m "first commit"
  • git push -u origin mater

push すると root ユーザの ID/PW が聞かれるので先程設定したパスワードを入力しましょう
成功するとプロジェクトにちゃんとファイルが追加されているのが確認できます

最後に

localhost で Gitlab を立ち上げる方法を紹介しました
docker コマンド一つで立ち上げることができるので適当に検証したいときに気軽にできると思います
せっかくなので CI や Container Registry も試してみたいと思います

参考サイト

2019年7月20日土曜日

nginx で error.log にログ意図的に出力させる方法

概要

nginx の error.log をテストで意図的に出力させたい場合はあると思います
そんなときにてっとり早くエラーログを出力させる方法を紹介します
なお今回は nginx コンテナを使っています

環境

  • macOS 10.14.5
  • docker 18.09.2

default.conf の編集

CGI 用の URI を活用します
.php でアクセスが来たらそのままバックエンドのアプリに流しますが実際はないのでこれでエラーログが出ます

  • vim default.conf
location ~ \.php$ {
    proxy_pass   http://127.0.0.1;
}

途中他の設定もありますが省略しています
なお default.conf はコンテナ内から cp しても OK です

  • docker -d run nginx
  • docker cp 403876658f4e:/etc/nginx/conf.d/default.conf .

default.conf を使って nginx コンテナを起動

作成した default.conf を使ってコンテナを起動します

  • docker run -d -p 80:80 -v $(pwd)/default.conf:/etc/nginx/conf.d/default.conf nginx

動作確認

docker logs には標準出力と標準エラーのどちらも流れてきます
標準出力のみを表示する場合は

  • docker logs -f 403876658f4e 2> /dev/null

標準エラーのみを表示する場合は

  • docker logs -f 403876658f4e > /dev/null

という感じで表示を制限できます
今回はエラーログを表示させたいので下を使います
そして curl を使って以下のようにアクセスしてみましょう

  • curl localhost/index.php

すると以下のようにエラーログが出力されると思います

2019/07/17 01:37:16 [alert] 7#7: 1024 worker_connections are not enough 2019/07/17 01:37:16 [error] 7#7: *1021 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 127.0.0.1, server: localhost, request: "GET /index.php HTTP/1.0", upstream: "http://127.0.0.1:80/index.php", host: "127.0.0.1"

最後に

nginx の error.log のテストをしたい場合に意図的にエラーログを出力する方法を紹介しました
これ以外にも方法はあると思いますが思いつかない場合には使ってみてください

2019年7月19日金曜日

LTSV 形式のログが出力されるアプリを docker fluentd ドライバでパースする方法

概要

前回 JSON 版を紹介しましたがそれの LTSV 版です

環境

  • macOS 10.14.5
  • docker 18.09.2

アプリ

LTSV を 1 行ごとに出力すれば、なんでも OK です

  • vim app.rb
require 'logger'
require 'securerandom'

unless ENV['LOCAL'] == "true"
  $stdout = IO.new(IO.sysopen("/proc/1/fd/1", "w"), "w")
  $stdout.sync = true
  STDOUT = $stdout
end
logger = Logger.new(STDOUT)
logger.formatter = proc do |severity, datetime, progname, msg|
  %Q|timestamp:#{datetime.to_s}\tmessage:#{msg}\n|
end

loop do
  logger.info(SecureRandom.uuid)
  sleep(3)
end
  • ruby app.rb
timestamp:2019-07-17 21:02:07 +0900 message:7ccf7e56-3682-4a67-81dd-7da28ee579d9 timestamp:2019-07-17 21:02:10 +0900 message:c0643812-24e5-4685-8242-35e05a075bef

こんな感じで出力されれば OK です

Dockerfile

あとはこれを docker 上で動作させるようにします

  • vim Dockerfile
FROM ruby

ADD . /home
WORKDIR /home

CMD ["ruby", "app.rb"]
  • docker build -t myapp .

fluentd コンテナ

特にプラグインはないので設定ファイルだけ作成します

  • vim fluent.conf
<source>
  @type forward
</source>

<filter docker.app>
  @type parser
  format ltsv
  keep_time_key true
  key_name log
  reserve_data true
</filter>

<match docker.app>
  @type stdout
</match>

ポイントは parser プラグインを使うところです
これで事前に LTSV のログをパースします
ちなみにログは log というキーに詰め込まれているので key_namelog を指定します
reserve_data true にするとパースされる前のログもその残ります
不要であれば false を設定しましょう
また keep_time_key true を設定すれば LTSV 内に timestamp 情報が含まれている場合パース後もその情報を emit してくれます

あとはコンテナを起動すれば OK です

  • docker run --rm -p 24224:24224 -p 24224:24224/udp -v $(pwd)/fluent.conf:/fluentd/etc/fluent.conf -e FLUENTD_CONF=fluent.conf --name=fluentd fluent/fluentd

動作確認

まずアプリコンテナを起動しましょう

  • docker run -d --log-driver=fluentd --log-opt fluentd-address=localhost:24224 --log-opt tag="docker.app" myapp

次に fluentd コンテナのログを確認します

  • docker logs -f fluentd

すると以下のようにログが表示されるのが確認できます

2019-07-17 12:06:27.093063700 +0000 docker.app: {"container_id":"36577545866d341713bdcf120a53ec169fcfab74d57bd4203ad06aa7bd8c32e5","container_name":"/heuristic_hypatia","source":"stdout","log":"timestamp:2019-07-17 12:06:27 +0000\tmessage:fe8b95d0-3570-40ff-ae88-e8343f63dc5c","timestamp":"2019-07-17 12:06:27 +0000","message":"fe8b95d0-3570-40ff-ae88-e8343f63dc5c"} 2019-07-17 12:06:30.098940200 +0000 docker.app: {"container_id":"36577545866d341713bdcf120a53ec169fcfab74d57bd4203ad06aa7bd8c32e5","container_name":"/heuristic_hypatia","source":"stdout","log":"timestamp:2019-07-17 12:06:30 +0000\tmessage:2570d74d-ee3a-44c8-8663-30014560ed06","timestamp":"2019-07-17 12:06:30 +0000","message":"2570d74d-ee3a-44c8-8663-30014560ed06"}

timestamp と message がパースされて key-value として登録されていることがわかります
reserve_data false にすれば log キーのパースされていない元データがなくなります
そちらのほうがすっきりする場合はあります

最後に

LTSV のログを docker fluentd ドライバでパースする方法を紹介しました
filter で処理したあとはちゃんと match で受け取るのを忘れないようにしましょう

参考サイト

2019年7月18日木曜日

JSON 形式のログが出力されるアプリを docker fluentd ドライバでパースする方法

概要

アプリによっては JSON のログを 1 行ごとに出力する場合があります
そんな JSON のログを docker の fluentd driver で処理する方法を紹介します

環境

  • macOS 10.14.5
  • docker 18.09.2

アプリ

JSON を 1 行ごとに出力すれば、なんでも OK です

  • vim app.rb
require 'logger'
require 'securerandom'

unless ENV['LOCAL'] == "true"
  $stdout = IO.new(IO.sysopen("/proc/1/fd/1", "w"), "w")
  $stdout.sync = true
  STDOUT = $stdout
end
logger = Logger.new(STDOUT)
logger.formatter = proc do |severity, datetime, progname, msg|
  %Q|{"timestamp": "#{datetime.to_s}", "message": "#{msg}"}\n|
end

loop do
  logger.info(SecureRandom.uuid)
  sleep(3)
end
  • ruby app.rb
{"timestamp": "2019-07-17 12:32:42 +0900", "message": "7947d912-4e97-4227-b49c-83e3ecb30e98"} {"timestamp": "2019-07-17 12:32:45 +0900", "message": "36d3af1e-803f-4e76-a9f2-40cb540d8a2d"}

こんな感じで出力されれば OK です

Dockerfile

あとはこれを docker 上で動作させるようにします

  • vim Dockerfile
FROM ruby

ADD . /home
WORKDIR /home

CMD ["ruby", "app.rb"]
  • docker build -t myapp .

fluentd コンテナ

特にプラグインはないので設定ファイルだけ作成します

  • vim fluent.conf
<source>
  @type forward
</source>

<filter docker.app>
  @type parser
  format json
  key_name log
  reserve_data true
</filter>

<match docker.app>
  @type stdout
</match>

ポイントは parser プラグインを使うところです
これで事前に JSON のログをパースします
ちなみにログは log というキーに詰め込まれているので key_namelog を指定します
reserve_data true にするとパースされる前のログもその残ります
不要であれば false を設定しましょう

あとはコンテナを起動すれば OK です

  • docker run --rm -p 24224:24224 -p 24224:24224/udp -v $(pwd)/fluent.conf:/fluentd/etc/fluent.conf -e FLUENTD_CONF=fluent.conf --name=fluentd fluent/fluentd

動作確認

まずアプリコンテナを起動しましょう

  • docker run -d --log-driver=fluentd --log-opt fluentd-address=localhost:24224 --log-opt tag="docker.app" myapp

次に fluentd コンテナのログを確認します

  • docker logs -f fluentd

すると以下のようにログが表示されるのが確認できます

2019-07-17 03:59:03.427725600 +0000 docker.app: {"container_id":"a7e199fa8f7b03fdcd84032f9633721668fd7a8f4c07f6931576978b7ff82bc7","container_name":"/reverent_tereshkova","source":"stdout","log":"{\"timestamp\": \"2019-07-17 03:55:46 +0000\", \"message\": \"d4d05f96-8523-4b3f-a965-89967600c58c\"}","timestamp":"2019-07-17 03:55:46 +0000","message":"d4d05f96-8523-4b3f-a965-89967600c58c"}

reserve_data false にすれば log キーのパースされていない元データがなくなります
そちらのほうがすっきりする場合はあります

最後に

JSON のログを docker fluentd ドライバでパースする方法を紹介しました
filter で処理したあとはちゃんと match で受け取るのを忘れないようにしましょう

参考サイト

2019年7月17日水曜日

docker の fluentd ドライバで stdout と stderr を別々に扱う方法

概要

docker の fluentd ドライバで stdout と stderr を別々のタグで扱う方法を紹介します
実際に stdout と stderr に出力するアプリを作成し試しています

環境

  • macOS 10.14.5
  • docker 18.09.2
  • fluentd 1.3.2
    • rewrite-tag-filter 2.2.0

stdout と stderr にそれぞれ出力するアプリの作成

Sinatra で作成します
ここはそれぞれに出力できるのであれば何でも OK です

  • bundle init
  • vim Gemfile
gem "sinatra"
  • bundle install --path vendor
  • vim app.rb
require 'sinatra/base'
require 'logger'

class MyApp < Sinatra::Base
  configure do
    unless ENV['LOCAL'] == "true"
      $stdout = IO.new(IO.sysopen("/proc/1/fd/1", "w"), "w")
      $stdout.sync = true
      STDOUT = $stdout
    end
    set :ologger, Logger.new(STDOUT)
    set :elogger, Logger.new(STDERR)
  end

  get '/stdout' do
    settings.ologger.info("stdout")
    'stdout'
  end

  get '/stderr' do
    settings.elogger.info("stderr")
    'stderr'
  end
end
  • vim config.ru
require './app.rb'
run MyApp

こんな感じです
Ruby アプリの場合 stdout は少し工夫が必要なのでそれを入れています
動作確認としては stdout と stderr だけに表示するように実行してみましょう

  • LOCAL=true bundle exec rackup config.ru 2> /dev/null
  • LOCAL=true bundle exec rackup config.ru > /dev/null

上が stdout のみで下が stderr のみ表示する実行方法です

Dockerfile

次に作成したアプリを docker 上で動作するようにします

  • vim Dockerfile
FROM ruby

ADD . /home
WORKDIR /home
RUN gem install bundler
RUN bundle install --path vendor
EXPOSE 9292

CMD ["bundle", "exec", "rackup", "config.ru", "-o", "0.0.0.0"]
  • docker build -t myapp .

これでイメージを作成します

fluentd コンテナの作成

まずは fluentd コンテナから立ち上げます
今回の目的は作成した Sinatra アプリの stdout と stderr を fluentd で別々に扱うことです
なので fluent.conf をそのように記載する必要があります
ポイントは rewrite_tag_filter を使って stdout と stdout に対して別々のタグを付与する必要がある点です

  • mkdir fluentd
  • cd fluentd
  • vim fluent.conf
<source>
  @type forward
</source>

<match docker.myapp>
  @type rewrite_tag_filter 
  <rule>
    key source
    pattern /^stdout$/
    tag out.${tag}
  </rule>
  <rule>
    key source
    pattern /^stderr$/
    tag err.${tag}
  </rule>
</match>

<match out.docker.myapp>
  @type stdout
</match>

<filter err.docker.myapp>
  @type record_transformer
  <record>
    hostname "#{Socket.gethostname}"
  </record>
</filter>

<match err.docker.myapp>
  @type stdout
</match>

record-transformer はテスト用に使っています
stderr にだけフィールドを追加して出力しています
デフォルトの fluetd イメージには rewrite-tag-filter プラグインが含まれてないのでプラグインをインストールしたイメージを作成します

  • vim Dockerfile
FROM fluent/fluentd

RUN apk add --update --virtual .build-deps \
        sudo build-base ruby-dev \
 && sudo gem install \
        fluent-plugin-rewrite-tag-filter \
 && sudo gem sources --clear-all \
 && apk del .build-deps \
 && rm -rf /var/cache/apk/* \
           /home/fluent/.gem/ruby/2.4.0/cache/*.gem
  • docker build -t myfluentd .

これで fluentd コンテナを起動しましょう

  • docker run -d -p 24224:24224 -p 24224:24224/udp -v $(pwd)/fluent.conf:/fluentd/etc/fluent.conf -e FLUENTD_CONF=fluent.conf --name=fluentd myfluentd
  • docker logs -f fluentd

アプリコンテナの起動

ではアプリコンテナを起動しましょう
コンテナを起動する際にロギングドライバに fluentd を指定しましょう
またタグは docker.myapp にします

  • docker run -d --log-driver=fluentd --log-opt fluentd-address=192.168.128.100:24224 --log-opt tag="docker.myapp" -p 9292:9292 myapp

動作確認

fluentd コンテナのログを見ておきましょう
そしてアプリの stdout, stderr にアクセスしましょう

  • curl localhost:9292/stdout
  • curl localhost:9292/stderr

すると以下のように source -> stderr なログにだけ hostname というフィールドが追加されていることがわかると思います

2019-07-17 00:10:43.000000000 +0000 out.docker.myapp: {"source":"stdout","log":"I, [2019-07-17T00:10:43.723529 #1] INFO – : stdout","container_id":"9000f9bafa59b2de3d857ebba520caa76143c1d54bb006d63d92e77318bfa448","container_name":"/gifted_thompson"} 2019-07-17 00:10:43.000000000 +0000 err.docker.myapp: {"source":"stderr","log":"172.17.0.1 - - [17/Jul/2019:00:10:43 +0000] \"GET /stdout HTTP/1.1\" 200 6 0.0262","container_id":"9000f9bafa59b2de3d857ebba520caa76143c1d54bb006d63d92e77318bfa448","container_name":"/gifted_thompson","hostname":"7d6a67bf27a1"} 2019-07-17 00:11:01.000000000 +0000 err.docker.myapp: {"container_id":"9000f9bafa59b2de3d857ebba520caa76143c1d54bb006d63d92e77318bfa448","container_name":"/gifted_thompson","source":"stderr","log":"I, [2019-07-17T00:11:01.546225 #1] INFO – : stderr","hostname":"7d6a67bf27a1"} 2019-07-17 00:11:01.000000000 +0000 err.docker.myapp: {"source":"stderr","log":"172.17.0.1 - - [17/Jul/2019:00:11:01 +0000] \"GET /stderr HTTP/1.1\" 200 6 0.0009","container_id":"9000f9bafa59b2de3d857ebba520caa76143c1d54bb006d63d92e77318bfa448","container_name":"/gifted_thompson","hostname":"7d6a67bf27a1"}

最後に

ポイントは rewrite-tag-filter を使って stdout と stderr を一旦受け取った上で再度タグ付けをする点でした
今回は 1 つのコンテナのログのみ対応しましたが他にも stdout と stderr を別々に扱いたい場合は docker.myapp のように別のタグの match を定義すれば OK です

参考サイト

2019年7月16日火曜日

Ubuntu 上で簡単にネットワークの帯域を制限する方法

概要

wondershaper を使います
メンテされていないのであまりオススメしませんがてっとり早くかつ簡単に動きます

環境

  • Ubuntu 16.04
  • wondershaper 1.4

wondershaper インストール

  • git clone https://github.com/magnific0/wondershaper.git
  • cd wondershaper
  • ./wondershaper -h

Ubuntu や CentOS の場合はこれが最新が入るのでおすすめです

帯域を制限する

  • ./wondershaper -a ens160 -d 128 -u 128

ifb0 というインタフェースが作成されます
-d で下り、-u で上りの帯域を制限できます

speedtest-cli で確認してみる

  • wget -O speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py
  • chmod +x speedtest-cli
Retrieving speedtest.net configuration… Testing from Softbank BB (x.x.x.x)… Retrieving speedtest.net server list… Selecting best server based on ping… Hosted by GLBB Japan (Tokyo) [16.95 km]: 17.806 ms Testing download speed…………………………………………………………………….. Download: 0.07 Mbit/s Testing upload speed…………………………………………………………………………………… Upload: 0.39 Mbit/s

多少誤差はありますが制限されていることが確認できます

帯域制限を解除する

  • ./wondershaper -c -a ens160

ifb0 が削除されない場合は手動で削除しましょう

  • ip l delete ifb0

2019年7月15日月曜日

golang でカスタム error のフィールドにアクセスする方法

概要

基本的に error は Error() という関数を実装しその関数だけを呼び出します
しかし自分で作成したカスタム error のフィールドに直接アクセスしたい場合があると思います

環境

  • macOS 10.14.5
  • golang 1.11.5

サンプルコード

  • vim main.go
package main

import (
    "fmt"
    "reflect"
)

type MyError struct {
    msg  string
    code int
}

func (e *MyError) Error() string {
    return fmt.Sprintf("code: %d, msg: %s", e.code, e.msg)
}

func genError() error {
    return &MyError{"this is my error", 400}
}

func main() {
    err := genError()
    if err != nil {
        fmt.Println(err)
        fmt.Println(reflect.TypeOf(err))
        fmt.Println(err.(*MyError).msg)
        fmt.Println(err.(*MyError).code)
    }
}

説明

ポイントは error から MyError を取得するところです
err.(*MyError) という呼び出しをすることで直接カスタムエラーにアクセスすることができます

これを応用すればカスタムエラーに関数を実装してそれを呼び出すことも可能です

package main

import (
    "fmt"
)

type MyError struct {
    msg  string
    code int
}

func (e *MyError) Error() string {
    return fmt.Sprintf("code: %d, msg: %s", e.code, e.msg)
}

func (e *MyError) DoubleCode() int {
    return e.code * 2
}

func genError() error {
    return &MyError{"this is my error", 400}
}

func main() {
    err := genError()
    if err != nil {
        fmt.Println(err.(*MyError).DoubleCode())
    }
}

Type Assertion

この参照の仕方は Type Assertion と言います
簡単に言えばキャストですがインタフェース用のキャストになります
golang の error はインタフェースなのでそれを MyError にキャストするのに Type Assertion を使っている感じです

参考サイト

2019年7月14日日曜日

haproxy サーバに複数の IP を振って IP ベースのバランシングをしてみた

概要

haproxy の dst 機能を使えば IP ベースのバランシングが可能です
例えば haproxy サーバは一台しか用意できないが IP アドレスは複数用意できる場合に使えます

環境

  • macOS 10.14.5
  • Vagrant 2.1.1
  • Ubuntu 16.04 LTS
  • docker 18.09.7

準備

基本はこの手順 で構築します

今回は haproxy サーバが複数の IP を持っている想定なので Vagrantfile は以下のように書き換えます

  • vim Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.define "vm01" do |v|
    v.vm.box = "ubuntu/xenial64"
    v.vm.network "private_network", type: "dhcp"
    v.vm.provider "virtualbox" do |vb|
      vb.memory = "1024"
    end
  end
  config.vm.define "vm02" do |v|
    v.vm.box = "ubuntu/xenial64"
    v.vm.network "private_network", type: "dhcp"
    v.vm.provider "virtualbox" do |vb|
      vb.memory = "1024"
    end
  end
  config.vm.define "vm03" do |v|
    v.vm.box = "ubuntu/xenial64"
    v.vm.network "private_network", type: "dhcp"
    v.vm.network "private_network", type: "dhcp"
    v.vm.provider "virtualbox" do |vb|
      vb.memory = "1024"
    end
  end
end

ポイントは vm03 に network が複数ある点です

  • vagrant up

haproxy.cfg の設定

dst を使った定義をします
今回 haproxy サーバ (vm03) に振られた IP は「172.28.128.5」「172.28.128.6」の 2 つとします

  • vim haproxy.cfg
frontend web
    default_backend test
    bind :80
    acl is_dst_test dst -i 172.28.128.5
    acl is_dst_test2 dst -i 172.28.128.6
    use_backend test if is_dst_test
    use_backend test2 if is_dst_test2

backend test
    option redispatch
    retries 3
    server vm01_test 172.28.128.3:80 weight 1
    server vm02_test 172.28.128.4:80 weight 1

backend test2
    option redispatch
    retries 3
    server vm01_test2 172.28.128.3:81 weight 1
    server vm02_test2 172.28.128.4:81 weight 1

ポイントは dst です
ここに haproxy サーバに割り当てられた IP を指定します
そして IP ごとに backend を定義すれば OK です
またコンテナを起動する際にも注意が必要です

  • docker run -d -p 80:80 -v $(pwd)/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg --name web --net=host haproxy

--net=host を必ず付与するようにしましょう
そうしないと destination の IP がコンテナ内部の IP になってしまいうまく dst でバランシングできなくなってしまいます

動作確認用の stack deploy

2 つの stack をデプロイします
それぞれの stack に dst で振り分けます

  • vim docker-compose.yml
version: '3.4'

services:
  nginx:
    image: valian/nginx-test-page
    ports:
      - "80:80"
    deploy:
      replicas: 2

これが「172.28.128.5」にアクセスしたときに振られる stack です

  • vim docker-compose2.yml
version: '3.4'

services:
  nginx:
    image: valian/nginx-test-page
    ports:
      - "81:80"
    deploy:
      replicas: 2

そしてこれが「172.28.128.6」にアクセスしたときに振られる stack です
ポートを 80 と 81 で LISTEN するようにしましょう

  • docker stack deploy -c docker-compose.yml test
  • docker stack deploy -c docker-compose2.yml test2

デプロイが完了して以下のようになっていれば OK です

  • docker service ls
ID NAME MODE REPLICAS IMAGE PORTS hbzcyfhc6zn0 test2_nginx replicated 2/2 valian/nginx-test-page:latest *:81->80/tcp bf6xj2lgy42r test_nginx replicated 2/2 valian/nginx-test-page:latest *:80->80/tcp

動作確認

  • curl 172.28.128.5

で test のサービスとしてデプロイしたコンテナからのみ応答が返ってくることを確認します

  • curl 172.28.128.6

では test2 からのコンテナからのみ応答が返ってくることを確認します

最後に

docker + haproxy の dst を使って IP ベースのバランシングを実現してみました
ポイントは docker network の host ドライバを使う点でした
ネットで調べると transparent mode が有効になってる haproxy で tombull/haproxy というのがありこれを使えばできるという紹介記事もあったのですが使わないでも実現できました

あと気になったのは haproxy コンテナのスケールアウトです
今回の場合、haproxy 用のコンテナホストを増やすと IP も追加になるはずです
そうなるとエンドポイントが複数に分かれてしまうのでまた上位にバランサを置かなければならなくなります
それだと永遠にスケールしないのでおそらく DNS を使うしかないんじゃないかなと思っています
もしくはバーチャル IP みたいなのを用意すればできるのだろうか、、

参考サイト

2019年7月13日土曜日

docker Swarm 環境にロードバランサを導入する

概要

docker Swarm には Ingress という機能がありこれを使うことで分散配置されたコンテナにどのコンテナホストからもアクセスすることができるようになります
エンドポイントも複数に分散してしまいます
今回は Ingress の更に上にロードバランサを設けて Ingress で publish したポートを一つのエンドポイントでアクセスする方法を紹介します

環境

  • macOS 10.14.5
  • Vagrant 2.1.1
  • Ubuntu 16.04 LTS
  • docker 18.09.7

準備

この手順 で docker Swarm 環境を構築しました
また今回は Ingress 用のバランサも作る必要があるので VM を 3 台にしています
Swarm クラスタが 2 台で残り 1 台はクラスタに入れないようにします

stack deploy

まずは stack deploy でコンテナを作成しましょう
クラスタ内のコンテナホストは 2 台なのでそれぞれにコンテナができるように replicas: 2 にしましょう

  • vim docker-compose.yml
version: '3.4'

services:
  nginx:
    image: valian/nginx-test-page
    ports:
      - "80:80"
    deploy:
      replicas: 2
  • docker stack deploy -c docker-compose.yml test
  • docker stack ls
NAME SERVICES ORCHESTRATOR test 1 Swarm
  • docker service ls
ID NAME MODE REPLICAS IMAGE PORTS ouh22aumey6x test_nginx replicated 2/2 valian/nginx-test-page:latest *:80->80/tcp
  • docker stack ps test
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS tql4b06k241l test_nginx.1 valian/nginx-test-page:latest vm02 Running Running 2 minutes ago uz4mk5orsl5r test_nginx.2 valian/nginx-test-page:latest vm01 Running Running 2 minutes ago
  • curl vm01
HOSTNAME=efc280b9eead

vm02 からもコンテナにアクセスできることを確認しましょう

ingress サーバからバランシングする (nginx)

やり方はいろいろあると思いますが今回は nginx を使ってみます
新たに追加した vm03 サーバをロードバランサにします

  • vim default.conf
server {
   listen 80;
   location / {
      proxy_pass http://test;
   }
}
upstream test {
   server 172.28.128.3;
   server 172.28.128.4;
}

vm01 と vm02 は IP で指定しています
バランシングする nginx コンテナから Swarm クラスタ内のコンテナホストにアクセスします
その際に名前を引けないので IP を指定しています

nginx コンテナを起動しましょう

  • docker run -d -p 80:80 -v $(pwd)/default.conf:/etc/nginx/conf.d/default.conf --name web nginx

vm03 にアクセスして Ingress と通して vm01 と vm02 に配置されたコンテナにアクセスできることを確認します

  • curl vm03
HOSTNAME=efc280b9eead

片方のコンテナをダウンさせてみる

今回 stack deploy で 2 台のコンテナを起動させています
インシデントのシミュレーションとして片方のコンテナをダウンさせてみます
vm01 or vm02 のコンテナホストにログインして直接コンテナを停止してみましょう

  • docker stop 0dfc9f942286

curl vm03 を続けていると挙動を確認できます
デフォルトの nginx の状態だとダウンしたコンテナにもリクエストを投げてしまうのでたまに 502 になるのが確認できます
stack deploy の場合 replicas: 2 で指定したコンテナ数以下になると自動でコンテナを再作成するのでそのうち 502 は発生しなくなります

502 を発生させないようにするには基本的には nginx を haproxy に置き換えるのが良いと思います

ingress サーバからバランシングする (haproxy)

ということで haproxy もやってみたいと思います
先程同様に vm03 上で動作させます

  • vim haproxy.cfg
frontend web_proxy
    default_backend test
    bind *:80

backend test
    option redispatch
    retries 3
    server vm01 172.28.128.3:80 weight 1
    server vm02 172.28.128.4:80 weight 1

redispatch を指定することでダウンしたコンテナにアクセスしないようにします

  • docker run -d -p 80:80 -v $(pwd)/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg --name web haproxy

起動して動作確認しましょう

  • while true:; do curl vm03; sleep 1; done

これで先程同様にコンテナを停止してみましょう
すると 502 は出ずにコンテナが再作成されるまで片方のコンテナにだけアクセスし続けるのが確認できると思います

おまけ: クラスタ内のコンテナホストの IP を取得する方法

  • for NODE in $(docker node ls --format '{{.Hostname}}'); do echo -e "${NODE} - $(docker node inspect --format '{{.Status.Addr}}' "${NODE}")"; done

おまけ: dockerd のジャーナルログの確認

  • sudo journalctl -u docker

tail したい場合は -f オプションも使います
ただ今回の動作確認のようなコンテナのダウンや再作成のログは表示されないようです
表示させたい場合はログレベルを debug にする必要があります

  • sudo vim /etc/docker/daemon.json
  • sudo kill -SIGHUP $(pidof dockerd)

最後に

docker Swarm の Ingress の機能で publish したポートを更にロードバランサで分散してみました
nginx と haproxy を使ってみましたが基本は haproxy を使ったほうが良いかなと思います

参考サイト

2019年7月12日金曜日

ruby (sinatra) で sitemap_generator を使ってみた

概要

sitemap_generator は ruby で sitemap.xml.gz が作成できるライブラリです
アクセスごとに生成するのではなく事前に生成してデプロイするタイプです
今回は普通に XML ファイルを生成する方法と Sinatra などの Web アプリ上に組み込む方法を検討してみました

環境

  • macOS 10.14.5
  • Ruby 2.6.2p47
    • sitemap_generator 6.0.2
    • sinatra 2.0.5

ライブラリインストール

  • bundle init
  • vim Gemfile
gem "sitemap_generator"
  • bundle install --path vendor

普通に使ってみた

  • vim app.rb
require 'sitemap_generator'

SitemapGenerator::Sitemap.default_host = 'http://sample.local'
SitemapGenerator::Sitemap.create do
  add '/about', :priority => 0.8
  add '/apps', :changefreq => 'hourly', :priority => 0.8
  add '/blog', :changefreq => 'daily', :priority => 0.6
end
SitemapGenerator::Sitemap.ping_search_engines

ping_search_engines は主要なクローラ更新の ping を送信することができます

  • bundle exec ruby app.rb

public/sitemap.xml.gz が作成されます
中身を確認してみましょう

  • gzip -dc public/sitemap.xml.gz | xmllint --format -
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:pagemap="http://www.google.com/schemas/sitemap-pagemap/1.0" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
  <url>
    <loc>http://sample.local</loc>
    <lastmod>2019-07-02T21:32:40+09:00</lastmod>
    <changefreq>always</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>http://sample.local/about</loc>
    <lastmod>2019-07-02T21:32:40+09:00</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>
  <url>
    <loc>http://sample.local/apps</loc>
    <lastmod>2019-07-02T21:32:40+09:00</lastmod>
    <changefreq>hourly</changefreq>
    <priority>0.8</priority>
  </url>
  <url>
    <loc>http://sample.local/blog</loc>
    <lastmod>2019-07-02T21:32:40+09:00</lastmod>
    <changefreq>daily</changefreq>
    <priority>0.6</priority>
  </url>
</urlset>

lastmod はデフォルトだと生成した時間になります
changefreq はルートパスは always になります
それ以外は weekly がデフォルトになるようです
priority は指定がない場合 0.5 がデフォルトになります

Sinatra で使ってみた

  • vim Gemfile
gem "sitemap_generator"
gem "sinatra"
  • bundle install --path vendor
  • vim app.rb
require 'sitemap_generator'
require 'sinatra/base'

module Util
  Paths = [
    '/about' => {
      :priority => 0.8
    },
    '/apps' => {
      :changefreq => 'hourly',
      :priority => 0.8
    },
    '/blog' => {
      :changefreq => 'daily',
      :priority => 0.6
    }
  ]

  def self.sitemap
    SitemapGenerator::Sitemap.default_host = 'http://sample.local'
    SitemapGenerator::Sitemap.create do
      Paths.each do |path|
        path.each do |k, v|
          add k, v
        end 
      end
    end
    # SitemapGenerator::Sitemap.ping_search_engines
  end
end

class MyApp < Sinatra::Base
  Util::Paths.each do |path|
    path.keys.each do |p|
      get p do
        p
      end
    end
  end
end

Util.sitemap
  • vim config.ru
require './app.rb'
run MyApp
  • bundle exec ruby app.rb

public/sitemap.xml.gz が作成されます
そして

  • bundle exec rackup config.ru

でアプリが起動します
localhost:9292/about などにアクセスするとレスポンスが返ってきます

ポイントは require 'sinatra/base' すること
require 'sinatra' してしまうと 4567 で WEBrick が起動してしまいます

robots.txt は

ちゃんと .gz を付与しましょう

User-Agent: *
Allow: /
Sitemap: https://sample.local/sitemap.xml.gz

最後に

sitemap_generator を使ってみました
動的にレンダリングする機能はないので sitemap.xml を生成してからサイトにデプロイしましょう
Sinatra アプリなどですでにルーティングのパスを定義している場合はその情報を使って sitemap.xml を生成するようにすれば漏れはなくなると思います

ちゃんと作成できているか確認するには https://www.xml-sitemaps.com/ とかのサイトを使えば sitemap.xml を生成してくれるので比較してみると良いと思います