- 投稿日:2019-11-25T23:12:22+09:00
Unity + DockerでUnityプロジェクトからAARファイルを自動生成
はじめに
UnityのSceneをAndroidのSubView(FrameLayout)として埋め込むまで
https://qiita.com/_nonono/items/253aa15d6027ecc8ad66
このような記事があり、AndroidにUnityのシーンをFrameLayoutに埋め込むことができます(便利Unityで開発せずビルド成果物(AARファイル)のみ必要な場合
1.指定のバージョンのUnityをインストール
2.Unityを立ち上げAndroidプロジェクトをExport
3.Android Studioでビルド
上の3つの作業を手作業で行うので一苦労だと思います
Unity、Android Studioのバージョンを意識したりしなければならいので良い方法が必要です「なんとか良い方法を!」と思って
Unityの入ったDockerイメージを使ってAARの生成を自動化
してみたので、作業を下に記していきますTL;DR
いきなり一連の作業をスクリプトにできないので、dockerの中で作業を1つずつして、最後にスクリプトにまとめます
やったこと
docker pull gableroux/unity3d
- Dockerコンテナに入りライセンスファイルを用意
- ライセンスファイルをコピー
- ビルド用クラスを作成
- Unityプロジェクトをビルド
- build.gradleとAndroidManifest.xmlを編集
- GradleでAndroidプロジェクトをビルド
- build.shを書いて完全自動化
実際の環境
・MacBook Pro (15-inch, 2016) macOS 10.13.6(17G10007)
・docker desktop ver. 2.1.0.5(40693)1. Unityの入ったDockerイメージをpullする
まずは、Unityの入っているDockerイメージをインターネットから落としてきます
次のコマンドを実行すると最新版のコンテナが自分のローカルに保存されます
Dockerイメージをpulldocker pull gableroux/unity3dしかし、このイメージではAndroidプロジェクトを生成/ビルドができないので、
Unityバージョン
とUnityプロジェクトのビルドターゲット
を指定しなければなりません。この記事では、
Unity 2018.4.5f1
でビルドターゲットをAndroid
とするので、Dockerイメージのtagに2018.4.5f1-android
を指定します
※目的のバージョンのイメージが無い場合があるので、 DockerHub のページで確認しておきましょう目的のDockerイメージをpulldocker pull gableroux/unity3d:2018.4.5f1-android
docker images
を実行するとpullしてきたイメージが確認できますDockerImageの確認cha84rakanal$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE gableroux/unity3d 2018.4.5f1-android 3cc6afa5c2bd 3 months ago 6.7GB
docker run -it {image name} bash
を実行すればイメージからコンテナが作成され、Dokcer内のターミナルに接続できる
なので、接続してUnity
とAndroid SDK
があるかを確認しておきますUnityとAndroidSDKの確認cha84rakanal$ docker run -it --rm gableroux/unity3d:2018.4.5f1-android bash root@7462f939f4d0:/# echo $ANDROID_HOME /opt/android-sdk-linux root@7462f939f4d0:/# ls /opt/ android-sdk-linux Unity Unity-2018.4.5f1 root@7462f939f4d0:/#2. Dockerコンテナに入ったUnity用のライセンスを生成する(1回のみ・手作業)
普段使うmacOSやWindowsでUnityを一番最初に起動すると次の画面が出てアクティベーションをする必要があります
次の作業は、DockerコンテナにインストールされているUnityのアクティベーションを行います
まず、Unityプロジェクトジェクトのディレクトリに移動してdockerコンテナを立ち上げます
※ パスワードを平打ちするので気をつけましょうDockerコンテナの立ち上げcd /path/to/project docker run -it --rm \ -e "UNITY_USERNAME=username@example.com" \ -e "UNITY_PASSWORD=example_password" \ -e "TEST_PLATFORM=linux" \ -e "WORKDIR=/root/project" \ -v "$(pwd):/root/project" \ gableroux/unity3d:gableroux/unity3d:2018.4.5f1-android \ bash次に、Dockerコンテナ内で以下のコマンドを実行します
アクティベーションをするxvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \ /opt/Unity/Editor/Unity \ -logFile /dev/stdout \ -batchmode \ -username "$UNITY_USERNAME" -password "$UNITY_PASSWORD"すると、出力に次のようなXMLが表示されるので、表示されたXMLを
unity3d.alf
というファイル名で保存しますターミナルでの出力LICENSE SYSTEM [2017723 8:6:38] Posting <?xml version="1.0" encoding="UTF-8"?><root><SystemInfo><IsoCode>en</IsoCode><UserName>[...]もし、XMLではなく
401
エラーが出た場合は、2段階認証を切りましょう
Googleでサインイン
やFaceBookでサインイン
でUnity IDを作成している場合でも401
エラーがでるので、メールアドレスから作成したUnityIDを使いましょう401エラーCan't activate unity: No sufficient permissions while processing request HTTP error code 401
unity3d.alf
が用意できたら https://license.unity3d.com/manual にアクセスします。アクセスしたらサイトの指示に従ってunity3d.alf
をアップロード、質問に答えていきましょう。すべて終わるとUnity_v2018.x.ulf
かUnity_v2019.x.ulf
がダウンロードできるので保存しておきます。
このUnity_v*.x.ulfファイル
は今後必要になるので大切に保管しておきますプロジェクトチームの誰かがこの作業をやれば、Unityのバージョンを変えない限り、
3以降の作業でUnity_v2018.x.ulf
を使いまわすことができます(多分※アクティベーションの詳しい手順は このページ に書いてありますが、まとめとしてこの記事にも記しています
3. Dockerコンテナに入ったUnityにライセンスを当てる
2の手順でダウンロードしてきたファイル
Unity_v2018.x.ulf
をUnityプロジェクトのルートディレクトにおきますUnity_v2018.x.ulfの設置/path/to/project ├── Assets ├── Library ├── Logs ├── Packages ├── ProjectSettings ├── README.md └── Unity_v2018.x.ulf次に、Unityプロジェクトジェクトのディレクトリに移動してdockerコンテナを立ち上げます
Dockerコンテナの立ち上げcd /path/to/project docker run -it --rm \ -v "$(pwd):/root/project" \ gableroux/unity3d:gableroux/unity3d:2018.4.5f1-android \ bashここから先の作業7.が終わるまでは、dockerコンテナ内のターミナルで
exit
をしてはいけませんライセンスファイルをUnityプロジェクトのルートディレクトに設置できたら、dockerコンテナ内のターミナルで次のコマンドを実行します。これでライセンスファイルの設置は完了です
Unityのライセンスファイルを設置set -e set -x mkdir -p /root/.cache/unity3d mkdir -p /root/.local/share/unity3d/Unity/ set +x cp ~/project/Unity_v2018.x.ulf /root/.local/share/unity3d/Unity/Unity_lic.ulf4. ビルド用クラスを作成してAssets/Scripts/Editorに配置
Unityプロジェクトをコマンドラインからビルドできるように次のC#ソースを
Assets/Scripts/Editor
に設置しますApplicationBuild.csusing UnityEngine; using System; using System.Linq; public static class ApplicationBuild { private static string[] GetAllScenePaths() { return EditorBuildSettings.scenes .Where(scene => scene.enabled) .Select(scene => scene.path) .ToArray(); } public static void AndroidBuild() { string[] scenes = GetAllScenePaths(); BuildPipeline.BuildPlayer(scenes, "./Build/", BuildTarget.Android, BuildOptions.AcceptExternalModificationsToPlayer); } }公式ドキュメント を確認すると、 ビルドのパラメータを色々設定できます。
今回は、プロジェクトのエクポートパスは./Build
、ビルドターゲットはAndroidなのでBuildTarget.Android
、AndroidのビルドはExternalで行うのでオプションにBuildOptions.AcceptExternalModificationsToPlayer
を指定していますBuildPipeline.BuildPlayer
BuildPlayer(EditorBuildSettingsScene[] levels, string locationPathName, BuildTarget target, BuildOptions options)
パラメータ levels The Scenes to be included in the build. If empty, the currently open Scene will be built. Paths are relative to the project folder (Assets/MyLevels/MyScene.unity). locationPathName 成果物の保存先のパス target ビルドする BuildTarget options ビルドしたプレイヤーを実行するか、などの追加の BuildOptions 5. コマンドラインからUnityプロジェクトをビルド
次のコマンドでUnityプロジェクトをコマンドラインからビルドできます
コマンドラインからUnityプロジェクトをビルド/opt/Unity/Editor/Unity -batchmode -quit -nographics -logFile ./build.log -projectPath . -executeMethod ApplicationBuild.AndroidBuildログは
./build.log
に保存されるので、エディタ等で確認するとビルドが進んでるのがわかります
ビルド中は、ターミナルに何かこれといって表示されるものは無いです(無視できるエラーとかは出るビルドが完了するとログに次の行が記録されます
build.logExiting batchmode successfully now!※成果物の保存先のパス
./Build
ディレクトリがすでにあるとビルドが停止してしまうので消しておきましょう6. APKではなくAARを作成するようにbuild.gradleとAndroidManifest.xmlを編集
5の作業が終わり、ビルドが成功すると、
./Build
ディレクトリにAndroidプロジェクトが生成されますコマンドラインでのビルド後/path/to/project ├── Assets ├── Build │ └── {Project Name} │ ├── build.gradle │ ├── gradle.properties │ ├── libs │ ├── local.properties │ ├── proguard-unity.txt │ └── src ├── Library ├── Logs ├── Packages ├── ProjectSettings ├── README.md └── Unity_v2018.x.ulfデバイスにインストールする
.apk
の作成であれば、gradle
コマンドでビルドして終了です
今回は、.aar
の作成なので、build.gradle
とAndroidManifest.xml
を編集する必要があります
build.gradle
apply plugin: 'com.android.application'
をapply plugin: 'com.android.library'
に変更applicationId 'com.project.unitytest'
を削除bundle
をコメントアウト(Unity2018.3.x以降??)build.gradle--- apply plugin: 'com.android.application' +++ apply plugin: 'com.android.library' --- applicationId 'com.project.unitytest' --- bundle { +++ /*bundle { language { enableSplit = false } density { enableSplit = false } abi { enableSplit = true } --- } +++ }*/
AndroidManifest.xml
- 該当のintent-filterタグとその子をすべてコメントアウト
AndroidManifest.xml<!--<intent-filter>--> <!--<action android:name="android.intent.action.MAIN" />--> <!--<category android:name="android.intent.category.LAUNCHER" />--> <!--<category android:name="android.intent.category.LEANBACK_LAUNCHER" />--> <!--</intent-filter>-->UnityプロジェクトからAARファイルの生成を行うには、この作業も自動化しておく必要があるので、
UnityのPostProcessの機構を使って自動化しておきます[Unity] PostProcessでビルド後に処理を差し込む
https://qiita.com/edo_m18/items/346439f7678218e85e69※
build.gradle
の文字コードはUTF-8
で書き出すこと、 C#のでのUTF8Encoding
はUTF-8 with BOM
であるので注意が必要。UTF-8 with BOM
のbuild.gradle
をgradle
に渡すとエラーになるので注意
※UnityのPostProcessBuildにはこの記事ではふれません
※build.gradle
のbundle
のコメントアウトだが このページ によれば次に示す置換でも問題ないbuild.gradle--- bundle { +++ splits { language { --- enableSplit = false +++ enable false } density { --- enableSplit = false +++ enable false } abi { --- enableSplit = true +++ enable true } }bundle部分を処理するC#の例build_text = build_text.Replace("bundle {", "splits {"); build_text = build_text.Replace("enableSplit = false", "enable false"); build_text = build_text.Replace("enableSplit = true", "enable true");7. コマンドラインでAndroidプロジェクトをビルド
残る作業は
./Build
ディレクトリに生成されたAndroidプロジェクトをビルドしてAARファイルを生成するだけです
生成されたAndroidプロジェクトのディレクトリ(build.gradle
があるディレクトリ)に移動して次のコマンドを実行するだけですAARファイルを生成gradle bundleDebugAarAARファイルを生成gradle bundleReleaseAarデバッグとリリースがあるので必要に応じて使い分けましょう
ビルドが成功するとプロジェクトのディレクトリに
build
ディレクトリが生成されて、
その中に{PROJECT_NAME}-debug.aar
{PROJECT_NAME}-release.aar
が生成されますビルド後/path/to/project ├── Assets ├── Build │ └── {Project Name} │ ├── build │ │ └── outputs │ │ └── aar │ │ ├── {PROJECT_NAME}-debug.aar │ │ └── {PROJECT_NAME}-release.aar │ ├── build.gradle │ ├── gradle.properties │ ├── libs │ ├── local.properties │ ├── proguard-unity.txt │ └── src ├── Library ├── Logs ├── Packages ├── ProjectSettings ├── README.md └── Unity_v2018.x.ulf8. 作業をシェルスクリプトにまとめる
4から7の作業をまとめて、コマンド一行でAARファイルの生成できるようにします
ライセンスファイルはUnityプロジェクトのルートディレクトリに設置して、6の作業はUnityのPostProcessBuildで自動化しておきましょうそうして作業をまとめたスクリプトが次になります
build.sh#!/usr/bin/env bash set -e set -x mkdir -p /root/.cache/unity3d mkdir -p /root/.local/share/unity3d/Unity/ set +x cp ~/project/Unity_v2018.x.ulf /root/.local/share/unity3d/Unity/Unity_lic.ulf cd ~/project/ && rm -r ./Build && /opt/Unity/Editor/Unity -batchmode -quit -nographics -logFile ./build.log -projectPath . -executeMethod ApplicationBuild.AndroidBuild cd ~/project/Build/{Project Name} && gradle bundleDebugAarUnityプロジェクトのルートディレクトリに移動して、dockerコンテナ内で
build.sh
を実行しますAARの自動ビルドcd /path/to/project chmod 777 build.sh docker run -it --rm \ -v "$(pwd):/root/project" \ gableroux/unity3d:gableroux/unity3d:2018.4.5f1-android \ /bin/bash -c "/root/project/build.sh"さいごに
これでコマンドを実行してビルドされるのを待つだけになりました!
これをBitbucket Pipeline
やCircleCI
、GitHub Actions
とかのCIツールに載せていけるといいですね
まとめている間に、既にmacOSやWindowsにUnityがインストールされていて、かつ、Android SDKがインストールされているなら、Docker上じゃなくていいかなと思いました余談
実際の出力cha84rakanal$ time docker run -it --rm -e "WORKDIR=/root/project" -v "$(pwd):/root/project" gableroux/unity3d:2018.4.5f1-android /bin/bash -c "/root/project/build.sh" + mkdir -p /root/.cache/unity3d + mkdir -p /root/.local/share/unity3d/Unity/ + set +x ALSA lib confmisc.c:767:(parse_card) cannot find card '0' ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory ALSA lib confmisc.c:1246:(snd_func_refer) error evaluating name ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM default ALSA lib confmisc.c:767:(parse_card) cannot find card '0' ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory ALSA lib confmisc.c:1246:(snd_func_refer) error evaluating name ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM default /home/builduser/buildslave/unity/build/Editor/Platform/Linux/UsbDevices.cpp:UsbDevicesQuery Welcome to Gradle 5.1.1! Here are the highlights of this release: - Control which dependencies can be retrieved from which repositories - Production-ready configuration avoidance APIs For more details see https://docs.gradle.org/5.1.1/release-notes.html Starting a Gradle Daemon (subsequent builds will be faster) > Task :help Welcome to Gradle 5.1.1. To run a build, run gradle <task> ... To see a list of available tasks, run gradle tasks To see a list of command-line options, run gradle --help To see more detail about a task, run gradle help --task <task> For troubleshooting, visit https://help.gradle.org BUILD SUCCESSFUL in 5s 1 actionable task: 1 executed debugger-agent: Unable to listen on 28 Starting a Gradle Daemon (subsequent builds will be faster) Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0. Use '--warning-mode all' to show the individual deprecation warnings. See https://docs.gradle.org/5.5.1/userguide/command_line_interface.html#sec:command_line_warnings BUILD SUCCESSFUL in 2m 7s 21 actionable tasks: 21 executed real 33m9.065s user 0m0.162s sys 0m0.178s cha84rakanal$
time
コマンドで実行時間を図ったら30分もビルドしてるので、dockerコンテナ内よりホスト側でビルドした方がはやいやんけw
- 投稿日:2019-11-25T21:05:47+09:00
DockerとQEMUでRaspbianイメージをカスタマイズする
TL;DR
dockerとqemuでRaspbianのイメージファイル
.img
にchrootし、直接カスタマイズする何がうれしい
- 設定がコード化できる
- 同じ環境をたくさん作れる
- 完成物が小さい
- PCだけで作業できる(シリアルケーブル不要)
作業用Dockerイメージ
FROM ubuntu:18.04 RUN apt update &&\ apt install -y kpartx libarchive-tools wget qemu-user-static binfmt-support --no-install-recommends &&\ apt clean &&\ rm -rf /var/lib/apt/lists/*適当に作業用ディレクトリを作成し、マウントする
$ mkdir workspace $ docker build -t nullsnet/raspbian_customizer . $ docker run --rm -v ${PWD}/workspace:/workspace -it nullsnet/raspbian_customizer bashUbuntu in Docker
ここからdockerのUbuntu内での作業
マウント
作業用ディレクトリに移動した後、Raspbianのイメージファイルをダウンロード&展開する
$ cd workspace $ wget https://downloads.raspberrypi.org/raspbian_lite_latest -O-|bsdtar -xvf $ image=$(ls -1tr *.img) $ echo $image 2019-09-26-raspbian-buster-lite.img
kpartx
でマッピングして、適当なディレクトリにマウントする
loop[0-9]p1
がboot
パーティション、loop[0-9]p2
がrootfs
パーティションになる$ kpartx -av $image $ add map loop8p1 (253:16): 0 524288 linear 7:8 8192 $ add map loop8p2 (253:17): 0 3858432 linear 7:8 532480 $ mkdir rootfs boot $ mount $(ls /dev/mapper/loop*p1) boot $ mount $(ls /dev/mapper/loop*p2) rootfsこの時点で
boot
とrootfs
の中身が触れるようになる
ファイルを改変するだけの設定で良いなら、ここまででよい$ ls rootfs bin boot dev etc home lib lost+found media mnt opt proc root run sbin srv sys tmp usr var $ ls raspbian-boot/ COPYING.linux bcm2708-rpi-cm.dtb bcm2710-rpi-2-b.dtb bcm2711-rpi-4-b.dtb fixup.dat fixup4x.dat issue.txt kernel8.img start4cd.elf start_db.elf LICENCE.broadcom bcm2708-rpi-zero-w.dtb bcm2710-rpi-3-b-plus.dtb bootcode.bin fixup4.dat fixup_cd.dat kernel.img overlays start4db.elf start_x.elf bcm2708-rpi-b-plus.dtb bcm2708-rpi-zero.dtb bcm2710-rpi-3-b.dtb cmdline.txt fixup4cd.dat fixup_db.dat kernel7.img start.elf start4x.elf bcm2708-rpi-b.dtb bcm2709-rpi-2-b.dtb bcm2710-rpi-cm3.dtb config.txt fixup4db.dat fixup_x.dat kernel7l.img start4.elf start_cd.elfchroot
qemu-arm-static
をコピーする$ cp /usr/bin/qemu-arm-static rootfs/usr/bin/
ld.so.preload
を書き換える
chroot時は$PLATFORM
が定義されないため,これをやっておかないとバイナリ実行時にエラーが出てしまう$ sed -i 's/${PLATFORM}/v7l/' rootfs/etc/ld.so.preload
rootfs
にchrootしてbashを起動する$ chroot rootfs /bin/bashこれであとは好きなように設定できる
raspi-config
raspi-config
による設定も可能$ raspi-config nonint do_hostname raspi $ raspi-config nonint do_wifi_country JP $ raspi-config nonint do_change_timezone 'Asia/Tokyo' $ raspi-config nonint do_change_locale ja_JP.UTF-8apt
apt
も当然可能$ apt update $ apt upgrade -y $ apt install -y gitsystemd
ssh
を有効にしておき、実機起動直後にすぐ接続できるようにもできる$ systemctl enable sshdocker
dockerのインストールだってできる
$ curl -sSL https://get.docker.com/ | sh $ systemctl enable dockerShell Script
設定をシェルスクリプトにしておき、chroot時に呼び出すこともできる
$ chroot rootfs /bin/bash -C YOUR_SCRIPT.sh後片付け
chrootを抜けた後、
qemu-arm-static
を削除してld.so.preload
を元に戻す$ exit $ rm rootfs/usr/bin/qemu-arm-static $ sed -i 's/v7l/${PLATFORM}/' rootfs/etc/ld.so.preloadイメージファイルをアンマウントしてマッピングを削除する
$ umount boot rootfs $ rmdir boot rootfs $ kpartx -d $image $ rm /dev/mapper/loop*SDカードの作成
作成した
*.img
ファイルを、普通にdd
等でSDカードへ書き込めばよい
- 投稿日:2019-11-25T19:33:02+09:00
Kea DHCPでAct-Sbyのhot-standby構成を組む
ゴール
Kea DHCPを用いてAct-Sbyのhot-standby High-Availability構成を組み,動作させることができること.
About Kea DHCP
Kea DHCPとは,ISC DHCPの後継として開発されているオープンソースのDHCPサーバプログラムである. MPL2.0ライセンスのもとISCのGitlabで公開されている.最新のメジャーバージョンはISC DHCPとの違いは下記のように書かれている.
1. モジュラーコンポーネントデザインとHooksモジュールによる拡張 2. REST APIによるオンラインRe-configuration 3. 既存のシステムとのインテグレーション可能なデザイン1つ目に述べられているhooksモジュールについては,ユーザ自身でこのhooksモジュールを書くことが可能であることを示している.これによりKea DHCPの独自のモジュールを実装し,より柔軟なサービス提供ができるように考慮されている.今回対象とするHA構成についても,このhooksモジュールとして実装されているHA機能を提供するライブラリをincludeして利用する形で実現する.
2つ目に述べられているREST APIによるconfigurationについては,このKea DHCPには
kea-ctrl-agent
が内包されており,このagentがREST APIの仲介者となりkea-dhcp4
プロセス(DHCPv4)やkea-dhcp6
プロセス(DHCPv6)に対して各種設定のgetやset等の様々な操作をすることが可能である.またKea DHCPはconfigを変更した際にrestartが極力必要とならないよう設計されている.多くのconfiguratinにおいては,configのreloadを行うことでconfigの適用を行うことができる.最後に,既存システムとのインテグレーションについて述べられているが,これはどうもKea DHCPのconfigurationを,configファイルだけでなくDatabaseを用いることでも行うことができる点を指しているようである.これにより,実行環境とconfigurationを分離することができることがこの3つ目の要旨であるようだ.たしかに,実行環境とconfigの環境が異なり,さらにDBを用いて管理をすることができるのは一部のIT管理者にとっては有効かもしれない.
Kea DHCPのHA構成とISC DHCPのFailover
かつてのISC-DHCPではHA機能は提供されておらず,Failover機能のみ提供されているものであった.しかしながらKea DHCPでは
release 1.4.0
よりHA構成が適用可能となった.リリースノートからも分かる通り,1.4.0は2018年6月にリリースされたものであり,まだ1年少々しか経過していない.そのためまだ機能が十分でない部分等も多くあると推察できる.1.4.0のリリース当初はこのHA機能はPremium featureとして提供されるようであったが,現在は通常のhooks libraryとして提供されている.ISC-DHCPのFailoverとKea DHCPのHA構成については下記で述べられている.
Kea High Availability vs ISC DHCP Failover - Kea DHCP
比較してみると,ISCのFailoverがDHCPv4のみに適用可能だったのに対し,Keaではv4とv6の両方に対して適用可能となっている.他には,ISCでは2つのサーバでの冗長構成のみしか組むことができなかったが,Keaでは2つのactiveサーバと無限のバックアップサーバを用いることが可能である.他にもリースDBのレプリケーション等がKeaでは実施可能であったり,ISC DHCPではOMAPIであったが,Keaではより一般的となっているREST APIによるconfigurationを採用するなど,多くの点でKea DHCPはISC DHCPをfollowする形となっているが,一部機能においてはISCよりデグレしている点が見受けられる.たとえばLoadBalancingだと,ISCではFlexible LBが可能であったが,Keaでは50/50のLBモードしか現状選択することができない.いずれもRFC3074に準拠しているようだ.また,Lazy lease updates(MCLT)の項目についてはISC DHCPでは可能であったがKeaではこれを許さず,リースアップデートが完全に完了してからでなければclientへresponseを送らないなど,より厳密となっている.このLazy lease updates(MCLT)については特にHA構成を組んだ場合のパフォーマンスに大きく影響することとなる.Kea DHCPでHA構成を組む
Introduction
では早速Kea DHCPを用いたHA構成を組んでいく.
今回はKea DHCP verion 1.5.0を利用する.基本的にはマニュアルを熟読の上設定を行なっていくこととなる.v1.5.0では下記を参照することとなるだろう.
Kea Administrator Reference Manual
HA構成を組む上ではこのマニュアルのうち15.4.8. ha: High Availability
をrefer するとよい.
また,HA構成において,HA構成を組んだホスト間のリースアップデート等はkea-ctrl-agent
を介したREST API経由で実施されることとなる.そのため,Kea DHCPのREST API,kea-ctrl-agentについての理解も深めておくとより理解が深まると思う.Build and Configuration
今回はdockerを用いて検証環境を構成してHA構成を組むことを試みる.
下記のdocker file とdocker-compose.ymlを用いてkea-dhcp2台をデプロイし,その間をkea-mng
ネットワークで繋いでおく.docker compose up
する前に,docker network create kea-mng
をしておく必要がある.これはHA構成を組むにあたりheartbeat
やlease-update
をREST APIで実施するためのmanagement network相当のものである.L3疎通性さえあれば問題はない.FROM ubuntu:latest ENV KEA_DHCP_VERSION=1.5.0 ENV LOG4_CPLUS_VERSION=1.2.1 RUN apt-get update && apt-get install -y iputils-ping net-tools traceroute nmap tcpdump iproute2 python3 vim jq curl automake libtool pkg-config build-essential ccache libboost-dev libboost-system-dev liblog4cplus-dev libssl-dev && \ curl -SL https://downloads.isc.org/isc/kea/${KEA_DHCP_VERSION}/kea-${KEA_DHCP_VERSION}.tar.gz | tar -xzC /root && \ cd /root/kea-${KEA_DHCP_VERSION} && \ ./configure --enable-shell && \ make -s -j$(nproc) && \ make install && \ ldconfigversion: '3' services: kea_1: image: kea:1.5.0 container_name: kea_1 privileged: true networks: - kea-mng tty: true kea_2: image: kea:1.5.0 container_name: kea_2 privileged: true networks: - kea-mng tty: true networks: kea-mng: external: true環境が構築できたら,実際にconfigを行っていく.
manualを見て分かるとおり,HA構成の実現にはhooks libraryを用いる.
今回はDHCPv4のデーモンに対してHA構成を行う例を示す.DHCPv6に対しても似たconfigで実現できると思われる.早速HA構成を組むためのconfigを見ていこう.
マニュアルのHA構成のconfigサンプルとしてこのような例がある.{ "Dhcp4": { ... "hooks-libraries": [ { "library": "/usr/lib/hooks/libdhcp_lease_cmds.so", "parameters": { } }, { "library": "/usr/lib/hooks/libdhcp_ha.so", "parameters": { "high-availability": [ { "this-server-name": "server1", "mode": "load-balancing", "heartbeat-delay": 10000, "max-response-delay": 10000, "max-ack-delay": 5000, "max-unacked-clients": 5, "peers": [ { "name": "server1", "url": "http://192.168.56.33:8080/", "role": "primary", "auto-failover": true }, { "name": "server2", "url": "http://192.168.56.66:8080/", "role": "secondary", "auto-failover": true }, { "name": "server3", "url": "http://192.168.56.99:8080/", "role": "backup", "auto-failover": false } ] } ] } } ], ... } }
hooks-libraries
としてlibdhcp_ha.so
を読み込む.デフォルトでは/user/lib/hooks
配下に存在するが,keaのinstall pathによっては異なってくるので注意しよう.基本的にはkeaのroot配下の./lib/hooks
配下にあると考えて良さそうだ.またHA構成を組む場合,lease-update
を送受信してリース情報を交換する必要がある.これを行うためにlibdhcp_lease_cmds.so
を読み込む必要がある.
読み込んでいないとlease情報がAPI経由でやり取りすることができない.たとえばこのlibdhcp_lease_cmd.so
を読み込まずにlease情報をgetしようとすると,下記のようになる.root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "lease4-get-all", "service": [ "dhcp4" ] }' http://localhost:8080/ | jq .[] { "result": 2, "text": "'lease4-get-all' command not supported." }さらに各種パラメータのsettingもここで実施する.パラメータの意味はざっくりと下記の通りである.
this-server-name: HA構成のうち自身を指すユニークな名前. mode: HAのモード. heartbeat-delay: パートナーへのハートビートの送信間隔.[ms] max-response-delay: パートナーをdownと判定するまでの時間.[ms] max-ack-delay: パートナーがクライアントからの通信に応答するべきmax時間.これを超えるとunackedと判定される.[ms] max-unacked-clients: パートナーを障害と判定するunackedクライアント数. peers: HAを構成するホストの指定これらをマニュアルを見つつ適切に設定することとなる.
このうち,動作を大きく変えるのがmode
である.このパラメータはHAをどのモードで動作させるかを決定する.
Kea DHCPのHA動作モードはload-balancing
とhot-standby
のうちから選ぶ.
想像できる通り,load-balancing
モードでは50/50のact-actによる構成,hot-standby
モードではact-sbyのホットスタンバイ構成となる.今回はまず
hot-standby
モードとして下記の通り設定することとする.
primary に相当するのがkea_1
でありIPアドレスは172.18.0.3
,secondaryに相当するのがkea_2
でありIPアドレスは172.18.0.2
とした.this-server-name: kea_1 //(2台目は"kea_2") mode: hot-standby heartbeat-delay: 10000 max-response-delay: 60000 max-ack-delay: 10000 max-unacked-clients: 0 "peers": [ { "name": "kea_1", "url": "http://172.18.0.3:8080/", "role": "primary", "auto-failover": true }, { "name": "kea_2", "url": "http://172.18.0.2:8080/", "role": "secondary", "auto-failover": true } ]上記を踏まえてconfigを作成し,各サーバに適用して動作を確認してみよう.
たとえば私の検証では下記のようなconfgをkea_1
に投入した.kea_2
もほぼ同様であるが,"this-server-name"
等を適切に変更する.{ "Dhcp4": { "interfaces-config": { "interfaces": ["eth0"], "dhcp-socket-type": "raw", "outbound-interface": "use-routing" }, "control-socket": { "socket-type": "unix", "socket-name": "/tmp/kea-dhcp4-ctrl.sock" }, "lease-database": { "type": "memfile", "persist": true, "name": "/usr/local/etc/kea/kea-leases4.csv", "lfc-interval": 1800 }, "expired-leases-processing": { "reclaim-timer-wait-time": 10, "flush-reclaimed-timer-wait-time": 25, "hold-reclaimed-time": 3600, "max-reclaim-leases": 100, "max-reclaim-time": 250, "unwarned-reclaim-cycles": 5 }, "renew-timer": 1200, "rebind-timer": 2400, "valid-lifetime": 3600, "option-data": [ { "name": "domain-name-servers", "data": "8.8.8.8, 8.8.4.4" }, { "name": "default-ip-ttl", "data": "0xf0" } ], "hooks-libraries": [ { "library": "/usr/local/lib/hooks/libdhcp_lease_cmds.so", "parameters": { } }, { "library": "/usr/local/lib/hooks/libdhcp_ha.so", "parameters": { "high-availability": [ { "this-server-name": "kea_1", "mode": "hot-standby", "heartbeat-delay": 10000, "max-response-delay": 10000, "max-ack-delay": 5000, "max-unacked-clients": 5, "peers": [ { "name": "kea_1", "url": "http://172.18.0.2:8080/", "role": "primary", "auto-failover": true }, { "name": "kea_2", "url": "http://172.18.0.3:8080/", "role": "standby", "auto-failover": true } ] } ] } } ], "reservation-mode": "disabled", "host-reservation-identifiers": [ "hw-address" ], "subnet4": [ { "id": 1, "subnet": "172.18.0.0/16", "pools": [ { "pool": "172.18.1.1 - 172.18.1.250" } ], "option-data": [ { "name": "routers", "data": "172.18.0.1" } ] } ] }, "Logging": { "loggers": [ { "name": "kea-dhcp4", "output_options": [ { "output": "/usr/local/var/log/kea-dhcp4.log", "maxver": 8, "maxsize": 204800, "flush": true } ], "severity": "DEBUG", "debuglevel": 99 } ] } }まずは
kea_1
でkeaを起動する.今回,起動にはkeactrl
コマンドを用いた.
起動後にRESTAPIでha-heartbeat
を送信し,それぞれのサーバのステータスを見てみよう.# kea_1 root@0d5f73b7ffc7:/# keactrl start INFO/keactrl: Starting /usr/local/sbin/kea-dhcp4 -c /usr/local/etc/kea/kea-dhcp4.conf INFO/keactrl: Starting /usr/local/sbin/kea-ctrl-agent -c /usr/local/etc/kea/kea-ctrl-agent.conf root@0d5f73b7ffc7:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq [ { "arguments": { "date-time": "Fri, 25 Oct 2019 17:50:37 GMT", "state": "waiting" }, "result": 0, "text": "HA peer status returned." } ] root@0d5f73b7ffc7:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq curl: (7) Failed to connect to 172.18.0.2 port 8080: Connection refused root@0d5f73b7ffc7:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq [ { "arguments": { "date-time": "Fri, 25 Oct 2019 17:50:42 GMT", "state": "partner-down" }, "result": 0, "text": "HA peer status returned." } ] root@0d5f73b7ffc7:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq curl: (7) Failed to connect to 172.18.0.2 port 8080: Connection refused起動後,
kea_1
はstateがwaiting
から,partner-down
にシフトしていることが見受けられる.
kea_2
に対しても同様のリクエストを行なっているが,kea_2
ではまだkeaを起動していないためConnection refused
が返っている.
kea_1
を起動し,kea_2
を起動していない場合,kea_1
はこの状態に収束する.ここで
kea_2
を起動する.# kea_2 root@d864a28cbf84:/# keactrl start INFO/keactrl: Starting /usr/local/sbin/kea-dhcp4 -c /usr/local/etc/kea/kea-dhcp4.conf INFO/keactrl: Starting /usr/local/sbin/kea-ctrl-agent -c /usr/local/etc/kea/kea-ctrl-agent.conf root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq [ { "arguments": { "date-time": "Fri, 25 Oct 2019 17:51:37 GMT", "state": "partner-down" }, "result": 0, "text": "HA peer status returned." } ] root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq [ { "arguments": { "date-time": "Fri, 25 Oct 2019 17:51:41 GMT", "state": "waiting" }, "result": 0, "text": "HA peer status returned." } ] root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq [ { "arguments": { "date-time": "Fri, 25 Oct 2019 17:51:47 GMT", "state": "partner-down" }, "result": 0, "text": "HA peer status returned." } ] root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq [ { "arguments": { "date-time": "Fri, 25 Oct 2019 17:51:49 GMT", "state": "ready" }, "result": 0, "text": "HA peer status returned." } ] root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq [ { "arguments": { "date-time": "Fri, 25 Oct 2019 17:51:54 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ] root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq [ { "arguments": { "date-time": "Fri, 25 Oct 2019 17:51:55 GMT", "state": "ready" }, "result": 0, "text": "HA peer status returned." } ] root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq [ { "arguments": { "date-time": "Fri, 25 Oct 2019 17:51:57 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ] root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq [ { "arguments": { "date-time": "Fri, 25 Oct 2019 17:51:59 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ]ログを見てみる.
# kea_1 root@0d5f73b7ffc7:/# grep INFO /usr/local/var/log/kea-dhcp4.log | grep ha-hooks 2019-10-25 17:50:27.312 INFO [kea-dhcp4.ha-hooks/1358] HA_CONFIGURATION_SUCCESSFUL HA hook library has been successfully configured 2019-10-25 17:50:27.312 INFO [kea-dhcp4.ha-hooks/1358] HA_INIT_OK loading High Availability hooks library successful 2019-10-25 17:50:27.359 INFO [kea-dhcp4.ha-hooks/1358] HA_LOCAL_DHCP_DISABLE local DHCP service is disabled while the kea_1 is in the WAITING state 2019-10-25 17:50:27.359 INFO [kea-dhcp4.ha-hooks/1358] HA_SERVICE_STARTED started high availability service in hot-standby mode as primary server 2019-10-25 17:50:37.372 INFO [kea-dhcp4.ha-hooks/1358] HA_STATE_TRANSITION server transitions from WAITING to PARTNER-DOWN state, partner state is UNDEFINED 2019-10-25 17:50:37.372 INFO [kea-dhcp4.ha-hooks/1358] HA_LEASE_UPDATES_DISABLED lease updates will not be sent to the partner while in PARTNER-DOWN state 2019-10-25 17:50:37.372 INFO [kea-dhcp4.ha-hooks/1358] HA_LOCAL_DHCP_ENABLE local DHCP service is enabled while the kea_1 is in the PARTNER-DOWN state 2019-10-25 17:51:52.821 INFO [kea-dhcp4.ha-hooks/1358] HA_STATE_TRANSITION server transitions from PARTNER-DOWN to HOT-STANDBY state, partner state is READY 2019-10-25 17:51:52.821 INFO [kea-dhcp4.ha-hooks/1358] HA_LEASE_UPDATES_ENABLED lease updates will be sent to the partner while in HOT-STANDBY state 2019-10-25 17:53:11.474 INFO [kea-dhcp4.ha-hooks/1358] HA_DEINIT_OK unloading High Availability hooks library successful root@0d5f73b7ffc7:/## kea_2 root@d864a28cbf84:/# grep INFO /usr/local/var/log/kea-dhcp4.log | grep ha-hooks 2019-10-25 17:51:33.880 INFO [kea-dhcp4.ha-hooks/901] HA_CONFIGURATION_SUCCESSFUL HA hook library has been successfully configured 2019-10-25 17:51:33.880 INFO [kea-dhcp4.ha-hooks/901] HA_INIT_OK loading High Availability hooks library successful 2019-10-25 17:51:33.928 INFO [kea-dhcp4.ha-hooks/901] HA_LOCAL_DHCP_DISABLE local DHCP service is disabled while the kea_2 is in the WAITING state 2019-10-25 17:51:33.928 INFO [kea-dhcp4.ha-hooks/901] HA_SERVICE_STARTED started high availability service in hot-standby mode as standby server 2019-10-25 17:51:45.738 INFO [kea-dhcp4.ha-hooks/901] HA_STATE_TRANSITION server transitions from WAITING to SYNCING state, partner state is PARTNER-DOWN 2019-10-25 17:51:45.738 INFO [kea-dhcp4.ha-hooks/901] HA_LEASE_UPDATES_DISABLED lease updates will not be sent to the partner while in SYNCING state 2019-10-25 17:51:45.738 INFO [kea-dhcp4.ha-hooks/901] HA_SYNC_START starting lease database synchronization with kea_1 2019-10-25 17:51:45.743 INFO [kea-dhcp4.ha-hooks/901] HA_LEASES_SYNC_LEASE_PAGE_RECEIVED received 0 leases from kea_1 2019-10-25 17:51:45.745 INFO [kea-dhcp4.ha-hooks/901] HA_SYNC_SUCCESSFUL lease database synchronization with kea_1 completed successfully in 6.724 ms 2019-10-25 17:51:45.745 INFO [kea-dhcp4.ha-hooks/901] HA_STATE_TRANSITION server transitions from SYNCING to READY state, partner state is PARTNER-DOWN 2019-10-25 17:51:45.745 INFO [kea-dhcp4.ha-hooks/901] HA_LEASE_UPDATES_DISABLED lease updates will not be sent to the partner while in READY state 2019-10-25 17:51:56.910 INFO [kea-dhcp4.ha-hooks/901] HA_STATE_TRANSITION server transitions from READY to HOT-STANDBY state, partner state is HOT-STANDBY 2019-10-25 17:51:56.911 INFO [kea-dhcp4.ha-hooks/901] HA_LEASE_UPDATES_ENABLED lease updates will be sent to the partner while in HOT-STANDBY state 2019-10-25 17:51:56.911 INFO [kea-dhcp4.ha-hooks/901] HA_LOCAL_DHCP_ENABLE local DHCP service is enabled while the kea_2 is in the HOT-STANDBY state 2019-10-25 17:53:12.373 INFO [kea-dhcp4.ha-hooks/901] HA_DEINIT_OK unloading High Availability hooks library successful root@d864a28cbf84:/#これらをみてみると,起動直後,これらのHAは下記のような順序で状態遷移していることがわかる.
2019-10-25 17:50:27.359:: kea_1: waiting , kea_2: UNDEFINED 2019-10-25 17:50:37.372:: kea_1: partner-down, kea_2: UNDEFINED 2019-10-25 17:51:33.928:: kea_1: partner-down, kea_2: waiting 2019-10-25 17:51:45.738:: kea_1: partner-down, kea_2: syncing 2019-10-25 17:51:45.745:: kea_1: partner-down, kea_2: ready 2019-10-25 17:51:52.821:: kea_1: hot-standby, kea_2: ready 2019-10-25 17:51:56.910:: kea_1: hot-standby , kea_2: hot-standbyこのそれぞれのstateの意味はkeaのdocumentを参照し,簡単に理解をまとめると下記の通りである.ほぼgoogle翻訳.
- Backup: backup-serverの平常状態で,この状態であればアクティブなサーバーからlease-updateを受信する. - hot-standby: hot-standbyモードのアクティブサーバーの平常状態.hot-standbyモードにおけるプライマリサーバーとスタンバイサーバーは,通常この状態をとる.プライマリサーバーはDHCPクエリに応答し,スタンバイサーバーが存在する場合,スタンバイサーバーとバックアップサーバーにlease-updateを送信する - load-balancing: load-balancingモードのアクティブなサーバーの平常状態.loadbalancingモードにおけるプライマリサーバーとセカンダリサーバーは,通常のこの状態をとる.両方のサーバーがDHCPクエリに応答し,リース更新を相互に送信し,バックアップサーバーが存在する場合にはバックアップサーバーに送信する. - partner-down: partnerがオフラインであることを検出すると,アクティブサーバーはこの状態に移行する.バックアップサーバが利用できなくともサーバーはこの状態に移行しない.パートナーダウン状態では,サーバーはすべてのDHCPクエリに応答し,'通常時に現在使用できないpartnerによって処理されるべきクエリ'にも応答する. - ready: lease-databaseをアクティブなパートナーと同期した後、アクティブなサーバーはこの状態となる.この状態は、パートナーに通知される可能性がある(partner-down状態にある可能性が高いため、通常の操作に戻る可能性が高い.その場合,ready 状態にあるサーバーも続けて通常の動作を開始する.) - syncing: アクティブなサーバーはこの状態に移行して,アクティブなパートナーからリースを取得し,ローカルリースデータベースを更新します.この状態になると,dhcp-disableを発行して,リースの取得元のパートナーのDHCPサービスを無効にします. DHCPサービスは最大60秒間無効になります.その後,同期パートナーが再び停止し,サービスを再度有効にできない場合に備えて,自動的に有効になります.同期が完了すると,同期サーバーはdhcp-enableを発行して,パートナーのDHCPサービスを再度有効にします.同期操作は同期です.サーバーは,パートナーからの応答を待っており,リースの同期が行われている間は何もしていません.データベースをパートナーと同期しないように構成されているサーバー,つまりsync-leases構成パラメーターがfalseに設定されている場合,この状態に移行することはありません.代わりに,待機状態から準備完了状態に直接移行します. - terminated: ha-hooksライブラリがHA構成を組めず,問題を解決するために手動オペレーションが必要な場合にアクティブサーバーはこの状態となる.HAセットアップの様々なの問題により,サーバーがこの状態になる可能性がある.. Kea 1.4.0リリースの時点でのHAを終了させる唯一の問題は,各サーバーのクロックが60秒以上離れている場合である.terminated 状態のサーバーは選択されたHAモード(ロードバランシングまたはホットスタンバイ)に基づいてDHCPクライアントに応答し続けますが,lease-updateは交換されず,ha-heartbeatは送信されない.terminated状態になると,サーバーは再起動されるまでこの状態となる.オペレータはサーバーを再起動する前に,根本原因を解決する必要がある. - wainting: 起動された各サーバーはこの状態となる.バックアップサーバーは,この状態からバックアップ状態に直接移行する.アクティブなサーバーは,その状態を確認するためにpartnerにha-heartbeatを送信する.pertnerがオフラインであると考えられる場合,サーバーはpartner-down状態に移行する.そうでない場合,sync-leases構成パラメーターの設定に応じてsyncing状態またはready状態に移行する.両方のサーバーがwaiting状態(同時起動)にあると思われる場合,プライマリサーバーが先に状態遷移する.プライマリサーバーがready状態に移行するまで,セカンダリサーバーまたはスタンバイサーバーはwaiting状態のままとなる.hot-stanbuyモードでのDHCPリース動作
HA構成を組むことができていることが確認できたので,この状態でDHCPのアドレスリースを行い,HA構成に受けるアドレスリース時の振る舞いを確認していこうと思う.
DHCPリースを模擬するために,今回はdhtestを利用した.
構築してhot-standby HA構成のサーバ群が接続されているNW上に存在するdhtest用VMで,下記の通りコマンドを実行する.
./dhtest -i <interface> -m <mac_addr>
mac_addr
はdhtestホストから送出するDHCPパケットのchaddr
フィールドに入るMACアドレスと思えばよい.
これを数回繰り返し実施した時の振る舞いを見てみる.
dhtestホストでは下記のようにアドレスリースが模擬できているようである.[root@a50202e240fb dhtest-master]# ./dhtest -i eth0 -m 00:00:00:11:22:33 DHCP discover sent - Client MAC : 00:00:00:11:22:33 DHCP offer received - Offered IP : 172.18.1.1 DHCP request sent - Client MAC : 00:00:00:11:22:33 ^[[ADHCP ack received - Acquired IP: 172.18.1.1 [root@a50202e240fb dhtest-master]# ./dhtest -i eth0 -m 00:00:00:11:22:34 DHCP discover sent - Client MAC : 00:00:00:11:22:34 DHCP offer received - Offered IP : 172.18.1.2 DHCP request sent - Client MAC : 00:00:00:11:22:34 DHCP ack received - Acquired IP: 172.18.1.2 [root@a50202e240fb dhtest-master]# ./dhtest -i eth0 -m 00:00:00:11:22:35 DHCP discover sent - Client MAC : 00:00:00:11:22:35 DHCP offer received - Offered IP : 172.18.1.3 DHCP request sent - Client MAC : 00:00:00:11:22:35 DHCP ack received - Acquired IP: 172.18.1.3 [root@a50202e240fb dhtest-master]#この時,
kea_1
とkea_2
ホストでパケットキャプチャを行うと.## kea_1 root@0d5f73b7ffc7:~# tcpdump -i eth0 port 67 or port 68 -v -n tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes ^[[O06:44:35.308246 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:33, length 251, xid 0x6a521a43, Flags [none] Client-Ethernet-Address 00:00:00:11:22:33 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Discover Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:35.311244 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318) 172.18.0.3.67 > 172.18.1.1.68: BOOTP/DHCP, Reply, length 290, xid 0x6a521a43, Flags [none] Your-IP 172.18.1.1 Client-Ethernet-Address 00:00:00:11:22:33 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 Subnet-Mask Option 1, length 4: 255.255.0.0 Default-Gateway Option 3, length 4: 172.18.0.1 Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4 Lease-Time Option 51, length 4: 3600 DHCP-Message Option 53, length 1: Offer Server-ID Option 54, length 4: 172.18.0.3 RN Option 58, length 4: 1200 RB Option 59, length 4: 2400 06:44:35.311902 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:33, length 263, xid 0x6a521a43, Flags [none] Client-Ethernet-Address 00:00:00:11:22:33 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Request Requested-IP Option 50, length 4: 172.18.1.1 Server-ID Option 54, length 4: 172.18.0.3 Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:36.316180 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318) 172.18.0.3.67 > 172.18.1.1.68: BOOTP/DHCP, Reply, length 290, xid 0x6a521a43, Flags [none] Your-IP 172.18.1.1 Client-Ethernet-Address 00:00:00:11:22:33 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 Subnet-Mask Option 1, length 4: 255.255.0.0 Default-Gateway Option 3, length 4: 172.18.0.1 Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4 Lease-Time Option 51, length 4: 3600 DHCP-Message Option 53, length 1: ACK Server-ID Option 54, length 4: 172.18.0.3 RN Option 58, length 4: 1200 RB Option 59, length 4: 2400 06:44:38.007073 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:34, length 251, xid 0x23552c97, Flags [none] Client-Ethernet-Address 00:00:00:11:22:34 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Discover Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:38.009206 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318) 172.18.0.3.67 > 172.18.1.2.68: BOOTP/DHCP, Reply, length 290, xid 0x23552c97, Flags [none] Your-IP 172.18.1.2 Client-Ethernet-Address 00:00:00:11:22:34 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 Subnet-Mask Option 1, length 4: 255.255.0.0 Default-Gateway Option 3, length 4: 172.18.0.1 Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4 Lease-Time Option 51, length 4: 3600 DHCP-Message Option 53, length 1: Offer Server-ID Option 54, length 4: 172.18.0.3 RN Option 58, length 4: 1200 RB Option 59, length 4: 2400 06:44:38.009418 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:34, length 263, xid 0x23552c97, Flags [none] Client-Ethernet-Address 00:00:00:11:22:34 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Request Requested-IP Option 50, length 4: 172.18.1.2 Server-ID Option 54, length 4: 172.18.0.3 Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:39.014721 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318) 172.18.0.3.67 > 172.18.1.2.68: BOOTP/DHCP, Reply, length 290, xid 0x23552c97, Flags [none] Your-IP 172.18.1.2 Client-Ethernet-Address 00:00:00:11:22:34 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 Subnet-Mask Option 1, length 4: 255.255.0.0 Default-Gateway Option 3, length 4: 172.18.0.1 Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4 Lease-Time Option 51, length 4: 3600 DHCP-Message Option 53, length 1: ACK Server-ID Option 54, length 4: 172.18.0.3 RN Option 58, length 4: 1200 RB Option 59, length 4: 2400 06:44:40.623865 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:35, length 251, xid 0x4a57a2d2, Flags [none] Client-Ethernet-Address 00:00:00:11:22:35 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Discover Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:40.625059 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318) 172.18.0.3.67 > 172.18.1.3.68: BOOTP/DHCP, Reply, length 290, xid 0x4a57a2d2, Flags [none] Your-IP 172.18.1.3 Client-Ethernet-Address 00:00:00:11:22:35 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 Subnet-Mask Option 1, length 4: 255.255.0.0 Default-Gateway Option 3, length 4: 172.18.0.1 Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4 Lease-Time Option 51, length 4: 3600 DHCP-Message Option 53, length 1: Offer Server-ID Option 54, length 4: 172.18.0.3 RN Option 58, length 4: 1200 RB Option 59, length 4: 2400 06:44:40.625322 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:35, length 263, xid 0x4a57a2d2, Flags [none] Client-Ethernet-Address 00:00:00:11:22:35 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Request Requested-IP Option 50, length 4: 172.18.1.3 Server-ID Option 54, length 4: 172.18.0.3 Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:42.630578 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318) 172.18.0.3.67 > 172.18.1.3.68: BOOTP/DHCP, Reply, length 290, xid 0x4a57a2d2, Flags [none] Your-IP 172.18.1.3 Client-Ethernet-Address 00:00:00:11:22:35 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 Subnet-Mask Option 1, length 4: 255.255.0.0 Default-Gateway Option 3, length 4: 172.18.0.1 Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4 Lease-Time Option 51, length 4: 3600 DHCP-Message Option 53, length 1: ACK Server-ID Option 54, length 4: 172.18.0.3 RN Option 58, length 4: 1200 RB Option 59, length 4: 2400## kea_2 root@d864a28cbf84:~# tcpdump -i eth0 port 67 or port 68 -v -n tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 06:44:35.308297 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:33, length 251, xid 0x6a521a43, Flags [none] Client-Ethernet-Address 00:00:00:11:22:33 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Discover Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:35.311922 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:33, length 263, xid 0x6a521a43, Flags [none] Client-Ethernet-Address 00:00:00:11:22:33 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Request Requested-IP Option 50, length 4: 172.18.1.1 Server-ID Option 54, length 4: 172.18.0.3 Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:38.007102 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:34, length 251, xid 0x23552c97, Flags [none] Client-Ethernet-Address 00:00:00:11:22:34 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Discover Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:38.009439 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:34, length 263, xid 0x23552c97, Flags [none] Client-Ethernet-Address 00:00:00:11:22:34 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Request Requested-IP Option 50, length 4: 172.18.1.2 Server-ID Option 54, length 4: 172.18.0.3 Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:40.623903 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:35, length 251, xid 0x4a57a2d2, Flags [none] Client-Ethernet-Address 00:00:00:11:22:35 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Discover Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Server 06:44:40.625346 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291) 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:35, length 263, xid 0x4a57a2d2, Flags [none] Client-Ethernet-Address 00:00:00:11:22:35 Vendor-rfc1048 Extensions Magic Cookie 0x63825363 DHCP-Message Option 53, length 1: Request Requested-IP Option 50, length 4: 172.18.1.3 Server-ID Option 54, length 4: 172.18.0.3 Parameter-Request Option 55, length 5: Subnet-Mask, BR, Default-Gateway, Domain-Name Domain-Name-Serverこれら2つのパケットキャプチャをみて理解できるとおり,
kea_1
はクライアントからの3つ全てのDHCP要求(DISCOVERY
,REQUEST
)において,クライアントに対してOFFER
,ACK
を返しており,kea_2
はクライアントからの要求には全く応答パケットを送出していないことがわかる.
これはHAモードのうち,primaryの1台のみがDHCPに応答するhot-standby
の動作となっていることがわかる.またこの時の
lease-update
を見てみよう.## kea_1 root@0d5f73b7ffc7:/# tcpdump -i eth0 port 8080 -v -n tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 06:44:35.313735 IP (tos 0x0, ttl 64, id 57517, offset 0, flags [DF], proto TCP (6), length 403) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0x066f), seq 220565762:220566113, ack 2766104306, win 254, options [nop,nop,TS val 26311210 ecr 26310630], length 351: HTTP, length: 351 POST / HTTP/1.1 Content-Length: 279 Content-Type: application/json { "arguments": { "expire": 1572335075, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:33", "ip-address": "172.18.1.1", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http] 06:44:35.315597 IP (tos 0x0, ttl 64, id 2855, offset 0, flags [DF], proto TCP (6), length 208) 172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x0bf1), seq 1:157, ack 351, win 235, options [nop,nop,TS val 26311210 ecr 26311210], length 156: HTTP, length: 156 HTTP/1.1 200 OK Content-Length: 48 Content-Type: application/json Date: Tue, 29 Oct 2019 06:44:35 GMT [ { "result": 0, "text": "IPv4 lease added." } ][!http] 06:44:35.315738 IP (tos 0x0, ttl 64, id 57518, offset 0, flags [DF], proto TCP (6), length 52) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xd5cc), ack 157, win 262, options [nop,nop,TS val 26311211 ecr 26311210], length 0 06:44:38.012408 IP (tos 0x0, ttl 64, id 57519, offset 0, flags [DF], proto TCP (6), length 403) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0xfd19), seq 351:702, ack 157, win 262, options [nop,nop,TS val 26311480 ecr 26311210], length 351: HTTP, length: 351 POST / HTTP/1.1 Content-Length: 279 Content-Type: application/json { "arguments": { "expire": 1572335077, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:34", "ip-address": "172.18.1.2", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http] 06:44:38.015389 IP (tos 0x0, ttl 64, id 2856, offset 0, flags [DF], proto TCP (6), length 208) 172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x07cf), seq 157:313, ack 702, win 243, options [nop,nop,TS val 26311480 ecr 26311480], length 156: HTTP, length: 156 HTTP/1.1 200 OK Content-Length: 48 Content-Type: application/json Date: Tue, 29 Oct 2019 06:44:38 GMT [ { "result": 0, "text": "IPv4 lease added." } ][!http] 06:44:38.015425 IP (tos 0x0, ttl 64, id 57520, offset 0, flags [DF], proto TCP (6), length 52) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xd1ae), ack 313, win 270, options [nop,nop,TS val 26311480 ecr 26311480], length 0 06:44:40.625311 IP (tos 0x0, ttl 64, id 57521, offset 0, flags [DF], proto TCP (6), length 176) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x58cc (incorrect -> 0x473e), seq 702:826, ack 313, win 270, options [nop,nop,TS val 26311745 ecr 26311480], length 124: HTTP, length: 124 POST / HTTP/1.1 Content-Length: 53 Content-Type: application/json { "command": "ha-heartbeat", "service": [ "dhcp4" ] }[!http] 06:44:40.632575 IP (tos 0x0, ttl 64, id 2857, offset 0, flags [DF], proto TCP (6), length 303) 172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x594b (incorrect -> 0x866a), seq 313:564, ack 826, win 243, options [nop,nop,TS val 26311746 ecr 26311745], length 251: HTTP, length: 251 HTTP/1.1 200 OK Content-Length: 142 Content-Type: application/json Date: Tue, 29 Oct 2019 06:44:40 GMT [ { "arguments": { "date-time": "Tue, 29 Oct 2019 06:44:40 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ][!http] 06:44:40.632606 IP (tos 0x0, ttl 64, id 57522, offset 0, flags [DF], proto TCP (6), length 52) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xce1a), ack 564, win 279, options [nop,nop,TS val 26311746 ecr 26311746], length 0 06:44:41.628464 IP (tos 0x0, ttl 64, id 57523, offset 0, flags [DF], proto TCP (6), length 403) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0xfb10), seq 826:1177, ack 564, win 279, options [nop,nop,TS val 26311845 ecr 26311746], length 351: HTTP, length: 351 POST / HTTP/1.1 Content-Length: 279 Content-Type: application/json { "arguments": { "expire": 1572335080, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:35", "ip-address": "172.18.1.3", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http] 06:44:41.633534 IP (tos 0x0, ttl 64, id 2858, offset 0, flags [DF], proto TCP (6), length 208) 172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x0080), seq 564:720, ack 1177, win 252, options [nop,nop,TS val 26311846 ecr 26311845], length 156: HTTP, length: 156 HTTP/1.1 200 OK Content-Length: 48 Content-Type: application/json Date: Tue, 29 Oct 2019 06:44:41 GMT [ { "result": 0, "text": "IPv4 lease added." } ][!http] 06:44:41.633621 IP (tos 0x0, ttl 64, id 57524, offset 0, flags [DF], proto TCP (6), length 52) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xcb4f), ack 720, win 287, options [nop,nop,TS val 26311846 ecr 26311846], length 0 06:44:42.637356 IP (tos 0x0, ttl 64, id 43254, offset 0, flags [DF], proto TCP (6), length 176) 172.18.0.2.42824 > 172.18.0.3.8080: Flags [P.], cksum 0x58cc (incorrect -> 0x7e2b), seq 1837942929:1837943053, ack 2537261301, win 254, options [nop,nop,TS val 26311946 ecr 26310830], length 124: HTTP, length: 124 POST / HTTP/1.1 Content-Length: 53 Content-Type: application/json { "command": "ha-heartbeat", "service": [ "dhcp4" ] }[!http] 06:44:42.640120 IP (tos 0x0, ttl 64, id 59900, offset 0, flags [DF], proto TCP (6), length 303) 172.18.0.3.8080 > 172.18.0.2.42824: Flags [P.], cksum 0x594b (incorrect -> 0xb605), seq 1:252, ack 124, win 227, options [nop,nop,TS val 26311946 ecr 26311946], length 251: HTTP, length: 251 HTTP/1.1 200 OK Content-Length: 142 Content-Type: application/json Date: Tue, 29 Oct 2019 06:44:42 GMT [ { "arguments": { "date-time": "Tue, 29 Oct 2019 06:44:42 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ][!http] 06:44:42.640722 IP (tos 0x0, ttl 64, id 43255, offset 0, flags [DF], proto TCP (6), length 52) 172.18.0.2.42824 > 172.18.0.3.8080: Flags [.], cksum 0x5850 (incorrect -> 0x01b8), ack 252, win 262, options [nop,nop,TS val 26311946 ecr 26311946], length 0## kea_2 root@d864a28cbf84:/# tcpdump -i eth0 port 8080 -v -n tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 06:44:35.313767 IP (tos 0x0, ttl 64, id 57517, offset 0, flags [DF], proto TCP (6), length 403) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0x066f), seq 220565762:220566113, ack 2766104306, win 254, options [nop,nop,TS val 26311210 ecr 26310630], length 351: HTTP, length: 351 POST / HTTP/1.1 Content-Length: 279 Content-Type: application/json { "arguments": { "expire": 1572335075, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:33", "ip-address": "172.18.1.1", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http] 06:44:35.315569 IP (tos 0x0, ttl 64, id 2855, offset 0, flags [DF], proto TCP (6), length 208) 172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x0bf1), seq 1:157, ack 351, win 235, options [nop,nop,TS val 26311210 ecr 26311210], length 156: HTTP, length: 156 HTTP/1.1 200 OK Content-Length: 48 Content-Type: application/json Date: Tue, 29 Oct 2019 06:44:35 GMT [ { "result": 0, "text": "IPv4 lease added." } ][!http] 06:44:35.315755 IP (tos 0x0, ttl 64, id 57518, offset 0, flags [DF], proto TCP (6), length 52) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xd5cc), ack 157, win 262, options [nop,nop,TS val 26311211 ecr 26311210], length 0 06:44:38.012495 IP (tos 0x0, ttl 64, id 57519, offset 0, flags [DF], proto TCP (6), length 403) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0xfd19), seq 351:702, ack 157, win 262, options [nop,nop,TS val 26311480 ecr 26311210], length 351: HTTP, length: 351 POST / HTTP/1.1 Content-Length: 279 Content-Type: application/json { "arguments": { "expire": 1572335077, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:34", "ip-address": "172.18.1.2", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http] 06:44:38.015323 IP (tos 0x0, ttl 64, id 2856, offset 0, flags [DF], proto TCP (6), length 208) 172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x07cf), seq 157:313, ack 702, win 243, options [nop,nop,TS val 26311480 ecr 26311480], length 156: HTTP, length: 156 HTTP/1.1 200 OK Content-Length: 48 Content-Type: application/json Date: Tue, 29 Oct 2019 06:44:38 GMT [ { "result": 0, "text": "IPv4 lease added." } ][!http] 06:44:38.015447 IP (tos 0x0, ttl 64, id 57520, offset 0, flags [DF], proto TCP (6), length 52) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xd1ae), ack 313, win 270, options [nop,nop,TS val 26311480 ecr 26311480], length 0 06:44:40.625346 IP (tos 0x0, ttl 64, id 57521, offset 0, flags [DF], proto TCP (6), length 176) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x58cc (incorrect -> 0x473e), seq 702:826, ack 313, win 270, options [nop,nop,TS val 26311745 ecr 26311480], length 124: HTTP, length: 124 POST / HTTP/1.1 Content-Length: 53 Content-Type: application/json { "command": "ha-heartbeat", "service": [ "dhcp4" ] }[!http] 06:44:40.632539 IP (tos 0x0, ttl 64, id 2857, offset 0, flags [DF], proto TCP (6), length 303) 172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x594b (incorrect -> 0x866a), seq 313:564, ack 826, win 243, options [nop,nop,TS val 26311746 ecr 26311745], length 251: HTTP, length: 251 HTTP/1.1 200 OK Content-Length: 142 Content-Type: application/json Date: Tue, 29 Oct 2019 06:44:40 GMT [ { "arguments": { "date-time": "Tue, 29 Oct 2019 06:44:40 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ][!http] 06:44:40.632623 IP (tos 0x0, ttl 64, id 57522, offset 0, flags [DF], proto TCP (6), length 52) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xce1a), ack 564, win 279, options [nop,nop,TS val 26311746 ecr 26311746], length 0 06:44:41.628608 IP (tos 0x0, ttl 64, id 57523, offset 0, flags [DF], proto TCP (6), length 403) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0xfb10), seq 826:1177, ack 564, win 279, options [nop,nop,TS val 26311845 ecr 26311746], length 351: HTTP, length: 351 POST / HTTP/1.1 Content-Length: 279 Content-Type: application/json { "arguments": { "expire": 1572335080, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:35", "ip-address": "172.18.1.3", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http] 06:44:41.633418 IP (tos 0x0, ttl 64, id 2858, offset 0, flags [DF], proto TCP (6), length 208) 172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x0080), seq 564:720, ack 1177, win 252, options [nop,nop,TS val 26311846 ecr 26311845], length 156: HTTP, length: 156 HTTP/1.1 200 OK Content-Length: 48 Content-Type: application/json Date: Tue, 29 Oct 2019 06:44:41 GMT [ { "result": 0, "text": "IPv4 lease added." } ][!http] 06:44:41.633649 IP (tos 0x0, ttl 64, id 57524, offset 0, flags [DF], proto TCP (6), length 52) 172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xcb4f), ack 720, win 287, options [nop,nop,TS val 26311846 ecr 26311846], length 0 06:44:42.637308 IP (tos 0x0, ttl 64, id 43254, offset 0, flags [DF], proto TCP (6), length 176) 172.18.0.2.42824 > 172.18.0.3.8080: Flags [P.], cksum 0x58cc (incorrect -> 0x7e2b), seq 1837942929:1837943053, ack 2537261301, win 254, options [nop,nop,TS val 26311946 ecr 26310830], length 124: HTTP, length: 124 POST / HTTP/1.1 Content-Length: 53 Content-Type: application/json { "command": "ha-heartbeat", "service": [ "dhcp4" ] }[!http] 06:44:42.640413 IP (tos 0x0, ttl 64, id 59900, offset 0, flags [DF], proto TCP (6), length 303) 172.18.0.3.8080 > 172.18.0.2.42824: Flags [P.], cksum 0x594b (incorrect -> 0xb605), seq 1:252, ack 124, win 227, options [nop,nop,TS val 26311946 ecr 26311946], length 251: HTTP, length: 251 HTTP/1.1 200 OK Content-Length: 142 Content-Type: application/json Date: Tue, 29 Oct 2019 06:44:42 GMT [ { "arguments": { "date-time": "Tue, 29 Oct 2019 06:44:42 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ][!http] 06:44:42.640696 IP (tos 0x0, ttl 64, id 43255, offset 0, flags [DF], proto TCP (6), length 52) 172.18.0.2.42824 > 172.18.0.3.8080: Flags [.], cksum 0x5850 (incorrect -> 0x01b8), ack 252, win 262, options [nop,nop,TS val 26311946 ecr 26311946], length 0上記より,
kea_1
からkea_2
に向けて{ "arguments": { "expire": 1572335075, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:33", "ip-address": "172.18.1.1", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }というjsonフォーマットで
lease-update
が送出され,{ "result": 0, "text": "IPv4 lease added." }という形で応答していることがわかる.
Kea DHCPにおいてはこのようにREST APIを用いてlease-update
の交換を行うことが特徴の一つであると言えるだろう.まとめ
というわけで,無事Kea DHCPを使って
hot-standby
モードのHA構成を組むことができました.
本記事では以下のことを実施しました.
- ISC DHCPの後継であるKea DHCPについて紹介し,冗長構成の違いを紹介した.
- ISCのKea DHCPを用いて
hot-standby
モードのHA構成を組むことについて紹介した.- hot-standyモードのHAにおいて実際にクライアントを模擬してDHCPによるアドレスリースを行い,2つのサーバの応答の違いをパケットキャプチャによって確認した.
- HA構成における2サーバ間の
lease-update
について,REST APIによって行われていることを確認した.今後は,
load-balancing
モードでのHA構成の構築と,hot-standby
構成との振る舞いの違いや,HA構成を取ることによるパフォーマンス影響,HA構成で本当に冗長化ができているのか,等について見てみようと考えています.References
- 投稿日:2019-11-25T17:35:21+09:00
Docker と ECS Scheduled Taskを使って定期的におじさんからメッセージが届く仕組みを作る??
皆さんはおじさんは好きカナ?( ̄ー ̄?
僕は好きだヨ!!✋( ̄▽ ̄ ♥ ?
定期的におじさんからメッセージが届いたら素敵じゃないカナ????
と言うわけで作ってみタヨ!!(^з<)❗?✋?概要
ECS Scheduled Taskを使っておじさんから定期的に Slack メッセージが届く仕組みを作ります。
おじさんのメッセージ生成には Ojichat を使わせていただきました。環境構築
AWS CLI用のIAMユーザーの作成
AWS CLI用のIAMユーザーを作成します。以下の記事を参考にしながらIAMユーザーを作成します。
今回は Terraform の例もあるため、 Admin権限を持つユーザーを作成しました。
イメージをプッシュする権限のみ必要な場合はecr:GetAuthorizationToken
を付与してください。
SecretAccessKey
は再発行されないため、分からなくなってしまったらユーザーを再作成する必要があります。AWS CLI のインストール
AWS CLI をインストールし、設定を行います。インストール方法は下記記事を参考にしてください
macOS に AWS CLI をインストールする
AWS CLI の設定インストールが完了したら AWS CLI の設定を行い、AWS CLI から S3 のバケット一覧を取得できることを確認します。(動作確認)
$ aws --version $ aws configure $ aws s3api list-buckets # S3 のバケット一覧を表示するコマンドSlack に Ojichat の絵文字
:ojichat:
を作成する
:ojichat:
を追加するとテンション上がります。??Slack のWebHookURL を作成する
おじさんからの Slackメッセージを受け取るのに必要です。
WebHook の設定を行うことで、curlコマンドで簡単にメッセージを Slack に送信することができます。イメージを作成
おじさんメッセージの生成 & Slack にメッセージを飛ばす Docker イメージを作ります。
同ディレクトリ にDockerfile
とentrypoint.sh
を作成し、以下のコマンドでビルドします。
entrypoint.sh
中の {your_channnel_name} にはおじさんからのメッセージを受け取りたいチャンネル名を入力してください。
「おのじゅん」は私のあだ名です。DockerfileFROM golang:1.13.4-stretch RUN go get -u github.com/greymd/ojichat COPY entrypoint.sh / ENTRYPOINT ["/entrypoint.sh"]entrypoint.sh#!/bin/bash COMMENT=$(ojichat $NAME) echo $COMMENT curl -X POST --data-urlencode "payload={\"channel\": \"##{your_channnel_name}\", \"username\": \"Ojichat\", \"text\": \"$COMMENT\", \"icon_emoji\": \":ojichat:\"}" $WEB_HOOK_URL$ docker build -t ojichat:latest --no-cache .ビルドができたら以下のコマンドでおじさんからSlackにメッセージが届くことを確認します。
NAME
にはおじさんに呼んで欲しい名前を、WEB_HOOK_URL
には Slack のWebHookのURLを入力します。$ docker run --rm -e NAME=おのじゅん -e WEB_HOOK_URL=https://hooks.slack.com/services/xxx/yyy/zzzz ojichat:latest無事おじさんからメッセージを受け取ることができました。
おじさんを ECR にプッシュする
おじさんイメージがきちんと動くことがわかったので、次はおじさんイメージを ECR にプッシュします。
GUI からおじさんを格納するリポジトリを作成します。
プッシュコマンドを表示
ボタンを押すとイメージのビルドからプッシュまでの手順が表示されます。
その手順に従ってイメージを ECR にプッシュします。$(aws ecr get-login --no-include-email --region ap-northeast-1) docker build -t ojichat . docker tag onojun-ojichat:latest {YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/onojun-ojichat:latest docker push {YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/onojun-ojichat:latestおじさんを動かすロールを作成する
おじさんイメージは Fargate 上で動かします。
必要な権限を持つロールを作成します。
ojichat-policy
を作成します。{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*" } ] }次に
ojichat-role
を作成し、ojichat-policy
をアタッチさせ、信頼関係を編集します。{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ecs.amazonaws.com", "ecs-tasks.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] }おじさんタスク定義を作成する
おじさんのタスク定義を作成します。
必要な環境変数を設定します。
今回はハードコードしてしまいましたが、WEB_HOOK_URL
などのクレデンシャル情報はParameterStore
に格納し、それをFromValue
で参照するとタスク定義に書かずに済みます。おじさんクラスターを作成し、おじさんタスクを実行する
ojichat-cluster
を作成し、作成したタスク定義ojichat-task
からタスクを実行します。おじさんを ScheduledTask で動かす
Fargate にいるおじさんからメッセージが届いたら、次はおじさんのメッセージを自動化します。
スケジュールタスクを設定することで実現することができます。僕はおじさんが好きなので1分ごとにメッセージが飛ぶようにしました。
Terraform
######################## ## Credential Infos ######################## provider "aws" { access_key = local.access_key secret_key = local.secret_key region = "ap-northeast-1" } ######################## ## ECR ######################## # ECS Repository resource "aws_ecr_repository" "repository" { name = "ojichat" } # Repositry Policy # Permit pull image resource "aws_ecr_repository_policy" "repository_policy" { repository = aws_ecr_repository.repository.name policy = data.aws_iam_policy_document.repository_policy.json } data "aws_iam_policy_document" "repository_policy" { statement { effect = "Allow" actions = [ "ecr:GetDownloadUrlForLayer", "ecr:BatchCheckLayerAvailability", "ecr:BatchGetImage" ] principals { type = "*" identifiers = ["*"] } } } ######################## ## IAM ######################## # IAM Policy resource "aws_iam_policy" "policy" { name = "ojicaht-policy" description = "for ojichat" policy = data.aws_iam_policy_document.policy.json } data "aws_iam_policy_document" "policy" { statement { effect = "Allow" actions = [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "ecs:RunTask", "logs:CreateLogStream", "logs:PutLogEvents", "iam:PassRole" ] resources = ["*"] } } # IAM Role resource "aws_iam_role" "role" { name = "ojichat-role" description = "role for ojichat" assume_role_policy = data.aws_iam_policy_document.role.json } data "aws_iam_policy_document" "role" { statement { actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = ["ecs.amazonaws.com", "ecs-tasks.amazonaws.com", "events.amazonaws.com"] } } } resource "aws_iam_role_policy_attachment" "schedule_policy_attachment" { role = aws_iam_role.role.name policy_arn = aws_iam_policy.policy.arn } resource "aws_iam_role_policy_attachment" "event_role_policy_attachment" { role = aws_iam_role.role.name policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceEventsRole" } ######################## ## ECS ######################## # cluster resource "aws_ecs_cluster" "cluster" { name = "ojichat-cluster" } # task difinition resource "aws_ecs_task_definition" "task_definition" { family = "ojichat-task" task_role_arn = aws_iam_role.role.arn container_definitions = data.template_file.container_definitions.rendered network_mode = "awsvpc" cpu = 256 memory = 512 requires_compatibilities = ["FARGATE"] execution_role_arn = aws_iam_role.role.arn } data "template_file" "container_definitions" { template = <<EOF [ { "cpu": 0, "environment": [ { "name": "WEB_HOOK_URL", "value": "${local.web_hook_url}" }, { "name": "NAME", "value": "おのじゅん" } ], "name": "ojichat", "image": "${aws_ecr_repository.repository.repository_url}", "logConfiguration": { "logDriver": "awslogs", "secretOptions": null, "options": { "awslogs-group": "/ecs/ojichat-task", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "ecs" } }, "essential": true } ] EOF } # scueduled task resource "aws_cloudwatch_event_rule" "schedule_rule" { name = "ojichat-event" schedule_expression = "cron(* * * * ? *)" is_enabled = true } resource "aws_cloudwatch_event_target" "fargate_scheduled_task" { rule = aws_cloudwatch_event_rule.schedule_rule.name arn = aws_ecs_cluster.cluster.arn role_arn = aws_iam_role.role.arn ecs_target { task_definition_arn = aws_ecs_task_definition.task_definition.arn task_count = 1 launch_type = "FARGATE" network_configuration { subnets = [local.subnets_id] assign_public_ip = true } } }参考
greymd/ojichat
Amazon Elastic Container Service とは
Amazon ECS クラスター
Amazon ECS タスク定義
Amazon ECS サービス
- 投稿日:2019-11-25T17:26:39+09:00
nuxtで作った静的ファイルをgolangのechoでホスティングする。
背景
機能の内容は言えませんが、外部APIを利用した既存の機能の簡易版のツールを作りたい。そういった依頼が舞い込んできました。
すでにその機能は提供されていますが、画面仕様のもと多くの項目の入力が必要で、客先でさくっと処理させるには不向きでした。
そのため、既存機能の登録機能のみを切り出してほしいとのことでした。
二つ返事で了承。
今後のことを考えフロントエンドは別プロジェクトにし、APIは既存のものを利用。
外部のAPIはフロントエンドでのスクリプト読み込み後のAjax送信とサーバーサイドでのポストの処理があり、特定のホスト名と特定のIPにしか対応してませんでした。
それは困った。
ということで、nuxtで静的サイトとしてgenerateしたものを既存のプロジェクトでホスティングさせてしまえば、ホスト名とIPの課題は解決するということでやり方を書いておきます。※ソースは抜粋しか載せられないのでその点はご理解ください。
流れ
- nuxtでプロジェクトを作成しgenerateすることで静的サイトへ
- golangとechoでできた既存システムでgenerateした静的サイトをホスティング
- リリース方法を検討していい感じにリリースできるようにする。
nuxtでプロジェクトを作成しgenerateすることで静的サイトへ
- ソースを載せられないのでいい感じにご自身で実装してください。
静的サイトへgenerate
- なんのこっちゃない。あまりやったことないがとりあえずlocalで
yarn generate
- generate?めんどくさいのでpackage.jsonをいじって
package.json"gen": "nuxt generate",yarn gen
- distに静的なファイルが出力されました。 静的なファイルっていうくらいなので、要はHTMLを生で書いたのと同じ状態ですよね?ということで、ブラウザでこのファイルを開く
- おや?スクリプト(画面のイベント)が発火してない?そんなことある? どうやらnuxt generateしても、ルート情報が正しくないと、生成されたスクリプトを読み込めないらしい。 この場合だと当然、/hoge/hoge/index.htmlでアクセスしてる。少し調べるとわかりますが、どうやらgithubpageやnetlifyやS3でホスティングしないと正常に動かないらしい。 なるほど。
じゃあ、当社で使っているbacklogで「ブラウザで直接開く」なるものがあるので、まずはそいつでホスティングさせてみましょう。
backlog「ブラウザで直接開く」で開いても正しく動かない。
なるほど。URL見ればわかるが、これはHTMLをただブラウザで開いてるだけか。。。ホスティングサービスとは別物。。。
gitと名乗る以上極力githubに寄せてほしいところだが、できないものはできないのでどうしようもない。じゃあ、githubでpage作るか。
はい、そうです。金払わんとできない。金はない。やめる。じゃあ、いったんnetlifyで。
いける!どうやらホスティングさせてあげれば正しく動くみたい。ということが分かったので、nuxtはこんなところにしておく。ホスティングする際のエントリーポイントをルートに追加
http://hostname/nuxtprojectで受ける予定なので、nuxt.config.jsに以下を追加router: { base: '/nuxtproject' }golangとechoでできた既存システムでgenerateした静的サイトをホスティング
- まずはgitから落としてプロジェクトへぶっこむ
cd echoproject git clone <nuxtprojectURL>
echoのプロジェクト直下にぶっこむ。
depやglideで入れたかったのですが、なかなかてこずってできなかったので、直接ぶっこむ。
- 次にgenerateする
cd nuxtproject yarn gen
後で、distもリポジトリにぶっこむつくりにしますが、いったんはこんな感じでOK.
- ホスティングする
非常に簡単。やっぱりFWって素晴らしい。
e.Static("/nuxtproject", "nuxtproject/dist")これだけでOK。
- nuxtprojectを編集した際は毎回cloneしてgenerateする必要があるよ。
こっからいろいろ考える。
- パッケージ化しようとしたけど、うまくできなかったので、以下の流れで本番のモジュールに組み込む
- distをリポジトリ保存する。
- echoprojectのデプロイ時にcloneする。
- 環境により変更すべき値を考慮する。
distをリポジトリ保存する。
.gitignoreでdistを外し、yarn generateした後にpushする。
さすがに毎回手でやるのはだるい。本当ならビルド環境用意してそいつにやらせてあげればいいのだけれども、サーバー用意するのがめんどいうえ、金がない。
なのでローカルで必ずやるようにする。どうやらgitの機能にHookがあるらしい。
コミット前、プッシュ前様々なタイミングでフックができるらしいが、今回はコミットする前を選択。
.git/hooks/pre-commitにコマンド書いとけばいいらしい。#!/bin/sh yarn yarn gen git add . exit 0
- インストールして静的なファイルを生成、できたファイルを管理対象にして終わり。pushは自分でやる これで常にdistが最新される。
echoprojectのデプロイ時にcloneする。
本来ならここでビルドすればよいのだが、dokerのイメージにnuxtとnodeとnpmとyarnを入れるのは避けたかったので、クローンするだけ。その際今回のnuxtprojectはパブリックなリポジトリではないので、いじいじする。
gitはnetrcの認証情報をもとにリポジトリからとってくることができるらしい。じゃあ簡単だ。
echoprojectにechoproject_nutrcをまずは作ります。# 自分の情報を設定してね。 machine <git_host> login <git_user_id> password <git_user_pwd>これをdockerfileで ~/.netrcにしてやればOK
RUN cp echoproject_nutrc ~/.netrc環境により変更すべき値を考慮する。
当然開発環境と本番環境は分かれてまっせということ。
echoprojectではENVという環境変数で振り分けてますが、nuxtはどうしましょう。
よし、どちらもgenerateしてホスティングするdistの名称を変えればOKじゃね?ということでそのようにします。
nuxt.config-dev.jsを作る
nuxt.config-dev.jsでgenerateの際の出力フォルダを指定
generate: { subFolders: false, dir: './dist_dev' },
- package.jsonに下記を追加
"gen-dev":"nuxt generate --config-file nuxt.config-dev.js",.git/hooks/pre-commitにyarn gen-devを追加
これでdistとdist_devができるので後はechoprojectでdistをホスティングするのかdist_devをホスティングするの指定してあげればOK。
非常にむりやりではありますが、今回はこのようにしましたとさ。
- 投稿日:2019-11-25T16:21:55+09:00
【Docker-02】Docker for Macで使い捨てMySQL環境の作成
はじめに
SQLの練習をしたい時にローカルで使い捨てできる環境あると良いなーと思っていたらDockerで簡単に実現できそうなのでやってみました。
仮想環境の知識が乏しい時はAWSに立てるか?とか考えてたので正気じゃないですね本記事の執筆にあたり、「Dockerで使い捨てのMySQL環境を用意する。事前データを投入して起動する。」を出典とさせて頂きました。この場を借りてお礼申し上げます!
やってみる
Docker Desctop for Macのインストール
公式からインストールします。
https://docs.docker.com/docker-for-mac/install/書いてて思い出しましたが、確かブラウザでDockerの起動とか練習するSaasがあったような。。?
そちらでもMySQL環境作成できるかもしれませんね。本記事では割愛させて頂きます。コンテナの起動
MySQL公式のイメージを使用するので、いきなり起動。便利すぎる。
コマンドは改行せずに1行で書き切ってもOKです。
パスワードは確か英小文字大文字数字の8行とか9行じゃないとダメだったような気がしますが、適当に入力してもログインできました。なんでだwサイトに倣い、ポートは43306をマッピング。3306:3306でも良いと思います。
$ docker container run --rm -d \ > -e MYSQL_ROOT_PASSWORD=okome \ > -p 43306:3306 --name mysql mysql:5.7mysqlに接続
ローカルのシェルでmysqlしたら怒られた。
「mysql」コンテナのシェルを起動した後にmysqlに接続するのが正解のようです。
パスワードはコンテナ起動時に指定した「okome」を入力。$ docker exec -it mysql /bin/bash # mysql -uroot -p Enter password: mysql>うおお動いた!早い!かんたん!
特に.mysqlは指定していないのでまっさらの初期状態ですが、勉強目的の使い捨て環境と考えるなら全然問題ないですね。捗るぜ。。mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.00 sec)事前にデータ投入して起動する
「Dockerで使い捨てのMySQL環境を用意する。事前データを投入して起動する。」のリンクにあった「事前にデータ投入をした MySQL Docker イメージを作る場合は /docker-entrypoint-initdb.d を活用すると便利」を拝読すると、docker-entrypoint-initdb.d/にテーブルや実データを定義したgzやgzipを置けばコンテナ起動時に読み込んでくれるとのこと。
Dockerfileをサイト通りに記述し、公式からダウンロードしたworld.sql.gzを同じディレクトリに配置します。
$ cat Dockerfile FROM mysql:5.7 COPY world.sql.gz /docker-entrypoint-initdb.d/world.sql.gz $ ls -l Dockerfile world.sql.gz配置後はイメージをビルドし、起動。
$ docker build -t mysql-image . $ docker container run -e MYSQL_ROOT_PASSWORD=okome --name mysqlserver -d mysql-imageシェルを起動してmysqlに接続。
world databaseできとるやん。。素晴らしすぎる。$ docker container exec -it mysqlserver /bin/bash # mysql -uroot -p Enter password: mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | world | +--------------------+ 5 rows in set (0.00 sec) mysql> use world Database changed mysql> show tables; +-----------------+ | Tables_in_world | +-----------------+ | city | | country | | countrylanguage | +-----------------+ 3 rows in set (0.01 sec)おわりに
実際に手を動かすと、Dockerの可搬性の高さを実感できますね。
DB周りの知識が弱いんで、これを使ってガリガリ手を動かします!オプションも散らかってきたので整理しなきゃですね。
あと薄々気づいてはいたのですが、dockerコマンドのcontainerって省略できたんですね。
それが一番有益だったかもしれないw簡単ですが以上です。
- 投稿日:2019-11-25T12:11:06+09:00
【Docker】とにかく触ってみる(06:Docker Machine作成/失敗版)
最初に
まだ基本的なことがあやふやなので、CentOS上にDocker環境を構築するところから再開してみます。
10回くらい構築すれば覚えると思うけど、、、
Dockerのインストール
この作業は流石に慣れて来ました。
teratermsudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io sudo systemctl start docker sudo systemctl enable docker docker --version実行結果Docker version 19.03.5, build 633a0eaDockerイメージの確認
teratermsudo docker images実行結果REPOSITORY TAG IMAGE ID CREATED SIZEubuntuの取得
前回PUSHしたubuntuをPULLしてみます。
teratermsudo docker pull wakataka3/dummy_ubuntu:ver191124実行結果ver191124: Pulling from wakataka3/dummy_ubuntu 7ddbc47eeb70: Pull complete c1bbdc448b72: Pull complete 8c3b70e39044: Pull complete 45d437916d57: Pull complete Digest: sha256:134c7fe821b9d359490cd009ce7ca322453f4f2d018623f849e580a89a685e5d Status: Downloaded newer image for wakataka3/dummy_ubuntu:ver191124 docker.io/wakataka3/dummy_ubuntu:ver191124teratermsudo docker images実行結果REPOSITORY TAG IMAGE ID CREATED SIZE wakataka3/dummy_ubuntu ver191124 775349758637 3 weeks ago 64.2MB???
IMAGE IDが前回コミットしたものと異なったので確認したところ、思い切りubuntuにタグ付けしてからPUSHしていました。。。こういう地味なミスが毎回あるので、実機確認しておかないと怖いんですよね。。。
teratermsudo docker tag ubuntu wakataka3/dummy_ubuntu:ver191124 sudo docker push wakataka3/dummy_ubuntu:ver191124Docker Machineについて
Docker Machineとは、Dockerエンジンを搭載した仮想マシンの作成、起動、停止、再起動などをコマンドラインから実行できるツールのこと。
本稿ではDocker Machineをインストールしていないので、エラーが出てきました。teratermsudo docker-machine ls実行結果sudo: docker-machine: command not foundそのため、別のEC2を起動し、Docker Machineをインストールしてみます。
毎回新しく設定しないと、どの操作が有効だったかの切り分けが出来ないのでサクッと作れるAWSはここでも重宝しています。公式ページからコマンドをコピー&ペーストします。
teratermcurl -L https://github.com/docker/machine/releases/download/v0.16.2/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine && chmod +x /tmp/docker-machine && sudo cp /tmp/docker-machine /usr/local/bin/docker-machine docker-machine version実行結果docker-machine version 0.16.2, build bd45ab13teratermdocker-machine ls実行結果NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORSDocker Machine作成(失敗)
teratermdocker-machine create --driver amazonec2 \ --amazonec2-access-key xxxxxxxxxxxxxxxxxxxx \ --amazonec2-secret-key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ --amazonec2-vpc-id vpc-xxxxxxxxxxxxxxxxx \ --amazonec2-subnet-id subnet-xxxxxxxxxxxxxxxxx \ --amazonec2-region ap-northeast-1 \ --amazonec2-ami ami-0c1c738e580f3e01f \ aws-waka-takaその他オプションについてはDockerドキュメントを参照します。
実行結果Running pre-create checks... Creating machine... (aws-waka-taka) Launching instance... Waiting for machine to be running, this may take a few minutes... Detecting operating system of created instance... Waiting for SSH to be available... Error creating machine: Error detecting OS: Too many retries waiting for SSH to be available. Last error: Maximum number of retries (60) exceededSSHの接続時間超過はしていましたが、AWSのEC2インスタンス上では問題なく起動していました。
念の為、「docker ls」で確認してみます。teratermdocker-machine ls実行結果NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS aws-waka-taka - amazonec2 Running tcp://3.112.201.223:2376 Unknown Unable to query docker version: Cannot connect to the docker engine endpointここで行き詰まりました。。。
諦めかけて1個ずつ確認していったところ、キーペアが「aws-waka-taka」になっている。
こんなキーペアは作った記憶がにないので、Docker Machine作成時にキーペアを指定しなかったから?腑に落ちないけれど、それしか心当たりがないので、キーペアを指定して再度作成。
Docker Machine作成リトライ(失敗)
teratermdocker-machine create --driver amazonec2 \ --amazonec2-access-key xxxxxxxxxxxxxxxxxxxx \ --amazonec2-secret-key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ --amazonec2-vpc-id vpc-xxxxxxxxxxxxxxxxx \ --amazonec2-subnet-id subnet-xxxxxxxxxxxxxxxxx \ --amazonec2-region ap-northeast-1 \ --amazonec2-ssh-keypath xxxxxxx \ --amazonec2-ami ami-0c1c738e580f3e01f \ aws-waka-taka2ひとまず、本件は30分考えて分からなからなかったので保留します。
新規環境作り直してもう一度、どこが駄目だったか見直してみます。
そう思ったけど、Docker Machineは前回飛ばしたAutomated Buildと違ってクリティカルなので、もう少し調べてみようと思います。最後に
本稿は失敗手順の記事ですが、もう少し勉強してどこが悪かったのか見極めるために残しておきます。
この手の失敗って絶対にどこかでショボいミスをしているだけだろうから…ちなみに、参考書ではVirtual Boxを使っていたので、次回はCentOSにVirtual Boxをインストールして試してみようと思います。
EC2にVirtual Boxって微妙な気がしますが、独自色出せるほどの知識がないので、愚直にやっていくことにします。
- 投稿日:2019-11-25T11:53:52+09:00
DockerでGeforceを使ってSpleeterを回す
Spleeter(https://github.com/deezer/spleeter) という、python+Tensorflowを使った、音声分離アプリケーションがあります。これを使うと楽曲のファイルからヴォーカルだけを抜き出したりできます。
使い方も上のサイトやこちらに書いてあったりするのですが、nvidia-dockerが古いだとか、GPU使ってないだとかで決め手に欠けています。ですので今まとも?な方法のメモ書きを残します。(本当は漏れのない手順書を書きたいとこでしたが、実施から時間が経って記憶から抜けている部分もあります・・)
自分で構築するのが面倒な人は、参考の1, 2を使うとよいでしょう。ただし録音に使ったデータがクラウド上に保存されるのかどうかは保証できません。
動作確認環境
- AMD Ryzen 3700X
- ASRock B450M Steel Regend
- メモリ: 32GB
- (ブランド不明)Geforce GTX 1080Ti
- Ubuntu 18.04 server
- nvidia公式driver
導入手順
nvidia driverを入れる
ホストマシン(Dockerを呼び出すマシン)にnvidiaの公式ドライバを入れます。おそらくCUDAドライバあたりも必要です。Docker ceを入れる
最新のnvidia-dockerがDocker ce 19.03を要求するので、入れます。Ubuntu標準dockerではダメです。
https://docs.docker.com/install/linux/docker-ce/ubuntu/nvidia-dockerを入れる
最近のはnvidia-docker2がdeprecatedされており、最新のnvidia-dockerではspleeterに書かれているコマンドでは実行出来ないので注意してください。
https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)spleeterを入れて使う
https://github.com/deezer/spleeter/wiki/2.-Getting-started#using-docker-image
nvidia-dockerではなく、docker run --gpus all等を使う必要があります(allは手持ちのGPUによって変えてください)。
例: docker run --gpus all -v $(pwd)/output:/output deezer/spleeter:gpu separate -i audio_example.mp3 -o /output参考
- spleeter使って分離してくれるサービス https://moises.ai/
- Spleeterを簡単に使えるGoogle colaboratoryのノートを作成しました. https://qiita.com/Ryo0o0oOO/items/65acd38f4034800388c0
- 投稿日:2019-11-25T09:25:44+09:00
openapi-generatorで手軽にスタブサーバとクライアントの生成
はじめに
手元のOASでさくっとスタブサーバやクライアントを作りたい時用に本記事をまとめました。
Web版SwaggerEditorからでもスタブサーバのソースコード一式生成してくれるのですが、
業務でNDA契約がどうのこうので使えないケースがあるみたいなのでそうゆう時に役立つかもです。今回紹介するOASのバージョンは3.0です。(OAS 3.0)
今回はDockerを使用します。
作業環境
Windows 10 pro Docker version 19.03.2 docker-compose version 1.24.1Dockerfileの準備
下記の
Dockerfile
をプロジェクト直下に用意します。FROM openjdk:8-jdk-alpine WORKDIR /app RUN apk --update add bash nodejs npm maven git RUN rm -rf /var/cache/apk/*ls RUN git clone https://github.com/openapitools/openapi-generator /generator RUN cd /generator && mvn clean package RUN npm install -g swagger-cli
- javaを使うのでJDKの入ったイメージをベースにしています。(DockerHub)
- 作業ディレクトリは
/app
直下にしています。- 自動生成ツールにOpenAPI Generatorを使用するのでCloneします。
- Mavenを使用して
openapi-generator
のJavaプログラムをビルドします。swagger-cli
はOASをYAMLからJSONに変換したりするのに便利なので入れてます。docker-compose.ymlの準備
下記の
docker-compose.yml
をプロジェクト直下に用意します。docker-compose.ymlversion: '3' services: tools: build: ./ volumes: - .:/app tty: true command: sh
- 前述で用意した
Dockerfile
をビルドしてバックグラウンドでコンテナを起動する想定で作成しています。- ホスト側のカレントディレクトリをコンテナ側の作業ディレクトリにマウントしています。
Swaggerの準備
Swaggerファイルをプロジェクト直下に用意します。
今回はサンプルとして、get
とpost
のシンプルなAPIのOASを用意しました。openapi.ymlopenapi: 3.0.0 info: title: Sample API version: 0.0.1 servers: - url: http://localhost:3000/api description: SampleApiServer tags: - name: Sample paths: /getTest: get: tags: [ "Sample" ] summary: Get test. description: Get test. operationId: getTest parameters: - in: query name: name schema: type: string responses: '200': description: Sample /postTest: post: tags: [ "Sample" ] summary: Post test. description: Post test. operationId: postTest requestBody: content: application/x-www-form-urlencoded: schema: type: object properties: name: type: string responses: '200': description: Sample実践
コンテナを起動します。(けっこう時間かかります、長くて10分くらい?
PS C:\Users\user\Desktop\project> docker-compose up -d --buildコンテナの中に入ります。
PS C:\Users\user\Desktop\project> docker-compose exec tools bashOASをYAML形式からJSON形式に変換します。
bash-4.4# swagger-cli bundle -o openapi.json openapi.ymlExpressなNodeJSスタブサーバを生成します。
bash-4.4# java -jar /generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate \ -i openapi.yml \ -g nodejs-express-server \ -o serverJavascriptなクライアントを生成します。
bash-4.4# java -jar /generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate \ -i openapi.yml \ -g javascript \ -o clientここまでやると下のような感じで、クライアントとスタブサーバが自動生成されているかと思います。
※Javascript以外の言語でも生成できるのでOpenAPI Generatorをご参照ください。
おまけ
どうせなら、SwaggerUIとSwaggerEditorもDockerで立ててしまいましょう。
下記をdocker-compose.yml
に追記します。docker-compose.ymlswagger-ui: image: swaggerapi/swagger-ui volumes: - ./openapi.yml:/usr/share/nginx/html/openapi.yml environment: API_URL: openapi.yml ports: - "9001:8080" swagger-editor: image: swaggerapi/swagger-editor ports: - "9002:8080"コンテナを起動します。
PS C:\Users\user\Desktop\project> docker-compose up -d --buildlocalhost:9001でSwagger UIに、localhost:9002でSwagger Editorにアクセスできるかと思います。
以上です!
- 投稿日:2019-11-25T08:18:55+09:00
【Heroku】デプロイ後にcode=H14 desc="No web processes running"
事象
話の流れとしてはこの記事の続きになります。
Dockerで開発環境構築をしたRailsアプリのコンテナをpushすることができたのですが、その後
$ heroku addons:create heroku-postgresql:hobby-dev $ heroku container:release "コンテナ名" $ heroku openとして、サイトを開いてみると、
とHerokuのエラーが発生していました。
解決方法
まず、画面に書かれている通り、コンソール上で
heroku logs --tail
を実行してみたところ$ heroku logs --tail 2019-09-10T22:53:00.595256+00:00 heroku[router]: at=error code=H14 desc="No web processes running" method=GET path="/" host=safe-basin-05606.herokuapp.com request_id=e797e1d4-8327-4285-b525-986ed50ea467 fwd="160.237.76.19" dyno= connect= service= status=503 bytes= protocol=https 2019-09-10T22:53:01.484857+00:00 heroku[router]: at=error code=H14 desc="No web processes running" method=GET path="/favicon.ico" host=safe-basin-05606.herokuapp.com request_id=f8c9be65-f445-4b0d-a4fa-83b27c76762f fwd="160.237.76.19" dyno= connect= service= status=503 bytes= protocol=httpsと出力されました。
ログによると
code=H14 desc="No web processes running"
が発生しているようなので、この文言で検索してみると、こちらの公式ドキュメントを発見。公式ドキュメントによると、
heroku ps:scale web=1
を叩けとのことなので、叩いてみると、$ heroku ps:scale web=1 › Warning: heroku update available from 7.26.2 to 7.29.0. Scaling dynos... ! ▸ Couldn't find that process type (web).と出力され、失敗してしまいました。
改めて、Web上でherokuのBuildlogを見てみると、
と表示されていました。
どうやら、heroku.ymlが含まれていないことが原因の模様で、解決するにはheroku.ymlを作成するか、
heroku stack:set heroku-18
を実行する必要があるとのこと。そこでまずはheroku.ymlを作成する方針をとってみました。
Buildlogに記載されていた公式ドキュメントを参考に下記のシンプルなheroku.ymlを作成しました。
heroku.ymlbuild: docker: web: Dockerfileその後
$ heroku stack:set container $ git push heroku masterを実行し、再度
heroku ps:scale web=1
を実行してみると、$ heroku ps:scale web=1 Scaling dynos... done, now running web at 1:Freeとなり成功しました。
改めてURLにアクセスしてみると、Herokuに関するエラーは消え、Railsにおいて、DBが見つからないエラーが発生していたので、公式サイト
を参考に$ heroku run rake db:migrate
としてマイグレーションを実行したところ、とうとうお馴染みの画面がお出迎えしてくれました。
原因
公式ドキュメントによると、webサイトに対して
dynos
が割り当てられていないのが原因で、H14が発生してしまう模様で、それを割り当てるためのコマンドがheroku ps:scale web=1
です。ちなみに
dynos
とは、dyno
の複数形で、公式のdyno
解説記事によると、Herokuで使用されるコンテナのことをdyno
と呼んでいるそうです。ただ、
heroku.yml
を作成するまで、heroku ps:scale web=1
が成功しなかった理由は今の所わかっていません。ご存知の方がいれば、ご教示いただきますと幸いです。また、
heroku.yml
を作成せず、Buildlogに記載されていた、heroku stack:set heroku-18
を実行したらどうなっていたかというのも気になりますので、同じ症状に出会い試された方がいれば、ご教示いただけますと幸いです。まとめ
Herokuデプロイ後に
code=H14 desc="No web processes running"
が発生したら、
heroku ps:scale web=1
を試してみてください。
このコマンドが成功しない場合は、heroku.yml
を作成した後で再度試してみてください。この記事が少しでも誰かのお役に立てれば幸いです。
最後までお読みいただきありがとうございました。参考文献
公式ドキュメント
Heroku Error Codes
https://devcenter.heroku.com/articles/error-codes#h14-no-web-dynos-runningBuilding Docker Images with heroku.yml
https://devcenter.heroku.com/articles/build-docker-images-heroku-yml#getting-startedGetting Started on Heroku with Rails 5.x
https://devcenter.heroku.com/articles/getting-started-with-rails5#migrate-your-databasedyno:Heroku プラットフォームの中核
https://jp.heroku.com/dynos
- 投稿日:2019-11-25T04:28:36+09:00
Tensorflow Dockerでjupyter notebookを使う
docker runで起動
公式サイトにあるように、jupyterのタグが付いたイメージをpullして、下記のようにrunすれば、jupyter notebookが起動する。
$ docker run -it -p 8888:8888 tensorflow/tensorflow:nightly-py3-jupyterホストのウェブブラウザで
http://127.0.0.1:8888/?token=...
を開く。
/tf
がルートとして開く。コンテナ内で bash シェル セッションを開始後に起動
コンテナ内で bash シェル セッションを開始する。
$ docker run -it -p 8888:8888 tensorflow/tensorflow:nightly-py3-jupyter bashコンテナ内で下記のコマンドを実行して、jupyter notebookが起動する。
# jupyter notebook --port 8888 --ip=0.0.0.0 --allow-rootホストのウェブブラウザで
http://127.0.0.1:8888/?token=...
を開く。
コンテナ内の任意のディレクトリをルートとして、jupyter notebookが起動することができる。(上の例では、jupyter付きのイメージを使っているが、jupyterなしのイメージを起動後にpipで入れた場合でも可能。)
参考
https://qiita.com/tand826/items/0c478bf63ead75427782
https://qiita.com/ciela/items/0e0392f600c92b93d7c6
- 投稿日:2019-11-25T01:34:53+09:00
Tensorflow(GPU) Docker環境構築
Tensorflow(GPU)のDocker環境構築に関する備忘録。
環境
Ubuntu 18.04
CPU: Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz
GPU: GeForce GTX 1080Tensorflow Dockerの要件を確認
https://www.tensorflow.org/install/docker?hl=ja#tensorflow_docker_requirements
Docker 19.03のインストール
https://docs.docker.com/install/linux/docker-ce/debian/
NVIDIA Container Toolkitのインストール
https://github.com/NVIDIA/nvidia-docker#ubuntu-16041804-debian-jessiestretchbuster
Note that with the release of Docker 19.03, usage of nvidia-docker2 packages are deprecated since NVIDIA GPUs are now natively supported as devices in the Docker runtime.
(Docker 19.03のリリースでは、NVIDIA GPUがDockerランタイムのデバイスとしてネイティブにサポートされるようになったため、nvidia-docker2パッケージの使用は推奨されないことに注意してください。)
nvidia-docker2をインストールせずに、「Quickstart」の説明に従って、nvidia-container-toolkitをインストールする。
NVIDIA Container Runtimeのインストール
InstallationとDocker Engine setupを実施。
補足
NVIDIA Container Runtimeをインストールしないと、
$ sudo docker run --runtime=nvidia --rm nvidia/cuda nvidia-smiのようにruntimeを指定して実行すると、
docker: Error response from daemon: Unknown runtime specified nvidia. See 'docker run --help'.とエラーが表示される。
Why do I get the error Unknown runtime specified nvidia?
TensorFlow の Docker イメージをダウンロード
最新リリース版Tensorflow(GPU)、python3、jupyter付きのイメージをpullする例。
(py3を指定しないと、python2となるので注意。)$ docker pull tensorflow/tensorflow:latest-gpu-py3-jupyterTensorflow(GPU) Dockerの動作確認
tensorflow < 2.0のイメージの場合。
(公式ページには、こちらが載っている。2019.11.25)docker run --runtime=nvidia -it --rm tensorflow/tensorflow:latest-gpu-py3-jupyter \ python -c "import tensorflow as tf; tf.enable_eager_execution(); print(tf.reduce_sum(tf.random_normal([1000, 1000])))"tensorflow2.0<=のイメージの場合。
docker run --runtime=nvidia -it --rm tensorflow/tensorflow:latest-gpu-py3-jupyter \ python -c "import tensorflow as tf; print(tf.reduce_sum(tf.random.normal([1000, 1000])))"