/
カテゴリー

ICTSC2020 バックボーン解説

今回のインフラリーダーを務めさせていただいたえると(@proelbtn)です。

1年間ICTSCに参加していただいた参加者の皆さん、大変ありがとうございました。今回のコンテストも楽しんでいただけましたでしょうか?

また、ICTSCに協力していただいた企業・団体の皆様、誠にありがとうございました。おかげさまで会期中大きなトラブルもなく大会を開催することが出来たと思います。

以下に、今回のバックボーンについてのスライドを示します。このスライドは会期中に発表したものになります。

簡単な解説は会期中にスライドで紹介しましたが、時間が限られた中での発表のため、その中では伝えきれなかったことも多くあります。また、本戦に参加していない方々にもこのブログを通してネットワークやサーバなどのインフラ技術について興味を持ってほしいと思っています。

それぞれ、担当者が記事を書いているため、以下にそれぞれの記事へのリンクを示します。

今年度も長時間問題を解くのが不可能になるようなトラブルを引き起こすことなく、インフラを提供することができたのではないかなと思います。改めて、参加していただいた参加者の皆さん、機材・会場等を提供していただいた企業・団体の皆さん、ありがとうございました。

今回でICTSCは最終回になってしまいますが、またどこかで参加者の皆さんとお会いできる日を楽しみにしています!

 /

humstackについて

https://github.com/ophum/humstack

主な仕組み

今回作成したhumstackはkubernetesのようにリソース情報を保存するapiserverとそのapiで取得する情報を元にリソースを展開するagent(kubernetesで言うところのcontrollerやoperator)によって構成されています。
humstackでは主に以下のagentが動作しています。

  • VirtualMachineAgent
    • 仮想マシンを作成・削除します。
  • BlockStorageAgent
    • 仮想ディスクを作成・削除します。
  • NodeNetworkAgent
    • 仮想ネットワーク(Linux Bridgeやvlanインターフェース)を作成・削除します。

humstack dashboardについて

https://github.com/ophum/humstack-dashboard
humstackでは操作する方法としてWebUIであるhumstack dashboardを容易し問題の作成に利用しました。

UIから問題で使用するVMやディスクネットワークを定義できます。

dashboardでは、問題の作成だけでなくチーム毎の展開を行うことが出来ます。
上記の定義を元に各チーム毎に展開できます。ICTSCではノードの障害が起きたときに公平性が損なわれる可能性を減らすため、1つの問題は1つのノード上で動作させます。そのため、dashboardでは展開先のノードを指定できるようにしています。

humstackのCeph利用について

qemuの起動オプションで以下の指定をすることで、rbdのディスクを接続することが出来ます。

-drive file=rbd:image-name

ceph/go-cephを利用してcephイメージの作成・削除を実装しました。
問題VMの仮想ディスクのベースイメージ(問題を展開する際に元にするイメージ)化を行う際にスナップショットを作成し、スナップショットを利用して問題の仮想ディスクを作成することでディスク使用量を減らすことが出来ました。
@onokatioがhumstackにスナップショットの作成などの機能を実装してくれました。

ベースイメージが5GB, 5VMの場合
※ αは起動後に増える使用量

1チーム使用量20チーム使用量
スナップショット未使用25GB + α500GB + α
スナップショット使用25GB + α25GB + α

Cephクラスタについて

分散ストレージの一種で、複数のマシンでストレージデバイスを管理し、多重に保管することでデータの完全性を高めることができます。
今回は問題VMのブロックストレージを保管する目的で使用しました。

Cephの特徴

  • 各HDD/SSDにOSDと呼ばれるサービスが動作し、モニターと呼ばれるサービスがどのOSDにどのデータが置いてあるかを管理する
  • モニターは冗長構成のためある程度生き残っていれば読み書きに問題がない
  • データはデフォルトで3OSDに複製されるので、ある程度生き残っていれば読み書きに問題がない
  • NFSやS3互換API、RBDなどのインターフェイスでデータを操作できる

使われ方

humstackの章で先述したように、「RBD」と呼ばれる、仮想的なブロックストレージを複数作りCephに保存できる仕組みを採用しました。

またスナップショット機能と呼ばれる、親となるブロックストレージからCopy on Write方式で子となるブロックストレージを生成できる機能を利用しました。データ変更・削除のみが子に保存され、それ以外のデータ読み込みは親へとパススルーされます。

これにより、問題のブロックストレージを各チーム分コピーしても、物理ディスク使用量は数倍にはならず、実際には各チームごとの変化分のみデータが増加する程度に抑えられました。
今までICTSCでは電源やメモリ、ストレージが足りない問題が多々ありましたが、今回はCephスナップショットの採用で大変余裕を持ったストレージ設計が行えました。

構成

以下の構成を行いました。

  • SSD 1TB x 6個(それぞれ別サーバーに配置)
    • 主にVMのブロックストレージ系をこのSSDからなるプールに保管しました
  • HDD 2TB x 3個(それぞれ別サーバーに配置)
    • バックアップや雑多な目的に利用しました
  • モニター x 8サービス(それぞれ別サーバーに配置)
    • 意思決定には奇数台である必要があるため、実際には常に1台はスタンバイ状態でした。
  • マネージャー x 8サービス(それぞれ別サーバーに配置)
    • OSDやモニターの死活監視、スケール、更新や、ダッシュボードを提供していました。

性能

ホットステージ期間での負荷テストでは、CPUとストレージ帯域を100%まで使い切る様子が確認できました。
(正確なメトリクスが残っていないので概算ですが)SSDのみのプールの場合、最大IOPS・最大速度がSSD一つとより少し低いぐらいの性能が出ています。
各OSDへのレプリカがライトバック方式で行われていること、ネットワークが10Gでありボトルネックにならなかったことなどが起因して、出せる最大スペックでのストレージ性能が出ていたと考えられます。
結果的に、問題VMの全展開も数分程度に収まるようになりました。

