msawady’s learning memo

フルスタックエンジニアを目指して、学んだことをログっていくところ

JJUG ccc 2018 fall 行ってきました

JJUG ccc 2018 fall の感想、まとめ

今年は Python で自動化スクリプトを書いたり、インフラも含めたトラブルシューティングがメインで、 Java は 100行くらいしか書いていないのですが、JJUG ccc に行きました。結果としてはメチャメチャ勉強になりました。 振り返りとして、聞いたセッションのサマリや感想を書きたいと思います。

セッション一覧(午後から参加しました)

  • 思考停止しないアーキテクチャ設計 - 川島義隆さん
  • Serverless Architecture in the Java Ecosystem - PRATIK PATEL(IBM)さん
  • Migration Guide from Java 8 to Java 11 - KUBOTA Yujiさん
  • 秒間 200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 - Hironobu Isodaさん(ウルシステムズ)& @nappa (Ryosuke Yamazaki)さん
  • Scala とマイクロサービスでつくる証券会社とスタートアップ - 村上拓也さん(FOLIO)

思考停止しないアーキテクチャ設計 - 川島義隆さん

セッション資料: 思考停止しないアーキテクチャ設計 ➖ JJUG CCC 2018 Fall

アーキテクチャ設計とは

アーキテクチャ設計のやり方

  • どれくらい時間をかけるか?: コード規模によるが、20%くらい?
    • 設計のコストと、やり直しのコストの総和が最小になるところ
  • どのように評価するか?: 各品質特性シナリオをどの程度満たせているか
  • どのようにメンテナンスしていくか?: ADR(Architecture Design Records) をソースコードの隣に残す

それでも考慮漏れは発生する

  • 思考停止しない
  • 機能要求からもアーキテクチャ要求は生まれる
    • 機能要求から、データストア、キャッシングなどを見直す必要性
    • 機能要求と非機能要求 のそれぞれを考慮する
  • ビジネス環境とアーキテクチャのずれにより溜まる技術的負債
    • 金銭的に定量評価し、優先順位をつけて対応する

感想

安定の @kawasima さん。アーキテクチャ設計というぼんやりした話を分かりやすく理解できた。 「なんとなくわかってること」を綺麗に言語化してくれた感じです。そして参考文献を読んでみたいと思いました。

Serverless Architecture in the Java Ecosystem - PRATIK PATEL(IBM)さん

セッション資料(関連動画): Serverless in the Java Ecosystem - YouTube

Serverless Architecture について

  • サーバをプロビジョニング、管理せずにプログラムを実行する
  • FAAS(Function As A Service) を利用したアーキテクチャ

メリット

  • コスト: 実際にプログラムが動いている時間に対する課金
  • スケーラブル
  • サーバのプロビジョニングにかかるスキル、コスト、時間を節約できる

流れで捉える

  • インフラ仮想化の流れ: ベアメタル -> VM -> コンテナ -> Function
  • アプリ構成の流れ: モノリス -> マイクロサービス -> Function

アーキテクチャ設計において気をつけること

各Function を"小さく"保つ

  • 実行時間を短くする: コスト、FAASの制約
  • ランタイムで依存するライブラリ: 起動時間を短くする
  • イベント駆動にする
  • ステートレスにする

実際のデモ

  • デプロイは CLI から簡単に行える
  • ランタイムが小さければ起動は早くなる
  • 連続する処理: それぞれの処理は小さく保ちたいが、連続性を保ちたいケース
    • Open Whisk(Sequences), Step Function(AWS Lambda) を利用する
    • Manager プロセスは走らせない(2重課金を避ける)

感想

IBM の @prpatel さんによる英語でのセッション。内容としては基本的だなぁという印象。ただプレゼンテーションとスライドが素晴らしかった。

ステートや一時データを外出しするには、フロントエンドやデータ管理のところでより気をつける必要が有りそうだなぁと思いました。

「きれいなコード」じゃないと動かなくなるので、インフラを気にしなくて良くなる分、アプリも気をつけなきゃ行けなくなるのかなと。

Migration Guide from Java 8 to Java 11 - KUBOTA Yujiさん

セッション資料: Migration Guide from Java 8 to Java 11 #jjug

Java 11 移行のステップ

  • ランタイムを 11 にしてテスト
  • コンパイラを 11 にしてテスト
  • (必要に応じて) モジュール化 - 今回は触れない

ランタイムを11 にして起こること

メリット

  • Java8 は完全サポート
  • flight recorder, TLS1.3 などの新機能を使える
  • G1GC が Parallel Full GC になる, ZGC などの新しいGCを使える
    • JavaGC戦国時代なので、試してみても面白い
  • VM のパフォーマンス向上

互換性によるエラー

  • JVM オプションがいくつか消えている
    • loggin 周りのオプションは大きく変更されている
  • oracleJDK では JavaFX が消えている
  • JDK の内部APIが消えたり動いていたりしている
    • jaxb, jax-ws などのxml操作系も..
    • reflection は default では禁止
  • Applet, Java-ws も消えている
  • JDKのモジュール化によって起こるエラー: 大抵はオプションで回避できる

非機能の変更

  • コンテナサポート: コンテナに割り当てられたリソースに応じて使用量を変える
  • デフォルトGC が G1GC になっている

機能/挙動の変更(自分が気になったものPickUp)

  • 数字/日付/時刻 のフォーマットが変わる
  • Reference::clone 廃止
  • Nashornがdeprecated
  • Swing の見た目が変わる
  • java.nio.channels.*Channel がリファクタされてBlocking/NonBlocking処理が分離

コンパイラを11にして起こること

基本方針は「エラーは諦めて11に対応する」「deprecated はコストと見合い、ただ消えることは覚悟しろ」

メリット

  • 新しい機能、API
    • var
    • Reactive stream
    • Unmodifieable collection
    • file, system の API

