データ指向アプリケーションデザインのまとめ

www.oreilly.co.jp

昨年夏から続けてきたデータ指向アプリケーションデザインの輪読がほぼ終わったので、書かれていた内容を咀嚼するために理解した内容をアウトプットすることにしました。

それぞれの技術レイヤで一般的な選択肢と選択基準を文章化して、後から見返したときにデータシステムを設計の参考になるように基準となる軸が明確になるよう意識しています。

詳しい方から見ると私の理解が甘く間違いを書いてしまっているところもあるかもしれません。見つけられたらご指摘下さい。

整理した観点

単一のシステム

データモデルの選択

選択肢

選択基準

スキーマに柔軟性を持たせるかとアプリケーションにデータのマージが必要かどうかで判断する。

パフォーマンスを考えると、スキーマを変更することがなければスキーマオンライト(RDB)、スキーマに柔軟性を求めるならスキーマオンリードのデータモデル。

スキーマオンリードを選択したときの観点。ドキュメントデータベースは結合のサポートが弱い。グラフデータベースはドキュメントベースに比べてデータの持ち方が複雑。単一のデータだけを扱えればいいならパフォーマンスが高いドキュメントデータベースを選び, 関係性の抽出を行うパフォーマンスを重視するならグラフデータベースを選ぶ。

ストレージエンジンの選択

選択肢

  • データの持ち方
    • 行指向
    • 列指向
  • インデックスの有無
    • インデックス無し
    • インデックス有り
      • インデックスはメモリ上に保持
        • ハッシュインデックス
      • インデックスをファイルに保持可能
        • SSTable
        • LSMツリー(SSTableにlog-structuredを取り入れたもの)
        • Bツリー

選択基準

データを扱うアプリケーションのワークロードがトランザクション処理(OLTP)なのか分析(OLAP)なのかで選択する。

OLTPは全てのデータの中から一部を取り出して扱うのでランダムアクセスに強くなるようにインデックスが有る方がよい。データの持ち方は行指向。

OLAPは一度に大量のデータをスキャンするのでデータのアクセスができるだけシーケンシャルになるように列指向でデータを保持するのがよい。インデックスの有無は必要に応じて決める。

インデックスを有りにするとデータの読み込みとクエリ実行のパフォーマンスが上がるがデータ追記のパフォーマンスが下がる。

インデックスを有りにすると決めたのならほとんどの場合でBツリーを選べば良い。Bツリーはコンタントにパフォーマンスが高くなる。LSMツリーを採用するのはコンパクション中にパフォーマンスが落ちることを許容できてそれ以外のときにBツリーよりも高いスループットを求めるとき。

ただし、OLAPで列指向のデータの持ち方をして、かつインデックスを有りにするならLSMツリーを採用する。Bツリーは使えない。

データシステム間の通信

選択肢

  • エンコード(デコード)方式
    • 言語バインド有り
      • バイナリフォーマット
        • 言語固有のフォーマット
    • 言語バインド無し
  • データフローの形式
    • データベース経由
    • サービス呼び出し経由
      • REST
      • RPC
    • 非同期のメッセージパッシング経由

選択基準

テキストフォーマットはバイナリフォーマットに比べてデータサイズが大きくなる。ただし、人間が読みやすいメリットがある。

バイナリフォーマットにスキーマが有ると、そのデータを扱うアプリケーションの前方互換性と後方互換性の保証が可能になる。ただし、それらを保証できるかはフィールドに必須フィールドをいれるのか、フィールドに初期値を設定するかといった設計による。

ThriftとProctocol Buffersに比べてAvroはデータサイズが小さい。その代わり、Avroはスキーマ中のフィールドの順番がライターとリーダーで一致しない。フィールドの値の扱いはリーダーが決める。

エンコード方式で言語固有のフォーマットを選択することはまずない。

  

データフローの形式を考える。通信相手に送信したあとレスポンスを返して欲しいときはREST。レスポンスが不要な場合は非同期のメッセージパッシング経由。後者は送信相手が一時的に応答負荷になっていたとしても正常に戻った後にメッセージを受け取ってもらえることが期待できる。ただし、メッセージが届くことが保証されているわけではない。(exactry-onceのメッセージブローカがれば保証される?)

データベース経由とRPCについては力不足で選択基準を整理できていなかった。データベースの扱いを除外するのはローカルのデータベースなのかリモートのデータベースなのかで扱いが変わるため。リモートのデータベースの場合は分散システムの話が関わってくる。レプリケーションやパーティショニングの有無やトランザクションの種類, 求める一貫性の強さ, 必要なパフォーマンスでデータベース経由を採用できるか決まる。RPCを除外するのは完全に自分の力不足。*1

高負荷への対応

水平スケールを選択肢にいれており続く大規模環境とも範囲が重なるが、垂直スケールとの比較のために小規模環境の節にいれることにした.

選択肢

  • 垂直スケール
  • 水平スケール

選択基準

コストをかけることができて現実に構築できるマシンスペックでデータシステムを運用できるなら垂直スケール。コストを抑える代わりに分散システムの様々な問題に対処していくことができるなら水平スケール。

一区切り

「高負荷への対応」で水平スケールを採用するならば 単一システムで扱った範囲に加えてこのあとに続く分散システムについても設計を行う.

分散システム

パーティショニング

選択肢

  • パーティショニング無し
  • パーティショニング有り
    • パーティショニング方式(キーバリュー型のデータの場合のみを考える)
      • レンジパーティショニング
      • ハッシュパーティショニング
    • セカンダリインデックス
    • リバランスの戦略

選択基準

分散システムでデータやクエリの負荷をある程度均等に分配したいときにパーティショニング有りを選ぶ。

ここではデータがキーバリュー型であることを想定する。

パーティショニングの方式はレンジパーティショニングとハッシュパーティショニングが基本になる。どちらを選ぶかはデータの偏りによる。パーティションに含まれるデータができるだけ均等になるように選ぶ。

セカンダリインデックスが必要になるのはプライマリーキーに加えて別の条件でも検索したくなるとき。セカンダリインデックスを採用すると読み取りのパフォーマンスが上がる一方、セカンダリインデックスの更新が必要になるので書き込みのパフォーマンスは下がる。

セカンダリインデックスはローカルインデックスとグローバルインデックスの二種類。全体に対する検索ではグローバルインデックスがある方が効率的だが、データを追加するときに発生するセカンリインデックスの更新処理はローカルインデックスと比べて重い。一方で、絞り込みの検索ならローカルインデックスの方が効率が良い。グローバルインデックスが必要になる代表例は全文検索システム。

リバランスの戦略はデータ量の変動とノード数の変動が発生したときにパーティションを均一に保つかどうかで決める。均一に保つことを諦めるかわりにシンプルに運用できるのがパーティション数固定。均一に保つことを目指すならデータ量に合わせてパーティション数を増やす。一方、データ量が増えるときにパーティションごとのデータ量を均等にするだけではなく、ノードあたりのデータ量も均等にすることを目指すならノード数による変動を選ぶ。

レプリケーション

選択肢

選択基準

分散システムで耐障害性を持たせない選択肢はないので実質レプリケーション無しは選択肢に入らない.

レプリケーションアーキテクチャはデータの一貫性と可用性のバランスで決める。書き込みの衝突を考慮せずに済むようにしたいならシングルリーダーを採用する。ただし、ノードやネットワーク障害、レイテンシのスパイクに弱い。マルチデータセンタを考えるならシングルリーダだと耐障害性に不安があったりレイテンシの問題が出てくるのでマルチリーダ。どのレプリケーションアーキテクチャあっても読み取りの整合性の考慮は必要。

リーダレスが選択肢に入る基準は整理できていない。単一データセンタに閉じていてノード障害に強くしたい場合?

レプリケーション方式は単純だが決定性に欠けるステートメントベース、決定性はあるがストレージフォーマットのバージョンが必ずリーダーとフォロワーで一致していないといけないため運用が難しいwrite-aheadログの転送、ストレージフォーマットのバージョンに後方互換性を持たせることができる論理(行ベース)ログ、バグを仕込んでしまう可能性が他より高いが自由度が高いトリガベースから選ぶ。

データ処理のエンジン

選択肢

選択基準

有限のデータを処理した結果が欲しいならバッチ処理。データの量が不定でその都度入ってくるデータを処理して結果が欲しいならストリーム処理。

一区切り

レプリケーションの一貫性とトランザクションの一貫性の覚書を書いておく。アプリケーションの要件から具体的なデータベースを選ぶときの参考としての覚書。

レプリケーションによって発生する読み取り(R)と書き込み(W)の問題

