こんにちは。@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 && 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の利活用についてスポットを当て紹介します。

 /

問題文

顧客が増えてきたため、今まで単一ノードで動作していた通信販売サービスを複数台構成にし、前段に1種類の負荷分散アプライアンスを導入したい。また、成長株なので社長はいくらでも投資してくれると言っている。よって、コストについては考えなくて良い。この時、導入する負荷分散アプライアンスの動作形式として最も適切だと考えられるのは、以下の4つのうちどれか。

トラブルの概要 (必須)

なし

解説

  • 負荷分散サーバにSSLアクセラレータを搭載し、セッション単位で分散を行う。
  • HTTPのcookieをベースにセッション単位で分散を行う。
  • IPアドレスベースでノード単位の分散を行う。
  • SSLセッションIDをベースにセッション単位で分散を行う。
    この4つの選択肢が与えられていました。

まず、通信販売サービスは、HTTPSを用いている可能性が高い、もしくは今後導入する可能性が高いと考えられるため、HTTPのcookieは通常のLBでは触れない可能性が高くなります。SSLセッション毎に分散をすると、サービスのセッションが各サーバで同期されていない場合ユーザ側は頻繁にサービスのセッションが切れてしまう可能性が考えられます。IPアドレスベースで分散するのと、SSLアクセラレータを搭載し、HTTPのcookie等のセッション情報をベースに分散するのでは、コストを考えない場合、SSLアクセラレータを搭載しHTTPのセッション情報をもとに分散するほうがより適切に分散ができると考えられます。

講評

4択問題なので、ほぼすべてのチームが回答していました。実際にはコスト等を考えると、L3やL4LBが現実解だと思います。最近はDPDKを使ってL4LBやるのが流行っているらしいです。

 /

こんにちは,ぱろっくです.

妖精の国 第二のトラブルに関する解説を書かせていただきます.

問題文

国から国への船旅の途中、激しい嵐に遭った。荒れ狂う海の中で波に流され、気がつけば小さな島に漂着していた。

エイト「何とか元の航路に戻りたいけど……船の修理が終わるまでこの島で過ごすことになりそうね」

探索していると、島の中央に神殿があることに気づいた。神殿の柱には見知らぬ文字と美しい模様が刻まれていたが、何を表しているのかはわからなかった。そして、祭壇には大きな黒い箱が安置されていた。

エイト「何だろうこの箱……ってサーバじゃない」

詳しく調べてみると、そのサーバは強大な力を持っていることがわかった。そして、その中には先人の訪れた形跡があった。

エイト「どうやらこのサーバの力を試してみようとしたけど、途中で力尽きてしまったようね。この強大な力を使えれば、魔法の研究や応用が爆発的に発展するに違いないわ! よくわからないけど、私たちで動かしてみましょう」

採点基準

  1. CUDAのシンボリックリンクがcuda-8.0ではなくcuda-7.5にはられているのに気づき,修正してmakeが通るようになっている.
  2. GPUのスレッド数をTitanXの上限まで落としてsuccessと出力できている.

解説

この問題では,近年流行しているGPUプログラミングに関するものでした.普段触ったことのない方には新鮮に感じたと思います.これをきっかけにGPUプログラミングに興味を持ってもらえたら嬉しいです.

今回起きていたトラブルは,まずCUDAを新しいバージョンに入れ替えた際にシンボリックリンクが張り替えられていない問題が発生していました.

makeファイル内で記述されていたコンパイル時の-arch及び-codeコンパイラオプションをTitanX(pascal)に合わせていたのですが,古いバージョンの方にシンボリックリンクがはられたままであったためコンパイルが通りませんでした.

そのため,まずシンボリックリンクを張り替えれば,このトラブルは解決し,コンパイルが通るようになります.

次に,実行ファイルの出力がfailedになってしまうトラブルが起きます.

これは,ファイル内でGPUの1ブロックあたりのスレッド上限数である1024を超えてしまい,処理が正常に行われていないため起こるトラブルです.

そのため,ソースコード内のスレッド数の指定を1024以下に変更するなどの対策をすればトラブルが解決しsuccessと出力されるようになります.

まとめ

以上が本問題の解説となります.

この問題は,さくらインターネット株式会社様の「高火力コンピューティング」サービスから機材提供をしていただきました.ご提供していただき本当にありがとうございました.問題提供していた環境を普段も使いたいという方はこちらをご参照ください.

作成者
麻生情報ビジネス専門学校 何 力平
概要
 RFC 3986では 予約文字(Reserved Character)と非予約文字(Unreserved Character)が定められています。
予約文字:区切り文字などの特定目的で用いるために予約されている文字であり、その目的以外ではURLに使用できません。
ファイル名には次の文字は使えません
: / ?  * < > | \
URLには次の文字は使えません(予約文字)
: / ? * # [ ] @ ! $ & ‘ ( ) + , ; =
パーセントエンコーディング(英: percent-encoding)とは URLにおいて使用できない文字を使う際に行われるエンコード(一種のエスケープ)の名称である。RFC3986のSection 2.1で定義されている。
一般にURLエンコードとも称される。
本問題ではURLにおいて使用できない文字を使い、ダウンロードできないようになっている。
問題文
あなたはABC社のCEです。
お客様から「会社新製品のマニュアルをダウンロードできない」というようなクレームが出ました。
  URL  http://192.168.1.1/product/question#20160227.txt
原因を分析し、その原因を説明する簡単な文章を提出してください。
ダウンロードしたマニュアルも併せて提出してください。
お客様の作業環境:
     Windows7/10+Firefox/Chrome/Iexplorer10/Iexplorer11
解決方法
解決方法は#をパーセントエンコーディングを使い、%23に替えることです。
http://192.168.1.1/product/question%2320160227.txt
講評
本問はネットワーク問題かサーバー問題か分類しにくいだけど、現実でよく出るトラブルです。
RFC 3986では予約文字と非予約文字が定められています。予約文字-区切り文字など特定目的で用いるために予約されている文字で その目的以外ではURLに使用できません。
それでWeb ブラウザが#や!や@などの予約文字があるURLを解読するときに認識ミスを起こしました。
解決方法は#をパーセントエンコーディングを使い、%23に替えることです。パーセントエンコーディングとは URLにおいて使用できない文字を使う際に行われるエンコードの名称です。一般にURLエンコードとも称されます。このような知識は教科書にも載っていないから、選手たちの知識の広さと深さが要求されます。