デメリット

  • Java8 サポートが難しくなる
  • 依存ライブラリのアップデート
  • ビルド/CI環境のアップデート

実作業

  • ビルド、CIツール変更
    • mavan は 3.5.4以上
    • FindBugs -> SpotBugs, Cobertula -> JaCoCo
  • Applet, java-ws に代替するライブラリ導入
  • ほとんどのライブラリ/プラグインはバージョンアップ必須

感想

OpenJDK 開発者の久保田さんによる裏話も交えたJava11移行の話。 なかなか大変そうだけど、やってみたいな...弊社でJava11チャレンジしたプロジェクトあるのかしら...

秒間 200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 - Hironobu Isodaさん(ウルシステムズ)& @nappa (Ryosuke Yamazaki)さん

セッション資料: 200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 #jjug_ccc #ccc_g6

広告RTBの概要

  • SSP からのリクエストに対して100ms以内にBidを入れる必要がある
    • ネットワークレイテンシも含めた100msなので、光速が遅くて大変。
    • Bid処理にかけられる時間は数ms -> Logicad は平均 3ms
  • スループットとレイテンシを向上させる必要がある
    • メインはレイテンシ向上について

パフォーマンスチューニング

  • とにかく測定
  • 測定してホットスポットを見つけてアプローチ
  • デプロイ前の性能テストで性能デグレを防ぐ
    • 測定結果を可視化するのも大事
  • perf でデータ取得、FrameGraphで可視化JMeterで負荷をかける

FrameGraph

  • perf でデータを取得、perf-map-agent でJavaメソッドとの対応を紐付け、FrameGraphでグラフ化
    • メソッドの呼び出し関係、それぞれの所要時間がわかる
    • 正規表現で該当箇所の検索ができる
  • 幅を取っているところに注目する
  • 修正前後の比較で性能デグレを検知できる

Real World でのチューニング

  • Java 以外にもレイテンシの発生源はいっぱいある
    • システム全体をみてチューニングする必要がある
  • Java VM は疑われがち
    • Java の無罪を証明するのもJavaエンジニアの役目
  • Looking Glass: ping/traceroute をたたけるツールでネットワークを検証
  • Nginx: Nginx のログとJavaプロセスのログを突き合わせる
  • 色んなツールを試すことで新たな発見がある
    • FlameScope: CPUの使用率をモニタリング
    • 1 core しか使っていない時間が見つかる
      • logback のローテーション
      • pmap になんかやられてる
  • コスパ大事。経済合理性を意識して、ハマりすぎない。
  • (質疑応答) system_call の回数を減らして strace からでも何から実行されたか分かるようにしている

感想

2016 spring で個人的に一番おもしろかった @nappa さん。 内容はその時のセッションの内容も含んでいたので、割と分かりやすかった。

セッション資料: https://www.slideshare.net/nappa_zzz/java-70326737

チューニングはシステム全体を見てやるべき、は完全同意。

Java の無罪を証明するのもJavaエンジニアの役目パンチラインですね。

Scala とマイクロサービスでつくる証券会社とスタートアップ - 村上拓也さん(FOLIO)

セッション資料: Scala と Microservices でつくる 証券会社とスタートアップ / FOLIO in JJUG CCC 2018 Fall - Speaker Deck

Scala を選んだ理由

  • Java Ecosystem のメリット
  • 型システムの持つ表現力が、金融という複雑なドメインの記述に適している
  • 静的型による安全さ、IDEサポートによる小回りの良さ

型システムの表現力

  • case class で primitive をラップして名前をつける
    • 何の数字/文字列なのかを宣言的に記述することで間違いをへらす
      • 置き間違いを防ぐ(firstName, lastName)
      • 意味の違う数字が混ざらないようにする(UnrealizedPnL, RealizedPnL)
    • 暗号化してる/してないの表現
    • 中置記法により、simple な記述で安全な演算を行う
      • JPY + JPY はOK, JPY * JPY はNG
  • IDE サポートによりパッケージ構造の変更が簡単

MicroServices

  • 前提
    • 性格の違う業務を幅広く多数あわせ持つ
    • 外接の多さ
    • 高いサービス継続性が求められる
  • MicroServices にすることで
    • ドメイン知識を深める
    • QA/リリース の方式を分ける
    • 腐敗防止層をつくる
    • 障害範囲の局所化

Domain Driven Development

MicroServices を支えるツール/フレームワーク

  • Finagle: RPC フレームワーク
    • IDL(thrift) を用いたRPC
  • Finatra: アプリケーションフレームワーク

    • 豊富なメトリクスを取得できる
    • 分散トレーシング
  • (自作) thrift-cli: デバッグCLI

  • Jenkins: ビルドだけでなく、バッチもJenkinsで(JP1がわり)
    • Config as Code で Prod/Stg の環境差分を防ぐ、管理する
  • folicon-bot: Consul に問い合わせるslackbot
    • 各環境の状況をslackで共有できるように

感想

テーマ投資のスタートアップ Folio 村上さんのセッション。 Scala + MicroServeices + DDD が上手くハマって開発を進められていることが伝わってきます。 自社の仕事と同じドメインであることもあり、case class のメリットはすごく分かる...

終わりに

アーキテクチャ設計、サーバレス、Java11移行、チューニング、Scala と幅広く話を聞けて勉強になりました。 正直、こんなに楽しめると思っていなかったけど、ほんとに学びと刺激を貰えた一日でした。

特に最後の Scala の話は同業界ということもあり、非常に刺激がありました。

とにかく、行ってよかった!!

【ポエム】自動化でチームの学習を促進する

自動化について思っていることをつらつらと

  • 自分のチームでは「自動化されていないものは(極力)本番に乗せない」というポリシーのもと、インフラの構築/エンハンス/運用 を行っています。
  • インフラチーム = 「手順書とパラメータシートによる辛いマニュアル作業」というイメージが有りましたが、 今のチームでは自動化することへの辛さは感じながらも楽しく仕事をすることが出来ています。