ここで扱う問題は全てレプリケーション間のコピーが非同期、あるいはリーダーと1つのフォロワーは同期的にコピーされるが残りのフォロワーとは非同期でコピーされる準同期であることに起因したもの。クライアントが一つしかなくリクエストに並行性がなかったとしてもレプリケーションの方法やタイミングによって整合性が取れなくなることがある。

リーダーと全てのフォロワーでデータのレプリケーションが同期的に行われることはパフォーマンスを考えると採用されない。

リーダーのアーキテクチャ 一貫性の性質 防げるトラブル
シングルリーダー read-after-write一貫性 「自分が」書いた内容をすぐにまた読み込むと値が空っぽ、あるいは上書きする前の結果が返ってくる
モノトニックな読み取り レプリケーションのコピーを実行中に複数のクライアントから読み込み処理があったとき、発生順序が先だったクライアントの読み込みがコピー後の正しい結果で、発生順序が後だったクライアントの読み込みがコピー前のものになる
一貫性のあるプレフィックス読み取り 因果関係がある書き込みがあったとき、その書き込み結果を読み込むと因果関係が無視した順序で読み込んでしまう(e.g. 質問とそれに対する答えの会話の書き込みがあったのに、答えの文言を先に読んで後から質問の文言を読んでしまう)
マルチリーダー シングルリーダーであったこと全部 シングルリーダーであったこと全部
LWWなど 書き込みの衝突
リーダーレス クオラム(条件付き) 読み取り内容が最新である保証がない
読み取り修復 レプリカ間のデータが等しい保証がない
エントロピー処理 レプリカ間のデータが等しい保証がない
LWWなど リーダーレスには順序制御がない
バージョンベクトル 複数クライアントが書き込んでいるとき、上書きなのか並行書き込みなのかを判断できない

トランザクションの一貫性と分離レベル

この話は分散システムもレプリケーションも前提ではない。*2

クライアントが複数あって、それぞれが単一オブジェクト、あるいは複数オブジェクトを操作するときの一貫性の強さ。

  • 直列化可能性は最も強い一貫性。並行して実行された複数のトランザクションがあったとしても、順番に行儀良く実行されたときと同じ結果になる。悲観的ロックである2PLで実現する。
  • 直列化可能なスナップショット分離。楽観的ロックで実現する

ただし、2PLがパフォーマンスを悪化させるため現実的ではない。現実的には分離性を確立して並行したトランザクションを受け入れる。*3

分離性には以下のような例がある。

対応策 防げるトラブル
スナップショット分離(SQL標準で定義される概念で一番違いのはリピータブルリード) 読み取りスキュー
アトミックな書き込み 更新のロスト
ロック*4 更新のロスト
compare-and-set*5 更新のロスト
衝突の実体化(ロック) 書き込みのスキュー*6
read-committed ダーティリードとダーティライト

メモ

  • ハッシュインデックスとBツリーの比較は?
  • もしも大量の画像を扱うならオブジェクトストレージを選択することになると思うが、まとめた内容で関連するのは耐障害性/高可用性に関する部分のみ?
  • レプリケーション方式は 論理(行ベース)ログを選んでおけば良いように見えるがデメリットは?
  • レプリケーションの有無とトランザクションに関係はない。
  • マルチリーダーとリーダーレスを選択する境界は?
  • ダーティライトの回避策の一般解は行レベルのロックと紹介されていた。分散DBでロックを取る方法は合意の利用なので、read-committedを実現するには合意が必要?
  • 線形化可能と直列化可能に関連があるのはなんとなく分かったが、関係性を言語化するのが難しい。今言語化できるのは下記が精一杯。

*1:RPC恐い

*2:こう書いたが正直自身がない

*3:Twitterで@tzkbさんからこのスレッドを紹介されてトランザクションの一貫性と分離レベルは別の概念だということを理解した

*4:レプリケーションがあると使えない

*5:レプリケーションがあると使えない

*6:ファントムによって引き起こされる

機械学習基盤をKubernetesで運用してきて

※ この記事は別のサイトで2018年12月08日に公開していた記事をコピーしたものです.

 この記事について

普段からオンプレのKubernetesクラスタを使った研究開発に携わっており、LANケーブルの配線から新バージョンのKubernetesやエコシステムの検証、構築、運用保守、独自ツールの開発に加えてフロントエンドのGUIをmaterial-uiで作ったりとほぼ全レイヤをやっています。

今回は機械学習の実行基盤としてKubernetesを運用してきたなかで得られた知見について書いていきます。

運用中のKubernetesクラスタについて

運用しているKubernetesクラスタはオンプレミスのCPUサーバとGPUサーバの混合構成で、さらにGPUサーバは複数の世代のGPUが混ざっています。 データサイエンティスト向けに内製した機能を持つことが大きな特徴ではありますが、用途は機械学習に限定しておらず内部向けのサービスを動かしていたりBotを動かしたりと様々な目的で使われています。

内製した仕組みは独自のWebAPIインターフェースを持っており、Dockerを知っているけれどKubernetesを知らないエンジニアでもKubernetes上にコンテナを作ることができるようになっています。 この仕組みにより、わざわざ複雑なKubernetesマニフェストを覚えなくてもデータサイエンティストはKubernetesのリソース管理の恩恵を受けることができます。 また、このインターフェースはKubernetesクラスタの運用側が開発しており、全体の利用状況を見て安定して利用できるように利用者のPodに様々な設定を追加、変更できる仕組みになっています。

この記事では運用中に実際にあったトラブルに対処してきた経験から得られた知見について書いて行きます。

知見

Jupyterの扱いは難しい

データサイエンティストにとって試行錯誤しながら分析できるJupyterは必須のツールでしょう。

ただ、リソース管理者であるKubernetesの運用者としてはJupyterの扱いは頭を悩ませる原因の一つです。 業務時間中に次々と思いついたことをあれこれ試し続けて数日のうちにいい結果が得られるなんてことはまずなく、定例打ち合わせに参加したり雑務で数時間別のことをしたり、横から新しい仕事を振られた結果Jupyterコンテナを立てたことも忘れて貴重なGPUを確保したまま一週間放置されるなんてこともあります。

今はモニタリングしているPodのリソース状況から、GPUを使うPodを作りながらも一週間程度利用していないと思われるPodの作成者に必要がないならデータを退避して一度リソースを空けてもらうように直接相談するようにしています。 システマチックにPodを削除することも可能なのですが、人それぞれの事情があるので直近ではそういった仕組みを導入することはないと思います。

PersistentVolumeをマウントしてチェックポイントを実施する

機械学習を扱うとGPUを使っても学習に1週間かかることが少なくありません。それなのに、あと少しで学習終わるというときにPodがkillされると悲惨です。

学習中のモデルはPersistentVolume上に定期的にチェックポイントしましょう。

自分だけではなく、他の人のためにもメモリの最大量(limits)を設定する

先ほどコンテナがkillされる可能性を挙げましたが、同一Node上のPodがメモリを使いすぎたためKubernetesによってkillされることが理由の半数以上を占めています。

KubernetesはPodをkillする優先度として各PodにQoSを与えています。 QoSは低い順に"Best Effort" < "Burstable" < "Guaranteed"となっており、雑に言うならばそれぞれ「リソースについてまったく宣言していないPod」「CPU、メモリのどちらか、または両方が必要な最少量だけが宣言されているか最少量と最大量に差があるPod」、「CPUとメモリの両方で必要な最少量と最大量が同じ値で宣言されているPod」になります。 分かりづらければ、リソースに対して何も設定しない"Best Effort"と厳密に設定する"Guatanteed"とそれ以外の"Burstable"と考えると覚えやすいです。

実際の環境で、メモリの最少量が宣言されているが最大量が宣言されていないQoSが"Burstable"のPodとQoSが"Best Effort"の学習用のコンテナが同じNode上で同居した際に"Burstable"のPodのメモリが膨らみ続けてしまった結果、"Burstable"のPodがNodeのメモリを全て使ってしまいQoSが"Best Effort"設定の学習用のPodを停止させてしまうことがありました。

これを防ぐには"Burstable"のPodがメモリの最大量(limits)を指定しておき、想定以上のメモリを使うようになればそのコンテナだけが停止するように振る舞うべきでした。

メモリのlimitsを設定するのは同じNodeで実行している他の人のコンテナを守ることにつながります。

PodをGuaranteedの設定で起動する

先ほどの例では"Burstable"のPodがlimitsを設定すべきと書きました。 しかし、"Best Effort"の学習用のPodが大量のメモリを必要とする場合、"Burstable"のPodがlimitsを指定していたとしてもいずれ"Best Effort"の学習用のPodがNode上のメモリを食い潰すことになればQoSの低さから優先的にkillされてしまいます。

