1クール続けるブログ

とりあえず1クール続けるソフトウェアエンジニアの備忘録

Kubernetes完全ガイド読んだ感想・まとめ

青山さんのKubernetes完全ガイド読みました

インプレスさんより出版されているKubernetes完全ガイドを読みました。今まで行き当たりばったりでKubernetesを学んできた自分にとって、ものすごく学びが多かったですし、体系だった知識に整理できた一冊でした。図がたくさんあって理解もスムーズだったように思えます。 Kubernetesにこれから触る人や私みたいにその場しのぎで学んできた方は絶対に読むべき一冊だと思います。
※ 私はAmazon ECSとGKEを使い始めて半年くらいです

book.impress.co.jp

今回Kubernetes入門を見て改めて整理出来た項目を中心に挙げていきたいと思います。

kubectl

Contextの切り替え

kubectlで操作する際に、操作対象のクラスターや認証情報が必要になります。実態はデフォルトだと、$HOME/.kube/configに記載されるらしいです。EKSとGKEを使っている場合に、複数の異なる環境にアクセスする場合にはContextの登録・切り替えが必要になります。

# クラスタ名の登録
kubectl config --kubeconfig=config-demo set-cluster development --server=https://1.2.3.4 --certificate-authority=fake-ca-file

# 認証情報の登録
kubectl config --kubeconfig=config-demo set-credentials developer --client-certificate=fake-cert-file --client-key=fake-key-seefile

# コンテキスト名 / クラスタ名 / namespace / 認証情報
kubectl config --kubeconfig=config-demo set-context dev-frontend --cluster=development --namespace=frontend --user=developer
kubectl config --kubeconfig=config-demo set-context dev-storage --cluster=development --namespace=storage --user=developer
kubectl config --kubeconfig=config-demo set-context exp-scratch --cluster=scratch --namespace=default --user=experimenter

# 設定情報を見る
kubectl config --kubeconfig=config-demo view

# 切り替えコマンド
kubectl config --kubeconfig=config-demo use-context dev-frontend

Configure Access to Multiple Clusters - Kubernetes

kubectlで適用される差分

今まで意識してこなかったと同時に、なぜ意識できなかったのか後悔の多いところです。 さらに言えば、kubectl applyのマニフェストのマージ方法というところになります。 削除項目の差分適用は、新しくapplyされるマニフェストと、以前applyされたマニフェストとの比較で行われますが、追加・更新の項目の差分適用は以前applyされたマニフェストではなく、そこからKubernetesが変更を加えたものとの比較になります。つまり、kubectl get deploy xxxxx -o yamlした結果との比較です。 applyした結果、podのテンプレートと変更があれば、ローリングアップデートが起こりますね。以前適応したマニフェストにrollingUpdateについて書いてない状態から、rollingUpdateでmaxSurge25%、minUnAvailable25%ととしても、デフォルト値としてKubernetes側がセットした値とかわらないため、rollingUpdateは起こりません。

また、削除項目の差分適用が以前applyされたマニフェストとの比較ということで、単純にcreateされたマニフェストから、applyで更新すると削除できない項目が出てきてしまいます。createコマンドを使用するときはkubectl create --save-configを使用すること。

マニフェストファイルの設計

結合度の高いリソースである場合、例えばDeploymentとService、HPA、PDBは同じマニフェストファイルに書いても良いかもしれない。ConfigMapやSecretなどの疎結合のリソースに関してはマニフェストファイルを分けるべき。1つのファイルに書く場合には---で区切る。上から順に適用されていくため、書く順番は考慮されるべきである。
kubectl apply -f ./ -R再帰的にマニフェストを適応させることが出来るらしいので、ディレクトリ構造は極力わかりやすい形に持っていくことも可能。