今回は「自動化されたインフラを作る流れ」と「それによるチームのKnowledgeの蓄積」について書きます。

「自動化されたインフラ」を作る流れ

基本的には以下のような流れで「自動化されたインフラ」を作っていきます。

  1. まずは手で構築する
  2. 作業メモを書く
  3. コード/設定ファイルから再構築する
  4. 作業メモをアップデートする

まずは手で構築する

よほど簡単なタスクでない限り、いきなり自動化して構築することは難しいです。 まずは検証環境を利用して手で構築することで作りたいものの全体感や、他の部分との依存関係を掴みます。 このフェーズでのゴールは「動くものを作る」です。

作業メモを書く

これがめちゃめちゃ大事です。 動くものができた段階で、以下のような内容を書いた作業メモを作ります。

  • 何をしたのか
  • 他のインフラとの依存関係
  • ハマりどころはどこか

これをすることにより、

  • 自動化に向けた設計
  • 他のメンバーへのKnowledgeシェア

を行うことができます。

最初のマニュアル構築は無駄のない手順でできないことのほうが多いので、復習の意味を込めてメモを作り、「本当にやる必要があった作業はなにか」「作業順序はどのようにすべきだったのか」を明らかにします。そして、これはそのまま自動化に向けたインプット(= 設計) になります。

また作業メモをチームに展開することで、レビューを受けて設計をブラッシュアップすることが出来ると同時に、タスクの内容をチームに展開することができます。他のメンバーへタスクを引き継ぎやすくすると同時に、本番適用後の運用タスクも引き継ぎやすくなります。

自動化のインプットを整備し、タスクの属人化を避けるためにも、作業メモを作ることは非常に重要です。

コード/設定ファイルから再構築する

作業メモに基づいてコード/設定ファイルから再構築します。このときに大事なことは最初に手で作ったものはすべて消すことです。自動化されたインフラを作るチームにおいて、手で作ったものはすべてゴミです。メンテナンスやコストの観点から、手で作ったものはすべて消してからコードや設定ファイルによる構築を行います。

作業メモをアップデートする

コード/設定ファイルからの再構築が完了したら、最初に作った作業メモをアップデートします。

  • 事前に作った作業メモの間違っているところの修正
  • 自動化の際のハマりどころ
  • 自動化できなかった部分のメモ
  • コードや設定ファイルのREADME、パラメータのサンプル

実際に構築すると事前のメモとの食い違いが出てくるはずなので、その修正を行う必要があります。

また、コードや設定ファイルから構築しようとすると、一発で出来ることは少なく、何かしらの「ハマり」が出てくるので、そこについてもメモを残してチームに共有します。

そして全てを自動化できない or 自動化しようとするほうがナンセンス なことはままあります。具体的には「cloudformationが対応していないからCLIでやった」「認証情報は手動で取得して貼り付けた」など。こういった部分についてもメモを残します。

最後に、作業やメンテナンスを不定期に行う必要がある際には、コードの使い方やパラメータのサンプルを残します。

ここまでやると、「自動化のためのコード/設定ファイル」と「その部分についてのドキュメント」が残り、「運用しやすい自動化されたインフラ」が残ります。

チームの Knowledge の蓄積

以上のような流れで自動化されたインフラを作っていくと、タスクをやった当人は「手で作り」「ドキュメントを作り」「自動化する」という3つの側面でタスクに当たることになるため、否が応でもKnowledgeが貯まります。

一方で、「個人のKnowledge」を「チームのKnowledge」にするためにはいくつか気を使う必要があります。

作業メモ(=ドキュメント)をみんなでレビューする

ドキュメントは有ることが大事なのではなく、メンバーに読まれていることが大事です。

× チームのKnowledge = ドキュメントの量 × 質
○ チームのKnowledge = ドキュメントの量 × 質 × 読んだメンバー数

最初にドキュメントを作成したあとはみんなで読み、レビューするというフローを作ると良いです。チーム全体で内容を確認し、分からないことを質問するようにします。なので、このレビューは「seniorメンバー/リーダーが確認すること」と「juniorメンバーも納得すること」という2つのゴールを持ちます。

全員が対面でレビューすることはコストが高いので、基本的にはチャットやGithub/Gitlabでのトークで行います。そこで「juniorメンバーからコメントする」など、質問が出やすくなるような工夫をすると、ドキュメントの質が上がるだけでなく、チームへのKnowledgeシェアも進みやすくなります。

急ぎのタスクはペアプロ

「手で作り」「ドキュメントを作り」「自動化する」するには一人でやると、普通に手で構築するよりも時間がかかります。ただ、スケジュールがタイトだからといってドキュメンテーションをおざなりにしたり、レビューを省略したり、まして手動で構築することは避けるべきです。

解決策としては、ペアプロ(作業)が有効です。一人でやるよりも集中できるので単純に効率が上がるだけでなく、2人で作業することで「ハマり」時間を短くすることが出来るので、1人でやるよりも格段に作業効率が上がります。また、各メンバーの暗黙知(エディタの使い方、コマンドのオプション...) を伝えることにもつながるので、Knowledgeの蓄積という面から見ても効果が高いです。

終わりに

自動化はエンジニア心をくすぐる非常に楽しい作業です。一方で、ドキュメンテーションやKnowledgeシェアを怠ると、誰にもメンテできないブラックボックスにもなりがちです。

自動化と並行してドキュメンテーションやKnowledgeシェアを進めることで、面倒くさい定形作業を省力化できるとともに、juniorメンバーの成長も進みます。

【Python】運用スクリプトはshellじゃなくてPythonで書こうよ、という話

