windyakinってなんて読む

うぃんぢゃきんではない

右クリックしたリンクのURLをAPIに送信するためのブラウザ拡張機能「かんかんみかん」を作りました

ブラウザでリンクを右クリックしたときに、そのリンクのURLを特定のAPIエンドポイントへPOSTデータとして送信することができるブラウザ拡張機能を作った。

この拡張機能は、前々から筆者が Twitter の投稿のブックマークを目的として作成した Cloud Functions のエンドポイントに URL を送信するために使っていたものを、 API エンドポイントの指定や、 POST する JSON のフォーマットをテンプレートで設定できるようにすることで、一般にも利用しやすくすることで公開したものである。

現時点で公開している最新版 v2.0.0 で設定できる項目は以下の通り。

  • Post URL
  • Content-type
    • application/jsonapplication/x-www-form-urlencoded から選べる
  • Post data template
    • 送信する Body とする JSON のテンプレート
    • {{{ url }}} が右クリックで選択しているリンクのURLに展開される
  • Fillter (Regexp)
    • 右クリックで選択したリンクのURLが正規表現に当てはまらなかった場合にリクエストを送信しないようにすることができる

これらの機能は、自分が使うために必要最低限の機能として実装したもので、若干高度なオプション設定が多く、作りとしても粗雑なことは否めないので、ぜひ使ってくださいとも言いづらいが、例えばブックマークサービスのAPIエンドポイントにURLを送信するとか、何らかの開発に役立てることができればと思う。

GitHubスクリプトも公開してあるので、必要であれば参照してほしい。

最後に愚痴になるのだが、2022年5月現在ブラウザ拡張機能を取り巻く環境は正直言って微妙で、ほんの少し前まで ChromeFirefox で共通のプログラムで管理できていたのだが、 Chrome Web Store が Google の利益を目的として Chromium が独自に提唱している Manifest V3 という新しいフォーマットでしか新たな拡張機能の投稿を受け付けなくなってしまった。これのせいで Firefox とまた道を分かつことになってしまっており、そのために2つのブラウザの差分を吸収するように別々のスクリプトを管理することになっているので、煩わしさがすごい。

今回作った拡張機能はまだ Manifest V3 の変更による影響を受ける箇所は少なかったが、今後様々な拡張機能がこういった対応をしなければならないと思うと、なかなか大変そうな話だと思う。

シニアエンジニアになった

4月1日から職場での肩書が「シニアエンジニア」になった。この肩書は勝手に名乗っているわけではなくペパボの職位等級制度によるもので、全部で8段階ぐらいある等級の中、入社時には3等級の「エンジニア」のだったのものが、1つ上の4等級となった。等級は数が大きいほど強いという意味になるので、ちょうどキルラキルの極制服ようだなとかねがね思っている。

この職位等級制度の詳しいことについては、ペパボのHRブログに書いてあるのでそちらを確認してほしいのだが、今回はちょうどいい区切りなので、自身が普段どういう専門性を持って仕事をしているのかについてかんたんに説明しようと思う。

今の自分の所属チームについて

ペパボへの入社は2019年の夏頃で、入社当初の配属はEC事業部のプロダクトチームであった。このチームは主に「カラーミーショップ アプリストア」というサービスの運用とそれらに関係する諸々の領域を担当をしており、後に事業部のチームの配置転換の際には「アプリストアチーム」として名前を改められ、私も現在に至るまでこのチームに所属している。

またそれとは別に2年ほど前からアプリストアチームと兼任で、可用性向上プロジェクトのメンバーとしてもアサインされて活動中である。このチームはメインの商材であるカラーミーショップの可用性を向上させるために、インフラ周りを中心に改善を行っていくために組織された事業部を超えた横断的なプロジェクトである。その中でも自身がメインで関わっているのが、可用性向上のための既存システムを Kubernetes 上でスケーラブルに動かす仕組みづくりで、 Kubernetes 上で稼働させるために必要な既存システムの改修などを行っている。

Vue(Nuxt) のフロントエンド開発

アプリストアチームでは主にバックエンドに Rails 、フロントエンドに Vue(Nuxt) が使われている。特にデベロッパーサイトと呼んでいる、アプリストアへアプリを提供しているデベロッパー向けのダッシュボードがあるのだが、それの立ち上げに大きく関わった。

