【データ分析初心者必見】データも専門知識もなくてもクラスター分析をしてみたい!

はじめに

初めまして、ANDPADプロダクト部に所属している谷口と申します。 
普段はプロダクトの要件定義や開発スケジュール管理をしながら、プロダクトの方向性をエンジニアと決めるプロダクトマネジメントの業務をしています。

今回の記事のテーマは「初心者でもできるデータ分析」です。

この「データ分析」に関して執筆するにあたって、安宅和人さんの『シン・ニホン AI×データ時代における日本の再生と人材育成』の記載を紹介したいと思います。

これ(データ分析のリテラシー)はこれからの『読み書きソロバン』なので、高等教育を受けるような人は基本的に全員、少なくとも8割ぐらいの人は学んでおくべきです。

これを読んで、自分は「"全員"か、マジか〜」と思いつつ、最近になって大慌てでデータ分析の勉強をはじめました。
もしかすると同じように「流石にデータ分析について何も知らないのはまずいかも」と考えている人もいるかもしれません。


そこで今回は、

  • 「データ分析に興味はあるけれど、敷居が高いと思って手を出せていない」
  • 「データ分析を勉強したいと思っても、分析対象となるデータがない」

という方向けに、手始めにサンプルデータを揃えてクラスター分析という機械学習の分類手法を試す方法をこの記事でお伝えしたいと思います。

 

かく言う自分もゴールデンウィーク前はPythonを触ったことすらなかったので、気構えずに記事を読んでいただければと思っております。

 

なお、今回の記事は自分が勉強用に使用した『Python実践データ分析100本ノック』を参考にしています。

www.shuwasystem.co.jp

前提

分析のゴール

Python機械学習のライブラリ(scikit-learn)に入っているサンプルデータ「※アヤメのデータ」を使って※クラスター分析を行う。

※紺色の花を付ける多年草の一種
※外的数値から自動的に分類された、一定の特徴を持ったグループのこと。教師なしで自動的に分類されているため、人が気づかないような切り口で分類されることも多く、新しい気付きを得るヒントになる。

必要なライブラリ

Pythonでデータ分析するに当たって一通り必要なライブラリをご紹介します。

・Python3系

MacにはデフォルトでPython2.7が入っているのですが、古いバージョンのため、アップデートする必要があります。

・pandas

データ分析をサポートするためのツールが一通り揃っているライブラリです。

下記のようにデータをエクセルのような表形式に出力することができます。

f:id:taniguchi_toshiokun:20200523190311p:plain

・scikit-learn

機械学習系のライブラリです。

「線形回帰」「決定木分析」「クラスタリング」など聞いたことのある分析は大体このライブラリに一通り揃っています。
今回利用する「アヤメのデータ」のように、サンプルのデータが揃っているので、実データがない方も簡単にライブラリの挙動を試すことができます。

・matplotlib

グラフを描画するのに使用するライブラリです。

・jupyter notebook

下記のようにブラウザ上でPythonを使うことができるライブラリです。

f:id:taniguchi_toshiokun:20200510160557p:plain

 

分析手順

jupyter notebookで「アヤメのデータ」を加工する

scikit-learnの「アヤメのデータ」をインポート

# データ分析をサポートするためのツールが一通り揃っているライブラリ
import pandas as pd

# scikit-learnに付属しているアヤメのデータ
from sklearn.datasets import load_iris

data = load_iris()

# アヤメのデータを集計できるようにpandasのDataFrameに変換
iris_data = pd.DataFrame(data.data, columns=data.feature_names)
print(iris_data.head())

f:id:taniguchi_toshiokun:20200523184413p:plain

 

scikit-learnには"Toy datasets"というデータ分析を学習する用のデータセットが一通り揃っています。

load_boston(ボストンの住宅の価格帯情報),load_boston(ワインの情報)なども用意されていますが、今回は分類のデータセットとしてよく使われるload_iris(アヤメのデータ)を使いたいと思います。
各項目の意味は下記の通りです。

sepal length (cm) がく片の長さ
sepal width (cm) がく片の幅
petal length (cm) 花弁の長さ
petal width (cm) 花弁の幅
各項目の値の標準化

各項目の値はそれぞれ標準偏差が異なるので、利用回数の標準化してからクラスター分析を行います。
標準化にはscikit-learnにあるStandardScalerを利用することができます。

# データセット標準化機能のインポート
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()

#標準化したアヤメのデータ
iris_data_sc = sc.fit_transform(iris_data)

K-means法でクラスター分析

クラスター分析の代表的な手法の一つであるK-means法という方法を使用して、がく片と花弁の特徴からアヤメを5つのクラスターに分けます。

# クラスター分析をするためのKMeansのインポート
from sklearn.cluster import KMeans

kmeans = KMeans(n_clusters=5, random_state=0)
clusters = kmeans.fit(iris_data_sc)

クラスターを分けたら、そのクラスターを元データに項目として割り当てます。

iris_data["cluster"] = clusters.labels_

クラスターの特徴の洗い出し

クラスターごとのアヤメの数を出します

iris_data.groupby("cluster").count()

クラスターごとの各特徴の平均を出します

iris_data.groupby("cluster").mean()

集計結果はこちら。

f:id:taniguchi_toshiokun:20200523184416p:plain

クラスターごとの集計からわかることを整理

クラスターの数値上の特徴を捉えることはできますが、実際にクラスターに所属しているアヤメが「何の品種か」「どういった特性を持っているのか」といったことは分かりません。
クラスター分析は自動的に分類されるため、各クラスターがどのような特徴を指しているのかはあくまで「質的」に判断する必要があります。

クラスターの分類を可視化する

今回分類したクラスターは4つの変数を使用しているので、二次元上で分布を確認したい場合には、次元削除という方法を使う必要があります。
次元削除の代表的な手法である主成分分析を使って、2つの変数でデータを表現できるようにしてみましょう。

#主成分分析
from sklearn.decomposition import PCA
X = iris_data_sc
pca = PCA(n_components=2)
pca.fit(X)
x_pca = pca.transform(X)
pca_df = pd.DataFrame(x_pca)

#グラフで色付けするため
pca_df["cluster"] = iris_data["cluster"]

# グラフ描画のロジック
import matplotlib.pyplot as plt
%matplotlib inline
for i in iris_data["cluster"].unique():
  tmp = pca_df.loc[pca_df["cluster"]==i]
  plt.scatter(tmp[0], tmp[1])


# 凡例の表示
plt.legend()

実行結果は下記の通りです。

 

f:id:taniguchi_toshiokun:20200523185629p:plain

二次元上に各分析単位の分布がプロットできたことによって、「クラスター0とクラスター3」「クラスター1とクラスター2とクラスター4」という単位でグルーピングができることがわかりました。
実際のデータ分析では、このクラスター分析をベースにして

