ICTSC(ICTトラブルシューティングコンテスト) 2020にチーム「インターネット」で参加しました.結果は40チーム中11位で予選を通過しました.

「ICTトラブルシューティングコンテスト2020」予選結果発表 – ICTSC | ICT トラブルシューティングコンテスト

チーム名の「インターネット」はkoyamaが適当につけました.インターネットは人権ですね.教科書にも書いてあるはずです.

チームは以下のLinuxClubのメンバー4人です.

Write-up

ルーティング

Networkが作成できない? [unknown]

prefixがかぶっていることに気づいたタイミングで終了

公式の解説:networkが作成できない? | ICTSC Tech Blog

経路を受け取れない [panakuma]

対象のルータにログインをし、journalctlでBGPのプロセスが出しているエラーを追っていると何らかの原因でプロセスが死んでいることが判明し、/var/logの中身を読んだりしているとメモリ不足が原因だと判明しました。 解決策としては、ルータに割り当てるメモリの量を増やすという単純な物でした。

繋がらなくなっちゃった! [panakuma]

以下の事象が原因でトラブルが発生していた

  • OSPFのルート交換をするインターフェースの指定が間違っていた
  • OSPFのエリアの設定が間違っていた
  • ルータIDが間違っていた

VyOSで動作していたため、とりあえずshow configurationを叩いてみたり、OSPFのステータスを確認しながらトラブルシューティングをしていきました。

また、この問題ではなぜこのような事象が発生したかを問われていたので、問題文を参考にルータの設定をいじくっていたら間違えてしまったのだろうという仮説を元に回答をしました。

またビルド失敗しちゃった~… [cl0wn]

マルチステージビルドをしているDockerfileがあります。一つ目のイメージで実行ファイルを作って、もう一つのイメージでそれを動かすということをしています。

エラーを見ると

standard_init_linux.go:211: exec user process caused "no such file or directory"

どうやら、一つ目のイメージにはあったファイルが2つ目のイメージにはなくてダイナミックリンクに失敗しているっぽいです。

したがって、static linkにしてやれば解決します。

RUN CGO_ENABLED=0 go build -o /app ./main.go

公式の解説:またビルド失敗しちゃった~… | ICTSC Tech Blog

ウェブ

WEBページが見れない [cl0wn]

この問題は「apacheサーバーのdocumentルートを/home/user/html/に変更したところ、そのディレクトリ以下のファイルhome.htmlがブラウザから閲覧できない」という問題を解消しなさいという問題でした。

まず、/home/home/user/home/user/htmlディレクトリのothersに実行権限、/home/user/html以下のhome.htmlのothersに閲覧権限が付与されているかを確認しました。すると/home/userのothersに実行権限が付与されていないことが分かりました。したがって、chmod o+x /home/userしました。

この状態で/home.htmlにアクセスするとまだ表示されません。エラーログにはclient denied by server configuration: /home/user/html/home.htmlが吐かれていました。上で権限の設定はしたので、なんでだろうなと思って調べてたら、この記事を見つけました。試しに/etc/httpd/conf/httpd.confに以下を追記してみます。

<Directory "/home/user/html">
    Require all granted
</Directory>

エラー内容が変わりました。どうやらうまく動いたようです。

どんなエラーが出ていたかというとAH00132: file permissions deny server accessでした。問題文に「SELinuxを無効にしてはいけない」と書いてあったのでSELinuxのせいかなと思いました。試しにsetenforce Permissiveにすると、/home.htmlが正常に表示されるようになりました。ls -Z /home/user/html/home.htmlを使ってSELinuxのコンテキストを確認してみるとファイルタイプがuser_home_tになっていました。apacheがファイルを読むためにはファイルタイプがhttpd_sys_content_tに設定されていなければならないそうです。したがって、ファイルタイプを変更すれば問題は解決しそうです。chconによるファイルタイプの変更はリブート時にもとに戻ってしまうそうなので、semanageを使ってファイルタイプを変更します。setenforce EnforcingでSELinuxの設定をもとに戻して、sudo yum install policycoreutils-pythonでsemanageをインストール、sudo semanage fcontext -a -t httpd_sys_content_t /home/user/html/home.htmlを実行しました。すると正常に/home.htmlが閲覧できるようになりました。

