Opt Technologies Magazine

オプトテクノロジーズ 公式Webマガジン

会社の納会用に投票アプリを作りました(Kubernetes)

alt

オプト社内で先日納会が行われ、その中でOpt-Technologiesとして出し物用のWebアプリを作成しました。 この記事では、今回の納会アプリのインフラ構成(Kubernetes/k8s)について解説します。

あいさつ

どうも、 @ocadaruma です。

オプトでは半期ごとに納会を開催しており、先日行われた2016年度下期の納会では、様々な出し物の披露とともに、社員の表彰などがありました。

Opt-Technologiesも前回に引き続き、出し物に使うWebアプリを提供しました。

前回の技術スタックは納会の余興としてはオーバーキル気味でしたが、今回もそのマインドを受け継ぎ、技術検証の場として好き放題に利用させてもらいました。

この記事では、今回の納会アプリのインフラ周りについて解説します。

よろしければ、前回の納会アプリの記事も合わせてご覧下さい。

tech-magazine.opt.ne.jp

tech-magazine.opt.ne.jp

仕様

前回はクイズアプリでしたが、今回は投票アプリです。

  • 有権者数は1000人ほど。
  • 一人あたりの持ち票は10票。
  • AとB(仮名)が競うコンテンツが流れるので、好きな方に好きなだけ(10票以内で)票を投じる。
  • 票数がリアルタイムで表示される。(焦りを誘う)
  • 投票は全7回戦。
  • 全投票が終わったら、管理者はランキング画面を表示する。
  • ランキング画面では、「7回戦通して、勝ち馬に投じた票数順」上位50名をカウントダウン表示

1人あたり10票持っているため、前回よりもリクエストが集中することが想定されます。

システム構成図

  • Kubernetes(k8s)を検証する名目で、投票アプリのシステム構成は下記のようにしました。

architecture

  • 以降、それぞれについて解説していきます。

Google Container Engine (GKE)

  • GCPのマネージドk8sクラスタです。
  • この図には載せていませんが、dockerイメージはContainer Registryでホスティングしています。
  • 本番は以下の2ノードで運用しました。(デフォルトのQuotaに引っかかり、n1-highmem-16で揃えられなかった)
    • n1-highmem-16 × 1
    • n1-highmem-8 × 1

Cloud DNS

  • せっかくなので納会専用にドメインを取得し、DNSサーバにCloud DNSを使用しました。
    • Route53と違ってレジストラの機能は持ってないので、取得は別サービスを使いました...

Network Load Balancer

  • GKEでは、k8sLoadBalancer 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)

  • 前述の通り、今回の要件ではLBでSSL terminationが出来ないため、Akka-HTTPアプリの前にNginxを立てています。
  • yamlはこのような感じになりました。
---
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デプロイが適しそうな場面があれば、積極的に利用していきたいです。