を調査することで、さらに母集団(アヤメのデータ)の特徴を詳細に分析することになります。

最後に

今回の記事ではクラスター分析の触りの部分のご紹介をしました。

思っていたよりも簡単に機械学習を使ってデータをクラスターに分けることができることが分かったのではないでしょうか。

今回はサンプルデータを使ったクラスター分析のご紹介でしたが、実務に応用できる例としては下記のようなケースが挙げられると考えられます。

  • プロダクトマネージャーがユーザーの各機能の利用回数から、今まで見えてこなかったユーザー像を可視化する
  • 営業企画が営業メンバーの成績を様々な項目に分解してクラスター分析をすることで、「優秀な営業マン」の特徴を洗い出し、新人の営業マンの研修プログラム開発に役立てる
  • カスタマーサクセスマネージャーがクライアントのヘルススコアを機械学習をかけることで、「継続するクライアント」はどのようなヘルススコアが高いのかを知り、カスタマーサクセスチームのKPI設定の参考にする

クラスター分析のメリットは、「経験」や「勘」ではなく、利用回数などのデータを使うことで、統計的な根拠を持って分析単位を分類することができるようになることです。しかもご紹介したとおり、機械学習自体は触りだけであればそこまで難しいものではありません。

まずは機械学習で何ができるのか体感するため、scikit-learnにあるデータセットを用いて、jupyter notebookで遊んでみてはいかがでしょうか?

Androidのテストケース名は全て日本語が分かりやすいのでは?

f:id:zigenin:20200526143815p:plain:w128

はじめに

こんにちは。 Andpadのモバイルアプリの開発を担当しているzigeninです。 2019年の11月頃から、モバイルアプリにテストコードを少しずつ追加しています。

Androidでは、どのようなテストケースの名だと読みやすいのか、自分の中では答えが出ました。それは、テストケース名を全て日本語で記述してしまうことです。日本語だと微妙なニュアンスを短く正確に表現できるからです。

テストケース名の具体例

たとえば、以下のkotlinメソッドがあったとします*1

/*
 * Todoリストの項目を渡すと、作業中のタスクの割合と完了したタスクの割合を返す。
 */
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        ...
    }
}

このメソッドの引数tasksにnullを渡した場合、(0f, 0f)を返すのが期待動作です。 ここでは、そのテストコードのテストケース名として、3つのスタイルを示します。

  1. 筆者が最近まで書いていたスタイル
    • "testGetActiveAndCompletedStatsReturnsZerosWhenTasksIsNull"
  2. Google CodeLab流儀
    • "getActiveAndCompletedStats_error_returnsZeros"
  3. 全て日本語のスタイル
    • "Todoリストがnullのとき、作業中と完了したタスクの割合は両方0とする"

最初のスタイルは、昔ながらのtest<Method名>というスタイルを意識したものです。今どき、先頭にtestを付けるのはナンセンスらしいです。また、切れ目が良く分からないので可読性が低いです*2

2番目のスタイルは、Google CodeLabの流儀です。1番目に比べると、単語の切れ目が分かりやすく、可読性が高いです。

最後のスタイルは、テスト対象メソッドも含めて全て日本語で書いたものです。他のスタイルよりもテスト内容が分かりやすいと感じています。これを英語で記述しようとしたら、"bothActiveRatioAndCompleteRatio_are_zero_if_tasks_is_null"のようになると思います。この英語の記述だとひと目見ても意味が分からないのと、そもそも英語的に正しいのか謎な点が問題です*3

まとめ

環境が許せば*4、テストケース名は全て日本語で記述するのが分かりやすいと思っています。 最後に、本記事の主張は、まだ個人的な考えの段階です。

補足

JUnit5について

AndpadのAndroidアプリではJUnit4を使っていますが、JUnit5からメソッド名とは別に表示名(@DisplayName)を付けられるようになりました。JUnit5を使っているなら、メソッド名は英語にしておいて、表示名に日本語を付けたほうが良さそうです。

iOSについて

XCTestの仕様上、iOSではテストケース名の先頭に"test"を付ける必要があります。 "test"を先頭に付ければ、日本語も普通に記述できます。 メソッド名に日本語入れるのは嫌だが、日本語の説明文がどこかしらには表示されて欲しいなら、XCTContext.runActivityを使えば実現できます。

*1:元ネタはGoogle CodeLabです

*2:今にしてみれば、camelCaseにこだわらないで、"_"を入れれば、大分ましになったのですが

*3:筆者の英語力の問題な気がしますが

*4:日本語ネィティブの方が多い環境

失敗をさらけ出す勇気を持つためにはどうしたらいいのか

はじめに

株式会社アンドパッドでプロダクトマネージャー兼デザイナーをしています山田と申します。クラウド型の建築建設プロジェクト管理サービスであるANDPADの施工管理領域のプロダクトロードマップ策定やターゲットクライアントとのヒアリングなどを行っています。

 

私達がよりよいプロダクトを作る過程では不確実性が高い議論が度々発生します。意見の相違は緊張を生み創造性を呼び起こすので悪いものではありません(楽しくは無いかもしれませんが)。ただ緊張を前向な議論に変えるのは簡単ではなく、チームに気兼ねなく発言できる雰囲気が必要です。そのような雰囲気が土台にあると「熱い」トピックスを「冷たく」できます。

 

そして、そのように切り替えられるスキルを持つチームはプロダクトに対して効果的な役割を果たすと考えています。効果的なチームの定義を説明したのちに、最後にアンドパッドで取り組んでいる活動をご紹介します。

 

効果的なチームとなる条件

アリストテレスというプロジェクトをご存知でしょうか?Googleが効果的なチームとなる条件を明らかにするためにGoogleの社内チームを調査したプロジェクトです。チームによって生産性が異なるのはなぜか、高いチームはどのような取り組みをしているのかを調査し、結果として効果的なチームは以下の要素を持っていることが分かりました。

最初に挙げられている心理的安全性というのは「関連のある考えや感情について人々が気兼ねなく発言できる雰囲気」のことです。この心理的安全性がチームの土台となり相互信頼を生み出してきいき、そして効果的なチームと繋がります。

f:id:cafeyamada:20200517195830p:plain

https://rework.withgoogle.com/jp/guides/understanding-team-effectiveness/steps/help-teams-determine-their-needs/

調査の詳細に関してはGoogleが公開している「効果的なチームとは何か」を知るを参照ください。

 

スペースシャトル「コロンビア号」

