ICTSC2020予選を通過したおはなし

はじめに

2020年10月31日にICTSC2020の予選にLynT4χ(リンタッカイ)として参加させていただきました。 これまでもICTSCには何度か弊学メンバーで出場していましたが、今年が最期だろうということで、メンバー3人(うち1人は予選欠席:fearful:)で記念受コンしました。 結果としては、40チーム中6位で、まずまずかなと思います。

今回も備忘録として、今回ぼくが解いた問題のwriteup(回答)を残しておきます。 問題文などは、ちゃんと保存していないので、公式の解説ページから頂戴いたしました。

ルーティング

networkが作成できない?(100/100)

dockerのネットワークについてはあまり意識して見たことがなかったので、勉強になりました。

問題

docker-composeを使って,NetBoxを運用していました. 運用サーバに新しくインターフェースを追加したところ,NetBoxがうまく起動できなくなりました. 原因を調査して,修復してください.

回答

この問題では、新たに追加されたIPv4インターフェースが、dockerにて利用されるdefaultネットワークのIPv4アドレス空間を専有してしまっていたために、トラブルが発生したと考えられます。

そのため、以下のように設定を変更し、正常にdocker-compose upができることを確認いたしました。 ご確認のほど、よろしくお願いいたします。

手順

1: ~/netbox-docker/docker-compose.ymlの最終行以下に, 次の設定を追加

networks:
default:
ipam:
config:
- subnet: 192.168.1.0/24

2: docker-composeコマンドにより、NetBoxが正常に起動できることを確認

user@app0:~/netbox-docker$ sudo docker-compose up -d
Starting netboxdocker_redis_1 ... 
Starting netboxdocker_postgres_1 ... 
Starting netboxdocker_redis_1
Starting netboxdocker_redis-cache_1 ... 
Starting netboxdocker_postgres_1
Starting netboxdocker_redis_1 ... done
Starting netboxdocker_netbox-worker_1 ... 
Starting netboxdocker_postgres_1 ... done
Starting netboxdocker_netbox_1 ... 
Starting netboxdocker_netbox_1 ... done
Starting netboxdocker_nginx_1 ... 
Starting netboxdocker_nginx_1 ... done

参考

またビルド失敗しちゃった〜...(100/100)

Goはほとんど触ったことがなかったので、不安でしたが、なんとなくggってくと回答が見えてきたので、なんとか解けました。 ダイナミック/スタティックリンクの同様のトラブルはGo以外でも発生しそうだなあと解きながら思ったので、面白い問題でした。

問題

新入社員の障害太郎くんがGoの勉強をしようとしています。 どうやらDockerのマルチステージビルドを使って、Goのバイナリをコンテナ上で実行しようとしたらうまく立ち上がらないようです。 先輩のトラシュウさんに聞いたところ、「Dockerfileが間違っている」というメモを残して業務に戻ってしまいました。 先輩のトラシュウさんの代わりに原因を特定して修正してあげてください。

回答

この問題では、goのバージョン1.4以降から、デフォルトでビルドしたバイナリファイルがダイナミックリンクになってしまうことが原因で、トラブルが発生していたと考えられます。 バイナリファイルがダイナミックリンクになってしまっていると、別環境(今回の環境ではbuilderイメージではないほうのイメージ)では、バイナリファイルを正常に実行できません。

これを修正する方法として、

1: cgoを無効にすることで、ダイナミックリンクではなくスタティックリンクのバイナリファイルを生成する 2: go buildの引数に、スタティックリンクとするようなオプションを指定する - 例: $ go build -a -tags netgo -installsuffix netgo --ldflags '-extldflags "-static"'

の2通りが考えられます。

今回は特に、cgoを利用しているコードはなかったため、builderイメージに、CGO_ENABLED=0という環境変数を新たに追加することで、正常にコンテナを起動でき、curlコマンドで想定のレスポンスが得られることを確認いたしました。 ご確認のほど、よろしくお願いいたします。

手順

1: dockerファイルを次のように書き換え(4行目にCGO_ENABLEDを追加)

FROM golang:1.15.0 AS builder
ENV GO111MODULE=on
ENV GOPATH=
ENV CGO_ENABLED=0
COPY ./server/main.go ./
RUN go mod init ictsc2020
RUN go build -o /app ./main.go


