24時

アジカンの24時が好きな(自称)女子中学生

Linux上にDockerをインストールするまでの作業

これと同じことをやることが頻繁にあるので、手順書(笑)にしておくことにしました。リモートマシンのUbuntuにDockerをインストールすることを想定。

SSH鍵転送

SSH鍵作成

TARGETは適宜変更。

TARGET=sushi

鍵作成

mkdir ~/.ssh/$TARGET
ssh-keygen -t rsa -b 4096 -N "" -C "$TARGET" -f ~/.ssh/$TARGET/id_rsa

鍵転送

変数は適宜変更。

TARGET_HOST=sushi1
TARGET_NAME=10.10.10.10
TARGET_USER=jony
TARGET_PORT=22

鍵送信

cat << EOF >> ~/.ssh/config
Host $TARGET_HOST
    HostName $TARGET_NAME
    User $TARGET_USER
    Port $TARGET_PORT
    IdentityFile ~/.ssh/$TARGET/id_rsa

EOF

ssh-copy-id -i ~/.ssh/$TARGET/id_rsa $TARGET_HOST

Dockerインストール

sshリモートホストに接続する。

sudoをパスワードなしでできるようにする

sudo -s

TARGET_USERは適宜変更

TARGET_USER=jony

sudoers編集

echo "$TARGET_USER ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
exit

Dockerインストール

変数は適宜変更。

TARGET_USER=jony
TARGET_DOCKER_VERSION=17.03

Hosts in Rancherにあるスクリプトを使うと、楽に特定のバージョンのDockerをインストールできます。

sudo apt-get update
sudo apt-get -y upgrade
curl https://releases.rancher.com/install-docker/${TARGET_DOCKER_VERSION}.sh | sh
sudo usermod -aG docker $TARGET_USER

Git操作メモ(1. 取り消し系)

Gitの取り消し系の操作

コミットとかステージングをやり直したい時の操作について書いていきます。

ファイルの変更の取り消し

$ git checkout <ファイルまたはディレクトリ>

これで一応ファイルの変更の取り消せるんですが、より正確にはこういうことをやっているらしい。

オブジェクト格納領域から指定されたファイルを取り出し、ワーキングディレクトリに配置する

git checkoutを図解する | To Be Decided

たとえば以下のような操作を行なったとしましょう。

コミット(1) -> hoge.txt編集(2) -> hoge.txtをステージング(3) -> hoge.txt編集(4)

この場合、(4)を行なった後にgit checkout hoge.txtを実行すると、hoge.txtの内容は(2)の時のものになります。ファイルの内容を(4)から(1)に戻すのは、後述「ステージングの取り消し」に記載した方法で(3)のステージングを取り消せばできます。

ちなみにgit checkoutで新規に作成したファイルが削除されることはありません。

参考: git checkoutを図解する | To Be Decided

ステージングの取り消し

$ git reset HEAD <ファイルまたはディレクトリ>

または

$ git reset <ファイルまたはディレクトリ>

git resetコマンドはステージングを取り消すっていうか、HEADやstageやworking directoryの状態を前に戻すコマンドです。オプションを特につけないで実行した場合、HEADとstageの状態はリセットされ、working directoryで行なった変更はリセットされません。

HEADのみ状態を前に戻したい場合は--soft、HEAD、stage、working directoryの状態を前に戻したい場合は--hardのオプションをつけます。

たとえばそれら3つの状態を最新のコミットの状態に戻したい場合こんなコマンドを実行します。

git reset --hard <最新のコミット>

これで実質「ステージングの取り消し」と「ファイルの変更の取り消し」を行なっています。

参考: git-resetは結局何を戻すのか

Untracked filesの削除

$ git clean -f

追跡していないファイルは全て削除されます。追跡してないディレクトリも削除する場合は以下のコマンド。

$ git clean -df

これらを実行する前に-nオプションをつけてドライランを行なっておくといいですね。

参考: git Untracked files(未追跡ファイル)を1発で消し去る

コミットの取り消し

git reset 使います。

  • ステージングを取り消したい時: git reset <path>
  • コミットを取り消したい時: git reset <commit>

って覚えとくといいんじゃないかなぁと思います。

「今まで行なったファイルの変更とかステージングもなかったことにしてやるぜ!」って場合は

$ git reset --hard HEAD^

「今まで行なったファイルの変更やステージングはなかったことにしたくないけど、さっきのコミットは取り消したいなぁ」って場合は

$ git reset --soft HEAD^

pushの取り消し

ローカルとリモートのリポジトリで最新のコミットを取り消したい場合

$ git reset HEAD^
$ git push -f origin <branch>

リモートのみ最新のコミットを取り消したい場合

$ git push -f origin HEAD^:<branch>

その他の取り消し系操作

Gitでやらかした時に使える19個の奥義に色々書いてあります。