心理的に安全というのはどういう状況でしょうか。NASAアメリカ航空宇宙局)は、2003年にスペースシャトル「コロンビア号」を打ち上げました。打ち上げ後にビデオチェックしていたエンジニアがシャトルが破損した可能性に気づいて上司に報告しましたが、対策が講じられれず時間が過ぎていきます。居ても経っても居られなくなったエンジニアはミッションリーダーがいる全体会で破損した懸念を取り上げてもらおうと上司ばかりがいる会議の中で発言を決意します。しかし地位の高い人に話すことによって自身のキャリアへの悪影響を気にして結局は発言しませんでした。その8日後、スペースシャトルは燃え上がり7名の宇宙飛行士が死亡します。

f:id:cafeyamada:20200517195957p:plain

もし発言をしていればリーダーがそこまで言うならばと認識を改め、念の為に外壁に耐熱素材を補強しておけば生存の可能性が変わったかも知れません。もちろん事故の要因としては飛行計画チームのリスク判断が甘かったなど他の要因もあり、彼がミーティングで発言しなかったことだけが事故に繋がったわけではありません。ただ彼は発言を躊躇しました。気兼ねなく発言できる雰囲気がなかったのです。

 

職場でのイメージリスク

1965年、組織改革の研究を行っているMITのエドガー教授とウォレン教授が心理的安全を生み出しました。教授らは心理的安全があると希望を否定するデータが提示された際に生じる保守的な姿勢(学習不安)を克服でき保身を考えなくてよくなり、より集中できるようになると結論しています。 

職場で調和を乱すことはイメージリスクの1つです。イメージリストを最小限にするのは簡単です。絶対的な自信がなければ、何もせず、何も発言しなければいいのです。ただ、これでは創造性がなく、イノベーションが犠牲なり、何より本来あるべき人間関係を作るチャンスまでをも失ってしまいます。ちなみにイメージリスクには以下の4つがあり、リスク毎に関連する活動量が低下します。

  • 無知だと思われる不安 => 質問をしなくなる
  • 無能だと思われる不安 => 支援を求めなくなる
  • ネガティブだと思われる不安 => 批評をしなくなる
  • 邪魔をする人だと思われる不安 => フィードバックを求めなくなる

気をつけなければいけないのが、心理的安全というのはプレッシャーがなくメンバー同士が居心地良く仲が良い状態のことではありません。チームの結束性というのはかえって異議を唱えることの積極性を弱める可能性があります。人は意見が一致している調和を乱したくはないのです。これを集団思考と言います。

 

しっぱいCafe

イメージリスクを気にせずに無知や無能をさらけ出すには勇気がいります。その勇気を後押しするためにアンドパッドでは定期的に「しっぱい」を共有する会を開いています。

 

しっぱいCafeではゆるふわに自身の体験談を語り共有します。体験談は業務のことでもプライベートなことでも構いませんが、最初は話しやすい日頃の雑談のようなことから話始めます。雨の予報なのに傘を忘れてきてしまったとか、お弁当があるのにご飯を食べてきてしまったなどです。日常の失敗談が良いところは聞き手はそういう失敗を非難したり代替案を提示したりはしません。クスッと笑うだけです。それに日常の失敗であれば聞き手は質問から行います(例:傘を忘れるなんて何かあったんですか?)。これを利用して失敗を話しても避難されず受け入れる雰囲気を養生します。

f:id:cafeyamada:20200518131611p:plain

2020年3月にデザインチームで開催したしっぱいCafeの様子

ちなみにしっぱいを1つ共有したら好きなお菓子を食べれる権利が与えられます。しっぱいをさらけ出してメンバーが笑って受け入れてくれて、お菓子も食べられる。またさらけ出したい、さらけだすと良いことがあると思ってもらいたいからです。 

 

業務でデザインなどを何かしらアウトプットを聞き手が見ると、人は感情的な反応型フィードバックか指示型フィードバックをしてしまいがちです。そうすると心理的安全性は損なわれ、よりイメージリスクを気にするようになります。そのためにアウトプットに最初に触れたときは非難せず笑って質問して聞いてあげることが大事なのです。しっぱいCafeはその土台となります。

 

幸い私達は共有を躊躇っても誰かの命を奪う仕事ではありませんが、私達がよりよいプロダクトを作ることができなければ、ユーザーの業務は停滞し、幸せを作ることができなくなります。そうならない為にこれからもしっぱいをさらけ出し続けたいと思います。


Rails未経験でANDPADにジョインして半年を振り返る

はじめに

はじめまして。バックエンドエンジニアの小野寺です。
主に Rails でバックエンドの開発をしているANDPADに、Rails 未経験で入社しました。
 
入社前は、バックエンドエンジニアとして、
  • ASP.NET Framework MVC を使った Web サービス開発
  • Node.JS、TypeScript を使った API 開発
といったことをしていました。
 
他にも、React を使ったフロントエンドの開発、ECS、RDS などを使ったインフラの構築なども、少しかじっていました。 
 
この記事では、そんな自分が Rails 未経験で入社してからの約半年を振り返ります。

なぜオクトに入社したのか

転職活動では、使用している技術にはあまり拘らず、幅広く見ていました。技術も大事ですが、「何をやるのか」「どんな価値を提供するのか」を重視していました。いろいろな会社と話をして、最終的にオクトに入社することを決めました。
 
f:id:andpad_onodera:20200430150525p:plain
決め手となった理由をいくつか紹介します。
  • 建築・建設業界を IT で変えていくという挑戦に興味を持った
  • 急成長していくフェーズに携われるチャンスだと思った
  • 面談、面接時の印象が良く、この人達と一緒に働いてみたいと思えた

 

入社するまで

いざ入社が決まると、本当に未経験で大丈夫なのか不安になってきました。

f:id:andpad_onodera:20200430150940p:plain

 
少しでも不安を解消するため、RubyRails の勉強をしました。
主に、使用したのは以下の2つです。
 

プロを目指す人のためのRuby入門

何はなくとも Ruby を使えるようにならなくてはと、この本を読みました。
プログラミングの基礎中の基礎の内容がないので、他の言語の経験がある人におすすめです。

Railsチュートリアル

次は Rails を触ってみなくてはと、こちらをやってなんとなくの使い方を学びました。

railstutorial.jp

 

入社してから

入社後は、オンボーディングを受けてから業務に入ります。
正直、ちゃんとアウトプットが出せるか不安な部分もありましたが、比較的早く業務を進める上で必要なことをキャッチアップできたのではないかと思います。
その要因を3つ挙げてみます。
 

入社前の勉強

前職の通勤時間などを利用してコツコツやっていて良かったと心から思いました。
これをやっていなかったら、コードが読めなすぎて入社後数日で発狂していたと思います。

f:id:andpad_onodera:20200430151629p:plain

メンター制度をフル活用

ANDPADでは新入社員にメンターが付いてくれます。
 