ちなみに立ち上げは入社してすぐの頃であったのだが、前職ではフロントはほぼ触っていなかったものの、CSSフレームワークなどを使ってウェブサイトを作っていた経験や、業務とは関係なく Nuxt を使ったウェブサイトを試しに作ったりして知見を高めた。その結果。年末ごろにはゴリゴリ Nuxt を書けるようになり、デザイナーさんが上げてくるモックアップから、デザインフレームワーク(Vuetify)のコンポーネントなどを提案して、それらを使って画面の実装することができるようになっていた。また素人ではあるもののデザインに関する基本的な感覚があったので、よりモックアップに近いものを作れる能力があることに気づいた。

既存システムの Docker & Kubernetes 化のための改修など

可用性向上プロジェクトでは、主にカラーミーショップという商材を Kubernetes 上で稼働させるために、それらに関係することであればアプリケーションの改修や、ミドルウェア・インフラ面にも踏み込んで対応を行っている。例えばこれまで行ってきた内容は以下の通り。

アプリケーションをコンテナイメージ化するため作業

  • 開発用のコンテナというのはかねてから存在したものの、コンテナ単体でシステムが動くものではなかったので新たに Dockerfile を作成
  • それらを GitHub Actions でイメージビルドする CI/CD パイプラインの整備
    • 当時はまだ全社的に GitHub Actions が導入されはじめたばかりだった
    • それにあたって必要になった自作 GitHub Actions の作成もした

ログのフロー設計とそのためのアプリケーション改修

  • 当初 Pod の Sidecar から直接 BigQuery に転送していたが、諸々の検討した上でログを集約する Statefulset な Fluentd のサービスを新たに立ち上げた
  • Rails でそれまでファイルに吐き出していたログを直接 Fluent への転送ができるようにした
    • その際に依存する Gem を最小限にするために新たにシンプルな Gem を製作して導入

Kubernetes の上での設計やアプリケーション動作のために必要な Manifests を作成

  • 基本的にゴリゴリ書いていく感じ
  • 現状でもアプリケーションロールこそ分離されているものの、以降に際して1つの Pod に詰め込む単位などを検討
  • Twelve-factors に沿ってアプリケーションで読み込んでいる設定値を環境変数に逃がして読み込めるようにしたりする

ファイルキャッシュを永続化するためにストレージサービスを利用したシステムを構築

  • Kubernetes のライフサイクルとファイルキャッシュの相性が悪いので小さなアプリケーションを新たに用意して資産を活かせる形とした

開発支援のためのツール開発

Kuberenetes の環境を作って終わり!じゃなくて、実際に開発者に使ってもらえるようにするために開発を支援するためのツールを作っている。

ブラウザ拡張機能

開発環境の切り替えに必要な作業を簡略化するために初めてブラウザ拡張機能を作った。作った時期が悪くて Chrome の Manifest V3 に対応する羽目になった件については後日書く。

Slack Bot

Chat Bot というよりかは、最近 Slack が推している Slash command を使ったインタラクティブな画面を実装。開発者が自分の環境を Slack から作ることができるようになる。

大量アクセスによるインシデント対応

大量にアクセスが来ると色々と困ることがある。 k8s に完全に移行するといくらか軽減されるかもしれないが、まだ移行できていないためシステム全体に影響が波及しやすい。もちろんそういったことが起きない起こさせないために日々改善はしているが、起きてしまったときの対応も必要である。

実際に起きた時に何が原因になっているのかの初動の調査や、必要なアクセス制限の判断などを積極的にハンドリングしていた。またそれらをドキュメントやダッシュボード化してなるべく自分以外の人でも対応できるようにしていた。

最近は未然に防げる仕組みが整ってきたので出動回数は減ってきている。

登壇・執筆等

口下手と言うか、単純にしゃべるのが下手すぎるのでこういう登壇の機会はあまりないのだが、一応表立ってしゃべることもある。

執筆というほどではないが、会社のテックブログには手厚めの文書がいくつかある。自分はどちらかというと、文章を書く方が得意なので「なにをやったのか」「どうやったのか」を世に出していくにあたっての質を大事にしているし、世の中は常にそういうものを求められるようになっていると思う。なんでも手軽にできる時代だからこそ、信頼度の高い内容を出していかなければならないように感じている今日このごろ。

シニアエンジニアになってどうですか?

実際のところ仕事内容には大きな差が生まれていない。ペパボの場合、シニアへの昇格は追認行為なので、立候補する際にはシニア相当の働きをしていると認められないとシニアになれないことになっている。であるからして、シニアになったからといって何かが劇的に変わるかというとそうでもない。ただ求められるレベルはこれからも上がっていくと思われるので、日々精進していくのみですかね。

10年前に公開していたCGIプログラムで脆弱性が見つかりCVEに登録された話

かつて「ぜろちゃんねるプラス」という2ちゃんねる型スレッドフロート掲示板システムの開発と公開をしていたことがある。