はじめに

  • 自分のプロジェクトではベースとなるシステムがPythonで書かれていることもあり、インフラ/運用系スクリプトも基本的にPythonで書かれています。
  • 一方で、社内の他のプロジェクトではやはりshellが主流です。。。先日もジュニアなエンジニアが先人の残した"芸術的な"シェルとにらめっこしているところを目撃しました....
  • どちらも経験した自分としては、圧倒的にPythonで書くほうが良いというのが正直な感想です。
  • 今回の記事ではPythonを利用するメリット、導入に向けた準備について書いて行きます。

Pythonを利用するメリット

大きく、以下のようなメリットがあると思います。

言語機能的な問題

shell よりも Python の方が言語として優れている!なんて一元的かつ宗教的なことは言いたくないですが、とはいえ、Pythonの方が優れている部分は多くあります。

オブジェクト指向的に書きやすい

正直、shell はいわゆるアプリの開発者にとっては中々とっつきづらいものかと思います。

プログラマーの君! 騙されるな! シェルスクリプトはそう書いちゃ駄目だ!! という話 - Qiita

上のリンクにも有るように、そもそもデータの扱い方がJavaPythonのようなオブジェクト指向言語とは発想が全く違います。

  • Python: コマンドの実行結果を変数(構造体)に格納→ロジック(if, for)を用いて変数を加工→次のコマンドのインプットに変数を渡す...
  • shell: コマンドの実行結果をパイプする→インプットをフィルタ(grep)、加工(awk, sed)してパイプ→インプットを用いて次のコマンドを実行(xargs)...

shell はどちらかと言うと関数型に近い発想で書く必要があり、複雑な処理を綺麗に書くには中々のスキルが必要になります。 そして大抵のケースでは"ステキな正規表現によるgrep"と"趣深いawkでのデータ加工"を組み合わせた"芸術的ワンライナー"が行われ、ジュニアなエンジニアは"勉強"が必要となっているように思います....

Pythonオブジェクト指向的に書けるので、ジュニアなアプリエンジニアでも少ないコストでキャッチアップできます。 特にデータ加工をif, forなどの馴染み深いやり方で行えるので、ロジックを追いやすくなりデバッグや修正も楽になります。

json, yaml の扱いが楽

AWSを使っていると、あらゆるデータがjson, yaml形式で記述されます。

shell でも jq, yq といったライブラリを使って処理を行うことは出来ますが、これも結局は上記のパイプを基本とした処理になるので、"芸術的ワンライナー"ルートに入りやすくなります。

Pythonでは組み込みライブラリのjson, yaml を使うことでdictとして扱うことができます。dictの変数になってしまえば、if, forといった手続き的なロジックを用いてデータの処理を出来るため、ロジックを追いやすくなります。

IDEの補助を受けられる

shell にも様々なIDEプラグインがあるものの、やはりPythonの方がメソッド補完、型補完、文法チェックなどの機能が充実しているように思います。

シニアなエンジニアであれば vim などのシンプルなエディタでもガリガリと書くことが出来ますが、ジュニアなエンジニアにとっては「このクラスにこのメソッド有ったっけ」「このメソッドの引数なんだっけ」などを覚える/調べる手間を省いてくれるIDEの補助は有り難いものです。 また、コーディングルールを揃えることも容易なので、プロジェクト全体的なコードの質を担保するという点においても、IDEの補助を受けられるというのは無視できないメリットです。

テストが書ける

正直、これが一番のメリットだと思います。

pytest などを利用した単体テストを書くことが出来るので、「プロダクションのコードはちゃんとテストされているのに、運用系スクリプトは全然テストされていない(Test In Productionな状態)」を回避することができます。

もちろん、運用系スクリプトはファイル/ネットワークのI/Oが多かったり、前提条件が様々だったりとテストが書きづらいものではありますが、

  • データの取得部分とフィルタ・加工部分のメソッドを分けてフィルタ・加工部分のテストを書く
  • 静的文法チェック(Lint)を走らせる
  • dry_run オプションを使って実環境での仮実行を行う

など、様々なやり方を使ってロジックの正常性を担保することができます。また、単体テストであればCIツールと組み合わせることも可能なので、継続的に品質を担保できるとともに、リファクタや追加改修の心理障壁を下げる効果も期待できます。

ログ、標準出力をコントロールしやすい

Python には logging というログ出力のライブラリが有るため、実行時の標準出力をコントロールしやすいというメリットが有ります。

  • 普段の実行時にはファイルにのみ出力する
  • 手動実行時には標準出力にも出力する
  • -v, --verbose オプションを用意してdebugしやすくする
  • debug のときでもライブラリの出力は抑止する(boto がとにかくうるさい...!)

などなど。こうした柔軟な出力制御を柔軟に出来るのは開発時、運用時ともに地味に効いてきます。

Python 導入に向けた準備

Python のメリットを書いてきましたが、導入時にやらなければいけないことはそれなりにあります。が、それほど難しいことは無いと思います。

  • 環境をどう揃えるか
  • Pythonへのキャッチアップ
  • コーディングルール

環境をどう揃えるか

基本的なLinuxサーバにはPython2系がデフォルトでインストールされています...がPython3系を使う方がベターです(詳しくは割愛)。 また、各種ライブラリやバージョンも開発環境、ビルド環境、本番環境で揃えたいですよね。

Pythonのバージョンはそれほど難しい問題ではありません。AWSであればcloud-initを用いてインスタンス作成時にインストールすれば良いですし、puppet/ansible などを使うのも良いでしょう。どちらにしろ、簡単に自動化出来る部分です。

ライブラリについてはpip, venv を利用するのが良いでしょう。本番環境で利用するrequirements.txt を作成し、ビルド環境や開発環境ではそれを利用したvenvを作成すればライブラリを揃えることが出来ます。これも大きな問題にはならないでしょう。

Python へのキャッチアップ

Python 自体がそれほど難しい言語では無いので、特にアプリエンジニアにとっては、キャッチアップのコストはあまり問題にはなりません。
むしろ、上で書いたように shell が中々難しい言語なので、キャッチアップへのコストという面では大きく変わらないと思います。

