Pull RequestのコメントからGitHub Actionsを実行する
GitHubのPull Requestのコメントから任意のGitHub Actionsを実行する必要があり、やり方に少々癖があったので紹介します。
issue_comment
ドキュメントを眺めているとPull Requestに関連するイベントはいくつかありますが、Pull Requestのコメントをトリガーとしたイベントが無いように見えます。
pull_request
イベントはPull Requestの作成やラベル付けをトリガーとするイベントpull_request_review
イベントはレビューされたときをトリガーとするイベントpull_request_review_comment
イベントはレビューについたコメントに関するイベント
どうにかPull Requestのコメントをトリガーに起動できないか調べていたらCommunity Forumで以下の投稿を見つけました。
この投稿によるとissue_comment
イベントでPull Requestのコメントもトリガーとなるようです。
Issueへのコメントもトリガーの対象となるので、適切にフィルタリングする必要があると書かれています。
実装
今回は/plan
とコメントに入力することでterraform plan
が実行されるGitHub Actionsを作ってみたいと思います。
steps
までのコードが以下になります。
name: "/plan" on: issue_comment: types: [created, edited] jobs: manual_plan: name: "manual plan" if: contains(github.event.comment.html_url, '/pull/') && startsWith(github.event.comment.body, '/plan') runs-on: [ubuntu-latest] steps:
ポイントはジョブ全体でif
を設定して起動する条件を設定しているところです。
contains()
でPull Requestのコメントのみに反応するようにし、startsWith()
で/plan
とコメントに入力された場合のみにしています。
実行されるブランチ
上記の設定のままsteps
で処理を書いていけば意図した通りに動作しそうですが、1つ問題があります。
issue_comment
をトリガーとした場合、actions/checkout
でpullできるブランチがmasterブランチになってしまいます。(正確にはリポジトリのデフォルトブランチ)
Pull Requestのコメントで実行するので、そのPull Requestのブランチでジョブを実行したいです。
actions/checkout@v2
からはブランチを指定してpullできるようになりました。
Actionsをトリガーした内容が入った変数、つまり${{ github }}
をもとにGitHubのAPIを使ってPull Requestのブランチ名を取得します。
それをactions/checkout@v2
にセットし、Pull Requestのブランチでジョブを実行できるようにします。
steps: - name: get upstream branch id: upstreambranch env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" - name: Checkout upstream repo uses: actions/checkout@v2 with: ref: ${{ steps.upstreambranch.outputs.branchname }}
Pull Requestに結果を書き込み
せっかくなのでplan
の結果をPull Requestのコメントに書き込みたいと思います。
GitHubに関連する操作はactions/github-script
を使えば大体の事ができます。
- id: plan run: terraform plan -no-color - name: plan success message uses: actions/github-script@0.9.0 if: steps.plan.outputs.exitcode == 0 env: STDOUT: "```${{ steps.plan.outputs.stdout }}```" with: github-token: ${{secrets.GITHUB_TOKEN}} script: | github.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: "#### `terraform plan` Success\n" + process.env.STDOUT })
LFSをやってみたので振り返る
長期休暇を利用してLFSをやってみたので振り返ってみたいと思います。
きっかけ
日頃から業務でLinuxを使ってサービスを運用していますが、最近はクラウドやコンテナなどを使うことがほとんどでLinuxのほんの一部だけを知っていれば運用できています。
しかし、何らかの障害が発生したときの調査は平常時に使う一部の知識だけでは難しいことが多いです。
そんな状態から脱出すべく、以下の書籍を読みました。
これらを読んでいるとLinuxの仕組みがわかってきますが、ただテキストを読んでいるだけだとどうしても印象が薄くなってしまい忘れてしまうことが目立ってきました。
そこで何か手を動かしてできることがないか調べてみるとLFS(Linux From Scratch)を見つけたので、やってみることにしました。
期待したこと
LFSをやることで以下の内容を取得できることを期待して手を動かしてみました。
- Linuxの基盤となる部分を知る
- サービスを運用するときの障害対応力を上げる
構築中に感じたこと
Linuxで使われるファイルやディレクトリを手作業で作成する
LinuxをインストールするとFHSに即したディレクトリが構築され、/etc/passwd
や/etc/group
には初期のユーザー情報が記述されています。
LFSで1から構築する場合はこれらのファイルやディレクトリも自分で作成することになります。
当たり前だと言えばそうかもしれませんが、なかなか新鮮な感覚でした。
ソフトウェアのバージョンを合わせることの重要性
LFSは構築に使うパッケージを全て自前でビルドします。
その関係上、まずはホストシステムでLFSの構築に使う環境の構築を行います。この環境の構築はホストにインストールされているソフトウェアを使うため、このバージョンが異なっていると予期せぬエラーに遭遇することもあります。
Linux KernelやGlibcなど、変更するのが大変なものもありますが、ホストのOSを変えるなどして、できる限り合わせる必要があります。
最低でもマイナーバージョンまでは揃えるべきです。
ホスト環境をチェックするスクリプトが載っているので、よくチェックすることをオススメします。
クラウドでは使用頻度が少ないコマンドを実行する
LFSを始めると最初に専用のパーティションを作成することになります。
パーティションを作成するためにfdisk
コマンドを実行したり、それをマウントするためのmount
コマンド、/ets/fstab
への記述、そして構築が進んでいくと作り途中のLFS環境に入るためのchroot
コマンド、デバイスノードを作成するmknod
コマンドなど、クラウドでサービスを運用しているとあまり使わないコマンドを実行します。
クラウド以外で運用する場合やLinuxに関連する開発を行う場合、これらのコマンドは定期的に使うことになるので久しぶりに実行して思い出すことができたのは良い機会だったと思います。
知らないこと、忘れていることはその場で調べる
これが今回の構築で一番良かったところです。
LFSのドキュメントはとてもよくできており、ほぼ全ての作業をコピペするだけで構築できます。
そのコピペするコマンドの中には上記の本で触れられているがあまり使わないコマンドや、そのそも出てこないものなどもあります。
それらのコマンドを知らないままでもコピペで実行すれば構築できてしまいますが、コマンドのオプションも含め、知らないものは全て調べます。
時間はかかりますが勉強になります。
また、LFSでは全てのパッケージをビルドするため、インストールする全てのライブラリに触れます。
その中には知らないパッケージや生成されるコマンドがあると思います。
LFSのドキュメントに簡単には説明が書いてありますが、これも調べるととても勉強になります。
期待したものは得られたか
構築が完了した時点で期待した成果が得られたのか振り返ります。
Linuxの基盤となる部分を知る
これに関しては十分に達成できたと思います。
ただ、ライブラリやコマンドはビルドしただけなので、ふつうのLinuxプログラミングで書かれている内容をもう一度やってみるとさらなる理解が得られそうな気がしています。
サービスを運用するときの障害対応力を上げる
障害対応力は構築が完了した段階だとまだ上がった実感はありません。
少なからず関連はしていると思うので、実際に対応を行ってみて変化を感じるかどうか様子を見てみたいと思います。
今後にむけて
サービスをLinuxの上で運用していくことはまだしばらく続くと思うので、さらなる理解と障害対応力を磨いて行きたいと思います。
そのために次はこれらの書籍をよんでみようかと思っています。
TerraformのStateをS3からTerraform Cloudに移行する
TerraformのStateをチームで管理するときに、複数の環境からplanやapplyを実行できるようにするためにS3などのクラウドのストレージをバックエンドにすることが多いと思います。
通常のストレージをバックエンドにしていると、複数の環境から同時にapplyが実行されたときに壊れてしまう可能性があります。CIで実行するようにしていても、設定が適切ではなく平行に動いてしまうような場合などに壊れる可能性があります。
Terraform CloudにはStateのロック機能がありこのような事故から開放されるため、Stateの管理をS3からTerraform Cloudに移行したいと思います。
環境
- Terraform v0.12.24
- 元のBackendはS3
- Terraform Cloudのアカウントは作成済み
手順
Terraform Cloudへのアクセス設定
まずはterraformコマンドがTerraform Cloudにアクセスできるようにします。
terraformのバージョンが0.12.21以降の場合は terraform login
コマンドを実行して、ブラウザに表示されたトークンを入力すれば完了です。
loginコマンドが使えない場合は $HOME
に.terraformrc
というファイルを作成し、以下のようにトークンを設定します。
credentials "app.terraform.io" { token = "xxxxxx.atlasv1.zzzzzzzzzzzzz" }
また、TF_CLI_CONFIG_FILE
という環境変数を設定すれば、任意の場所に任意の名前で設定ファイルを作成できます。
backendの書き換え
S3の設定が書かれていたbackendをTerraform Cloudのものに書き換えます。
backend "s3" { ... }
これを削除して以下のように書き換えます。
backend "remote" { hostname = "app.terraform.io" organization = "your_organization_name" workspaces { name = "your_workspaces_name" } }
移行
準備が整ったので移行させていきます。
terraform init
を実行するだけで移行ができます。
実行すると以下のように聞かれるので yes
を入力します。
$ terraform init Initializing the backend... Backend configuration changed! Terraform has detected that the configuration specified for the backend has changed. Terraform will now check for existing state in the backends. Terraform detected that the backend type changed from "s3" to "remote". Acquiring state lock. This may take a few moments... Do you want to copy existing state to the new backend? Pre-existing state was found while migrating the previous "s3" backend to the newly configured "remote" backend. No existing state was found in the newly configured "remote" backend. Do you want to copy this state to the new "remote" backend? Enter "yes" to copy and "no" to start with an empty state. Enter a value:
yes
を入力し、問題なく成功すれば以下のように表示されます。
Successfully configured the backend "remote"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
これで移行が完了です。
参考
Kubernetes上で動くGoサーバーでプロファイラを動かしWeb UIで表示する
Goにはプロファイラとして標準パッケージにpprofが搭載されています。
pprofの使い方としてはすでに多数の優良記事が存在するため、ここでは扱いません。
今回はpprofをk8s上で動くサーバーに対して実行し、結果をWebUIで表示する必要があったのでそのやり方を紹介します。
環境
インフラ構成
- GoサーバーのHTTP API用のPortが80
- Ingress経由でアクセス
- pprof用のPortが6060
- プロファイラ結果のWeb UI用のPortが8080
- このPortをServiceのNodePortでMacに公開
やり方
pprofの設定
まずは通常通りpprofの設定をしてGoをビルドします。
import ( ... _ "net/http/pprof" ) func main() { ... go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() ... }
このバイナリがk8sのPodで動いているとします。
Serviceの定義
通常のAPIへのアクセスに使うServiceが以下です。
apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app: my-service ports: - name: http protocol: TCP port: 80 targetPort: 80
これに追加してpprofのWebページにアクセスするためのServiceを定義します。
このServiceのTypeをNodePortにすることでお手軽にアクセスできるようになります。
apiVersion: v1 kind: Service metadata: name: my-service-pprof spec: selector: app: my-service-api type: NodePort ports: - name: http-pprof protocol: TCP port: 8080 targetPort: 8080 nodePort: 30080
pprofの実行
Goのバイナリが動くPodに入り以下のコマンドでpprofを実行します。
$ pprof -http=0.0.0.0:8080 /usr/local/bin/my-api http://localhost:6060/debug/pprof/profile
ここで大事なのはpprofのWebページのアドレスを 0.0.0.0
にすることです。ここを 127.0.0.1
にしていると外部からアクセスできなくなってしまいます。
あとは計測したいエンドポイントにアクセスし以下のようなメッセージが表示された後、ホストからNodePortの 30080
にアクセスすればプロファイル結果がWebUIで表示されます。
Saved profile in /root/pprof/pprof.my-api.samples.cpu.001.pb.gz Serving web UI on http://0.0.0.0:8080 Couldn't find a suitable web browser! Set the BROWSER environment variable to your desired browser.
Magiskのroot化を維持したままOTAアップデートする
Magiskでroot化するとOTAアップデートが無効化されるため、セキュリティパッチなどのOSのアップデートを行うことができなくなります。
しかし、適切な手順を踏むことでroot化を維持したままOTAアップデートを行うことができます。
検証環境
- Pixel3
- Android 10
- TWRPを使用しないroot化(boot.imgのパッチ)
手順
注意事項
以下の手順の中で再起動を促すボタンが数回表示されますが、再起動してはいけない場面が多数あります。
手順の中で再起動をすると記載したとき以外は再起動のボタンが表示されてもタップしないように注意してください。
root化を維持できなくなります。
大まかな流れ
- 一時的にMagiskをアンインストール
- アップデートをインストール
- アップデート後のイメージにMagiskをインストールするように設定する
Magiskのアンインストール
まずはOTAアップデートをインストールするために一時的にMagiskをアンインストールします。
Magisk Managerを起動し、アンインストールをタップします。
その後に表示されるダイアログでイメージの復元
を選択します。
復元が完了しました!
と表示されればOKです。
ここで再起動をしてはいけません。
アップデートのインストール
1つ前の作業で正規のイメージになったため、アップデートのインストールができるようになりました。正規の手順でOTAアップデートを行います。
デバイスの[設定]→[システム]→[システムの更新]に移動し、 [ダウンロードとインストール]ボタンを押してインストールします。
インストールが完了すると再起動のボタンが表示されますが、タップしてはいけません。
Magiskの再インストール
再度Magisk Managerを開き、インストールをタップします。
タップ後に表示されるダイアログで非アクティブスロットにインストール(OTA後)
を選択します。
警告が表示されますが、はい
を選択してインストールを行います。
インストールが完了すると、再起動のボタンが表示されるのでタップして再起動します。
これでOTAアップデートにMagiskがインストールされます。
参考
Magiskでroot化したPixel3のテザリング制限を解除する
民泊系と呼ばれるSIMを使っている場合、制限がかかっていてテザリングをすることができません。
しかしMagiskでroot化していればこの制限を解除でき、民泊系の数百GB/月という容量を使ってテザリングをすることができます。
前提
- Pixel3 (XL)
- Android 10
必要なモジュールのインストール
テザリングの制限を解除するためにMagiskHide Props Config
を使います。
このツールはbusybox
というツールに依存しているためこちらもインストールします。
インストールはMagisk Managerのタブにあるダウンロードを表示し、そこにあるモジュールの一覧から見つけ出して画面の案内にそってインストールします。
テザリング制限を解除
適当なターミナルアプリを用意して以下のコマンドを順番に入力していけば解除できます。
ターミナルアプリがインストールされていない場合は以下のアプリを入れましょう。
ターミナルから以下のコマンドを順に入力します。
su
props
4
n
net.tethering.noprovisioning
true
y
y
を入力後に端末が再起動します。
再起動後、設定からテザリングを有効にできます。
MacのCLI環境の構築を自動化する
前回、IDEをやめてvim-lspと独自コマンドに移行したことを紹介しましたが、あれだけだと少し問題があります。
PCを買い替えたときやクリーンインストールをしたときなどに同じ環境を簡単に構築することができないともう一度構築することが面倒になってしまい、インストールするだけで使えるIDEでいいやとなってしまいます。
そこで今回はAnsibleを使ってCLI環境の構築を自動化したときに簡単に使えるように意識したポイントを紹介します。
Ansible自体の使い方は紹介しませんので別の記事を参照してください。
自動化したコードは以下のリポジトリで公開しています。
ポイント
以下のことを意識して自動化しました。
- 詳細な手順が書かれたドキュメントを用意する
- できるだけ実行するコマンドを少なくする
- アップデートしやすくする
- 別のOSでも動くようにする
1つずつ紹介していきます。
詳細な手順が書かれたドキュメントを用意する
いくら自動化していても、どのように実行すればいいか忘れてしまうと構築することができません。自動化した直後は覚えていても時間が経てばほぼ確実に忘れます。そのため自動化のキック方法をREADMEなどに書いておくべきです。
また、自動化に使っている依存ツール(今回であればAnsible)があればそれのインストール方法も書いておくと良いでしょう。その他にも前提条件があれば書いておきます。
READMEだけで構築が完了する粒度を意識します。
できるだけ実行するコマンドを少なくする
完全な自動化は難しいところもありますが、できる限り自動化して少ないコマンドで構築が終わるようにします。
このときにインストール対象に合わせていくつかのコマンドに分けることをオススメします。
これをやっておくことで構築するときに、そのとき必要なものだけインストールすることができます。
さらに、OSのアップデートなどで自動化が壊れたときにデバッグと修復が行いやすくなります。
アップデートしやすくする
構築後に新たな拡張をした場合、次に構築するときに追加した部分も反映されて欲しいです。そのためには自動化した部分を適宜アップデートする必要があります。
アップデートするときに手間がかかるような状態だと面倒になり放置してしまい、最終的にはアップデートする必要があることを忘れます。
これはもったいないのでアップデートしやすくしておきます。
具体的にやることとしては、
with_items
で重複の排除と項目の列挙- 複数のタスクで読み込まれる項目は
include_vars
で別ファイルから読み込むregister
で変数にする
を行いました。
これを行うと定義が見やすくなるだけでなく、実行ログに項目が表示されるようになるためエラーとなった項目を素早く見つけることができます。
また、各タスクにきちんとname
を定義しておくこともメンテナンス性の向上につながります。
別のOSでも動くようにする
インストールコマンドで各OSのパッケージマネージャを使うなど、OSごとに処理が分かれる部分がでてくると思います。
1つの環境でしか動かさないのであれば問題ありませんが、CLI環境はOSによる差を吸収しやすい部分ですし、サーバーにインストールする可能性も考慮するとどのOSでも構築できるようにしておくと便利だと思います。
OSによる分岐を行うには、タスクにwhen
を追加しOSの判定をすれば良いです。
Macの場合はansible_os_family == 'Darwin'
と書けば判定できます。
OSによる違いがたくさんある場合はOSごとにファイルを用意してinclude
に対してwhen
を使ってまとめて切り替えたりもできます。
おわりに
今回はCLI環境の構築を自動化するテクニックを紹介しました。
CLI環境は自分に一番合った設定にカスタマイズできる反面、インストーラーやインポート機能のようなものはなく、再度構築するのに時間がかかったり最悪の場合は再現できなくなってしまうリスクもあります。このようなことにならないためにも自動化をしておくことがオススメです。
自動化しておくことでサーバーで作業するときにも気軽に同じ環境を構築できパフォーマンスもあがります。