Digdagのbackfillを無効化する
Digdagでスケジュール実行の設定をしているワークフローを一時停止したあとに停止を解除した場合は、停止していた期間に実行されるはずだったセッションを実行するBackfillという機能があります。
GUIでPAUSEした後にRESUMEしたときや、CLIでのenable/disableを実行したときが該当します。
この機能はデフォルトで有効になっているため、冪等性が保たれていないジョブなどを一時停止した場合はBackfillを無効にする必要があります。
Backfillを無効にする方法として、digdag reschedule
を実行する方法とマニフェストファイルでskip_delayed_by
を設定する方法があります。
rescheduleコマンドを使う方法
rescheduleコマンドでスケジュールを更新して現時刻よりも前にスケジューリングされたセッションをスキップするように設定します。
まずはBackfillを無効にしたいワークフローのIDを取得します。
CLIからは以下のコマンドで取得できます。
$ digdag schedules
取得したIDで以下のコマンドを実行すればBackfillを無効にできます。
$ digdag reschedule <schedule id> --skip-to "$(date '+%Y-%m-%d %H:%M:%S %z')"
このやり方で注意しなければいけないポイントとして、Backfillが無効になるのは今回の一時停止で実行されなかったセッションだけであるところです。
後日また一時停止したときにもBackfillを無効にする場合は再度rescheduleコマンドを実行する必要があります。
冪等性が無い場合など、毎回Backfillを無効にしたい場合は2つめのやり方で設定できます。
skip_delayed_byを設定する方法
マニフェストファイルのschedule
にskip_delayed_by
を設定することで、指定された期間よりも以前のセッションをスキップするように設定できます。
例としては以下のようになります。
schedule: skip_delayed_by: 3m +setup: ...
この例の場合は一時停止を解除した時刻の3mよりも前のセッションを無効にします。
つまり、この設定を1s
とかにしておけばBackfillを完全に無効にできます。
ワークフローの内容によりBackfillが実行されると不都合がある場合は、この設定を入れておくことをオススメします。
Go ModulesなプロジェクトのDockerビルドを高速化する
Go Modulesに対応させたプロジェクトをDocker内でビルドして実行するとします。
単純にDockerfileを書いてしまうとソースコードに変更が入るたびにModulesのダウンロードが走ってしまい、とても時間がかかってしまいます。
そこでDockerのイメージレイヤのキャッシュ機能を使って高速化する方法を紹介します。
Go Modulesのキャッシュ
キャッシュが効かずに毎回Modulesのダウンロードが行われる原因としては、Docker内でGoのビルドをするためにソースコードをコピーしている COPY
コマンド部分が、前回の状態と異なるためキャッシュを使うことができないところにあります。
そのため、ソースコードをコピーする前にModulesのダウンロードを行えば、ソースコードの変更を行っても前回のキャッシュが使え、大幅な短縮になります。
以下が一例です。
FROM golang:1.12.4 as build ENV GO111MODULE=on WORKDIR /go/src/path/to/proj COPY go.mod . COPY go.sum . RUN set -x \ && go mod download COPY . . RUN set -x \ && go build -o /go/bin/app FROM ubuntu:18.04 COPY --from=build /go/bin/app /go/bin/app CMD ["/go/bin/app"]
ソースコード全体をコピーする前にModules関連だけをコピーして、 go mod download
を実行してダウンロードしています。
これでModulesに変更が入らない限り COPY . .
から実行されるため、大幅な短縮になります。
KubernetesのPodでDockerコンテナを実行する
KubernetesのPodでDockerコンテナを実行する方法を紹介します。
Dockerコンテナ内でコンテナを起動する方法のことをDinD (Docker inside Docker)などと言われていますが、同じことをKubernetesのPodで実行するには少し工夫する必要があります。
アプローチとして2種類のやり方があり、双方にメリットとデメリットがあるため状況に合わせて適切なものを選択する必要があります。
アプローチ
1つめのやり方はPodが動いているホストマシンのDockerデーモンを共有する方法で、DooD (Docker outside of Docker)とよばれるものをPodに適応させたパターンです。
2つめのやり方はPod内でDockerデーモンを動かす、DinD (Docker inside Docker)と呼ばれるものを適応させたパターンです。
それぞれの方法のやり方とメリット・デメリットについて紹介します。
DooDパターン
DooDパターンをk8sに適応させるのは簡単で、ホストの /var/run
を共有してDockerデーモンにアクセスできるようにするだけです。
apiVersion: v1 kind: Pod metadata: name: dood spec: containers: - name: docker image: docker:19.03 command: ["docker", "run", "nginx:latest"] volumeMounts: - mountPath: /var/run name: docker-sock volumes: - name: docker-sock hostPath: path: /var/run
DooDパターンの場合、コンテナ内で作成されたコンテナはホストが管理するその他のコンテナと同列の扱いになることに注意する必要があります。
つまり、コンテナを作成したPodと同列の扱いのコンテナとなります。
しかしながらPod内で作成したコンテナはk8sの管理下にはありません。
また、もし他にもコンテナを作成するPodが存在した場合は、コンテナ名などのリソースが競合する可能性があるので注意しなければいけません。
偶然発生する可能性は低いですが、k8sのリソースと競合する可能性もあります。
コンテナのポートをマッピングする場合も注意が必要で、デーモンが動いているのはホストマシンになるためポートマッピングされるのもホストになります。
そのためコンテナを作成したPodのIPで作成したコンテナにはアクセスできません。ホストのIPでアクセスする必要があります。
また、k8sの管理から外れるため、Podに指定したリソース制限が適用されません。
さらに -d
としてコンテナを起動した場合はPodの削除時に作成したコンテナが削除されません。
このように様々な落とし穴があるため、k8sのPodの場合はDooDパターンはオススメしません。次に解説するDinDパターンを使うべきです。
メリット
- 特別な権限を与える必要がない
- コンテナを動かしたいイメージにDockerコマンドをインストールするだけで動かすことができる
デメリット
- Podの中ではなくホストマシンの中にコンテナが作成される
- k8sの管理下からはずれたところにコンテナが作成される
- ホストでリソースの競合が発生する可能性がある
DinDパターン
DinDパターンはDooDパターンと比べると少しだけ複雑です。
dockerコマンドを実行するコンテナのサイドカーとしてDockerデーモンを動かし、dockerコマンドがサイドカーに対してアクセスするように設定します。
以下がその設定をしたマニフェストファイルです。
apiVersion: v1 kind: Pod metadata: name: dind spec: containers: - name: docker image: docker:18.09 command: ["docker", "run", "nginx:latest"] env: - name: DOCKER_HOST value: tcp://localhost:2375 - name: dind-daemon image: docker:18.09-dind resources: requests: cpu: 20m memory: 512Mi securityContext: privileged: true
Dockerデーモンを動かすコンテナは公式のイメージがあるのでそれを使います。この公式イメージは2375ポートでDockerのREST APIを公開しています。
そこに対してDocker CLIがアクセスするように環境変数 DOCKER_HOST
を設定します。
DinDパターンでは作成したコンテナはDockerデーモンが動くサイドカーコンテナの中に作成されるため、Podに設定したCPUやメモリなどのリソースの制限を受けた状態でコンテナを作成できます。
また、このPodを削除したときに作成したコンテナも同時に削除されます。
ポートマッピングを使用したときもDockerデーモンが動いているネットワーク、つまりはPodのネットワークに対して行われるため、ホストのリソースを汚すことなくPodのIPでアクセスできます。
同様にコンテナ名などのリソース競合もPod内でしかおこりません。
ただし一つだけ意識しなければならないことがあり、Dockerデーモンが動くコンテナは privileged
権限で実行しなければいけません。ここが障害にならない限りDinDパターンを選択することをオススメします。
メリット
- Pod内にコンテナを作成できる
- ホストに影響を与えることなくPod内で完結する
- Podの設定(リソースやネットワーク)を引き継ぐことができる
デメリット
- サイドカーコンテナが1つ増える
- privilegedでコンテナを動かす必要がある
まとめ
privilegedで動かしても問題ない環境であればDinDパターンがオススメです。
DooDを使う場合はリソース競合など、ホスト側でコンテナが動いていることを忘れずに使う必要があります。
QtでMacアプリを作ってみる
QtでMacのアプリをビルドし、.app
の形式として書き出します。
環境
事前準備
使用するXcodeのCommand Line Toolsを設定します。
$ sudo xcode-select -s /Applications/Xcode10.2.1.app
Command Line Toolsが正しく設定されているか確認します。
$ xcode-select -p /Applications/Xcode10.2.1.app/Contents/Developer
qtのインストール
Qtの公式サイトからオープンソース版をダウンロードします。
ダウンロードしたら流れにそってインストールします。
ここでQt本体にチェックをつけないとDeveloper Toolsしかインストールされないのでチェックを入れます。
ダウンロードが終わったらインストール完了です。
プロジェクトの作成
インストールが完了するとQt Creator
が起動するので、New Project
をクリックしてQt Widgets Application
を選択します。
適当なプロジェクト名を入力すれば作成されます。
Qt Creatorでビルドを実行するとこのような画面が作られます。
アプリ(.app)化する
Qt Creatorからはアプリを実行できたので次は.app
として書き出します。
書き出すにはqmake
というツールを使うため、qmake
にパスを通します。
.bashrc
や.zshrc
などに以下を追加します。
export PATH="$HOME/Qt/5.12.3/clang_64/bin:$PATH"
プロジェクトのあるディレクトリに移動して以下のコマンドを実行すれば、プロジェクトのあるディレクトリに.app
が作られます。
$ qmake -project
$ qmake
$ make
この環境ではmake
を実行するときにエラーになりました。
.pro
ファイルに以下を追加するとmake
が動くようになります。
QT += widgets
参考
DockerのCMDでログファイルをtailする
Dockerは標準出力に出力したログなどはlogs
コマンドで確認することができますが、Dockerで動かすアプリケーションがファイルにログを出力している場合はlogs
コマンドで確認することができません。
このような場合は、CMDでtail -f
することが多いと思います。
CMD tail -f /var/log/cron.log
しかしこれだとlogs
コマンドに何も流れてきません。
そこでログファイルの中身をexec
コマンドで確認してみると正しくログが出力されています。
なにが起こっているのでしょうか。
Dockerのファイルシステム
原因はDockerが採用しているファイルシステムにあります。
DockerはUnion File System
というファイルシステムを採用していて、このファイルシステムはcopy-on-write
という方法で動作しています。
Union File System
やcopy-on-write
についてはこの記事がわかりやすいです。
直接的な原因は、CMDでtailしているログファイルはDocker imageに保存されているファイルであり、コピーされてログが書き込まれるようになったファイルではないためです。
対策
CMDでtailする前にcopy-on-writeを発生させればいいだけです。
やり方は色々あると思いますが、1つの例としてはこのようにCMDを書けば意図した通りに動きます。
CMD : >> /var/log/cron.log && tail -f /var/log/cron.log
なお、このようにCMDで&&
を使って複数コマンドを実行するときは、DockerのPID1とゾンビプロセスのことを考慮する必要が出てくるので注意しましょう。
参考
dockerfile - Output of `tail -f` at the end of a docker CMD is not showing - Stack Overflow
自作したiOSライブラリの更新手順
自作したiOSのライブラリをCocoaPodsとCarthageで公開する記事はたくさんありますが、新バージョンの公開に関する記事はほとんど無く毎回困るのでまとめます。
Carthage
Carthageで公開しているライブラリの新バージョンをリリースするのは簡単で、GitHubで新しいrelease(タグ打ち)をするだけです。
そのため、準備が整ったらmasterにマージしてタグを打てばOKです。
CocoaPods
CocoaPodsは少し作業が発生します。ただ、公開するときに実行したコマンドの一部をもう一度実行するだけなので、忘れなければ簡単な作業です。
podspecの更新
podspecファイルに書いてあるバージョンをリリース予定のバージョンに更新します。
そのほかの内容は基本的に更新する必要はありません。もちろん、ここが変わるような変更を加えたリリースなら更新する必要があります。
.swift-versinoの更新
最近のXcodeには複数のバージョンのSwiftが付属しているため、ライブラリが使用するSwiftのバージョンを教えてあげないと正しく動かない場合があります。そのため.swift-version
というファイルを作成してリポジトリのルートに配置します。新バージョンのリリースでSwiftのバージョンが変わる場合はこのファイルに書いてあるバージョンも更新します。
バリデーション
更新したpodspecファイルに問題ないかチェックします。
pod lib lint
かpod spec lint
を実行します。
pod spec lint
はリポジトリのチェックまで行うため、新しいバージョンのタグが存在しない場合はエラーになります。未公開の場合はpod lib lint
を実行します。こちらはローカルにある情報だけでチェックします。
新バージョンの公開
ドキュメントの更新なども済ませたらmasterにマージして新バージョンのタグを打ちます。
その後、pod trunk push
を実行してpodsの公開は完了です。
前回のリリースから時間がたっているとpodsの認証が切れてしまっているので、pod trunk register hoge@hoge.com 'Hoge Huga'
と実行して再度認証し、その後もう一度リリースのコマンドを実行します。
複数のPodのログをまとめて表示する
Kubernetesを開発環境として使用しプロダクションと同じような環境で開発すると、複数のアプリケーションが可動することになると思います。
そうするとログも複数の場所に出力されることになります。
複数個のターミナルを立ち上げ1つずつログを表示してもいいですが、場所を取りますし確認する箇所が増えて面倒なので、まとめて表示する方法を紹介します。
今回は以下の2つの構成に合わせたやり方を紹介します。
- ログを標準出力に書き出している場合
- ログをファイルに書き出している場合
ログを標準出力に書き出している場合
これは stern
というアプリケーションを使えば簡単にできます。
使い方は簡単でPodの名前を引数として渡すだけです。
正規表現でいい感じに検索してくれて、オプションを入れればコンテナで絞り込むこともできます。
Dockerの標準に沿ったアプリケーションならこれを使えば解決です。
ログをファイルに書き出している場合
様々な理由によりDockerの標準に合わせることができず、ファイルにログを書き出している場合もあると思います。
この場合は kubectl exec
で tail
することになりますが、複数のPodのログをまとめるためには少し工夫をしなければいけません。
名前付きパイプを使う
名前付きパイプを使って複数の kubectl exec
の出力を1つにまとめます。
名前付きパイプについての説明はこの記事がわかりやすいです。
名前付きパイプに kubectl exec
を出力し、名前付きパイプを cat
することでログをまとめて表示します。
シェルスクリプトで一連の処理を書きます。
#! /usr/bin/env bash FIFO="/tmp/shout" mkfifo ${FIFO} trap 'rm ${FIFO}; exit 1' 1 2 3 15 PODA=$(kubectl get po -o=jsonpath='{.items[?(@.metadata.labels.app=="pod-a")].metadata.name}') PODB=$(kubectl get po -o=jsonpath='{.items[?(@.metadata.labels.app=="pod-b")].metadata.name}') PODC=$(kubectl get po -o=jsonpath='{.items[?(@.metadata.labels.app=="pod-c")].metadata.name}') kubectl exec -i ${PODA} -c hoge -- tail -f /var/app/log/hoge_log > ${FIFO} | \ kubectl exec -i ${PODB} -c hoge -- tail -f /var/app/log/hoge_log > ${FIFO} | \ kubectl exec -i ${PODC} -c hoge -- tail -f /var/app/log/hoge_log > ${FIFO} | \ cat ${FIFO}
stern
と比べると見やすさや使いやすさで数段劣りますが、同じようにまとめて出力することができます。