とはいえ、最低でも1人はエバンジェリストがいると良いですね。エバンジェリストがメンバーにトレーニングを行い、その内容をドキュメントに残して、New Comer へのトレーニングはメンバーが行う...という流れを作ることが理想的ですね。

// というか「shellは出来て当たり前」みたいな雰囲気の中、ロクにトレーニングが行われないことのほうが多いし、大きな問題では...???

コーディングルール

Python には pep8, pylint などのというコーディングルールが有るので、それを利用して適宜カスタマイズしていけば良いでしょう。

Pythonのスタイルガイドとそれを守るための各種Lint・解析ツール5種まとめ! - SideCI Blog

  • 変数名は snake_case, クラス名は CamelCase
  • 1行 80 字以内
  • 1メソッドの中のローカル変数は20個まで

などのルールが定義されています。デフォルトでは中々煩いルールも有る(変数名は3文字以上、メソッドドキュメント必須など)ので、プロジェクトのサイズやメンバーのレベルに合わせて適宜カスタマイズすることをおすすめします。 また、コーディングルールに準拠しているかどうかのテストはCIと組み合わせることも容易なので、「コーディングルールに準拠していなければmasterへのマージが出来ない」などの仕組みを作ることも出来ます。

おわりに

  • 正直、自分がshellが苦手ということも大きいのですが、難しいことを無理やりshellでやろうとして、保守性の低い"芸術的ワンライナー"が量産されることへの疑問が有ったのでこの記事を書きました
  • それなりにロジックを必要とする処理を書くのであればPythonの方が書きやすいし読みやすい→どうせなら全部Pythonで書けば良いんじゃない?というのが自分の意見です。
  • Python も shell もそれぞれ難しさはあります。が、プロジェクトとして採用するならPythonの方かと思います。
    • Pythonは「環境構築やコーディングルール」といった仕組みの部分が難しい→仕組みさえ作れば後は簡単。
    • shell は「可読性を高く保つこと、ミスなく書くこと」が難しい→個人のスキルに依存し続ける

長くなりましたが、以上です。実際に運用スクリプトを書く時のargparselogging の使い方についても記事を書きたいと思います。

【読書メモ】アナタはなぜチェックリストを使わないのか?

ミスを減らし、良い決断をするためのチェックリスト

  • 上司からオススメされた「アナタはなぜチェックリストを使わないのか? 」という本が非常に面白かったので共有します。
  • チェックリスト作りのHow to が書かれているようなタイトルですが、中身としては「ミスを減らし」「良い決断をする」道具としてチェックリストが有用であるということが書かれています。
  • 結果としてチェックリストの作成・運用を成功させるためのエッセンスを得ることが出来ます。巻末にある「チェックリストを作るためのチェックリスト」は非常に興味深いです。

何が問題なのか?

高度で複雑な問題を解決する必要のある職業・プロジェクト(e.g: 外科手術、高層ビルの建築、一流レストラン...)では以下の問題が共通して存在します。

  1. うっかりミス
  2. 問題が複雑であり、専門分化が進んでいる
  3. 常に予測不可能なことが起こる

うっかりミス

うっかりミス、というのは残念ながらよく起こります。人間の記憶力は有限かつ脆弱なので「やらなきゃいけないことが漏れる」「やり方を間違える」リスクは常に存在します。

さらに人間は怠惰なので「手順を省略する誘惑」が付きまといます。「慣れている作業だから」「急いでいるから」といった様々な理由(言い訳)で本来やるべきことを省略してしまい、失敗に終わってしまうケースも多いのです。

特に「権力を持ったキーマン(e.g: 外科手術における外科医)」がいて、その人を引き止める権限を誰も持っていない場合は非常に危険です。キーマンが誘惑に負けてしまった場合、チームとしてミスを止めることが不可能になるからです。

問題が複雑であり、専門分化が進んでいる

複雑かつ多様な問題が発生する業界では、一人の人間があらゆる分野に精通し、全ての問題を解決することは不可能になっています。そこで、分野を細かく分け各々がそれぞれの専門分野を深く理解する「スペシャリスト」になり、スペシャリスト同士が協力しながら問題を解決していく必要があります。

そこで問題になるのは「コミュニケーション」です。それぞれのスペシャリストが漏れなく、実りのあるコミュニケーションを取ることが出来なければ、ミスが起こったり、誤った決断をしてしまったりします。

常に予測不可能なことが起こる

高度な問題に取り組む時に「前もって予定していた通りに全てが上手くいく」ことは多くない、というかむしろ極めて稀です。大抵は実行している中で想定外の事象が発生し、それに対する対処が必要となります。

想定外の事象に対処するにはコミュニケーションが非常に重要になってきます。限られた時間の中で対処法を模索するには、メンバーのコミュニケーションが円滑に行われる必要があります。さらに言えば「円滑にコミュニケーションを行える」ための土台が「事前に」作られている必要があるのです。

チェックリストの効果

ミスを減らす

あらかじめ必要な手順を記したチェックリストを利用することでミスを減らすことが出来ます。そして、それ以上に重要なことはチェックリストを利用して「権力を再配分できる」点です。

「チェックする人」を「権力を持ったキーマン」と分けることにより、手順を省略する誘惑にチームとして戦うことが出来るからです。あらかじめ「チェックする人の権限」を保証することにより、キーマンによるミスを止めることが出来るのです。 本の中では「手術室の看護師」がメスの上にカバーを置き、チェックリストが埋まってからカバーを取り外し、外科医が手術を開始するというルールを保証するという例が挙げられています。

コミュニケーションを円滑にする

「チェックリストを埋める」という儀式自体がまずコミュニケーションになるので、その後のコミュニケーションを円滑にする役割を果たします。特に、「予測不可能なことが起こることが予測される(Known Unknown)」状況では事前にチェックリストを読み合わせてコミュニケーションを円滑にしておくことで、実際に予測不可能なことが起きた時のチームとしての対処能力を高めることが出来ます。

