config gemを使って環境変数も読み込む

config gemには、ENVオブジェクトから値を読み込むためのuse_envオプションがあります。

しかし単にuse_envをtrueにしても、ちゃんと環境変数が読み込まれません(env_separatorがデフォルトだと.になっているため)

なので以下のようにenv_separator_を指定します。

Config.setup do |config|
  config.use_env = true
  config.env_prefix = 'SETTINGS'
  config.env_separator = '_'
end

こうすれば、Rails起動前に例えば、SETTINGS_MYKEY=my_value環境変数を設定すれば、 Settings.mykey”my_value"が取得できるようになります。

また、SETTINGS_MY_KEY=my_value環境変数を設定すると、Settings.my.keyのようにネストされて設定されます。

※ちなみにconfig.env_prefixを設定しないと、Settings_MYKEY=my_valueと設定しないといけなくなる…。あとenv_prefixをつけたくないなと思ってconfig.env_prefix = ''を設定してもちゃんと動かなかった。

GAS(Google Apps Script)で秒まで指定して実行する

GAS(Google Apps Script)のトリガータイプに特定の時刻を指定しても分までしか指定できません。 また仮に12:00に指定しても12:00から12:01の間のどこかで処理が実行されるので、ちょうどこの秒に処理を実行したいというのが通常はできません。

しかしUtilities.sleepという関数を使うと指定した秒に処理を実行させることができます。

例えば、12:00:00に処理を実行したい場合、流れとしては、

  1. トリガーの"日付ベースのタイマー"を使って10時〜11時の間に 関数A を実行させるように設定
  2. 関数Aは11:59にメインの関数Bを実行するようにトリガーを設定
  3. 関数Bは12:00:00から現在の日時を引いて、12:00:00になるまでUtilities.sleepを使って処理を止める
  4. そしてメインの処理を実行させる

となります。 コードは以下のような感じです。

function main() {
  deleteTrigger();

  waitUntilTime();

  // メインの処理を実行

}

function waitUntilTime() {
  var notificationTime = new Date();
  notificationTime.setHours(12);
  notificationTime.setMinutes(0);
  notificationTime.setSeconds(0);
  Utilities.sleep((notificationTime - new Date()));
}

// "日付ベースのタイマー"を使って10時から11時の間にこのトリガーを実行するように設定する
function setTrigger() {
  var triggerDay = new Date();
  triggerDay.setHours(11);
  triggerDay.setMinutes(59);
  ScriptApp.newTrigger("main").timeBased().at(triggerDay).create();
}