公式の解説:WEBページが見れない | ICTSC Tech Blog

コンテナ

どこからもアクセスできなくなっちゃった [koyama]

LoadBalancerの先にあるKubernetesクラスタを対象に,クライアントからのアクセス制御を適切に行う問題でした.アクセス制御にはKubernetesのNetworkPolicyを使う制約が設定されていました.

リクエストのフローを整理すると以下になります.

host01
 | src: 192.168.0.10
 | dst: 192.168.0.1:30080
 |
 v
lb
 | src: 172.16.0.254
 | dst: 172.16.0.11:30080 or 172.16.0.12:30080
 |
 v
worker01 or worker02

この段階で,異なるネットワークセグメントを超える必要があることが分かりました.lbにはHAProxyがインストールされていて,IP Masqueradeが設定されているということだったのでL4 Direct Server Returnができそうだなと思いました.回答例ではKeepalived + LVSの組み合わせでやっていたのでつらぽよでした.

HAProxyの設定ファイルと格闘していたら時間切れになったので悲しかったです.HAProxyに不慣れなせいで,設定ファイルの記法で怒られまくりました.default, global, front, backendを上手く書く方法を学びたいなと思います.HAProxyはしばらく触りたくないです.

公式の解説: どこからもアクセスできなくなっちゃった | ICTSC Tech Blog

hostnameでつながらない!! [Mikaner]

docker-compose で立てたDatabaseサーバーのhostnameをdatabaseにしたところ,databaseで参照してもipが取れないという問題でした。

hostnameを変更してもDockerのDNSに反映されないみたいなので,Dockerのコンテナ名を変更しました。

変更前

version: '3.3'
 
services:
   db: # DO NOT CHANGE THIS LINE
     image: mysql:5.7
     hostname: database # DO NOT CHANGE THIS LINE
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: 8MvAMcDAirP8
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: DDzk6ERU33Rc
 
   wp:
     depends_on:
       - db
     image: wordpress:latest
     hostname: wordpress
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: database:3306 # DO NOT CHANGE THIS LINE
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: DDzk6ERU33Rc
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data:

変更後

version: '3.3'
 
services:
   db: # DO NOT CHANGE THIS LINE
     image: mysql:5.7
     hostname: database # DO NOT CHANGE THIS LINE
     container_name: database # 追記
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: 8MvAMcDAirP8
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: DDzk6ERU33Rc
 
   wp:
     depends_on:
       - db
     image: wordpress:latest
     hostname: wordpress
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: database:3306 # DO NOT CHANGE THIS LINE
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: DDzk6ERU33Rc
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data:

ダイエットしようぜ! [koyama, cl0wn]

GoでSHA256を計算するコードとDockerfileが渡されました.与えられたDockerfileにより作成したイメージはサイズが796MBでした.問題では,このイメージサイズを出来る限り削減することが求められました.

koyamaは以下の方針でイメージサイズの削減に取り組みました.

実際のDockerfileは以下です.

FROM golang:alpine AS build-env
COPY hash.go /work/
WORKDIR /work
RUN go build -ldflags="-w -s" -o myhash hash.go

FROM scratch
COPY --from=build-env /work/myhash /myhash
ENTRYPOINT ["/myhash"]

イメージサイズが1.49MBになりました.

$ docker images | grep ictsc-dit
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
ictsc-dit                latest              f74546496cca        8 minutes ago       1.49MB

upxによるバイナリサイズの削減ができそうだったので,cl0wnにお願いしました.

goupxというツールを見つけたのでそれを利用しました。

Dockerfileを以下のように変更しました。

FROM golang:alpine AS build-env
RUN apk add --no-cache git upx binutils
RUN mkdir /gobin
ENV GOBIN /gobin
ENV PATH $PATH:/gobin
COPY hash.go /work/
WORKDIR /work
RUN go build -ldflags="-w -s" -o myhash hash.go
RUN strip myhash
RUN go get github.com/pwaller/goupx
RUN goupx myhash

FROM scratch
COPY --from=build-env /work/myhash /myhash
ENTRYPOINT ["/myhash"]

参考