また「コミュニケーションを取る」という項目をチェックリストに入れることでコミュニケーションが漏れることを防ぐことが出来ます。 高層ビルの建築では「各工程における手順のチェックリスト(ミスを減らすためのチェックリスト)」とは別に「何時までにXという工程について誰と誰が議論する」という「コミュニケーションのためのチェックリスト」が作成され、コミュニケーションが漏れたり遅れたりすることを防いでいます。

チェックリスト作成時に気をつけること

いつ使うかを定義する

チェックリストには「行動、のち読む」「読む、のち行動」の2種類があり、状況によって使い分けます。本の中では「一時停止点(Pause Point)」という単語が使われていますが、予め一時停止点を共有しチェックリストの運用が確実に行われることを保証する必要が有ります。

項目を増やしすぎない

項目が多くなるほど、作業効率を下げてしまうとともに「省略する誘惑」が湧きやすくなります。具体的には「一時停止点あたり9項目まで」と定められています。本当に必要な項目に絞ってチェックリストを作成することで、作業を過剰に止めることなくミスを防ぐことが出来ます。

実運用の中で検証・更新する

チェックリストを作成したら実運用の中でテストをして、「項目に過不足がないか」「タイミングは適切か」「所要時間は適切か」などを検証します。また、時間とともにチェックすべき内容は変化するので、定期的にテストを行って項目を更新していくことも大切です。

// 当たり前といえば当たり前ですが、実際には行われていないケースが多いように思います。 // 現場にそぐわない膨大な項目のチェックリストが作成され、面倒になって利用されなくなって腐る...みたいな。

おわりに

IT業界の話は出てきませんでしたが応用の効く内容が多いように感じました。本番作業・障害対応は外科手術や旅客機の運行から学ぶところは多いですし、プロジェクト推進については高層ビルの建築と非常に似た問題(クリティカルパスが多い)を抱えています。

繰り返しになりますが、チェックリスト作りのHow to ではなく、「ミスを減らし」「良い決断をする」道具としてチェックリストが有用であることが書かれている本です。

エンジニアには単純な技術力だけでなく「ミスを減らす」「コミュニケーションを確実に取る」といったソフトなスキルも必要で、後者の部分について考える良いキッカケになりました。若手エンジニアには非常におすすめの一冊です。

【AWS】【Python】EC2インスタンスをCPU使用率の高い順に表示するLambdaスクリプト

EC2インスタンスをCPU使用率が高い順に表示する

  • 性能テストなどを行っていて、ボトルネックとなっているインスタンスを探したい
  • cloudwatchの結果をクエリして「当該時間帯にCPU使用率が高かったインスタンス」を探す
  • tabulateを利用して良い感じに表示する

IAMロール、ポリシー

以下の2つのbuilt-inポリシーをアタッチしたロールを作成し、Lambdaにアタッチします。

  • AWSLambdaExecute
  • AmazonEC2ReadOnlyAccess

tabulate を使えるようにする

表示にはtabulateを使いたいのですが、Lambdaではpipを利用することが出来ません。以下の記事に従って、tabulateを利用できるようにします。

【AWS】Lambdaでpipしたいと思ったときにすべきこと - Qiita

テストイベントの設定

引数には調査対象の環境(env)と、時間帯(timebox)を渡します。timeboxにはISOフォーマットを利用し、timezoneを設定することも可能です。以下のサンプルではJST(+09:00)でtimeboxを指定しています。

{
  "env": "test1",
  "timebox":{
      "start": "2018-02-23T12:30:00+09:00",
      "end":"2018-02-23T13:00:00+09:00"
  }
}

コード

Python 3.6 で書きます。

import datetime

import boto3
import dateutil.parser as parser
from tabulate import tabulate


def lambda_handler(event, context):
    ec2_client = boto3.client('ec2')
    full_info = ec2_client.describe_instances(
        Filters=[
            {
                'Name': 'tag:env',
                'Values': [event.get('env')]
            }
        ]
    )
    instances = []
    for r in full_info['Reservations']:
        for i in r['Instances']:
            instances.append(i)

    cw_client = boto3.client('cloudwatch')
    timebox = event.get('timebox', {})
    if not timebox:
        now = datetime.datetime.utcnow()
        timebox['start'] = now - datetime.timedelta(seconds=600)
        timebox['end'] = now
    else:
        timebox['start'] = parser.parse(timebox['start'])
        timebox['end'] = parser.parse(timebox['end'])

    ret = []
    for ins in instances:
        metrics = cw_client.get_metric_statistics(
            Namespace='AWS/EC2',
            MetricName='CPUUtilization',
            Dimensions=[{'Name': 'InstanceId', 'Value': ins['InstanceId']}],
            StartTime=timebox['start'],
            EndTime=timebox['end'],
            Period=60,
            Statistics=['Average']
        )
        # get latest data
        latest = None
        for d in metrics['Datapoints']:
            if not latest:
                latest = d
            else:
                if latest['Timestamp'] < d['Timestamp']:
                    latest = d
        if not latest:
            continue
        ret.append({
            'name': [t['Value'] for t in ins['Tags'] if t['Key'] == 'Name'][0],
            'instance_id': ins.get('InstanceId', ''),
            'private_ip': ins.get('PrivateIpAddress', ''),
            'CPU_usage': latest['Average'],
            'timestamp': latest['Timestamp'].isoformat()
        })
    ret.sort(key=lambda d: d['CPU_usage'], reverse=True)

    print(tabulate(ret, headers="keys"))
    return ret

補足と解説

