AlpineでRustのバイナリを動かす

Rustのプログラムをビルドする時に静的リンクにするようにして、ビルドしてできたバイナリをDockerコンテナのAlpineで動かすまでの話です。

cargo buildについて

Rustのパッケージマネージャ兼ビルドシステムのcargoというものがあります。まぁRust書いてる人なら知らない人はいないと思います。デフォルトではcrago buildは動的リンクでバイナリファイルを作成します。確認してみましょう。

$ cargo new hello --bin
     Created binary (application) `hello` project
$ cd hello
$ cargo build --release
   Compiling hello v0.1.0 (file:///usr/src/hello)
    Finished release [optimized] target(s) in 2.78 secs
$ ./target/release/hello
Hello, world!
$ file ./target/release/hello
./target/release/hello: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV),dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9cdb401d93645d19032e0850e1b283feb270f491, not stripped
$ ldd ./target/release/hello
        linux-vdso.so.1 (0x00007ffdffb94000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f182e9ec000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f182e7e4000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f182e5c7000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f182e3b0000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f182e011000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f182ee41000)
$

さて、このバイナリをAlpineで動かしたいんですが、この超軽量のOSにはlibcが入っておらず動的ライブラリは存在しません。つまりこのバイナリをAlpineにコピーしてきてそのまま動かすことはできません。

$ docker run -itd --rm --name al alpine:3.6 /bin/sh
5beae80c6a9b7d45b3820012abf62c1fad28b73f104bb91875ee8b1a2e6a270d
$ docker cp hello al:/
$ docker attach al
/ # ls -l hello
-rwxr-xr-x    1 501      dialout    3655624 Oct 13 15:16 hello
/ # /hello
/bin/sh: /hello: not found
/ #

そんなわけで静的リンクに変更してRustプログラムのビルドを行います。

どうしても動的リンクのバイナリを動かしたい場合はここのQiitaの記事を参考にすると良いと思います。

qiita.com

静的リンクのバイナリを作成する

muslのRustをインストールします。

https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/advanced-linking.html

手順長いしめんどくせーなと思った人はこれ使うといいかもしれないです

github.com

rust-musl-builderはDockerイメージも用意されてます。実際に使ってみました。

host$ docker run -itd --rm --name rustbuil ekidd/rust-musl-builder:stable /bin/bash
820af56751ce293d22b796c8bc4838b8a91deea01e8528cabbfa6dc7f61a0b36
host$ docker attach rustbuil 
rust@820af56751ce:~/src$ 
rust@820af56751ce:~/src$ export USER=rust
rust@820af56751ce:~/src$ cargo new hello --bin
     Created binary (application) `hello` project
