Foreverly

メモ帳

JAPAN CONTAINER DAYS V18.04に行ってきた

https://medium.com/@yukotan/japan-container-days-v18-04-%E3%81%AE%E8%B3%87%E6%96%99-4f380fb7b696

サイバーエージェントにおけるプライベートコンテナ基盤AKEを支える技術

https://speakerdeck.com/masayaaoyama/saibaezientoniokerupuraibetokontenaji-pan-akewozhi-eruji-shu

アドテクで求められる性能要件

  • Low Latency
  • High Traffic
  • High Computing

環境

AKEとよばれるプライベート基盤(オンプレ)

AKEとは

コンテナが流行り始めた2016年ごろにスタート。 ソースを読んで理解を深め、2017年04月ごろにリリース。 2017年07付きにk8s1.7がプロダクションでサポート。 type loadbalanceをサポート。 プロダクション環境にAKEをリリース。 コンテナ環境に合わせたCICD環境を作り上げると効率が上がる。

コマンドでクラスタが作れる。 Heatとういう機能をつかって構築している。

一連の流れ

パッチを当てたK8sをbuildしOSimageをつくり、Opencluster Heatで自動構築して、E2E Testを実行する。 テストで使用しているもの https://github.com/heptio/sonobuoy

Key Features

  • K8sとSwarms support
  • openstackと統合
    • Heatで構築、CinderをPV、Designateで名前解決、Keystoneで認証する。
    • MagnumはL3ネットワークを使わなければいけないので、開発が遅い。細かい設定ができない。
    • Rancher 2.0はGAが5月だった。細かい設定ができない。
    • Tectonicは知名度が低くかった。細かい設定ができない。
  • L4/L7ロードバランサーを用意
    • NodePort + 手動LBだとノードのスケールしたり、LoadBalancerの操作が必要で大変。
    • type: LoadBalancerはCloud Provider Integrationで実装、OpenStackではOctaviaが使える。
    • Octaviaは性能不足なのでBaremetal LBと連携するようなCloudProviderを実装した。
    • ingressは面倒
  • monitoring log
    • addonを追加することで後から利便性を高めることができる。
    • addonとしてEFKstack、Datadog、helmなどがある。
  • Tuning
    • アドテクのシステムに合わせてチューニング
    • Network,Kernel,K8s,Hypervisor
  • Multi Countainer の実行サポート
    • k8sだけではなく、Docker Swamも対応

利点

自分たちでコンテナ基盤をつくれば、なんでも作れて触れて最高 ハイブリットクラウド構成 マルチコンテナランタイム対応

デメリット

実装コスト、運用コスト

マイクロサービスアプリケーションとしての機械学習

機械学習人工知能分野のブームがきている。 この分野では人材不足、獲得合戦だが、 今の学生は優秀なので数年後には解消されていそうとのこと。

機械学習をアプリケーションで利用する

処理時間、サーバコストも高い コードと学習モデルの整合性が難しい 学習環境の用意も必要 機械学習エンジニアの担当領域が不明 機械学習エンジニアはモデリングなどが得意だけど、システム設計とかAPI提供などはメインじゃない

コストの高い要求をした時のメルカリのSREチームの解凍

メルカリのSREチーム「Dockerfile用意してください。なんとかします」

はじめてのDockerfile

ビルド時にキャッシュがある。 キャッシュを意識した順序で書く。

一週間後、S3に画像が入っているので、GCPのk8sという構成。 Datadogでのモニタリング、Spinnakerでのデプロイリリース管理

http://techlife.cookpad.com/entry/2015/09/16/182917

はじめてのSpinnaker

GUIで操作できる。 自動deploy,immutableである。 http://tech.mercari.com/entry/2017/08/21/092743 http://tech.mercari.com/entry/2017/12/17/205719 http://tech.mercari.com/entry/2017/12/02/093000

1週間でできた理由は,モノリシックアーキテクチャからマイクロサービスに変わっていたことで,疎結合な作りになっていたから. これにとり,異なる機能を影響なく短期間でリリースできるようになった。 基本機能は Monolithic のまま、新機能や大幅な変更が伴う機能は Microservice可。

機械学習では汎用的につくるのは難しい。

キモになる機械学習の更新は,手動更新を都度SREに依頼していた.後から気づいたことだが, コードとモデルは密結合なため,整合性が保たれていないと動かない.複数のモデルをサポートし始めたら,自分の運用が破綻するのは目に見え始めていた。 マイクロサービスにしたメリットは機械学習モデルのサービスの組み込みが早い。 影響範囲が明確,改善の見込みがあれば早期のモデルデプロイ→気モデルの軽量化チューニングという投機的デプロイもできる。 機械学習エンジニアからすると,最大限の成果を上げるためにはモデリングへの注力が大事。 とはいえ、運用は無視できないので、モデリングと運用を分けたチームで運用する予定。 サービス関連系のオーバーヘッドも実は無視できない。

マイクロサービス化、依存パッケージとかを自由に使えるようになるのでいいけど、汎用イメージをつくるのはよくない。

PersistentVolumeでデータをBlueGreenデプロイする。

機械学習のモデルを頻繁に更新するのでコンテナーにしてマイクロサービスにすると相性がよい。

まとめ

機械学習エンジニアの立場からマイクロサービスは相性がいい。

  • 影響範囲がわかりやすい
  • アルゴリズム選択の自由度が高い
  • サービスの組み込みが早い

機械学習とMicroservicesとの親和性は高いがMicroservicesの雛形や指針があり、機械学習のシステムの専属のチームがあるとよい。

Yahoo!JapanのK8s as a Serviceで加速するアプリケーション開発

https://www.slideshare.net/techblogyahoo/yahoo-japan-kubernetesasaservice

Yahoo!ズバトクonk8s

サービス内容

くじが当たるキャンペーンプラットフォーム

技術スタックIaaS上のVMに社内独自パケージシステムとPHPから構成. キャンペーン中の数10倍に跳ね上がるトラフィックは,VMで捌くのが大変.CI/CD周りが自動化されていないので, リリースが遅い.パフォーマンステスト環境の整備が難しいなどの問題があった.