また、ブロックストレージ以外にも雑多なデータ保管にCephを使いました。その際にはCephをLinuxにファイルシステムとしてマウントして利用しましたが、ローカルのストレージと同じ様に振る舞い、ファイルの故障なども起こりませんでした。

評価

構築中には様々な障害がありましたが、本戦期間中に大規模な問題なども起こらず、結果としてCephをストレージとして採用して成功たったと思います。
正直ここまでの速度が出るとは思わなかったので、予想以上の働きをしてくれました。

 /

今回、ネットワークの設計を行ったえると(@proelbtn)です。

今回のトポロジ

今回は、以下のような構成でネットワークを構築しました。

今回の構成

今回、ネットワーク側では、以下のような試みをしました。

  1. 対外接続冗長化 + フルルート受信した
  2. サーバとコアスイッチ間を10Gbpsで接続した
  3. NAVT用にP4スイッチを用意した
  4. VPN接続にWireGuardを利用した

この章では、これらの試みについて簡単に説明をしていきたいと思います。

1. 対外接続冗長化 + フルルート受信

今回もコロナウイルスの影響により、オンラインで本戦が開催されるだろうという話がありました。そのため、トランジットを提供してくださっているHomeNOCの東京・大阪の2拠点と接続を行いました。

東京回線の方は、NGN網内折返し通信 + EtherIPを利用して東京の拠点であるPOP03と接続を行いました。大阪回線の方は、NGN網内だけで到達することができないため、プロバイダーを経由して大阪の拠点であるNOC51とIPIPトンネルを張り接続を行いました。そのため、東京回線のLocal Preference値をデフォルトの100から200にして、パケットを送出する際は出来る限り東京回線を経由して通信を流すように設定しました。

実際に、パケットの流量を確認してみると、大阪回線の方のTXはほとんどないことが分かります。少しだけ通信を行っているのはBGPの通信が発生しているためだと考えられます。また、東京回線・大阪回線共に、2日目のRXが大きくなっていることが分かります。これらの詳しい理由については分かっていませんが、トラコンでは2日間で出題する問題が異なるため、このような傾向が見られたのではないかなと考えています。

2. サーバとコアスイッチ間を10Gbpsで接続した

今回は仮想化基盤側でCeph RBDを利用してVMのブロックストレージを用意するという試みを行いました。ネットワーク通信の部分がディスクのR/Wのボトルネックにならないようにするために、全てのサーバ間を10Gbpsで接続しました。昨年は、サーバー数が多かったため、一部のVMのみ10GbpsのNICを搭載させましたが、今年は高密度に問題VMを展開したため、全サーバーに10GbpsのNICを搭載して接続することができました。

実際に、問題VMを展開してみると、最大で400MB/s程度の帯域を利用していることが分かりました。

3. NAVT用にP4スイッチを用意した

今回NAVTの実装を行った @takemioIOです。
ICTSCにおいて問題VMの展開を容易にするためにNAVTと呼ばれるアドレス変換の仕組みを導入しています。Network Address Vlan Translation (NAVT)はアドレス変換(NAT)を行う際にvlanをkeyにしたアドレス変換のことを指しており、具体的にはinside側のアドレスvlanキーにしてoutside側から見たときにはvlan idをipアドレスに含ませて一意にステートレス変換をすることができる技術です。

e.g. VID:100, 192.168.0.1(inside) <=> 10.1.0.1 (outside)

これらを利用することで内部側で利用するアドレス空間はvlan idが異なるだけで全て同じアドレスを利用することができます。それによってコンテストにおいて、vlan idを参加チームごとに割り当てることでアドレス環境を全てのチームすることができ環境の差異をなくすことができる嬉しさがあります。

今回はP4と呼ばれる言語で実装を定義し、Tofinoと呼ばれるASICを利用してNAVTを作りました。またこれを導入したことでトラコン初の100Gbps接続を達成しました。

簡単に実装および利用した感想を述べます。
P4が動くソフトウェアスイッチの実装としてBMv2と呼ばれるOSSで公開されているリファレンス実装があり、それターゲットに事前に実装を行い、それをTofino向けに移植する形で実装を行いました。

BMv2向けに動作ができる拙作の実装がこちらになります https://github.com/takehaya/p4-navt
残念ながらTofino向けの実装したものはライセンスの都合上公開することができませんが、P4がどのようなものなのかを理解するには十分なリファレンスになると思います。

さて、この移植がかなり大変でした。まずASICごとにP4で作られるアーキテクチャが異なります。パケット処理なのでパイプラインというものがありますがその定義や変数の取り扱いなどはいくつも異なるのです。

皆さんになじみやすい例え方としてはウェブアプリケーションのフレームに似ているかもしれません。例えばRailsなど有名なものが大体MVCを基礎として作られていますがクラスの作り方やバリデーションには細かい差異があると思います。P4でも同じように大枠の仕組みは同じですがアーキテクチャと呼ばれる構造が微妙に異なります。今回はv1modelというBMv2に採用されているアーキテクチャとTofino Native Architecture(TNA)と呼ばれるTofinoに採用されているアーキテクチャが異なる部分になり大変だった理由の一つでした。今回はそれを上手いこと実装で違いを補わなくてはいけない訳です。

ちなみにですが TNAについての定義ファイルは https://github.com/barefootnetworks/Open-Tofino に公開されているのでこちらを参照すると雰囲気が伝わります。

