fastlaneのpilotでThis request is forbidden for security reasons - The API key in use does not allow this requestとエラーになったときの対処法
環境
fastlane 2.131.0
エラー内容
Spaceship::UnexpectedResponse: [!] This request is forbidden for security reasons - The API key in use does not allow this request /Users/travis/build/xxx/xxx/vendor/bundle/ruby/2.4.0/gems/fastlane-2.131.0/spaceship/lib/spaceship/connect_api/client.rb:134:in `handle_response' /Users/travis/build/xxx/xxx/vendor/bundle/ruby/2.4.0/gems/fastlane-2.131.0/spaceship/lib/spaceship/connect_api/client.rb:105:in `patch' /Users/travis/build/xxx/xxx/vendor/bundle/ruby/2.4.0/gems/fastlane-2.131.0/spaceship/lib/spaceship/connect_api/testflight/testflight.rb:78:in `patch_beta_app_review_detail'
対応策
TestFlightへアップロードするアカウントの権限をapp manager以上の権限にすることで解決します。
developerだとこのエラーになります。
備考
2FAの対応でアップロード用のアカウントを作ったときにこの問題に遭遇することが多いようです。
以下のissueでdeveloper権限でこの問題を回避する方法が望まれていますが、まだ回避策は無いようです。
gRPCサーバーのデバッグとServer Reflection
gRPCはRPCの1つでProtocol Buffersでシリアライズ化することで高速な通信を実現させています。そのため、通常のREST-likeなWEB APIのデバッグでつかうcurlやPostmanのようなGUIツールでデバッグすることができません。
そこで今回はgRPCで開発するときに役に立つデバッグの方法を紹介します。
CLIクライアント
一番基本となるデバッグ方法は自由にリクエストを送ることだと思います。
以下のようにいくつかCLIからgRPCのリクエストを送ることができるツールがあります。
公式が提供しているツールを使うのもいいですが、curl-likeなほうが使い慣れた人が多いと思うのでgrpcurlがオススメです。
このようにしてリクエストを送ることができます。
$ grpcurl -plaintext -import-path ./api -proto route_guide.proto -d '{"latitude": 10, "longitude": 20}' localhost:10000 RouteGuide.GetFeature
Server Reflection
gRPCのはデバッグ用の機能としてServer Reflection Protocolというものがあります。
通常は上記の例のように、リクエストを送ってレスポンスを表示するには関連する.proto
ファイルを指定する必要があります。
しかし、Server Reflection Protocolを使えばサーバーから必要な情報を取得することができ、.proto
ファイルが無くても実行することができます。
この機能を使うにはサーバーにReflectionの設定をする必要がありますが、数行に追加で済みます。
import ( "google.golang.org/grpc" + "google.golang.org/grpc/reflection" ) grpcServer := grpc.NewServer() + reflection.Register(grpcServer) ... _ = grpcServer.Serve(lis)
これでServer Reflectionの対応は完了です。
また、Server Reflectionに対応することで以下のようにサーバーに実装されているサービスの一覧や詳細も.proto
ファイルなしで確認できるようになります。
とくにサービスの一覧の表示は途中から開発に参加したプロジェクトなどではとても役に立つと思います。
$ grpcurl -plaintext localhost:10000 list RouteGuide grpc.reflection.v1alpha.ServerReflection
$ grpcurl -plaintext localhost:10000 list RouteGuide RouteGuide.GetFeature
$ grpcurl -plaintext localhost:10000 describe RouteGuide.GetFeature RouteGuide.GetFeature is a method: rpc GetFeature ( .Point ) returns ( .Feature );
環境変数
サーバー側のデバッグ情報として環境変数を設定することで追加のデバッグ情報を出力することができます。
これは各言語の実装により指定する項目が変わるので注意する必要があります。
Go
Goでは以下のように環境変数を設定すれば追加のログが出力されます。
$ export GRPC_GO_LOG_VERBOSITY_LEVEL=99 $ export GRPC_GO_LOG_SEVERITY_LEVEL=info
ドキュメントにも少しだけ記述があります。
また、Goの場合はGo自体に実装されたhttp2のデバッグ機能を使うのもオススメです。
こちらはフレームの内容なども出力されるため、とても役に立つと思います。
$ export GODEBUG=http2debug=2
2019/09/15 21:41:14 http2: Framer 0xc0003001c0: wrote SETTINGS len=0 2019/09/15 21:41:14 http2: Framer 0xc0003001c0: read SETTINGS len=0 2019/09/15 21:41:14 http2: Framer 0xc0003001c0: wrote SETTINGS flags=ACK len=0 2019/09/15 21:41:14 http2: Framer 0xc0003001c0: read SETTINGS flags=ACK len=0 2019/09/15 21:41:14 http2: Framer 0xc0003001c0: read HEADERS flags=END_HEADERS stream=1 len=68 2019/09/15 21:41:14 http2: decoded hpack field header field ":method" = "POST" 2019/09/15 21:41:14 http2: decoded hpack field header field ":scheme" = "http" 2019/09/15 21:41:14 http2: decoded hpack field header field ":path" = "/RouteGuide/GetFeature" 2019/09/15 21:41:14 http2: decoded hpack field header field ":authority" = "localhost:10000" 2019/09/15 21:41:14 http2: decoded hpack field header field "content-type" = "application/grpc" 2019/09/15 21:41:14 http2: decoded hpack field header field "user-agent" = "grpc-go/1.21.0" 2019/09/15 21:41:14 http2: decoded hpack field header field "te" = "trailers" 2019/09/15 21:41:14 http2: Framer 0xc0003001c0: read DATA flags=END_STREAM stream=1 len=9 data="\x00\x00\x00\x00\x04\b\n\x10\x14" 2019/09/15 21:41:14 http2: Framer 0xc0003001c0: wrote WINDOW_UPDATE len=4 (conn) incr=9 2019/09/15 21:41:14 http2: Framer 0xc0003001c0: wrote PING len=8 ping="\x02\x04\x10\x10\t\x0e\a\a"
Cベースの実装
Cをベースとした言語(C++, Python, Ruby, Objective-C, PHP, C#)の実装では以下のような環境変数を設定することで追加のログが出力できます。
$ export GRPC_VERBOSITY=DEBUG $ export GRPC_TRACE=all
Cベースの場合は他にも設定できる項目があり、以下のドキュメントに書かれています。
また、これらの環境変数はgRPCを内部で使用しているSDKなどでも有効です。
特にGoogleのSDKではgRPCがたくさん使われているため、それらのデバッグでも有効活用できると思います。
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