このような時はこの2つのPodが同じNodeにスケジュールされるべきではありません。スケジュールによって安全を確保するためにも全てのPodが"Guaranteed"の設定であることが理想です。

limitsを指定できないなら、せめてbackoffLimitに0を指定したJobとして実行する

初めて実行するので必要なメモリの最大量の目安が分からないが何度も試行錯誤して見積もりをしている暇がないといった事情でどうしてもメモリのlimitsを指定できないこともあると思います。そういう時は実行に失敗してもRestartしないように.spec.backoffLimitを0に設定したJobとして実行しましょう。

経験上、機械学習でコンテナがメモリ不足になるのはこれまで何度も実行してきた学習用のプロセスに今までよりも大きなデータを与えたときに起こりやすいです。

何も考えずに極端に大きなデータを与えてしまうとそのサーバのメモリを食い潰し(同じNode上の"Best Effort"のPodを停止させてから)killされた後に他のNodeでリスタートします。このようなPodはたいてい次のNodeでもメモリ不足を起こし他のPodを巻き込みながらkillされることになり、例えるならNodeを渡り歩く爆弾 になります。 backoffLimitの設定を忘れると悲惨です。現時点の最新版であるv1.13ではAPI ReferenceによるとbackoffLimitの初期値は6に設定されており、(初期の起動を含めると backoffLimit+1 回起動するので)最大で7つのNodeを渡り歩くことからその恐ろしさは推して知るべしです。

GPUサーバにGPUを使わないコンテナを割り当てない

GPUの資源は希少なのでGPUが余っているのにCPUやメモリが不足しているという状況を作らないことが大事です。 我々の環境ではnodeSelectorを使ってGPUが必要ないPodはGPUサーバに割り当てないようにしています。

実際の運用では副次的な効果もありました。 ある時、GPUを必要としないDB系のPodが複数回リスタートする設定で作られた際にリソースの見積もりがあまかったために数百GBのメモリを要求した結果、Nodeを渡り歩く爆弾になりNode上のPodを殺してまわったことがありましたが、GPUを必要としないDB系のPodはGPUサーバにスケジュールされることがなかったため長時間モデルを学習プロセスを守ることができました。

GPUの種類を指定できるようにする

複数の世代のGPUをまぜて運用すると、計算性能やメモリ量の違いからどうしても特定のGPU上で実行したいという要望が出てきます。 しかし、kubeletはサーバに積まれているGPUの数を認識するものの積まれているGPUの種類までは認識してくれません(要DevicePlugin)。

そこで、GPUを持つNodeにGPUの種類を区別できるラベルをつけ、nodeSelectorでスケジューリングされるサーバを指定できるようにしています。 公式ドキュメントのClusters containing different types of NVIDIA GPUsに具体的な方法が書かれている方法をそのまま採用しています。

GPUの空き状況をモニタリングして公開する

機械学習GPUが必要になる場合、同じ人でも試行錯誤を繰り返して特徴量を設計しているフェーズの時もあればパラメータ探索のために並行に大量の学習モデルを作りたいフェーズの時もあるのでユーザに対してQuotaを設けることはしていません。

その代わり、誰がどれだけGPUを使っているかをGrafanaで公開しており、ユーザが他のユーザの利用状況を確認できるようにしています。 自分が使いたいけれど特定の人が長期間GPUを占有している場合はユーザ間で調整をしてもらうようにお願いしていて、現状はそれでうまく回っています。

まとめ

機械学習基盤としてKubernetesを運用してきた知見をまとめてみました。 振り返るとほとんどはPodのリソース関係で安定してPodを動かし続ける知見になりました。

Kubernetesが想定する障害への対処策である「コンテナをステートレスに作ったうえでレプリカを作っておき、Podがダウンしたら生き残っているPodが処理を継続しつつ他のNodeに新しくPodを作り直して復帰する」という戦略を、希少なハードウェア資源を使って単一のプロセスが長時間かけて実行するような機械学習の学習用コンテナに適用することはできません。一度実行が中断されてやり直すコストが高かったり、リソースが限られているためレプリカなどで投機的な実行をする余裕がないからです。

分散処理ならタスクを分割した上で失敗したタスクをリトライする方式をとれるのですが、単一のコンテナで長時間かかる学習をマルチテナントのKubernetesクラスタで安定して動かせるようにするのは難しいですね。

Kubernetesのカスタマイズポイントのまとめ

※ この記事は2019年03月23日に別のサイトで公開していたものをコピーしたもので、最新のKubernetesに対応している保証はありません

インフラエンジニアがKubernetesを使って自社の基盤を設計する上で気になるのは「Kuberneretesが自社の要件にフィットするのかどうか」です。 そういった時のためにKubernetesは拡張可能なポイントを数多く備えています。 この記事では現時点で最新のKubernetes v1.13のカスタマイズ可能なポイントを整理して自社の要件に必要な機能をKubernetesに持たせることができるのかどうかを考えるための情報をまとめます。

なお、この記事で扱うのはAPIリクエストのカスタマイズの方法であり、ネットワークやストレージまわりのカスタマイズは対象としません。 通信のパフォーマンスやデータのバックアップ、リストアについて検討されている方はCNI(Container Network Interface)CSI(Container Storage Interface)のドキュメントをご覧ください。個人的にはCSIを使った実装の一つであるRookに注目しています。

KubernetesAPIリクエストのフロー

まず、カスタマイズをする前のKubernetesの基本的な処理の流れを確認します。KubernetesはRESTfullなAPIで設計されており、Masterで立ち上がっているAPI ServerがKubernetesの各種リソースに対するリクエストを受け付けています。

Kubernetesアーキテクチャはとてもシンプルで、ユーザがkubectlを実行すると対象のリソースの操作内容に対応するAPI Serverのエンドポイントにリクエストが送られます。 API Serverの役割はリクエストのヘッダを確認して認証と認可を実施したあと、受け取ったリクエストをetcd上に保存することです。Kuberbernetesは各種リソースに対応するControllerがetcdを監視しており、コンテナの作成やServiceの作成、変更などの実際のアクションはControllerがetcd上のデータの追加や変更を検知して実行します。 このように、あるべき姿を定義した上でControllerが状態の変化を監視し、変化があれば対応する処理を実施することをReconciliation loopと呼びます。

カスタマイズ機能の分類

Kubernetesにどんなことがカスタマイズできるのかを調べるということは、ユーザがリクエストを送ってからetcdにリソースの定義を書き込むまでの間にどのような情報を付与するか、またはオリジナルのリソースとそれに対応するControllerを定義するか、あるいはそのどれでもない完全に独自の仕組みを作るかに分類できます。

それぞれの選択肢には明確な違いがあり、API Serverのリクエストを補完する拡張やAPI ServerのAPIエンドポイントを増やす拡張、Kubernertesが想定する枠組みをほとんど使わない方法があります。

大まかに分類すると以下の通りです。

手法 説明
Admission Controller API Serverに対するリクエストの認証。認可を済ませたあとにそのリクエストをハンドルする拡張。様々な種類がある。どの拡張を実施するかはKubernetesクラスタ構築時に指定する
Custom Resource Definition API Serverに新しい種類のリソースを登録する。リソースの定義や振る舞いは独自に定義できる。クラスタ構築後でもサービスを停止することなく追加可能
Aggregated API Server k8sAPI Serverの基本プロトコルでは実現できない処理を実現するための専用のAPI Serverを別途用意する拡張

なお、最近のKubernetesの流行として押さえておくべきトピックとしてOperatorがあります。OperatorはCustom Resorce Definition(通称CRD)のうち、特定のアプリケーション、ミドルウェアに特化したものです。Operatorは特定のツールを抽象化するための強力な仕組みです。

例えば、ビッグデータのインメモリ分散処理フレームワークApache SparkがKubernetesをサポートしたすぐ後にGoogleApache SparkのためのOperatorを公表しました。Spark Operetorがデプロイされたk8sクラスタであれば、以下のマニフェストを使ってKubernetesにリクエストを投げることでSparkジョブを実行することができます。 

apiVersion: sparkoperator.k8s.io/v1beta1
kind: SparkApplication
metadata:
  name: spark-pi
  namespace: default
spec:
  type: Scala
  mode: cluster
  image: gcr.io/spark/spark:v2.4.0
  mainClass: org.apache.spark.examples.SparkPi
  mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.11-2.4.0.jar

もしもKubernetes上で特定のアプリ、ミドルウェアを使うことがあるのであれば、対応するOperatorが公開されているかを調べてみるのもいいかもしれません。