さらには表現力も異なります。例えばbitshiftが事実上Tofinoだとまともに使いこなせないのでできるだけTableを使ったシンプルな構成で実装をすると移植がしやすかったなと思いました。
またP4Runtimeと呼ばれるCPU処理に移譲するインターフェースがあるのですがそれを使ってARPリプライなどをBMv2ターゲットでは用意していました。しかしTofinoターゲットでは別のRuntimeが存在しそちらでなければ動かない機能などがあり完全にAPIのインターフェースごと変える必要が出てしまったために今回の対応スコープからは一旦落とすことにしました。

デバッグを行う際も大変でした。まず実機ではASICを通して処理をする故にtcpdumpが使えない訳です。またエミュレーターが提供されているのでそれで通信を眺めようとすると生パケットをコンソールにただ出してくるので目でhexを見ることになり一時期はL3までを目が慣れてしまい目でデコードしていました…

と、まだまだ辛かった話は存在するのですが一方使いこなすと非常にお手軽に100Gbps以上の性能をソフトウェアで定義しつつ引き出すことができるのは大変素晴らしいと思いました。実際XDP, DPDKなどではIRQ, CPUアフィニティを意識したりCPUコアを増やしてRSSを効かせるなどいろんなチューニングを意識する必要があり大変ですが、このような部分を無視して実装を書くことができるのは素晴らしく今後普及していくのだろうなと感じました。
実際利用した側としては安定感ももちろん素晴らしく、ホットステージを含め本戦期間中一度もSwitchが落ちることがなく動作しました。

最後になりますが機材スポンサーとして提供してくださったAPRESIA SYSTEMS様本当にありがとうございました。P4を利用した実装をはじめたいと思う方の参考になれば幸いです。

4. VPN接続にWireGuardを利用した

最後の試みは、VPN接続にWireGuardを利用した点です。ICTSCでは、外部から会場ネットワークに接続するためや監視基盤が動いているさくらのクラウドとのサイト間接続のためにVPNを利用しています。前回は、外部から会場ネットワークに接続するためにはOpenVPNを、さくらのクラウドとのサイト間接続にはIPsecを利用していました。ですが、前回はIPsecの接続に時間がかかりクラウド上の監視基盤がうまく利活用されなかったりという問題がありました。

そのため、今回はVPN接続にWireGuardを利用しました。WireGuardは様々なOS上で実装されているL3VPNの実装で、IPsecを超えるパフォーマンスとOpenVPN並みの接続の容易さを兼ね揃えたVPNの実装です。主に、リモートから作業をする運営用のVPN接続とクラウドに構築されている監視基盤との接続を行うために利用しています。リモートから作業する運営用のVPN接続では、30クライアントほど設定を行っていましたが、通信が遅くて困るということはなく利用できました。

また、クラウドとの接続では、BGP over WireGuardをして監視基盤との接続性を提供しました。監視基盤側では、Kubernetesのtype: LoadBalancerの機能を提供してくれるMetalLBを利用しており、MetalLBが広報している経路が直接会場側で受けていました。実際に、IPsecの代わりに使っても性能的に問題なく、設定も遥かに楽なのでとても良かったと思います。

起きたトラブル

Docker HubのRate Limitに引っかかる

今回、問題の中でDockerイメージをpullするような問題がありました。そのため、多くのチームが docker pull を実行することでrate limitに引っかかってしまったという問題です。

Docker Hubのrate limitはIPアドレスベースでかかるため、参加者ネットワークからインターネットに抜ける通信用のpoolを大きくしてあげることで対処しました。もう少しこの問題が予見されていたら、ローカルにリポジトリを立てて対策をすればよかったですが、完全にrate limitの存在を忘れていました。

以下は、実際にSRXに入れた設定なのですが、頑張ってIPアドレスをかき集めてきた様子が分かると思います。

こんにちは。@takemioIOです。
この記事は ICTSC2020 k8s運用解説、前編:構築と構成の後編 にあたります。
ここでは以前の記事ではk8sを構築したことについてと全体像について述べました。ここではその構築したk8sをどのように利用したのか、それらを通じた知見を述べます。

CDについて

こんにちは。CI/CDに関する部分を担当した sakuraiです
今年はArgoCDHelmfileを利用してクラスタごとにスコアサーバー(コンテストサイト)などをデプロイする運用をしました。

背景

クラスタチームではKubernetesクラスタを3つ運用し、次のような位置づけとしています。

  • wspクラスタ(監視系、ダッシュボードなど)
  • devクラスタ(開発用テスト環境)
  • prdクラスタ(コンテスト参加者へ提供する本番環境)

各クラスタへのアプリケーションのデプロイを行うためには、そのクラスタに対してkubectl apply -f hogehoge.yamlといったコマンドを打つ必要があります。しかし、これを手作業で行うことは

  • 単純に手間
  • 人為的なミスが起こりうる
  • アプリケーション側の人間がクラスタを触る必要がある

ということがあります。そこで、クラスタへのデプロイを自動化したりクラスタチーム以外がk8s上にアプリケーションをデプロイするときの動線を整備したりすることによって、k8sとその上のアプリケーションの運用を継続的に行えることを目指しました。

Helmfile

まず初めに、これまでスクリプトでごり押されていたマニフェスト群をテンプレート化を行いました。テンプレート化にはKustomizeとHelmが候補となりましたが、環境変数の変更のしやすさの観点からHelmを使用することにしました。また、要件としてアプリケーションを各クラスタへデプロイする際、環境変数を変えることでConfigMapやimageを変更できる必要がありました。このテンプレート化では例えば、

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: {{ .Values.__VAR__NAMESPACE }}
...

values.yaml

__VAR__NAMESPACE: "scoreserver"