とはいえ開発をしていたというのは自身がまだ学生だった10年以上前の話であり、現代において Perl CGI のプログラムを保守・運用しようというのがなかなか厳しい話なので、ウェブサイト上では2015年ぐらいから開発の終了も明言していた。ただホスティング元のOSDN(当時はSourceForge.jpであった)が現在までサービスの提供を続けてくれていることと、すでに利用している人に向けたアーカイブを目的として、特に非公開化することもしていなかった結果、現代でも一部のコミュニティなどで使われるという謎の代物となっていた。

開発者への脆弱性の報告

そんなこんなで絶賛放置していたのだが、3月22日の10時頃、開発用の掲示板に「クロスサイトスクリプティングが行える脆弱性」があるという内容の報告がもたらされた。自分はその掲示板を常に見ていないのだが、開発メンバーの1人がそれに気づいて同日の22時ごろに Twitter の DM 経由にて自分を含む当時の開発メンバーに通知された。調べると連絡が来るより数日前からすでにこの攻撃は一部で横行しているようで、このスクリプトを使っているサイトの運営者はなんらかの対応を行う必要がある状態となっていた。

しかし先述の通り、そもそも開発の終了を宣言しているプロジェクトである。開発元からの対応はしないならしないでもよいとも考えられるのだが、職業をエンジニアとしている者としては、一度出したものに「脆弱性がある」という状態のまま放置するのも居心地がわるいので、今回は特別に該当箇所のホットフィックス版をリリースをすることとした。

修正までの過程

まずは報告を受けた内容の真偽を確かめつつ原因の調査をする必要があるのだが、そのためにはサンドボックス環境を用意する必要がある。脆弱性への対応であることや、すでに攻撃が発生していることから、インターネット環境で試すことはあまり好ましいことではない。できればローカル環境で実験したいのだが、当時開発に使っていたXAMPPなんてとうに捨てており、また現代において Perl CGI が動く環境を用意するのはそこらへんの Rails を動かすより手間であるかもしれない。

ただこの問題については、数年前に気まぐれでプロジェクトを Docker イメージ化をしていたので割とすぐ用意することに成功した。数年前の自分の気まぐれはなかなかのファインプレーであった。

またぜろちゃんねるプラスのリポジトリSVN である。一応 GitHub にもプロジェクトの Organization とミラーリポジトリっぽいものがあるのだが、実は SVN とは履歴も状態が乖離している。我ながらひどい状態で放置していたものである。結局数年ぶりに SVN コマンドを叩くことになった。

ただ脆弱性の起因となるコード箇所の特定は比較的スムーズに行うことができた。これは当時の開発メンバーへの連絡により比較的早い段階で人手を招集でき、それぞれが調査を行ったおかげである。こういった緊急事態の初動は人手が多いほうがよい。

その他にもリリースパッケージの作成などリリース作業で手こずる場面は多かったのだが、23日0時にホットフィックス版である ぜろちゃんねるプラス v0.7.5 をリリースをした。後から気になって調べたが、前回のリリースから約3,000日ぶりの更新であったらしい。今回開発者への初報を受けてから約14時間、開発者の招集からは3時間弱での対応となったが、3,000日ぶりのリリースにしてはかなりスピード感があるほうではないだろうか。

IPAへの報告

ぜろちゃんねるプラスには自動アップデートの機能は備え付けられておらず、サイトの運営者が自身で情報を取りに来なければ脆弱性が含まれていることを察知できない。そのため情報の発信していく必要があるのだが、ぜろちゃんねるプラスのサイト上では最大限告知をしたとて限度がある。そこでIPAへ今回の脆弱性を報告をすることで、JVNからの発信を行ってもらうことにした。これで万全というわけではないことは重々承知だが、使えるチャンネルはできる限り使ったほうがよいだろうという判断である。

報告後、何度かメールのやりとりを行ってレポートが作成された。すでに攻撃が発生しており修正版も出ている内容だったためか、JPCERTの方には素早い対応をしていただき、3月30日にJVN上で公表されている。

なお今回は CVE の番号 CVE-2022-27496 も採番された。自身の関わったプロダクトに CVE が採番されるのはなかなか複雑な気持ちではあるが、経験値としては悪くないだろう。

なぜ今回対応できたか

冒頭にも書いたが今回そもそも対応するかどうかは判断が微妙なところだった。ほぼ義理と人情みたいなところで動いているのだが、一番大きかったのは会社でのインシデント対応によって培われた突発的な事象への対処能力かもしれない。正確には能力というよりかは気持ち的な問題で、いざコトが起きてしまったときに、ただ悲観的になって立ち止まったり放棄するのではなく、「よしやるか」とギアを入れ替えて問題に挑めるようになってきたような気がしている。

