/

問題文

あなたが所属している会社では、社内専用のWebサイトを別のサーバにリプレイスしました。Webサイトの移行はトラブルなく完了しましたが、後輩が担当していたネットワーク部分が上手く動作せず、社内ネットワークからWebサイトへアクセスできません。自分では手に追えなくなった後輩があなたに泣きついてきました。

「Webサイトには 『 http://192.168.15.125 』 でアクセスするんですが、社内ネットワークからアクセスすることが出来なくて……。多分、ルータのNAT周りの設定がおかしいと思うんですがどこを直したらいいんでしょうか……? 先輩助けてください!」

トラブルの原因を突き止めて、サイトが正常に閲覧できるようにしてください。

情報

  • 使用する手元機材 : AR2050V
  • ユーザー : admin
  • パスワード : IWgrk2Dz
  • 社内ネットワーク
  • IPアドレス:DHCPで配布
  • AR2050VのLANポートに接続すると社内ネットワークに入れる
  • Webサーバ
  • 社内ネットワークからWebサーバへアクセスするためのIPアドレス:192.168.15.125
  • なおWebサーバの本来のホストは192.168.15.125ではなく、192.168.15.32である。
  • Webサーバを操作することはできない

問題に関する禁止事項

  • NATルールを変更しない
  • 物理配線を変更しない
  • IPアドレスを変更しない
  • DHCPを変更しない

報告書を書く際の必須事項

  • 追記・修正するためのコンフィグ
  • 『 http://192.168.15.125 』にアクセスした結果

トラブルの概要

双方向NATをするための以下の要素が正しく設定されていないため、参加者PCからWebサーバへアクセスできません。

  • proxy arp の設定が漏れている
  • zone localの内容が間違っている
  • zone globalの内容が間違っている

解説

Webサーバへアクセスできないのは、双方向NATするための条件が揃っていないことが原因です。

双方向NATとは、1台のNATデバイスで「内部の送信元アドレス変換」と「外部の送信元アドレス変換」を同時に行うNATのことです。双方向にアドレスが変換されることから、内部ホストと外部ホストの双方ともお互いのIPアドレスを知ることなく通信することが可能になります。

双方向NATの条件がそろっていないことが原因ということですが、NATの設定から順を追ってみていこうと思います。

NATの確認

まずはNATの設定を確認しましょう。

今回のNAT設定は以下のようになっていました。

nat
rule 10 masq any from private to global with src global.wan.client
rule 20 portfwd any from private to local.lan.server with dst public.wan.server

今回は、スタティックNATで双方向NATを実現しています。

1行ずつ見ていきましょう。

「内⇒外」方向のスタティックNAT

rule 10 masq any from private to global with src global.wan.client

上記の設定は、

  • rule コマンドで masq アクションを指定されているため、「内⇒外」方向へのスタティックNATである。
  • すべてのアプリケーションの送信元エンティティー「from private」で宛先エンティティー「to global」のとき、送信元IPアドレスを「with src global.wan.client」に変換する。

を表します。

次に、このNATに関するエンティティーの内容を確認します。

zone private
network lan
ip subnet 192.168.15.64/26
host client
ip address 192.168.15.70
ip address 192.168.15.71
ip address 192.168.15.72
ip address 192.168.15.73
ip address 192.168.15.74
ip address 192.168.15.75
ip address 192.168.15.76
ip address 192.168.15.77
ip address 192.168.15.78
ip address 192.168.15.79
ip address 192.168.15.80
!
zone global
network wan
ip subnet 192.168.15.0/24
host client
ip address 192.168.15.125

送信元エンティティーである「private」は、ルータのLANポートに接続したときに降ってくるDHCPが指定されています。

宛先エンティティーである「global」は、NATの設定通りであるとすると、送信元IPアドレスを変換するための条件(ネットワーク定義にWebサーバが属するネットワークアドレス、ホスト定義に特定のipアドレス)が設定されているはずですが、でたらめな条件になっていることが分かります。

よって、このゾーン定義の内容を修正する必要があります。

no zone global
zone global
network wan
ip subnet 192.168.15.0/26
host client
ip address 192.168.15.33

送信元ipアドレスを変換するための「with src global.wan.client」は、show runnning-configを確認すると ip route 192.168.15.33/32 eth1 が設定されているので、192.168.15.33 を指定すると良さそうです。

「外⇒内」方向のスタティックNAT

rule 20 portfwd any from private to local.lan.server with dst public.wan.server

上記の設定は、

  • ruleコマンドで portfwd アクションを指定されているため、「外⇒内」方向へのスタティックNATである。
  • すべてのアプリケーションの送信元エンティティー「from private」で宛先エンティティー「to local.lan.server」のとき、宛先IPアドレスを「with dst public.wan.server」に変換する。

を表します。

次に、このNATに関するエンティティーの内容を確認します。

zone local
network lan
ip subnet 192.168.15.0/24
host server
ip address 192.168.15.33
!
zone private
network lan
ip subnet 192.168.15.64/26
host client
ip address 192.168.15.70
ip address 192.168.15.71
ip address 192.168.15.72
ip address 192.168.15.73
ip address 192.168.15.74
ip address 192.168.15.75
ip address 192.168.15.76
ip address 192.168.15.77
ip address 192.168.15.78
ip address 192.168.15.79
ip address 192.168.15.80
!
zone public
network wan
ip subnet 192.168.15.0/26
host server
ip address 192.168.15.32
!

送信元エンティティーである「private」は、1行目のNATのときと同じなので割愛します。

宛先エンティティーである「local.lan.server」は、問題文に書かれていた、社内ネットワークからWebサーバにアクセスするときのIPアドレス192.168.15.125 が当てはまるはずですが、1行目のNATと同様に、ネットワーク定義もホスト定義もでたらめになっています。

よってこのゾーン定義を修正する必要があります。

no zone local
zone local
network lan
ip subnet 192.168.15.64/26
host server
ip address 192.168.15.125

宛先ipアドレスを変換するための「with dst public.wan.server」は、Webサーバの本来のipアドレスが指定されているので、修正する必要はありません。

代理応答機能を追加する

Firewallはすべてのエンティティー間を許可しているので問題ないため、NAT周りはすべて修正出来ました。

双方向NATでは、ルータ本体に設定されていないIPアドレスを使用しています。

よって、そのIPアドレスに対してルータが代理応答する必要があります。

特定のIPアドレス範囲を代理応答させるためには local-proxy-arp コマンドを用いて設定する必要があります。

しかし、show runnning-configを確認した限り、設定されていないようなので、追加設定をしなければなりません。

local-proxy-arp 192.168.15.125/32
local-proxy-arp 192.168.15.33/32