ANDPAD は機能が豊富で、会社、ユーザごとに機能や権限など細かく制御できるため、最初は仕様を理解するのが大変です。加えて、Rails の経験がない自分は、コードを読んでも理解が合っているのかわからない部分があったため、メンターが付いてくれるのはとてもありがたかったです。
 
自分の場合は、毎朝 Q&A の時間をとってもらい、前日の作業で発生した疑問点を解消していました。それ以外の時間でも、調べてみてわからないところは、メンターを捕まえて聞きまくりました。
 
入社してしばらくは、メンターがどこにいるか常に監視していました。

f:id:andpad_onodera:20200430151809p:plain

小さなバグ修正からコツコツと

小さな修正でも、アウトプットが出せてリリースもできていると心にゆとりがもてます。修正する際は周辺のコードも合わせて読むよう意識していました。
 
別の修正や機能開発をする際に、だんだんと変更箇所の当たりがつくようになったので、これは意識しておいてよかったなと思います。また、小さな修正でもテストコードを書くことで、仕様理解や、テストデータの作成によるデータ構造の理解につながり役立ちました。
 

おわりに

なんの因果か、最近自分のチームにもう1人 Rails 未経験のエンジニアが入りました。まだまだ、Rails 的にこの書き方はどうなんだろうと迷うこともありますが、2人で一人前の Rails エンジニアに成長できるようにがんばっていけたらと思っています。
 
最後まで読んでいただき、ありがとうございました!

インターンから見たオクト~ 学生インターンのすヽめ ~


はじめに

こんにちは、開発部門インターンの飯沼です。この4月に修士2年になりました。先日まで就活で長いお休みをいただいていたのですが、今月から2ヶ月ぶりに復帰しました。昨年、1度だけこのブログに投稿した時は、Amazon SageMaker の使い方について書きました。ただ今回は職場復帰したばかりで、あまりTech について書けることがないので、この1年インターンとして携わってきたプロジェクトを振り返って、学生から見た Oct について考えてみたいと思います。

 

入社(2019年5月)

もともと2019年1月くらいまでは「俺は Web なんて絶対にやらない!」と公言していたのですが、いざ IT 業界で食べていこうと考えた時に「やっぱり Web は素養としてできるようになった方がいいかな?」と思い始めました。そこで、大学の卒業式の日、当時 Oct で働いていた友人に紹介してくれないか頼んだことが入社のきっかけでした。大学では映像系の研究をしているため、PythonC++ でかなり大規模なプログラムを組んだことはありましたが、入社の時点で Web は全くの未経験でした。入社が決まってから3週間程、研究そっちのけで Vue.js の勉強をして ANDPAD チャットの製作チームに参加することになりました。

 

Web 版 ANDPAD チャット (新チャット)

2019年5月に入社して最初のプロジェクトは、ANDPADチャットのフロントエンド開発でした。そのころは社員の方は今の半分くらいで、私のいた開発チームも3、4人しかいませんでした。

 

しかし、その後わずか1ヶ月でチームの人数は倍増します。そのころになると、同じチームだった、からまげさんの主導で色々なアジャイルな開発手法を試したりして、本格的にチーム開発っぽくなってきました。

 

事前にある程度勉強していたこともあって、Vue.js での開発には比較的すぐに慣れました。しかし、CSS でのUI実装はなかなか慣れず、フロントエンド小泉さんなどCSSスペシャリストに何度も助けていただきました。実はいまだに CSS に苦手意識があります・・・(汗)

こうやってお互いに補い合って開発ができるところが、チーム開発のいいところだと思います。研究は基本的になんでも一人でやらざるを得ないことが多いので・・・

5、6月は旧チャットからの機能移植がメインでしたが、7、8月は新規機能の開発にも取り組みました。その後、9月初旬ごろに新チャットがリリースされました。

 

リリース直後はバグ対応で昼休みが15分しか取れなかった日もあって結構大変だった記憶があります。これも9月半ば過ぎには落ち着いてきたので、このプロジェクトから離れることになりました。 

機械学習プロジェクト

新チャットの開発が落ち着いたことで、「次は何をしようか?」と言う話になりました。ちょうどそのころ、一部サービスに機械学習が導入できないかと言う話が持ち上がってきていました。私は大学や大学院で画像認識や映像処理などの研究をしていたので、このプロジェクトにアサインされました。

 

事情により詳細は伏せますが、セマンティックセグメンテーションや異常検出を使って画像の認識をしようという試みでした。前回書いた SageMaker の記事もこのプロジェクトの一環です。機械学習を行うためには、画像データに一枚一枚正解をつけるアノテーションという作業を行う必要があります。この作業は必要不可欠とは言え、本当に地道でかなりしんどかったです。

 

社内データの分析

諸般の事情で機械学習プロジェクトからは離れることになりました。それに伴って社内のデータ分析をするチームに移籍しました。このチームでは、SQLアクセスログを取ってきて、サービスの利用状況レポートなどの作成に関与していました。私は主にSQLを書いてBigqueryからデータを引っ張ってくるところを担当していました。

SQLは比較的文法が単純で割とすんなり身につきました。Redash をこの時初めて使いましたが、このサービスは非常に便利ですね。

redash.io

SQLで引っ張ってきたローデータから図表やグラフが簡単に作れます。これをコピペすれば、利用状況のレポートがお手軽に作れます。ただ、一つ難点があって、それはAPIが微妙に使いづらいことです。動的クエリという機能をAPIで使おうとすると、3段階に分けて実行する必要があります。動的クエリを使えば、WHERE や GROUP などで条件を指定する部分を変数として扱えるようになるため、各社個別にレポートを作成する時など非常に便利です。

 

就活からこれまで

年明けすぐに、就活のため他社のインターンなどに参加し、2ヶ月以上お休みをいただきました。就活ではいわゆる JTBC (Japanese Tradititonal Big Company) を中心に受けてきました。コロナの影響で思ったよりも早く選考が進み、3月いっぱいで内々定が出たので、今月から Oct に復帰しています。現在はデータ連携チームでRailsを書いています。

 

Oct のいい所・よくない所

ここまで、この1年間を振り返ってきましたが、ここからは私が思う Oct のいい所やよくない所を考えていきたいと思います。

 

個人的にOctに入ってよかったと思うところは、以下の4つです。

  • Web周りの知識が身についたこと 
  • チーム開発の経験
  • 非常にオープンな職場
  • 社員とも平等

一番はやはりWebまわりの知識が身についたことが大きな収穫だったと思います。また「インターンだから」といってやらせてもらえる仕事がショボくなったりはしませんし、機械学習あたりではかなり大きな役割も任せてもらえました。あと、ランチLTなどでの情報共有も盛んに行われています。JTBCでのインターン経験と比較しても、オフィスはとても明るい雰囲気なので、非常に働きやすい職場だと思いました。

 