これによりイメージのファイルサイズが600kBまで落ちました。goのバイナリにupxをそのまま使うと実行できなくなるという話を聞いたのでチキってgoupxを使いましたが、オプションが使えないというデメリットがありました。upxの-9を試してみたかったなと思いました。

公式の解説: ダイエットしようぜ! | ICTSC Tech Blog

データベース

備品は何処へ [Mikaner]

データベースから必要な情報を取ってくるSQLを書き,それを実行してCSVファイルに書き込み,SQLファイルとCSVファイルの両方を提出すれば良い問題でした。

Mikanerは以下の手順で問題の回答に当たりました。

  1. CSVファイルの書き出し方法を検索 => ここが見つかったので参考にしました。
  2. データベース内の構造を確認(下の図参照)
  3. SQLを結合し,データが取得できるか確認, 指定されたデータの全データ出力
  4. WHERE句とORDER BY句を追加し出力
  5. 不備が無いかを確認し提出

以下はコンテスト中に作成した図と提出したSQLのメモです。

SQLの結合

SELECT句にて必要な情報を並べ,あとは結合するだけの簡単なお仕事でした。 AS句を使えばもっと楽に書けたのですが,当時のMikanerさんはAS句の使い方をうろ覚えだったので,確実性を求めてメモにコピペをして済ませていました。 楽だから使えばいいのにね。

USE List;
SELECT Equipment_list.ID, Equipment_list.Name, Order_company_list.Name, Manufacturing_company_list.Name, Equipment_list.Price
FROM Equipment_list
INNER JOIN Order_company_list
ON Equipment_list.Order_company = Order_company_list.ID
INNER JOIN Manufacturing_company_list
ON Equipment_list.Manufacturing_campany = Manufacturing_company_list.ID -- ここ地味にc"a"mpanyになっててうざかった
INNER JOIN Location_list
ON User_place = Location_list.ID
INTO OUTFILE '/tmp/submit.csv'
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';

このあたりでKoyamaさんとPanakumaさんにSQLを見てもらったところ,INNER JOINはあまり使わないという意見を頂いたので,本番環境を意識してLEFT JOINに変更しました。

ここで変更したファイルで意気揚々と出力させようとしたところ,submit.csvがすでにあるから書けないという問題が発生しました。 /tmp内にアクセスして削除を試みましたが,権限がないため削除ができませんでした。sudo権限ください。 面倒くさかったので出力名を’test1.csv’に変更して出力しました。 このあとは実行した分だけ番号が増えていきます。

USE List;
SELECT Equipment_list.ID, Equipment_list.Name, Order_company_list.Name, Manufacturing_company_list.Name, Equipment_list.Price
FROM Equipment_list
LEFT JOIN Order_company_list -- 変更
ON Equipment_list.Order_company = Order_company_list.ID
LEFT JOIN Manufacturing_company_list -- 変更
ON Equipment_list.Manufacturing_campany = Manufacturing_company_list.ID
LEFT JOIN Location_list -- 変更
ON User_place = Location_list.ID
INTO OUTFILE '/tmp/test1.csv' -- 変更
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';

出力結果

"24","pc_x","Order_company_E","Manufacturing_company_E","1600"
"18","pc_r","Order_company_F","Manufacturing_company_G","1200"
"9","pc_i","Order_company_I","Manufacturing_company_G","1000"
"13","pc_m","Order_company_E","Manufacturing_company_K","900"
"17","pc_q","Order_company_J","Manufacturing_company_E","800"
"8","pc_h","Order_company_H","Manufacturing_company_F","700"
"19","pc_s","Order_company_D","Manufacturing_company_I","600"
"12","pc_l","Order_company_B","Manufacturing_company_J","600"
"1","pc_a","Order_company_A","Manufacturing_company_A","500"
"20","pc_t","Order_company_G","Manufacturing_company_J","500"
"7","pc_g","Order_company_G","Manufacturing_company_E","500"
"3","pc_c","Order_company_C","Manufacturing_company_A","300"
"2","pc_b","Order_company_B","Manufacturing_company_A","100"
"26","pc_z","Order_company_K","Manufacturing_company_B","100"
"6","pc_f","Order_company_F","Manufacturing_company_D","90"
"16","pc_p","Order_company_I","Manufacturing_company_B","65"
"15","pc_o","Order_company_A","Manufacturing_company_C","60"
"5","pc_e","Order_company_E","Manufacturing_company_C","60"
"21","pc_u","Order_company_A","Manufacturing_company_K","50"
"22","pc_v","Order_company_A","Manufacturing_company_A","50"
"23","pc_w","Order_company_B","Manufacturing_company_F","50"
"25","pc_y","Order_company_I","Manufacturing_company_B","50"
"4","pc_d","Order_company_D","Manufacturing_company_B","50"
"14","pc_n","Order_company_A","Manufacturing_company_F","20"
"10","pc_j","Order_company_J","Manufacturing_company_H","20"
"11","pc_k","Order_company_K","Manufacturing_company_I","10"