ただし、Operatorは想定した動作を簡単なyamlを使って実行するための仕組みです。普段の作業をテンプレートに落とし込めるので正常系の処理は楽になりますが、障害が発生したときはそのアプリやミドルに詳しい人が必要であることには変わらないので、Operatorがあれば運用の全てが解決するわけではないことは忘れないようにしましょう。 例えばorg.apache.spark.examples.SparkPi以外のSparkアプリケーションを実行すると毎回SparkのMasterのPodがOOMで死ぬといった場合はOperatorが対処できる範囲外で、spark.driver.memoryを見直してDriverに割り当てるメモリを増やすなどSparkの知識を必要とするチューニングが必要になります。

Admission Controller

Admisstion Controllerは最もシンプルで簡単なカスタマイズ手法です。 ユーザからリクエストを受けとったAPI Serverが認証と認可を済ませた後に、そのリクエストに含まれるパラメータを差し替えたり認可とは別に実行そのものを許可するかどうかを制御することができます。 Kubernetesがあらかじめ用意している複数のポリシーから選択してAPI Serverに設定することができます。

Kubernetes v1.13時点では以下がデフォルトで設定されています。

NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeClaimResize,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,Priority

API Server起動時に--enable-admission-pluginsオプションにカンマ区切りで書くことで他のAdmission Controllerの追加や削除をすることができます。

Admission Controllersは数が多いのでここでは個別の説明は省きます。 詳細を知りたい方は以下のリンク先にそれぞれの説明がありますので参照してください。

https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/

Custom Resource Definition

Custom Resource Definition(CRD)はAPI Serverに新規のエンドポイントを登録するための仕組みです。新しいリソースの定義をCRDとして登録すると、API Serverにそのリソースのエンドポイントを新規に追加することができます。

CRDだけではAPI Serverにリクエストのエンドポイントを追加することしかできません。追加したリソースの挙動はそのリソースのCRDに対応するCustom Resource Controllerが決定します。 このControllerは自分で実装した上でKubernetesクラスタ上にデプロイする必要があります。 デプロイされたContollerは独自のロジックに従い対応するリソースに対してReconciliation loopを実行していきます。

Custom Resource DefinitionとCustom Resource Controllerを実装するためのSDKの例として以下があります。

  • kubebuilder
  • Operator SDK
  • Metacontroller

余談ですが、通常だと1つのリソースに対して1つのControllerが存在するのですが、PodやDeploymentなどデフォルトで利用可能なリソースのControllerはKubernetesクラスタ構築時にkube-controller-managerという名前で1つのPodとしてデプロイされています。

Aggregated API Server

Aggregated API ServerはKubernetesの標準コンポーネントであるAPI Serverではなく新しく用意する別のServerで処理を実現するための仕組みです。 API Serverはユーザから受け取ったリクエストを処理する機能を持つことはこれまで説明してきましたが、実はその処理を他のServerを委譲する抽象化レイヤを持っています。 デフォルトのAPI Server以外にユーザから受け取ったリクエストを処理するAPI ServerをAggregated API Serverといいます。 Aggregated API Serverの例としてmetrics serverやservice catalogがあります。

Aggregated API Serverが登録されているAPI Serverはユーザのリクエストの認証と認可を済ませると該当する処理をAggregated API Serverに丸投げします。 Aggregated API Serverは受け取ったリクエストを内部で処理し、結果をAPI Serverと通じてユーザに返します。 Custom Resource DefinitionではAPI Serverが全ての処理を実施した上でReconcilation loop処理を実施していましたが、Aggregated API Serverではさらにその前でhookすることが可能になるため自由度はCustom Resource Definitionよりも高いです。

Aggregated API Serverを実装するためのSDKとしてapiserver-builderが用意されています。

カスタマイズ手法の比較

手法 追加実装 拡張の自由度の高さ
Admission Controller 不要 低い
Custom Resource Definition 必要 ある程度制約があるが比較的自由
Aggregated API Server 必要 ほとんど制約がなく自由

導入を検討する場合はまず最初に使えるAdmission Controllerがないかを調査し、必要な機能が不足していると感じればCustom Resource Definitionを検討し、それでも駄目ならAggregated API Serverを採用する順番がおすすめです。 自由度の高さはそのまま運用コストと利用方法を普及するコストの増加につながりますので、運用者が少ない場合はできるだけAdmission Controllerの利用に留めるのが正解です。

資料

【サイバーエージェント&ニフクラ】春の Kubernetes 祭り! @銀座 にて上に書いている内容を図にしてまとめて発表しました。

スライドはSpeakerDeckにて公開しておりますのでぜひそちらもご覧ください。

Kubernetesのカスタマイズポイントのまとめ

Docker Meetup #27参加報告

Docker Meetuo#27の参加報告

ざっと見て19:00の段階で8割の席は埋まっている

KubeCon 報告1: コンテナランタイムやFirecrackerの話題ひととおり振り返ってみよう (Container runtimes & Firecracker Recap) Kohei Tokunaga

  • 2018年はコンテナランタイム群雄割拠の年
  • KubeConで出てきたコンテナランタイムに関する話題を全て紹介する
  • k8sランタイムとセキュリティ
    • コンテナ内からの脅威とコンテナ外からの脅威に分けられる
    • コンテナ内からの攻撃は現実には存在しないが、そのうち発生してくると予想されている
    • 何をすればいいか
      • 脆弱性をなくすこと
      • 権限に制限を加える
      • コンテナが影響を与える範囲を狭める
    • これらはランタイムによって実現可能
    • Kubernetesセキュリティチームは脆弱性があってもgithubのissueで報告してはいけないというルールになっている
    • dockerdは拡張性の高さが特徴
    • cri-o
    • podman
      • 軽量かつrootlessなのが特徴
    • kata containers VS gVisor
      • kata
        • Pod単位のVMでコンテナを隔離
        • VMと名前があるが、runc並みにCPU, RAM, NW性能が高い
      • gVisor
        • ユーザ空間でカーネルを再実装している
        • NWはKataの半分程度の性能しかでないため、現実世界のアプリケーションでのパフォーマンスは低め
    • kata VS Nable
      • Nabla
        • 7種類のシステムカールのみを発行しており、バグを踏みにくいと主張
    • Firecrackerのミーティングが場外で行われたので参加してきた
      • 軽量VMでコンテナを隔離
      • 現状ではOCIやCRIには準拠していないが、Kataやcontainerdと統合してKubernetes上で使えるようになりつつある
      • containerdのプラグインの一つとして振る舞う
  • 感想
    • 自分の環境にどのランタイムが判断できる人ってどれだけいるんだろう

KubeCon 報告2: About kanister Makoto Hasegawa

  • 資料: https://speakerdeck.com/makocchi/kubecon-cncon-2018-recap-about-kanister
  • 管理者からみるとランタイムは動かしてみると面白いよ
  • Kanisterの話をする
  • CI/CD, Kubernetes, and Databases: Better Togethe の話
  • Cloud Nativeな環境でDBを動かすのは難しい
    • アプリと隔離されており、ペットとして扱われている(家畜じゃないよ、ペットだと、という意味)
    • DB専属のエンジニアが必要になってくる
  • 全部 CI/CDに組み込んで自動化できれば幸せになるはず
  • Kanister(燃料缶の意味)があればそれができるよとのこと
    • data capster/export
      • SCI対応でblock storageなどにファイルをdumpできるし、S3にアップロードもできる
    • database manipulation
      • fileterやマスキングか可能になる予定
    • CRDを使った処理などKubernetesと密接したDBのコントロールが可能
    • kanisterが対応しているもの
    • kanister blueprintが肝だと思っている
      • taskを定義したCRDで、コミュニティに広がれば誰もが恩恵を得られるようになる
  • Kanisterはhelmでインストールできる
  • productionクラスタとtestクラスタそれぞれでDBを用意して、cloud storageを介してデータをやり取りしながら検証するデモ
    • 異なるnamespaceに同じ内容のDBを構築したりすることもできるようになる

Docker Compose-on-Kubernetes Kunal Kushwaha

  • docker-composeの役割の紹介
  • compose on kubernetesの話へ
  • docker stackコマンドでkubernetesにデプロイするデモ
  • 感想
    • dockerだけを使っていた人があとからkubernetesに移行するときに便利
    • Daplyomentのサポートはあるけど、Jobのサポートがあるか気になる

DockerのCUIツール作った話 @gorilla0513

  • 資料: https://docs.google.com/presentation/d/1ty9MbOGT8HThYVC1DVZtmhKS5MyCNGkDdkCTs6vpqzk/edit#slide=id.p
  • こんなのを作った:https://github.com/skanehira/docui
  • 初心者の人に便利な機能を用意したかった
  • 作ったモチベーション
    • Goを勉強したい, コマンド操作がめんどくさい、ターミナルから抜けたくない
  • 作ったとき大変だったこと
    • Dockerは業務で使っていなかったので詳しくなかった
    • cuiライブラリのgocuiも詳しくない
    • docker apiって何か知らなかった
    • 一人で開発していて相談できる相手がいなかった
  • 人に使ってもらえることや形になるまで作りたくてモチベーションを維持していった
  • 開発にしてくれる人を募集中
  • 感想
    • 熱意で業務に関係がないツールを作りきったのすごい