上記の設定を有効にするためには、該当するインターフェースに対してリミテッドローカルプロキシーARPを有効にする必要がありますが、インターフェースvlan43とインターフェースeth1どちらとも有効になっているので、設定する必要はありません。

回答例

!
local-proxy-arp 192.168.15.125/32
local-proxy-arp 192.168.15.33/32
!
no zone global
zone global
network wan
ip subnet 192.168.15.0/26
host client
ip address 192.168.15.33
!
no zone local
zone local
network lan
ip subnet 192.168.15.64/26
host server
ip address 192.168.15.125
!

採点基準

  • proxy arp の設定が正しく追加されている(30%)(計30%)
  • zone global の設定が正しく修正されている(15%)(計45%)
  • zone local の設定が正しく修正されている(15%)(計60%)
  • client から 192.168.15.125 にブラウザからアクセスするとWebページが閲覧できる(40%)(計100%)

 

 /

問題文

ここに以前新入社員が作ったXenサーバーと、業務での検証用のVMがあります。

今年の新入社員に、Xenサーバーに入っているVMの更新を依頼し、作業を行ってもらっていましたが、

アップデートを行い再起動をしたらVMがインターネットに接続できなくなったと連絡を受けました。

このサーバーは今日は使用しませんが、新入社員の明後日以降の実習環境になるため、それまでに復旧させなければならず、

また、あなたは明日予定があり早く退社しなければならず、

明日の業務終了時間である日曜日の15:00(コンテスト終了時間)までに復旧させる必要があります。

また、新入社員の研修を兼ねているため、メンターであるあなたは今年の新入社員に「なぜダメだったのか」を説明する必要があり、

新入社員の教育につなげる必要があります。

このVMがインターネットに接続できない原因を突き止め、起動させてください。

また、その原因を説明してください。

情報

Xenの動いているサーバー

IPアドレス: 192.168.5.1

ユーザー: admin

パスワード: xenhost

OS: Ubuntu 18.04 ( Xen Hypervisor Kernel )

Xen上のゲストVM

IPアドレス: 192.168.5.100

ユーザー: root

パスワード: なし

OS: ArchLinux ( Para-Virtualization )

SSHサーバーは動作していません。ping疎通ができればOKです。

構成

UbuntuがDom-0として稼働し、ArchLinuxがDom-Uとして稼働していた

解説

この問題はXenサーバのカーネルに原因があり発生していました。
仮想マシンが使用しているカーネルのバージョンと、Xenサーバ側が仮想マシンの起動に指定しているカーネルのバージョンが一致していません。その状態でも仮想マシンの起動はできるのですが、Xenサーバが指定したカーネルのネットワークモジュールが見つからないためNICを認識せず、外部と通信できません。

解決方法は以下の3つがあります。未完

  1. arch.cfgに `bootloader = “pygrub”` を追加しkernel, ramdiskとextraを消してarchを起動
  2. ゲストマシン上で動作しているvmlinuz-linuxとinitramfs-linux.imgをXenサーバにコピーし、それを使って起動させる
  3. Xenサーバのキャッシュから圧縮されたカーネルを解凍しそれを使って起動させる

それぞれについて解説をしていきます。

まず1つ目は 起動する際に使用するDomain-Uの設定ファイルである arch.cfg に以下の操作をします。
bootloader = "pygrub" を追加
– kernel, ramdiskとextraの行を削除
その後、編集後のファイルを使って sudo xl create arch.cfg を実行し仮想マシンを起動します。そうすると起動時に仮想マシンのディスクに設置してあるブートローダーであるgrubの設定ファイルを読み、ゲストの起動時に「ゲストのディスクに実際にインストールされたカーネル」で起動することができます。
その結果、正しいカーネルモジュールを使用することができるようになり、NICが認識され外部と通信できるようになります。

編集前

name = "arch"
kernel = "/guest/vmlinuz-linux"
ramdisk = "/guest/initramfs-linux.img"
extra = "root=UUID=24225d68-7d38-41fa-b8b5-d8b6c7cfa3b9 rw"
memory = 768
hostname = "arch"
disk =  [ "phy:/guest/arch.img,xvda,w" ]
vif = [ 'mac=00:16:3e:00:00:01,bridge=xenbr0,vifname=arch' ]

編集後

name = "arch"
bootloader = "pygrub"
memory = 768
hostname = "arch"
disk =  [ "phy:/guest/arch.img,xvda,w" ]
vif = [ 'mac=00:16:3e:00:00:01,bridge=xenbr0,vifname=arch' ]

2つめは、動作している仮想マシンのディスクをXenサーバ側にマウントし、仮想マシンのカーネルをXenサーバにコピーします。仮想マシンのカーネルを使用して起動することでNICが認識され外部と通信が可能になります。以下のコマンドを順に実行すると、起動した仮想マシンがNICを認識し外部と通信が可能となります。

<br />[Xenサーバ]$ sudo mount -t ext4 -o loop,offset=1048576 /guest/arch.img /mnt

[Xenサーバ]$ sudo cp /mnt/boot/vmlinuz-linux /guest/

[Xenサーバ]$ sudo cp /mnt/boot/initramfs-linux.img /guest/

[Xenサーバ]$ sudo xl create -c arch.cfg

3つめは仮想マシン内にXenサーバが指定しているバージョンのカーネルが残っていました。それを使って起動すると同様にNICが認識され外部と通信が可能になります。以下にカーネルのバージョンを合わせて起動するコマンドを記載します。

<br />[仮想マシン]# pacman -U /var/cache/pacman/pkg/linux-4.19.1.arch1-1-x86_64.pkg.tar.xz

[仮想マシン]# reboot

講評

この問題の解答を提出した8チームのうち完全解答したチームは4チームでした。私の想定では3つの想定解のうち1個目の pygrub で解いて、他の解答はしてこないだろうと思っていたら完全解答した4チーム全てが pygrub を使わずに来たので驚きました。さらに1チームだけ仮想マシン内のキャッシュから起動させていたのでよく気づいたなと思いました。

解答数が少なかったので難しすぎたかなと思いましたが、完全解答したチームはすごいなと思いました。

ちなみに

カーネルのバージョンはfileコマンドで確認することもできます。

 /

問題文

さて、突然ですが超有名大企業ICTSC Comに入社した新入社員のあなたのミッションは、SRv6を使ってHostAからHostBへの通信の際にIDS(Snort)をサービスチェインして通るようにするということを任されました。
しかし現在はSRv6でHostAからHostBへ通信はIDS Nodeを通らずに通信をしてしまっています。
これをIDS Nodeを通ってからHostBに通るようしてほしい。