その他

  • Stern(https://github.com/wercker/stern)導入で複数のpodをログを同時に見ることが可能
  • 環境変数KUBE_EDITORで使用するエディタを指定できる。
  • kubectl copyコマンド
  • kubectl port-forward deployment/xxxxxx 8080:3306でkubectlを実行したインスタンス(もしくはローカルマシン)のポート8080にアクセスすると対象Podの3306番のpodに接続される。
  • デバッグ-v=6-v=8を使う。

Workloadsリソース

Podのデザインパターン

  • サイドカー:メインコンテナに機能を追加する。個人的にはkinesisアクセスログを流すflunetdコンテナとかがこれに当たると思いました。
  • アンバサダー:外部システムとのやり取りの代理を行う。HAProxyコンテナのイメージです。AWSでいうならRDSの死活監視を行ってトラフィックを流すようなもの。
  • アダプタ:外部からのアクセスのインターフェース。JavaアプリケーションとかだったらPrometheus Tomcat Exporterのようなイメージでしょうか。

Rolling Updateの処理

マニフェストをapply -> .spec.template以下の構造体のハッシュ値を計算 -> 同じハッシュ値のReplicaSetが既存していなければ作成 -> ローリングアップデート戦略にもとづいて、現在のReplicaSetのPod数を減らし新しいReplicaSetのPod数を増やしていく。ロールバックは、戻すリビジョンを指定しそれに対応するReplicaSetのPod数を増やし、現在のReplicaSetのPod数を減らしていく。kubectl rollout pause deployment sampleでローリングアップデートを止めることも可能。

Daemon Setのアップデート戦略

OndeleteRolling Updateがある。OndeleteはDaemosetのtemplateが変更されてもPodの更新は行われない。更新する場合には手動でPodをdeleteしオートヒーリングでPodを新たに作成する際に新しい定義となる。apiVersionがapps/v1はデフォルトがRolling Updateですが、それより前のバージョンでのデフォルト値はOndeleteなので注意が必要。
参考: Default apps/v1beta2 StatefulSet and DaemonSet strategy to RollingUpdate · Issue #49604 · kubernetes/kubernetes · GitHub

Stateful Setの特徴

業務では使用したこと無いワークロードであるところのStateful Setですが他のワークロードとの違いが顕著であるように見受けられます。以下に列挙していきます。

  • 作成されるPod名のSuffixは数字のインデックスが付与されたのものになる。
  • scaleすると、今あるpodのIndexに+1された名前のPodが作成される。デフォルトでは、Ready状態になってから次のPodを作成し始める。なので増えるときは1つずつ。ただ、podManagementPolicyをPararellにするとdeploymentと同じように複数同時に起動する。
  • Rolling UpdateはpodManagementPolicyにかかわらず1つずつ行われる。partitionを設定することで一部だけを更新することが可能
  • Podの再起動時にも同じPersistentDiskを使用する

その他

  • Podテンプレートのコンテナ実行時のコマンド指定
kubernetes Dockerfile
command ENTRYPOINT
args CMD
  • Pod全コンテナの/etc/hostsを書き換える機能があり、.spec.hostAliasに指定する
  • kubectl applyしたときに--recordオプション必須つけることでアノテーションにchange-causeが記録される

Discovery&LBリソース

Service

kind type description
ClusterIP ClusterIP クラスタ内のみから疎通性があるInternal Networkに作り出される仮想IP。spec.ports.portがClusterIPで待ち受けるIPアドレス。spec.ports.targetPortが転送先コンテナのPort番号。手動でIPを固定する場合には、spec.ClusterIPを指定する。
ExternalIP ClusterIP typeはClusterIP。特定のKubernetesノードで受信したトラフィックをコンテナに転送する。spec.ExternalIPsで受信するノードのIPを列挙する
NodePort NordPort ExternalIPと似ている。ただしこれは、全ノードからトラフィックを受け付けるイメージ。NodePortで利用できるポート範囲は30000~32767
LoadBalancer LoadBalancer Production環境でクラスタ外からトラフィックを受けるときに使用される。Kubernetesクラスタ外に疎通性のある仮想IPを払い出す。上の外部疎通ServiceはノードがSPOFになるが、これは外部のロードバランサを利用するためにノードの障害に強い。GCPAWSでは、spec.loadBalancerSourceRangesに接続を許可する送信元IPアドレス範囲を指定できる。
Headless Service ClusterIP DNSラウンドロビンを使ったエンドポイントの提供。type:ClusterIPかつspec.ClusterIPがNoneのもの。負荷分散に向かない。
ExternalName Service ExternalName クラスタ内からService名で名前解決するとCNAMEが返ってくる。外部サービスと疎結合にするために使用する。
None-Selector Service * 自分で指定したメンバに負荷分散することが出来る。EndPointとServiceを同じ名前で作ることで紐付けられる。typeは自由だがメリットを考えるとClusterIP一択。Selectorには何も指定しない

Ingress

Ingressリソースは一旦作成すると、その後あんまりにも触る機会が無く忘れてしまいがちです。
L7のロードバランシングを行うリソース。L4のServiceと区別するため別のリソースとして扱われている。大別してクラスタ外のロードバランサを利用するものとクラスタ内のIngress用Podをデプロイするものがある。Ingressは事前に作成されたServiceをバックエンドとして転送を行う仕組みになっています。なのでNodePortのリソースをまず用意します。その後に、IngressリソースのserviceNameの項目で結びつけます。L7なので当然パスベースルーティングもマニフェストで指定することが出来ます。https通信を行う場合には別途でSecretを作成する必要性がある。

その他

  • 正式なFQDNを指定せずに、Service名だけでも解決できるのは、/resolve/confsearch [namespace].svc.cluster.localという文言があるため。

Config & Storageリソース

Secret

1つのSecretの中に複数のKey-Value値。 大体は、マニフェストから作成することが考えられる。base64エンコードした値をマニフェストに埋め込みますが、その状態で管理してもセキュリティなど無いに等しいため、kubesec(https://github.com/shyiko/kubesec)と暗号鍵のマネージドサービスを使って暗号化した状態でリポジトリに保存しておくのが良きかと思う。

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

利用方法も複数ある。環境変数として渡す手段とVolumeとしてマウントする手段があります。Volumeとしてマウントした場合には動的なSecretの変更を行うことができます。また、それぞれ特定のKeyを渡す手段(spec.containers[].env[].valueFrom.secretKeyRef)とすべてのKeyを渡す手段(spec.containers[].envFrom.secretRef)がある。マニフェストに一覧性を持たせたい場合には前者、マニフェストを冗長にしたくない場合には後者を用いると良さげ。

kind type description
Generic Opaque マニフェストから作成するケースが多そう。DBなどの資格情報はこれで管理する。
TLS kubernetes.io/tls kubectl create secret tls custom-tls-cert --key /path/to/tls.key --cert /path/to/tls.crtで作成。keyが秘密鍵でcertが証明書ファイル。
Dockerレジストリ kubernetes.io/dockerconfigjson Registoryサーバと認証情報を引数で指定して作成する。podテンプレートのspec.containers[].imagePullSecretsで名前を指定する。
Service Account kubernetes.io/service-account-token 自動で作成されるもの。利用者が意識することはほとんどない

ConfigMap

ConfigMapも殆どの場合でマニフェストから作成されるケースが多そう。Secretと違いBase64エンコードしない点に注意。数値はダブルクォートで囲む。渡し方はSecretとほぼ変わらず、spec.containers[].env[].valueFrom.secretKeyRef -> spec.containers[].env[].valueFrom.configMapKeyRef / spec.containers[].envFrom.secretRef -> spec.containers[].envFrom.configMapRefとフィールドの名前が変わったくらい。

Persistent Volume Claim

ややこしい名前のリソースたちの違い

  • Volume
    • 予め用意された利用可能なボリューム
      • EmptyDir : ホスト上の領域を一時的に確保して利用できる。podがterminateされると削除される
      • hostPath : ホスト上の領域をコンテナにマッピングする。セキュリティに注意。
      • gcePersistentDisk : 同プロジェクト同ゾーンの複数のGCE VMから読み取り専用でマウントされる。大体は予めデータを置いている想定かと思われる。
  • Persistent Volume
    • 実はConfig & StorageリソースではなくClusterリソース
    • 予め作成しておく必要性がある
    • Dynamic Provisioningを使用しない場合にはラベルを付与すること
    • アクセスモードが複数あるが、大体はReadWriteOnceかReadOnlyManyが許可されていて、ReadWriteManyは許可されていない。
    • Reclaim PolicyでPersistentVolumeClaimが削除された際のPersistent Volumeの挙動を決められる。
  • PersistentVolumeClaim
    • その名の通りPersistent Volumeを要求するリソース。マニフェストに指定した条件にあうPVの割当をおこなう。
    • podテンプレートの定義のspec.volumes[].persistentVolumeClaim.ClaimNameにPVCの名前を指定する。
    • 予めPVを使用しなくても、動的にPVを作成して割り当てるのがDynamic Provisioning。StorageClassを作成してPVCのマニフェストで指定する必要あり。
    • StatefulSetはPVCの定義がマニフェストに含まれている

その他

  • SecretとConfigMap以外からPodテンプレートで環境変数を渡すには
    • 静的設定
env:
     - name: TZ
        value: Asia/Tokyo
  • podの情報を参照 。env[].valueFrom.fieldRef.fieldPathで指定する。
  • コンテナの情報を参照。env[].valueFrom.resourceFieldRef.containerNameでコンテナの名前、env[].valueFrom.resourceFieldRef.resouceで取得するフィールドを指定する。
  • SubPathマウントで特定のディレクトリをルートとしてマウントすることができる。mountPathがコンテナ側・subPathがボリューム側のディレクトリパスの指定となる。

リソース管理とオートスケーリング

requestsとlimit

requests/limitともに各コンテナに定義する。
この値が適正かどうか判断する際に役に立つのが、Nodeごとにリソース状況。これはkubectl describe node hogehogeで見ることが可能。
Requestsを大きくしすぎず、RequestsとLimitsの間に顕著な差を作らないことで適正なスケールアウトができるようになる。

  • requests
    • 使用するリソースの下限を指定するもの。
    • この値をもとにNodeにPodがスケジューリングされる。
  • Limits
    • 使用するリソースの上限を指定するもの。
    • この値を超えないようにPodの使用するリソース量がコントロールされる。
    • メモリの場合はLimitsを超えるとOOMでコンテナプロセスが殺される。

HPA(水平スケーリング)

30秒に1回の頻度でオートスケーリングすべきかのチェックが走る。必要なレプリカ数の計算は以下で計算される。
マニフェストの中でDeploymentを指定する。

desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]

スケールアウトの式 (最大で3分に1回)

avg(currentMetricValue) / desiredMetricValue > 1.1

スケールインの式(最大で5分に1回)

avg(currentMetricValue) / desiredMetricValue < 0.9

その他

  • LimitRangeでリソースの最小・最大値・デフォルト値を設定可能。新規でPodを作成するときに適用される。
  • OOM KillerによってPodを停止される場合には、QoS Class「Best Effort」「Burstable」「Guaranteed」の順で停止される。RequestsとLimitsが指定されていてなおかつ値が同じ場合には「Guaranteed」となり削除されにくくなる。逆にどちらも指定していない場合には、停止される対象になりやすい。
  • ResouceQuotaによってNameSpaceごとのリソース量・数に制限をかけることができる。

コンテナのライフサイクル

ヘルスチェック

Amazon ECSでもELBを使用したヘルスチェックがありますが、それよりもだいぶ柔軟です。ヘルスチェック方式は3種類あり、コマンドベースのチェック(exec)とHTTPベースのチェック(httpGet)、TCPベースのチェック(tcpSocket)がある。HTTPベースのチェックではホスト名やHeaderを設定できたりもする。
Amazon ECSにおける「healthCheckGracePeriodSeconds(ヘルスチェックまでの猶予期間)」にあたるのがinitialDelaySecondsとなります。

  • Liveness Probe
    • 失敗時にはPodを再起動する。メモリリークなどでPodの再起動なしには状態が回復しずらい場合に使用。
    • 成功と判断するまでのチェック回数であるSuccessThresholdは必ず1
  • Readiness Probe

コンテナライフサイクル

restartPolicyはPodに定義するものでPodが停止した場合にどのような挙動を取るか指定したものです。基本的には、終了コードにかかわらず起動するAlwaysなのではと思うのですが、Jobなどは終了コード0以外で再起動するOnFailureになるのではと思います。Neverは再起動を行わないものです。

Init Containers

初期化処理を行うためのコンテナ。InitContainersの処理が終わらないと、spec.containersで指定したコンテナが起動しない。GCSからファイルをGETしてくる必要性があるときなどに使用している。この機能がAmazon ECSにも欲しい…。

postStart/preStop

コンテナの起動・終了時に行われる処理。postStartは、コンテナのENTRYPOINTとほぼ同じタイミングで実行されるため、どうしてもコンテナ内部で処理をしなくてはいけない場合以外はinitContainersに処理をしてもらったほうが良さそう。
preStopの処理の流れは、この本を読む前にこの記事(Kubernetes: 詳解 Pods の終了 - Qiita)で知っていましたが、改めて別の図も交えてだとよりわかりやすいなと思う。spec.terminationGracePeriodSecondsをpreStop処理にかかる時間を考慮した時間にしないと、デフォルトの30秒がたった時点でSIGKILLコマンド、preStop処理の途中だった場合はSIGTERMコマンドが送られ、その2秒後にSIGKILLが送られる。nginxやapacheなどのWEBサーバでGraceful Shatdownを行う場合に、コマンドだと非同期での実行となる場合があるのでsleepコマンドを入れておくことが望ましい。

その他

  • 親リソース(ReplicaSet)が削除された場合には子リソースのPodを削除するという処理がはいる。この親子関係は、マニフェストmetadata.ownerReferenceから読み取ることが可能。ex) Deployment削除 -> ReplicaSet削除 -> pod削除
  • podを即座に削除したい場合