function deleteTrigger() {
  var triggers = ScriptApp.getProjectTriggers();
  for(var i=0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == "main") {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}

AWS Elastic Beanstalkのデプロイメントポリシーについて

AWS Elastic Beanstalkのデプロイメントポリシー(デプロイ方法の種類)はここに書かれていますが、機械翻訳なのか品質が酷く大変分かりにくいので、メモしておきます。

バッチとは

デプロイメントポリシーを説明する前に、バッチについて説明しておく。 Elastic Beanstalkの設定画面やドキュメントにバッチという単語がでてきて最初は分かりづらいと思う。(ここでのバッチはバッチサーバーのバッチではなく、1束/1郡/1まとまり といった意味)

まずElastic Beanstalkはデプロイメントポリシーにローリング(Rolling)もしく追加バッチとローリング(Rolling with additional batch)を選択すると、以下のバッチサイズというのが設定できる。 f:id:shepherdMaster:20190128225509p:plain ここで指定した割合を現在のインスタンス数をかけたものがバッチになる。たとえば10インスタンスあって、バッチサイズ30%だとしたら、バッチ=3インスタンスになる。割り切れない場合は切り捨てになるのでたとえば10インスタンスあってバッチサイズ25%だとしたら、バッチは2インスタンスになる。

デプロイ種類

1回にすべて(All at once)

  • デプロイの流れ
  • 注意点
    • それぞれのインスタンスはデプロイが実行される間、短時間だがサービス停止状態になる
    • 古いアプリケーションと新しいアプリケーションが混在する時間が存在する

ローリング(Rolling)

  • デプロイの流れ
    • まずバッチサイズ分のインスタンスがLBからデタッチされ、デプロイをする
    • ヘルスチェックが通れば、そのバッチがLBに再アタッチされる。
    • 全てのインスタンスにデプロイがされるまで LBからデタッチ⇒デプロイ⇒ヘルスチェックOKならLBに再アタッチ を繰り返す
  • 注意点
    • デタッチされている間はリクエストを受け付けるインスタンス数はバッチサイズ分減る。
    • 古いアプリケーションと新しいアプリケーションが混在する時間が存在する

追加バッチとローリング(Rolling with additional batch)

ローリング(Rolling)と違って最初にバッチサイズ 分のインスタンスを追加するので、リクエストを受け付けるインスタンスの数は減らない。そのため本番環境では一番このデプロイメントポリシーが選ばれると思う。

  • デプロイの流れ
    • バッチサイズ 分のインスタンスを追加作成&そのインスタンスにデプロイ実行
    • バッチサイズ 分のインスタンスがLBからデタッチされ、デプロイが実行される。
    • デプロイが終わりヘルスチェックが通れば、次のバッチサイズ分のインスタンスが同様にLBからデタッチされデプロイされ..を繰り返す
    • 最後に追加作成分のインスタンスをshutdownする。(起動時刻が古いインスタンスからshutdownされている気がする)
  • 注意点
    • 古いアプリケーションと新しいアプリケーションが混在する時間が存在する

変更不可(Immutable)

  • デプロイの流れ
    • まず1台インスタンスを立ち上げ、デプロイをする。
    • ヘルスチェックが通れば 現在のインスタンス数-1 分新たにインスタンスを立ち上げ、デプロイする
    • 全部のヘルスチェックが通れば、古いのをshutdownしていく
  • 注意点
    • 古いアプリケーションと新しいアプリケーションが混在する時間が存在する

デプロイ時間

デプロイ種類ごとにデプロイ時間が変わってきます。 ここをを見てもらえば分かりますが、以下のような順で 1回にすべて(All at once)が一番デプロイが速いです。

1回にすべて(All at once) < ローリング(Rolling) < 追加バッチとローリング(Rolling with additional batch) < 変更不可(Immutable)

古いアプリケーションと新しいアプリケーションが混在する時間をなくしたい

結局どのデプロイメントポリシーを選んでも古いアプリケーションと新しいアプリケーションが混在する時間は存在する。 もし古いアプリケーションと新しいアプリケーションが混在する時間をなくしたいなら、ここに書いてある、Blue-Green Deploymentを行う必要がある。

最近買ってとても良かったもの

最近買ってとても良かったもの紹介します。
特に人感センサー付きライトはみんな買って損がないと思います。

人感センサー付き ライトソケット

人感センサー付き照明は本当に便利で、ライトに近づくだけでスイッチがオンになり、しばらくするとオフになるので、わざわざスイッチをオンオフする必要がなくなります。超楽。自分は玄関と洗面所に設置してます。
で、人感センサー付き照明はこのような感じで電球一体型がメインです。 (https://amzn.to/2PRBMar は安いですが、パナソニックのやつとかは3000円近くします)
上記の商品は電球一体型ではないので安いし、既存の電球が使えるし、電球が切れたら電球だけ買い直せばOKです。あと照明がオフになるまでの時間を調節できるのがとても良いです。これで1000円なんだから破格だ。
ただこの商品は真下はセンサーの検知外なので真下も検知してほしい場合は、こっちのような電球一体型を買ったほうがいいです。
また自分の電球はE17型だったので以下のような変換ソケットをかましました。

スマートリモコン Nature Remo mini

家に帰ったら部屋がエアコンが起動していて涼しい/暖かい状態にしたかったのと、寝る時ベッドの上で照明をオフにしたかったので、買いました。最高。
IFTTTの連携も楽で、Androidだと家電やシーンの操作用ボタンをホーム画面に設置出来て便利。とりあえずホーム画面に照明オフボタンを設置。
daikinのエアコンだったので対応しているか不安だったが問題なかった。ただ照明は入居したときにリモコンがついてなかったので、このアプリスマホにインストールして照明のリモコンの赤外線を出せるようにしてNatureRemoに家電認識させた。
NatureRemoは家電の製品名で家電登録できるようにしてほしい。。

モデム収納ボックス

モデム周りって散乱しがちだと思いますが、この収納ボックスはモデムや電源タップを入れることができて、部屋がスッキリします。
自分はwifiルータも入れてます。
またボックスの上にモバイルWi-Fiルータスマホを置いて充電するのにちょうどいいです。

バスサンダル

このバスサンダルはタオルハンガーや棒状のものに引っ掛けられて便利。

折りたたみコンテナ

いくつかこのコンテナを買って部屋に収納しきれないものは全部突っ込んでおいて積み重ねている。折り畳めるし、耐久性もある。

+d ニンジャピン

+d ニンジャピン 15ヶ入り クリアー D-331-CL

+d ニンジャピン 15ヶ入り クリアー D-331-CL

壁にさしてもピン跡が目立たない画びょう。

Kubernetes勉強会第6回 〜Kubernetesの実行と管理、CustomResourceDefinitions(CRD)、Container Runtime Interface(CRI)〜

Kubernetes in Action

Kubernetes in Action

「Kubernetes in Action」を読んで学んだ結果を社内で共有しました。 その第6回の内容です。

Kubernetesの実行と管理をしやすくするには

コンテナのイメージを管理しやすくする

imageのサイズは小さくし、不要なファイルやツールは含めないように。

イメージに適切にタグをうち、imagePullPolicyを賢く使う

docker imageのtagにはlatestが使えるが、podのyamlファイルにはlatestを指定しないようにしよう。 latestを指定しているともしimageを更新しても、新しくscheduleするpodにはその新しいimageが使用されるが、既存のpodのコンテナのimageは古いバージョンのまま。 またlatestタグを使うと1つ前のバージョンへrollbackができない。

もしあなたがimageのタグにmutable tag(同じタグを使い続けること)を使うならpodのspecのimagePullPolicyはAlwaysにしなければならない。 しかしそれはproductionでは大きな落とし穴になる。 もしAlwaysを設定したら、新しいpodがscheduleするたびにcontainer runtimeは毎回container registryに問い合わせることになる。 これはpodが起動するのが少し遅くなる。なぜならnodeは毎回imageが更新されているかチェックする必要がでてくるため。またcontainer registryが応答がない場合、podが起動するのを妨げてしまう。

複数の情報をLabelに含める

podだけではなく他のリソースにもlabelをつけることを忘れないようにしよう。 各リソースに複数のlabelをつけるようにすれば、リソースの取得が簡単になる。 labelには例えば以下のものがよいだろう。

  • リソースが属しているアプリケーション(もしくはマイクロサービス)の名前
  • Enviroment(development, QA, staging, production 等)
  • バージョン
  • リリースのタイプ(stable, canary, green or blue for green/blue deployments等)
  • テナント(あなたがもしpodをnamespaceを使用する代わりに各テナントで動かしている場合)
  • シャードシステムならそのシャード

annotationを通して各リソースを表現する

他の追加情報はannotationに含めましょう。 たとえばそのアプリケーションの責任を持っている人の情報とか。 もしマイクロサービスであれば、そのpodが使用している他のサービスの名前とか。 バージョン情報とか、ツールやGUIツールが使用するmetadataとか。

なぜプロセスが終了したのかの情報を提供する

Kubernetesにはなぜコンテナが終了したのかの情報を含めることができます。 コンテナの /dev/termination-log に終了した理由を書き込んでおけば、 kubectl describe podしたときにContainersのLast StateのMessageに表示されます。

アプリケーションログをハンドリングする

アプリケーションログは標準に出すべき。 そうすればkubectl logsで見れる。 一つ前のpodのlogはkubectl logs --previousでみれる。

特定のログファイルにはいていたら、 $ kubectl exec <pod> cat <logfile> でみれる。 または、pod内にコンテナが1つなら $ kubectl cp foo-pod:/var/log/foo.log foo.log でログをローカルへコピーすることができる。 複数のコンテナが実行されていたら -c containerName オプションでコンテナを指定しよう。

Kubernetesはログ集約システムは提供していない。 しかしそれをログ集約システムをデプロイするのは簡単。 Google Kubernetes EngineならEnable Stackdriver Logging checkbox にチェックを入れればいい。

EFK(ElacsticSeach, Fluentd, kibana)のようなスタックをデプロイすれば使うこともできる。 fluentdのようなログ収集ミドルウェアは複数行ログ(たとえばJavaスタックトレースのような)は1行1行別のログになってしまうので、jsonとして出力するのがよい。 ただjsonログは人間には見にくいので(kubectl logsした場合)、 標準出力にはplain textにして、特定のログファイルにはjsonで出力するのがよい。

カスタムリソースを作成する

独自のカスタムリソースを定義し、Kubernetesを拡張することができる

CustomResourceDefinitions

新しいリソースを作成するためには、CustomResourceDefinition objectを定義する必要がある。 CustomResourceDefinitionを定義すれば、他のリソースと同様に、yamljsonを通してそのinstanceを作成することができるようになる。 以下が定義例。以下ではWebsiteという名前のリソースを新たに定義している。

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: websites.extensions.example.com
spec:
  scope: Namespaced
group: extensions.example.com
version: v1
names:
  kind: Website
  singular: website
  plural: websites

このリソースのinstanceを作成する場合は以下。

apiVersion: extensions.example.com/v1
kind: Website
metadata:
  name: kubia
spec:
  gitRepo: https://github.com/luksa/kubia-website-example.git

そして新しく定義したこのリソースに対するcontrollerを実装する必要がある。

実装例 https://github.com/luksa/k8s-website-controller

controllerもpodとして動かすことができる。

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: website-controller
spec:
  replicas: 1
  template:
metadata:
  name: website-controller
  labels:
    app: website-controller
spec:
  serviceAccountName: website-controller
  containers:
  - name: main
    image: luksa/website-controller
  - name: proxy
    image: luksa/kubectl-proxy:1.6.2

custom objectのバリデートはKubernetes1.8でアルファ機能として導入された。 API serverで バリデートするためにはCustomResourceValidation機能を有効にする必要がある。

custom objectのためのcustom API serverを提供する

Kubernetesでcustom objectのサポートを提供する良い方法は、自分のAPI serverを実装し、clientがそのAPI serverとやり取りさせること。 Kubernetes 1.7から、KubernetesのデフォルトのmainのAPI Serverと自分で作成したAPI serverを統合することができるようになった。 複数のAPI Serverがあっても1つのAPIServerとして扱うことができる。

custom api serverを登録するには、custom api serverをpodとしてdeployしserverを通して公開すればよい。

Service Catalogを使ってKubernetesを拡張する

Service Catalogとは名前の通りserviceのカタログで、Service Catalog機能を使えば、カタログの中からサービス(アプリケーション)を選んで、pod,service,configmap等を直接扱うこと無く簡単にデプロイすることができるようになる。

Service Catalogは以下のリソースを提供している。

  • ClusterServiceBroker
    • サービスを提供する(外部)システムについて定義したもの
  • ClusterServiceClass
    • 提供されるサービスについて定義したもの
  • ServiceInstance
    • 提供されたサービスのinstance
  • ServiceBinding
    • はServiceInstanceとclients(pods)のセットを結びつけるもの

Kubernetesは、提供されるサービスのリストをbrokerへ問い合わせ、ClusterServiceClassリソースを作成する ユーザが提供されるサービスが必要な場合、KubernetesはServiceInstanceを作成し、ServiceInstanceをpodsに結びつける(bindingする) それらのpodsに必要な認証情報や接続に必要な情報をsecretを通して渡す。

Service Catalog API server と Controller Manager とは

Service Catalogは以下の3つのコンポーネントからなる分散システムである。

  • Service Catalog API Server
  • etcd as the storage
  • Controller Manager, where all the controllers run

Service Brokers と the OpenServiceBroker API とは

クラスタの管理者は1つ以上の外部ServiceBrokerをService Catalogに登録することができる。 各brokerはOpenServiceBroker APIを実装する必要がある。

Service Catalogがサービスの一覧を取得したあと、Service Catalog は各ClusterServiceClassリソースを生成する。各ClusterServiceClassリソースは、サービスの1つのタイプが定義されている(例えばあるClusterServiceClassは“PostgreSQL database”)。 各ClusterServiceClassは、1つ以上のservice planを持っている。ユーザは必要とするそのサービスレベルを選択することができる。たとえばdatabase ClusterServiceClassはHDDのfreeプランや、SSDのPremiumプランを提供したりするだろう。

Serviceの提供と使用

実際にserviceが提供されるためには、service instanceを作成する必要がある。 例:

apiVersion: servicecatalog.k8s.io/v1alpha1
kind: ServiceInstance
metadata:
  name: my-postgres-db
spec:
  clusterServiceClassName: postgres-database
  clusterServicePlanName: free
  parameters:
    init-db-args: --data-checksums

重要なのは、このservice(これだとpostgres-database)は、Kubernetesクラスタ外に作成されてもいいということ。なので例えばクラウド上のDBでもよい。 serviceへの接続情報はServiceBindingリソースを使ってsecretを通して与えられる。

例:

apiVersion: servicecatalog.k8s.io/v1alpha1
kind: ServiceBinding
metadata:
  name: my-postgres-db-binding
spec:
  instanceRef:
    name: my-postgres-db
  secretName: postgres-secret #ここに好きなsecret名を指定

将来的にはPodPresetsというKubernetesの機能が導入される予定。

Service CatalogはServiceBindingで指定した名前で新しいSecretを生成し、必要な情報をsecret内に保存する。 あとはpodからそのsecretをmountして使えば、podが接続情報を得ることができる。

Summary

  • CustomResourceDefinition objectを作成することで、Custom resourcesをAPI Serverに登録することができる
  • API Serverのコードを変更すること無く、CustomObjectのインスタンスを保存、取得、更新、削除することができる
  • custom objectsを実際にKubernetes上で動くようにCustom Controllerを実装することができる
  • API aggregation機能を通し、Custom API Serverを使うことでKubernetesを拡張することができる
  • Kubernetes上に構築されたPasSはコンテナを使ったアプリケーションを構築するのが楽になる
  • パッケージマネージャーのhelmは既存のアプリケーションをresource manifestsを必要とせずにデプロイすることができる

複数クラスターをkubectlで扱う

以下のように環境変数KUBECONFIGにコロン区切りでクラスタファイルを指定すると複数のファイルを読み込むことができ、つまり複数クラスタを扱うことができる。

KUBECONFIG=~/.kube/config1:~/.kube/config2

kubeconfigの構成を理解する

apiVersion: v1
clusters:
- cluster:
    certificate-authority: /home/luksa/.minikube/ca.crt
    server: https://192.168.99.100:8443
  name: minikube
contexts:
- context:
    cluster: minikube
    user: minikube
    namespace: default
  name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
  user:
    client-certificate: /home/luksa/.minikube/apiserver.crt
    client-key: /home/luksa/.minikube/apiserver.key

上記のようにKubeconfig fileは以下で構成されている。

  • clustersのリスト
  • contextsのリスト
  • current contextの名前
  • usersのリスト

それぞれの詳細

cluster

  • Kubernetes clusterを表している。API ServerのURL、certificate authority(CA)ファイル、他には必須ではないがAPI serverと通信するためのいくつかのオプション。 CA certificateは分割して保存できそれをkubeconfigファイル内から指定できる。また直接certificate-authority-dataフィールドに含めることも出来る。

user

  • どのuserもAPI Serverと通信する時に使うcredentialsを定義する。credentialsには以下が使える
    • usernameとpassowrd
    • authentication token
    • client key and certificate
  • client key and certificateはkubeconfig fileに含めることができる。もしくはファイルを分割してconfig file内で指定することができる。以下のように
users:
- name: minikube
  user:
    client-certificate: /home/luksa/.minikube/apiserver.crt
    client-key: /home/luksa/.minikube/apiserver.key

context

  • contextはclusterとuserを紐付けるもの。またkubectlで使われるデフォルトのnamespaceを指定できる。複数のcontextで、同じuserや同じclusterを指定することができる。

他のcontainer runtimesを使用する

Dockerの代わりにrktを使う

container runtimeとしてdockerのほかにrktがある。 rktはデフォルトでPod(複数の関連するコンテナを実行する)の概念をサポートしている。 rktはDocker-formatted container imageをそのまま実行することができるのでリパッケージは必要がない。 Kubernetesでrktを使うためにはkubeletに--container-runtime=rktオプションを渡してあげればよい。 なおkubeletはContainer Runtimeとやりとりする唯一のコンポーネント

他のcontainer runtimesをCRIを通して使う

Kubernetes 1.5 から Container Runtime Interface(CRI) が導入された。 CRIはplugin APIで他のcontainer runtimeを統一的に操作することができる。 kubeletはDockerやrktをCRIを通して操作する。

新しいCRI実装として、CRI-Oというものもでてきた。 参考: kubernetes用コンテナランタイムcri-oを試す

CRI-Oの開発の背景には、dockerの開発速度が早い中、それに依存するkubernetesが不安定になっており、より小さく単純な、専用のランタイムを用意することでkubernetesの安定化に繋げたいという意図があったようです。

また、Kubernetesをコンテナの代わりにVM上でアプリケーションを動かせる新しいCRI実装が開発中。 そのうちの一つはFrakti(The hypervisor-based container runtime for Kubernetes.)というもの。

ほかには Mirantis VirtletというCRI実装があり、これは、docker imageの代わりに実際にVM imagesを動かすというもの。

Cluster Federation

Kubernetesは複数のKubernetesを1つのKubernetesとして扱うための Cluster Federationという機能がある。 これにより、Kubernetes Clusterを複数のdata center、複数のアベイラビリティゾーン、もしくは複数のクラウド冗長化することができる。

※ただ、Federation(の最初のversion)はあまりいけていないらしく、SIG-Multiclusterで仕切り直し中とのこと。

Kubernetes勉強会第5回 〜node affinity, pod affinity、Init containers、lifecycle hooks〜

Kubernetes in Action

Kubernetes in Action

「Kubernetes in Action」を読んで学んだ結果を社内で共有しました。 その第5回の内容です。

node affinity, pod affinity

Kubernetesのnode affinity, pod affinityについて

Summry

  • もしnodeにtaintsをセットすると、podがそのtaintsを許容できるtolerationsをセットしていない限りそのnodeにそのpodはscheduleされない。
  • taintsは3タイプあり、NoScheduleは完璧にscheduleされない。 PreferNoScheduleはそこまで厳密ではない。NoExecuteはnode上の実行中のpodであっても追い出す。
  • NoExecute taintは、nodeがunreachableやunready状態になった場合podがどのくらいの時間schedulingされるのを待つか指定できる。
  • Node affinityはどのnodeにpodがscheduleされるべきか指定できる。
  • Pod affinityは同じaffinity ruleをもったpodが実行されているnodeにpodをscheduleすることができる
  • Pod anti-affinity はお互いのpodを避けさせることができる

Init containers

Init containersを使うと、Pod内のコンテナを起動する前に特定の順番で特定の処理を行うことができる。

https://qiita.com/tkusumi/items/f64fff37a724b86d9819#init-containers-alpha

Pod内で初期化処理をするコンテナを指定をできる機能です。Podの定義にinitContainersという項目で、従来のcontainersで指定したコンテナの前に起動するコンテナを指定できるようになるようです。複数のコンテナを指定することができますが、containersがパラレルに起動されるのに対してinitContainersは指定した順番に1つずつ起動していくようです。

使用用途としては例えば以下のようなものが挙げられます。

  • データを動的にダウンロードしてくる
  • データベーススキーマの反映
  • アプリケーションが何らかの事前条件を満たすまでウェイトする

Init containersを使えば事前条件が満たされるまでpodのmainコンテナが起動するのを遅らせることができます。 readiness probesを設定するのを忘れてはいけません。 アプリケーションがまだ準備段階ではない場合にserviceにpodが追加されるのを防ぐことができますし、 Deployment controllerが、bad versionをrolloutするのを防ぐためにも使用されます。

もしInit Containerの実行がエラーになった場合、KubernetesはInit Containerの実行が成功するまでpodを再起動します。 しかしもしrestartPolicyNeverに設定していたなら再起動しません。

例:

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox
    command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
  - name: init-mydb
    image: busybox
    command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']

参考: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/

lifecycle hooks

podのlifecycleに以下の2つのhookを定義することができる。これらをlifecycle hooksと呼ぶ。

  • Post-start hooks
  • Pre-stop hooks

pod全体に設定するinit containersと違って、lifecycle hooksはpodのコンテナごとに設定できる。 lifecycle hooksはcontainerがstartするときとstopするときに実行される。

lifecycle hooksは、以下の点でlivenes、 readiness probesと似ている。

  • containerの内部で実行されるコマンド
  • URLに対してHTTP GET Requestとして実行される

Post-start hooks

Post-start hookはコンテナのmain processが実行された後すぐに実行される。 もしコンテナのアプリケーションとは別のアプリケーションを動かしたい場合、あなたがそのアプリケーションの開発者ならアプリケーション内で別のアプリケーションを動かすことができるが、アプリケーションの開発者ではない場合、post-start hookは役に立つ。 Post-start hookはmain processと平行して実行されるが、以下の点でcontainerに影響を与える まずPost-start hookの実行が完了するまで、containerのstateWaitingのままでありreasonContainerCreatingのまま。そのためpodのstatusはRunningではなくPendingのまま。 もしPost-start hookが実行に失敗したりnon-zero exit statusを返した場合、main containerはkillされる。

以下が定義例。

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-poststart-hook
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    lifecycle:
      postStart:
        exec
          command:
          - sh
          - -c
          - "echo 'hook will fail with exit code 15'; sleep 5; exit 15"

hookによって開始されたprocessが標準出力に出力してもそれをみるすべはない。そのため永続的volumeに書き込むこと。

Pre-stop hooks

Pre-stop hooksはコンテナが終了する前にすぐに呼び出される。つまりコンテナが終了するのをブロッキングする。 なのでコンテナを削除する呼び出しが送られる前にPre-stop hooksは終了しなければならない。 またPre-stop hooksには何のパラメーターも渡されない。 Podの終了時の振る舞いの詳細はTermination of Podsを参照すること。

Pre-stop hooksの実行タイミング等は以下の記事がとても詳しいです。 Kubernetes: 詳解 Pods の終了

以下が定義例。コンテナが終了する前に http://POD_IP:8080/shutdown へリクエストが送信される。

apiVersion: v1
kind: Pod
metadata:
  name: pre-stop-hook-httpget-pod
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    lifecycle:
       preStop:
         httpGet:
            port: 8080
            path: shutdown

また以下のように定義すると、shellコマンドが実行される。

apiVersion: v1
kind: Pod
metadata:
  name: pre-stop-hook-shell-pod
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    lifecycle:
       preStop:
         exec:
           command: ["sh", "-c", "sleep 1; nginx -s quit; sleep 5"]

参考: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/

システムリニューアルで重要視していること

自分は今の会社に入社してから、PHPで書かれたシステムをRubyで書かれたシステムへリニューアルするプロジェクトをやっています。 PHPで書かれたシステムが122万行くらいあって、DBのテーブルも300以上あるのでそう簡単にはリニューアルはできません。 またただ書き直すだけでは意味がないので、アーキテクチャから見直したりDB設計を見直したりしてます。 で、やっと主要機能のリニューアルが終わってきて、だいたい全体の半分くらいリニューアルが終わりました。 リニューアルの仕方は画面or機能ごとにRubyに書き換えて移行をしています。なのでPHPのシステムと並行稼動しているわけです。 あとRubyへリニューアル作業をしているのは初期は数人でしたが、今では大半のエンジニアがPHPよりもRubyで書くようになりました。

ということで自分がPHPからRubyへのシステムリニューアルで重要視していることをまとめてみました。(正確には社内で発表する機会があったのでそれをもとにこの記事を書いている) 書いてあるのは当たり前のことなので主に自分への備忘録目的です。

アーキテクチャから見直す

  • アーキテクチャが適切でなければリニューアルしても不適切なシステムが出来上がるだけ
  • アーキテクチャにはDB設計も含まれる
  • DB設計が間違っていればアプリケーションは正しいものにはなりえない

技術的負債に対して明確な殺意をもつ

  • 技術的負債はもちろん返却していく
  • その際に絶対に保守的にならない
  • 保守的というのは、「ここを大きく変えると他も直さないといけなくて大変だからそっとしておこう」とか「この仕様がよくわからないけど消したら怖いから残しておこう」とか
  • 保守的になったらこのプロジェクトは終わり
  • 常にアグレッシブでいること

メンテナンス性

メンテナンス性はいくつかの要素があるが以下の2つを大切にしている。

  • 一貫性を大切に
    • 一貫性があるシステムはそれだけで生産性が
高い
    • 「一貫性のあるスタイルは”正しい”スタイルよりも大切」By リーダブルコード
    • 例えばエンジニアがそれぞれ考える”正しさ”を持ち寄ると、バラバラのプログラムが出来上がってしまう
  • シンプルに
    • もし何か判断に迷ったらシンプルな方にたおす

仕様の整理

  • 正しい仕様をもう一度考える
  • 複雑性のわりにユーザへの貢献度が低い機能や仕様を削る
  • 何のためにあるのかよく分からない仕様も思い切って捨てる
  • もちろんディレクターやCSの人と相談したり合意を取ることは必要

継続的な開発

  • リニューアルして終わりではなく継続的な開発ができるようにしていく
  • 継続的にリファクタリングしたり
  • 継続的に不要となった機能を消したり
  • 継続的にバージョンアップしたり
  • 継続的にモダンな技術を取り入れたり

おまけ: レガシーシステムを憎みすぎない

古いプログラムや技術的負債と向き合っていると、 ついつい憎んでしまいます。 しかし憎みすぎると既存の仕様や仕組みをガラッと変えるような判断をしがちです(坊主憎けりゃ袈裟まで憎い)。 レガシーシステムの機能や仕様の中にも不適切な部分と適切な部分があります。その適切な部分は活かすべきです。 慈悲の心を持って、きちんと何が不適切な部分なのか冷静&客観的になって見極めるようにしていきましょう。