CNAB: Cloud Native Application Bundle Kohei Ota

  • CNAB: 分散アプリをパッケージングする企画
    • MicrosoftとDocker者が主体となって開発を進めている
  • Cloud Nativeはいろいろコンポーネントがあり、それぞれの学習コストが高い
  • CNABの基本構成
  • bundle,json: CNABのバージョンやイメージ等
  • app/Dockerfile:
  • run:
  • DuffleというCLIでCNABで管理しているアプリを操作する
    • goで書いてるのにgo getじゃ落ちてこないなどハードルが高め
  • Docker-appもCNABをサポートしている
    • https://github.com/docker/app
    • 2018年にDocker社がリリースしたエコシステム
    • docker-composeだと環境ごとの差分を扱うのが大変なので用意されたもの
  • Duffleのdemo
    • DuffleのREADME.mdに書かれているコマンドを順番に実行
  • 感想
  • 分散アプリと聞いてHadoopみたいなものの管理ツールかと思ったけれど、単にKubernetesのパッケージングツール?
  • 今回のデモの範囲だとhelmとの違いがよくわからなかった

LT 1: GitLab Serverlessとその中身(KubeCon 報告LT) Takuya Noguchi

  • AWS Lambdaがあるけどベンダ非依存でサーバレスを使いたいよねという人向けにKnativvveが出てきた
  • Gitlab ServerlessはKubecon Seattle中にalpha版としてリリース
  • Knative
  • Istioを含むヘビーな子
  • RedHat, アイビーエムgあサポート開始
  • Gilab Serverlessを使う手順
  • TrigerMesh CLI
    • tmコマンド で操作

LT 2: kubectlを実行してからetcdに書き込むまでに @yosshi_

LT 3:Rancher’s New Mluti-Tenant Prometheus Support Yutaka Ichikawa

  • 資料: https://qiita.com/cyberblack28/items/8a1dbd26aef8a85e6b8e
  • コンテナベースオーケストレーションの本の6章を担当、増刷決まりました
  • KubeConではMySQL OperatorやVittesが面白かった
  • Rancher 2.2で組み込まれるPrometheusの話をする
  • Prometheusはalpha版
  • カタログの機能としてGrafanaとPrometheusをそれぞれカタログが用意されていたが、それらのデプロイがさらに簡単になった
  • Monitoringというボタンを押して、Prometheusを選択してSaveするだけ
  • 各種方法は公式のBlogに書いてあるので読んでね

LT 4: JKDv18.12でCFP採択までに考えたこと Ryoma Fujiwara

  • 大きいイベントで話をしたいと考えてる人の背中を後押しするのが目的の発表
  • 考えたこと
  • 求められていることはなにか
  • 実感、共感を持ってもらう話をするには?
  • 求められていること
    • 例えば個別技術の細かい話だが、自分はOSSのメンテナでもないので、無理だと思った
    • そこで、沿い文の取り組みを知ってもらったり、何かを得てもらい、行動や判断にいい影響を与えたい
    • ユーザ企業にしか話せないことを考え、プロダクト開発で技術的にぶつかった課題やみんなが直面するであろう課題について話しをすることにした
  • 実感、共感を持ってもらう話をするには?
    • キラキラした話は実感をもってもらいにくい
    • そこで、実際に問題にぶつかった人に話してもらうのがいいと思い、いろんな担当者に登壇してもらうことにした
  • 発表することになると、正しいことを話すために調べて新しい知識を得られる
  • 予告: CloudNativeDays TokyoのCFPを近いうちに募集します

ラズパイクラスタにKubernetesをインストール(成功編)

「ラズパイクラスタにKubernetesをインストール(失敗編)」でCentOSを選んだためDockerのインストールに失敗したので、今度はUbuntuで試してみる。

今回の作業内容

全体の作業概要

Dockerをインストールしようとすると依存関係のエラーが出て、その解決方法を調べるのに時間がとられた。

OSイメージをMicroSDに焼く

UbuntuのwikiからOSイメージをダウンロードする。

公式イメージはPi2用しか配布されていないのでPi3にインストールするためには一手間が必要になる。一応Unofficialとして個人が公開しているPi3用のイメージへのリンクがwikiに貼られている。

いろいろ面倒そうなので公式イメージを使うことにした。

ubuntu-18.04.1-preinstalled-server-armhf+raspi2.img.xzMacにダウンロードしてxzコマンドで解凍する。

$ xz -d ubuntu-18.04.1-preinstalled-server-armhf+raspi2.img.xz 
$ ls
ubuntu-18.04.1-preinstalled-server-armhf+raspi2.img

MicroSDカードをUnMountしてからSDカードへ書き込む。1枚書き込むのにだいたい30分かかる。

$ diskutil unmountDisk /dev/disk2
Unmount of all volumes on disk2 was successful
$ sudo dd bs=1m if=./ubuntu-18.04.1-preinstalled-server-armhf+raspi2.img of=/dev/disk2
2252+0 records in
2252+0 records out
2361393152 bytes transferred in 1949.493602 secs (1211285 bytes/sec)

このOSイメージはPi2用なので、Pi3用で動かすためには

  • dbtファイルの置き換え
  • config.txtの書き換え

が必要。

また、Pi3 B+で動かすためにはさらにbootloader用にbootcode.bin, fixup.dat and start.elfを置き換える必要がある。

dbtファイルを含めて必要なファイルはGithubraspberrypi/firmwareリポジトリで公開されている。

wgetでダウンロードする。

$ wget https://github.com/raspberrypi/firmware/raw/master/boot/bcm2710-rpi-3-b-plus.dtb
$ wget https://github.com/raspberrypi/firmware/raw/master/boot/bootcode.bin
$ wget https://github.com/raspberrypi/firmware/raw/master/boot/fixup.dat
$ wget https://github.com/raspberrypi/firmware/raw/master/boot/start.elf

MicroSDカードが/Volume/system-bootにマウントされているとして、ダウンロードしたファイルをコピーする

$ cp bcm2710-rpi-3-b-plus.dtb /Volumes/system-boot/
$ cp start.elf /Volumes/system-boot/
$ cp bootcode.bin /Volumes/system-boot/
$ cp fixup.dat /Volumes/system-boot/

次に、/Vomue/system-boot/config.txtの中身を以下のように変更する。

# For more options and information see
# http://www.raspberrypi.org/documentation/configuration/config-txt.md
# Some settings may impact device functionality. See link above for details

# コメントアウト
#kernel=uboot.bin
#device_tree_address=0x02000000

# 追記
kernel=vmlinuz
initramfs initrd.img followkernel

終わったらSDカードを取り出す。

$ diskutil eject /dev/disk2
Disk /dev/disk2 ejected

OSをインストールする

MicroSDカードを刺した状態で電源をいれるとOSのインストールが始まる。

ログインプロンプトが表示されたら

アカウント名:ubuntu

パスワード: ubuntu

でログインしてすぐにパスワードの初期化を求められるので実施する。

  • ホスト名の設定: hostname XXX

続いて、ネットワークの設定をしてパッケージを更新する。

  • eth0にアドレスを割り当てる

eth0へのネットワークの割り当てはsudo dhclient eth0でできる。

続いて、パッケージを更新する。

  • sudo apt update
  • sudo apt upgrade -y
    • 途中、keyboad-configurationの設定を聞かれる.

上記を3台に実施する。

ホスト名とIPアドレス、それぞれの役割は以下の通り。

ホスト名 IPアドレス 役割
compute-0-0 192.168.10.109 KubernetesのMaster
compute-0-1 192.168.10.108 KubernetesのNode1
compute-0-2 192.168.10.107 KubernetesのNode2

このままだとネットワークの名前解決ができないので、各ホストの/etc/hostsに以下の内容を追記する。

192.168.10.109 compute-0-0
192.168.10.108 compute-0-1
192.168.10.107 compute-0-2

Dockerのインストール

インストールしたUbuntuはarmhf(32bit版)として動作している。docker-ceの推奨は64bitだがUbuntuであればarmhfをサポートしているので問題ない。

$ sudo apt-get update

$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# armv7lなのでarmhfアーキ用を実行
$ sudo add-apt-repository \
   "deb [arch=armhf] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

$ sudo apt-get update

$ sudo apt-get install docker-ce

$ docker --version
Docker version 18.09.0, build 4d60db4