FROM alpine:3.12
COPY --from=builder /app .
EXPOSE 1323
ENTRYPOINT "./app"

2: dockerコンテナが正常に起動できることを確認

user@server01:~/app$ docker run -p 80:1323 ictsc2020 &
[1] 26697
user@server01:~/app$ 
____    __
/ __/___/ /  ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.1.17
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:1323

3: curlコマンドで正常なレスポンスが得られることを確認

user@server01:~/app$ curl localhost
Welcome to ICTSC2020!

参考

ウェブ

WEBページが見れない(100/100)

途中まで、もうひとりのチームメンバーが解いてくれていた問題を引き継いで解いた問題です。 (SE)Linuxの権限設定って複雑で管理しづらいなあと思った問題でした。

問題

apacheのDocumentRootを/var/www/htmlから/home/user/htmlに変更して、$curl http://127.0.0.1/home.htmlを実行したら、403エラーが返ってきてアクセスできない。

回答

この問題では、主に権限の正しい設定が行われていないことが原因で、403エラーが返されるというトラブルが発生していたと考えられます。 そこで、次の手順により、正しい設定を行うことで、curlコマンドで想定のレスポンスが得られることを確認いたしました。 ご確認のほど、よろしくお願いいたします。

手順

1: /etc/httpd/conf/httpd.confに次の設定を追加

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

2: 新たに指定したDocumentRootディレクトリのコンテキストを元のDocumentRootコンテキストと同様のものに設定

$ ls -ld --context /home/user/html
drwxr-xr-x. user user system_u:object_r:user_home_t:s0 /home/user/html
$ ls -ld --context /var/www/html
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 /var/www/html
$ sudo chcon system_u:object_r:httpd_sys_content_t:s0 /home/user/html -R
$ ls -ld --context /home/user/html
drwxr-xr-x. user user system_u:object_r:httpd_sys_content_t:s0 /home/user/html

3: /home/userディレクトリに実行権限を付与

$ chmod 711 /home/user

4: httpdサービスをリスタート

$ sudo systemctl restart httpd

5: curlコマンドで正常にレスポンスが得られることを確認

$ curl http://127.0.0.1/home.html 
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Apache Server</title>
</head>
<body>
<div style="text-align: center;">
<h1> Successful Access To Apache Server</h1>
</div>
</body>
</html>

参考

コンテナ

どこからもアクセスできなくなっちゃった(40/200)

この問題では、トラブルの原因をある程度特定できたところで、コンテスト終了に近づいていたので、その部分だけリポートし、部分点を頂きました。

問題

k8sクラスターを、マスター1台(master)、ワーカー2台(worker01, worker02)、ロードバランサー1台(lb)という構成で構築しました。 ロードバランサーにはHAProxyを用いており、kube-apiserverであるmaster(172.16.0.1:6433)へのプロキシと、各ワーカーの30080のNodePort(172.16.0.11:30080, 172.16.0.12:30080)へのロードバランシング, k8sクラスタの各ノードのデフォルトゲートウェイとしてiptablesを用いたMasqueradeを行っています。

k8sクラスタでは、nginxをreplica 数1つで展開するDeploymentと、それを外部にNodePort 30080で公開するServiceが作成されています。 このnginxに対して外部(external)からの通信においてhost01からのみアクセスできるといったアクセス制限を行うため、NetworkPolicyを用いて制限をかけたところ、host01からもアクセスできなくなってしまいました。

なぜhost01からもアクセスできないのか原因と解決方法、解決でき再起動しても問題のない作業手順を報告してください。

回答

この問題では、ロードバランサーによって接続元IPアドレスが変更されているために、正常にトラフィックのフィルタリングが行えず、トラブルが発生したと考えられます。 実際に、以下のように、nginxのログを確認したところ、接続元IPアドレス192.168.0.0/24の空間ではないものになっています。 これを考慮した、設定を行う必要があります。