今回も報告を受けたときに比較的早い段階で「対応する」という方向に舵を切って対処し、忘れ去っていたリリース方法までがんばって思い出してリリースできたことは非常によい経験だったように思う。

今後のぜろちゃんねるプラスについて

改めて言うが、今回のホットフィックス版のリリースは極めて異例の対応である。今後また同じような報告を受けた際に同じような対応ができるかについては明言を避けたい。これを機会にぜろちゃんねるプラスの利用者には「開発を終了しているもの」を使っているというリスクを理解し、適切な対処をしてほしいと思っている。

復旧までのタイムライン

  • 2022年3月22日 10:00
  • 21:45
    • 報告を受け当時の開発メンバーが招集
  • 22:30
    • ローカル環境にて報告内容の再現を確認
  • 23:00
    • パッチコード作成完了
    • リリース向けのパッケージ化や準備を開始
  • 2022年3月23日 00:30
    • ぜろちゃんねるプラス v0.7.5 リリース
  • 10:00
  • 2022年3月30日 12:00

日付・時刻はすべて日本時間

Kubernetes の Pod を定期的に再起動させる

イメージのバージョン管理面倒くさい問題を定期的な再起動で解決する

前回のエントリで k3s を運用している話を書いたが、 GitOps 運用の中で私はコンテナの Docker イメージのバージョン管理をサボっている。現状各アプリケーションのイメージはすべて latest のタグが振られているのみで、イメージを更新しても GitOps に使っている manifests のリポジトリを更新される仕組みも用意していない。

だけどもこれだと Pod が生きている限りイメージが更新されても Pod が使うイメージが変わらないままになっていまうので、 CronJob を使って Deployment で起動している Pod を定期的に再起動している。これに併せて imagePullPolicy: Always を指定しておけば、 Pod の再起動の度に最新のイメージを取ってくることができるという目論見である。

ServiceAccount を用意する

ServiceAccount と (Cluster)Role と (Cluster)RoleBinding を用意する。自分は最初この3者の関係がよくわかっていなかったのだが、色々調べていくうちにでいうと Role と ServiceAccount の多対多の関係を表現する中間テーブルが RoleBinding であると理解した。要は Google の IAM 管理で行う「権限」のグループを作ってその権限をユーザに割り当てる行為と同じである。また Role / RoleBinding の頭に Cluster がついて ClusterRole や ClusterRoleBinding になると、その権限は namespace を超えて実行することができる。

まずは Role について。最終的に ServiceAccount が使えるようになる Kubernetes API を一覧にする。 kubectl コマンドで kubectl [verbs] [resources] が使えるようになるという理解が近い。ここに apiGroups という概念も登場する。 Pod は apiVersion: v1 と表現されるが、この場合はコア機能であるので "" (空文字) が指定される。一方 Deployment は apiVersion: apps/v1 と指定するため apps を指定することになる。

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: pod-restarter
rules:
  - apiGroups:
      - ""
    resources:
      - pods
    verbs:
      - get
      - list
      - delete
  - apiGroups:
      - apps
    resources:
      - deployments
    verbs:
      - get
      - list

次に ServiceAccount。特に凝ったことはしないのでそのまま名前をつけるだけ。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: pod-restarter

最後に Role と ServiceAccount を紐付ける RoleBinding を記述する。

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: pod-restarter
subjects:
  - kind: ServiceAccount
    name: pod-restarter
    namespace: default
roleRef:
  kind: ClusterRole
  name: pod-restarter
  apiGroup: rbac.authorization.k8s.io

CronJob を用意する

Pod を再起動させる方法は色々なあるが、 delete pods を定期的に実行されるようにした。これだとダウンタイムが出るが、自分が使うサービスなのでこのままにする。おそらく通常は Deployments の annotate に日付を入れると manifests の更新とともに Pod が再作成されるという方法をとることになるが、自分の環境は ArgoCD で即刻上書きされるので向いていないためこうした。

bitnami/kubectl のイメージ上から kubetl の jsonpath で Deployment の名前の一覧のみを出力し、その結果を xargs にパイプして delete pods を実行させるようにしている。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: pod-restarter
spec:
  schedule: "0 2 * * *"
  concurrencyPolicy: Replace
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccount: pod-restarter
          restartPolicy: Never
          containers:
            - name: pod-restarter
              image: bitnami/kubectl:latest
              command: ["/bin/sh", "-c"]
              args:
                - |
                  kubectl get deployments -o=jsonpath='{range .items[*]}{.spec.template.metadata.labels.app}{"\n"}{end}' | xargs -I "{}" kubectl delete pods -l app={}