HostA: 10.0.0.1/24から HostB: 10.0.1.1/24にPingが飛ばせています。
この状態ではIDS Nodeを通らずHostAからHostBへSRv6を使ってルーティングされています。

IDS Nodeではnetwork namespaceで切り分けられたLinuxとSnortが内部に存在します。残念ながらSnortはディフォルトではSRに対応できていないため注意してください。またSnort自身の設定は変えなくても問題ないように設定されています。
そしてちゃんとパケットが流れればSnortのコンソールを確認するとアラートが流れることが確かめることができます。

以下にトポロジーと認証情報を添付するので確認してみてください。

情報

全てのホストは以下情報で入れます
ユーザー: admin
パスワード: hello_5g

以下のIPアドレスはManagementのための接続アドレスです。

Host A

IPアドレス: 192.168.12.6

Router 1

IPアドレス: 192.168.12.1

Router 2

IPアドレス: 192.168.12.2

Router 3

IPアドレス: 192.168.12.3

Router 4

IPアドレス: 192.168.12.4

IDS

IPアドレス: 192.168.12.5

Host B

IPアドレス: 192.168.12.7

構成

問題スタート

HostAからHostBにPingが飛ばせる。この状態ではAからBへSRv6を使ってルーティングされてる

問題ゴール

HostAからIDSを通ってHostBにPingが飛ばせる様になる。
IDSではICMPを認識して検出ができる.

注意事項

  • 必ずSRv6の機能をつかうようにしてください。
  • トラブルはSRだけとは限りません。あらゆる事がありえます。
  • SnortはIDSが立ち上がっている中でnetwork namespace(veth)で区切られている。以下の方法で簡単に確認ができます。

下には便利なチートシートです。

# srextを使うときはこれを実行してからではないと動きません(公式ドキュメントから抜粋)
sudo depmod -a
sudo modprobe srext

# vethにpacketが通ってきているか見たいとき
ip netns exec Snort tcpdump -i veth0-Snort

# Snort側のnamespaceに移る
ip netns exec Snort bash

# Snortでのアラートが見たいとき(exec Snort bashをした後に使える)
Snort -c /etc/Snort/Snort.conf -A console

トラブルの概要

SFCをしたいができていない。

解説

この問題は、2つのIPv4対応ホスト(hostA and hostB)がSegment routing over IPv6 dataplane(SRv6)を介して通信できるようにして、サービスチェインを実装する問題でした。

トポロジーの細かい要素としては以下のようになります。

  • Router
    • R1,R3,R4: SRv6をサポートするルーター
    • R2: v6だけサポートするルーター
  • IDS: namespaceで区切られたSnortが動作しているサーバ

実際にパケットの動きを見ていきましょう。

まず今回の基本設定の状態はAからBへのトラヒックはR1,R2,R3,R4を通じてBへ渡されます。
実際のフローを少し細かく文章化すると以下のようになります。

IPv4のペイロードがHostAからHostBへ送信される

IPv4のペイロードがHostAから送信されます。
ここではSegment routing header(以下SRH)は付与されず、宛先がHostBになっているIPv4パケットとして送信されます。

パケット構成は以下のようになります。

  • Ethernetヘッダ
  • IPv4
    • 送信元アドレス: HostA
    • 送信先アドレス: HostB

HostAから送信されたパケットがR1に到着

R1では以下の作業が行われます。

  • IPv6カプセル化をする
  • 2つのノードを対象にするヘッダであるSegment Routing Header(SRH)を挿入しカプセル化をする

SRHの中にはロケーター情報として、R3,R4を経由するという順序情報と、Segments Left(SL)というSRに対応したルーターを残りいくつ通るかの情報が記載されています。

この作業が終わった後のパケット構成は次のようになります。

  • Ethernetヘッダ
  • IPv6ヘッダ
    • 送信元アドレス: R1::1
    • 宛先アドレス: R3::bb
  • SRH
    • ロケーター情報: R4::bb, R3::bb
    • Segments Left(SL): 1
  • IPv4
    • 送信元アドレス: HostA
    • 送信先アドレス: HostB

上記情報から、IPv6ヘッダの指し示す宛先がR3であるため、R1のルーティングテーブルを見てパケットはR2へと転送されます。

R1から転送されたパケットがR2に到着

R2ではSRv6に対応するための設定は投入していません。
しかし、IPv6の経路情報はR2にインストールされているため、IPv6ルーティングによりR3に転送することができます。

R2から転送されたパケットがR3に到着

R3ではSRv6に対応するための設定が投入されているため、R3::bbに対応する操作を実行し、次の処理を行えるようにするためにEnd動作をします。
End動作により、IPv6ヘッダの値とSRHのSegments leftの値が書き換わります。

  • Ethernetヘッダ
  • IPv6ヘッダ
    • 送信元アドレス: R1::1
    • 宛先アドレス: R4::bb
  • SRH
    • ロケーター情報: R4::bb, R3::bb
    • Segments Left(SL): 0

この情報から、次はR4へとパケットを転送します。

R3から転送されたパケットがR4に到着

R4がSRv6網の終端となります。
この先のネットワークはIPv4ネットワークで、SRv6とIPv6のカプセル化を解除する必要があるため、End.DX4動作をしてカプセル化の解除を行います。

その際、具体的には外部のIPv6ヘッターを除去し、R3でSRv6の設定(function)により決められたIPv4 Next hopへとパケットを転送します。

  • Ethernetヘッダ
  • IPv4
    • 送信元アドレス: HostA
    • 送信先アドレス: HostB

上記の手順により、HostBにPing Requestを送信することができました!

次はPing Replyパケットが送信されるまでのフローを見ていきましょう。

問題出題状態では、R3を通らずに R4 -> R3 -> R1 のようにパケットが転送されるようになっています。

IPv4のペイロードがHostBからHostAへ送信される

IPv4のペイロードがHostBから送信されます。

  • Ethernetヘッダ
  • IPv4
    • 送信元アドレス: HostB
    • 送信先アドレス: HostA

HostBから送信されたパケットがR4に到着

