オプト社内で先日納会が行われ、その中でOpt-Technologiesとして出し物用のWebアプリを作成しました。 この記事では、今回の納会アプリのインフラ構成(Kubernetes/k8s)について解説します。
あいさつ
どうも、 @ocadaruma です。
オプトでは半期ごとに納会を開催しており、先日行われた2016年度下期の納会では、様々な出し物の披露とともに、社員の表彰などがありました。
Opt-Technologiesも前回に引き続き、出し物に使うWebアプリを提供しました。
前回の技術スタックは納会の余興としてはオーバーキル気味でしたが、今回もそのマインドを受け継ぎ、技術検証の場として好き放題に利用させてもらいました。
この記事では、今回の納会アプリのインフラ周りについて解説します。
よろしければ、前回の納会アプリの記事も合わせてご覧下さい。
仕様
前回はクイズアプリでしたが、今回は投票アプリです。
- 有権者数は1000人ほど。
- 一人あたりの持ち票は10票。
- AとB(仮名)が競うコンテンツが流れるので、好きな方に好きなだけ(10票以内で)票を投じる。
- 票数がリアルタイムで表示される。(焦りを誘う)
- 投票は全7回戦。
- 全投票が終わったら、管理者はランキング画面を表示する。
- ランキング画面では、「7回戦通して、勝ち馬に投じた票数順」上位50名をカウントダウン表示
1人あたり10票持っているため、前回よりもリクエストが集中することが想定されます。
システム構成図
- Kubernetes(k8s)を検証する名目で、投票アプリのシステム構成は下記のようにしました。
- 以降、それぞれについて解説していきます。
Google Container Engine (GKE)
- GCPのマネージドk8sクラスタです。
- この図には載せていませんが、dockerイメージはContainer Registryでホスティングしています。
- 本番は以下の2ノードで運用しました。(デフォルトのQuotaに引っかかり、
n1-highmem-16
で揃えられなかった)n1-highmem-16
× 1n1-highmem-8
× 1
Cloud DNS
Network Load Balancer
- GKEでは、k8sの
LoadBalancer
serviceを作成すると、Network Load Balancerおよびターゲットプールが自動的に作られます。 - 最初はL7 Load Balancerを使ってSSL terminationもやろうとしたのですが、今回のアプリはWebSocketを利用するため断念し、各Podにtermination用のNginxを挟むことにしました。
LoadBalancer serviceに関する余談
LoadBalancer
serviceを作成すると、Network Load Balancerつまり特定のポートの転送ルールが作られるわけですが、ということは80番をexposeするLoadBalancer
serviceは一つしか作れないのか?と思いませんか?(port rewritingを行うようなGCEインスタンスが別途立ったりもしない)- もちろんそんなことはなく、k8sの各ノードに、転送ルールの外部IPからのパケットを振り分けるiptablesルールが設定されるため、特定のserviceへトラフィックを流すことができる仕組みになっています。
Kubernetes
Services
- Webアプリを外部にexposeする
LoadBalancer
serviceと、内部でのRedisへのエンドポイントを提供するClusterIP
serviceの2つを作成しました。 - ちなみに、minikubeなどローカル環境で動かしている場合でも、
LoadBalancer
serviceを作ることで自動的にNodePortも開くため、動作確認は問題なく行えます。(もちろんLBは立ちません)
--- apiVersion: v1 kind: Service metadata: name: redis-service namespace: production spec: type: ClusterIP ports: - port: 6379 targetPort: 6379 selector: app: redis --- apiVersion: v1 kind: Service metadata: name: app-service namespace: production spec: type: LoadBalancer ports: - name: http port: 80 targetPort: 80 - name: https port: 443 targetPort: 443 selector: app: server
Pod (Nginx + App)
--- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: app-deployment namespace: production spec: replicas: 3 template: metadata: labels: app: server namespace: production spec: containers: - name: proxy image: /path/to/nginx_image imagePullPolicy: IfNotPresent ports: - containerPort: 80 - containerPort: 443 readinessProbe: httpGet: path: /health port: 80 initialDelaySeconds: 5 timeoutSeconds: 10 - name: server image: /path/to/app_image imagePullPolicy: IfNotPresent ports: - containerPort: 3000 env: - name: SERVER_PORT value: "3000" - name: REDIS_HOST value: "redis-service" - name: REDIS_PORT value: "6379" resources: limits: memory: "2500Mi"
Pod (Redis)
- 状態の共有および、リアルタイム投票結果のbroadcastにはRedisを使用しました。
- アプリのPodを複数立てるので、全WebSocketコネクションに投票結果を通知するために必要です。
- 実プロダクトで利用する際はReplicationやRedis-Clusterを利用すると思いますが、今回はSingle Podです。
--- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: redis-deployment namespace: production spec: replicas: 1 template: metadata: labels: app: redis spec: containers: - name: redis image: redis:3.2.6-alpine ports: - containerPort: 6379
ハマった点
Resource Quotaは設定必須
- 負荷試験で大量のコネクションを張った際、ノードごとunhealthyになって再起動を要する現象が発生しました。
- k8sでは、Resource Quotaはデフォルトでかかっていないので、適切に設定しておくことが必要です。
まとめ
- オプトではデプロイのcontainerizeはあまり進んでいないのが現状ですが、今回GKEを利用してみて、containerizeの知見がある程度得られました。
- ローカルと環境を揃えやすい点 / 簡単に複数環境を用意できる点 / デプロイが早い点がメリットに感じました。
- 今後、containerデプロイが適しそうな場面があれば、積極的に利用していきたいです。