Yahoo ズバトクというサービスの基盤をkubernetesにした。 Zlabと協力してスタックをモダン化.OpenStackの上にk8s,Docker,ConcourseCIが,アプリもJavaで作り変えた

開発フローもモダン化

dev/stg/proの3環境にデプロイするまで,GitHubgのPR,Jenkins・独自ツールでビルド・構築していたのが、 GithubとConcurseが全てテスト/ビルド/デプロイが走る統合型に変わって便利になった。 リリースに掛かる時間も数時間から10分程度に変わった。

障害発生時の対応も変わった。 IaaSのHyperVisorが落ちたら、その上で動くVMを退避→サーバ稼働再開という流れ k8sを入れるとHyperVisorダウンをトリガにオートヒーリングで自動的にサービスが継続できる。 障害調査も分散環境のログ取得から,ログは1箇所で見れば良くなった。 今まではサーバーログを集めて調査してたが次はk8s側の機能でログを集約、splunkを使っている。

移行コストの話

内製プラットフォーム側の追加対応・CI/CD、言語のスイッチ、考え方や設計方針もk8s化する必要があった。 具体的にはクラウドネイティブ化。 これはZlabの協力を得て,考え方を変えた。 まだ移行の課題はあって。既存の設計をもう少しクラウドネイティブ化するための設計変更などもある。 今後はリリースまでの時間をもっと短くしたい。

Yahoo!JapanのK8s as a Service

Zlabは株式会社.ヤフージャパンの100%子会社.インフラ基盤技術の調査・研究開発。

Kaas

k8sクラスタの作成、削除、アップグレードを簡単値行える

  • セルフサービス
  • マネージド
  • スケーラブル

障害や問題のあるノードの修復(セルフヒーリング)

ノードAが壊れたら、削除して、新規のノードを自動作成し、クラスタを一定に保つ。

ゼロダウンタイムのアップグレード

利用可能なノード数を一定に保つことでサービス断なしに達成・ 全ノードの更新が必要な脆弱性対応にも即座に自動で対応できる。

クラスタアドオン

クラスタでサポートするアドオン:Ingres Controller、Ingressホストの自動登録。 Prometeus/Grafanaとダッシュボードで分かりやすく

Kaasの価値

煩雑なk8sのオペレーションから運用者を解放

  • クラスタの作成、削除、設定変更
  • ノード(VN)の追加、削除

これらは人間のやる仕事じゃないのでソフトウェア+Kaasにやらせる。 AUTOMATE ALL THE THINGS!!

KaaSの要件

数万台でも動くスケーラビリティ、非同期で処理できるモデル、処理が失敗しても再開できる・一部の破壊が全体の障害に影響を与えない堅牢性。

  • スケーラブル
  • 非同期モデル
  • 堅牢性
    • 処理が失敗しても再開できる

複雑な分散システムとして実装する必要があるが、近くに優れた分散システムの基盤があることに気づく。

KaaSは何を元に作っているのか.分散システム基盤としてのk8sに対して拡張機能を追加することで対応。 もともとのk8s自体が分散処理で動くように作り込まれているから k8sの拡張機能とすることで、付加価値が生まれる箇所のロジックに集中して開発ができている

分散システム基盤としてのk8s

kaas on k8s

母体のk8sで便利なもの.CustomResourceDeginitions。k8sAPIを拡張して,任意のリソースを追加できる。(エンドポイント,Watch APIなど) CRDを書いただけではなにもおきないのでコントローラを書く。 callbackとworkerを書けばOK。 カスタムコントローラの実装ができる。 これはControllerという形でフレームワークが用意されていて、実装者はコールバックだけをフレームワークに登録すれば良い。 カスタムリソースとカスタムコントローラのパターンで開発している

K8sのセキュリティのベストプラクティス

https://speakerdeck.com/ianlewis/kubernetesfalsesekiyuriteifalsebesutopurakuteisu

Ian Lwwis

k8sはインフラの提供をしてくれる

Guestbookアプリで説明 - WebFronted - web app - Message - メッセージを保存閲覧 - NGWord - NGワードを検出

k8s API

  1. Frontend Podからトークン取得
  2. トークン取得し、APIサーバを攻撃
  3. シークレットなどを取得し、さらにサービスを攻撃

Mitigate 1 & 2:RBAC

RBAC(Role Based Access Control)をきちんと設定しよう!

RBACは1.6から標準

Role Based Access Control ユーザやサーボすアカウントへロールを付与 ロールが権限を持つ get secrets tipdate configmap etc RBACはネームスペース展開 GKEではIAMと連携

Mitigate 2:API Sercer Firewall

API Server FirewallでAPIサーバーへのアクセスにIP制限かけよう!(Backend network側しかアクセスさせないようにする)

APIサーバへのアクセスをIPアドレスに制限 GKE なら1コマンドでできる

Mitigate 3:Network Policy

Network Policyで、DBやRedisなどKVSへのアクセスは必要なPodだけに制限しよう! telnet redis port番号 とかで接続できてしまう。 データベースへのアクセスを必要のあるPodに制限 ラブルセレクターでPodを洗濯 ネットワークプラグインで実装されている Calico, Weave,etc

ingressで設定したものしかportにアクセスできない。

ホストへアクセス

  1. コンテナ外へ突破
  2. kubeletを攻撃(権限や情報にアクセスされるなど)
  3. 同じホストに実行中のコンテナを攻撃

Mitigate1 :non-rootユーザで実行

コンテナをrootで実行すると色々できちゃうからroot意外のユーザーで実行する コンテナで別ユーザを実行すると、ホストのユーザがとれていない状態になる。 spec: securityContext: runAsUserでユーザを指定できる

runAsUser:1000

Mitigate1 :読み込みせんようファイルシステム

読み込み専用ファイルシステムもtrueにしておくと良い spec: securityContext: readOnlyRootFilesystem: true readOnlyRootFilesystem: true eadonlyfilesystemの使い所は大事

Mitigate1 :no_nre_privs

allowPrivilegeEscalation: false

自分が持っている権限委譲は付与できないようにする。 AllowPriviledgeEscalationはk8s 1.9での挙動では注意 https://qiita.com/inajob/items/943a634a1941030e5075

Mitigate 1: seccpmp/AppArmor/SELinux