一方で、「あまり良くないなあ」と思うところもあります。

  • ドキュメント類が未整備
  • 情報へのアクセス

何個も挙げようかと思いましたが、思い浮かんだのはこの2つだけでした(笑)

また、最近ではこのどちらも大幅に改善されてきています。


私が入った頃は旧チャットの仕様書がなくて、機能移植しようにもどうすればいいのか困ってしまったこともありました。また、情報のアクセス権が不明確なのも少し不満でした。かなり機密性の高い情報にアクセスさせてもらえる一方で、Google スプレッドシートなどにアクセス権がなく、最低限必要な情報を入手するのに時間がかかるなんてこともありました。

 

しかし、最近ではConfluenceにドキュメントがまとめられるようになり、情報の集約は進んでいますし、正式に社用アカウントが発行されたことで情報へのアクセス障壁も無くなりました。

 

学生インターンのすヽめ

Octが学生のインターンを積極的に採っているのかはわかりませんが、私はこの会社で働くことができて非常に良かったと思っています。教育制度が未整備であるという欠点はありますが、様々な技術に触れ、実務に携わるという経験はなかなかできるものではないと思います。また技術力の向上だけでななく、学外の人とのつながりを作るという意味でも良い経験になります。ここで言うのも難ですが、こうした経験は就活でも強みになります。是非、大学生、大学院生のうちに長期のインターンをしておくことをお勧めします。

 

最後に

なんだか書いていて退職エントリーみたいだなあとか思えてきましたが、私はまだしばらくOctにいるつもりです。少なくとも今年中は、Railsを書いたりしながら開発に携わっていきたいと思っています。

 

今回は、1年の振り返りがメインになってしまいましたが、次回以降記事を書かせていただく機会があれば、ちゃんとTechな記事を書きたいです。

 

近々、研究の中間報告が済んだら、もっとコミットできるようになるので、どうぞよろしくお願いいたします。

 

追申

MacBook Proのキーボードの調子が悪いです。

塵も積もれば山となる、Vue.js製スプレッドシートのパフォーマンス改善記

はじめに

はじめまして、オクトのフロントエンドエンジニアの小泉です。約1年前に入社し、Vue.js(Nuxt.js)でプロダクトのWebフロント開発に携わっています。

 

初めて会社のブログに寄稿するにあたって、自分がオクトでどんなことをしているかを書こうと思ったのですが、私が担当しているプロダクトは現時点で正式リリース前のため、今回はその中に組み込まれている簡易スプレッドシート機能について、また特にそのパフォーマンスを改善するために行ったことについて書いていきます。

 

といっても、スプレッドシート的なものを自前で実装しようという人はなかなかいないと思いますので、データ量の多いリアクティブなサービスをVue(Nuxt)で開発する際のハマりポイントとして参考程度に流し読みして頂ければ幸いです。

 

スプレッドシート機能とは

現在、オクトで自分が開発に携わっているプロダクトには、商品情報を入力していって、合計金額を計算する明細編集という機能があります。

 

この機能について、チームのデザイナーさんから最初に提案されたデザインイメージは下記のようなものでした。

f:id:ytr0903:20200424122028p:plain

この時点では「こういう画面で編集したい」という程度で、リッチなキーボード操作というものが具体的な要件には含まれていなかったのですが、

 

このExcelライクなデザインを実現すると、ユーザーさんは一般的なスプレッドシートと同じ操作を期待し、「なぜキーボードの矢印キーでセル移動できないのか」「どうして複数選択したセルをDeleteキーで一括消去できないのか」などの要望が上がってくることは想像に難くありません。

 

なのでここは、「このデザインに対して自分が欲しいと思う機能」を実装する必要があると判断し、専用のスプレッドシート機能の開発に取りかかりました。

 

ANDPADのサービス内には他にも明細編集機能が存在しますが、使っているフレームワークが異なるなどの事情から、流用するよりも作り直した方が早いと判断し、一から作っていくことにしました。既存のライブラリを使わなかった理由は

  • 要件に適したものがあまり見つからなかったこと
  • 既存のライブラリを使ってしまうとデザインや機能面での細かいカスタマイズが難しくなること
  • 一般的なスプレッドシートと違って関数機能やセル同士の連動などを汎用的に実装する必要がない

ことから、そこまで複雑な実装にならないだろうと見込んだからです。

 

基本的な機能を1週間ほどで実装し、そこから半年ほど、他の作業の合間に少しずつ機能を追加したりパフォーマンスを改善したりを続けていった結果、現在はこのような形になりました。 

f:id:ytr0903:20200424122235g:plain

当然ながら、機能面でGoogleスプレッドシートなどの有名サービスには及ばないものの、キーボードでの操作、マウスでの範囲選択、切り取り・コピー・ペースト、履歴での元に戻す・やり直すなど、いわゆる一般的なスプレッドシートに期待される機能を一通り搭載しながら、それなりのパフォーマンスと操作性を実現できているのではないかと思います。

 

基本的な実装の仕組み

この項は、パフォーマンス改善の項のための前提知識でしかなく、他のプロダクトに応用できるようなものではないので、興味がない方は適度に読み飛ばして頂いて大丈夫です。

 

スプレッドシートを構成するコンポーネントは大きく分けると

SpreadSheetWrapper.vue > SpreadSheet.vue > SpreadSheetCell.vue

の3つに分かれています。

 

SpreadSheetWrapper は描画ではなく処理のみを行っているコンポーネントで、まとめてしまうとあまりにコンポーネントサイズが大きくなりすぎるために分けています。サーバーから渡されたデータとスプレッドシートで編集するためのデータの相互変換とバリデーションを担っています。

 

SpreadSheet.vue に渡されるデータは、列の配列の中に行の配列があり、どこに表示するかは配列番号で管理しています。

f:id:ytr0903:20200424122318p:plain



いずれかのセルが編集された後、セルの移動やフォーカスが外れるタイミングで、編集後の配列がSpreadSheetWrapperにemitされます。

 

配列からオブジェクトへの変換や金額計算などは、全てスプレッドシートから配列を受け取ったSpreadSheetWrapperで行い、その結果を履歴に保存しつつ、金額計算が反映された状態で配列に再変換したものがスプレッドシートに渡されます。

 

編集完了のタイミングで毎回ラッパーを通して、長すぎる文字数のバリデーションや全角数字の自動半角数字変換を行うので、常に有効なデータのみが保持されます。

 

SpreadSheetではこのデータを、行の数と列の数だけv-forでループしてひたすらセルを並べています。それぞれのセルには表示用のdivタグと編集用のinputタグ(またはtextareaタグ)があり、選択したタイミングでセルのinputタグにフォーカスすることで、そこからキーイベントを取得したり、そのまま文字の編集を行えたりします。