というようにマニフェストの一部を変数として、その変数に対応する内容を記したファイルを用意することで目的のマニフェストを出力することができます。

# helm template --values values.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: scoreserver
...

さらに、この変数の内容(values.yaml)を各クラスタと紐づけるためにHelmfileを利用しました。HelmfileではHelmのテンプレートに加えて環境を定義することができます。ここでdevelop/productionというように環境を定義し、環境変数も紐づけていきます。

helmfile.yaml

environments:
  workspace:
    values:
    - environment/workspace/values.yaml
  develop:
    values:
    - environment/develop/values.yaml
  production:
    values:
    - environment/production/values.yaml
...

単純な置き換えとしては次のようになります。

helm template --values environment/production/values.yaml

↓

helmfile --environment production template .

Argo CD

クラスタへのアプリケーションデプロイの自動化ツールとして、Argo CDを利用しました。Argo CDはGitHub上のマニフェストを参照して、アプリケーションのデプロイを行うことができます。また、GitHub上でそのマニフェストが変更された場合に自動でデプロイを行うことができます。したがって、Argo CD上でデプロイ設定を行った後はGitHub上でマニフェストを管理することでデプロイを行うことができるため、アプリケーション管理者がクラスタへ触る必要がなくなります。

(Argo CD自体の導入はとても簡単なので内容としては割愛します。GUIで設定できるし適当に使いたいときもおすすめできそう。)

Argo CDはhelmなどのテンプレートエンジンに対応していますが、Helmfileには対応してないためプラグインとして追加します。

deploy.yaml

spec:
  template:
    spec:
      # 1. Define an emptyDir volume which will hold the custom binaries
      volumes:
      - name: custom-tools
        emptyDir: {}
      # 2. Use an init container to download/copy custom binaries into the emptyDir
      initContainers:
      - name: download-tools
        image: alpine:3.8
        command: [sh, -c]
        args:
        - wget -qO /custom-tools/helmfile https://github.com/roboll/helmfile/releases/download/v0.128.0/helmfile_linux_amd64 &amp;&amp; chmod +x /custom-tools/*
        volumeMounts:
        - mountPath: /custom-tools
          name: custom-tools
      containers:
      - name: argocd-repo-server
        volumeMounts:
        - mountPath: /usr/local/bin/helmfile
          name: custom-tools
          subPath: helmfile
kubectl -n argocd patch deploy/argocd-repo-server -p "$(cat deploy.yaml)"

アプリケーション情報の登録はCUIまたはGUIで行うことができるが、こちらもテキストで管理が可能です。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: scoreserver-production
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: production
  source:
    repoURL: /* CENSORED */
    path: scoreserver
    targetRevision: master
    plugin:
      name: helmfile
      env:
        - name: ENV
          value: production
  destination:
    name: kubernetes-prd #ここでクラスタを指定する。Argo CDが動いているクラスタ以外は別途事前登録する必要がある。
    namespace: scoreserver-production 
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Argo CDにはadminユーザがデフォルトで設定されていますが、ユーザの追加を行ったり権限設定を行うことが可能です。(設定方法)
ICTSCではprdクラスタへの変更を制限して、本番環境への破壊や意図しない変更を防ぐようにしています。

Argo CD notifications

デプロイ結果をSlackに通知するためにArgo CD notificationsを使いました。
(比較的最近v1.0~になって変更が辛かったデスネ)

ドキュメントがアレなんですが、最低限以下だけすれば動きそうです。(別途Slack側でOauth tokenの発行は必要)

# kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/v1.0.2/manifests/install.yaml
# kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/v1.0.2/catalog/install.yaml
# kubectl edit cm -n argocd argocd-notifications-cm

data:
  defaultTriggers: |
    - on-deployed
    - on-health-degraded
    - on-sync-failed
    - on-sync-running
    - on-sync-status-unknown
    - on-sync-succeeded
# kubectl edit secret -n argocd argocd-notifications-secret

stringData:
  slack-token: CENSORED

以下のような通知が届くようになります。

監視基盤

こんにちは、監視基盤を担当した梅田@x86takaです。

今回は監視基盤として行ったことを説明します。

まず初めに、今年の監視基盤の構成を説明します。

構成

まず、最初に監視対象です。

監視対象はNTT中央研修センタをメインに行いました。

  • NTT中央研修センタ
    • サーバ
      • S7 * 2
      • m4 * 2
      • RH1288 * 4
    • ネットワーク機器
      • MX5
      • SRX1500
      • SN2410
  • さくらクラウド
    • k8sクラスタ
      • prd クラスタ
      • wsp クラスタ

NTT中央研修センタとさくらクラウドの構成は、前回の記事に詳細にかかれています。

また、これらの監視に利用したコンポーネントは以下の通りです。

  • 分析
    • Elasticsearch
  • データ収集
    • Logstash
    • Prometheus
    • Elastiflow
    • Zabbix
  • 可視化
    • Kibana
    • Grafana
  • その他
    • AlertManager

具体的にこれらのコンポーネントを、どのように利用したのかという話をしたいと思います。

Hardware監視

ここでは、サーバのIPMIから得られるデータやネットワーク機器(SNMP)のデータの監視についてです。

今回は、Zabbixを利用しNTT中央研修センタにある合計11台のホストを監視しました。

使用したテンプレートも含め、以下のスクリーンショットを載せておきます。

IPMIからは、サーバのハードウェアの状態の監視を行いました。

IPMIのデータ取得は、Zabbixのデフォルトの設定では行えません。
ENVにZBX_IPMIPOLLERS=1 のように、0以上の値を設定することにより取得できるようになります。

            - name: ZBX_IPMIPOLLERS
              value: "1"