seccomp + AppArmor + SELinuxで多段で守る eccomp + AppArmor/SELinuxで壁を増やす

SECCOMP

seccomp: security.alpha.kubernetes.io/pod: docker/default

metadata: annotations:seccomp.security.alpha.kubernetes.io/pod : docker/defaultとするとseccompが有効になってお勧め(ただしalpha)

seccompはv1.10でもまだalpha版 https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp

unshare -U でネームスペースから突破できてしまう。`

AppArmor

container .apparmor .security.beta.kubernates.io/hello: runtime/default

SELinux

SELinuxRedhat系、AppArmorはDebian系、

seLinuxはラベルで設定可能 https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

Mitigate 2&3: kubeletの権限を制限する

RBAC for kubelet

--authorization-mode=RBAC,Node --admission-control=...,NodeReaatriction

Rotate lubelet certs

--rotate-certificates

Mitigate: PodSecurityPolicy

トラフィックを傍受

  1. ネットワーク上の通信を傍受

Istio

  1. サービス間のプロキシー
  2. 暗号化
  3. 証明証の自動更新
  4. ポリシーがセントラルサーバで集中して管理する

CNCF Cloud Native Interactive Landscape

https://landscape.cncf.io/

Cloud Native Apps 入門

https://speakerdeck.com/tnir/cloud-native-apps

What is Cloud Natic

クラウドネイティブなシステム

https://www.cncf.io/about/charter/

CNCF

2015年にk8sプロジェクトの寄贈先としてLinux Foudationのもとでスタート 20プロジェクト(2018年4月現在) メンバーシップ(スポンサー)~180社(2018/4) Technical Oversight Communite

What is Cloud Natic Application

cloud application maturity https://www.nirmata.com/2015/03/09/cloud-native-application-maturity-model/

CI/CD基盤

イメージビルドの省力化・自動化・標準化は重要 Gitlab Runner + Gitlab Container Registry

Gitlab CI評価

CNCF CIにも採用済 Cloud Native対応 GitHub/GHE対応 企業ユースには適する UIの洗練感はない

アーキテクチャ

積極的なマイクロサービスは行わない。 

マイクロサービス化に拘らない

  • 既存コード資産
  • 慣れたフレームワーク
  • I/Oを熟考した「サービス」を実装していく

KubeCon tips and k8s at Github

https://speakerdeck.com/tnir/kubecon-tips-and-kubernetes-at-github

The Twelve-Factor Appsに従う

コミュニティとの関わり

コミュニティに支えられた。

まとめ

k8sで実行できるようにアプリをcloud Native化しよう。k8sで廃れても対応できそう。 cloud Native化の仕組みが大事。 microservicesにこだわらないことも大事 コミュニティに支えられた。

Spinnakerを利用したk8sへの継続的デリバリ

k8sで安全にappをデプロイする仕組みについて ※安全とは作業ミスをなくすなど

導入するメリット

なぜコンテナを使うのか? コンテナを利用するメリットは ポータビリティ、軽量、実行環境の隔り

開発と本番環境の環境差分をなくす 設定漏れやパッケージの差分など

k8sの役割

複数のDockerホストの管理 コンテナの死活監視 障害時のセルフヒーリング ロードバランサーの組み込み

k8sにはCI/CDの機能がないので別途用意する必要がある

CI/CD

高品質なプロダクトを素早くユーザに届ける

CI:継続的インテグレーション DEVELOPE→DEPLOY→TESTを自動で回す仕組み a.g. Jenkins

CD:継続的デリバリ CIで回したものをstg,本番環境にデプロイする仕組み

Spinnakerにより、k8s上でCDを実現できる CIは別途用意

Spinnakerとは

Netflix社が開発したOSS マルチクラウド対応CDツール アプリケーションの自動デプロイに必要な機能が実装 パイプラインやNlue/Greenデプロイなど

機能紹介

k8sにデプロイする機能 GUIでパイプラインを作成できる パイプラインとはワークフローみたいなもの 複数のデプロイメント方法をサポート Red /Black Deploy(Blue/Green) Rollying Red/Black Deploy Canary Deploy

Red/Black Deploy

切り替え切り戻しを一瞬で行い時

Rollying Red/Black Deploy

断続的に切り替えたいとき

Canary Deploy

テスト的検査 最小構成だけ切り替えて様子見したいとき 問題なければ、切り替える

切り戻しもGUIやパイプラインで簡単に可能

パイプラインにカスタムスクリプトの実行が可能

serverspec,selemiumなどで工程ごとに試験ができる

CIツールは別途必要。 連携できるのは、TravisとJenkins

進捗状況を通知できる

パイプラインの成功、失敗をslackなどで通知など

パイプラインで承認フローを組み込める

stg,testは自動、本番は管理者の承認で先にすすめる。

承認(manual judgment)

その他機能

  • white-listed execution windows
  • chaos monkey integrarion
  • enable monitoring
    • datadog
    • prometheus
    • stackdrive
  • triggering on webhock
  • authentication

spinnaker と k8sのマッピング

instance, pod server group, replicaset clustrt, deployment load bakancers, service security group, Ingress

Spinnakerのこれから

2ヶ月ごとにバージョンアップ k8sのmanifestをデプロイ kayentaと連携

k8sの運用設計ガイド

細かい機能はあとで理解し、k8sはなにをするのかをまずは理解した方がいい。 明確な目的を持てば、自然とどう使えばいいかわかる。

テーマ

自律的なチームとシステムを作るためのk8sの利用/運用設計

why自律的?

独立的に動けて。自由がある。 moving fast, innovating

moving fast

速さというのは急ぐこと空ではなく、何かをなくすことから生まれる チューニングも思いクエリをなくす。承認リレーをなくす。

k8s自体の狙いとズレていないか?

3GB.4GBのイメージを使ってしまうとか k8s design architecture オーケストレーションを排除して、セルフオペレーションのためのもの。

チームの設計

技術と組織は表裏一体 どういうチームだったらk8sを使える?

コンウェイの法則を逆手に取る

自分が作りたいシステムを設計したチームをつくればいい

アプリケーション系、インフラ共通基盤系 これらは密結合せず、チームを分けた方がいい プロダクトチームとクラスタアドミンチーム

責任の設計

システムごとに必要なエンジニアリング作業がある

疎結合になる責任教会を決める

ソースコード、Container、ノード、クラスタ コンテナとノードが教会

プロダクトチームの責任

顧客の課題を解決

クラスタチームの責任

プロダクトチームのパフォーマンスを最大化すること。

You build it,you run it!!

クラスタの設計

開発環境、本番環境毎にクラスタをつくるのはよくない 環境が増えるたびにクラスタが増えて管理コストが上がる。 開発と本番環境が一致していることが保証しづらい。 ステージング環境が必要になっtラ、またクラスタをつくるのか。

リージョン毎に1つだけクラスタをつくる。

  1. 開発と本番環境が一致
  2. プロダクトがクラスタを意識しない

環境を特別視しない

開発と本番環境をわけても、serviceA がserviceBに影響を与えないようにしといけない結局、

リージョンごとに1つだけくらつたをつくる。

  1. aws,gcp,herokuに開発環境せんよう窓口はない
  2. ユーザが開発環境用として認識すればいい

プロダクト、サービス毎にクラスタをわける

メンテされないクラスタがでてくる。 他のアービスと熊津するばあいはどうする

クラスタ感通信yほり、暮らした無い通信のお方がネットワークの制御がしやすい。Neteork PolivyやIstopがあるので。

同じノードにのっているとセキュアじゃない

クラスタは1つ専用のノードを用意する

クラスタの粒度

Namespaceの設計

Namespaceでバーチャルクラスタを作る

環境毎にNamespaceを分ける

環境だけでなく差=ビス感もわけたい

サービス名+環境にNamespaceを分ける

Network Pokivyの設計

Namespaceレベルで制御する

基本はAll Denyにしてホワイト絵リストで通信可能なNamespaceを設計する

RBACの設計

RBCSCで権限委譲する admin Roleとcluster admin Roleでプロダクトチームと、クラスタアドミンチームでわける。

Borg-cluster adminチームはGmail adminチームの権限はみれない、 権限は強すぎるなら削った方外い。

アプリケーションContainerの設計

Disposable

  1. ステートレスに
  2. ログは標準出力に

k8sはあるべき状態に治す。いまの状態から。control move 死んでも立ち上がるので、大丈夫というアプリケーションをつくるのがよい。

IMuutable

  1. Latest tagは使わない
  2. 開発と本番環境で同じイメージを使う

Resilient

自然に復旧する動きにする

  1. Liveness Proveを使う(生きてるけど、バックエンドにつながらない時は通さない。Containerは生きているけど、プロセスがゾンビの時はkillしてもらう。)
  2. Crash only
  3. PDBを使う

Observable

  1. Liveness Prove,Readiness Probe
  2. ログは標準出力に
  3. メトリクス、トレース

Single Concern

  1. 1Container 1プロセス

Loosely COupled

!. Labelで引っ掛ける 2. 順番はないほうがいい 3. Affinityも極力避ける(疎結合にしたい) 4. (Externaml)Serviceで固定IPも避ける

12 Factoro Appをみるのがいい

オペレーションの設計

必ず宣言的なアプローチをロツ

  1. バージョン管理
  2. Control Loop
  3. 1リソース1ファイル

バージョン管理では GUIのデプロイツールはあんまりよくない。 yamlのパラメータを変えた時、どこ変えたのかわかんない どうあるべきかを書けばいい。

WHY k8sがYAMKベースなのか

YAMLCLIcurlrest APIが自然と使える。

モニタリングの設計

メトリクス イベント トレース どうはねているのか どのクエリ、どの関数がエラーなのか イベント、ログ なぜを把握するためのもの。 メトリクスやトレースにはきづけないもの 4つのレベルでモニタリングする ソースコード -> コンテナ -> ノード -> クラスタ

『コンテナ疲れ』と戦う、k8s - PaaS -ServerLessの活用法!

正しいテクノロジースタックの洗濯ができる知識

コンテナつらくないですか? コンテナ具術は抽象度が低い エンジニアのかばーしないといけない責任は似が広い k8sはエンジニアスキルが高いことを前提では SREも日本の企業にあっているだろうか

Containerの次はなんだろう

10年前はクラウド黎明期。 EC2のEUリージョン解説

5年前はクラウドは定着 DevOpsがもてはやされた。IaaSやCI/CDが中心。

テクノロジーの流れは 抽象度が高く、自動化の繰り返し 自動化の好循環

次はより抽象化、より自動化。 PaasやServerless

PaaS

開発者がアプリケーション開発に専念 アプリケーションのライフサイクルを支援するプラットフォーム PaaSの内部はContainerを使っている Containerが廃れてもPaaSは進化し続けている

Serverless

サーバ管理をせずともアプリケーションの構築と実行を行う仕組み

ServerlessとContainerの関係

Slervelessプラットフォームは込んでナでFucctionえお実行

CNCF serverless whitepaper http://gs2.hatenablog.com/entry/2018/02/16/114739

Fluentd and Distributed Logging in Container Era

ログにもプロダクションのログ。ビジネスや、サービスのためのログ。 サービスログ、システムログなどがある。 コンテナは生まれて消えるので、ログ管理は大変。マイクロサービスが流行り、いろんなコンテナに、アプリがある。

ソースレイヤーでパースする。先に統一すると後が楽。統一された型を持ったレイヤーにするのが大事。 Fluentdでは基本はjson型に変換する。 aggregatorはfluentdからfluentdに送ること。

logging driverはDockerコンテナのログを取れる。 fluent-loggerはfluentdはコンテナのアドレスを知らなくていい。

コネクションが増えるとパフォーマンスが落ちるので、コンテナのアプリケーションのログの送り先に直接redisに送るとかではなく、fuentdを送る。 バッファリングや、ロードバランシングを考えてくれる。分離して管理する。

agregation serverとしてfluentdをさらに置く。 コネクションを一つにまとめて、ロギングだけのコンテナを置くと、ソースサイドの負荷も下げられる。

destinationapiコールが多いか、少ないかで変わる。 Bigqueryとかは課金があるので、前段にアグリッションサーバを置くことがある。 ネットワークを分散して、障害で落ちてもいいように、ロードバランシングしたほうがいい。 ログは飛ばし先の分散が重要。

ログのフォーマットは統一しよう。アプリケーションレイヤーとシステムレイヤーが分断されるとツライ。 Fluentdで飛ばすログが1ファイルに全部入り(スタックトレースアクセスログなど)つらい。

macからvagrantへrake spec実行ユーザ変更

macのユーザでSSH接続しようとするので、vagrant ユーザに変えたかった。 documentには以下のように記載があった。

Serverspec with SSH backend logs in to target servers as a user configured in ~/.ssh/config or a current user. If you’d like to change the user, please edit the below line in spec/spec_helper.rb.

options[:user] ||= Etc.getlogin

~/.ssh/configssh_config に記載したがどうしても上の options[:user] ||= Etc.getlogin が先に読み込まれて 現在のログインユーザーで実行されてしまった。 なので以下のように強引に書き換えて通った。

-options[:user] ||= 'vagrant'
+options[:user] ||= Etc.getlogin

spec_helper.rb をいい感じに書き換えて ssh_config を読ませるようにしたい。

"msg": "Failed to connect to the host via ssh: Permission denied (publickey,password).\r\n"の対応

環境

  • コントローラー:Mac
  • ターゲット:CentOS6.9(vagrant)

ansible -i inventory/inventory.ini playbook -m ping

pingで疎通確認をしたところ以下のメッセージが出た。 SSH接続失敗しているようだった。

"msg": "Failed to connect to the host via ssh: Permission denied (publickey,password).\r\n"

公開鍵の権限など確認したけど問題なく原因がわからなかったのですが、 - vvv オプションで確認したところ以下のようにSSH接続するuserが指定されていないのが原因だった。

ESTABLISH SSH CONNECTION FOR USER: None

なので、 以下のようにインベントリにユーザを指定すれば解決しました。

192.168.33.27 ansible_user=vagrant

SSHが通らない時に原因はいくつかあると思いますが、 -vvv-vvvv を指定すれば原因が掴めそうです。

ReactでHello World!

Node.jsのインストール

Homebrewでインストール

brew -v
brew update
brew install nodejs
node -v

インストール用プロジェクトの作成

JSのプログラムをプロジェクト単位で管理。 npm init -y でパッケージの管理ファイル(package.json)を作成する。

mkdir hello_react
cd hello_react
npm init -y

package.jsonの変更

  1. desctiption(説明文)を記入
  2. Githubで公開しないprivateなプロジェクトと指定
  3. 開発ツールの起動スクリプトstartを定義
  4. webpack実行用スクリプトwebpackを定義
{
  "name": "hello_react",
  "version": "1.0.0",
  "description": "Hello React", #1
  "private": true,              #2
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server", #3
    "webpack": "webpack -d"        #4
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

npmパッケージをインストール

npm install react react-dom
npm install webpack webpack-cli webpack-dev-server --save-dev
npm install babel-cli babel-loader babel-preset-env babel-preset-react --save-dev
npm install eslint eslint-loader eslint-plugin-react --save-dev
npm install css-loader style-loader --save-dev

インストール結果の確認用Reackコード

  1. ディレクトリー作成
mkdir src
mkdir public
  1. .babelrc作成
vim .babelrc
cat .babelrc
{
  "presets": ["env", "react"]
}
  1. .eslintrc.json作成
vim .eslintrc.json
cat .eslintrc.json
{
  "env": {
    "browser": true,
    "es6": true
  },
  "parserOptions": {
    "sourceType": "module",
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true,
      "jsx": true
    }
  },
  "extends": ["eslint:recommended", "plugin:react/recommended"],
  "plugins": ["react"],
  "rules": {
    "no-console": "off"
  }
}
  1. webpack.config.js作成
vim webpack.config.js
cat webpack.config.js
module.exports = {
  entry: {
    app: "./src/index.js"
  },
  output: {
    path: __dirname + '/public/js',
    filename: "[name].js"
  },
    devServer: {
    contentBase: __dirname + '/public',
    port: 8080,
    publicPath: '/js/'
  },
  devtool: "eval-source-map",
  mode: 'development',
  module: {
    rules: [{
      test: /\.js$/,
      enforce: "pre",
      exclude: /node_modules/,
      loader: "eslint-loader"
    }, {
      test: /\.css$/,
      loader: ["style-loader","css-loader"]
    }, {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
     }]
  }
};
  1. public/index.html作成
vim public/index.html
cat public/index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge, chrome=1" />
  <title>React App</title>
</head>
<body>
  <div id="root"></div>
  <script type="text/javascript" src="js/app.js" charset="utf-8"></script>
</body>
</html>
  1. src/index.js
vim src/index.js
cat src/index.js
import React from 'react'
import ReactDOM from 'react-dom'

ReactDOM.render(
  <h1>Hello, world!!</h1>,
  document.getElementById('root') 
)
  1. 確認
npm start

ターミナルに webpack: Compiled successfully. が表示されたら ブラウザーhttp://localhost:8080 をアクセスしてHello World! と表示されたらOK.

Pythonのクラスの基本

クラスの基本

クラスの書式

class クラス名:
    メソッドや属性

メソッド

クラス内の関数をメソッドと呼ぶ。 メソッドにはクラスに関連する処理を記述。

属性

クラス内のデータを属性と呼ぶ。 属性はクラスのインスタンスごとのデータを持つ。 self.hoge のようなクラスに関するデータのこと。

classのインスタンスの利用

クラスを設計図として、インスタンスを作成

c1 = NewClass()
c2 = NewClass()

右辺にクラス名()と書いてインスタンスを作成。 c1とc2がそれぞれインスタンスとなる。 作成時に値を渡したい場合は()の中に引数を渡せば良い。

initメソッド

__init__ メソッドはクラスからインスタンスが作られた直後に実行される特殊メソッド。 メソッドの書式は関数と同じ。 メソッドの第一引数にはインスタンス自身が渡される。(self)

class User(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def info(self):
        return self.name + ':' + str(self.age)

Userクラスのインスタンスを作る例。

user1 = User('田村ゆかり', 17)

インスタンス作成時にクラスタ名に渡される引数は__init__メソッドに渡されている。 クラスのメソッドの第一引数に指定されているselfは実際に呼び出し元から引数を設定する場合は無視する。 Userクラスの場合は、インスタンス作成時にnameとageに渡される値を設定する。

吉祥寺.pm13に参加してきました

こちらの勉強会で発表してきました。

吉祥寺.pm13 - connpass

吉祥寺.pmだけど今回は西新宿。

テーマが「新しい挑戦、新しい視点」だったので、新年度に向けた決意的な感じで発表してきました。 2018年の抱負は「ちゃんと調べて、ちゃんと理解する」で行こうと思います。 最近プログラミングの勉強(Python)を始めたので、 PythonでWEBアーキテクチャの実装をして、理解を深めました。

speakerdeck.com

イベント駆動や、DBもやっていきたいので頑張ります。 自分の発表内容は以下のオマージュ(パクリ)なのですが process-bookの著者様がいらっしゃり、 お世話になりました。というお気持ちで一杯になりました。 発表も盛り上がってて凄かった。

2015年Webサーバアーキテクチャ序論

process-book

イベント駆動はこれを参考にがんばりたい

イベント駆動プログラミングとI/O多重化

黒曜石を使って、あまり画面をみないで前を見て発表するを意識したけどまだだめ。 もっとアウトプットして、発表練習や発表の場を持とうと思います。 他の人のプログラミングの発表は何もわからん状態になってしまったので、 プログラミングの勉強がんばりたい。。。

吉祥寺.pm様ありがとうございました。

Python Programming for Web Architectures

タイトルをかっこよくしてみた。 最近PyQなるものを初めてPythonの書き方を覚えているのですが、 様々なライブラリがあることを知り、簡単なTCPサーバが書けることを知りました。

普段はWebサービスのサーバ管理をしていますが、 TCP/IPの通信をクライアントーサーバ間でどのような処理でされているのか、 IPやPortってどんな役割を果たしているのかなど実装を通じて学んでみました。

他にもApacheなどのWebサーバはpreforkモデルですが、 preforkってどんな処理なのか、Nginxが解決するC10K問題って何かなども Webサーバアーキテクチャのおさらいもしようと思います。

主に以下をパクリもとい参考にしています。 なので本記事を読まなくてもいいので、以下の記事や本だけでも読んでみて下さい。 2015年Webサーバアーキテクチャ序論 process-book Learning Python Network Programming

イベント駆動モデルはわからんので断念してます。

IP,Portそしてsocket

socketはエンティティがプロセス間通信を実行できる仮想エンドポイントで、 仮想エンドポイントを識別するために必要になるのが、IPとPort番号になる。 IPアドレスでホストがわかるけど、TCP/UDP通信でどのプロセスと通信をするのか決めるために Port番号が必要になる。

ss,netstat,lsofコマンドで、何気なくどのポートがなんのプロセスか、 どのプロセスがファイルを掴んでいるのかなど確認していましたが、 socket通信を理解すると、コマンドの確認結果の理解に繋がります。

3WAYハンドシェイク

TCPUDP通信ですが、ここではTCP通信について取り上げます。 クライアントとサーバーの間の3WAYハンドシェイクプロセスによってTCP接続を確立します。 SYN→SYN/ACK→ACKのやつですね。

TCPコネクションの図やTCP jokeなどをみると理解が深まります。

TCPサーバのリスニング接続を設定Pythonの関数create_listen_socket()として書いてみます。

def create_listen_socket(host, port):
    """ サーバーが接続要求を受け取るソケットを設定する """
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # アドレスファミリー、ソケットタイプ、プロトコル番号を指定して新しいソケットを作成
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((host, port))
    sock.listen(100)
    return sock

クライアントとサーバ間のメッセージのやりとりの例(ユニキャスト通信)

メッセージをsocketから受信するための関数をrecv_msg()関数として以下に定義しました。

def recv_msg(sock):
    """ データがソケットに到着するのを待ってから、メッセージの区切り文字として '¥0'を使用してメッセージに解析する """
    data = bytearray() # 新しいバイト配列を返す。
    msg = '' 
    # ソケットから4096バイトを繰り返し読み込み、デリミタが表示されるまでバイトをデータに格納する
    while not msg:
        recvd = sock.recv(4096) # ソケットからデータを受信し、結果を bytes オブジェクトで返します。一度に受信するデータは、4096bufsize
        if not recvd:
            # ソケットが途中で閉じられたら
            raise ConnectionError()
        data = data + recvd
        if b'\0' in recvd:
            # b '\ 0'をメッセージの区切り文字にする
            msg = data.rstrip(b'\0')
    msg = msg.decode('utf-8') #バイト型から文字列型へデコード
    return msg

メッセージの受信を待っている間にプログラムが必要とするものは何もないので、この関数はメッセージ全体を受信するまでループ内でsocket.recv()を呼び出します。 nullバイトを受け取ったかどうかを見るために繰り返しデータをチェックし、うけとったら、null バイトを取り除き、UTF-8からデコードして受信データを返します。

最後にsend_msg()関数とprep_msg())関数を作成しました。 これはメッセージにnullバイトの区切り文字をつけるのと、UTF-8エンコーディングして送信するための関数です。

def prep_msg(msg):
    """ メッセージとして送信する文字列を準備する """
    msg += '\0'
    return msg.encode('utf-8') # 文字列をバイト型へエンコード
def send_msg(sock, msg):
    """ 文字列をソケットに送信する準備 """
    data = prep_msg(msg)
    sock.sendall(data)

さきほど書いた関数をモジュールとして、 新たにメッセージを受けるサーバのスニペットを以下のように定義して書きました。

def handle_client(sock, addr):
    """ sockを通じてclientからデータを受けとり,echoを返す """
    try:
        msg = chatmodule.recv_msg(sock) # messageを完全に受信するまでblockする
        print('{}: {}'.format(addr, msg))
        chatmodule.send_msg(sock, msg) # 送信するまでblock
    except (ConnectionError, BrokenPipeError):   # ConnectionError のサブクラスで、もう一方の端が閉じられたパイプに書き込こもうとするか、書き込みのためにシャットダウンされたソケットに書き込こもうとした場合に発生。
         print('Socket error')
    finally:
        print('Closed connection to {}'.format(addr))
        sock.close()

if __name__ == '__main__':

    listen_sock = chatmodule.create_listen_socket(HOST, PORT)
    addr = listen_sock.getsockname() # ソケット自身のアドレスを返します。この関数は、IPv4/v6ソケットのポート番号を調べる場合などに使用。
    print('Listening on {}'.format(addr))

    while True:
        client_sock, addr = listen_sock.accept()
        print('Connection from {}'.format(addr))
        handle_client(client_sock, addr)

最初に、listen_sockをcreate_listen_socket()呼び出して定義します。 次に、クライアントからの接続要求を永久にlistenし、listen_sock.accept()をブロックするmainループに入ります。 クライアント接続が開始されると、プロトコルに従ってクライアントを処理するhandle_client()関数が呼び出されます。 部分的にメインループを整理し、この一連の操作を再利用できるように別の関数にしました。 これでサーバー側の処理が書けたので、次はクライアント側です。

クライアントのスニペットも以下に書きました。 mainループでしていることはメッセージとしてqを入力してクライアントを終了するまで永遠にループさせています。 メインループ内では、まずサーバーへの接続を作成をして、次に、ユーザーにプロンプ​​トを表示します。 送信するメッセージを入力すると、上で書いたsend_msg()関数を使ってメッセージが送信されます。 その後、サーバーの応答を待ちます。応答がきたら、それを出力します。 クライアントとサーバがこれで書けますので、参考にしてみて下さい。

if __name__ == '__main__':

    while True:
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.connect((HOST, PORT))
            print('\nConnected to {}:{}'.format(HOST, PORT))
            print("Type message, enter to send, 'q' to quit")
            msg = input()

            if msg == 'q': break
            chatmodule.send_msg(sock, msg) # 送信するまでBlock
            print('Sent message: {}'.format(msg))
            msg = chatmodule.recv_msg(sock) # messageを完全に受信するまでBlock
            print('Received echo: ' + msg)

        except ConnectionError:
            print('Socket error')
            break
        finally:
            sock.close()
            print('Closed connection to server\n')

クライアントとサーバのソケット通信の動きをまとめると

  1. サーバはsocket、bind、listenでクライアントの接続を待ち受ける
  2. 接続きたらacceptにより実際のデータの読み出しまで待つ。
  3. データがきたら、リクエストを処理して、クライアントにレスポンスを返す。
  4. クライアントとの接続をcloseで閉じて、またaccept待ち状態になりライアントからの接続をLISTENする。

これでクライアントとサーバのソケット通信がsocket、bind、listenを使用して理解できたと思います。 ただ、実行するとわかりますが、ひとつのクライアントからのリクエストしか処理できません。 クライアントからの接続を accept したあとは、ループを抜けるまでは新規のクライアント接続をブロックしてしまいます。

1:1の通信なのでユニキャスト通信です。 WEBサーバアーキテクチャとしてはシリアルモデルと言われるものになります。 Apacheは複数のユーザからのリクエストにレスポンスを返せるのはなぜでしょうか、 複数のプロセスがリクエストを返しているからですね。 それでは次はマルチプロセスモデルをみていきましょう。

マルチプロセスモデル

プロセスをforkして子プロセス子プロセスにリクエスト処理を任せるモデルです。 forkはプロセスをコピーするのでリソースを使い重いと言われますが、 しかし、Copy On Write(Cow)という仕組みで、差分のみをコピーするため実際にはそこまでリソースを使いません。 ただCoWでメモリコピー負荷が抑えられていますが、リクエスト毎にforkが発生するとリソースが使われますので 事前にforkさせておくのがpreforkモデルです。 事前に一定数の子プロセスをforkして、それらを使いまわす(MaxRequestsPerChildなど)ことで、リクエスト毎にforkをしなくてすみます。

また、マルチプロセスモデルは、プロセス間通信が必要なのでforkによるパフォーマンスが低下する可能性がありますが、 メモリを共有していないということは、コード内で競合状態が発生しないのでロックなどを気にしなくてすむので、処理を書くのが簡単になります。

今回はaccept()したあとの処理をプロセスで行いました。workerモデルと呼ばれるものです。 acceptしたプロセスからworkerプロセスにsocket接続を引き渡しますので コンテキストスイッチの負荷がかかってしまいます。

while True:
    client_sock,addr = listen_sock.accept()
    proc = Process(target=handle_client,
                       args=[client_sock, addr])
    proc.start()
    print('Connection from {}'.format(addr))
    proc.join(1)

ちなみにpreforkモデルはaccept()からclose()までの処理を各プロセスにやらせます。 accept()からclose()までの処理がシンプルに書けます。 (ただ自分は上手く書けなかったので、だれか実装例を教えてほしい)

デメリットは同時接続数がプロセスの数ということです。 同時接続数が子プロセスの数を超えると、accept()がされないので、接続は未処理となり詰まります。

Python multiprocessing 備忘録

マルチスレッドモデル

マルチスレッドのとマルチプロセスの違いはなんでしょうか。 基本的には同じでスレッドにはリクエストごとにスレッドを生成する1コネクション1スレッドのモデルと、 事前にスレッドをPoolしておくモデル(スレッドプール)があります。 スレッドの利点としてプロセスと比較した場合のメモリ占有量は軽量と言われています。 スレッドはリソースを共有しているため、複数のスレッド間で通信が可能で異なるメモリアドレスを読み書きすることができますが、 2つのスレッドがメモリを共有し始め、スレッドの実行順序を保証する方法がない場合は、間違った値を返したり、システム全体がクラッシュしたりする可能性があります。

if __name__ == '__main__':
    listen_sock = chatmodule.create_listen_socket(HOST, PORT)
    addr = listen_sock.getsockname()
    print('Listening on {}'.format(addr))
    while True:
        client_sock,addr = listen_sock.accept()
        # Thread は自動的にhandle_client()関数を実行し、同時にこのwhile loopを実行
        thread = threading.Thread(target=handle_client,
                                  args=[client_sock, addr],
                                  daemon=True)
        thread.start()
        print('Connection from {}'.format(addr))

接続するクライアントごとに、handle_client()関数を実行するだけの新しいスレッドを作成されます。 スレッドが受信または送信時にブロックすると、OSは他のスレッドをチェックして、 それらがブロッキング状態から抜けたかどうかを確認し、存在する場合はそのスレッドの1つに切り替えます。 スレッドコンストラクタ呼び出しのdaemon引数をTrueに設定しました。 ctrl-cでプログラムを終了できますが、明示的にすべてのスレッドを閉じる必要はありません。 複数のクライアントでこのエコーサーバーを試すと、メッセージを接続して送信する2番目のクライアントがすぐに応答を受け取るのがわかります。

キューやロックの処理はチャットサーバなどを実装するとわかると思います。 (ただ自分は上手く書けなかったので、だれか実装例を教えてほしい)

マルチスレッドのコンテキスト切り替えに伴うコスト スレッドセーフ

C10K問題

インターネットが発展してWebサーバーが同時に1万のクライアントを処理する時代になり、 マルチプロセス/スレッドではこの問題が解決できません。 解決策として、イベント駆動アーキテクチャであるNginxが誕生しました。 Nginxが目指すものはイベントループで1つのスレッドで数万の同時接続を処理することです。

しかし、全てがイベント駆動で解決するというわけではなく、マルチスレッド・アプローチで解決できたり、 他にはスケーリングの問題としてもC10Kは使われます。

また、アーバン・エアーシップ社が単一のノードで500.000件の同時接続。 C500k問題に直面した模様。 膨大な数のモバイルデバイスに通知サービスを提供するには、非常に多くのアイドル接続を並行して処理する必要があります。

今は、1000万の同時接続である「C10Mの問題」に直面しています。 このようなものは大規模分散システムの領域でしょうか。 いろんなアーキテクチャを組み合わせないと解決できなそうですね。 これらの知識についても勉強したいです。

TCP/IP - Solving the C10K with the thread per client approach C500k in Action at Urban Airship C10M

イベント駆動型サーバーアーキテクチャ

1プロセス/スレッドでは同時に複数のブロック処理を扱えないため、 新たにプロセスやスレッドを生成し処理をさせていましたね。

イベント駆動ではイベントループで一のスレッドを複数の接続にマッピングさせて 接続、リクエストの入出力操作から発生したすべてのイベントを処理させます。 新しいイベントがキューに入れられ、スレッドはいわゆるイベントループを実行します。 キューからイベントをdequeueしてイベントを処理し、次のイベントを取得するか、新しいイベントがプッシュされるのを待ちます。 したがって、スレッドによって実行される作業は、複数の接続を単一の実行フローに多重化するスケジューラの作業と非常に似ています。 次のスライドを参考になりそうです。

イベント駆動プログラミングとI/O多重化

イベント駆動型サーバーアーキテクチャPythonで イベント駆動型プログラミングを書かないと理解が深まりそうにないので 次回は以下を調べてみようと思います。 (だれか参考になる実装例や説明教えてください)

  • イベント駆動型プログラミング
    • それは何であり、どのように機能するのですか?
  • asyncioモジュール
  • asyncioベースのプログラミング
  • Twisted
  • Gevent

オブジェクト、メソッドまとめ

Socket family:socket.AF_INET=アドレス (およびプロトコル) ファミリーを示す定数で、 socket() の 最初の引数に指定することができます。
Socket type:ソケットの種類を指定します。SOCK_STREAMとSOCK_DGRAMをそれぞれ指定すると、TCPベースのソケットとUDPベースのソケットが作成されます。
socket.bind(address):ソケットを address にbindします。
socket.listen([backlog]):サーバーを有効にして、接続を受け付けるようにします。backlog が指定されている場合、少なくとも 0 以上でなければなりません (それより低い場合、0 に設定されます)。システムが新しい接続を拒否するまでに許可する未受付の接続の数を指定します。指定しない場合、デフォルトの妥当な値が選択されます。バージョン3.5 で backlogの引数が任意になりました。
socket.setsockopt(level, optname, None, optlen: int)
level:SOL_SOCKET:level パラメータは、オプションのプロトコルレベルを指定します。オプションをソケットレベルで取得するには level パラメータに SOL_SOCKET を指定します。
TCP のようなそれ以外のレベルの場合、そのレベルのプロトコル番号を指定します。
フラグ:SO_REUSEADDR フラグは、 TIME_WAIT 状態にあるローカルソケットをそのタイムアウト期限が自然に切れるのを待つことなく再利用することをカーネルに伝えます。
socket.listen:サーバーを有効にして、接続を受け付けるようにします。backlog が指定されている場合、少なくとも 0 以上でなければなりません (それより低い場合、0 に設定されます)。システムが新しい接続を拒否するまでに許可する未受付の接続の数を指定します。指定しない場合、デフォルトの妥当な値が選択されます。バージョン 3.5 で変更: backlog 引数が任意になりました。
socket.sendall(bytes[, flags]):ソケットにデータを送信します。ソケットはリモートソケットに接続済みでなければなりません。オプション引数 flags の意味は、上記 recv() と同じです。
send() と異なり、このメソッドは bytes の全データを送信するか、エラーが発生するまで処理を継続します。
正常終了の場合は None を返し、エラー発生時には例外が発生します。
エラー発生時、送信されたバイト数を調べる事はできません。
バージョン 3.5 で変更: ソケットのタイムアウトは、データが正常に送信される度にリセットされなくなりました。
ソケットのタイムアウトは、すべてのデータを送る最大の合計時間となります。
システムコールが中断されシグナルハンドラが例外を送出しなかった場合、
このメソッドは InterruptedError 例外を送出する代わりにシステムコールを再試行するようになりました (論拠については PEP 475 を参照してください)。
socket.accept:接続を受け付けます。ソケットはアドレスにbind済みで、listen中である必要があります。戻り値は (conn, address) のペアで、 conn は接続を通じてデータの送受信を行うための 新しい ソケットオブジェクト、 address は接続先でソケットにbindしているアドレスを示します。新たに作成されたソケットは 継承不可 です。システムコールが中断されシグナルハンドラが例外を送出しなかった場合、このメソッドは InterruptedError 例外を送出する代わりにシステムコールを再試行するようになりました (論拠については PEP 475 を参照してください)。