R4では以下の作業が行われます。

  • IPv6カプセル化をする
  • 2つのノードを対象にするヘッダであるSegment Routing Header(SRH)を挿入しカプセル化をする
  • その後のパケットは以下のようになります

    • Ethernetヘッダ
    • IPv6ヘッダ
      • 送信元アドレス: R4::dd
      • 宛先アドレス: R1::1
    • SRH
      • ロケーター情報: R1::1
      • Segments Left(SL): 0
    • IPv4
      • 送信元アドレス: HostB
      • 送信先アドレス: HostA

    上記情報から、IPv6ヘッダの指し示す宛先がR3であるため、R1のルーティングテーブルを見てパケットはR2へと転送されます。

    R4から転送されたパケットがR2に到着

    R2ではSRv6に対応するための設定は投入していません。
    しかし、IPv6の経路情報はR2にインストールされているため、IPv6ルーティングによりR1に転送することができます。

    R2から転送されたパケットがR1に到着

    R1がSRv6網の終端となります。
    この先のネットワークはIPv4ネットワークで、SRv6とIPv6のカプセル化を解除する必要があるため、End.DX4動作をしてカプセル化の解除を行います。

    その際、具体的には外部のIPv6ヘッターを除去し、R3でSRv6の設定(function)により決められたIPv4 Next hopへとパケットを転送します。

    • Ethernetヘッダ
    • IPv4
      • 送信元アドレス: HostB
      • 送信先アドレス: HostA

    ここまでが基本的な状態の設定として問題環境に設定していた状態でした。
    SRv6について理解するためのきっかけになったと思います。

    では実際の問題を解くフェーズに入っていきます。

    今回の問題で行う方策をまとめると、

    • Snortがあるホスト(IDS)を通ってパケットを動かす必要がある
      • しかしSnortはSRv6に対応していない

    ということになります。

    (ちなみに 普通Snortは別ホストじゃないんですか? という質問については、この問題で使用するVMのリソースがコンテストで使用している物理サーバーのうち一台のサーバーをおおむね専有するほど多く、メモリを節約するためにしょうがなく行った苦肉の策でした。
    ですが出題としては全く問題ないのでこのまま出題しました。)

    今回のトポロジーから、Snortがあるホストにパケットを転送させるにはR1で付与するSRHのロケーター情報にR5を追加することで、R5を経由することができるようになります。

    しかし、SnortはSRv6に対応していません。そのため、一度SRv6のカプセリングを外してIPv4のパケットだけに変換する必要があります。

    「SRv6はデータプレーンにロケーション情報等が付与されているだけなので一旦外す」という方法が考えれると思います。
    ちょっと意地悪に見えるかなぁと思い、出題的には # srextを使うときはこれを実行してからではないと動きません(公式ドキュメントから抜粋) と書いておいたコードがありました。
    このコードについてインターネットで調べると、例えば

    いhttps://github.com/netgroup/SRv6-net-prog

    というリンクが見つかり、以下のようなそれっぽいfunctionを見つけることができます。

    • End.AD4: Endpoint to IPv4 SR-unaware APP via dynamic proxy
    • End.EAD4: Extended End.AD4 behavior that allow SR-unaware VNFS to be the last SF in SFC

    このことから、bbの定義を

    srconf localsid add fc00:5::bb end.ad4 ip 192.168.1.2 veth0 veth1

    このようにすることで、無事Snortまで通信が転送されます。Snortから戻ってきたパケットは正常にR4に流れていきます。
    別解としてEnd.EAD4を利用する方法もありますが、こちらでも問題はありません

    具体的な挙動としては、veth0に対して流れてきたパケットのSRHを外して、 IPv4 Payload オンリーにして流してあげるだけです。veth1に戻ってきたら、先程外したSRHを再度付与します。
    これで無事通信をIDSを通じてhostBへ行くことができました!

    問題作成の狙いとお気持ち

    この問題は次世代ネットワークについての知識をつけてほしいということから作りました。そもそもほぼ全員がSRv6ってなんやねんというところからで辛いかなと思ってたんですが、まずは存在を知ってもらって、その上でおもしろいところや有用性を理解してもらえたら嬉しいなと思って作った問題でした。また、狙いとしてSRv6はほぼmininet以外で遊べる環境というものは公開されていないため直接OSに対して書き込むconfigとかがあまり見ることができないというのがあります。なので今回は公開をすることでトラコン参加者以外も幸せになるおもしろい問題になったんじゃないかと思います。

    ちなみにですが実は自動起動の設定にsystemctlを使用したため、問題参加者はそのへんをガサゴソするとconfigがでてきて参考になったかも知れません。
    なおホントはもう少し難しい問題にする予定だったんですが、、、これではあまりにも解けなそうではと言われて変更して、変更したやつも、ちょっとむずかしいのでは?と言われてSRのコマンドだけの修正で解ける問に変更されたので Baby とつけていました。ちょっとpwn系のCTF問題みたいなネームみたいですね。
    それでも難しいと言われたので誰も挑戦してくれないかなとヒヤヒヤしていましたが、いくつかのチームは挑戦してくれたのでホッとしました笑

    回答例

    R1のSegment ID table(SID, つまり通るべき経路の指定)をR3,R5,R4の順番でオーダーする
    ip route add 10.0.1.0/24 encap seg6 mode encap segs fc00:3::bb,fc00:5::bb,fc00:4::bb dev eth1
    IDS NodeのSnortはSRHを処理することができません。よってIPv6 headerを取り外してあげる必要があります。よってサービスチェインのダイナミックプロキシを行う必要があります
    srconf localsid add fc00:5::bb end.ad4 ip 192.168.1.2 veth0 veth1
    ちなみにR1でR3,R4で指定してR3でR5に行くようにoverSRv6を更にしたらいいのではと思うかもしれないですが、拡張カーネルが必要で今回は対応していないため困難です

    これは今回出題したSRv6についての問題を体験できるVagrantFileです。もしよかったらぜひ遊んでみてください!!
    https://github.com/takehaya/SRv6_SFC_Example

    採点基準

    点数となるのはIDSNodeまでの中継するべきノードをSRv6でオーダーされていて50%+その後にダイナミックプロキシされるで満点を与える方針
    そもそもオーダーがない場合などは部分点をつけることはしません

 /

問題文

192.168.19.10のインターフェースから192.168.19.125にICMPパケットが通らないので通してください。

192.168.19.66のインターフェースから192.168.19.140にICMPパケットが通らないので通してください。

※192.168.19.125と192.168.19.140からもICMPパケットを常に送っております。

情報

IPアドレス: 192.168.19.100 ユーザー: admin パスワード: ospf

問題のゴール状態

192.168.19.10のインターフェースから192.168.19.125にICMPパケットが通る。

192.168.19.66のインターフェースから192.168.19.140にICMPパケットが通る。

解説

トポロジーはこのようになっていました。

 

トポロジーを見て貰えれば分かりますが、ゲートウェイがサブネット外にあります。

よってeth1、eth2ともにサブネットを/27にして、eth1ではパケットにvlanタグがついているのでeth1をvlan56のインターフェースにすることで解決する問題でした。

解き方

192.168.19.125への通し方

eth1のインターフェースでsudo tcpdump -ei eth1を行うと、802.1qタグでVLAN 56がついたARP Requestを見ることができます。