今回は準備期間中に、HDDの故障が発生したりしていましたので重要な監視になりました。

(Problemのメッセージ)

hardDisk [1] status major

また、ネットワーク機器はSNMPによる監視, メトリクスの取得を行いました。

後述する、GrafanaでZabbixで取得した、対外トラフィックの可視化を行いました。

サービス監視

主にPrometheusを利用し、Grafanaで可視化を行いました。

Prometheus

Prometheusでは Node-expoter, Ceph-expoter, BlackBox-expoterなどを利用しデータの収集を行いました。

Prometheusのデータの永続化についてです。
監視項目が多い場合かなりのストレージを使うため、注意が必要でした。

ICTSC2020の場合、10日間で20GBのストレージを消費しました。

また、PrometheusのDBのサイズが肥大化し起動に時間がかかるようになっていました。
長期の運用を考える場合に考え直さなければならない部分だと思っています。

Deploy

ICTSC2020ではArgoCDによるデプロイを行っています。
しかし、ConfigMapに書かれている設定の変更時にPodの再起動が行われません。

そのため、DeployのアノテーションにConfigMapのhash値をいれておき、ConfigMapに変更があったときのみPodのUpdateが行われるようにしました。

このような形です。

      annotations:
        checksum/config_volume: {{ $.Files.Get "templates/prometheus-configmap.yaml"| sha256sum }}
        checksum/blackbox_volume: {{ $.Files.Get "templates/blackbox_exporter-configmap.yaml"| sha256sum }}
        checksum/ceph_target_volume: {{ $.Files.Get "templates/ceph-exporter-target.yaml"| sha256sum }}
        checksum/node_volume: {{ $.Files.Get "templates/node_exporter-configmap.yaml"| sha256sum }}

Grafana

Grafanaでは、DatasourceにPrometheus, Zabbixを利用し可視化していました。

全体で3つGrafanaが存在したため、Dashboardのjsonをk8sのConfigMapで管理しています。

改善点

ICTSCではConfigMapの定義yamlに、すべてのdashboardのjsonが以下のように一つのファイルに書かれています。

---
apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: grafana-import-dashboards
  namespace: monitoring