ubuntuユーザがdockerコマンドを実行するたびにsudoが必要である。

sudoがなくてもdockerコマンドを実行できるようにubuntuユーザをdockerグループに追加しておく。

$ sudo gpasswd -a ubuntu docker
Adding user ubuntu to group docker
$ sudo systemctl restart docker

なお、依存関係のエラーでdocker-ceのインストールに失敗することがあった。

その場合はapt-utilの依存関係を修復する。

$ sudo apt-get install docker-ce
Reading package lists... Done
Building dependency tree       
Reading state information... Done
You might want to run 'apt --fix-broken install' to correct these.
The following packages have unmet dependencies:
 apt-utils : Depends: apt (= 1.6.3) but 1.6.6 is to be installed
 docker-ce : Depends: docker-ce-cli but it is not going to be installed
             Depends: containerd.io but it is not going to be installed
             Recommends: aufs-tools but it is not going to be installed
             Recommends: cgroupfs-mount but it is not going to be installed or
                         cgroup-lite but it is not going to be installed
             Recommends: pigz but it is not going to be installed
             Recommends: libltdl7 but it is not going to be installed
E: Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).

$ sudo apt --fix-broken install
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Correcting dependencies... Done
The following additional packages will be installed:
  apt-utils
The following packages will be upgraded:
  apt-utils
1 upgraded, 0 newly installed, 0 to remove and 89 not upgraded.
16 not fully installed or removed.
Need to get 0 B/192 kB of archives.
After this operation, 0 B of additional disk space will be used.
Do you want to continue? [Y/n] Y
...

修復が完了したらもう一度docker-ceをインストールする。

$ sudo apt-get install docker-ce
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following additional packages will be installed:
  aufs-tools cgroupfs-mount containerd.io docker-ce-cli libltdl7 pigz
The following NEW packages will be installed:
  aufs-tools cgroupfs-mount containerd.io docker-ce docker-ce-cli libltdl7
  pigz
0 upgraded, 7 newly installed, 0 to remove and 89 not upgraded.
1 not fully installed or removed.
Need to get 29.7 MB of archives.
After this operation, 149 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y
...

Kubernetesをインストールする

Kubernetesは1.8からSwapが有効になっているとkubeletが起動しなくなっている。 今回の環境はSwap領域は存在しないので気にしてくていいが、もしSwapが有効になっていれば無効化が必要。

# swap領域が0になっている
$ free -h
              total        used        free      shared  buff/cache   available
Mem:           923M        138M         84M        4.4M        701M        764M
Swap:            0B          0B          0B
# 念のためSwapの有無を確認
$ swapon -s

続いて、公式ドキュメントに従って全ホストでkubeadm, kubelet, kubectlをインストールする。

# rootになる
sudo su -
apt-get update && apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl

インストールしたパッケージのバージョンを確認する。

# dpkg -l |grep kube
ii  kubeadm                          1.13.1-00                          armhf        Kubernetes Cluster Bootstrapping Tool
ii  kubectl                          1.13.1-00                          armhf        Kubernetes Command Line Tool
ii  kubelet                          1.13.1-00                          armhf        Kubernetes Node Agent
ii  kubernetes-cni                   0.6.0-00                           armhf        Kubernetes CNI

続いて、Kubernetesクラスタのネットワークを設計する。

Kubernetesはoerlay networkを構築する必要がある。そのためのツールとして有名なところではflannnel, weave, Calicoなどがある。

今回はARMプロセッサの上で動くUbuntu 18.04の32bit版で動作することが要件になる。

Calicoの要件AMD64プロセッサであること。Ubuntuの32bitで動作するか不安なので見送る。

WeaveはARMをサポートしたバイナリをリリースしているものの、32bitに対応しているか明記されていなかったこととWeaveをラズパイ3B+で動かしたときにカーネルに起因するバグがあるissueを見つけたので見送る。

そこで、今回はflannelを採用することにした。

先ほど書いたようにホストは192.168.10.0/24で設定されている。

flannelが環境構築のために公開しているマニフェストは初期値が10.2544.0.0/16になっている。Pod IPのCIDRは10.244.0.0/16を割り当てることにした。違うCIDRを割り当てたい場合は、後述するflannelの環境構築時にgithub上のマニフェストを指定するのではなく、ローカルにダウンロードしたマニフェストを編集して利用すればいい。

KubernetesのMasterを起動するため、compute-0-0でkubeadm initを実行する。

$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16
[init] Using Kubernetes version: v1.13.1
[preflight] Running pre-flight checks
[preflight] The system verification failed. Printing the output from the verification:
KERNEL_VERSION: 4.15.0-1017-raspi2
CONFIG_NAMESPACES: enabled
CONFIG_NET_NS: enabled
CONFIG_PID_NS: enabled
CONFIG_IPC_NS: enabled
CONFIG_UTS_NS: enabled
CONFIG_CGROUPS: enabled
CONFIG_CGROUP_CPUACCT: enabled
CONFIG_CGROUP_DEVICE: enabled
CONFIG_CGROUP_FREEZER: enabled
CONFIG_CGROUP_SCHED: enabled
CONFIG_CPUSETS: enabled
CONFIG_MEMCG: enabled
CONFIG_INET: enabled
CONFIG_EXT4_FS: enabled
CONFIG_PROC_FS: enabled
CONFIG_NETFILTER_XT_TARGET_REDIRECT: enabled (as module)
CONFIG_NETFILTER_XT_MATCH_COMMENT: enabled (as module)
CONFIG_OVERLAY_FS: enabled (as module)
CONFIG_AUFS_FS: enabled (as module)
CONFIG_BLK_DEV_DM: enabled
DOCKER_VERSION: 18.09.0
OS: Linux
CGROUPS_CPU: enabled
CGROUPS_CPUACCT: enabled
CGROUPS_CPUSET: enabled
CGROUPS_DEVICES: enabled
CGROUPS_FREEZER: enabled
CGROUPS_MEMORY: missing
    [WARNING SystemVerification]: this Docker version is not on the list of validated versions: 18.09.0. Latest validated version: 18.06