dateutil.parser を利用してISOフォーマットの時刻文字列をdatetimeに変換できます。

    timebox = event.get('timebox', {})
    if not timebox:
        now = datetime.datetime.utcnow()
        timebox['start'] = now - datetime.timedelta(seconds=600)
        timebox['end'] = now
    else:
        timebox['start'] = parser.parse(timebox['start'])
        timebox['end'] = parser.parse(timebox['end'])

Dimensions で InstanceId 、StartTimeEndTimeで時間帯を指定してcloudwatch のデータをクエリします。

    for ins in instances:
        metrics = cw_client.get_metric_statistics(
            Namespace='AWS/EC2',
            MetricName='CPUUtilization',
            Dimensions=[{'Name': 'InstanceId', 'Value': ins['InstanceId']}],
            StartTime=timebox['start'],
            EndTime=timebox['end'],
            Period=60,
            Statistics=['Average']
        )

CPU使用率が高い順にデータをソートして、tabulate で綺麗にprintします。headerにはデータのkeyを指定します。

    ret.sort(key=lambda d: d['CPU_usage'], reverse=True)

    print(tabulate(ret, headers="keys"))

出力結果

tabulateを利用することで、綺麗に表示されます。(サーバー名、instance_idなどはマスクしました)

name       instance_id          private_ip      CPU_usage  timestamp
---------  -------------------  ------------  -----------  -------------------------
server1    i-1a1a1a1a1a1a1a1a1  111.1.1.11      13.234     2018-02-23T04:28:00+00:00
server2    i-2b2b2b2b2b2b2b2b2  111.1.2.22       7.854     2018-02-23T04:27:00+00:00
server3    i-3c3c3c3c3c3c3c3c3  111.1.3.33       5.342     2018-02-23T04:28:00+00:00
server4    i-4d4d4d4d4d4d4d4d4  111.1.4.44       3.47458   2018-02-23T04:29:00+00:00
server5    i-5e5e5e5e5e5e5e5e5  111.1.5.55       1.208     2018-02-23T04:28:00+00:00
server6    i-6f6f6f6f6f6f6f6f6  111.1.6.66       0.95      2018-02-23T04:28:00+00:00
server7    i-7g7g7g7g7g7g7g7g7  111.1.7.77       0.758     2018-02-23T04:27:00+00:00
server8    i-8h8h8h8h8h8h8h8h8  111.1.8.88       0.738     2018-02-23T04:27:00+00:00
server9    i-9i9i9i9i9i9i9i9i9  111.1.9.99       0.730958  2018-02-23T04:29:00+00:00
server10   i-10j10j10j10j10j10  111.1.10.110     0.6       2018-02-23T04:29:00+00:00
server11   i-11k11k11k11k11k11  111.1.11.121     0.4       2018-02-23T04:29:00+00:00
server12   i-12l12l12l12l12l12  111.1.12.132     0.38337   2018-02-23T04:26:00+00:00
server13   i-13m13m13m13m13m13  111.1.13.143     0.36428   2018-02-23T04:29:00+00:00
server14   i-14n14n14n14n14n14  111.1.14.154     0.35      2018-02-23T04:26:00+00:00
server15   i-15o15o15o15o15o15  111.1.15.165     0.324     2018-02-23T04:29:00+00:00

おわりに

  • 性能テストや障害時に「ボトルネックとなっているインスタンスを特定 -> private ip を見てssh」といった流れをスムーズに出来るようになりました。

  • 使用するメトリクスを変えることで、ネットワーク使用率やディスクI/Oなどを見ることも出来るので、中々便利なものだと思います。

  • 3rd Partyのライブラリの利用方法や、データ操作/表示のやり方を学べたので個人的にも良い勉強になりました。

【AWS】【Python】Lambdaを利用して他のAWSアカウントのEC2インスタンスを再起動する

Lambdaを利用した他アカウントの操作

やったことの流れは以下の通り。

  • IAMロール/ポリシーの設定、信頼関係の編集
  • 他のアカウントのIAMロールにSTSしてEC2クライアントを取得
  • tagの値で対象のインスタンスを抽出し、再起動

IAMロール/ポリシーの設定

Lambdaにアタッチするロールと、他のアカウント(操作対象のインスタンスを保持しているアカウント)側のIAMロールの2つを編集する必要があります。

LambdaのIAMロール/ポリシー

今回のケースで必要な権限(ポリシー)は以下の2つです。

  • AWSLambdaExecute (built-in):
    Lambda実行のための基本的な権限です。具体的には、cloudwatch logs へのフルアクセス、S3へのPut/Get

  • 他のアカウントのIAMロールへのSTS権限:
    他のアカウントのIAMロールへの一時的な認証情報を取得(STS)してインスタンスのコントロールを行うために、Lambda側には「他のアカウントのIAMロールへのSTSを行う権限」を与える必要が有ります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "arn:aws:iam::999999999999:role/OtherAccountRole"
            ]
        }
    ]
}

他のアカウント側のIAMロール/ポリシー

他のアカウント(操作対象のインスタンスを保持しているアカウント)では以下のポリシーを持ったIAMロールを作成します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:RebootInstances"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

また、このIAMロールの信頼済みエンティティに、上記のLambdaロールを追加する必要が有ります。IAM画面から信頼関係の編集を行い、以下のように編集します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:role/RebootLambdaRole"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Lambda のコード、テストイベント

Python3.6で書きます。

ソースコード

'''
AWS Lambda script for reboot instances.
This scripts need STS for IAM Role on Another Account.
'''

import boto3


def lambda_handler(event, context):
    sts_client = boto3.client('sts')
    sts_res = sts_client.assume_role(
        RoleArn=event['sts_target'],
        RoleSessionName='RebootOtherAccount'
    )
    cred = sts_res['Credentials']

    ec2_client = boto3.client(
        'ec2',
        aws_access_key_id=cred['AccessKeyId'],
        aws_secret_access_key=cred['SecretAccessKey'],
        aws_session_token=cred['SessionToken'],
    )

    full_info = ec2_client.describe_instances(
        Filters=[
            {
                'Name': 'tag:need_reboot',
                'Values': ['true']
            }
        ]
    )
    
    instance_ids = []
    for r in full_info['Reservations']:
        for i in r['Instances']:
            instance_ids.append(i['InstanceId'])

    return ec2_client.reboot_instances(
        DryRun=event.get('dry_run', True),
        InstanceIds=instance_ids,
    )