Tips

git resetのmode

Soft、Mixed、Hardがあります。git resetでどのmodeを指定するかによって、リセットされる対象が変化します。

  • Soft: HEAD
  • Mixed: HEAD、stage
  • Hard: HEAD、stage、working directory

@

HEADのエイリアス。コマンド実行する時HEADって書くのが面倒臭かったら代わりに@が使えるよぅ!(=゚ω゚)ノ

Kubernetes Hardwayメモ

k8sクラスタはkubeadmとかrkeとかを使うと、クラスタに必要なコンポーネントを自動で作成してくれたりしてある程度楽に構築できます。しかし、あえてそれらを手作業でデプロイしていって、手動でk8sのクラスタを構築したいって人はKubernetes Hardway(下記リンク)を参考にして進めるといいです。これ結構勉強になるハンズオンです。

github.com

以下ではKubernetes Hardwayでやったこととか、注意点とかをメモしていきます。ちなみにこの記事を書いた時点でのmasterへの最新コミットは4f5cecbで、これはk8s v1.9.0対応です。

事前にk8sのクラスタにはどんなコンポーネントがあるかを知っておくといいです。(下記参考)

qiita.com

Prerequisites

gcloudのインストール

gcloudコマンドはGoogle Cloud SDKコンポーネントなので、Google Cloud SDKをインストールする。

Cloud SDK のインストール  |  Cloud SDK のドキュメント  |  Google Cloud

Cloud SDKのアップデート

gcloudなどのCloud SDKのバージョンが古い場合にはgcloud components updateを実行することでアップデート可能。

Install CFSSL

cfsslインストールする

Provisioning Compute Resources

  • この章から課金発生するので注意
  • Kubernetes Controller3台、Kubernetes Worker3台の合計6台のGCEインスタンスを作成

ここでふと「こういうハンズオンで冗長構成を作るとき、なんでMasterの役割があるやつは2台じゃなくて3台なんだろうな〜」とか思った。ちょっとk8sの話と脱線するけど面白い記事を見つけた。

qiita.com

スプリットブレインについては知ってたけどマジョリティ(多数決)で生かす方を決定するというのは知らなかった。etcdの冗長構成を作る時は奇数台にしなくちゃいけない。

Provisioning a CA and Generating TLS Certificates

  • Cloud Flareのcfsslというツールを使ってローカルにCAを作成
  • コンポーネントに必要な証明書や秘密鍵を作成していく

CA証明書、秘密鍵の作成

$ ./cfssl gencert -initca ca-csr.json | ./cfssljson -bare ca
2018/04/23 16:35:15 [INFO] generating a new CA key and certificate from CSR
2018/04/23 16:35:15 [INFO] generate received request
2018/04/23 16:35:15 [INFO] received CSR
2018/04/23 16:35:15 [INFO] generating key: rsa-2048
2018/04/23 16:35:15 [INFO] encoded CSR
2018/04/23 16:35:15 [INFO] signed certificate with serial number 476734857894076781436314039079823636939593070907

CFSSLの使い方についてはCFSSLのWikiを参考に

github.com

Generating Kubernetes Configuration Files for Authentication

Node Authorizationついて

この記事が参考になった

qiita.com

コマンドの説明

コマンド 内容
kubectl config set-cluster kubeconfigファイルにクラスタエントリを設定(クラスタの作成)
kubectl config set-credentials kubeconfigファイルにユーザーのエントリを設定(ユーザーの作成)
kubectl config set-context コンテキストを作成し、クラスタとユーザーを関係付ける。今回はdefaultという名前のコンテキストを作成した

余談だけどkubectl config set-context--namespaceオプションをつけるとデフォルトのネームスペースを変更できる

Generating the Data Encryption Config and Key

EncryptionConfigはk8s v1.7から追加された機能。etcdのデータを暗号化する。対応しているのはetcd v3以降で、etcd v2では無効。

etcdはk8sの構成情報を保存するためのKVS。

Bootstrapping the etcd Cluster

各controllerノード(controller-0, controller-1, controller-2)でetcdを起動する。

INTERNAL_IPについて

この変数に代入されるのはインスタンスのインタフェース0に割り当てられたプライベートIPアドレス。GCEの全てのインスタンスメタデータサーバー(metadata.google.internal)にメタデータを格納していて、そこにクエリを飛ばすことでインスタンスのホスト名やインスタンスIDなどを入手できるそう。

インスタンス メタデータの格納と取得  |  Compute Engine ドキュメント  |  Google Cloud

クエリを飛ばす際はヘッダーにMetadata-Flavor: Googleをつける必要がある。また、インスタンスからのクエリ発行要求では追加の承認はいらないらしい(楽でいいね)

etcdを起動する際に指定する.pemファイル