kubectl delete pod hogehoge --grace-period 0 --force

高度なスケジューリング

Node Affinity

Podが特定のノード上でしか実行できないような条件付ができる。

  • requiredDuringSchedulingIgnoredDuringExecution
    • 必須条件のポリシー
    • この条件を満たさないとスケジューリングされない
    • 検証環境でプリエンプティブインスタンスに起動されると困るものは、priemptive:falseのものを指定したりとか
  • preferredDuringSchedulingIgnoredDuringExecution
    • 優先条件のポリシー
    • この条件を満たさない場合でもスケジューリングされる
    • 優先度の重み(weight)を設定することができる

Node AntiAffinity

これは、matchExpressionで指定できるオペレータでNotがついているものを使えば実現できるもの。

Inter-Pod Affinity

基本的にはNode Affinityと同じだが、topologyKeyというのを指定する必要がある。topologyKeyはスケジューリング対象の範囲を示す。これはホストやゾーンなどを指定でき、matchExpressionのじょうけんを満たすPodのホストもしくはゾーンに配置するという意味になる。

TaintsとTolerations

Podが条件を提示してNode側が許可する形のスケジューリング。node affinityと違う点は他にもあり、考慮されるのがスケジューリングのときだけではないという点がある。

まとめ

本当にめっちゃめちゃ良い本だったので買いましょう。

GKEのマスターノードVerが上がったので変更点まとめる

2018年8月20日からGKEのマスターノードのVersionが1.8.xは1.9.7に、1.9.xは1.10.6に上がりました。
ノードの自動アップグレードonにしているクラスターは何もしなくても、ワーカーノードのVersionも上がってることかと思います。 ただ、バージョン上がると急に期待している動作と違う挙動が見られることもあります。 例えば、v1.9.7ではgcePersistentDiskのSubPathマウントがうまくいかず、Podが起動しないという事象が起こり得ます。

本番運用しているシステムでワーカーノードの自動アップグレードはリスクがあります。 そのため、マスターノードがバージョンアップしたタイミングで、新しいバージョンのノードプールを作成し、Podを新バージョンのノードプールで起動するのを確認していく方法を取るというプロジェクトが多いのではと思います。

バージョンアップに際し、変更点を認識することが必要です。ということで、ver.1.9.xになることでどのような変化があるのか調べてみました。 deploymentなどのワークロードがGAになったこともあり、デフォルト値の変更などが気になるところです。

以下を参考にいたしました。 気になったことがあれば順次拾っていきます。

www.mirantis.com

github.com

kubernetes.io

Workloads API

kindがDaemonSet, Deployment, ReplicaSet, StatefulSetのapiVersionがextensions / v1beta2からapps / v1へと変更になります。今後、apps / v1への開発は行われるが、その前のバージョンには行われないようなのでこの機会にマニフェストを変更しておきたいところです。apiVersionを新しくしたマニフェストkubectl applyした後に、kubectl get deployment sample -o yamlしても 、apiVersionはextensions / v1beta1と表示されることがあります。これは、登録したマニフェストがapiServer内部で互換性のあるバージョンに関しては変換されるからです。 そして、kubectl get deployment sample -o yaml --v=6と打つと、/apis/extensions/v1beta1/namespaces/…というリソースにアクセスしていることが分かる。
実は、kubectl get deployments.v1.appsと打つと、表示されるマニフェストのapiVersionはapps/v1となり、オプションで--v=6として再度叩くと、apis/apps/v1/namespaces…というリソースにアクセスしていることが分かる。

kubectl convertを使用して、グループバージョン間でマニフェストを変換できるとのことです。以下のコマンドで変換できます。 kubectl convert -f sample_deployment.yaml -o yaml --output-version='apps/v1'

デフォルト値として、今までセットされていたものが外れているケースもある。.spec.selectorだ。今までは、デフォルト値として.template.metadata.labelsに記載された値をセットしていた。apps/v1からは明確な記載となる。

API Machinery

Admission Control