user@host01:~/manifests$ kubectl logs my-deployment-d46f5678b-b67hs -n my-ns
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
172.16.0.12 - - [31/Oct/2020:08:38:32 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.20.5.0 - - [31/Oct/2020:08:42:41 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.16.0.12 - - [31/Oct/2020:08:42:55 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.20.5.0 - - [31/Oct/2020:08:43:00 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.16.0.12 - - [31/Oct/2020:08:43:20 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.20.5.0 - - [31/Oct/2020:08:43:22 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.16.0.12 - - [31/Oct/2020:08:47:35 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.20.5.0 - - [31/Oct/2020:08:48:02 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.16.0.12 - - [31/Oct/2020:08:48:14 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"

ダイエットしようぜ!(100/100)

参考リンクとしても貼りましたが、golang smallest imageとかでggると一番上に該当記事が出てくるので、これを読めば10分ぐらいで解ける問題でした。googling大事。

問題

GoでSHA256するhash.goを書いた。これを実行するコンテナが欲しかったので雑にDockerfileを書いてビルドしてイメージを作成した。問題なくSHA256できるようになったが、イメージが大きくてテンションが上がらない。 あなたには、Dockerfileを編集したりビルドコマンドを変えたり、あるいはビルド後のイメージに対してなにかしたりしてイメージを小さくしてほしい。 ただし、hash.goのコードにはこだわっているので編集してはならない。 $ make buildictsc-ditという名前のDocker imageを作ることができる。詳細はMakefileを参照されたい。採点の際にVMを確認する場合、どのイメージが回答によって作成されたイメージかすぐに判別できるよう、是非使ってほしい。

回答

この問題では、以下の2つのいずれかを実践することで、Dockerイメージのサイズを減らすことができます。

  1. ベースとなるgolangのDockerイメージのタグをalpineにする
  2. マルチステージビルドを利用する

今回は、よりイメージサイズを減らすことができる、マルチステージビルドを利用しました。 その結果、イメージサイズを2.05MBまで減らすことができました。

手順

1: Dockerfileの中身を以下のように書き換え

# step 1
FROM golang:alpine AS builder

ADD . /work

WORKDIR /work

RUN go build -o /go/bin/hash

# step 2
FROM scratch

COPY --from=builder /go/bin/hash /go/bin/hash

ENTRYPOINT ["go/bin/hash"]

2: make buildでイメージをbuild 3: docker imagesでイメージサイズを確認

user@vm01:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ictsc-dit           latest              7a50b38100f3        5 minutes ago       2.05MB

4: docker runで正常にコンテナが動作することを確認

user@vm01:~$ make run
docker run -it --rm --name ictsc-dit-container ictsc-dit
data: aaa
SHA256: 9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0

参考

データベース

備品は何処へ(100/100)

ぼくの読解力が悪いのか、問題文と終了状態の定義が曖昧な気がして、それの確認に時間が取られた問題でした。。 普段DBを触るにしてもWeb Frameworkのラッパー経由が多く、SQLをそのまま触る機会は減っていたので、復習しながら取り組めたのでよかったです。

問題

あなたは現在、自社の備品を管理する部署に勤務している。管理システムはとても古くMySQLを利用している。 ある日、上司から特定の製造会社のパソコン(Manufacturing_company_E)、特定の建物(HQ)で相性が悪く交換するためリストを作成してほしいと依頼を受ける。なお、注文会社はパソコンの管理を行う専門会社である。 MySQLのデータベースからcsvファイル(submit.csv)を出力するSQL文(submit.sql)を作成し実行しなさい。

回答

USE List;
SELECT  Equipment_list.ID               AS "Equipment ID"
,Equipment_list.Name             AS "Equipment Name"
,Order_company_list.Name         AS "Order company Name"
,Manufacturing_company_list.Name AS "Manufacturing company Name"
,Equipment_list.Price            AS "Equipment Price"
FROM Equipment_list
JOIN Manufacturing_company_list
ON Equipment_list.Manufacturing_campany=Manufacturing_company_list.id
JOIN Order_company_list
ON Equipment_list.Order_company=Order_company_list.id
JOIN Location_list
ON Equipment_list.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/submit.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"';
24,"pc_x","Order_company_E","Manufacturing_company_E",1600
17,"pc_q","Order_company_J","Manufacturing_company_E",800

おわりに

Dockerはアルバイトでよく使っているので、今回Docker問題が多くてラッキーでした。予選ラッキー通過でした。 k8sは最近さわりはじめたばかりなので、本戦までには太刀打ちできるようになっていてほしいです(To自分)。 今回、ネットワーク問題はほとんど解いておらず、得意かというとそうでもないので、本戦までに頑張っていきたいです。

参加者、運営のみなさんトラブルシューティングお疲れさまでした。