data:
  grafana-net-2-dashboard.json: |
    {
      "__inputs": [{
        "name": "DS_PROMETHEUS",
        "label": "Prometheus",
        "description": "",
        "type": "datasource",
        "pluginId": "prometheus",
        "pluginName": "Prometheus"
      }],
      "__requires": [{
        "type": "panel",
        "id": "singlestat",
        "name": "Singlestat",
        "version": ""
      }, {
        "type": "panel",
        "id": "text",
        "name": "Text",
        "version": ""
      }, {
      .........

しかし、この状態だとdashboardの変更した際にConfigMapの定義ファイルとjsonが同じところにあるため書き換えが大変でした。

そのため、jsonとConfigMapの定義を分離することにしました。

apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: grafana-import-dashboards
  namespace: monitoring
data:
  grafana-net-2-dashboard.json: |-
{{ .Files.Get "dashboard/grafana-net-2-dashboard.json" | indent 4}}

templateに置かれているConfigMap定義から、別ディレクトリに置かれているjsonを読み込む形に変更しました。

その際にindent 4 をつけることによって、yamlに読み込まれた際のインデントを気にする必要がなくなります。

  k8s-dashboard.json: |-
{{ .Files.Get "dashboard/k8s-dashboard.json" | indent 4}}

また、サーバの監視にGrafana.comに公開されているdashboardを利用しました。

https://grafana.com/grafana/dashboards/11074

上にある公開されているDashboardの一部において、jsonの定義でConfigMapのサイズ制限を超えてしまう問題が発生しました。
GrafanaのAPIを利用して、Grafana.comから取得したデータのUploadを行い回避を行いました。

grafana_host="http://grafana:3000";
grafana_cred="${GF_ADMIN_USER}:${GF_ADMIN_PASSWORD}";
grafana_datasource="prometheus";
ds=(2842 1860 11074);
for d in "${ds[@]}"; do
  echo -n "Processing $d: ";
  j=$(curl -s -k -u "$grafana_cred" $grafana_host/api/gnet/dashboards/"$d" | jq .json);
  echo "{\"dashboard\":$j,\"overwrite\":true, \
        \"inputs\":[{\"name\":\"DS_PROMETHEUS\",\"type\":\"datasource\", \
        \"pluginId\":\"prometheus\",\"value\":\"$grafana_datasource\"}]}" \
  | curl -s -k -u "$grafana_cred" -XPOST -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    $grafana_host/api/dashboards/import -d "@-" ;
  echo "" ;
done

今回は行いませんでしたが、Grafana.comのようなPrivateなDashbordの公開場所を設けるなど、
k8sのConfigMapに書き込まない方がよいと感じました。

また、以前公開しているPomeriumの記事に関連してGrafanaの認証をGitHubで行えるようにしています。

以下のような設定をgrafana.iniに書くことによってGitHub OAuth2を有効にしました

    [auth.github]
    enabled = true
    allow_sign_up = true
    client_id = {{ .Values.github_clientid }}
    client_secret = {{ .Values.github_client_secret }}
    scopes = user:email,read:org
    auth_url = https://github.com/login/oauth/authorize
    token_url = https://github.com/login/oauth/access_token
    api_url = https://api.github.com/user
    team_ids = xxxxxxx
    allowed_organizations = ictsc

GitHub Organizationで利用している場合、team_idsという設定項目でGitHub Organizationの特定のTeamのみ利用できるといった指定をすることができます。

team_idsというものは、チーム名ではなくGithubAPIから取得できる数字のIDを書かなければなりません。

GitHubのwebからは取得できないため、curlで取得する必要があります。

GrafanaのSession管理

また、Grafanaはセッション管理などにSQLiteを利用しています。

Grafanaをレプリカを行って運用する場合にはDBのLockがかかってしまうことがあります。

具体的に、DBのLockがかかるとGrafanaから突然ログアウトされるような現象が発生します。

そのため、MySQLサーバなどを用意しgrafana.iniで以下のようにデータベース接続の設定を行うことで解消します。
databaseに接続情報を設定し、[session] をmysql変更します。

[database]
    # You can configure the database connection by specifying type, host, name, user and password
    # as seperate properties or as on string using the url propertie.
    # Either "mysql", "postgres" or "sqlite3", it's your choice
    type = mysql
    host = helm-grafana-mysql:3306
    name = grafana
    user = ictsc
    # If the password contains # or ; you have to wrap it with trippel quotes. Ex """#password;"""
    password = hogehoge
[session]
     # Either "memory", "file", "redis", "mysql", "postgres", default is "file"
     provider = mysql 

ネットワーク監視

次は、ネットワーク監視についてです。

主に、DataSourceはSNMP, sflowからデータを取得を行いました。

監視対象は、NTT中央研修センターにあるネットワーク機器です。
繰り返しになりますが、列挙しておきます。

  • ネットワーク機器
    • MX5
    • SRX1500
    • SN2410

これら3台の機器を、ZabbixからSNMPでデータ取得を行いました。

これらの機器のICTSC2020での使用用途を簡単に説明します。

  • MX5
    HomeNOC様とのBGPフルルートを受け取っているルータです。
    2拠点と接続し冗長化を行っています。
  • SRX1500
    MX5の配下に接続されている、Firewallです。
  • SN2410
    サーバ間の通信など、データ通信のコアスイッチとして利用しています。

これらの機器から取得したデータの可視化を行いました。

まず、HomeNOC様との対外トラフィックの可視化についてです。

以下は、取得したデータをZabbixのScreenで表示させたのものです。

今回はGrafanaで様々な監視のdashboardを扱っていますので、GrafanaでZabbixのデータを表示を行います。

GrafanaからZabbixのデータを表示するために、以下のPluginのインストールを行います。
https://grafana.com/grafana/plugins/alexanderzobnin-zabbix-app/

この際、zipファイルからインストール作業をする必要はなく、k8sのマニフェストからENVでインストールするプラグインを指定できます。

          - name: GF_INSTALL_PLUGINS
            value: "alexanderzobnin-zabbix-app"

今回、ZabbixをDatasourceとしたDashboardとして対外トラフィックの可視化を行いました。

以下は、本戦二日間の実際のDashboardになります。

Elastiflow

Elastiflowを利用したflow情報の可視化です。

構築はElastiflowのdocker-composeファイルを参考にk8sのtemplateを書いて構築をしました。
https://github.com/robcowart/elastiflow/blob/master/docker-compose.yml

参考までに、作成したelastiflow-logstash用のファイルを載せておきます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: elastiflow
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: elastiflow
  replicas: 2
  template:
    metadata:
      labels:
        app: elastiflow
    spec:
      containers:
        - name: elastiflow
          image: robcowart/elastiflow-logstash:4.0.1
          env:
            - name: LS_JAVA_OPTS
              value: "-Xms3g -Xmx3g"
            - name: ELASTIFLOW_AGENT_ID
              value: "elastiflow"
            - name: ELASTIFLOW_GEOIP_CACHE_SIZE
              value: "16384"
            - name: ELASTIFLOW_GEOIP_LOOKUP
              value: "true"
            - name: ELASTIFLOW_ASN_LOOKUP
              value: "true"
            - name: ELASTIFLOW_OUI_LOOKUP
              value: "true"
            - name: ELASTIFLOW_POPULATE_LOGS
              value: "true"
            - name: ELASTIFLOW_KEEP_ORIG_DATA
              value: "true"
            - name: ELASTIFLOW_DEFAULT_APPID_SRCTYPE
              value: "__UNKNOWN"
            - name: ELASTIFLOW_RESOLVE_IP2HOST
              value: "true"
            - name: ELASTIFLOW_NAMESERVER
              value: "127.0.0.1"
            - name: ELASTIFLOW_DNS_HIT_CACHE_SIZE
              value: "25000"
            - name: ELASTIFLOW_DNS_HIT_CACHE_TTL
              value: "900"
            - name: ELASTIFLOW_DNS_FAILED_CACHE_SIZE
              value: "75000"
            - name: ELASTIFLOW_DNS_FAILED_CACHE_TTL
              value: "3600"
            - name: ELASTIFLOW_ES_HOST
              value: "elasticsearch:9200"
            - name: ELASTIFLOW_NETFLOW_IPV4_PORT
              value: "2055"
            - name: ELASTIFLOW_NETFLOW_UDP_WORKERS
              value: "4"
            - name: ELASTIFLOW_NETFLOW_UDP_QUEUE_SIZE
              value: "4096"
            - name: ELASTIFLOW_NETFLOW_UDP_RCV_BUFF
              value: "33554432"

            - name: ELASTIFLOW_SFLOW_IPV4_PORT
              value: "6343"
            - name: ELASTIFLOW_SFLOW_UDP_WORKERS
              value: "4"
            - name: ELASTIFLOW_SFLOW_UDP_QUEUE_SIZE
              value: "4096"
            - name: ELASTIFLOW_SFLOW_UDP_RCV_BUFF
              value: "33554432"

            - name: ELASTIFLOW_IPFIX_UDP_IPV4_PORT
              value: "4739"
            - name: ELASTIFLOW_IPFIX_UDP_WORKERS
              value: "2"
            - name: ELASTIFLOW_IPFIX_UDP_QUEUE_SIZE
              value: "4096"
            - name: ELASTIFLOW_IPFIX_UDP_RCV_BUFF
              value: "33554432"
---
apiVersion: v1
kind: Service
metadata:
  name: elastiflow
  namespace: monitoring
  annotations:
    metallb.universe.tf/address-pool: privateIP
spec:
  ports:
    - name: netflow-port
      port: 2055
      protocol: UDP
      targetPort: 2055
    - name: sflow-port
      port: 6343
      protocol: UDP
      targetPort: 6343
    - name: ipfix-port
      port: 4739
      protocol: UDP
      targetPort: 4739
  selector:
    app: elastiflow
  type: LoadBalancer

ElastiflowのDashboardは以下にあるjsonファイルをKibanaからimportを行うことによって、みれるようになります。

https://github.com/robcowart/elastiflow/tree/master/kibana

コンテスト中のflow数はこのような形です。

監視基盤については以上です。

終わりに

今回k8sをどのように利活用したかについて説明しました。これが今回の我々の成果になります!
前編・後編と説明しましたが面白く読んでもらえましたでしょうか?
最後になりますが参加してくださった皆さん、スポンサーとしてリソース提供をしてくださったさくらインターネット様ありがとうございました。

もし今回の記事が皆さんがk8sを運用するにあたっての参考になれば幸いです。

今回のk8sを利用したインフラの取りまとめを担当した@takemioIOです。

みなさん1年間コンテストに参加してくださってありがとうございました。
今回のコンテストも楽しんでいただけたでしょうか。

今回も引き続き機材提供してくださいました企業様、新規で機材提供して頂いた企業様、誠にありがとうございました。お陰様で無事開催ができ、運営学生もインフラ技術を学ぶことができました。特にk8sの担当者としてはさくらインターネット様のクラウドリソースを十二分に使わせていただきましたことを感謝いたします。

今年一年を通じて我々は k8s を利用し、スコアサーバー、監視基盤、問題用VMプロビジョニングツールの運用などを行ってきました。
この記事は我々が行ったk8sに関する利活用を解説する記事となっており、各担当者の寄稿文形式で行います。
まず前編ではネットワークやストレージなどのk8s自体に関する構築と構成について、後編では構築したk8sの利活用についてスポットを当てCDや監視などの運用についてを述べたいと思います。

全体像

このセクションでは今回の全体像について述べます。
我々のk8sはさくらのクラウド上に構築されています。
単一のk8sクラスタの構成と乗っかるコンテンツとしては以下の通りです。

画像に示している様にネットワーク周りではL4LBにMetalLB, L7LBにNginx Ingress Controllerを利用し、ストレージには Rook + Ceph、監視には Prometheus, Grafana, ELK, Elastiflow…etcと言った形で利用していました。

これらは予選と本戦通じて利活用されました。
我々の運用しているk8sクラスタは以下に示す合計3つとなります。

  • prd: 参加者がアクセスするためのスコアサーバーなど絶対に落とせないものを載せるクラスタ
  • dev: 実験のための利用がメインで、カナリアとして利用するためのクラスタ
  • wsp: 監視やツールなどを展開できる実験に巻き込まれるわけにはいかないが prd と共存させたくない時に使うクラスタ

また本戦においては問題が展開されているコアネットワークとVPNを貼る必要があり、以下の様な構成になっていました。この様にすることで本戦においての監視などは全てk8s上に載せることが可能になりました。

以降のセクションではマネージなk8sを運用していれば 全く ハマることがなく関わること少ないような部分だと思いますが、どのように転びながら構築をしたのかという話をします。

ストレージ

このセクションではk8sで利用してるストレージ周りについてを説明します。
今回の問題:Nginxが展開できない でも題材になっていましたが Rook + Ceph を我々は採用をしました。(実はこの問題は我々の構築においてもうっかり消し忘れていたことから起きたものでもありました)

Rookとは OSSのクラウドネイティブなストレージオーケストレーターと言うモノで、ストレージのソフトウェアをスケーリングや監視、スケーリングなどの機能ををk8sのエコシステムに載せることで高い可用性を実現したツールです。

今まで StateFulSet を利用する際に hostPath/data などを書いて適当にマウントする運用をしており、データの管理が大変辛かったと言うのがありました。

例えばStateFulSetを複数利用したいと考えると hostPath を複数書くことになります。その際に新しく追加したいと考えた際には /data/1, /data/2,/data/3…etc とどれがどこで何を使ってるのかわからない悲しいことが起きます。また雑にデータを処分することも叶わず rmコマンドを叩いて苦痛を伴った運用でカバーを行う羽目になっていました。

そこで、Rookを導入することで分散ストレージに加えてk8sでPV/PVCを使える様に我々クラスタにおいても可能になりました!
Cephの採用理由はCephであれば充分に枯れているので信頼性があると言う点やVeleroなどを併用することでDisasterRecoveryやDataMigrationが可能になると言うところや耐障害性を求めることができるのが嬉しい点としてあります。

また単純にRookの現在の安定バージョンのストレージプロバイダーは Cephのみであることからこれを選択することなりました。

Rook + Cephを利用した構築については本家ドキュメントが参考になるので今回は言及しませんのでそちらをご覧ください。

ネットワーク

k8sの運用構築担当の東畑@siberiy4です。 以降の解説記事は私が担当します
ここではk8sがおいてあるVPCの構成や、Metal LBの利用法についてを説明します。
まず、今年度のVM構成としては、

  • KubernetesのAPI用LB 2台
  • Kubernetesのマスター 3台
  • VyOS 1台
  • Kubernetesのノード 3台

となっていました。
API用LBと、Kubernetesのマスター、VyOSは対外疎通できるスイッチに接続しています。
プライベートなスイッチを作成し、KubernetesのノードとVyOSを接続しています。
この状態ではKubernetesのノードが対外疎通できないため、Kubernetesのノードがインターネット通信を行うためにVyOSにはProxy ARPの設定を入れています。

kube-apiserver の冗長

Kubernetesの冗長化のため、Kubernetesのマスターを複数台立てています。
複数のマスターを利用するためには、各マスターに対ししてロードバランスしてあげる必要があります。
そのためにLBとして HAProxyが稼働しているVMを2台用意しています。
2台利用している理由としては、単純にHAPorxyを1台稼働させた場合はSPOFとなってしまいます。
この対策として、2台用意しKeepalivedによってアクティブ・スタンバイ構成にしています。

アプリの外部公開

K8s上のアプリケーションを外部に公開するためのLoadBalancer Serviceを作成するため、MetalLBを利用しています。
昨年度のコンテストではMetalLB をLayer2モードで稼働させていました。
Layer2モードの場合、選出された単一のノードがすべてのトラフィックを受け取ります。
このため、ノードごとに処理負荷の偏りが発生してしまいます。

これを解決するために、MetalLBをBGPモードで利用することにしました。
BGPモードで利用するにはクラスタ外に用意したルーターが必要です。
用意したルーターとk8sの各ノードにあるMetalLBのSpeakerがBGPのピアを張ることで ECMPによるロードバランスができます。
そのため、VyOSを追加しそのVyOSに全てのKubernetesのノードを接続するように変更しました。
以下に 今年のクラウド上のトポロジー図を示します。

image.png

この追加したVyOSを利用して、コンテスト会場と各クラスタでVPN接続を行いました。 VPNソフトとして、Wireguardを利用しています。
これによって、WSPクラスタからすべてのリソースの監視が可能になります。

また、MetalLBでは複数のアドレスプールを用意することができます。
プライベートアドレスのプールを用意することで、コンテスト会場などの運営のみにアプリケーションを公開もできます。
使用するアドレスプールはServiceで指定します。

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    peers:# BGPのピアの設定
    - peer-address: K8S_BGP_ROUTER_IPADDRESS 
      peer-asn: 65020
      my-asn: 65021
    address-pools:
    - name: globalIP
      protocol: bgp
      addresses:# e.g.- 192.0.2.1-192.0.2.254 or 192.0.2.0/24
      - METALLB_ADDRESS_RANGE 
      - METALLB_ADDRESS_RANGE 
    - name: privateIP
      avoid-buggy-ips: true
      protocol: bgp
      addresses:
        - PRIVATE_ADDRESS_RANGE
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: scoreserver
  annotations:
    metallb.universe.tf/address-pool: globalIP
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    targetPort: 80
    protocol: TCP
  - name: https
    port: 443
    targetPort: 443
    protocol: TCP
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

Ingress

SSLの終端やパスベースのルーティングを行うために、Ingressを利用しています。
昨年と同様に、Ingress ControllerとしてNginx Ingress Controllerを 使用しています。

クラスターのプロビジョニング

引き続き東畑が解説をします。

さくらのクラウドにKubernetesのマネージドサービスがないため、VMを作成しKubeadmによってKubernetes クラスタを構築しています。
手順としては、

  1. Terraform でVM作成
    Terraform for さくらのクラウド(v2)を利用してTerraform で各VMを作成しています。
    クラスタ作成時、さくらのクラウドでオブジェクトストレージのサービスが存在しなかったため、minioを稼働させたVMを作成しtfstateを保存しています。
  2. AnsibleでKubeadmのインストール/VyOSのBGPのコンフィグ投入
    Ansible のDynamic Inventory として、minioに保存されたtfstateを解釈しJSONとして出力してくれるPythonスクリプトを利用しています。
    Ansibleのplaybook は二つあり、一つ目はVyOS用です。
    Terraform for さくらのクラウド では複数インターフェースのIP設定はできないため、VyOSのKubernetesのノード側インターフェースにIPを割り当てをしていません。 そのための措置として、Terraform outputにあらかじめ出力してあるVyOS用のIPをインターフェースに設定します。
    また、VyOSにBGPのConfig、MetalLBから広報されたアドレスとKubernetesのノードを対外疎通させるためのProxy ARPなどのネットワーク設定をします。
    二つ目はサーバーのパッケージインストール用です。
    Keepalived、HAProxy、Kubeadmのインストールや、管理用ユーザーの追加を行います。
  3. 手動でKeepalived、HAProxy,kubeadmによるクラスタ構築。
    以降手作業。 Keepalived、HAProxy、kubeadmは設定ファイルのひな型が用意してあり、それらを各VMに適用します。
    あとは、MetalLB, Cert Manager, Nginx Ingress Controller, Argo CDなどをApplyすればKubernetesクラスタの土台が出来上がります。  
    課題としては、terraformの実行からKubernetesクラスタの構築まで2,3時間かかるのを眺める必要がありますのでpackerなどを利用しvmを事前にイメージ化するなどをしておくことを検討すべきだったなと思いました。

終わりに

今回は k8sの全体像と構築にあたっての詳細なエッセンスについて説明しました。

昨年私が一番最初に立てたk8sのクラスタはLBがそもそもL7のみでその単一のhostpathで上手いこと一つのアドレスを利用し複数のアプリケーションを公開していました。そこから振り返ると今年はようやく人が触れても問題なさそうなものになってきたなと思いました。

次回はk8sの利活用についてスポットを当て紹介します。