Admission webhooksがbetaになった。Admission Controllerの拡張機能。 Admission Controllerは認証・認可の後、Objectが永続化する前にクライアントからの要求を受け入れるか判定する仕組み。2種類あり、mutatingはクライアントの要求書き換え、validatingはクライアントの要求を受け入れるかどうかを判断する。 mutatingはマニフェストに、決まったアノテーションを付けたり、別コンテナ(例えばEnvoy)を付与したりできる。validatingは、マニフェストの内容やクライアントによって受容するか決められる。

Admission Webhooksはkube-apiserverにCallback先としてHTTPサーバを登録しておくと、そこにリクエストが飛んで来て、それに対してレスポンスを返すことで、Admission Controlを成立させる機能。

GKEにおいても使えそうな感じがするのですが(Istioがこの仕組み使って自動でEnvoyを配置しているため)、方法がわかりませんでした。

参考:Learn more about Admission Webhooks - Speaker Deck

Custum Resource

Custom Resource Definition (CRD)というのがbetaになった。ユーザ独自で定義したリソースのこと。knativeもこの仕組みを使っているはず。 Controllerの作成に便利なツールも出てきている(kube-builderなど)

GitHub - kubernetes/sample-controller: Repository for sample controller. Complements sample-apiserver

KubernetesのCRD(Custom Resource Definition)とカスタムコントローラーの作成

Kubernetes勉強会第6回 〜Kubernetesの実行と管理、CustomResourceDefinitions(CRD)、Container Runtime Interface(CRI)〜 - 世界中の羊をかき集めて

Extending Kubernetes with Custom Resources and Operator Frameworks - Speaker Deck

Auth

RBAC