rust@820af56751ce:~/src$ cd hello/
rust@820af56751ce:~/src/hello$ cargo build --release
   Compiling hello v0.1.0 (file:///home/rust/src/hello)
    Finished release [optimized] target(s) in 1.42 secs
rust@820af56751ce:~/src/hello$ cd ./target/x86_64-unknown-linux-musl/release/
rust@820af56751ce:~/src/hello/target/x86_64-unknown-linux-musl/release$ ./hello
Hello, world!
rust@820af56751ce:~/src/hello/target/x86_64-unknown-linux-musl/release$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=e0b4652a02afe0963acacca0c7631c621f39ac3c, not stripped
rust@820af56751ce:~/src/hello/target/x86_64-unknown-linux-musl/release$ ldd hello
        not a dynamic executable
rust@820af56751ce:~/src/hello/target/x86_64-unknown-linux-musl/release$

通常のcargo buildとは違ってバイナリファイルができる場所がtarget/x86_64-unknown-linux-musl/releaseの下になっています。見事に静的リンクでビルドできたようです。ではAlpineに移して実行してみましょう。

host$ docker cp rustbuil:/home/rust/src/hello/target/x86_64-unknown-linux-musl/release/hello .
host$ docker cp hello al:/
host$ docker attach al
/ # ls -l hello
-rwxr-xr-x    1 501      dialout    3896520 Oct 13 15:57 hello
/ # /hello
Hello, world!
/ #

動作しました〜

AlpineでGoのバイナリ動かなくて焦った話(anaconda)

先に結論を言うとGoバイナリをAlpineで動かしたい時はルート証明書が必要になることがあって、ca-certificatesをインストールすりゃ解決するってこと。

「Goはさいきょーだから、バイナリファイル1つありゃうごくやろー!」って思ってたぼくはチンパンジーでした。

過程

Go言語ではanacondaっていうライブラリを使うとTwitterAPIを簡単に叩くことができます。最近Streaming APIにも対応したそう。

github.com

これを使って以下のようなコードを書きます。このプログラムは簡単に説明すると環境変数にセットされたTwitterAPIキーを使用してStreaming APIを叩いて取得したツイートを表示し続けるだけのものです。

ツイートが取得できない(APIキーが間違ってるとか)と「ERROR」と出力してプログラムが終了します。

package main

import(
  "log"
  "github.com/ChimeraCoder/anaconda"
  "os"
)

func main(){
  anaconda.SetConsumerKey(os.Getenv("CONSUMER_KEY"))
  anaconda.SetConsumerSecret(os.Getenv("CONSUMER_SECRET"))
  api := anaconda.NewTwitterApi(os.Getenv("ACCESS_TOKEN"), os.Getenv("ACCESS_SECRET"))
  stream := api.PublicStreamSample(nil)

  for {
    x := <-stream.C
    switch tweet := x.(type) {
    case anaconda.Tweet:
      log.Println("tweet.Text: ",  tweet.Text)
    case anaconda.StatusDeletionNotice:
      //pass
    default:
      log.Fatal("ERROR")
    }
  }
}

これをビルドしてバイナリファイルを作り、Dockerコンテナで動かそうと思いました。コンテナは公式のalpineのDockerイメージを使用します。Linuxの64bit環境で動かすのでGOOS=linux GOARCH=amd64 go buildというようにビルドします。

あとはこんな感じのDockerfileを書いてdocker build

FROM alpine
COPY test /
CMD ["/test"]
mbp2016:test shibujibu$ docker build -t hoge .
Sending build context to Docker daemon  5.962MB
Step 1/3 : FROM alpine
 ---> 76da55c8019d
Step 2/3 : COPY test /
 ---> 2193b80c19bb
Removing intermediate container 8098e29f2a77
Step 3/3 : CMD /test
 ---> Running in 20cb9a6ef9b5
 ---> ba8189720e34
Removing intermediate container 20cb9a6ef9b5
Successfully built ba8189720e34
Successfully tagged hoge:latest
mbp2016:test shibujibu$

コンテナを動かします。test.envにはTwitterAPIのキーの環境変数を記述してあります。

mbp2016:test shibujibu$ docker run --env-file test.env --rm hoge
2017/09/18 12:42:45 ERROR
mbp2016:test shibujibu$

は?エラー???

問題

test.envに記述したAPIキーは全部合っています。意味がわからん。

とりあえずGoバイナリを動かすコンテナに使用するイメージにちょっと変更を加えてみました。具体的にはDockerfileを書き換えてgolang:alpineからイメージを作ってそれを使うこととしました。

FROM golang:alpine
COPY test /
CMD ["/test"]

再びイメージをビルドしてからコンテナを動かします。

mbp2016:test shibujibu$ docker build -t hoge .

(省略)

mbp2016:test shibujibu$ docker run --env-file test.env --rm hoge
2017/09/18 12:48:50 tweet.Text:  RT @AndhraPradeshCM: అమెరికాలోని న్యూయార్క్<200c>లో జరుగనున్న ఐక్యరాజ్య సమితి ప్రత్యేక సమావేశానికి భారతదే
శం తరపున ఆంధ్రప్రదేశ్<200c>ను పంపించాలని కేం…
2017/09/18 12:48:50 tweet.Text:  RT @jyotigaurmba: #Prophecies_Of_Nostradamus #HappyBirthdayPM
सभी भविष्यवाणी के अनुसार तो दुनिया का मुक्तिदाता भारत में…
2017/09/18 12:48:50 tweet.Text:  松岡さん…ペテ…テルくん…

動いた!!!!

原因

alpine:latest(ここで使ったバージョンは3.6)とgolang:alpine(バージョンは1.9.0-alpine3.6)の違いを見てみます。なんとなくインストールされているパッケージが気になりました。apk infoでインストールされているパッケージのリストを出力することができます。

mbp2016:test shibujibu$ docker run --rm alpine apk info
WARNING: Ignoring APKINDEX.84815163.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.24d64ab1.tar.gz: No such file or directory
musl
busybox
alpine-baselayout
alpine-keys
libressl2.5-libcrypto
libressl2.5-libssl
zlib
apk-tools
scanelf
musl-utils
libc-utils
mbp2016:test shibujibu$ docker run --rm golang:alpine apk info
WARNING: Ignoring APKINDEX.84815163.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.24d64ab1.tar.gz: No such file or directory
busybox
alpine-baselayout
alpine-keys
libressl2.5-libcrypto
libressl2.5-libssl
zlib
apk-tools
scanelf
libc-utils
ca-certificates
musl
musl-utils
mbp2016:test shibujibu$

golang:alpineに入っていてalpineに入っていないのはca-certificatesだけですね。

TwitterAPI使う」→「OAuth使う」→「HTTPS通信でルート証明書が必要?」というわけで、ca-certificatesをインストールすりゃいけるんじゃないかと思いました。Dockerfileを書き換えます。

FROM alpine
RUN apk --update add ca-certificates
COPY test /
CMD ["/test"]
mbp2016:test shibujibu$ docker build -t hoge .
Sending build context to Docker daemon  5.962MB
Step 1/4 : FROM alpine
 ---> 76da55c8019d
(省略)
mbp2016:test shibujibu$ docker run --env-file test.env --rm hoge
2017/09/18 15:29:44 tweet.Text:  RT @NegritoNd: C'est simple. Il suffit d'un voyage en Asie dans la ville de Guangzhou tu trouve tes
fournisseurs dans le cosmétiques. Tissa…

動きました!

ブリッジに接続したらDHCPからIPが割り当てられない(VMware)

ゲストOSのインタフェースをVMwareのブリッジネットワークに接続したけど、一向にIPが割り当てられない時の対策。Windows 8.1の話です。

[ コントロールパネル ]

[ ネットワークとインターネット ]

[ ネットワーク接続 ]

Wi-Fiを使っているならWi-Fi以外のやつのプロパティを開いて「VMware Bridge Protocol」のチェックを外す

Alpine Linuxのコンテナのタイムゾーンの変更

コンテナのタイムゾーンはデフォルトではだいたいUTC(協定世界時)になってるんですが、これをJST(日本標準時)に変更したい場合、よく知られている方法として

$ docker run -e TZ=Asia/Tokyo hoge

というように環境変数TZを設定してやればいいというのがあります。ただしAlpine Linuxはこれだけではうまくいきません。

実験

dockerイメージalpine:3.6でコンテナを作ります。そしてdateコマンドを実行します。

mbp2016:~ shibujibu$ docker run alpine:3.6 date
Mon Aug 28 05:44:58 UTC 2017
mbp2016:~ shibujibu$ docker run -e TZ=Asia/Tokyo alpine:3.6 date
Mon Aug 28 05:45:02 GMT 2017
mbp2016:~ shibujibu$ 

タイムゾーンGMT(グリニッジ標準時)になってる???

原因

答えはほとんどここのQiitaの記事に書いてあります

qiita.com

AlpineにはUbuntuとかCentOSみたいなディストリと違って/usr/share/zoneinfoが存在しません。

mbp2016:~ shibujibu$ docker run -it alpine:3.6 /bin/sh
/ # ls /usr/share/
apk   man   misc
/ # 

というわけでLinuxのTime Zone Databaseをインストールします。

/ # apk --update add tzdata
fetch http://dl-cdn.alpinelinux.org/alpine/v3.6/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.6/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2017a-r0)
Executing busybox-1.26.2-r5.trigger
OK: 7 MiB in 12 packages
/ # ls /usr/share/
apk       man       misc      zoneinfo
/ # export TZ=Asia/Tokyo
/ # date
Mon Aug 28 15:52:41 JST 2017
/ # 

おお!JSTになった!

ちなみにfluent/fluentdなんかはベースイメージがAlpine Linuxなので注意です。

DockerでEFKの環境を作って、fluentdのコンテナでTZが異なる外部ホストのrsyslogからデータを取得していた時、kibanaでsyslogのデータのtimestampを見たら未来の時間になってました。fluentdのTZをJSTにしたら解決しましたね。

とりあえずdocker-composeでEFK動かした時のメモ

初めてfluentd触った時設定ファイルのあれやこれやがちんぷんかんぷんだったのでメモっておきました。ほぼ自分用。

とりあえず何も考えずにここのサンプルを実行 docs.fluentd.org

このサンプルは何やってんのか

  • httpdのコンテナの出力をDockerのlogdriverを使ってfluentdに転送
  • fluentdでデータの整形をした後elasticsearchに転送
  • kibanaで可視化

fluent.confについて

# fluentd/conf/fluent.conf
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>
<match *.**>
  @type copy
  <store>
    @type elasticsearch
    host elasticsearch
    port 9200
    logstash_format true
    logstash_prefix fluentd
    logstash_dateformat %Y%m%d
    include_tag_key true
    type_name access_log
    tag_key @log_name
    flush_interval 1s
  </store>
  <store>
    @type stdout
  </store>
</match>

<source>

データの入力元

  • @type forward

  • port 24224

    • リッスンポート
  • bind 0.0.0.0

    • 入力を受け付ける送信元IP
    • 0.0.0.0なら全てのIPからの入力を受け付ける

<match>

データの解析・出力

  • 正規表現に合致するタグの入力に対して処理を行う
  • **.*ならピリオド1個を含むタグの入力ならどんなものでも処理する
  • ちなみにこのサンプルでsourceから渡されてくる入力のタグはhttpd.accessしかない(ここのタグ名は後述のlogdriverのオプションで設定している)

  • @type copy

    • 使用するfluentdの出力プラグインをここで指定する
    • @type copyout_copyプラグインを指定している
    • out_copyは出力先が複数ある場合に使う。最低でも一つの<store>ディレクティブを要する
    • 出力プラグインについてはここを参照

<store>

出力先の設定とか出力するデータのフォーマットを決める

  • @type elasticsearch

    • fluent-plugin-elasticsearchの使用
    • これはgem installで追加したプラグイン。fluentdに元から入ってない
  • host elasticsearch

  • port 9200

    • 出力先のポート
  • logstash_format true, logstash_prefix fluentd

    • elasticsearchに登録するドキュメントのindexの名前のプレフィックスをlogstashからfluentdに変更する
    • ちなみにデフォルトのindexはlogstash-2017.08.01みたいなものになるが、変更後はfluentd-2017.08.01になる
    • 「fluentd使うのにlogstash_◯◯なのか...」っていう違和感めっちゃ感じるけど大丈夫
  • logstash_dateformat %Y%m%d

    • indexの日付のフォーマットを変更する
    • logstash_prefix fluentdと組み合わせるとfluentd-20170801ってな感じの形式にできる
  • include_tag_key true, tag_key @log_name

    • tagkeyの追加と設定
    • サンプルではelasticsearchに登録されたドキュメントに"@log_name": "httpd.access"ってフィールドが追加されるはず
  • type_name access_log

    • ドキュメントのtypeの名前の設定
  • flush_interval 1s

    • 出力にどんだけの時間の間隔を空けるか

2つ目の<store>

elasticsearchの他に端末にもデータの出力を行うってわけ!

docker-compose.ymlのlogdriverについて

3行目〜

web:
    image: httpd
    ports:
      - "80:80"
    links:
      - fluentd
    logging:
      driver: "fluentd"
      options:
        fluentd-address: localhost:24224
        tag: httpd.access
  • fluentdログドライバを使用している
    • ここから送出されるログデータはin_forwardで受け取れる。ってことはデータの形式としてはout_forwardのものと同じ?
  • fluentd-address
    • fluentdのアドレスとポート
    • 当たり前なんだけどfluentdのポートはpublishしてやる必要がある
  • tag httpd.access
    • fluentdのsourceのタグ名を設定する
    • ちなみにこいつを設定しないとタグ名がhttpdのコンテナのコンテナIDになってしまう↓ f:id:tckw_aya:20170829024122p:plain

まとめ

elasticsearchいいよねfluentdいいよねkibanaいいよねEFKいいよね。

Docker + Kerberos認証入門

「Dockerを使ってKerberosの環境を作ろう!」という内容です。Kerberos認証のことは参考書でちょっと読んだ程度で全然わかってないので、手を動かしながら勉強したいなと思った次第です。

目次


環境

単一ホスト上に合計3つのDockerコンテナを立てます。それぞれサービス、クライアント、KDCの役割があります。クライアントがサービスにSSHで接続する時、Kerberos認証をかけ、ログインまでできるようにするのが到達目標です。

以下、今回作成するコンテナです。

  • サービス(ホスト名: myservice)
  • クライアント(ホスト名: myclient)
  • KDC(ホスト名: mykdc)

また全てのコンテナはcentos:7.3.1611のイメージを使用して作り、新たに定義したネットワークkrb_networkに繋ぎます。DNSは使用しません。ちなみにDocker for Macを使ってます。

コンテナの起動まで

Kerberos用のネットワークの定義

コンテナに静的IPを割り当てたかったんですけどDockerのデフォルトのブリッジネットワークではそれができないので、新たにネットワークを作ってそこでIPを割り当てることにしました。

$ docker network create --subnet=192.168.1.0/24 krb_network

コンテナ作成

$ docker run -itd --name service1 -e TZ=Asia/Tokyo --net=krb_network --ip=192.168.1.10 --hostname myservice.mydomain.com --add-host=myclient.mydomain.com:192.168.1.11 --add-host=mykdc.mydomain.com:192.168.1.12 centos:7.3.1611

$ docker run -itd --name client1 -e TZ=Asia/Tokyo --net=krb_network --ip=192.168.1.11 --hostname myclient.mydomain.com --add-host=myservice.mydomain.com:192.168.1.10 --add-host=mykdc.mydomain.com:192.168.1.12 centos:7.3.1611 

$ docker run -itd --name kdc1 -e TZ=Asia/Tokyo --net=krb_network --ip=192.168.1.12 --hostname mykdc.mydomain.com --add-host=myservice.mydomain.com:192.168.1.10 --add-host=myclient.mydomain.com:192.168.1.11 centos:7.3.1611

このdocker runコマンドでは以下のようなことをやっています。

タイムゾーンは別に変更しなくてもいいです。

確認

コンテナがちゃんと動いているか

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
27e8658076c9        centos:7.3.1611     "/bin/bash"         5 minutes ago       Up 5 minutes                            kdc1
ec5a5306a0c3        centos:7.3.1611     "/bin/bash"         5 minutes ago       Up 5 minutes                            client1
db937ce4d851        centos:7.3.1611     "/bin/bash"         5 minutes ago       Up 5 minutes                            service1

hostsファイルと時刻の確認 (以下はmykdcで確認した場合)

$ docker attach kdc1 
[root@mykdc /]# cat /etc/hosts
127.0.0.1   localhost
::1    localhost ip6-localhost ip6-loopback
fe00::0    ip6-localnet
ff00::0    ip6-mcastprefix
ff02::1    ip6-allnodes
ff02::2    ip6-allrouters
192.168.1.10    myservice.mydomain.com
192.168.1.11    myclient.mydomain.com
192.168.1.12    mykdc.mydomain.com mykdc
[root@mykdc /]# date
Mon Jul 17 10:03:45 JST 2017
[root@mykdc /]# 

Kerberos関係のパッケージのインストール

krb5-workstationのインストール

myserviceとmyclientで行います

[root@myclient /]# yum -y install krb5-workstation

krb5-serverのインストール

mykdcで行います

[root@mykdc /]#  yum -y install krb5-server

/etc/krb5.confの編集

全てのコンテナで編集し、以下のようにします。default_ccache_name = KEYRING:persistent:%{uid}コメントアウトも忘れずに。

/etc/krb5.conf

(省略)

[libdefaults]
 dns_lookup_realm = false
 ticket_lifetime = 24h
 renew_lifetime = 7d
 forwardable = true
 rdns = false
# default_realm = EXAMPLE.COM                                                                                                                                  
 default_realm = MYDOMAIN.COM
# default_ccache_name = KEYRING:persistent:%{uid}

[realms]
# EXAMPLE.COM = {                                                                                                                                              
#  kdc = kerberos.example.com                                                                                                                                  
#  admin_server = kerberos.example.com                                                                                                                         
# }                                                                                                                                                            

 MYDOMAIN.COM = {
  kdc = mykdc.mydomain.com
  admin_server = mykdc.mydomain.com
 }

[domain_realm]
# .example.com = EXAMPLE.COM
# example.com = EXAMPLE.COM
.mydomain.com = MYDOMAIN.COM
mydomain.com = MYDOMAIN.COM

KDCの設定

各種設定ファイルの編集

mykdcで/var/kerberos/krb5kdc/kdc.conf/var/kerberos/krb5kdc/kadm5.aclの編集をします。

/var/kerberos/krb5kdc/kdc.conf

[kdcdefaults]
 kdc_ports = 88
 kdc_tcp_ports = 88

[realms]
# EXAMPLE.COM = {                                                                                                                                              
  MYDOMAIN.COM = {
  #master_key_type = aes256-cts                                                                                                                                
  acl_file = /var/kerberos/krb5kdc/kadm5.acl
  dict_file = /usr/share/dict/words
  admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab
  supported_enctypes = aes256-cts:normal aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal camellia256-cts:normal camellia128-cts:normal des-hmac-sh\
a1:normal des-cbc-md5:normal des-cbc-crc:normal
 }

/var/kerberos/krb5kdc/kadm5.acl

# */admin@EXAMPLE.COM     *
*/admin@MYDOMAIN.COM *  

Kerberosデータベースの作成

[root@mykdc /]# kdb5_util create -s
Loading random data
Initializing database '/var/kerberos/krb5kdc/principal' for realm 'MYDOMAIN.COM',
master key name 'K/M@MYDOMAIN.COM'
You will be prompted for the database Master Password.
It is important that you NOT FORGET this password.
Enter KDC database master key: 
Re-enter KDC database master key to verify: 

root/adminプリンシパル作成

[root@mykdc /]# kadmin.local
Authenticating as principal root/admin@MYDOMAIN.COM with password.
kadmin.local:  addprinc  root/admin
WARNING: no policy specified for root/admin@MYDOMAIN.COM; defaulting to no policy
Enter password for principal "root/admin@MYDOMAIN.COM": 
Re-enter password for principal "root/admin@MYDOMAIN.COM": 
Principal "root/admin@MYDOMAIN.COM" created.
kadmin.local:  exit
[root@mykdc /]# 

KDC起動

ではKDC管理サーバーとKDCサーバーを起動しましょう

[root@mykdc /]# kadmind
[root@mykdc /]# krb5kdc

出力が何もなくてめっちゃ不安になったので、プロセスが動いているか、何のポートが開いているかを確認しました。

ssnetstatも使えなかったので、とりあえず追加でnet-toolsをインストールしました。

[root@mykdc /]# ps ax
  PID TTY      STAT   TIME COMMAND
    1 pts/0    Ss     0:00 /bin/bash
   85 ?        Ss     0:00 kadmind
   87 ?        Ss     0:00 krb5kdc
   95 pts/0    R+     0:00 ps ax
[root@mykdc /]# netstat -atun
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:88              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.11:41085        0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:749             0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:464             0.0.0.0:*               LISTEN     
tcp6       0      0 :::88                   :::*                    LISTEN     
tcp6       0      0 :::749                  :::*                    LISTEN     
tcp6       0      0 :::464                  :::*                    LISTEN     
udp        0      0 0.0.0.0:88              0.0.0.0:*                          
udp        0      0 127.0.0.11:50525        0.0.0.0:*                          
udp        0      0 0.0.0.0:464             0.0.0.0:*                          
udp6       0      0 :::88                   :::*                               
udp6       0      0 :::464                  :::*   

OKですね。

サービスプリンシパルの作成

mysericeのコンテナで設定します。

[root@myservice /]# kadmin -p root/admin
Authenticating as principal root/admin with password.
Password for root/admin@MYDOMAIN.COM: 
kadmin:  addprinc -randkey host/myservice.mydomain.com
WARNING: no policy specified for host/myservice.mydomain.com@MYDOMAIN.COM; defaulting to no policy
Principal "host/myservice.mydomain.com@MYDOMAIN.COM" created.
kadmin:  ktadd host/myservice.mydomain.com
Entry for principal host/myservice.mydomain.com with kvno 2, encryption type aes256-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab.
Entry for principal host/myservice.mydomain.com with kvno 2, encryption type aes128-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab.
Entry for principal host/myservice.mydomain.com with kvno 2, encryption type des3-cbc-sha1 added to keytab FILE:/etc/krb5.keytab.
Entry for principal host/myservice.mydomain.com with kvno 2, encryption type arcfour-hmac added to keytab FILE:/etc/krb5.keytab.
Entry for principal host/myservice.mydomain.com with kvno 2, encryption type camellia256-cts-cmac added to keytab FILE:/etc/krb5.keytab.
Entry for principal host/myservice.mydomain.com with kvno 2, encryption type camellia128-cts-cmac added to keytab FILE:/etc/krb5.keytab.
Entry for principal host/myservice.mydomain.com with kvno 2, encryption type des-hmac-sha1 added to keytab FILE:/etc/krb5.keytab.
Entry for principal host/myservice.mydomain.com with kvno 2, encryption type des-cbc-md5 added to keytab FILE:/etc/krb5.keytab.
kadmin:  exit

klistでKerberos のキーテーブルを確認します。

[root@myservice /]# klist -e -k /etc/krb5.keytab 
Keytab name: FILE:/etc/krb5.keytab
KVNO Principal
---- --------------------------------------------------------------------------
   2 host/myservice.mydomain.com@MYDOMAIN.COM (aes256-cts-hmac-sha1-96) 
   2 host/myservice.mydomain.com@MYDOMAIN.COM (aes128-cts-hmac-sha1-96) 
   2 host/myservice.mydomain.com@MYDOMAIN.COM (des3-cbc-sha1) 
   2 host/myservice.mydomain.com@MYDOMAIN.COM (arcfour-hmac) 
   2 host/myservice.mydomain.com@MYDOMAIN.COM (camellia256-cts-cmac) 
   2 host/myservice.mydomain.com@MYDOMAIN.COM (camellia128-cts-cmac) 
   2 host/myservice.mydomain.com@MYDOMAIN.COM (des-hmac-sha1) 
   2 host/myservice.mydomain.com@MYDOMAIN.COM (des-cbc-md5) 

ユーザープリンシパルの作成

どこかのコンテナ内で以下のコマンドを実行します。

[root@myclient /]# kadmin -p root/admin
Authenticating as principal root/admin with password.
Password for root/admin@MYDOMAIN.COM: 
kadmin:  addprinc user1
WARNING: no policy specified for user1@MYDOMAIN.COM; defaulting to no policy
Enter password for principal "user1@MYDOMAIN.COM": 
Re-enter password for principal "user1@MYDOMAIN.COM": 
Principal "user1@MYDOMAIN.COM" created.
kadmin:  exit
[root@myclient /]# 

TGTの取得

myclientで行います。kinitコマンドでTGT(Ticket Granting Ticket)を取得します

[root@myclient /]# kinit user1
Password for user1@MYDOMAIN.COM: 
[root@myclient /]# 

klistでチケットを表示してみます。

[root@myclient /]# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: user1@MYDOMAIN.COM

Valid starting     Expires            Service principal
07/17/17 11:08:28  07/18/17 11:08:28  krbtgt/MYDOMAIN.COM@MYDOMAIN.COM

SSH接続まで

ログイン用ユーザー追加

myserviceでsshでログインするユーザーを作成します。ここで作成するユーザーはさっき作成したユーザープリンシパルと同じ名前にしてください。

 [root@myservice /]# adduser user1  

SSHサーバーのインストール

openssh-serverをインストールします。

[root@myservice /]# yum -y install openssh-server

このままsshdを実行するとCould not load host keyのエラーが発生するので鍵を作ります。

[root@myservice /]# ssh-keygen -A
ssh-keygen: generating new host keys: RSA1 RSA DSA ECDSA ED25519 

次に/etc/ssh/sshd_configを編集します。

/etc/ssh/sshd_config

(省略)
# Kerberos options
KerberosAuthentication yes
#KerberosOrLocalPasswd yes
KerberosTicketCleanup yes
#KerberosGetAFSToken no
#KerberosUseKuserok yes

# GSSAPI options
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no
#GSSAPIEnablek5users no
(省略)

sshdを実行してサーバーを立てます。

[root@myserver /]# /usr/sbin/sshd

SSHクライアントのインストール

myclientで行います

[root@myclient /]# yum -y install openssh-clients

SSH接続

オプションで-o GSSAPIAuthentication=yesをつけてsshを実行します(オプションつけなくてもログインできるかも)

[root@myclient /]# ssh -o GSSAPIAuthentication=yes user1@myservice.mydomain.com
Last login: Mon Jul 17 11:09:37 2017 from myclient.mydomain.com
[user1@myservice ~]$ cat /etc/hostname
myservice.mydomain.com
[user1@myservice ~]$ 

ログインに成功しました〜!再度チケットを表示してみましょう。

[root@myclient /]# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: user1@MYDOMAIN.COM

Valid starting     Expires            Service principal
07/17/17 11:08:28  07/18/17 11:08:28  krbtgt/MYDOMAIN.COM@MYDOMAIN.COM
07/17/17 11:09:37  07/18/17 11:08:28  host/myservice.mydomain.com@MYDOMAIN.COM
[root@myclient /]# 

新たなチケットが追加されています。

これでDockerを使ったKerberos環境の構築を終わりたいと思います。お疲れ様でした。

参考

特定のバージョンのDockerをインストールする

単純にDockerを使う分には最新のバージョン使ってればいいと思います。しかし、たとえばRancherのようなものを扱いたい場合、最新のバージョンのDockerをサポートしていなくて扱えなかったりします。こういう時バージョンを指定してインストールしたいものですが、どうすればいいんだよという話。

Rancherのサイトにあるスクリプトを実行する

Rancherの公式サイトには実行するだけで任意のバージョンのDockerをインストールできるシェルスクリプトが用意されています。

下のリンクからRancherのページに飛んでください

rancher.com

たとえば

curl https://releases.rancher.com/install-docker/17.03.sh | sh

を実行するとホストに17.03系の最新のDockerがインストールされます。

この17.03.shは単純にyumやaptのリポジトリの追加とDocker CEのインストールをしているだけです。ついでにDockerのデーモンも起動します。

バージョンの変化とスクリプトの変化

上でDockerをインストールして満足した人はここから先読まなくていいです。

気づいた方もいるとは思いますが、17.03.sh以降と17.03.shより前のバージョンのスクリプトでは、追加するリポジトリとインストールするパッケージが違います。

たとえば以下はCentOS7でスクリプトを実行した時

リポジトリ パッケージ
17.03.sh以降 https://download.docker.com/linux/centos/docker-ce.repo docker-ce-${docker_version}.ce
17.03.shより前 https://yum.dockerproject.org/repo/main/centos/7 docker-engine-${docker_version}

以下はUbuntu16.04で実行した時

リポジトリ パッケージ
17.03.sh以降 deb [arch=$(dpkg –print-architecture)] https://download.docker.com/linux/ubuntu xenial stable docker-ce-${docker_version}.ce
17.03.shより前 deb [arch=$(dpkg –print-architecture)] https://apt.dockerproject.org/repo ubuntu-xenial main docker-engine=${docker-version}~[ubuntu-xenial|xenial]

17.03.shからyumもaptも同じドメインリポジトリを使用していますね。そしてどちらもDocker CEをインストールするようになりました。

Docker EngineとDocker CE

結局のところ両方ともDockerなんですが、最近Docker CE(無償版)とDocker EE(有償版)というものができたのでDocker Engineというくくりで扱わなくなった模様。以下は参考

kenoha.hatenablog.com

www.publickey1.jp