効果

いまのところ毎日深夜2時に再起動がかかる運用になっており、最長1日待てば最新のイメージが落とされるようになっている。普段は依存パッケージのセキュリティ更新しかしないためこの運用で問題なく感じている。

ただここまで紹介したのは結構な面倒くさがりの所業で、一般公開しているサービスなどでこれをするのは非常におすすめしない。一旦はこれで運用ができているのはあくまで個人の管理しているサービスだからである。

k3s を運用している

個人的なプロジェクトを外部公開するときや、私的なツール郡を動かすためにVPSを借りている。基本的に自分が作ったサービスたちはコンテナ化されているため、それらのコンテナの起動管理についてはこれまで docker-compose を使っていた。しかし業務で Kubernetes を使う機会が多くなり、コンテナや Kubernetes の運用に関するいろいろな知見も溜まってきたので、自前で Kubernetesクラスタが欲しくなったのだが、趣味にしては維持費が高い。正直そこまで可用性が必要もないが、 ArgoCD で GitOps による自動デプロイとかそういう楽なことだけはしたいという欲が湧いてきたので、 k3s をつかってシングルノードの Kubernetes を運用することにした。この記事ではその際どういったことをしたかなどをまとめる。

k3s のインストール

おもむろに k3s をいれるが、ワン・コマンドで入れられるので便利。なんならアップグレードもこれでできるのですごい。

curl -sfL https://get.k3s.io | sh -

ただしこのままだと kubectl を叩く度に sudo が必要になるので面倒。もう少しよしなにしたい。 Linuxbrew で入れた kubectl で使えるようにするために、 ~/.kube/config に接続設定を含めて出せばいちいち sudo しなくてもよくなる(セキュリティはパーミッションなど最低限のことはやったほうがよい)。

sudo kubectl config view --raw > ~/.kube/config

SealedSecret

Git で Kubernetes の Manifests を管理するとき、 Secret をそのままコミットすると危険なのだが、 SealedSecret を使うことで暗号化した状態にできる。暗号化を行うために一度クラスタに Secret を読み込ませる必要があったり、情報の更新が若干面倒というデメリットもあるが、そのあたりは GitOps したい度とのトレードオフだろう。

kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/${VERSION}/controller.yaml

${VERSION} は SealedSecret のリポジトリの最新を使うとよい。

Secret 情報の暗号化は kubeseal コマンドを使って行う。Secret の manifests が用意できていれば以下のようなコマンドを実行する。

kubeseal --scope namespace-wide -o yaml < secret.yaml > sealedsecret.yaml

ArgoCD

GitOps のために ArgoCD を入れる。 core-install.yaml はコア機能のみで Web UI などはインストールされない。

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/core-install.yaml

初めて触れるなら Web UI はあったほうがよさそうに思う。リソースの管理がどういうものかとか、更新のタイミングなども見ることができるので面白い。

ArgoCD の使い方は公式のドキュメントが一番くわしい。

CLI で操作する場合は argocd コマンドなどを導入すると GitOps につかうリポジトリの登録や、実際のアプリケーションの設定などがコマンドからできて便利。

argocd repo add git@github.com:windyakin/k8s-manifests.git --ssh-private-key-path ${SSH_PRIVATE_KEY_PATH}
argocd app create argocd-applications \
  --repo git@github.com:windyakin/k8s-manifests.git \
  --path argocd-application \
  --auto-prune \
  --dest-namespace default \
  --dest-server https://kubernetes.default.svc \
  --revision HEAD

k3s 運用してみてどうか

もともとは microk8s を導入していたのだが、どういうわけか liveness probe を入れたあたりから KubernetesAPI が重くなってしまい、 kubectl すらまともに使えなくなってしまったため、 k3s に乗り換えたという経緯があった。 コンテナを立てて運用する程度であれば k3s の機能でも十分であり、 microk8s に比べるとオーバーヘッドも少なく助かっているところではある。

またもともとやりたかった GitOps による運用ができたのも楽でよい。基本的にほったらかしで、監視等も入れていないのだが、不自然な挙動などはなく正常にうごいている。ただ貧弱なサーバーのシングルノードなので可用性の向上や、スケーリングといった恩恵は受けることは出来ない。 Kubernetes の美味しいところを知っていてそれにあやかりたい場合は、趣味の割り切りとして使ってよいのではないだろうか。