そこで/etc/network/interfacesに書いてあるeth1のインターフェースをeth1.56のインターフェースにすることで、リクエストされている192.168.19.10のARPに応えることが可能となります。

しかし、ARPが来ていた192.168.19.30がサブネット外であるため、192.168.19.125から来たICMPパケットをデフォルトゲートウェイであるeth0に流してしまうので、
eth1のサブネットを/28から/27に直し、/etc/network/interfacesに書いてあるStatic routeを反映します。

これで192.168.19.125に対してICMPパケットが通ります。

192.168.19.140への通し方

eth2においてsudo tcpdump -i eth2を行うとICMP リクエストパケットが来ているがリプライを返していないことがわかります。

sudo tcpdump -i eth0 を行うと192.168.19.100であるeth0のインターフェースよりICMPのリプライを返しています。

そこで/etc/network/interfacesに書いてあるStatic routeが反映されているかip routeを確認すると反映されていないことが分かります。

/etc/network/interfacesに書いてあるStatic routeをコマンドで入力するとNetwork Unreachableと出てきます。

ゲートウェイがルーティングテーブル内にないことが分かり、Static routeのゲートウェイのIPアドレスを確認すると、eth2のサブネット外になっていることがわかります。

eth2のL2内にあるゲートウェイのIPアドレスを調べるために送られてきているICMPパケットのMACアドレスを確認し、ARPテーブルを確認するとStatic routeに書いてあるゲートウェイのIPアドレスがあるので、そのアドレスがサブネット内に来るように計算し、設定を行うとICMPが通るようになります。


ちなみにこの問題はパスワードがospfとなっていたが全く関係なく、L2の調査が出来るかどうかを問う問題でした。

 /

本問題は、 iptables の設定を間違えたように見せかけて、運用者が Docker、ないし docker-compose に詳しくなかったためにハマってしまう、というシナリオで出題した問題です。

問題文

あなたの会社では、最近会社のブログを Docker へ移行した。
ブログには WordPress を使っており、DBは MariaDB、そしてその管理用に phpMyAdmin を 8080 番ポートで動作させている。
phpMyAdmin は外部からのアクセスを防ぐため、ローカルホストからのみアクセスを許可するようにしたつもりであったが、どうやら Docker に移行した際にうまく設定が適用されなくなってしまったらしい。

上記の問題を解決してください。

アクセスに必要な情報

IPアドレス: 192.168.20.1 ユーザー: admin パスワード: bloom-into-docker

  • WordPress の管理者ID / パスワード: admin / admin
  • データベースの ID / パスワード: wordpress / wordpress

問題のゴール状態

問題文に示した意図を達成すること

 

解説

情報として与えられたサーバへログインすると、ホームディレクトリに docker-compose.yml が置かれており、問題文で説明されていたアプリケーションが Docker のコンテナオーケストレーションを行う docker-compose で設定されていたことが分かります。
また、ブラウザから 192.168.20.1 にアクセスすると WordPress で構築されたブログが表示され、8080番ポートへアクセスすると phpMyAdmin が表示されるようになっていました。

Docker の動作中コンテナを確認するコマンドである docker ps を叩くと以下のような結果が得られます。 (コンテナIDは異なる場合があります)

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3424d4d54bae wordpress:5.0.3-php7.1-apache "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 0.0.0.0:80->80/tcp wordpress
f195b424d1cf phpmyadmin/phpmyadmin:4.8 "/run.sh supervisord…" 5 minutes ago Up 5 minutes 9000/tcp, 0.0.0.0:8080->80/tcp phpmyadmin
b9c12504b150 mariadb:10.2.22 "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 0.0.0.0:3306->3306/tcp database

この問題のゴール状態は「問題文に示した意図を達成すること」とありますが、意図とはなんでしょうか? それは問題文に示されています。
問題文には「外部からのアクセスを防ぐため、ローカルホストからのみアクセスを許可するようにしたつもりであったが」と書かれています。
意図をエスパーして iptables -L  で iptables のルールを確認してみると、それらしいルールが登録されていることが分かります。 (dpt:http-alt に関連する2行がそうです)

# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:http
ACCEPT     tcp  --  localhost            anywhere             tcp dpt:http-alt
DROP       tcp  --  anywhere             anywhere             tcp dpt:http-alt
ACCEPT     tcp  --  10.0.0.0/8           anywhere             tcp dpt:ssh
ACCEPT     tcp  --  172.16.0.0/12        anywhere             tcp dpt:ssh
ACCEPT     tcp  --  192.168.0.0/16       anywhere             tcp dpt:ssh
DROP       tcp  --  anywhere             anywhere             tcp dpt:ssh

つまり、TCPの8080ポートに対するアクセスは、送信元がlocalhostからのものを許可し、それ以外は拒否するようになっています。
「どうやら Docker に移行した際にうまく設定が適用されなくなってしまった」と問題文にあるとおり、おそらく普通に Apache や nginx のような HTTPサーバをローカルで実行していればこのルールは正しく適用されます。

このルールが意図の通りに動作しないのは Docker 上のコンテナで HTTPサーバが動いていることによるものです。
詳しい説明は参考で示したリンク先を参照して頂きたいのですが、 Docker でコンテナのポートを外部に公開した場合、外部からのアクセスは iptables のDOCKERチェインへジャンプするルールがPREROUTINGチェインに追加されます。
そのため、上記のINPUTチェインに追加された 8080ポート (http-alt) に対するルールは処理されません。

さて、問題の背景が分かったところでこの問題に対処する必要がありますが、実は本問題の解答にあたり、 iptables と格闘する必要はありません。この問題のジャンルが軽量コンテナであることもヒントになっています。

そもそもなぜ外部から TCPの8080ポートへアクセスできるのかといえば、それは外部のインタフェースからのアクセスを受け付けているからに他なりません。

docker ps の結果における PORTS の列に表示されているように、 0.0.0.0:8080->80/tcp というのは、ホストが全てのIPからTCPの8080ポートでアクセスを受け付けることを意味しています。
これを拒否するようにするためには、コンテナが受け付けるIPアドレスを制限すれば良いのです。

docker-compose のコンフィグファイルのドキュメントを参照すれば、 docker-compose.yml"8080:80""127.0.0.1:8080:80" に変更することで問題が解決できることが分かります。

反映は docker-compose up -dで可能ですが、とりあえずホストを再起動しても反映されます。困ったら再起動しましょう。

また、WordPressやデータベースの認証情報はただの罠です。特に意味はありません。

別解

これは意表を突かれたのですが、 phpMyAdmin の Docker イメージは内部で nginx を使っているようで、その nginx のコンフィグを書き換えることで対処してきたチームが1チームいました。
Docker のコンテナのライフタイムや設定の柔軟性を考えるとあまり望ましくはありませんが、大会における条件は満たしていたため満点としました。