f:id:ytr0903:20200424122405p:plain


シート切り替えや履歴管理、コピー/ペーストや列幅の変更など、個々の機能ごとの実装を見ていくとキリがないので、ざっくり説明するとこのような仕組みで動いています。

 

機能を列挙していくと何となくすごいものに見えますが、実際には一つ一つのキーイベントやマウスイベントに対してひたすらロジックを追加していくだけの地味な作業なので、これを作ること自体は時間をかければ誰でもできます。

f:id:ytr0903:20200424163546p:plain

このコードだけでも何となくアナログ感が伝わるでしょうか。

 実際に苦労したのは機能そのものの実装ではなく、それらの機能を次々に入れなが、データ量が増えても破綻しないようにパフォーマンスを両立させることでした。

初期の実装では10行程度のデータでも、表示に数秒かかり、操作をするたびにまた数秒待たせてしまうような状態でした。そこからどのように改善していったかを書いていきます。

 

パフォーマンスを改善するための道のり

当たり前ですが、「これをやれば一気に速くなる」というような銀の弾丸はもちろん存在せず、基本的にはトライアンドエラーで少しずつ改善していきました。

 

本当はきちんと秒数を計測してどの程度改善されたかを数値化すべきなのですが、フロントエンドの速度はブラウザやサーバーなど様々な条件に左右されてしまうため、計測は行えていません。この記事では体感で明らかに速くなった改善をいくつか挙げています。

 

この時、数値ではない体感ベースでの改善をなるべく継続的に行うために、一定のデータ量を基準にして進めるようにしました。今回のケースでは、「100行のデータをストレスなく表示・編集できる」ことを目標に改善していきました。

 

inputタグをできる限り減らす

機能を作り始めた最初期の段階は、全てのセルを、CSSで見た目を整えたinputタグで作り、クリックしてそのinputタグにフォーカスが移るようにしていました。

これは自然な実装ですが、早々に破綻しました。inputタグをmountする描画コストが大きいため、数行ならまだしも、10行~20行のデータでも既に表示するまでに数秒待たされるようになります。