組み込み(デフォルト)のadmin/edit/viewロールが定義されていて、これをcluster role aggregationで使うことができる。以下のような形。以下に出てくるcronTabはカスタムリソースのため組み込みのRoleでは制御されていない。ClusterRoleなので、namespace関係なし。Roleであれば、namespaceで分割する。

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: aggregate-cron-tabs-edit
  labels:
    # Add these permissions to the "admin" and "edit" default roles.
    rbac.authorization.k8s.io/aggregate-to-admin: "true"
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
rules:
- apiGroups: ["stable.example.com"]
  resources: ["crontabs"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

CLI

kubectl

  • kubectl cp /tmp/foo <some-namespace>/<some-pod>:/tmp/bar -c <specific-container>でremoteのファイルをlocalにcopyできる
  • kubectl create pdbでデフォルト値をセットしなくなったらしい

Network

IPv6

IPv6対応(alpha)したとのこと。ただ、alphaの機能を使用するためには、 GKEの場合にはクラスター作成時に指定をすることが必要なので、面倒かもしれない。

CoreDNS

CoreDNSがリリース。kube-dnsの代わりに使用することもできる(1.11でデフォルトがCoreDNSだったような)

Other

  • –cleanup-ipvsフラグができてkube-dns起動時に既存のルールをフラッシュするか決められるように
  • ホストの/etc/resolve.confまたは-resolv-confに "options"を追加することでpodのresolve.confに反映させることが出来るようになった

Strorage

  • PersistentVolumePersistentVolumeClaimのサイズは0より大きくなくてはいけない。
  • GCEマルチクラスタでは、ノードを持たないゾーンでPersistentVolumeオブジェクトが動的にプロビジョニングされなくなります。

まとめ

1.9でBetaになった機能は拡張するためのものが多い。よりエンタープライズでも運用できるように改善されてきたイメージです。 ただ、使いどころを間違えると管理がかなり大変になりそうので気をつけなきゃですね。

記事書いてて思いましたが、まだまだ勉強が全然足らないですね…。

Goの並行処理解説ブログを参考に少しソースを書き換えてみて理解を深める

Goの並行処理について書かれたブログ

medium.com

このブログがイラスト付きで本当に可愛くGoの平行処理の基礎を教えてくれて良いんです。

このブログを参考に整理してみる

このブログはある一つの例を取って、並行処理に記述しています。それは鉱物の発見・掘り出し・加工です。 元のブログでは、それぞれの動作をするGopher君が描かれています(めちゃめちゃ愛らしいので是非見てください)。

シングルスレッド処理とマルチスレッド処理の違い

シングルスレッド

Gopher族Gary君が1人で鉱物の加工までをこなします。なんてマルチな才能なんだ。 ブログではシングルスレッドのソースコードは載ってなかったので愚直に書きました。

出力

From Finder:  [Ore Ore Ore]
From Miner:  [minedOre minedOre minedOre]
From Smelter:  [smeltedOre smeltedOre smeltedOre]
package main

import (
    "fmt"
    "strings"
)

func main() {
   // すべてGary君のお仕事
    theMine := [5]string{"Rock", "Ore", "Ore", "Rock", "Ore"}
    foundOre := finder(theMine)
    minedOre := miner(foundOre)
    smelter(minedOre)
}

func finder(mine [5]string) []string {
    foundOre := make([]string, 0)
    for _, m := range mine {
        if m == "Ore" {
            foundOre = append(foundOre, m)
        }
    }
    fmt.Println("From Finder: ", foundOre)
    return foundOre
}

func miner(foundOre []string) []string {
    minedOre := make([]string, 0)
    for _, fo := range foundOre {
        minedOre = append(minedOre, fmt.Sprintf("mined%s", fo))
    }
    fmt.Println("From Miner: ", minedOre)
    return minedOre
}

func smelter(minedOre []string) {
    smeltedOre := make([]string, 0)
    for _, mo := range minedOre {
        smeltedOre = append(smeltedOre, strings.Replace(mo, "mined", "smelted", -1))
    }
    fmt.Println("From Smelter: ", smeltedOre)
}

マルチスレッド

Gary君は鉱物の発見、Janeは掘り出し、Peterは加工を行うようにしました。 ブログのほうではそれぞれをgoroutineにして処理を行っていました。そのまま写経するのも面白くないのでselectcontextを使って書いてみました。

出力

From Finder:  ore
From Miner:  minedOre
From Smelter: Ore is smelted
From Finder:  ore
From Miner:  minedOre
From Smelter: Ore is smelted
From Finder:  ore
From Miner:  minedOre
From Smelter: Ore is smelted
func main() {
    theMine := [5]string{"rock", "ore", "ore", "rock", "ore"}
    oreChannel := make(chan string, 5)
    minedOreChan := make(chan string, 5)

    ctx := context.Background()
    ctx, cancel := context.WithTimeout(ctx, time.Second)
    defer cancel()

    go func(ctx context.Context) {
        for {
            select {
            // Janeの仕事
            case foundOre := <-oreChannel:
                fmt.Println("From Finder: ", foundOre)
                minedOreChan <- "minedOre"
            // Peterの仕事
            case minedOre := <-minedOreChan:
                fmt.Println("From Miner: ", minedOre)
                fmt.Println("From Smelter: Ore is smelted")
            }
        }
    }(ctx)

    // Gary君の仕事
    go func(mine [5]string) {
        for _, item := range mine {
            if item == "ore" {
                oreChannel <- item
                time.Sleep(10 * time.Millisecond)
            }
        }
    }(theMine)

    <-ctx.Done()
}

channel

channelはgoroutineが相互にやり取りするときに使用されます。上の例だと、finderとminerがやり取りするときとminerがsmelterとやり取りするときに使用しています。goroutineはchannelに対して送信と受信ができます。<-を使用して表現します。

oreChannel <- item  // 送信
foundOre := <-oreChannel  // 受信

このchannelという機構のおかげでJaneはGary君がmineをすべて見つけるのを待たずして、見つけた分から処理をしていくことができます。

channelはいろんな状況でgoroutineをブロックします。これによって同期的な処理を行うことも可能です。 バッファ付きチャネルとそうでないチャネルの2種類があります。チャネルに送信するときバッファがない場合は、ブロックされます。oreChannel := make(chan string, 5)宣言のときにcapを指定しないとbufferなしのchannelになるはずです。

context

goの1.6まではcontextパッケージが無く自前で実装していたそうな。contextパッケージは、キャンセルを伝搬できるのも強みとのこと。 やはりdeeeetさんは神

Go1.7のcontextパッケージ | SOTA

まとめ

selectchannelの組み合わせで楽に並行処理を実装できる。場合によっては、selectじゃなくてfor~rangeのほうが良いかもしれない。 タイムアウト系はcontextパッケージを利用する。

CurlでGCSにファイルをアップロードする

gsutilがどうしても使えないけどGCS使いたい

例えば、KubernetesのPostStartとかPrestopでGCSからダウンロード/アップロードするとか。
Dockerfile内でGCS使わなくてはいけなくなった。でもそれだけのためにRUNして後でお片づけするのも時間ちょっと増えるし嫌みたいな時とか。

curlでの処理

アクセストークンを取得

jqが使えれば、もっとちゃんとしたのが書けるのですが今回は無い想定で書いてます。もっと良い方法があれば是非教えていただきたく。 以下を参照。

Creating and Enabling Service Accounts for Instances  |  Compute Engine Documentation  |  Google Cloud

TOKEN=$(curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token?alt=text" -H "Metadata-Flavor: Google" | grep access_token |awk '{ print $2 }')

POSTリクエストでファイルアップロードする

TEXT="文字列"
curl -X POST --data "${TEXT}" \
                    -H "Authorization: Bearer ${TOKEN}" \
                    -H "Content-Type: text/plain" \
                    "https://www.googleapis.com/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${OBJECT_NAME}"

k8sのdeploymentのpostStartに記述してみる

アップロードするパスは後で見やすくなるように適宜スラッシュ入れて区切るべしってやつですね。アップロードが多くなるようなら日にちで一旦スラッシュ入れたりとかになるんでしょうか。 postStart / preStopで複数行の記述をする方法に関しては以下を参考にしました。

kubernetes - multiple command in postStart hook of a container - Stack Overflow

          preStop:
            exec:
              command:
              - "sh"
              - "-c"
              - >
                ACCESS_TOKEN=$(curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token?alt=text" -H "Metadata-Flavor: Google" | grep access_token |awk '{ print $2 }');
                TEXT="文字列";
                OBJECT_NAME=Bucket/$(date +"%Y%m%d%H%M%S")_$(hostname);
                curl -X POST --data "${TEXT}" \
                    -H "Authorization: Bearer ${ACCESS_TOKEN}" \
                    -H "Content-Type: text/plain" \
                    "https://www.googleapis.com/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${OBJECT_NAME}";

docker参照コマンドチートシート

よく使うDockerコマンド

調査系のみ

コンテナの基本情報を絞って表示

docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
NAMES               IMAGE               STATUS
r1                  redis               Up 25 minutes

コンテナのIPアドレス一覧

docker ps -q | xargs docker inspect --format '{{ .Name }} - {{ .NetworkSettings.IPAddress }}'
/r1 - 172.18.0.2

コンテナがマウントしているボリュームを表示

docker run  -v /docker/redis-data:/data --name r1 -d redis
# マウント一覧を表示
docker ps -q   | xargs docker inspect --format '{{ .Name }} - {{range $mount :=.Mounts}}{{ $mount.Source }} <-> {{ $mount.Destination }}{{end}}'
/r1 - /docker/redis-data <-> /data

コンテナの統計情報

docker ps -q | xargs docker stats
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT     MEM %
     NET I/O             BLOCK I/O           PIDS
3c1719011282        r1                  0.08%               6.918MiB / 737.6MiB   0.94%     26.3kB / 90B        6.37MB / 0B         4

docker内のネットワーク

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
cc9ab9e4598e        bridge              bridge              local
fa054a9af353        host                host                local
f50397115ef2        none                null                local
$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "cc9ab9e4598e563b04c3c58f93ccdc15d9c1c682abbaf05e44034c63254cde4f",
        "Created": "2018-08-06T16:05:15.115331313Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.1/24",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "0e4a62e4d4a90d38f9f3d1be868f278807b390cdaba92951448ef64bdedd492e": {
                "Name": "tutorial_web_1",
                "EndpointID": "b396b59a93be30084d902e882eb21e961eac9d8599edbf32e832e1c05d3dc305",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/24",
                "IPv6Address": ""
            },
            "94158270f37a09f5ee28412dc569b048319fec166e1b0cf952fd8efa24c798a7": {
                "Name": "tutorial_redis_1",
                "EndpointID": "a14d820f0659721ebe1293e56871e1e68f94d6e07c5067f85c892e37a86bcfe4",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

まとめ

コンテナの起動や削除は開発環境(ローカル)はcompose、検証・本番環境はECS/GKEだから使用頻度が高いのは調査系のみだった。

pod(Kubernetes)のlifecycle.prestopの挙動

コンテナ削除時すぐにコンテナをSTOPされると困ることありません?

 

例えば….?

ApacheやNginxなどのWebサーバはSIGTERMが送られても、処理中のコネクションがCloseされないまま終了してしまう。 理想としては、残っている接続済みのコネクションを終了してからコンテナがSTOPして欲しいですよね。

Prestopを設定する

SIGTERMでの挙動は以下のようなもの(https://httpd.apache.org/docs/2.2/ja/stopping.htmlより引用)

TERM あるいは stop シグナルを親プロセスに送ると、即座に子プロセス全てを kill しようとします。 子プロセスを完全に kill し終わるまでに数秒かかるかもしれません。 その後、親プロセス自身が終了します。 処理中のリクエストは全て停止され、もはやリクエストに対する 応答はされません。

これでは、まずいのでPrestopフックを入れます。PrestopフックはPodのシャットダウンプロセスの最初に実行されるものです。Deploymentの.spec.template.spec.containers.lifecycle.preStopに処理を記載していきます。httpGet等でhttpリクエストを引っ掛けることもできますが、基本的にはexecで記述することが多いように思えます。execは記述したコマンドの同一のコンテナで実行します。

Apacheのwebサーバの場合だと、httpd -k graceful-stopをDeploymentの.spec.template.spec.containers.lifecycle.preStop.exec.commandに書いていくような形となるかと思います。Pod preStop フックの実行と Service エンドポイントからの Pod IP アドレス削除は並列して実行されるため、preStop フックの実行が先に実行されてしまいアクセスがまだ来る状態でコネクション作成をやめてしまうことになります。 なので、httpd -k graceful-stopの前に数秒置く必要があります。また、指定したコマンドが非同期の場合には、勝手にpreStopフックが完了されたとみなし、SIGTERMがPodのコンテナに送られてしまいます。そのため、非同期のコマンドのあとにはそのコマンドでかかる時間sleepしておくなどするべきです。

もしapacheのリクエストのタイムアウト時間を30秒としているならば、以下のような設定となるかと思います。 terminationGracePeriodSecondsはpodのシャットダウンプロセスが始まってからSIGKILLが送られるまでの時間です。preStopの設定を入れている場合は、preStop処理が終了後にSIGTERMが送られ、シャットダウンプロセスが始まってからterminationGracePeriodSecondsで指定した秒数が経過してもプロセスが完了していない場合にはSIGLKILLが送られます。デフォルトは30秒となっているはずです。

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: httpd
    lifecycle:
      preStop:
        exec:
          command: ["sh", "-c", "sleep 2; httpd -k graceful-stop; sleep 30"]
  terminationGracePeriodSeconds: 40

kubernetesソースコード上では以下のように、preStopが実装されてます。 preStopの定義がDeploymentにない場合は、すぐにStopContainerが送られてしまうのがわかると思います。

        // Run the pre-stop lifecycle hooks if applicable and if there is enough time to run it
    if containerSpec.Lifecycle != nil && containerSpec.Lifecycle.PreStop != nil && gracePeriod > 0 {
        gracePeriod = gracePeriod - m.executePreStopHook(pod, containerID, containerSpec, gracePeriod)
    }
    // always give containers a minimal shutdown window to avoid unnecessary SIGKILLs
    if gracePeriod < minimumGracePeriodInSeconds {
        gracePeriod = minimumGracePeriodInSeconds
    }
    if gracePeriodOverride != nil {
        gracePeriod = *gracePeriodOverride
        glog.V(3).Infof("Killing container %q, but using %d second grace period override", containerID, gracePeriod)
    }

    err := m.runtimeService.StopContainer(containerID.ID, gracePeriod)
    if err != nil {
        glog.Errorf("Container %q termination failed with gracePeriod %d: %v", containerID.String(), gracePeriod, err)
    } else {
        glog.V(3).Infof("Container %q exited normally", containerID.String())
    }

Prestopフックですが、標準出力などに吐き出してもStackdriverなどでは拾えないので注意です。なお失敗した場合には、kubectl describe <pod>のイベントのところに表示されます。

インフラ初心者がAWS(Amazon ECS)についてまとめてみた

前提となる知識の理解

以下の資料を読んで理解を深めた。
アマゾン ウェブ サービスの概要
20180411 AWS Black Belt Online Seminar Amazon EC2
リージョンとアベイラビリティーゾーン - Amazon Elastic Compute Cloud
AWSにおけるマルチアカウント管理の手法とベストプラクティス
AWS アカウント ID とその別名 - AWS Identity and Access Management
AWS Black Belt Online Seminar AWS Identity and Access Management (AWS…

RegionとAvailability Zone

  • AWS クラウドインフラストラクチャはリージョンとアベイラビリティーゾーン (AZ) を中心として構築される。
  • リージョンは2つ以上のAZ から構成されている(ローカルリージョンを除く)。各リージョンは、他のリージョンと完全に分離されるように設計されている。
  • 各AZは互いに影響にを受けないように、地理的・電源的・ネットワーク的に独立している。AZ間は低遅延の高速専用線で繋がれている。
  • AZ は 1 つ以上の独立したデータセンターで構成される。各データセンターは、冗長性のある電源、ネットワーキング、および接続性を備えており、別々の設備に収容されている。このような AZ によって、お客様は単一のデータセンターでは実現できない高い可用性、耐障害性、および拡張性を備えた本番用のアプリケーションとデータベースを運用できる。
  • AZはAWS上ではリージョンコードとそれに続く文字識別子によって表されます (us-east-1a など)
  • 東京リージョンのリージョンコードはap-northeast-1で大阪ローカルリージョンのリージョンコードはap-northeast-3となっている。

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/images/aws_regions.png

Account

  • AWSアカウントはAWSアカウントIDによって一意に識別される。全てのAWS製品の中で一意であれば、AWS アカウントの別名を作成することもできる(ただし一つまで)
  • AWSアカウントとは、”リソース管理の単位”かつ”セキュリティ上の境界”であり、”AWS課金単位”でもある。
  • 単一でなく、複数のAWSアカウントの使用がAWSゴールドパートナーであるNRIさんの推奨である。
  • 複数のAWSアカウントを用いる理由(メリット)
    • ガバナンスの観点
      • 本番環境の管理コンソール(後述)を分離できる
      • 本番環境と非本番環境を管理するメンバーとで明確な権限の分離
      • 環境間におけるセキュリティ対策の分離が可能
    • 課金の観点
      • 分離したアカウント毎に明確な課金管理を行うことができる。
      • 複数のコストセンターやLOB等に対し、シンプルな課金やチャージバックの運用を行うことができる。
    • 組織の観点
      • 異なるLOBを異なる課金管理と共にサポートすることができ、LOB単位での管理を容易に行うことができる
      • 統制や課金が異なる専用アカウントを用いる個々の顧客に対してサービスプロバイダー型のサービスを提供しやすい
    • 運用の観点
      • 変更による影響範囲を縮小し、リスクアセスメントをシンプルにできる。例)開発環境の変更が本番環境に及ばない
      • AWSアカウントのリソース上限にかかる可能性を緩和できる

サービスにアクセスする方法

AWS IAM (Identity and Access Management)

  • AWS操作をよりセキュアに⾏うための認証・認可の仕組み
  • AWS利⽤者の認証と、アクセスポリシーを管理
    • AWS操作のためのグループ・ユーザー・ロールの作成が可能
    • グループ、ユーザーごとに、実⾏出来る操作を規定できる
    • ユーザーごとに認証情報の設定が可能 f:id:jrywm121:20180505205648p:plain

AWSアカウント(root)ユーザー

  • AWSアカウント作成時のID
  • アカウントの全ての AWS サービスとリソースへの完全なアクセス権限を持つ
  • アカウントの作成に使⽤したメールアドレスとパスワードでサインイン
  • ⽇常的なタスクには、それが管理者タスクであっても、root ユーザーを使⽤しないことを強く推奨
  • AWSのroot権限が必要な操作
    • AWS ルートアカウントのメールアドレスやパスワードの変更
    • IAMユーザーの課⾦情報へのアクセスに関するactivate/deactivate 等々…
  • AWSルートアカウントはIAMで設定するアクセスポリシーが適⽤されない強⼒なアカウントであるため、⼗分に強度の強いパスワードを設定した上、通常は極⼒利⽤しないような運⽤をする。Security CredentialのページからAccess Keyの削除を行う。

IAMユーザー

  • AWS操作⽤のユーザー(1AWSアカウント毎に5000ユーザーまで作成可能)
  • ユーザーごとにユーザー名/パス(オプション)/所属グループ(上限は10)/パーミッション(Json形式で記述)

IAMグループ

  • IAMユーザーをまとめるグループ(1AWSアカウント毎に100グループまで作成可能)
  • グループ毎にグループ名/パス(オプション)/パーミッション

IAMで使⽤する認証情報

  • アクセスキーID/シークレットアクセスキー(2つまで) = REST,Query形式のAPI利⽤時の認証に使⽤
  • X.509 Certificate = SOAP形式のAPIリクエスト⽤
  • AWSマネジメントコンソールへのログインパスワード
  • 多要素認証(MFA)

アクセス条件の記述

f:id:jrywm121:20180505214653p:plain

AWS CloudTrail

  • AWSアカウントで利⽤されたAPI Callを記録し、S3上にログを保存するサービス。
  • AWSのリソースにどのような操作が加えられたか記録に残す機能であり全リージョンでの有効化を推奨。
  • 適切なユーザーが与えられた権限で環境を操作しているかの確認と記録に使⽤。
  • 記録される情報
    • APIを呼び出した⾝元
    • APIを呼び出した時間
    • API呼び出し元のSource IP
    • 呼び出されたAPI 等々…

IAMロール

  • AWSサービスやアプリケーション等、エンティティに対してAWS操作権限を付与するための仕組み
  • IAMユーザーやグループには紐付かない
  • 設定項⽬は、ロール名とIAMポリシー

AWS STS(Security Token Service)

  • ⼀時的に利⽤するトークンを発⾏するサービス
  • ユーザーに対して、以下の3つのキーを発⾏
    • アクセスキー:(ASIAJTNDEWXXXXXXX)
    • シークレットアクセスキー:(HQUdrMFbMpOHJ3d+Y49SOXXXXXXX)
    • セッショントークン(AQoDYXdzEHQ……1FVkXXXXXXXXXXXXXXXXXXXXXXXX)
  • トークンのタイプにより有効期限は様々

AWSを運用する上での常識を身につける

以下の資料を読んで理解を深めた。 (英語のホワイトペーパーは読めてない)
20180403 AWS White Belt Online Seminar AWS利用開始時に最低限おさえておきたい10のこと
AWS Well-Architected Framework
AWS Black Belt Online Seminar 2016 AWS CloudTrail & AWS Config
Amazon RDS 入門

セキュリティ

  • AWSルートアカウントは極力使用しない
    • 十分に強度の強いパスワードを設定した上で多要素認証(MFA)で保護し、通常は極力使用しない運用を行う。
    • Security CredentialのページからAccess Keyの削除を行う。
  • ユーザには最小限の権限を付与する
    • IAMユーザとIAMグループを利用する
    • 最小限のアクセス権を設定し、必要に応じて追加のアクセス権限を付与する。
    • 特権のあるIAMユーザ(機密性の高いリソースやまたはAPIにアクセスが許されているユーザ)に対してはMFAを設定する
    • 認証情報を定期的にローテーションする
  • 認証情報を埋め込まない
    • 認証情報をOSやアプリケーション側に持たせることなく、IAMロールを使用してAWSサービス操作権限を付与する
    • 認証情報はSTS(Security Token Service)で生成し、自動的に認証情報のローテーションが行われる
  • 証跡取得(ログ取得)
    • AWS CloudTrailによって操作ログを取得する。取得したログをCloudWatch Logsに転送し監視することも可能
    • Amazon GuardDutyを利用して疑わしいアクティビティをログから検知する
  • 各レイヤでのセキュリティ対策
    • ネットワークレイヤ(ネットワークACL
      • VPCのサブネット単位で設定するステートレスなファイアーウォール
      • ベースラインとなるポリシーを設定するのに用いる
    • Amazon RDS / EC2などのリソース(セキュリティグループ)
      • インスタンス(グループ)単位に設定するステートフルなファイアーウォール
      • サーバの機能や用途に応じたルール設定に適している
    • AWS WAF
    • AWS Shield

コスト最適化

  • 適切なサイジング
    • AWS CloudWatchでリソース利用状況を把握する
      • AWS上で稼働するシステム監視サービス
      • システム全体のリソース使用率、アプリケーションパフォーマンスを把握
      • 予め設定した閾値を超えたら、メール通知、AutoScalingなどのアクションを起こすこともできる
      • またCloudWatch LogsでOS上やアプリケーションのログも取得可能

データのバックアップ

  • EC2のAMI(Amazon Machine Image)を作成。
  • EBSのスナップショットを取得。作成時はデータ整合性を保つために静止点を設けることを推奨
  • RDSの自動バックアップ機能

障害や不具合への対策

f:id:jrywm121:20180506011824p:plain

  • ELB(Elastic Load Balancing)の活用
  • AutoScalingの活用
  • EC2 AutoRecoveryの活用

Amazon VPC(Virtual Private Cloud)

以下の資料を読んで理解を深めた。
20180418 AWS Black Belt Online Seminar Amazon VPC
AWS Black Belt Online Seminar 2016 AWS CloudFormation

概要

  • AWS クラウドの論理的に分離されたセクションをプロビジョニング(※1)し定義した仮想ネットワーク内の AWS リソースを起動することができる
  • 独自の IP アドレス範囲の選択、サブネットの作成、ルートテーブル、ネットワークゲートウェイの設定など、仮想ネットワーク環境を完全にコントロールできる
  • VPC のネットワーク設定は容易にカスタマイズすることができる。たとえば、インターネットとのアクセスが可能なウェブサーバーのパブリックサブネットを作成し、データベースやアプリケーションサーバーなどのバックエンドシステムをインターネットとのアクセスを許可していないプライベートサブネットに配置できる。
  • 既存のデータセンターと自分の VPC 間にハードウェア仮想プライベートネットワーク (VPN) 接続を作成することができるので、AWS クラウドを既存のデータセンターを拡張するかのように活用することができる。

※1 プロビジョニング:
必要に応じてネットワークやコンピューターの設備などのリソースを提供できるよう予測し、準備しておくこと

VPCをカスタマイズするコンポーネントたちと構成例

f:id:jrywm121:20180506014018p:plain

f:id:jrywm121:20180506014126p:plain

↓あとでそれぞれの項目を補足する
インターネット接続VPCのステップ

  • アドレスレンジの選択
    • 推奨: /16 65,534アドレス
  • AZに於けるサブネットの選択
    • VPCあたりのサブネット作成上限数はデフォルト200個
    • /24に設定すれば、200個作成する上で効率が良い(サブネットあたりのIPアドレス数 251)
    • サブネットで利用できないIPアドレス(/24の例)

      ホストアドレス 用途
      .0 ネットワークアドレス
      .1 VPCルータ
      .2 Amazonが提供するDNSサービス
      .3 AWSで予約
      .255 ブロードキャストアドレス
  • インターネットへの経路を設定
    • ルートテーブルはパケットがどこに向かえば良いかを示すもの
    • PublicサブネットのIPがIGW(Internet GateWay)経由でインターネットまたはAWSクラウドと通信するときにパブリックIPを利用
    • EC2のOSで確認できるのはプライベートIPのみ
  • VPCへのIN/OUTトラフィックを許可 f:id:jrywm121:20180506120927p:plain

    |ネットワークACL |セキュリティグループ| |:--:|:--:| |サブネットレベルで効果|サーバレベルで効果| |Allow/DenyをIN・OUTで指定可能(ブラックリスト型)|AllowのみをIN・OUTで指定可能(ホワイトリスト型)| |ステートレスなので、戻りのトラフィックも明示的に許可設定する|ステートフルなので、戻りのトラフィックを考慮しなくて よい| |番号の順序通りに適用|全てのルールを適用| |サブネット内のすべてのインスタンスACLの管理下に入る|インスタンス管理者がセキュリティグループを適用すればその管理下になる|

VPCとのプライベート接続

  • VPN接続 : バーチャルプライベートゲートウェイを利用したサイト間VPN
    • 1つのVPN接続は2つのIPsecトンネルで冗長化
    • ルーティングは静的(スタティック) / 動的(ダイナミック:BGP)が選択可能
  • 専用線接続 : AWS Direct Connectを利用し、一貫性のあるネットワーク接続を実現する。本番サービス向け。
    • AWSとお客様設備を専用線でネットワーク接続

VPC設計のポイント

  • CIDR(IPアドレス)は既存のVPC、社内のDCやオフィスと被らないアドレス帯をアサイ
  • 複数のアベイラビリティゾーンを利用し、可用性の高いシステムを構築
  • パブリック/プライベートサブネットへのリソースの配置を慎重に検討

VPCと他サービスとのやり取り

  • サービスによってVPC内と外のどちらにリソースやエンドポイントが存在するかが異なる
  • VPC Endpointは、グローバルIPをもつAWSのサービス(例えば、DynamoDB / Lambda / S3 / SQS 等…)に対して、VPC内部から直接アクセスするための出口です。
  • VPC EndpointはGateway型の動作とPrivateLink (Interface型)の動作に分かれる
    • Gateway
    • PrivateLink (Interface型)
      • サブネットにエンドポイント用のプライベートIPアドレスが生成される。
      • VPC内部のDNSがエンドポイント向けの名前解決に対してしてプライベートIPアドレスで回答する。
      • エンドポイント用プライベートIPアドレス向け通信が内部的にサービスに届けられる。

f:id:jrywm121:20180506101243p:plain

NATゲートウェイ

Amazon VPCの実装方法

  • マネジメントコンソール
  • AWS CLI / AWS SDK
  • Ansibleなどのサードパーティツール
  • AWS CloudFormation
    • EC2やELBといったAWSリソースの環境構築を、設定ファイル(テンプレート)を元に自動化できるサービス
    • テンプレートには起動すべきリソースの情報をJSONYAMLフォーマットのテキスト形式で記述する
    • 構築済みの環境からテンプレートを作成するツール(CloudFormer)も有益

VPC Flow Logs

  • ネットワークトラフィックをキャプチャし、CloudWatch LogsへPublishする機能
  • ネットワークインタフェースを送信元/送信先とするトラフィックが対象
  • セキュリティグループとネットワークACLのルールでaccepted/rejectされたトラフィックログを取得
  • 運用例:Elasticsearch Service + kibanaによる可視化

f:id:jrywm121:20180506104749p:plain

Amazon ECS(Elastic Container Service)

以下の資料を読んで理解を深めた。 20180220 AWS Black Belt Online Seminar - Amazon Container Services
20180313 Amazon Container Services アップデート
What is Amazon Elastic Container Service? - Amazon Elastic Container Service
Task Definition Parameters - Amazon Elastic Container Service Amazon EC2 Container Service(ECS)の概念整理 - Qiita

概要

  • Amazon EC2 Container Service (ECS) は、Docker コンテナをサポートするスケーラビリティに優れた高性能なコンテナ管理サービス
  • Amazon EC2 インスタンスのマネージド型クラスターで簡単にアプリケーションを実行できる
  • 簡単な API 呼び出しで、Docker が有効なアプリケーションを起動および停止したり、クラスターの完全な状態を問い合わせたり、セキュリティグループ、Elastic Load Balancing、Amazon Elastic Block Store (Amazon EBS) ボリューム、AWSIdentity and Access Management (IAM) ロールなどの機能にアクセスすることができる
  • Amazon EC2 Container Registry (ECR)
    • 開発者が Docker コンテナイメージを簡単に保存、管理、デプロイできる完全マネージド型の Docker コンテナレジストリ

コンテナを利用した開発に必要な技術要素

  • アプリのステートレス化
    • ステートが必要なものはコンテナの外に置く
  • レジストリ
    • コンテナの起動元となるイメージの置き場所
      • アプリ+実行環境をpush / 実行時にpull→build→run
    • 高い可用性、スケーラビリティが求められる
      • 落ちたらデプロイ不能、同時に大量にpullされることもある
      • 自前で持つとその管理コストがかかるので、Amazon ECRを利用しましょう
  • コントロールプレーン / データプレーン
    • コントロールプレーン = コンテナの管理をする場所
      • どこでコンテナを動すか / 生死の監視 / いつ停止するか / デプロイ時の配置 → Amazon ECS / Amazon EKS
    • データプレーン = 実際にコンテナが稼働する場所

f:id:jrywm121:20180506123718p:plain

Task: コンテナ(群)の実行単位

  • Task Definitionの情報から起動される
    • Task Definitionとは、jsonフォーマットのテキストファイルで、これは1つ以上(最大10)のコンテナーを記述できる
    • 記述するパラメータは以下のようなものがある
      • family : タスク定義の名前で、この名前をベースにシーケンシャルにリビジョンナンバーが付与される
      • taskRoleArn : タスク定義を登録するときに、IAM タスクロールを割り当てて、タスクのコンテナに、関連するポリシーに指定された AWS API を呼び出すためのアクセス権限を付与できる。
      • executionRoleArn : タスク定義を登録するとき、タスクのコンテナがユーザーに代わってコンテナイメージを取得して、CloudWatch にコンテナログを発行するためのタスク実行ロールを提供することができる
      • networkMode : タスクのコンテナで使用する Docker ネットワーキングモード。有効な値は、none、bridge、awsvpc、および host です。Fargate 起動タイプを使用している場合、awsvpc ネットワークモードが必要。
      • containerDefinitions
        • name : コンテナの名前。Docker Remote API の コンテナを作成する セクションの name と docker run の --name オプションにマッピングされる。
        • image : コンテナの開始に使用するイメージ。新しいタスクが開始されると、Amazon ECS コンテナエージェントは、指定されたイメージおよびタグの最新バージョンをプルしてコンテナで使用する。
        • memory : コンテナに適用されるメモリのハード制限    などなど…
  • Task内のコンテナ群は同じホスト上で実行
  • CPUとメモリの上限を指定し、それをもとにスケジュールする。

Service : ロングランニングアプリ用スケジューラ

  • Taskの数を希望数に保つ
  • Task Definitionを新しくするとBlue/Greenデプロイ
  • ELBと連携することも可能
  • メトリクスに応じてTask数のAuto Scalingも可能

IAM Role: Task毎に異なる権限のAWS認証が可能

※ タスクの単位について
例えば、裏にいるAPIをCallしてWEB UIを提供するもので、expressのアプリケーション のコンテナとnginx(expressにプロキシする) のコンテナで構成されるFront Serviceがある場合には、expressコンテナとnginxコンテナはTaskという単位でくくられる。

  • IAM RoleをTask毎に設定可能
  • AWS SDKを利用していれば自動的に認証情報が得られるため、アクセス鍵等の埋め込み不要

コンテナの数をAuto Scalingさせる

  • 何らかのメトリクス(例えば、コンテナのCPU・メモリ使用率 / リクエスト数)に応じて、コンテナの数を自動スケールさせたい
  • コントロールプレーンの課題 : メトリクスの変化に対して、コンテナ数をどの程度変化させるのか
  • データプレーンの課題 : コンテナのスケールに応じて、インスタンス数もスケールが必要

ECSのTarget TrackingとFargateの組合せがオススメ

  • Target Trackingとの連携

    • メトリクスに対してターゲットの値を設定するだけ(例: CPU使用率 50%)
    • その値に近づく様に、Application AutoScalingが自動的にServiceのDesiredCountを調整
  • Fargateを利用したコンテナAuto Scalingの優位性

    • Serviceのスケールに応じて自然にコンテナが起動・終了する
    • コンテナの起動時間に対してのみ課金