また、解決できることは分かっていましたが望ましくない解答として、 iptables と格闘することによる解答方法があります。このときは、以下のDNATのルールに着目する必要があります。

iptables -L DOCKER -t nat
Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere
DNAT       tcp  --  anywhere             anywhere             tcp dpt:mysql to:172.18.0.2:3306
DNAT       tcp  --  anywhere             anywhere             tcp dpt:http-alt to:172.18.0.3:80
DNAT       tcp  --  anywhere             anywhere             tcp dpt:http to:172.18.0.4:80

内部的には、このルールが Docker 内のコンテナへの NAT を実現しているため、このルールが適用される送信元IPをローカルホストへ絞るようにすることで本来望んでいた動作を得ることができます。
具体的な操作は述べませんが、上記の設定を修正した上で、これを永続化するようにすることができれば目的を達成できます。
しかし、 Dockerチェインのiptables のルールの操作は Docker が責任を追っており、これをユーザが弄ることは望ましい操作ではありません。事実、このルールを変更しても再起動後にはDockerのデーモンによりルールが上書きされてしまいます。
大会中の解答では dockerd が iptables を操作しないよう修正し、なんとか再起動後もルールが維持されるようにしたチームが2チームいました。
しかし、これは他のコンテナ等が存在する場合には管理が大変煩雑になるためあまり望ましくありません。

本来であればこのような解答を行うことが望ましくないと分かるよう誘導するべきであり、これは作問を行った私のミスだと考えています。

講評

採点基準として、解法の良し悪しに関係なく、ホストの再起動後も期待した動作を維持したチームには満点の200点、再起動後は期待した動作を維持できなかったチームには100点を与えました。

本戦においては、12チームが想定解法で満点となりました。また、2チームが別解により満点、1チームが別解により100点となりました。

本問題は想定していたよりも正答率がかなり高く、最近の学生は Docker が使えるのだな、と個人的には少し感動しました。
まだまだ Docker や Kubernetes は登場したてではありますが、アンケート等の結果も見ると興味を持って触っている方が多いように思いました。

この講評を書きながら、そういえば私は ICTSC5において  FreeBSD における軽量コンテナシステムである Jail の問題を出題したことを思い出しました。Dockerがここまで流行るとは思ってなかった……
皆さんも FreeBSD、そしてJail を使ってみてください。

参考

パブリックIPを持つサーバでDockerを起動するとportが全開放される問題の対処法 – grep Tips *

記事中の問題とは関係がありませんが、 Docker が iptables へ設定するルール等が詳しく説明されています。

また、出題した環境で用いられていた docker-compose.yml および永続化されていた iptables コンフィグ (/etc/iptables/rules.v4) はこちらです。 https://gist.github.com/kyontan/d96a6afbaae615b1b156844a1be261a1
問題環境を再現するためには Ubuntu Server 18.04 において netfilter-persistent および iptables-persistent を導入し、上記の iptables のルールを読み込んだ上で docker-compose up -d でコンテナを起動してもらえればと思います。

 /

問題文

日本拠点とベネチア拠点間にGREトンネルを開通する。
しかし、うまく疎通性の確認が取れない。
あなたは今から日本拠点のトンネル構築をするルータである 892J-A の設定を調査して解決し、何が原因だったのか報告してほしい。

情報

  • 892J-A

ポート: FastEtheret0〜7
ユーザー: admin
パスワード: 1Fa3Iiik

892J-Aのみ操作を行う

問題のゴール状態

PCから192.168.4.255 への疎通性がある

提出すべきもの

  • 設定したコンフィグ
  • 参加者PCから 192.168.4.255 までのトレースルートの結果

構成

禁止行為

  • 物理配線・論理構成(IPアドレス・VRFなど)の変更

トラブルの概要

  • tunnel vrf コマンドを入れていない為、GREトンネルがアップしない
  • +VRF tunnel にスタティックルートを書いてない為、192.168.4.255にPingが飛ばない

解説

今回のトラブルは、この中のGREトンネルのインターフェース(892J-Aのtunnel80)がLinkUpせず、PCから 192.168.4.255 へ疎通できないものでした。

まず、CiscoのGREトンネルの挙動についてのおさらいですが、GREトンネルはそれ自体のIPアドレスとは別にカプセル化したパケットの宛先IPと送信元IPもしくは送信元インターフェースを指定する必要があります。トンネルのインターフェースがLinkUpする条件は、この宛先IPアドレスへの経路がルーティングテーブルにインストールされているかどうかとなります。

これはVRF-Liteと呼ばれるルーティングテーブルを仮想的に分割する技術を使用していても使用することは可能ですが、その場合はカプセル化したパケットの宛先IPアドレスがどのルーティングテーブルに従って送信するかを明示的に指定する必要があります。今回の場合カプセル化したパケットはVRF global に従うように設定する必要があります。

その部分の解答コンフィグを以下に示します。

interface tunnel80
tunnel vrf global
exit

また、トンネルインターフェースとPCを接続するインターフェースが所属するVRF tunnel に、192.168.4.255 への経路が書かれていなかったため、それを手動で追加する必要があります。

その部分の解答コンフィグを以下に示します。

ip route vrf tunnel 192.168.4.255 255.255.255.255 192.168.4.201

以上のコマンドを入力することで、PCから192.168.4.255まで疎通することができるようになります。その時のtracerouteの結果は以下のようになります。

192.168.4.255 へのルートをトレースしています。経由するホップ数は最大 30 です

1 <1 ms <1 ms <1 ms 192.168.4.1
2 <1 ms <1 ms <1 ms 192.168.4.255

トレースを完了しました。

ここまでのコマンドとtracerouteの結果を示すことで、この問題は完答となります。またコマンドはこの通りでなくても同じことができるものであれば得点としています。

回答例

int tunnel 80
tunnel vrf global
exit
ip route vrf tunnel 192.168.4.255 255.255.255.255 192.168.4.201

採点基準

  • tunnel vrf コマンドを正しく入力している。:60%
  • スタティックルートまで正しく入力し、想定通りのtraceroute結果を示している。:満点
 /

問題文

監視サーバを建ててルータの先にある892JをSNMPで監視しようとしたら上手く監視ができない。
監視サーバからSNMPのMiBを取得できるようにしてくれ。

情報

監視サーバのアドレス

http://192.168.2.129:8080
– ユーザー Admin
– パスワード: zabbix

1841-B

ユーザー: admin
パスワード: iK8Cjr3B

892J-A

監視対象IP: 192.168.2.100

問題のゴール状態