WHERE句とORDER BY句を追記

あとは並び替えるだけと思って問題文よく読んだら,HQという場所にあるE社のPCだけを出力するっぽいことがわかったので,WHERE句とORDER BY句を追記しました。 よく読まなかったら危なかった。

USE List;
SELECT Equipment_list.ID, Equipment_list.Name, Order_company_list.Name, Manufacturing_company_list.Name, Equipment_list.Price 
FROM Equipment_list
LEFT JOIN Order_company_list
ON Equipment_list.Order_company = Order_company_list.ID
LEFT JOIN Manufacturing_company_list
ON Equipment_list.Manufacturing_campany = Manufacturing_company_list.ID
LEFT JOIN Location_list
ON Use_place = Location_list.ID
WHERE Manufacturing_company_list.Name='Manufacturing_company_E' AND Location_list.Building_name='HQ' -- 追記
ORDER BY Equipment_list.Price DESC -- 追記
INTO OUTFILE '/tmp/test2.csv' -- 変更
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
ESCAPED BY '"'
LINES TERMINATED BY '\r\n';

不備の確認

出力先ファイルをsubmit.csvに変更し,出力したCSVファイルの名前もsubmit.csvに変更し,提出しました。提出直前にLocation_listとEquipment_listをつないでいるON句でUse_placeのテーブル指定を忘れていたことに気付きましたが,修正面倒だしFROMの方だから問題ないやろってお気持ちで放置しました。

提出した出力結果

"24","pc_x","Order_company_E","Manufacturing_company_E","1600"
"17","pc_q","Order_company_J","Manufacturing_company_E","800"

公式の解説:備品は何処へ | ICTSC Tech Blog

プログラミング

jsonが壊れた!! [koyama]

誤りのあるGoのソースコードが渡されるので,それを修正して正常な動作が確認できればOKな問題でした.

構造体の記述に問題があり,ダブルクオートの欠落とJSONスキーマ指定に不備がありました.

変更前

type User struct {
        ID int32 `json="id"`
        Name string `json="name"`
        PasswordHash string `json="password_hash"`
        Count int32 `json="access_count`
}

変更後

type User struct {
        ID int32 `json:"id"`
        Name string `json:"name"`
        PasswordHash string `json:"password_hash"`
        Count int32 `json:"access_count"`
}

公式の解説: jsonが壊れた!! | ICTSC Tech Blog

感想

Mikaner

コンテナネーム変えた後SQL書いてたら他の問題が終わってました。 皆さんすごいですね。僕はKubernetes問やネットワーク問等、覗いてみて「なるほどわからん」を繰り返してました。 精進したいですね。

cl0wn

  • エラーで調べれば対処が書かれていることが多く、筋道立っていてありがたかったです。
  • upxもうちょっとやりたいです
  • ネットワーク系の問題を遠ざけていましたが、Networkが作成できない?とかやればよかったかなと思ってます
  • ネットワーク系の問題を本選までに解けるようになりたいなーと思う今日この頃です

panakuma

低レイヤーで問題が少なかったので、あまり歯ごたえがなかったなぁという感じです。問題のクオリティが例年より低いなと感じました。

koyama

簡単な問題で確実に得点できたのが良かったです.cl0wnとMikanerの2人が活躍してくれたおかげだと思います.ダイエット問は自身があったものの,Twitterで300KB程度のチームを見つけて悲しくなりました.Kubernetes問をやりながらLBでアクセス制御しちゃダメなのが納得できない気持ちでした.本戦に向けて精進したいと思います.