イベントはじめにこんにちは、初投稿になります。今年の1月からエニグモでデータサイエンティストをしている堀部と申します。Expertとしてはまさかの最上位という結果でした。コンペ前、コンペ中、コンペ後に分けて振り返ります。※本記事ではコンペのデータやタスクについては情報公開が難しいため触れません。以前のコンペや業務で使っていたコードをなるべくクラス・メソッド化してgitにまとめて整理しました。綺麗なコードとは言えないですがまとめておいたおかげで、新しい特徴量の作成やタスクに特化したデータの前処理に時間を割くことができました。汎用的な特徴量生成の処理やGBDTモデルの実験をやりやすくする下記のようなコードをまとめていました。素敵な環境でしたサイバーエージェントさんのできたてほやほやのオフィスのスクランブル交差点の見える席でもくもくと作業しました。頭を使いすぎて最後2時間くらいは少し頭がぼーっとしてました。コンペティションサイトの出来が素晴らしかったです。○aggleと違ってすいすいsubmitができストレスなく作業に集中することができました。LBも伸びたら採用ひたすら、2,3を繰り返すモデルはずっとlightgbmシングルでアンサンブルはしませんでした。lightgbmのハイパーパラメータはcat_smoothのみ手動チューニングしました。tunerを使って一度パラメータ探索をした結果を最後まで使いました。コンペが終わってすぐ結果発表&表彰式がありました。上位3名の方の解法を聞くことができました。その特徴量効きましたよねと共感したり、そのアイディアは思いつかなかったなとメモしたりすぐに情報共有できるオフラインコンペならではのよさを実感しました。その後の懇親会でも当日のコンペだけでなく別のコンペについての取り組みについても話を聞くことができ、普段からコンペに参加しておくメリットも感じました。次の日は日曜日だったので表彰式や懇親会での話を受けて思いついたアイディアを実装して追試をしていました。考えればこの特徴量は要らないかもと思ったものを削除しただけで、スコアが伸びたりとなぜコンペ中に気がつかなかったのかと後悔もありました。上位の方が公開してくれたコードやgitを見て自分のコードの汚さに愕然としました。普段から綺麗なコードを書く、他の人のコードを見て学ぶということを日頃から意識しようと胸に刻み込みました。最後に(真剣に取り組んだ)コンペの数だけ強くなる、強い人に会いに行くという大切さ、オフラインコンペならではの大変さ、楽しさを味わうことができたとても充実した一日でした。次回があればまたぜひ参加したいです。入社して1ヶ月が経ちましたが楽しい日々を過ごしています。ABテストの効果検証×傾向スコア、商品のクラスタリング、回帰予測モデリングなど、自分で思いついてビジネスに貢献できそうなものであれば自由に取り組んでよいボトムアップの環境です。Lookerなど、ツールへの投資が当たり前のように行われていることによさを感じています。コンペ以上に業務も楽しく取り組める強いチームをつくれたらと思っております。興味のある方はぜひ気軽にご応募ください。選考とは別にカジュアル面談も受け付けております。データサイエンティスト、データアナリスト、データエンジニア、検索エンジニアなど各種募集中です。BUYMAの検索周りを担当しているエンジニアの伊藤です。BUYMAではSolrを利用した検索システムがいくつかあります。今回はデータボリュームが一番大きい検索システムをターゲットとして、インフラ周りを含め全面的にシステムの刷新を行いました。既存の検索システムがどういったものだったのかなぜシステム更改が必要だったのか(どういう課題があったのか)更改後の検索システムはどういったものか今後の課題について等々についてご紹介したいと思います。既存の検索システムについて既存の検索システムは下記の通り、シンプルという点ではとても素晴らしいものでした。下記のような問題を抱えている状況でした。スケールアウトしない構成であるスケールアップの限界Solrのバージョンが古い本番環境のデータを利用した検証が難しい開発担当者にもSolrの知識が必要とされる問題に気付けない以降でその内容について補足していきます。スケールアウトしない構成である検索システムはデータ増とアクセス増の2軸でスケールアウト構成を取る必要があります。アクセス増にはSolrサーバを追加することで対応できるようにはなっていましたが、該当の検索システムはアクセス数というよりデータが多いことが特徴でこのデータ増に追従出来なくなっていました。GCが頻発するようになっていたため、暫定対処としてAWS側に移行し、GCチューニングを行いました。この対応も一時的なもので、データ数自体は日々増加している状況の中で使用しているGCのアルゴリズム的に、メモリを増やしてどうこうできる状況ではなくなっていました。STWのインパクトが大きくなるという負のループに陥るSolrのバージョンが古い既存の検索システムで利用しているSolrのバージョンは3.2.0でしたバージョンが古くても通常時の運用にはそれほど支障はありませんが、何か問題があった場合の調査が大変でした(ドキュメントが見つからない)またSolrのバージョンupに伴いindexing/検索性能はupしていますので、性能面でのデメリットであったり以降のバージョンで提供された様々な機能が利用できない等のデメリットがありました本番環境のデータを利用した検証が難しい既存システムで本番環境相当のデータで検証する際にはindexing処理だけで数日かかるという状況で、環境構築自体がハードルとなっていました。開発担当者にもSolrの知識が必要とされるSolrへの検索時はphp、rubyといったクライアント側でSolrのクエリを組み立てて実行していました。クライアント側を担当する開発担当者はその時々で変わり、ある程度はSolrを知っている人もいれば、全く知らない人まで様々です。Solrのクエリは書き方によってはキャッシュを使えていなかったり、または逆にキャッシュを無駄に汚してしまったりと、同じ検索結果を得るにしてもある程度Solrの知識を必要とします。問題に気付けない必要なデータやログが適切に可視化され監視されていないため、サービス影響、ユーザ問い合わせがあって初めて問題に気付くケースがありました更改後の検索システム上記で挙げた課題感を解決するために設計したアーキテクチャが下記になります。耐障害性の向上プラットフォームにKubernetesを採用し、想定外のアプリケーション停止、ノードdown時にもセルフヒーリングで自動復旧可能マイクロサービス化(隠蔽化)アプリケーション〜Solr間に検索APIを新たに用意。これによりアプリケーション側はSolrの独自クエリを意識することなく、検索APIを介したシンプルなI/Fをベースとした開発が可能に。今回せっかくシステム更改するならCloudNativeなシステムにしたいという思いがありこのような構成にしました。監視、ログ運用周りも今回の対応と合わせて刷新したことで対応範囲が広くなり苦労した点も多くありました。ここではいくつかピックアップして紹介したいと思います。Solr/ZookeeperをKubernetes上に構築、運用した経験がないSolrに関してはあまり構築事例もなく問題に遭遇した際の解析に難航リソース設計、ログ周りなど、コンテナ/Kubernetesだからこそプラスαで考慮しなければ行けない点が多々あった監視、ログ運用基盤の検討コンテナ/Kubernetesベースのシステムをどう監視すべきか?ログはどう扱うべきか?検索APIの設計、開発I/F仕様どうするか?開発しながらSolr/Zookeeper周りの設定や問題対応をするのが辛いSolrバージョンアップによる未知の問題対応indexing処理周りで問題多発SolrCloudかつDIHで並列indexing処理を行うこと自体が事例がなく、色々とはまる今後に向けてシステム更改により当初挙げていた課題感というのは概ね解決することが出来ました。今回のようなシステムを構築、運用したことでてきた新たな課題や、まだまだ改善が必要だなと思えるところもあります。indexing処理などはクライアント側の処理と密になっている箇所がまだまだあり、今後もブラッシュアップが必要Solrのような複雑かつstatefulなアプリケーションは運用がやはり大変。operatorを利用するなどして運用をもっと楽にしつつ、属人性もなくしていきたい。パーソナライズサーチなど)や自然言語処理や機械学習を活用したサービス導入なども積極的に行っています。今回技術的なところにはほぼ触れませんでしたが、ここどうなってるの?など少しでも興味、関心を持たれたら是非お気軽にお話だけでも聞きにいただければと思います。データサイエンティストはじめにエニグモでデータサイエンティストを名乗っている庄子です。今年の振り返りも兼ねてのポエムとなります。データサイエンティストが活躍するためのスキル要件として、いくらでも切りようがあると思いますが、特に自分自身に感じている課題について、4つの力という観点で書きたいと思います。提案力PoCとして小規模のデモを行うそのデータサイエンスのアウトプットが使えそうか、事業に詳しい人に想像してもらう実際にデータサイエンスを使って問題解決できそうな場合も、実際にやってみないと分からないですし、得られたアウトプットが事業に有効かどうかを、事業に詳しい人に意見をいただいた方が良いでしょう。筋が悪そうな分析は早めに判断してもらうためにも、なるべく小規模でPoCを行います。本年度の振り返りとしては、このPoCの手数がもっとあった方が良いと感じました。データサイエンティストが使う手法でできることは、データサイエンティストがもっとも良く分かるはずです。使われる手法に関して詳しい人がデータサイエンティスト以外のポジションを担っているケースもあると思いますが、そのケースはここでは考えないことにします。課題設定が済んでおり、入出力が決まっているテーブルデータに対しては、AutoMLを十分ですし、AutoMLに変えられるようなタスクをこなすだけであれば、データサイエンティストは価値を発揮できません。課題設定と分析を繰り返し、仮説検証を繰り返しながら課題設定の質を上げるスキルこそがデータサイエンティストの価値を差別化すると思います。提案を誤解なく伝えるために、共通言語を関係者と揃える使う手法や方法論を伝える概念を伝える伝えたことをドキュメントとして残し、説明の場にいなくてもそのドキュメントを読めば共通言語が理解できるようにする関係者が運用している具体的なデータを使って示す共通言語を揃える場合は、最初に自分が受ける説明として、こんな説明ならすっと理解できそうというのを思い出しながら、説明するということは普段気をつけているつもりでしたが、後から考えると考慮が不十分だと思うことは多々ありました。背景や経緯を共有できている前提から資料を作ってしまう場合です。分析レポートの報告の際に、なるべく普段気をつけていたはずだと思いますが、ドキュメントの作成や整備も、振り返るともっとやりようがあると思います。質問された点について補足を付け加えていくなどして、社内のナレッジの質を高め、リテラシーを共有できるのかと思います。定期的に情報交換の場を現場のメンバーと設けるこちらの取り組みを参考にし、ざっくばらんにデータ活用で解決してほしい課題について話すというランチミーティングを設定しました。insideこのランチミーティングをきっかけにとあるPoCに取り組みましたが、中途半端なところで頓挫してしまっている状況です。定期的にということも実現できていません。やったら効果的かもしれないと思うことをすぐに実現できず、せっかくヒアリングしたのに形にできないということに負い目を感じてしまった部分もあります。いずれにせよ、定期的にコミュニケーションを行うことで、風通しをよくする、心理的安全性を担保することが重要だったりするので、またどこかの機会で復活させていきたいと思います。現場での運用について体験させてもらう今年は取り組めなかったですが、ビジネス理解をもっとも得られる方法が実際に体験することにあると思います。データサイエンティストが共通言語の理解を得にいくということで矢印の向きは反対になりますが、先に挙げた共通言語を揃えるということにもつながると思います。得意な領域に専念し、タスクを切り出してより得意な人に任せることを考える実験的なコードを書くことに場合に専念する仕様が固まる、あるいはプロダクトに関わる部分はエンジニアに任せるプロダクトに乗るべきコードの要件はここで詳しく触れませんが、手元で自分が扱うコードが読みやすく、条件や機能の拡張がしやすいものだったら良いなと何度も思いました。そのようなコーディングを行うためには言語やフレームワークのベストプラクティスを踏まえている、専門家に任せた方がよいです。データサイエンスに関するプロジェクトを実行するにあたり、必要なスキルで全てに秀でるのは多くの人にとって現実的ではないでしょう。依頼をする内容を、その領域を詳しい人へ誤解なく橋渡しができるくらいの知識があるという前提は置きますが、なるべく、得意な領域に専念した方が、組織としてのアウトプット力は上げられるはずです。個人の領域として広くやれるのが弊社の良いところでもあるのですが、実験のためのコードを書く時間の割合を多くすることが、現在ネックになっていると思いました。タスクを分解する分解した単位で、インプットとアウトプットを明確にするこうやって書くと、基本的な仕事の進め方の話になりような気がします。達成基準が明確な、細切れなタスクに分解できるということは、仕事の依頼もしやすくなり、今後育成という観点でも必要になってくる力です。来て欲しい人に、その会社に来る魅力をきっちり伝える具体的なタスクや、データサイエンティストの要件を上手く伝える人的リソースを増やすために、データサイエンティスト自体の人員を増やすということも当然選択肢に入ると思います。今年度は、データサイエンティスト、および、データアナリストの採用にも、面接などを通し関わらせていただきました。活躍できそうな人を採用することが目的になりますが、活躍を期待できる人材ほど、他社からも内定が出ることは容易に想像できます。給与といった待遇面以外で考えるとすると、仕事内容やその会社で働くことの魅力を伝えるかどうかにかかっていると思います。そこで、ミスマッチをなくすということに務めてきました。課題と思っているとこと、なるべく包み隠さず伝えたということはできていたと思います。実際に業務を行うイメージを固めるために、面接官以外のメンバーとの面談の場を設けることも実現することができました。応募者の良いところを引き出して聞き出す会社のフェーズを考えると、データサイエンスに関する施策をドライブしてくれる人が活躍できそうという思いがあったため、提案力に優れている人を通す傾向が強かったですが、応募要件を上げてしまう大きな要因となっていたような気がします。今後は、始めは提案が少々不得意でも、技術力を活かして活躍できる人も採用したいという想いがあります。課題はたくさんある中で、その人の経験と相性が良さそうなタスクの質問をして、考えを掘り下げる、という質問の組み立てについても、改善していきたいと思います。できていた部分としては、データサイエンティストの業務経験が想定より不足していても、社内のメンバーとコミュニーケーションが安心して任せられ、学習やそれまでの業務への取り組みを伺い、間違いなく必要なスキルはキャッチアップできると判断できる方は面接を通していました。このケースでは応募者の良いところを上手く引き出せていたのではないかと思います。さいごにその1,2がコミュケーションに関する話題、その3,4が人的リソースに関する話題になります。大きな枠組の話題としては、如何にスキルや知識をアップデートするか、という日々のインプット・アウトプットも挙げられますが、また機会があれば書くかも、、しれません。今回できていないことを中心に書いてしまったせいか、ちょっと胃もたれしそうな気分になりました。やりたいのにやれてないことが多いということは、伸び代がすごい!とも言えるのかと思います。こんな私でも暖かく受け入れてくださり、(きっと)裁量を最大限に与えてくれる弊社なので、共に切磋琢磨してEnigmoをグロースさせたい仲間を絶賛募集中です。オブジェクト指向こんにちは、サーバーサイドエンジニアの平井です。昨年の1月にエニグモにインターンとして入社してから一年が経とうとしています。早いもので、新卒の肩書きもそろそろ無くなってしまいますね。今回は、Rubyによるデザインパターンを読んで、デザインパターンを勉強したので、そのアウトプットをさせていただきます。タイトルの通り、デザインパターンについて実際の使用例を探してみました。そのパターンと使用例は以下になります。StrategyパターンWardenObserverパターンrails-observersIteratorパターンactionpack/lib/action_dispatch/http/mime_type.rbBuilderパターンmastodonまずは、Strategyパターンから説明します。StrategyパターンStrategyパターンとはメソッドの中に溶け込んでいるアルゴリズムを別クラスとして分けて、切り替えができるようにするパターンです。サンプルコードを利用し、悪い例を修正していく形で、さらに、Strategyパターンについて説明していきます。endend問題点としては以下が挙げられます。output_reportメソッドについて、formatの種類が増えるにつれて、メソッドが長くなる。Strategyパターンを適用すると、以下のようになります。Report.new(HTMLFormatter.new)report.ouput_reportこのように、同じ目的(レポートをフォーマットする)を持ったオブジェクトを、ストラテジとして定義し、そのストラテジをごっそり交換できるようにするのがStrategyパターンです。ストラテジは同じインターフェースを持っているので、ストラテジの利用者(コンテキストと呼ぶそうです)は、どのストラテジを利用するかを知らずにすみます。適用前は、ReportクラスはHTMLとPlainText、それぞれの出力方式を知っていました。Strategyパターンを適用した後は、出力方式に関する知識はReportクラスからストラテジオブジェクトに移りました。実例Strategyパターンは、Wardenという認証フレームワークで使われています。github.comWarden::Strategies::Baseのサブクラスに認証の方法を実装することで、その認証方法を切り替えることができます。そのWardenを使った、Deviseというgem(ログイン方法を簡単に実装できる)のソースコードを見ると、実際にどのようにStrategyパターンが使われているのかを確認できます。下のソースコードを見るとわかるように認証するオブジェクトはwardenですが、認証方法に関する知識はDatabaseAuthenticatableクラスとRememberableクラスが持っています。endDatabaseAuthenticatableは、データベースにあるメールアドレスとパスワードで認証します。Rememberableは、クッキーからデータベースにあるレコードを探してきて認証します。ObserverパターンObserverパターンとはあるオブジェクトの状態の変化を、そのオブジェクト自身がその変化を知りたいオブジェクトに対して知らせる仕組みがObserverパターンです。システムの各部分が、あるオブジェクトの状態を知りたいとき、例えば、誰かの給料が変わった時に、その変更を経理部門が知る必要がある時を想定します。悪いパターンのサンプルコードを見ていきます。300このソースコードの悪い点は以下になります。他のオブジェクトが、財務情報を知る必要が出た時に、実際に変化したのは、Employeeではなく他のオブジェクトなのにも関わらず、Employeeクラスを修正する必要がある。Rubyによるデザインパターンに書かれていた設計原則として、変わるものを変わらなものから分離すると述べられています。この原則を当てはめて、変わるもの(誰がtaroの財務状況を知る必要があるか)を変わらないもの(Employeeクラス)から分離させます。Observerパターンを当てはめると以下のようになります。300このように、ニュースを持っているオブジェクトとそれを受け取る側にきれいなインターフェースを作るアイデアがObserverパターンです。ニュースを持っているオブジェクトはSubjectと呼ばれ、それに関心のあるオブジェクトはオブザーバーです。今の状態だと、Employeeはどれだけのオブザーバーが自分の給料の変更に関心があるかを知らなくて済みます。新しくtaroの給料の変更を知る必要があるオブジェクトが出てきた場合は、そのオブジェクトがオブザーバーとしての共通のインターフェースであるupdateメソッドを実装して、add_observersするだけで、Employee自身は何も変化しません。実例こちらは実際に使われている例ではなく、rails-observersを使うと簡単にActiveRecord用のobserverクラスを作ることができるという説明をします。endendこの場合、Comment#saveが終了した際に、after_save内の処理を実行します。サンプルコードでは、Subjectのセッターに、オブジェクトが変更した際に実行するメソッドを追加する必要がありましたが、rails-observersを使えば必要ありません。IteratorパターンIteratorパターンとはIteratorパターンとは集約オブジェクトが子オブジェクトのコレクションにアクセスする方法を外部に提供するテクニックです。外部イテレーターと内部イテレータがあります。外部イテレーターについて、サンプルコードを使って説明します。blue外部イテレーターの場合は上のように、子オブジェクトに対して処理内容を伝えます。その処理内容を子オブジェクトに伝えます。内部イテレーターが使われている箇所を、railsのソースコード内のactionpack/lib/action_dispatch/http/mime_type.rbから簡単に探すことができました。他の処理end上の例の場合、Mimesクラスの子オブジェクトに対して処理内容を伝える場合は、コードブロックを利用します。selectなどの配列を走査する際に便利なメソッドを使うことができます。actionpack/lib/action_dispatch/http/mime_types.rbに下のような処理がありました。他のMimeタイプをMimeクラスに追加する処理が続くTypeクラスのregisterメソッド内でMimesクラスに追加をする処理があり、actionpack/lib/action_dispatch/http/mime_types.rbでMimeタイプを追加していました。BuilderパターンBuilderパターンとはオブジェクトの構築のロジックに対して責任をもつBuilderを作り、そのBuilderを使ってオブジェクトを作成するパターンです。例としては、以下になります。builder.computer上の場合は、Computerを作るために必要なcdのsizeが760であるというような、Computerクラスのインスタンスを作成するためのロジックが、ComputerBuilderのクラスに集まっています。Computerを作るための実装の詳細をBuilderクラスが隠蔽しています。実例BuilderパターンはBuilderでグレップすれば良いので、簡単に見つけることができました。以下のソースコードは、mastodonという短文投稿型のSNSサイトのソースです。他のメソッドが続くRSSとは、Webサイトのニュースやブログなどの、更新情報の日付やタイトル、その内容の要約などを配信するため技術で、XML形式で記述されます。このRSSBuilderは、titleがchannelの子要素であるなどのXMLの構成の詳細を隠蔽しています。このRSSはapp/serializers/rss/account_serializer.rb上でRSSBuilderクラスを使って作成されています。endこのAccountSerializerクラスはRSSのxmlの構成の詳細を知りません。タイトルやlinkの情報を与えるだけでRSSを完成させることができます。まとめRubyによるデザインパターンを読んでみて、実際の使用例を探してみようと意気込んでみたものの中々見つけることができませんでした。普段から、ソースコードを読む際にデザインパターンを意識して読んでみるのもアリなのかなと思いました。実際に使用例を見つけるためのソースコードリーディングを通して、デザインパターンに対する理解を深めることができただけでなく、雑多に新たな知識を得ることができました。していたことをはっきり理解できた時の快感がソースコードリーディングの楽しみだなと改めて感じたので、積極的に読んでいこうと思いました。最後まで読んで頂き誠にありがとうございました。Rails)に追加した機能で、STI、ポリモーフィック関連を使ってみたので、どういう設計にしたかを書いていこうと思います。※使ってみたって話で、それぞれどういう特徴なのかなどの詳しい説明はしておりません。どんな機能作ったの?「〇〇キャンペーン」などの施策で、その日あった取引の中で特定の条件(商品ID、カテゴリーID、何円以上など)のものを絞り込み、その対象の取引に対して特定のアクションをさせます。今回はこの機能の「特定の条件で絞る」の設計を説明していきたいと思います。設計するなかで実現したかったこと「特定の条件で絞る」機能を作るなかで重要視していたのは、「条件が増える可能性が高い」ことです。今は商品ID、カテゴリーIDで絞れるけど、将来的にはブランドID、購入数などで絞れるようにするかもしれません。条件が容易に追加できることを意識しました。actionsの形はsolidusを参考にさせてもらいました。price_gteというサブタイプクラスを作成しました。STIかSTIだと条件が増えたときにクラスの追加のみで済む。具象テーブル継承、クラステーブル継承だとテーブルまで作らないといけないので面倒くさい。将来的に追加されるカラムが少ない。あるサブタイプクラスしか使わないカラムが増えていくと、テーブルにカラムが増えすぎるってこともあると思いますが、今回は問題ないと判断。サブタイプクラスで共通のメソッドでも、それぞれ処理が異なる。処理が同じなら親クラスで共通のメソッド生やせばすむし、結果enumを使って異なる処理だけを例外的に書けばいいよねってなりますが、今回はそれぞれ異なります。RailsのSTIの機能は使わなかったRails標準のSTIの機能は使いませんでした。理由としては「継承」を使いたくないから。今回は委譲で対応しております。(RailsのSTIを期待されてた方すみません。)※と言ってみたは良いものの、Railsから外れるツラミも結構大きいです。今回の機能では問題ないのですが、あまりおすすめはしないです。どの条件か(どの条件のクラスを使用するか。条件に必要な任意の値(〇〇円以上とか。商品IDとかはここには入らない。endend継承を使用しないので、サブタイプクラスを呼ぶメソッドを作ってます。endendrubyではダックタイピングできるので基本インターフェースは不要ですが、他のエンジニアにもこのメソッド使ってくれという願いを込めてmodule作りました。endend引数のrelationに取引のRelationを渡すことによって中間テーブルにある対象のIDたちで絞ることができます。(正確にはRelationに条件を付加します。endendこちらは商品ルールとは異なり、rulesテーブルのvalueカラムを使用します。Polymorphic関連なぜPolymorphic関連かrule_targetsモデルに対して、複数のモデルを紐付けたかったため。:targetableend※本当はSTIと同じくインターフェース用のmodule作ったほうがいいのですが、共通のメソッドがないので作ってません。ちなみにpromotionモデルはこうなっております。いろんな条件をまとめたrelationを受け取ることができる。endendPromotionモデルが窓口となっており、コントローラーなどの呼び出し元はdetectメソッドを呼ぶだけで条件での絞り込みが可能になります。各クラスがそれぞれの仕事を担ってくれているので結構シンプルなのではないでしょうか。case文が全くありません。さいごに現状新しい施策条件が増えた場合でもクラス一つ作るだけで解決するので、小一時間あれば条件の追加が可能となりました。明日は弊社新卒の平井くんです!どんな記事書くんでしょうね。Kubernetesこんにちは。インフラチームの夏目です。2019の22日目の記事です。最近はこちらのインタビューでも触れたとおりKubernetesクラスタを作ったり壊したりしていまして、今日の記事はKubernetesにおけるアプリケーションデプロイに関してのお話です。editでDeploymentsのイメージタグを最新のものへ変更といった人の手による温かみのあるデプロイをしている?それはそれで心がこもった良いやり方かもしれませんが、おそらく少数派ですし、システムに心は必要ありませんのでやめましょう。みなさん基本的にはSpinnakerやJenkinsXといったCDツールを利用されているかと思います。現在私達が開発中のシステムにおいては、デプロイパイプラインのCDツールとしてArgoCDを利用しています。以下のような特徴があり、シンプルにGitOpsデプロイを実現することができるツールです。GUIやCLIでmanifestの適用状態が確認できるkustomizeに対応しているSync/Hook機能でデプロイフローを柔軟にコントロールできるそもそもGitOpsとはどういった概念なのか、という点に関しては提唱元であるWeaveworksのblogがわかりやすいのでご参照ください。www.weave.worksなお、Weaveworksが開発しているFluxもGitOpsを実現するためのCDツールなのですが、ArgoCDと比べると公式ドキュメントがややわかりづらい印象があります。kustomizeには対応しているものの、他の機能(イメージタグ更新検知)とあわせて利用しようとするとmanifestの構成が複雑になる(選定時の実装では)、といった理由もあって採用には至りませんでした。EnigmoにおけるArgoCDを使ったGitOpsデプロイフローArgoCDのアーキテクチャは公式ドキュメント記載の以下の図のように、GitOpsの全ての領域をカバーするわけではなく、他のCIツールと併用することを前提としています。Actionsなども候補になるかと思いますが、EnigmoではメインのGitサービスとしてGitLabでソースコードを管理しており、GitLabにビルトインされたCI機能であるGitLabCIをフローに組み込みました。GitLabCIとArgoCDを組み合わせたデプロイフローは以下のような構成です。DeployFlowArgoCDの提唱するBestPracticeに則り、アプリケーションのコードリポジトリとKubernetesのmanifestリポジトリは分離させています。以下にフェーズごとの具体的な流れを書いてみます。PhaseアプリケーションリポジトリのmasterブランチにDockerイメージのバージョンアップMRをマージすると、GitLabCIで以下のジョブが実行されます。Dockerイメージのビルド・タグ設定ECRへDockerイメージをPushこれでアプリケーション側のデプロイ準備は終わりました。Phase更新したイメージタグを利用するようmanifestファイルを修正し、manifestリポジトリにMRを作成→マージすると、以下のフローで処理が走ります。manifestのコードをそれぞれGitにマージするだけで、kubectlを使うことなくデプロイが実行されました。ArgoCDのチュートリアルや概要的な部分は公式ドキュメントにわかりやすくまとめられているため省略させて頂きます。applyでクラスタへ適用!という原始時代のようなオペレーションを行っていました。継続的デリバリという方式なので当たり前ではありますが、このフローを構築してからはGitのマージ操作のみでデプロイ作業が完結し、タイトルのようにおよそ5分でアプリケーションがデプロイされるようになりました。Waveで解決することができました。db:migrateを実行するJobを作成し、annotationで他のDeploymentよりも先にSyncが実行されるように設定することで、他のアプリケーションよりも先に確実にdb:migrateが実行され、スキーマ関連のエラーが回避できます。現時点では上記デプロイフローには実装していませんが、Hookの仕組みを使うことでデプロイ後にSlackへ通知を飛ばしたり、正常終了したJobを削除する、といった振る舞いを加えることもできます。ConsデプロイスピードこれはArgoCDの短所ではないのですが、このデプロイフローは構成図でもわかるようにオンプレミスのGitLabと、AWSのマネージドサービス群の2つにわかれています。本来であればArgoCDから直接GitLabのmanifestリポジトリを参照し、GitLabのContainerRegistryからDockerImageをPullするようにすれば、オンプレミスからのイメージPushやRepositoryミラーリングが必要なくなり、もっとシンプルなフローになります。AWS側からオンプレミスのGitLabへアクセスするよう設定すると、オンプレミス側への依存が発生することと、KubernetesクラスタからはあくまでAWSのサービスのみとやりとりをするのが、リソースアクセス権限設定の観点からもわかりやすいだろう、という理由からこういった構成になっています。manifestリポジトリのミラーリングはリアルタイムではなくおよそ5分間隔で実行されます。これに加えて、ArgoCDがクラスタとmanifestの差分を3分間隔(調整可能)でチェックしているため、マージしてから最長8分待たないとデプロイされない、という状態になっています。いざデプロイしようと思ってマージをしてから実際にアプリケーションが更新されるまでの時間がやはり長過ぎるのではないか、という印象は否めないため、前述した理屈を否定する形にはなりますが、GitLabをAWSへ移行してArgoCDから直接参照させる、といった方法の改善を検討しています。イメージタグ更新修正の手間デプロイフローに記載したように、アプリケーションリポジトリの更新だけではなく、manifestファイルのイメージタグも都度修正しなければなりません。アプリケーションのコードを更新したら即デプロイ!ということにはならず、もうワンアクションが必要になります。プロダクション環境であれば、意図しないデプロイを防ぐためにもそういったアクションが挟まるのも良いかもしれませんが、テスト環境のようにスピーディーにデプロイして検証をしたい!という場合は手間でしかありませんね。あるため、同じような機能がほしい!というIssueがArgoCDのリポジトリに報告されていました。FluxとArgoCDは設計思想が違うから、というコメントとともにあえなくクローズされる......かと思いきや、ArgoCDとFluxがそれぞれの機能を統合したGitOpsツールの開発をすすめる旨の発表をしたため、緩やかに期待してArgoCDを使い続けよう、という状況です。www.weave.worksまとめKubernetesのGitOpsツールとしてArgoCDを利用していますDockerイメージのビルドとプッシュ用CIツールとして、GitLabCIを利用していますArgoCDを利用することで柔軟なデプロイフローを実現することできますこの記事では我々のシステムではどのようにGitOpsデプロイを実現しているか?という構成を紹介させて頂きました。Kubernetesの運用ツールはまだまだデファクトと呼べるようなものが揃っていない状況ですが、日々新しい機能が盛り込まれたり統廃合される様子は、ウォッチしていて楽しいものでもありますね。Computingの運用に一緒に立ち向かう仲間を募集しています。