k8s API Serverの秘密鍵と証明書(kubernetes-key.pem, kubernetes.pem)、CAの証明書(ca.pem)

Bootstrapping the Kubernetes Control Plane

各controllerノードでkube-apiserver、kube-scheduler、kube-controller-managerを起動する

kube-apiserverを起動する際に指定する.pemファイル

ca.pem, ca-key.pem, kubernetes-key.pem, kubernetes.pem

RBAC

ここ参考

qiita.com

kube-apiserverにアクセスする際、kubeletはkubernetesというユーザーとして認証される。

  • どのリソースにアクセスできるかを記述したもの: ClusterRole
  • ClusterRoleをUserに結びつけるもの: ClusterRoleBinding

また、kube-apiserver起動時--kubelet-client-certificateで指定したファイル(今回はkubernetes.pem)をクライアント証明書としてkubeletの認証に用いる。

インタフェースのプライベートIPについて

自動で10.240.0.10とか割り当てられてるのかと思ったら、「Provisioning Compute Resources」の章でcontrollerのインスタンス作成時に--private-network-ipで設定していた

Bootstrapping the Kubernetes Worker Nodes

各workerノード(worker-0, worker-1, worker-2)でkubelet, kube-proxy, containerd(コンテナランタイム)を起動する。

CNI

GKEではnetwork-pluginはkubenetになっているが、hardwayのハンズオンではcniというものを使用するらしい

kubeletを起動する際に指定する.pemファイル

ca.pem, worker-n.pem, worker-n-key.pem

Configuring kubectl for Remote Access

LBを通じてk8sのAPIを外部から叩けるようにする。

Provisioning Pod Network Routes

現時点ではあるノードにで作成されたPodと、その他のノードで作成されたPodが通信できないようになっているので、ルーティングテーブルにルールを追加する

pod-cidr

「Provisioning Compute Resources」でインスタンスを作成する際に、メタデータとして設定している。

Deploying the DNS Cluster Add-on

kube-dnsのデプロイ。kube-dnsはPodとしてデプロイするのでどこかのworkerノードで動くことになる。

kube-dns.yaml

リソースが4つ作られる

$ kubectl create -f https://storage.googleapis.com/kubernetes-the-hard-way/kube-dns.yaml
service "kube-dns" created
serviceaccount "kube-dns" created
configmap "kube-dns" created
deployment "kube-dns" created
$ kubectl get service,sa,configmap,deployment --namespace=kube-system
NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
svc/kube-dns   ClusterIP   10.32.0.10   <none>        53/UDP,53/TCP   23m

NAME          SECRETS   AGE
sa/default    1         5h
sa/kube-dns   1         23m

NAME                                    DATA      AGE
cm/extension-apiserver-authentication   1         6h
cm/kube-dns                             0         23m

NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/kube-dns   1         1         1            1           23m

addon-manager

kube-dns.yamladdonmanager.kubernetes.io/modeっていう設定が出てきた。以下参考

qiita.com

Smoke Test

スモークテストとは - IT用語辞典

kubectl port-forward

ローカルマシンのポート8080をnginxのPodの80番ポートに割り当てた

Cleaning Up

GCPのリソース削除

Golang EchoとTemplateのメモ(自分用)

入門記事

HTMLタグが勝手にHTML特殊文字に変換されちゃう

同じお悩み

stackoverflow.com

template.HTML('<p>hoge</p>')みたいにすればOK。

ベースのテンプレートにパスごとに違ったコンテンツを埋め込むには

同じお悩み

stackoverflow.com

template.New("").ParseFiles("page1.html", "base.html")みたいにすればOK。

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のDockerコンテナで動かしたいんですが、この超軽量の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 May 30 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イメージも用意されてます。実際に使ってみました。

$ docker run -itd --rm --name rustbuil ekidd/rust-musl-builder:stable /bin/bash
820af56751ce293d22b796c8bc4838b8a91deea01e8528cabbfa6dc7f61a0b36
$ 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に移して実行してみましょう。

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

動作しました〜...っていうかバイナリのサイズでっかいなぁ....

とりあえず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になってしまう

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

コンテナのタイムゾーンJST(日本標準時)に変更したい場合、よく知られている方法として

$ docker run -e TZ=Asia/Tokyo hoge

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

実験

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

$ docker run alpine:3.7 date
Mon Dec 28 05:44:58 UTC 2017
$ docker run -e TZ=Asia/Tokyo alpine:3.7 date
Mon Dec 28 05:45:02 GMT 2017
$ 

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

原因

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

qiita.com

AlpineのDockerイメージには/usr/share/zoneinfoが存在しません。

$ docker run -it alpine:3.7 /bin/sh
/ # ls /usr/share/
apk   man   misc
/ # 

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

/ # apk --update add tzdata
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/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 Dec 28 15:52:41 JST 2017
/ # 

おお!JSTになった!

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

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