error execution phase preflight: [preflight] Some fatal errors occurred:
    [ERROR SystemVerification]: missing cgroups: memory
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`

cgroupのmemoryが無効化されているらしい。/proc/cgroupsを確認するとたしかにmemoryのenabledが0(無効)になっている。

$ cat /proc/cgroups 
#subsys_name    hierarchy   num_cgroups enabled
cpuset  10  2   1
cpu 6   60  1
cpuacct 6   60  1
blkio   7   60  1
memory  0   68  0
devices 4   60  1
freezer 2   2   1
net_cls 3   2   1
perf_event  5   2   1
net_prio    3   2   1
pids    8   66  1
rdma    9   1   1

boot時の設定を変更してcgroupfsのmemoryを有効にする。

/boot/firmware/cmdline.txtcgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memoryを追記して再起動する。追記が終わったあとのファイルは以下の通り。

$ cat /boot/firmware/cmdline.txt 
net.ifnames=0 dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory

# reboot

再起動したあとはeth0に割り当てられたIPが消えているのでsudo dhclient eth0を実行する。

memoryサブシステムが有効になっているのがわかる。

$ cat /proc/cgroups 
#subsys_name    hierarchy   num_cgroups enabled
cpuset  2   1   1
cpu 5   34  1
cpuacct 5   34  1
blkio   4   34  1
memory  7   68  1
devices 11  34  1
freezer 9   1   1
net_cls 3   1   1
perf_event  8   1   1
net_prio    3   1   1
pids    6   39  1
rdma    10  1   1

再度Kubernetesをインストールしなおす.

$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16
[init] Using Kubernetes version: v1.13.1
[preflight] Running pre-flight checks
    [WARNING SystemVerification]: this Docker version is not on the list of validated versions: 18.09.0. Latest validated version: 18.06
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [ubuntu kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.10.109]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [ubuntu localhost] and IPs [192.168.10.109 127.0.0.1 ::1]
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [ubuntu localhost] and IPs [192.168.10.109 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[kubelet-check] Initial timeout of 40s passed.
[apiclient] All control plane components are healthy after 85.513804 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.13" in namespace kube-system with the configuration for the kubelets in the cluster
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "ubuntu" as an annotation
[mark-control-plane] Marking the node ubuntu as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node ubuntu as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: ntvqxo.ghxfq7gvi8rc79aw
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join 192.168.10.109:6443 --token ntvqxo.ghxfq7gvi8rc79aw --discovery-token-ca-cert-hash sha256:709766a97f016e2a0fd5113b12ce5afa1cebc740ecfa6596f8bbe9f30cb5897f

kubectlに認証設定を読み込ませるために指定されたコマンドを実行する。

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

動作していることが確認できる。

$ kubectl get node
NAME          STATUS   ROLES    AGE   VERSION
compute-0-0   NotReady   master   23m   v1.13.1

STATUSがNodeReadyなのはOveray Networkが設定されていないから。flannelをインストールする。

$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.extensions/kube-flannel-ds-amd64 created
daemonset.extensions/kube-flannel-ds-arm64 created
daemonset.extensions/kube-flannel-ds-arm created
daemonset.extensions/kube-flannel-ds-ppc64le created
daemonset.extensions/kube-flannel-ds-s390x created

# STATUSがReadyになった
$ kubectl get node
NAME          STATUS   ROLES    AGE   VERSION
compute-0-0   Ready    master   34m   v1.13.1

Nodeになるcompute-0-1とcompute-0-2で以下を実行する。

$ sudo kubeadm join 192.168.10.109:6443 --token ntvqxo.ghxfq7gvi8rc79aw --discovery-token-ca-cert-hash sha256:709766a97f016e2a0fd5113b12ce5afa1cebc740ecfa6596f8bbe9f30cb5897f 
[preflight] Running pre-flight checks
    [WARNING SystemVerification]: this Docker version is not on the list of validated versions: 18.09.0. Latest validated version: 18.06
[discovery] Trying to connect to API Server "192.168.10.109:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.10.109:6443"
[discovery] Requesting info from "https://192.168.10.109:6443" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "192.168.10.109:6443"
[discovery] Successfully established connection with API Server "192.168.10.109:6443"
[join] Reading configuration from the cluster...
[join] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet] Downloading configuration for the kubelet from the "kubelet-config-1.13" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Activating the kubelet service
[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "compute-0-1" as an annotation

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the master to see this node join the cluster.

compute-0-0からkubectl get nodeを実行するとNodeがクラスタとして認識されていることを確認できる。

$ kubectl get node
NAME          STATUS   ROLES    AGE   VERSION
compute-0-2   Ready    <none>   73s   v1.13.1
compute-0-1   Ready    <none>   75s   v1.13.1
compute-0-0        Ready    master   39m   v1.13.1

以上でKubernetesクラスタの構築ができました。

CentOSのコンテナを作ってみる。

$ kubectl run tmp-centos --rm -it --image centos -- bash
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
If you don't see a command prompt, try pressing enter.
[root@tmp-centos-cc49649b6-kpfvg /]# 

Deploymentが動くことも確認できた。

まとめ

Kubernetesクラスタのインストールは何度もやったことがあったので、初のラズパイといってもまぁすぐに終わるだろうと思っていたら、実家のネットワーク設定によるものやSDカードの不具合によるもの、OSの選定、OSインストール後の謎の個体差などなどでかなり時間がかかってしまった。

他にも実はここに書ききれない細かなトライ&エラーを繰り返している。Ubuntu 18系を使うのは初めてだったので、原因がOSのバージョン、カーネルのバージョン、ラズパイのどれに起因するものなのかの切り分けが大変だった。

トラブルの原因を調べて解決することを繰り返すことで知識がつくので、これはこれで新年からいい経験ができたと思う。

参考サイト

ラズパイクラスタにKubernetesをインストール(失敗編)

この記事はラズパイでKubernetesクラスタを構築する(物理編)の続きにあたる。

OSの選定を間違ってしまい作業が詰んだので失敗編としてまとめることにした。

正しい手順はラズパイクラスタにKubernetesのインストール(成功編)にまとめる。

OSとDockerのインストール

ラズパイ初心者なのでインストール可能なOSを一通り把握しておきたい。

Raspberry Pi で動く様々なOS一覧まとめ13種類!という2018.11.19の記事を読んでベーシックなRaspbianと普段業務で使っているCentOSで迷ったが、今後実際に使っていくことを考えるとKubernetesとして利用事例が多いCentOSを採用することにした。(そしてこれが失敗編を書くことになった理由である)

OSイメージをダウンロードする。 ラズパイのCPUはarmなので候補の一覧からarm向けを調べCentOS-Userland-7-armv7hl-RaspberryPI-Minimal-1810-sda.raw.xzというラズパイ向けのminimalイメージをダウンロードした。 

解凍にはxzコマンドが必要。brewでインストールする。

$ brew install xz

解凍する。

$ xz -d CentOS-Userland-7-armv7hl-RaspberryPI-Minimal-1810-sda.raw.xz 
$ ls
CentOS-Userland-7-armv7hl-RaspberryPI-Minimal-1810-sda.raw

続いて、このイメージファイルをddコマンドでMicroSDカードに書き込む。

USB Type-C ハブを使ってMacMicroSDカードを接続。

認識したディスクをdiskutilで調べると/dev/disk2で認識されている。

$ diskutil list
/dev/disk0 (internal):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                         500.3 GB   disk0
   1:                        EFI EFI                     314.6 MB   disk0s1
   2:          Apple_CoreStorage Macintosh HD            499.3 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3

/dev/disk1 (internal, virtual):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           +499.0 GB   disk1
                                 Logical Volume on disk0s2
                                 EF1D93DE-2460-4A71-B9DD-9C2223020689
                                 Unlocked Encrypted

/dev/disk2 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *31.5 GB    disk2
   1:             Windows_FAT_32                         31.5 GB    disk2s1

SDカードをUnMountしてからSDカードへ書き込む。1枚書き込むのにだいたい40分かかる。

$ diskutil unmountDisk /dev/disk2
Unmount of all volumes on disk2 was successful
$ sudo dd bs=1m if=./CentOS-Userland-7-armv7hl-RaspberryPI-Minimal-1810-sda.raw of=/dev/disk2
2712+0 records in
2712+0 records out
2843738112 bytes transferred in 2440.917174 secs (1165029 bytes/sec)

# ディスクの取り出し
$ diskutil eject /dev/disk2
Disk /dev/disk2 ejected

なお、購入したUSB Type-C ハブは接続中Wifiが使えなくなったので、調べ物をしたくなるたびにケーブルを抜く必要があった。

配線済みのラズパイクラスタにOSを書き込んだSDカードを差し込む。しかしディスプレイに何も表示されない。どうやら電源ケーブルを刺した状態で後からSDカードを差し込んでも認識しないらしい。一度USBを抜いてもう一度刺し直すと認識しだした。

しばらくするとログインプロンプトが出てくる。

初期アカウント: root

初期パスワード: centos

でログインする。

続いて以下を実行。

  • 各種パッケージをアップデート: yum update -y
  • 日本語配列を受け付けるように変更: localectl set-keymap jp106
  • 初期パスワードを変更: passwd
  • ホスト名の設定: hostname XXX

df -hを実行すると32GBのMicroSDを使っているにもかかわらずCentOSは2.7GB程度しか認識していない。

dffdisk -lで確認すると、/dev/mmcblk0MicroSDカード本体で、これが3つのパーティション/dev/mmcblk0p1,/dev/mmcblk0p2,/dev/mmcblk0p3で区切られており、この3つの合計が2.7GBほどだった。

ここで問題になったのは/がマウントしている/dev/mmcblk0p3には1.4GBしか割り当てられておらず、そのうち1.2GBがすでに使われてしまっていること。このままではKubernetesをインストールできない。

そんな時、/root/READMEに参考になる情報があると書かれているサイトを見つけた。

開いてみると以下の内容であった。

# cat /root/README
== CentOS 7 userland ==

If you want to automatically resize your / partition, just type the following (as root user):
rootfs-expand

どうやらパーティションを拡張するためのコマンドが用意されているらしい。

manを実行しても何もでなかったのでrootfs-expand --helpと適当に叩くとオプションが無視されそのまま実行されてしまい慌てたものの、実行が完了すると/が28GBに拡張された。

つづいて、Dockerをインストールする。

Dockerの公式ドキュメントでCentOS向けのインストールガイド通りに従いdocker-ce用のリポジトリを追加してyumでインストールしようとするとhttps://download.docker.com/linux/centos/7/armhfp/stable/repodata/repomd.xml: [Errno 14] HTTPS Error 404 - Not Foundが返ってくる。

armhfp向けのパッケージが存在しないようだ。

代わりに、ラズパイ公式サイトの手順に従い以下のコマンドでインストールする。

curl -sSL https://get.docker.com | sh

すると、今度はEither your platform is not easily detectable or is not supported by this installer scrypt.のエラーになった。

Docker CEのドキュメントを見ると、CentOS向けのDocker CEはARM64 / AARCH64をサポートしているがARM(32bit版)をサポートしていないとのこと。

どうやらOS選定の段階で詰んでしまった様子。

OSを32bitでも対応しているDebian系(Rassbianとか)に変えるか、CentOSにこだわるのであればラズパイで動かせる非公式の64bit版のOSイメージをいれるしかない(一応見つけることはできた)。

どうにかならないかと調べたところ、Raspberry Pi 3B系の 64bitモードに対応している OSの一覧まとめ 2018年版というサイトを見つけた。

これによると先ほどのサイトに書かれていなかったUbuntu 18.04も動かせるらしい。これなら32bit版でも64bit版でもdocker-ceをインストールできそうである。MicroSDカードの作り直しから実施して今度こそKubernetesのインストールを成功させる。

MicroSDをフォーマットする

OSを入れ直すためにラズパイから取り出したMicroSDMacにつないで初期化する。

以下のコマンドで初期化

$ diskutil eraseDisk FAT32 RPI /dev/disk2 
Started erase on disk2
Unmounting disk
Creating the partition map
Waiting for partitions to activate
Formatting disk2s2 as MS-DOS (FAT32) with name RPI
512 bytes per physical sector
/dev/rdisk2s2: 60996480 sectors in 1906140 FAT32 clusters (16384 bytes/cluster)
bps=512 spc=32 res=32 nft=2 mid=0xf8 spt=32 hds=255 hid=411648 drv=0x80 bsec=61026304 bspf=14892 rdcl=2 infs=1 bkbs=6
Mounting disk
Finished erase on disk2

初期化前と初期化後で比較してみる

# 初期化前
$ diskutil list
/dev/disk0 (internal):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                         500.3 GB   disk0
   1:                        EFI EFI                     314.6 MB   disk0s1
   2:          Apple_CoreStorage Macintosh HD            499.3 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3

/dev/disk1 (internal, virtual):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           +499.0 GB   disk1
                                 Logical Volume on disk0s2
                                 EF1D93DE-2460-4A71-B9DD-9C2223020689
                                 Unlocked Encrypted

/dev/disk2 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *31.5 GB    disk2
   1:                 DOS_FAT_32 NO NAME                 700.4 MB   disk2s1
   2:                 Linux_Swap                         511.7 MB   disk2s2
   3:                      Linux                         30.2 GB    disk2s3

#初期化後
$ diskutil list
/dev/disk0 (internal):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                         500.3 GB   disk0
   1:                        EFI EFI                     314.6 MB   disk0s1
   2:          Apple_CoreStorage Macintosh HD            499.3 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3

/dev/disk1 (internal, virtual):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           +499.0 GB   disk1
                                 Logical Volume on disk0s2
                                 EF1D93DE-2460-4A71-B9DD-9C2223020689
                                 Unlocked Encrypted

/dev/disk2 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *31.5 GB    disk2
   1:                        EFI EFI                     209.7 MB   disk2s1
   2:       Microsoft Basic Data RPI                     31.2 GB    disk2s2

ネットワークトラブル

無線親機を実家のネットワークに接続しようとしたら、「接続がタイムアウトしました」と出る。

使っている無線親機はBUFFEROのWMR-433W。調べるとファームウェアをアップデートするといいとのこと。参考

最新版のv1.5.2にアップデートしたものの結果は変わらず。

最終的には同じ機種を使っていた記事を見つけることができた。この記事を書いている間は実家に帰省していたこともあり、その場しのぎができればそれでよかった。そのため、この記事に習って実家にいるうちは有線でスイッチングハブとネットを接続することで解決することにした。

参考にしたサイト

ラズパイでKubernetesクラスタを構築する(物理編)

ラズベリーパイを触ったことがないので、後で見返すメモとして記録する。

この記事はラズパイケースにラズパイを設置する物理編である。

OSのインストールおよびKubernetesのインストールはラズパイクラスタにKubernetesのインストール(失敗編)とラズパイクラスタにKubernetesのインストール(成功編)の二つの記事に別途まとめる予定。

もともとこの記事を分割する予定はなかったものの、OSの選定が理由で作業が詰んでしまい大きく手戻りをすることになった。

その失敗をなかったことにするのはもったいないので、問題がなかった物理的な配線とOSインストール以降の失敗編と成功編の3つの記事に分けることにした。

用意したもの

年末に帰省したときに作業したので、実家にあるものはそのまま流用した。

ノートPCは除外するとして、最初から揃えるとするとだいたい3万円くらい?

必要なもの 用途
Raspberry Pi 3 Model B+ × 3 これがなくちゃ始まらない
4段積層式のラズパイケース ラズパイを縦に積み重ねるため。リンクのものはラズパイ用のヒートシンクが4セット入っているのでお得
MicroSD 32GB×3 ラズパイのディスクとして使う
USBキーボード ラズパイを操作するため。実家にあるだろうと思ったらなかったのでAmazonで注文
USBマウス ラズパイを操作するため。自宅のを借用
USB Type-C ハブ SDカードをMacBookProに認識させるため。自宅にも実家にもなかったのでAmazonで注文
USB充電器 ラズパイの電源
スイッチングハブ クラスタのSW
LANケーブル(15cm×1本, 30cm×3本) ラズパイ同士を接続するため
ラズパイ用のディスプレイ 実家のディスプレイを借用
HDMIケーブル ラズパイをディスプレイに接続するため。実家のPS4のケーブルを拝借
Micro USBケーブル 30cm×3本 ラズパイとUSB充電器を接続するため
無線親機 無線LANにつなぐため
両面テープ USB充電器などをケースに固定するため

全体の作業概要

  • 物理的な配線(本記事の範囲)
  • ラズパイにOSをインストール(ラズパイクラスタにKubernetesのインストール(失敗編/成功編))
    • OSイメージを焼いたMicroSDカードを用意
    • MicroSDを読み込ませてラズパイを起動
    • パスワード変更
    • パッケージの更新
    • /パーティションの拡張
    • ホスト名の設定
    • /etc/hostsの設定
  • Kubernetesのインストール(ラズパイクラスタにKubernetesのインストール(成功編))
    • Dockerをインストール
    • Kubernetesのインストール

作り方

まずはケースを開封するとケース本体と工具箱が出てくる。工具箱にはドライバ、ピンセット、ヒートシンクなど必要なものが揃っている。このピンセットはヒートシンクの取り付けやナットを締めるのに便利だった。

f:id:kuromt:20181229132008j:plain
ラズパイとケース

f:id:kuromt:20181229132116j:plain
ラズパイケースの工具箱に入っていたもの

まず、工具箱に入っているヒートシンクをラズパイの表に二箇所、裏に一箇所つける。

f:id:kuromt:20181229132402j:plain
表側

f:id:kuromt:20181229132425j:plain
裏側

つぎに、ケースのアクリル板についている保護シートをはずす。保護シートとアクリル板の間には隙間がほとんどなく、剥がすのに苦労する。そんな時はアクリル板の四隅にあるネジ穴の内側から外側に向かって工具箱のドライバで強めに引っ掻いてやると、保護シートがめくれ上がって剥がしやすくなる。ここがドライバの唯一の出番だった。

f:id:kuromt:20181229132838j:plain
引っ掻いたおかげで剥がしやすくなった

保護シートを外したら説明書に従ってラズパイを取り付けていく。先ほど書いたように、ナットは付属のピンセットを使ったり爪で締めた。

f:id:kuromt:20181229133023j:plain
1段目

f:id:kuromt:20181229133041j:plain
3段目

4段目にはUSB充電器を設置する。両面テープを使って固定する。

f:id:kuromt:20181229133226j:plain
USB充電器

f:id:kuromt:20181229133727j:plain
USB充電器の向き

最後に天板を取り付ける。ラズベリーがおしゃれ。

f:id:kuromt:20181229133109j:plain
4段目

ただし、すぐに天板の上にスイッチハブと無線親機を乗せることになるのこのラズベリーとはお別れである。

f:id:kuromt:20181229133517j:plain
スイッチハブと無線親機

スイッチハブと無線親機を15cmのLANケーブルで接続する。

f:id:kuromt:20181229133609j:plain
LANを配線後

ラズパイ本体にもLANを挿してスイッチングハブと接続する。

f:id:kuromt:20181229134247j:plain
ラズパイのUSBポートは手前、最上段のスイッチハブのUSBポートは左についている

こうして全ての配線が済んだ。 本当はUSB充電器と同じようにスイッチングハブと無線親機も両面テープで固定したいが、年明けに実家から帰宅する途中に剥がれてしまいそうなので帰宅するまで我慢する。

f:id:kuromt:20181229134418j:plain
接続後の状態

あとはUSB充電器とスイッチングハブを電源に接続して完了。

物理的な配線を終えたので、本来であれば無線親機を設定する。

しかし、手元のWMR-433WがPPPoEに対応していなかったため実家の環境では無線の設定ができないことが分かったため、スイッチングハブを有線で接続した。 参考

参考にしたサイト