ZabbixのUI上でSNMPの監視が行えることを確認する

トラブルの概要

この問題は1841-Bの192.168.2.1のIPアドレスが設定されているインタフェースにSNMPで使用されているポートがaccess-listによって遮断されているトラブルです。

解答例

ACLが設定されているインターフェースには以下のルールが書かれていました。
1841-B(抜粋)

interface FastEthernet0
ip address 192.168.2.1 255.255.255.192
ip access-group 100 in
access-list 100 permit tcp any eq 22 any
access-list 100 permit tcp any eq telnet any
access-list 100 deny udp any eq snmp any
access-list 100 deny udp any eq snmptrap any

Cisco機器のAccess control(ACL)は全ての条件にマッチしなかった場合にパケットを破棄するルール(暗黙のDeny)があるので、access-list 100 denyは書かなくてもDenyになります。
しかし、今回の問題ではあえてトラブルに気づかせる目的で明示的に書いてあります。
ゴールになる解答例はいくつかあります。

  • インタフェースに設定されているACLの設定を消す
interface FastEthernet0
no ip access-group 100 in
  • ACLの設定を「全ての通信を許可」にする
no access-list 100
access-list 100 permit any
  • 現在のACLに加えてSNMPで使うポートを許可する
no access-list 100
access-list 100 permit tcp any eq 22 any
access-list 100 permit tcp any eq telnet any
access-list 100 permit udp any eq snmp any
access-list 100 permit udp any eq snmptrap any

今回は特に制約がなく、Zabbixから監視ができればゴールとしていたためどの方法でも正解としています。

講評

この問題は1次予選・2次予選に引き続いて出した問題です。
既に学ばれている分野もあり正答率は高かったです。

 /

問題文