テストイベント

{
  "dry_run": true,
  "sts_target": "arn:aws:iam::999999999999:role/OtherAccountRole"
}

解説と補足

  • テストイベントから取得した他アカウントのIAMロールへのSTSを行い、取得した認証情報を利用してEC2クライアントを作成します。STSを利用して他アカウントのリソースにアクセスする場合は、clientの初期化時に以下のようにする必要があります。
    cred = sts_res['Credentials']

    ec2_client = boto3.client(
        'ec2',
        aws_access_key_id=cred['AccessKeyId'],
        aws_secret_access_key=cred['SecretAccessKey'],
        aws_session_token=cred['SessionToken'],
    )
  • 対象となるインスタンスをtagの値でフィルターします。ここではneed_reboot というタグにtrueと書いてあるインスタンスを抽出しています。
full_info = ec2_client.describe_instances(
        Filters=[
            {
                'Name': 'tag:need_reboot',
                'Values': ['true']
            }
        ]
    )
  • 細かいですが、DryRunフラグはちゃんと付けておきたいですね。
    return ec2_client.reboot_instances(
        DryRun=event.get('dry_run', True),
        InstanceIds=instance_ids,
    )

終わりに

以上です。IAMロールの設定のところで結構ハマりましたが、IAMロール/ポリシー、信頼関係などについて学ぶことが出来ました。 業務ではこんな感じのLambdaを書くことが増えたので、ブログの方にもメモを残していきたいと思います。

【GitLab】【Python】GitLabにユーザーを一括で登録、グループ参加

GitLab API を使ってユーザー一括登録

必要なもの

  • Python3
  • GitLabのAdminユーザーのprivate access token

python-gitlab のセットアップ

  • pip を利用してpython-gitlab のインストール
pip install python-gitlab 
  • 以下のような設定ファイル(python-gitlab.cfg)を用意
[default]
url = https://gitlab.somewhere.com/
private_token = AdminAccessToken
timeout = 5
ssl_verify = True
api_version = 4

ソースコード

#!/usr/bin/env python3

import argparse
import csv
import gitlab


def add_user(api, csvfile):
    with open(csvfile, 'r') as cf:
        csvreader = csv.reader(cf, delimiter=',', quotechar='|')
        for row in csvreader:
            params = {}
            params['email'] = row[0]
            params['username'] = row[1]
            params['name'] = row[2]
            params['reset_password'] = True
            user = api.users.create(params)
            print('User:{} has created'.format(user))


def add_user_to_group(api, csvfile):
    all_group = api.groups.list()
    with open(csvfile, 'r') as cf:
        csvreader = csv.reader(cf, delimiter=',', quotechar='|')
        for row in csvreader:
            user = api.users.list(username=row[0])[0]
            for i in range(1, len(row)):
                group = [g for g in all_group if g.full_path == row[i]][0]
                params = {}
                params['user_id'] = user.id
                params['access_level'] = gitlab.DEVELOPER_ACCESS
                group.members.create(params)
                print('User:{0} has joined in {1}'.format(user.name, group.name))


if __name__ == "__main__":
    parser=argparse.ArgumentParser()
    parser.add_argument('--csvfile')
    parser.add_argument('--group', action='store_true')
    args=parser.parse_args()

    api=gitlab.Gitlab.from_config(config_files=['./python-gitlab.cfg'])
    gl.session.proxies = {
          'http': 'http://yourproxy.com:9090',
          'https': 'https://yourproxy.com:8443'
    }

    if args.group:
        add_user_to_group(api=api, csvfile=args.csvfile)
    else:
        add_user(api=api, csvfile=args.csvfile)

補足と解説

  • 設定ファイルを読み込んでGitLabのAPIを初期化して、そのapiを利用してユーザー登録/グループ参加 を行います
  • Proxy環境で使いたいときは、apiにproxy設定をいれます
    api=gitlab.Gitlab.from_config(config_files=['./python-gitlab.cfg'])
    gl.session.proxies = {
          'http': 'http://yourproxy.com:9090',
          'https': 'https://yourproxy.com:8443'
    }
  • ユーザー登録時にパスワードを設定させるメールを送るようにしています
            params['reset_password'] = True

決まった初期パスワードを設定したいときは以下のようにします

            params['password'] = 'secret_p@ssw0rd'
  • Group に追加するときはDeveloper として登録されます
                params['access_level'] = gitlab.DEVELOPER_ACCESS

使い方

ユーザー登録

  • 以下のようなcsvファイルを用意する。左からメールアドレス、ログインID、表示名です。
tom.brady@patriots.com,tom.brady,Tom Brady
david.andrews@patriots.com,david.andrews,David Andrews
patrick.chung@patriots.com,patrick.chung,Patrick Chung
./add-user --csvfile filename.csv

ユーザーのグループ参加

  • 以下のようなcsvファイルを用意する。一番左にユーザーのログインID、右に登録したいグループをフルパスで並べます。
tom.brady,offence
david.andrews,offence,offence/lines
patrick.chung,defence
./add-user --group --csvfile filename.csv

終わりに

  • やっつけでつくったものですが、それなりに綺麗にかけたかなと。
  • グループ登録時の権限指定はファイルに書くようにしたいですね。
  • コードやサンプルファイルはGitHubに置きました。よろしければご利用ください。

github.com

業務ではPythonを使うことが多くなってきました。こういうことをパッと出来ると成長を感じますね。 AWSのLambdaなども書く機会が増えてきたので、Python繋がりでそういう記事も書きたいです。