そこで、セルコンポーネントをさらに、表示用のセル(SpreadSheetPreviewCell.vue)と、編集用のリアクティブなセル(`SpreadSheetTextCell.vue)に分け、

基本的にはPreviewCellしか表示せず、選択中のセルのみinputタグ(またはselectタグ)と切り替える形でその都度マウントし、mountedのイベントでフォーカスを移すように変えたところ、描画が約10倍速くなりました。

その後、プルダウンメニューを使うセルだけ、デザインの都合でそのままにしていたのですが、これもデータが多くなるにつれて無視できない遅延になっていったので、

編集中以外はPreviewCellに統一して、矢印マークをCSSで同じ場所に配置するようにしてさらに高速化させました。

 

背景とセルを分離し、背景はCSSのみで描画する

TextCellとPreviewCellを分離したものの、PreviewCellの状態でも行うべき処理がいくつか残っていました。例えば、選択中かどうか、切り取り中のセルかどうか、バリデーションエラーかどうか、などは、アクティブでないセルであっても表示に反映させる必要があります。

f:id:ytr0903:20200424123119p:plain

ところが、この判定とデザインの切り替えをPreviewCellで行ってしまうと、選択範囲が変わるたびに個々のセルで「いま自分が選択対象になったかどうか」の判定が行われ、全てのセルの更新が走ってしまいました。

 

これを避けるために、「PreviewCellは単純に自身が持っている値だけを表示する」ようにして、選択中かどうかといった見た目の変化は、セルと同じサイズの背景を描画する単一のコンポーネントで処理する方式に変更しました。

 

SpreadSheetCellsBackground.vue という長ったらしい名前のコンポーネントですが、やっていることは配列をそのままdivタグのループにしてCSSで装飾をしているだけです。

f:id:ytr0903:20200424123237p:plain

この変更の結果、選択されているかどうかなどのフラグをpropsで渡す必要がなくなり、選択中のセルのフォーカスを切り替える際のラグが4秒から1秒以下に改善されました。最初の改善と併せて、「見た目に関することはできる限りCSSで行う」というのがシンプルながら有効であることを再確認しました。

 

見えている範囲以外は描画しない

こちらも基本的ながら効果の高い改善でした。

Vue.jsにはIntersection Observerを利用したvue-observe-visibilityというプラグインがあるので、これを利用すれば表示範囲に出入りするごとにイベントをトリガーさせることができます。

f:id:ytr0903:20200424135743p:plain初めは行番号を管理するVisibleRowsという変数を用意して、表示領域に入るたびに行番号を追加し、visibleRows.includes表示を切り替える実装を行っていました。

 

ところが、確かに初期描画は速くなったものの、今度はスクロール時にかなりの待ち時間が発生するようになりました。

f:id:ytr0903:20200424160738g:plain

これでは初期描画が速くなっても意味がありません。

 

一定時間の経過後に、全ての行が一気に表示されていることから、処理後のDOM更新がボトルネックになっていると推測し、Vueの秘密のパフォーマンステク9選紹介 - Qiitaでも紹介されていた、子コンポーネントへの処理の分離を導入することにしました。

 

既に実装が進んでいるものを子コンポーネントに移すのはかなり困難だったため、自身のdataとしてisVisibleのみを持ち、渡されたslotの表示を切り替えるだけの単純なラッパーコンポーネントを作りました。

f:id:ytr0903:20200424161612p:plain

正直、これだけで改善されるかは確証がなかったのですが、これが予想以上の効果を生みました。

f:id:ytr0903:20200424161445g:plain

改善前のgifと比べると一目瞭然。すぐに表示されるのでわかりにくいですが、今までは同じタイミングで一気に表示されていたものが、行ごとに別々のタイミングで表示されており、処理が分散されていることがわかります。

 

中身は親コンポーネントに残したままなのでpropsのバケツリレーも起こらず、実装が複雑にならずに処理速度が明確に向上しました。

 

コンポーネントのupdatedの回数を減らす

表示自体は速くなったものの、セルの編集が完了してから別のセルに移動するまでの処理が遅いという問題が依然として残っています。

 

これを改善するために、Chrome拡張機能Vue.js devtoolsのPerformanceタブを見ながら原因を探っていると、updatedの回数が明らかにおかしいことに気づきました。

f:id:ytr0903:20200424161854p:plain

一度セルを移動しただけでPreviewCellのupdateイベントが6500回走っています。

 

100行×13列で1300個のセルがあるので、1回の編集で全てのPreviewCellが5回更新されているのではと推測できます。(誤差があるのは選択中のセルの切り替えに伴うものと考えられます)

 

Total TimeやAverage TimeはブラウザやPCの性能によって毎回バラつきがあるのですが、Countは信用して良いはずです。

 

対処法は大きく分けて2つ、「更新されていない箇所でupdatedが走らないようにする」「1回の処理でupdatedが2回以上走らないようにする」。

具体的には

  • 不要なpropsを渡さない

  • 1度の変更で同じ値が複数回更新されないようにする

  • propsをオブジェクトでまとめて渡さない(差分を検知できずに毎回updateが走ってしまうため。面倒でもStringやNumberに分解して渡す)

 

どれも当たり前ですが、根本的なロジックを見直す必要があるうえ、後から要らなくなったpropsを消さなくても困らないため、開発しているとつい後回しにしてしまいがちな部分でした。

f:id:ytr0903:20200424162102p:plain

渡されているpropsの中身や、その値を操作する親コンポーネントのイベントに無駄がないかを1つ1つ見直していった結果、

 

期待通りにPreviewCellの更新回数を1回(編集が完了したことで値が更新されたもののみ)に抑えることができました。

 

データのバリデーションをセルでは行わない

これも当たり前といえば当たり前なのですが、最初は「文字数を超えていたらアラートを出す」「全角数字が入力されたら半角に直す」みたいな簡単な処理はセルで行っていました。

 

しかし、値の修正が複数個所で行われていると、「wrapperから間違った値が渡されたら、cellで修正して再度emitする」というような、遠回りな処理が発生してしまいます。

 

さすがに無限ループにはならないように気を遣っていましたが、それでも親子間でのデータのやり取りが増えると、必然的にコンポーネントの更新回数も無駄に増えてしまいます。

 

全てのデータ修正をwrapperに寄せたことで、1度の操作で2回以上更新が行われなくなり、パフォーマンスを最適化することができました。加えて、処理の流れを一元化したことで履歴機能もスムーズに実装できるようになりました。

 

おわりに

スプレッドシートの改善について、まとまりなくつらつらと書いてきましたが、この記事の内容を一言で表すと「塵も積もれば山となる」です。

 

例えばこれが10行程度のデータを編集するためのものであれば、全てのセルで毎回更新が走ったとしてもそんなに重くなりませんし、そこの改善に力を入れても得られるものはそんなにありません。

f:id:ytr0903:20200424161854p:plain


updatedの項で貼ったこの画像も、セルごとの平均処理時間は230ms。並行処理の数が少なければもっと少なくなるので、本来であればそこまで致命的な数字ではありません。そもそも、細かく気を遣わなくても必要十分なパフォーマンスを提供してくれるのがVue.jsやReact.jsのようなフレームワークであるとも考えています。

 

しかし、それが100件、セルの個数にすると1300個という単位のデータになると、一つ一つの処理が数ミリ秒増えるだけでも一気に重くなってしまいます。

 

記事に書いてきたことの1つ1つは非常に単純ですが、だからこそ普段は軽視しがちな領域の処理でもあります。

 

このような大規模なデータを扱う場合には、Vueのライフサイクルの正しい理解と、ほんの少しの高速化の積み重ねが必要になっていくことを学びました。

 

ちなみに、上記の改善をすべて行ったことで、最終的にどの程度の変化があったかというと、 

f:id:ytr0903:20200424213222p:plain

初回描画が9秒から3秒、セル選択が5秒から1秒未満、セル編集が3秒から1秒未満に短縮されました。

……これでもまだ遅い部分はありますが、データ量に依存することも踏まえれば、ある程度実用的な範囲には収めることができたかなと思っています。

まだまだ最適化の余地はあると思うので、今後もリリースに向けて改善を続けていく予定です。

 

ここまで読んでくださってありがとうございました。何らかの参考になれば幸いです。

 

また、このようなプロダクトの改善や新規開発に興味を持った方は、ぜひ一度弊社の採用サイトを覗いてみて頂ければと思います。Webブラウザ上でこのような複雑な機能開発をするのは、困難を伴いますが刺激的でとても楽しいです。一緒にお仕事できる方をお待ちしております。

hrmos.co

 

Kubernetesがコンテナイメージのダウンロードに失敗する原因を探る

はじめに

バックエンドエンジニアの須恵です。 過去の記事ではSREを名乗っていましたが、最近はGoでAPIサーバーを開発するのが主業務になっています。

依然SREチームの末席にも名を連ねさせていただいており、自分の開発しているAPIサーバーが乗っているKubernetesクラスターのメンテナンスや、クラスターに同居している他のサービスのCI/CD関連に取り組んだりしています。

会社のブログに寄稿するのは久しぶりです。

起きた問題

いつからか、そこそこ頻繁に、Podの更新が失敗する症状が発生するようになりました。 その際PodのステータスとしてErrImagePull または ImagePullBackOffが確認できました(ErrImagePullからImagePullBackOffに推移する)。

kubectl describeすると、このようになっています。

f:id:tsue88oct:20200407204958p:plain
拡大してご覧ください

ErrImagePullの発生原因としてまず候補に挙がるのは「存在しないイメージをダウンロードしようとしている」というものですが、イメージはECRにpush済みであり、docker pullすることもできます。

Kubernetesが調整ループを根気よく回し続けた結果、数分後~数時間後には解消していることがほとんどだったのですが、解消までの時間にムラがありすぎるのと、CircleCIのジョブやHelmのrelease statusが失敗扱いになってしまい、開発体験に悪影響を与えていました。

(特にHelmについては、保存されるhistoryの上限を超えて失敗するとデプロイ可能な状態に復旧するのに手間がかかります)

先に結論から

原因

イメージのダウンロードは(実際には)進行中であるが、 kubeletのタイムアウト期間(デフォルトの1分)の間にDockerから進捗報告ができず、ダウンロードを打ち切られている。

対策

対症療法

今動いているノードのkubeletのオプション

--image-pull-progress-deadline=10m

を直接変更する。

根本対応

CloudFormationで作られたノードであるため
テンプレートでkubeletのBootstrapArguments

--kubelet-extra-args "--image-pull-progress-deadline 10m"

を追加する。


ここからは、時系列で解決への道のりを書いていきます。

検索してIssueを発見

KubernetesのIssueを検索してみると、今回の困りごとと一致していそうなタイトルを見つけました。

github.com

このIssueのコメントに、

「kubeletに --image-pull-progress-deadline を設定しましたか?」

とあります。

(kubeletは、Kubernetesの多数あるコンポーネントのうち、ワーカーノードに常駐し、ノードのリソースやPodの状態を監視してAPIに送信したり、コンテナランタイムにコンテナ起動などの指示出しをするコンポーネントです)

続くこちらのコメント(および引用されたソースコード)によると、

「kubeletは、一定周期ごとにDockerからのイメージプル進捗報告を期待しており、進捗報告が得られない場合プルを打ち切る」こと、

「一定周期」は--image-pull-progress-deadlineで設定可能(デフォルトは1分)であることがわかります。

サポートに問い合わせ

本当にこのIssueと同件なのか、なかなか確信が持てず、確信に至るための打ち手も見えなかったので、AWSサポートを頼ることにしました。

(ところで、問い合わせの極意について、もうお読みになりましたか?)

サポートケースを開いて相談したところ、担当の方から、 「EKSログコレクターのログ提供」を手順を添えて依頼されました。

EKSログコレクターとは

こちらです↓↓

github.com

これは何

READMEを眺めてみると、

  • OSのログ
  • kubeletのログ
  • Dockerデーモンのログ

を収集してアーカイブするシェルスクリプトのようです。

ログコレクターの実行まで

一刻も早くサポートへログを提供したいところですが、ここでまた壁にぶつかりました。

ノードに入る手段の確立

ログコレクターを実行するにはワーカーノードの中に入らなければなりませんが、問題のノードにはSSM エージェントはインストールしておらず、セキュリティグループでポート開放もしていませんでした。

一時的に開放して作業するか思案していたところで、このようなツールを発見しました。

github.com

これは、SSMエージェントがインストールされたコンテナをDaemonSetとしてデプロイし、かつノードのディレクトリをマウントすることで、後付けでセッションマネージャーを利用可能とするものです。

必要十分な取り扱い方がREADMEに記載されていますが、都合により一部異なる手順で利用します。

このクラスターではkube2iamを動かしているので、ノードに紐付いているロールを改変する必要はなく、DaemonSetにannotationを書き足すだけで十分です(同様に、公式のIAM Role for Service Accountsを利用しているクラスターであれば、そちらの仕組みに乗ることができます)。

  template:
    metadata:
      annotations:
        # ポリシー AmazonEC2RoleforSSM を持つロール
        iam.amazonaws.com/role: arn:aws:iam::xxxxxxxxxxxx:role/ALLOW_SSM

また、eksctlで作ったクラスターではないので、下記のコメントアウトも必要でした。

        # For debugging .env files produced by eksctl
        # - name: etc-eksctl
        #   mountPath: /etc/eksct
      # For debugging .env files produced by eksctl
      # - name: etc-eksctl
      #   hostPath:
      #     path: /etc/eksctl
      #     type: Director

ログコレクターが動かない(パッケージ不足)

無事、kube-ssm-agentを利用してワーカーノードに入れるようになりました。 満を持してログコレクターを実行しますが、必要なパッケージが不足しておりエラーに阻まれます。

sh-4.2$ sudo bash eks-log-collector.sh

    Fatal Error! Application "iptables" is missing, please install "iptables" as this script requires it, and will not function without it. Exiting!

それならばと、iptablesをインストールします。

sudo yum install iptables
備考

※本当にノードにパッケージが存在しないのではなく、コンテナ内部ではそのように認識されているだけだと思います(別件でkube-proxyの起動設定を見たときに--proxy-mode=iptablesだったため)。


今度こそ…

sh-4.2$ sudo bash eks-log-collector.sh

    Fatal Error! Application "sysctl" is missing, please install "sysctl" as this script requires it, and will not function without it. Exiting!

なるほどそうですかと、procpsをインストールします。

sudo yum install procps

これでログコレクターが実行できるようになりました。

sh-4.2$ sudo bash eks-log-collector.sh
(略)
    Done... your bundled logs are located in /opt/log-collector/eks_i-xxxxxxxxxxxxxxxxx_2020-03-18_0619-UTC_0.6.0.tar.gz

kube-ssm-agentの内部でログコレクターを動かしたため、生成物はkubectl cpを使ってローカルにダウンロードできます。

kubectl cp ssm-agent-24lnj:/opt/log-collector/eks_i-xxxxxxxxxxxxxxxxx_2020-03-18_0619-UTC_0.6.0.tar.gz ./eks_i-xxxxxxxxxxxxxxxxx_2020-03-18_0619-UTC_0.6.0.tar.gz

サポートからの回答

ようやくログをサポートに提出できました。 いただいた回答によると、やはりkubeletのタイムアウトが発生しているとのことでした。 根拠として、下記のログをピックアップして提示いただきました。

# 多少編集を加えています
Cancel pulling image "略" because of no progress for 1m0s, latest progress: "d25013272171: Extracting [==================================================>]  88.27MB/88.27MB"
level=error msg="Not continuing with pull after error: context canceled"
PullImage "略" from image service failed: rpc error: code = Canceled desc = context canceled
container start failed: ErrImagePull: rpc error: code = Canceled desc = context canceled
Error syncing pod, skipping: failed to "StartContainer" for "略" with ErrImagePull: "rpc error: code = Canceled desc = context canceled"

「1分間進捗報告がなかった」ことにより、ErrImagePullへつながっています。

kubeletの設定変更を推奨されたので、実行します。

kubeletの設定変更

ユニットファイルを変更したあと、サービスを再起動します。 systemdのサービスとして動いていることを初めて知りました。

引き続きkube-ssm-agentが活躍します。

sudo vi /etc/systemd/system/kubelet.service

--image-pull-progress-deadline 10m \を追加します。

ExecStart=/usr/bin/kubelet --cloud-provider aws \
    --config /etc/kubernetes/kubelet/kubelet-config.json \
    --allow-privileged=true \
    --kubeconfig /var/lib/kubelet/kubeconfig \
    --container-runtime docker \
    --image-pull-progress-deadline 10m \
    --network-plugin cni $KUBELET_ARGS $KUBELET_EXTRA_ARGS

サービスを再起動します。

sudo systemctl daemon-reload
sudo systemctl restart kubelet

今回問題が起きていたのは開発環境のクラスターであったので、実際はそれほど気を揉むことなくエイヤと行いましたが、ノードとPodが定常状態(=リソース状況、Podの状態に急激な変化なし、かつコンテナランタイムに指示出し中ではない)であれば、 特に我々が運用しているサービスの側にダウンタイムが発生する懸念はありません。

処置後の経過観察

デプロイを何度も試行してみないことには効果が現れているか確認ができないので、気長に様子を見続けます。

すると、明らかに症状が改善していることがわかりました。

f:id:tsue88oct:20200408125807p:plain
🎉

kube-ssm-agentの除去

便利な反面、稼働させたまま放置しておくのはセキュリティ的によろしくありません。 用が済んだら削除しておきましょう。

kubectl delete ds ssm-agent

こうして簡単に削除できるのも利点ですね。

おわりに

自分の運用しているクラスターで開発体験が悪化することは慙愧の念に堪えませんが(というか、自分も開発しているから困る)、今まで知らなかったことを学ぶ良い機会になりました。

もっとKubernetesに詳しくなっていきたいです。