/ictsc/*/etc/toracon/*を消去してしまった。 それぞれ別の手段でマシンの中にバックアップが取られているらしい。

すぐにデータを復旧してほしい。

情報

  • IPアドレス: 192.168.18.1
  • SSHユーザー: admin
  • SSHパスワード: savefile
パス バックアップツール
/ictsc snapper
/etc/toracon etckeeper

問題のゴール状態

ファイルを復旧し消去してしまう前のパスに配置する

トラブルの概要

異なるツールでバックアップしていたファイルを、それぞれ復元させる問題。

解説

/ictsc2018  

この問題はSnapperとetckeeperという2つのアプリケーションを使ってバックアップを取っていた環境で、そのファイルを誤って削除したため復元する、という問題でした。

/ictsc2018 は、snapperとbtrfs subvolumeでバックアップが取られています。 snapper undochangeを実行するか、/ictsc2018/.snapshot/1/snapshots/以下に実ファイルが存在するので、そこからコピーすることで復元ができます。

/etc/toracon

/etc/toraconは、etckeeperとgitで管理されていました。

etckeeperはGitを用いてファイルのバージョン管理を行っているため、sudo git logsudo git statusでコミットを特定した後、 sudo git checkoutすることで復元できます。
また、etckeeperのコマンドラインを利用してetckeeper vcs checkoutなどすれば、特定のファイルを復元できます。

回答例

「/ictsc2018を復元させるため、snapper status 0..1で変更点を見つけ、snapper undochange 0..1をした」

「/etc/toraconを復元させるため、git showやgit log、git diffを実行してコミットを確認し、git checkout HEAD@DSC_0745.JPGを実行した」

採点基準

  • Snapperのスナップショットから、/ictsc2018/ictsc8-story.mdを復元できる 50%
  • etckeeperのコミットから、/etc/toracon/DSC_0745.JPGを復元できる 50%

※ ictsc8-story.mdはictsc8での語彙設定、画像ファイルは猫の写真

 /

問題文

「時代はコンテナでオンプレだ!」が口癖である高圧的な上司は、最近、グローバルアドレスの持つ自宅のゲートウェイルータからフォワーディングを行うことで自宅サーバを全世界に公開しているみたいです。
ある日、上司は鬼のような形相で、「レンタルサーバーで動いていたDockerコンテナを自宅サーバに移行したらhttpが通らなくなった!pingは通るのにだぞ!!お前Docker詳しいって言ってたよな、サーバにだけsshで入れるようにしてやるから原因を究明して直してくれよ!」とあなたに無茶振りをしてきました。重ねて、「Dockerはどの環境でも動くものだろ!もともと動いてたんだからイメージに修正は加えるな!」とのことです。
上司との会話より、レンタルサーバーから自宅サーバに移行したコンテナは http://hpn.finals.ictsc へのGETリクエストが失敗するとエラー出力がされるものであり、また hpn.finals.ictsc は上司の自宅のゲートウェイルータに振られるグローバルアドレスに解決されることがわかりました。

またこの問題を先輩に相談したところ、先輩はニヤニヤした顔で「これは俺も家でサーバやるときにハマった事あるなぁ・・大きく2通りやり方があるんだが今回はRouterにログインできないから片方しか試せないかな・・・そうだ!明日の15時までに2通りの解決法を思いついた上で問題を解いたら焼き肉に連れてってやるよ!」と言ってきました。

上司の命令、そして焼き肉のために問題を解決してください。

  • 基準点
    • connection-check コンテナが、 connection successd! を返すように修正し、その解決法を上司に報告する。
  • 満点
    • 問題の解決法その2 (Routerにログインできる場合の解法) を述べ、原理を先輩に報告する。

トラブルの概要

内部から、ルータの外部アドレスを宛先とした内部への通信が成功しない

解説

グローバルアドレスを持つルータ (以下 ルータ) の内部のプライベートネットワークに存在するサーバ (以下 内部サーバ) がある環境において、外部からのルータ宛のパケットの宛先IPアドレスを内部サーバのアドレスに変換する (DNAT) 設定を行うことで、内部サーバのサービスを外部に提供できます。
ここで、ルータ宛のパケットに対しDNATのみ設定する場合、ある内部PCからルータのグローバルアドレスを宛先とした内部サーバへの通信を行うことはできません。なぜなら、内部PCから見た行きパケットの宛先アドレスと戻りパケットの送信元アドレスが一致しないためであるからです。

以下のどちらかの設定をすることでこの問題は解決します。
1. ドメイン名に対しローカルアドレスが解決されるよう設定
2. ルータにヘアピンNAT (NATループバック) の設定

サーバのみにログインできる当問題では、解法1を行うことで基準点を与えました。
解法1の具体的な例として、コンテナ起動 ( docker run ) 時に --add-host オプションを付与し hpn.finals.ictsc が内部サーバのアドレスに解決されるようにするといった方法があります。
なお、当問題の趣旨は「他人の環境を直す」ことであるため、再起動したら問題が再度起きる解法である場合減点としました。
ルータに触れられる場合の解法として、ルータに対しSNATおよびDNAT (ヘアピンNAT) の設定が必要であることを述べたチームに追加点を与えました。

この問題は、作問者が実際に自宅サーバを運用している際に起こった問題を再現しました。
作問者の家のルータにはヘアピンNATの設定項目が無かったため、内部にDNSサーバを建てた上で、内部マシン群はすべて内部DNSサーバに問い合わせることで解決しました。これにより、コンテナを起動時のオプション ( --add-host ) が必要なくなるといった利点があります。
これから自宅サーバを始める方に対して、この問題を通して得た知識が役に立てば幸いです。

回答例

  • 解法1: コンテナを一度 docker rm -f connection-check で落とした後、docker run -itd --name connection-check --add-host hpn.finals.ictsc:192.168.22.130 local/connection-check でコンテナを起動する。
  • 解法2: ヘアピンNAT (NATループバック) の設定が必要であることを述べる。

採点基準

  • 計160点
    • 基準点: 上司から与えられた問題の解決 (110点)
    • 満点: 先輩から与えられた問題の解決 (50点)
 /

問題文

今最もクールでエキサイティングなコンテナオーケストレーターであるKubernetesを自社でも導入を行う予定で、現在構築を行っている。

クラウド上でのマネージドサービスの前段階として、自社マシンにてKubernetesを導入してみたが、なぜか正常に動作していないようである。 その理由を判断し、Kubernetesクラスタが正常に動くように修正を行ってほしい。

具体的にはコンテナ内からの名前解決が行えていないようである。この問題を解決し、どのPodを起動しても正しくコンテナ内から名前解決できるようにしてほしい。

トラブルの概要

本問題では、Kubernetesクラスタにおいて新しく起動したPodから名前解決が行えないトラブルでした。
実際に下記のようなコマンドを入力した場合にも、digコマンドがタイムアウトエラーが発生してしまう状態にありました。

$ kubectl run dig --image=whywaita/bionic-dnsutils
$ kubectl exec -it $(Podの名前) dig icttoracon.net

→タイムアウト

解説

この問題は、KubernetesのDNS情報を管理するコンポーネントの kube-dns の古い仕様によって、一部環境でのみ発生するバグです。

元ネタのIssueはこちらです。

近年のLinuxディストリビューションにおいて、systemdの採用が進んでおり、特にUbuntuにおいては多くのsystemdコンポーネントを採用しています。

Ubuntu 18.04にて採用されたnetplanなどは記憶に新しいでしょうか。netplanはYAML形式の設定ファイルを用いてLinuxのネットワーク設定を行えるようになったものですが、動作としてはsystemd-networkdというバックエンドのコンポーネント向けの設定ファイルを生成し、ネットワーク設定を動作させています。

1

systemd-networkd とともに採用されているコンポーネントが systemd-resolved です。詳細な説明は割愛しますが、DNSキャッシュサーバを各サーバ内で起動させておくデーモンです。

実際に動作しているUbuntuで設定を確認してみましょう。Ubuntu (やその他多くのLinuxディストリビューション) においてDNSサーバの設定は /etc/resolv.conf に記載されています。以前のディストリビューションであれば以下のようにDNSサーバを指定していました。

$ cat /etc/resolv.conf
nameserver 8.8.8.8 # Google Public DNS
nameserver 8.8.4.4

ですが、systemd-resolvedがインストールされている場合は以下のように指定されています。

$ cat /etc/resolv.conf
nameserver 127.0.0.53

127.0.0.53 はloopbackアドレスであり自分自身を指します。systemd-resolvedが動いていることで自分自身に対して名前解決を行う事で高速にクエリ返答が行えるようになったということになります。

さて、今回問題が発生したコンテナにおいても、名前解決を行う場合にはDNSサーバの指定が必要です。問題が起きたバージョンにおいて、kube-dnsが参照するDNSサーバの設定はどこから取得するのでしょうか。

答えはホストの/etc/resolv.confです。デフォルト設定では、このファイルをそのままコピーを行っています。その場合、ホストにはsystemd-resolvedが動いていたため問題無かった設定を、systemd-resolvedが動いていないコンテナ上でも動作させてしまうことになります。

今回のトラブルはそのような原因で発生していました。

実際にこのトラブルを解決させる手法は以下の方法が挙げられます。

  • kube-dns の参照する上位のDNSサーバを変更する
    • (1) ConfigMapを用いて設定を行う
    • (2) kubeadmの設定を変更する
  • (3) /etc/resolv.conf のコピー元を指定する

ここでは(1)について説明します。下記のConfigMapファイルを用意します。

apiVersion: v1
kind: ConfigMap
metadata:
name: kube-dns
namespace: kube-system
data:
upstreamNameservers: |
["8.8.8.8", "8.8.4.4"]

上記のConfigMapを適用することでPod内のIPアドレスが適切に設定され、DNS名前解決をすることが可能となります。参考: kubernetes.io/docs

ちなみに出題したKubernetesのバージョンはv1.10.12で、現在のバージョンと鑑みても少し古いものでした。
このようなPull Requestによって、最近のkubeadmであれば今回のような状況に意図せずなってしまった場合でも警告が出るようになっているからという意図でした。

回答例

解説にもあった通り、下記のファイルを用意します。

$ cat kube-dns-conf.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-dns
namespace: kube-system
data:
upstreamNameservers: |
["8.8.8.8", "8.8.4.4"]

既にnamespaceなどの設定も書いてあるため、そのままapplyします。

$ kubectl apply -f kube-dns-conf.yaml

これにより設定が反映され、コンテナ内から名前解決が行えるようになりました。

採点基準

  • 名前解決が行えるか
  • 適切な設定が行われているか

この2点を確認していました。

実際にあった回答の中で減点した項目としては、名前解決が行えるものの永続化が行われていなかったり、上位サーバとして不適切なDNSサーバのIPアドレスが設定されていたりしており、それらの回答は減点を行いました。

まとめ

Kubernetesという比較的新しく大きなプロダクト上で出題した問題でしたが、実際に起きているのはDNSサーバの設定ミスというどのような環境であっても起こりえる問題でした。
回答に私用する設定もそれほど大きくなく、Kubernetesの公式ドキュメントにもあるため、なぜその現象が発生しているのかを把握できれば解決までは早かったのではないかと思います。

近年大きく注目を集めているDockerを始めとするコンテナ技術ですが、実際に動作しているのは慣れ親しんだ環境です。Kubernetesの使い方だけではなく、その中で動いているプロダクトや、それらのプロダクトがどうやって動作しているのか、管理側にも興味を持って頂ければと思います。


  1. https://netplan.io/ より引用