/

今回、ネットワークの設計を行ったえると(@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の利活用についてスポットを当て紹介します。

 /

Q1

IPsec パラメータ「IPsecプロトコル」「暗号化」「認証」「Diffie-Hellman グループ」の4つを1番セキュアな組み合わせになるよう選択ください。

IPsecプロトコル

  • A: AH
  • B: ESP

暗号化

  • C: DES
  • D: 3DES
  • E: AES

認証

  • F: MD5
  • G: SHA

Diffie-Hellman グループ

  • H: 1
  • I: 2
  • J: 5

問題解説

「IPsecプロトコル」

AHは「Authentication Header」の略であり、認証機能を持っています。ESPは「Encapsulated Security Payload」の略であり、ペイロード部に対して暗号化を行うことができます。よって暗号化が行われるESPの「B」が正答となります。

「暗号化」

「電子政府における調達のために参照すべき暗号のリスト」*1より共通鍵暗号を抜粋

分類暗号技術
64 ビットブロック暗号該当なし
128 ビットブロック暗号AES , Camellia
ストリーム暗号KCipher-2

よって選択肢内にある「E」が正答となります。

「認証」

「暗号の危殆化に関する調査 報告書」*2の報告書に

ハッシュ関数においては、MD5 が既に危殆化している状態であると専門家の間では認識されおり、数分程度の探索でコリジョンが発見できるとの報告がなされている。

等の記載からSHAの「G」が正答となります。

「Diffie-Hellman グループ」

グループID鍵長
1768 ビット
21024 ビット
51536 ビット

上の表より共有鍵が最長となる5の「J」が正答となります。

Q2

正しいMTUの計算を選択ください。なお環境として「NGN_PPPoE」「L2TPv3」「IPsec ( プロトコルESP、暗号AES256、認証SHA-1 ) 」を使用しているものとします。

  • A: 1366
  • B: 1296
  • C: 1336
  • D: 1454

問題解説

PPPoE MTU1454
IP header (IPsec)20
SPI4
Sequence Number4
初期化ベクトル (AES 256)16
ESP 認証データ (SHA 1)12
=小計1398
=1398 以下で最大の 16 の倍数1392
Padding 長1
プロトコル (次ヘッダ)1
=小計1390
IP header (L2TPv3)20
UDP (L2TPv3)8
L2TPv312
Ethernet14
=L2TPv3 MTU1336

上の表より1336byteの「c」が正答となります。

Q3

インターネットVPNを実現する技術であり、楕円曲線DHを用いて鍵交換を行い、Linux Kernelのメインラインにマージされることが決定したものは以下のうちどれか。

  • A: IPSec
  • B: OpenVPN
  • C: WireGuard
  • D: VXLAN

問題解説

正答はC: WireGuardです。

IPSec, OpenVPNは共に楕円曲線DHを用いて鍵交換を行いますが、Linuxのメインカーネルにマージされておらず、利用する場合にはカーネルモジュールに追加するなどの追加作業が必要となります。

VXLANはL2ネットワークを延伸するためのプロトコルであり、DH鍵交換を行いません。

Q4

4G/LTEパケット交換網において、ユーザプレーンとコントロールプレーンで利用されるトンネリングプロトコルの組み合わせとして正しいものは次のうちどれか。

  • A: ユーザプレーン: GTPv0-U コントロールプレーン: GTPv1-C
  • B: ユーザプレーン: GTPv0-U コントロールプレーン: GTPv2-C
  • C: ユーザプレーン: GTPv1-U コントロールプレーン: GTPv1-C
  • D: ユーザプレーン: GTPv1-U コントロールプレーン: GTPv2-C

問題解説

正答はD: ユーザプレーン: GTPv1-U コントロールプレーン: GTPv2-Cです。

GTPv0-UとGTPv1-Cはそれぞれ3G時代に利用されていたユーザプレーンとコントロールプレーンのプロトコルであり、4G/LTE時代に利用されていたものとは別のプロトコルです。

ユーザプレーンとコントロールプレーンそれぞれが正しく4G/LTE時代に利用されているDが正解となります。

 /

問題文

あなたはローカルネットワーク上にwebサーバを構築し、IPv6アドレスを使用してwebページに接続できるようにセットアップをしています。
webページへはhttp://nginx.icttoracon.netでアクセスできるようにしたいです。

nginxのホストには、すでにnginxのパッケージをインストール済みです。
CSR1000Vとnginxのホストには固定でIPv6アドレスを割り当てました。
クライアント(VNC Server)にIPv6アドレスが自動設定されるように、CSR1000VにはSLAACの設定を行いました。

しかし、クライアント(VNC Server)のブラウザからhttp://nginx.icttoracon.netにアクセスしてもWelcome to Nginxのページを表示させることができません。
このトラブルを解決し、Welcome to Nginxのページを表示させてください。

クライアントが増えても自動でアクセスできるよう、設定変更はCSR1000Vとnginxホストのみとしてください。
DNSサーバはCSR1000Vを使用します。
各ノードにはssh/telnet用にIPv4アドレスが設定されていますので必要に応じて使用してください。
予選終了後に実環境で採点されるので、スコアサーバでの解答は不要です。

file

接続情報

HostProtocolIPv4 addressUser/Pass
CSR1000Vtelnet192.168.0.1admin/admin
nginxssh192.168.1.2admin/admin

ゴール

VNCサーバのブラウザからhttp://nginx.icttoracon.netでWelcome to Nginxのサイトが表示されること
上記のアクセスがIPv6で行われていること
(恒久的な設定でなくても構わない)

問題解説

本問題には3つの原因があります。
順を追って調べてみましょう。

疎通性確認

まずはクライアントマシンからnginxホストまでIPv6で疎通性があるか確認してみます。
簡単な確認ではありますが、トラブル原因のレイヤをある程度限定できます。

[email protected]:~$ ping fc01::2 -c 4
PING fc01::2(fc01::2) 56 data bytes
64 bytes from fc01::2: icmp_seq=1 ttl=63 time=0.887 ms
64 bytes from fc01::2: icmp_seq=2 ttl=63 time=0.607 ms
64 bytes from fc01::2: icmp_seq=3 ttl=63 time=0.802 ms
64 bytes from fc01::2: icmp_seq=4 ttl=63 time=0.699 ms

--- fc01::2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3040ms
rtt min/avg/max/mdev = 0.607/0.748/0.887/0.110 ms

コマンドの結果から本問題は初期状態でIPv6の疎通性があることが確認できます。

名前解決

名前解決ができるかどうか試してみましょう。
ドメイン名からIPv6アドレスを取得するにはAAAAレコードを参照します。
例としてAAAAレコードを取得するコマンドを以下に示します。

[email protected]:~$ dig nginx.icttoracon.net AAAA

; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> nginx.icttoracon.net AAAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 40684
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;nginx.icttoracon.net.        IN  AAAA

;; Query time: 89 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Wed Dec 04 22:46:46 JST 2019
;; MSG SIZE  rcvd: 49
[email protected]:~$

AAAAレコードは取得できていません。
問題文でDNSサーバはCSRとされていますが、クライアントはどこを参照しているのでしょうか。

[email protected]:~$ cat /etc/resolv.conf | grep -v "^#"

nameserver 127.0.0.53
options edns0
search localdomain
[email protected]:~$ cat /run/systemd/resolve/resolv.conf | grep -v "^#"

nameserver 133.242.0.3
nameserver 133.242.0.4
search localdomain
[email protected]:~$ 

IPv4でDNSサーバを受け取っているようですが、CSRのIPアドレスではありません。
1つ目の原因はDNSサーバ(CSR)が参照できていないことです。

問題文からクライアントの設定変更ではなくルータの設定変更で対応する方針であることがわかります。
クライアントの設定とCSRの設定を確認すると、クライアントのIPv6アドレスはRAを用いた自動設定であることがわかります。
ただしDNSサーバのアドレスが配布されていません。
RAでIPv6アドレスが設定されている場合は、以下の2つの方法でDNSサーバを配布することができます。

  • ステートレスDHCPv6
  • RAを用いたDNS配布(RFC8106)

例としてRAのみでDNSの配布を行います。
IOS-XEのコマンドリファレンスを参照すると、以下の設定でDNSの配布が行えそうです。

csr1000v#conf t
Enter configuration commands, one per line.  End with CNTL/Z.
csr1000v(config)#int gi 1
csr1000v(config-if)#ipv6 nd ra dns server fc00::1

クライアントで確認してみます。

[email protected]:~$ cat /run/systemd/resolve/resolv.conf | grep -v "^#"

nameserver 133.242.0.3
nameserver 133.242.0.4
nameserver fc00::1
search localdomain
[email protected]:~$ dig nginx.icttoracon.net AAAA

; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> nginx.icttoracon.net AAAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35863
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;nginx.icttoracon.net.        IN  AAAA

;; ANSWER SECTION:
nginx.icttoracon.net.    10  IN  AAAA    fc01::2

;; Query time: 12 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Wed Dec 04 22:51:49 JST 2019
;; MSG SIZE  rcvd: 77

[email protected]:~$

DNSサーバとしてfc00::1が設定され、AAAAレコードが正しく参照できています。

nginx設定

IPv6の疎通性があり、名前解決も行えているので一旦クライアントの作業を終え、nginxホストを確認してみます。
まずは80ポートの使用状況を確認してみます。

[[email protected] ~]$ sudo lsof -i:80
COMMAND  PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   1421  root    6u  IPv4   9815      0t0  TCP *:http (LISTEN)
nginx   1422 nginx    6u  IPv4   9815      0t0  TCP *:http (LISTEN)

typeを見るとIPv4となっており、nginxがIPv6アドレスで待ち受けていないことがわかります。
2つ目の原因はnginxはIPv6アドレスで待ち受けていないことです。
nginxがIPv6アドレスで待ち受けるよう、設定を変更します。

server {
    listen       80;
+   listen       [::]:80;
    server_name  localhost;

--- snip ---
[[email protected] ~]$ sudo nginx -s reload
[[email protected] ~]$ sudo nginx -s reload
[[email protected] ~]$ sudo lsof -i:80
COMMAND  PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   1421  root    6u  IPv4   9815      0t0  TCP *:http (LISTEN)
nginx   1421  root   10u  IPv6  10932      0t0  TCP *:http (LISTEN)
nginx   1540 nginx    6u  IPv4   9815      0t0  TCP *:http (LISTEN)
nginx   1540 nginx   10u  IPv6  10932      0t0  TCP *:http (LISTEN)

フィルタリング設定

nginxがIPv6で待ち受ける状態となりました。
しかしまだクライアントからアクセスができません。
nginxホストのディストリビューションを確認してみます。

[[email protected] ~]$ ls /etc | grep release
centos-release
redhat-release
system-release
system-release-cpe
[[email protected] ~]$ cat /etc/centos-release 
CentOS release 6.10 (Final)
[[email protected] ~]$

CentOS6.10であるため、フィルタリングはiptablesで行っていると予想されます。
iptablesのルールを確認してみましょう。

[[email protected] ~]$ sudo iptables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             anywhere            state RELATED,ESTABLISHED 
ACCEPT     icmp --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     tcp  --  anywhere             anywhere            state NEW tcp dpt:ssh 
ACCEPT     tcp  --  anywhere             anywhere            state NEW tcp dpt:http 
REJECT     all  --  anywhere             anywhere            reject-with icmp-host-prohibited 

一見すると問題が無いように見えますが、クライアントはWelcome to Nginxのページにアクセスできません。
それもそのはず、iptablesはIPv4のフィルタリング設定だからです。
実は初期状態からIPv4で80ポートは許可されており、クライアントはIPv4を用いてWelcome to Nginxのページを表示させることはできてました。

IPv6のフィルタリングはip6tableで行います。
ip6tableのルールを確認すると、80ポートのアクセスを許可しているルールが無いことがわかります。

[[email protected] ~]$ sudo ip6tables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all      anywhere             anywhere            state RELATED,ESTABLISHED 
ACCEPT     ipv6-icmp    anywhere             anywhere            
ACCEPT     all      anywhere             anywhere            
ACCEPT     udp      anywhere             fe80::/64           state NEW udp dpt:dhcpv6-client 
ACCEPT     tcp      anywhere             anywhere            state NEW tcp dpt:ssh 
REJECT     all      anywhere             anywhere            reject-with icmp6-adm-prohibited 

3つ目の原因はIPv6の80ポートが拒否されていることです。
問題文には恒久的な設定ではなくて構わないとしか記載されていないので、80ポートを許可する方法か、プロセスを停止する方法があります。
問題としてはどちらで行ってもいいですが、望ましいのは80ポートを許可する方法です。
ip6tablesで80ポートを許可します。

[[email protected] ~]$ sudo ip6tables -I INPUT 6 -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
[[email protected] ~]$ sudo ip6tables -I INPUT 6 -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
[[email protected] ~]$ sudo ip6tables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all      anywhere             anywhere            state RELATED,ESTABLISHED 
ACCEPT     ipv6-icmp    anywhere             anywhere            
ACCEPT     all      anywhere             anywhere            
ACCEPT     udp      anywhere             fe80::/64           state NEW udp dpt:dhcpv6-client 
ACCEPT     tcp      anywhere             anywhere            state NEW tcp dpt:ssh 
ACCEPT     tcp      anywhere             anywhere            state NEW tcp dpt:http 
REJECT     all      anywhere             anywhere            reject-with icmp6-adm-prohibited 

クライアントでアクセスしてみると、Welcome to Nginxのページが表示されます。