これは何?
ICTSC2021 夏の陣のためにお茶大生5人で勉強した時の記録です。
注意事項
- このサイトで紹介している問題は、ICTSC tech blog から参照しており、私たちや弊サイトに著作権はありません。
- このサイトの内容を読んでバカにする/誹謗中傷等のことはやめてください。
- このサイトの内容の正当性は保証していません。参考にし何か問題が起きたとしても自己責任でお願いします。
- 一部未完成のページが存在します。ご了承ください。
- 著作権等で問題がありましたら、HunachiのDMにまでご連絡していただけますと幸いです。
この記事の元になっているリポジトリ
https://github.com/Hunachi/ictsc-418
参加チーム名
Status code 418s
チームメンバー(アルファベット順)
- Hunachi Twitter, GitHub
- 主な担当: 雑多なやつ(Program,Linux,データベース)
- maimai-y
- 主な担当: Web系(Web,FTP)
- momom-i
- 主な担当: DNS系(DNS,IPv4,v6,メール)
- nonnonno
- 主な担当: ネットワーク系(ルーティング,Tunnel)
- とり
- 主な担当: Container系(LoadBalancer,k8s,Container)
何か問題があった場合の連絡先
本番の結果!!
順位
9位でした🎉
感想ブログ
ICTSC2021に向けた勉強会スケジュール
書いた人:Hunachi
*第1回勉強会*
- 開催日時:6/21 (月) 21:30~23:00
- 内容:dockerについて調べて触ってみる:whale:(数回同じ内容の可能性あり)
- 詳しい学習内容:Docker勉強会の第一回
- その他:任意参加
*第2回勉強会*
- 開催日時:7/5 (月) 21:30~23:00
- 内容:予選の過去問を見ながら、ルーティング(ネットワーク)とコンテナ(Docker)について知識をつける。
- 詳しい学習内容:過去問勉強会の第一回
- その他:任意参加
*第3回以降の勉強会*
- 開催日時:毎週月曜日 21:30~23:00
- 内容:それぞれが解いてきた過去問についてしゃべってもらいます!
- 宿題:自分の担当分野の過去問1問以上を解く。問題のサイトの解説より詳しい解説を書くようにする。
- その他:任意参加
過去問勉強会
第一回
内容
- 本番に向けて、問題を解く方法について考える&見ていく
- 過去問を見ていく
- hostnameで繋がらないhostnameでつながらない!!
第二回以降
- 解いてきた過去問について解説する。
参照した問題・解説のサイト: https://blog.icttoracon.net/2020/03/01/
生き返れMariaDB
解いた人:とり
参照した問題・解説のサイト:生き返れMariaDB
使用環境・ツール
- docker
- MariaDB
問題文
- MariaDBのコンテナがすぐに落ちる
- VM上でdocker ps -aをするとmariaDBコンテナが1つ存在している
- VM上でdocker start mariaDB→docker exec -it mariaDB bashをしても落ちて開けない
理想の終了状態
- コンテナが継続して起動している状態である
- docker exec -it mariaDB bashでコンテナに入ることができ、入った際にMariaDBのDBのictsc_incのusersテーブルを見たとき
+----+------------+-----------+------+-------+
| ID | First_Name | Last_Name | Age | Sex |
+----+------------+-----------+------+-------+
| 1 | Tarou | Yamada | 23 | MAN |
| 2 | Emi | Uchiyama | 23 | WOMAN |
| 3 | Ryo | Sato | 25 | MAN |
| 4 | Yuki | Tayama | 22 | WOMAN |
| 5 | Yuto | Takahashi | 21 | MAN |
+----+------------+-----------+------+-------+
- 社内システムがDBを参照出来るよう、3306番ポートが開いている。
配点
- このコンテナが不安定になっている原因を明確な証拠をもとに正しく特定できている(60%)
- コンテナが継続動作するように復旧できている(25%)
- 当該コンテナ上で問題文通りのレコードを参照できる(15%)
考えられる検証、修正手順
なんとなく、定期的に落ちるならメモリ不足かなと思った
バグの原因を特定する案
-
ログを確認する
docker logs
でログ情報を確認する(確認できるのかな)docker mysql exited with code 137
こんな感じで出ていたらmysqlが落ちていることがわかる- mysqlじゃないけど
-
$ docker stats
してみる
こんな流れでメモリ不足が出る
- dockerでメモリを過剰に使用
- mysqlのdockerがメモリ不足で落ちる
- ホストのoom-killerも動き、何故かmysqlが狙われてkillされる
メモリの割り当て
docker-machine inspect
以下のコマンドでメモリの割り当てをする
docker run -m 1024m hoge /bin/bash
https://qiita.com/niisan-tokyo/items/2d7d21aeb4e25f7a7bbe
oom-killerのkillを阻止する
こんな感じでkillの確認ができる
$ sudo cat /var/log/messages | grep Killed
Oct 1 11:11:54 ip-xx-xx-xx-xx kernel: [1983378.957901] Killed process 5789 (ruby) total-vm:4957320kB, anon-rss:2717004kB, file-rss:0kB
危なそうなプロセスの確認
$ dstat --top-oom
--out-of-memory---
kill score
mysqld 484
mysqld 484
mysqld 484
修正は/proc/PID/oom_adj
に(優先度低)-16から+15(優先度高)の値を設定することができる
解説
原因の特定方法
- docker inspectコマンドを使用する
docker inspectコマンドは、dockerコンテナの設定情報を参照することが出来るコマンドです。これを見ると、コンテナがどのような設定で動作しているのかが分かります。しかし、この設定データの量は膨大で、全てを見るには時間がかかってしまいます。 例えばですが、この問題の原因のメモリについて調べたい際、など、grepコマンドなどを使い検索すると、
$ docker inspect mariaDB | grep Memory
"Memory": 4194304,
"KernelMemory": 0,
"MemoryReservation": 0,
"MemorySwap": -1,
"MemorySwappiness": null,
といった値が出てきます。ここに出た"Memory": 4194304という値はバイト表記でして、MBに換算すると約4MBということが分かります。設定されていた値と同じですね。
- docker statsコマンドを使用する
docker statsコマンドは、コンテナのリソース使用状況を表示するコマンドです。Linuxのtopコマンドに似たような機能を持っています。 ここで、コンテナのリソース使用状況を知ることが出来ます。
$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
eb99786557f7 mariaDB 8.44% 3.664MiB / 4MiB 91.60% 656B / 0B 1.89GB / 0B 3
こちらでもMEM USAGE / LIMITの欄で3.664MiB / 4MiBと見えており、メモリが4MB制限であること、そして使用中のメモリが逼迫している状態であることから不安定になる原因であることが推定出来ます。
解決手順
2パターン。
- docker updateコマンドを使用しリソース設定を更新する
docker updateコマンドはコンテナの設定を更新するためのコマンドです。 コンテナの設定をコンテナを作り直すことなく変更することが可能です。 例として、問題のコンテナのメモリ制限を4MBから1GBへと緩和します。例えば、
docker update --memory 1G mariaDB
このコマンドを実行する事により、メモリ上限を4MBから1GBに変更することが出来ます。
$ docker inspect mariaDB | grep Memory
"Memory": 1073741824,
"KernelMemory": 0,
"MemoryReservation": 0,
"MemorySwap": -1,
"MemorySwappiness": null,
となり、Memory": 1073741824(byte)は約1GBですので、1GBへと緩和されたことが分かります
- 同じ設定のコンテナを作り直す
解法の一つとして、同じ設定のMariaDBコンテナを再作成する方法があります。 docker run -v mariaVOL:/var/lib/mysql -d --name mariaDB -e MYSQL_ROOT_PASSWORD=MariaPass -p 3306:3306 -d mariadb:latest
などで、メモリ制限を無くしたコンテナを作成します。 ただし、MariaDBのデータベースはボリュームmariaVOLにマウントされているという点に注意しなければなりません。mariaVOLへコンテナをマウントしないと、コンテナに入ってもデータベースを参照することが出来なくなってしまいます。
上記の2つの設定のどちらかを適用し、
$ docker exec -it mariaDB bash
$ mysql -u root -p ${MYSQL_ROOT_PASSWORD}
> use ictsc_inc;
> select * from users;
// selectの結果が表示される
コメント
$ docker stats
使う時は、一秒ずつ表示が増えてウザいので、$ docker stats --no-stream
とするといいらしい。
v4v6 移行が終わらない
解いた人:momom-i
参照した問題・解説のサイト:v4v6 移行が終わらない
問題
おお久しぶり!!
君が居ないあいだに社内ネットワークを IPv6 only にしておいたんだ。外向きの IP アドレスは v4 しかないけどルーターで NAT64 をしているからインターネットにはつながるようになっているよ。
ただ名前解決ができないのと社内ネットワークにある Web に繋がらなくなっちゃったんだ、これ以上は手が付かないから君がなんとかしてくれないかな?
初期状態
ubuntu-1
からcurl https://blog.icttoracon.net/
をしてもつながらないubuntu-1
からcurl http://[2403:bd80:c000:900::1]/
をしてもつながらない
終了状態
ubuntu-1
からcurl https://blog.icttoracon.net/
をするとステータスコード 200 のレスポンスが返ってくる。ubuntu-1
からcurl http://[2403:bd80:c000:900::1]/
をするとステータスコード 200 のレスポンスが返ってくる。
配点
ubuntu-1
からcurl https://blog.icttoracon.net/
をするとステータスコード 200 のレスポンスが返ってくる 80%ubuntu-1
からcurl http://[2403:bd80:c000:900::1]/
をするとステータスコード 200 のレスポンスが返ってくる。 20%
問題文でされた操作
- 社内ネットワークを IPv6 only にした
- 外向き IP アドレスは v4 しかないが、ルーターで NAT64 をしている
https://blog.icttoracon.net/
が返らない原因
プリフィックスのある v6 アドレスを DNS で生成されていない
まずは、DNS で IPv6 アドレスを問い合わせてみる
dig -t AAAA blog.icttoracon.net +short
NAT64、DNS64 そして通信に介在するネットワーク機器は同じ IPv4-IPv6 変換アドレス用のプリフィクスを使用するよう設定されている必要があります。(参照)
今回は外向き IP アドレスは v4 のみなのでプリフィックスのある v6 アドレスが返されないといけない。
https://[2403:bd80:c000:900::1]/
が返らない原因
ping6 2403:bd80:c000:900::1
をしてみる。もし疎通性がなければ、パケットフィルタリングが問題。もし疎通性があれば、前回と同様で web サーバが IPv6 で受け付けてないとか nginx だったら config の不備が問題。
考えられる解決方法
DNS64 に対応させる
BIND の場合、BIND のバージョンが 9.8 以降か確認し、/etc/bind/named.conf に以下のような記述 DNS64 の設定があるか確認する
options {
# IPv4は32bitなので96bit付け足してIPv6に対応させる
dns64 64:ff9b::/96 {
# クライアントを指定できるが、anyで良い気がする
clients { any; };
};
};
service named restart
をして再起動
UNBOUND の場合は、/etc/unbound/unbound.conf に
server:
# dns64をつければ後の文字はなんでも良いっぽい
module-config: "dns64 validator iterator"
dns64-prefix: 64:ff9b::/96
を設定して、unbound -c unbound.conf
で設定を読み込み
※参照サイト:
1.オライリー DNS64 ページ
2.unbound 公式 DNS64 ドキュメント
Nginx の config を修正
/etc/nginx/nginx.conf のlisten 80;
の下に以下のような記述があるか確認。
# IPv6のポート80でリッスンするよ〜という記述
listen [::]:80;
パケットフィルタリング
ip6tables という IPv6 の iptables コマンドで確認ができるらしい!(参照)
ip6tables -L
アプリケーションに必要なポートを web サーバ側で許可する
# TCP80番ポートのアクセス許可
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
※参考※
- NAT64:IPv6 ホストが IPv4 サーバーと通信することができるようにする技術(わかりやすい仕組み解説)
解説
unbound と Apache2 の設定が適切になされていないことで起こる問題です。
unbound
dns64-prefix
は ubuntu-router
の JOOL
の設定を参照する必要があります。これを確認すると dns64-prefix
が 64:ff9b::/96
で有ることがわかります。(jool global display
コマンドのpool6
で prefix が確認できそう(参照))
また、blog.icttoracon.net
ではデュアルスタック方式を採用しているおり dns64-synthall
が no に設定されていると、blog.icttoracon.net
にもともと設定されている IPv6 アドレスが AAAA レコードとして名前解決されるため、 ubuntu-1
からアクセスすることができなくなってしまいます。(元から AAAA レコードに何かアドレスが入っていたっぽいな。dns64-synthall のオンオフによる挙動はここら辺に載っている)
さらに、この dns-1
のサーバーは NAT64
ネットワーク内に設置されているために forward-addr が 8.8.8.8
になっていると通信が行えません。forward-addr: 64:ff9b::808:808
に変更する必要があります。(そうだったのか^^;;)
問題が発生している原因は 2 つ存在している。1 つ目は、unbound に DNS64 の設定が正しくされていない点である。これを解決するために /etc/unbound/unbound.conf.d/dns.conf
を以下のように変更する。(公式の example.conf 参照)
server:
# ログレベル
verbosity: 2
# pidfileの場所指定
pidfile: "/var/run/unbound.pid"
# ログを出すかどうか
use-syslog: yes
# さっき書いた通りでdns64を使用する場合はかく
module-config: "dns64 iterator"
# prefixを指定する
dns64-prefix: 64:ff9b::/96
# yesにするとDNS64やNAT64を通るようになる
dns64-synthall: yes
interface: ::0
access-control: ::0/0 allow
forward-zone:
# `.`にすると全て転送される
name: "."
# 8.8.8.8のプレフィックス付きIPv6アドレス。
forward-addr: 64:ff9b::808:808
これにより https://blog.icttoracon.net/
にアクセスできるようになる。
2 つ目の原因は ubuntu-router
の Web (Apache2) の Listen Address が適切に設定されていないことにある。これを解決するために以下の一行を config に追記する。
Listen [::0]:80
※参考※
- NAT64:IPv6 ホストが IPv4 サーバーと通信することができるようにする技術(わかりやすい仕組み解説)
- JOOL:Linux 向け NAT64 オープンソース(参照)
適当に俳句投稿サービス作ったらXSRF脆弱性孕んでた件。
解いた人:maimai-y
参照した問題・解説のサイト:適当に俳句投稿サービス作ったらXSRF脆弱性孕んでた件。
問題文
俳句投稿サービスHikerを作成した。Hikerでは、ユーザー作成後ログインして俳句を詠むことができる。詠んだ俳句は公開される。また、他のユーザーが詠んだ俳句に対してmogamigawaする機能(所謂お気に入り機能)がある。
ユーザーからのフィードバックで、意図しない俳句がmogamigawaされており困っているという情報が複数あった。それらのユーザーは共通して特定のWebサイトを閲覧したようである。 以上のことからHikerはXSRF脆弱性を孕んでいることが予想される。これらのユーザーはこの脆弱性を利用して、意図しない俳句をmogamigawaさせられたと考えられる。
任意の方法でこの脆弱性に対して対策を施してMerge Requestを建ててほしい。
使用環境・ツール
- サーバーサイド: Golang, Gin // GinはGo言語の人気webフレームワーク
- フロントエンド: multitemplate + Bootstrap4
- データベース: MySQL + GORM
- docker-compose
初期状態
- 各チームのVMはHikeのステージング環境である。remoteRepositoryは削除されているので、各自でfork先urlを設定すること。
- url: https://gitlab.com/ictsc2019-teamチーム番号/ictsc2019-f21-xsrf
- urlのチーム番号部分には1,2,3,…,15のような自分のチーム番号を代入すること
- 既に
ictsc
というユーザーが俳句を投稿している - Hikeで各自アカウントを新規作成後、ログインし https://hackmd.io/tRks_vT-QjasH9hUsEJ-BA?view にアクセスすると
ictsc
というユーザーの俳句をmogamigawaしてしまう - ソースコードはGitLabで管理されており、問題解答開始時にチームリーダーにOWNER権限のinviteのメールが送信される
- Hikerが動いているときは、http://192.168.15.1:8080 でサービスにアクセスできる
終了状態
- 適切なXSRF対策がされている
- 初期状態に示されているURL上の検証コードはあくまで一例であることに注意すること
- 修正されたソースコードのMerge RequestをGitLab上で作成する // Merge RequestはGitHubでいうPull Request
- スコアサーバに、Merge RequestのURLを提出する
解決方法
CSRFとは
CSRF ... Cross-Site Request Forgeries/クロスサイト・リクエスト・フォージェリ(偽サイトを使ってリクエストを偽造する)
https://medium-company.com/%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%AA/
他の参考サイト:
https://www.ipa.go.jp/security/vuln/websecurity-HTML-1_6.html
https://qiita.com/wanko5296/items/142b5b82485b0196a2da#csrf%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B
解決の流れ
CSRFへの代表的な対策
Formページ返却時のトークン付与 今回の例でいうと、はじめに掲示板への書き込み画面を表示する際にサーバがクライアントに対して特定の文字列(トークン)を設定します。実際に書き込みのリクエストがあった際にサーバーが**「この人に送ったトークンと同じトークンがリクエストに入ってる?」**と確認することで、攻撃者からの不正なリクエストを防ぐことができます。これは、攻撃者は利用者に送信したトークンの値を知らないためです。
- mogamigawaする画面を要求されたら、暗号論的擬似乱数生成器を用いて機密情報を作るようにする
- 機密情報も入れてmogamigawaする画面を返すようにする
- mogamigawaするときに、hiddenタグで機密情報も送るようにする
- セクションIDと機密情報があっているか確認するようにする
解説
ハッカソン的なイベントでよく適当にwebサービスを作ると思います。作りますね。そんなときのあるあるですが、割とWebのセキュリティを考えずにデプロイして成果発表みたいなノリです。良くないですね。そんな問題でした。
XSRF脆弱性の対策をします。様々な手法が考えられますが、今回はWeb Application Framework(WAF)にGinを採用しているので、utrack/gin-csrfを使って対策するケースで解説します。 utrack/gin-csrfで実装する理由は、問題環境はステージング環境でありdocker-composeで管理されていることから、様々な場所で実行されることを考慮して、utrack/gin-csrfのような環境に依存しにくい実装をしたいためです。
リモートリポジトリを追加する
ステージング環境にデプロイされているソースコードのディレクトリに移動してリモートリポジトリを追加します。
$ git remote add origin https://gitlab.com/ictsc2019-teamチーム番号/ictsc2019-f21-xsrf
この問題では解答にMerge Requestを建てなければいけないので、cloneしたときかこのタイミングでbranchを切ります。
$ git checkout -b xsrf-fix
$ git push --set-upstream origin xsrf-fix // これを書くと毎回origin xsrf-fixの部分を書かなくて良くなるっぽい
あとはcommit~~と徳~~を積んでMerge Requestをします。
server.go
の修正
utrack/gin-csrfのREADME.md
を参考に頑張ります。 // https://github.com/utrack/gin-csrf#readme
変更点は次の通りです。
import部分の追記
"net/http"
を追加しました。csrfのErrorFuncでhttp.StatusBadRequest
を使用するためです。"github.com/utrack/gin-csrf"
を追加しました。gin-csrfを使います。"ictsc2019-f21-xsrf/util"
を追加しました。csrfのSecretをランダムな文字列にする関数をapp/util/util.go
に追記して、それを使用するためです。
import (
"log"
"net/http"
"path/filepath"
"github.com/gin-contrib/multitemplate"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
csrf "github.com/utrack/gin-csrf"
"ictsc2019-f21-xsrf/domain"
"ictsc2019-f21-xsrf/handler"
"ictsc2019-f21-xsrf/util"
)
func main部分の変更
以下の記述を追加します。utrack/gin-csrfのREADME.md
の通りです。
// csrf
r.Use(csrf.Middleware(csrf.Options{
Secret: util.RandString(10),
ErrorFunc: func(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{
"error": "CSRF token mismatch",
})
c.Abort()
},
}))
そして、mogamigawaするAPIをPOSTに変更します。
apiRouter.POST("/mogamigawa", handler.NewMogamigawa)
以上がserver.go
の更新作業になります。
util.go
に追記
server.go
で呼び出されているutil.RandString(n int)
を、util.go
に作成します。
"math/rand"
を追加でimportしてください。
const rs2Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = rs2Letters[rand.Intn(len(rs2Letters))]
}
return string(b)
}
RandString(n int)を叩くことでn文字のランダムな文字列を返すことができます。これをcsrfのSecretに使います。
このSecretですが、文字列が固定されている解答がありました。これは第三者から推測が困難ではないかもしれないので適切ではありません。(減点はしてません)
RandString(n int)を叩くことでn文字のランダムな文字列を返すことができます。これをcsrfのSecretに使います。
このSecretですが、文字列が固定されている解答がありました。これは第三者から推測が困難ではないかもしれないので適切ではありません。(減点はしてません)
XSRF対策をする
今回は全てのFORM要素にhiddenなinputを用意して、そこにtokenを持たせ、送信させることにします。
hikeline.html
の修正
17行目にhiddenなinputを追加します。
<form method="POST" action="api/newhike">
<input type="hidden" name="_csrf" value="{{ .csrfToken }}">
また、hikeline.html
のmogamigawaのbutton部分は以下のように入れ替えます。この変更でmogamigawaのAPIをPOSTにした変更に対応し、XSRF対策ができます。
<!-- mogamigawa button -->
<div>
<form action="/api/mogamigawa?hike_id={{ .Hike_id }}" method="POST">
<input type="hidden" name="_csrf" value="{{ $.csrfToken }}">
<button type="submit">
<a href=""><i class="fas fa-water"></i></a>
</button>
<style>
button {
padding: 0, 0;
border-style: none;
}
</style>
</form>
</div>
signin.html
とsignup.html
にもformがありますが、ここはXSRFから保護しなければならない場所ではありませんよね?
55%の採点を受けたチームは、この部分でapi/mogamigawa
の対策はできてるんだけど、api/newhike
の対策がなされてない解答になっていました。
以上がhtmlの修正作業になります。
res.go
の修正
htmlに{{ .csrfToken }}
という新しいプレースホルダーを追加しました。これに対応して/app/handler/res.go
を更新します。
"github.com/utrack/gin-csrf"
を追加でimportしてください。gin-csrfを使います。
hikeline.html
を返す部分のc.HTML(http.StatusOK, hikeline.html, gin.H{})
に、以下のように記述を追加します。冗長なので、全箇所の記述は省略します。
c.HTML(http.StatusOK, hikeline.html, gin.H{
"csrfToken": csrf.GetToken(c),
})
Merge Request
以上の変更をcommitしたらpushして、GitLabでMerge Requestを建てます。
解説は以上です。
採点基準
- 適切なMerge Requestがなされている: +10%
- あまりにも杜撰な解答は許されません。:angry:
- 任意の手法でXSRF対策をしている: +90%
- mogamigawaのAPIとフロントエンドにのみ修正を加えた場合は半分の +45% にしています。
- mogamigawaのAPIに加えてXSRF対策の必要なnewhikeのAPIにも対策をした場合に +90% としました。
- 適切なXSRF対策がなされている場合にのみ点数を取れるようにしました。
講評
10チームが解答を提出してくれました。
参照した問題・解説のサイト: https://blog.icttoracon.net/2019/08/31/
接続が不安定になっちゃった!
解いた人:nonnonno
参照した問題・解説のサイト:接続が不安定になっちゃった!
使用環境・ツール
何台かのサーバ
問題文でされた操作・バグの内容
通常用セグメント 192.168.1.0/24 と、管理用セグメント 192.168.2.0/24 を持ったネットワーク上にいくつかのサーバがある。client1をこのネットワークに追加し設定したところ、client1とclient2間の通信が不安定になってしまった(通常用セグメントを使うとclient1とclient2の接続が不安定になるということっぽい)。 管理用セグメント (192.168.2.0/24) からは正常にアクセスできるため、こちらからsshすること。
理想の終了状態
192.168.1.0/24 のセグメントで正常に通信が行えるようにし、今後同じ状況にならないように設定を書き換えて、原因を報告してほしい。
考えられる原因とその検証・修正手順
解説
本問題は、動的割当のホストと静的割当のホストのIPアドレスが重複してしまい、通信が不安定になるトラブル。
この問題はVyOSの設定のservice dhcp-server global-parameters ‘ping-check false;’という項目でDHCPによるアドレス割り当ての前にアドレスの使用状況を確認する動作が無効化されていたため、静的割り当てのホストと同じアドレスがDHCPによって払い出されていた。
解答例
VyOS設定コマンド参考リンク
VyOSとclientの関係参考リンク(こちらにもコマンド説明あり)
ルータに対して以下の操作を行う。
configure
で設定モードに入る。
delete service dhcp-server global-parameters ‘ping-check false;’
として、 commit
と save
をして完了。
次に、クライアント端末に対して動的割り当ての設定を行う。
sudo dhclient -r
sudo dhclient
dhclientコマンドは、DHCPプロトコルを利用し、NICにIPアドレスを設定したり、借り受けたIPアドレスの解放や、デフォルトルーターやネームサーバーの情報を確認する目的にも利用される。
今回は、rオプションを使うことで現在借り受けているIPアドレスを開放し、再度DHCPクライアントとして起動してアドレスの付与を受ける。
採点基準
- 正常に通信を行えるかどうか
- 原因を特定し、今後同じ状況にならないような設定にしているか
クライアントのどちらか片方のアドレスをnetplanで静的に書き換えても正常に通信はできますが、問題文に今後同じ状況にならないように設定してください。と記載されているため、DHCPの設定まで直して満点。
APIが飛ばないんですけど
解いた人:maimai-y
参照した問題・解説のサイト:APIが飛ばないんですけど…
問題文
Webアプリケーションからドメインが異なるAPIにリクエストを発行する際には、クロスオリジンについて注意する必要があります。 CORS (Cross-Origin Resource Sharing) に関する以下の問いについて、それぞれ適切な選択肢を選んでください。
問1
問題文
https://example.com
と同じOriginを選んで下さい。
- A. https://example.com/hoge
- B. https://example.com:8080
- C. https://hoge.example.com
- D. http://example.com
解答
オリジン Origin は、ウェブコンテンツにアクセスするために使われる URL のスキーム (プロトコル)、 ホスト (ドメイン)、 ポート によって定義されます。 スキーム、ホスト、ポートがすべて一致した場合のみ、二つのオブジェクトは同じオリジンであると言えます。
A
理由:
B サーバーは既定で80番ポートで HTTP コンテンツを配信するため https://example.com
は https://example.com:80
と同じ
C ホストが異なる
D プロトコルが異なる
解説
ポート番号、プロトコル(HTTP か HTTPS か)、ホストが一致するときのみ同一のOriginとなります。したがって https://example.com/hoge
のみが正解です。
参考: https://developer.mozilla.org/ja/docs/Glossary/Origin
問2
問題文
app.ictsc
で動いているアプリケーションから api.ictsc
へ以下のような fetch()
を実行したところ、CORSのエラーで正常に動きませんでした。 api.ictsc
に設定する必要があるHTTP response headerをすべて選んでください。
fetch({
method: "POST",
headers: {
"Content-Type": "application/json"
},
"body": JSON.stringify(data)
})
- Access-Control-Allow-Origin
- Access-Control-Allow-Headers
- Access-Control-Allow-Methods
解答
全部
CORS (Cross-Origin Resource Sharing) とは...:
あるオリジンで動いている Web アプリケーションに対して、別のオリジンのサーバーへのアクセスをオリジン間 HTTP リクエストによって許可できる仕組みのこと
Access-Control-Allow-Origin:
指定されたオリジンからのリクエストを行うコードでレスポンスが共有できるかどうかを示します。
Access-Control-Allow-Headers:
実際のリクエストの間に使用できる HTTP ヘッダーを示すために使用されます。
Access-Control-Allow-Methods
リソースにアクセスするときに利用できる1つまたは複数のメソッドを指定します。
例:
Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Methods: *
解説
問題文中の fetch では https://api.ictsc
へ https://app.ictsc
から POST リクエストが実行されます。これはホストが異なるため、異なるOriginへのリクエストになるので、応答の HTTP ヘッダに Access-Control-Allow-Origin
が必要です。
リクエストには Content-Type
ヘッダが含まれており、その値が application/json
になっています。Content-Type
が以下の3つの値以外のときは、実際のリクエストの前に preflight request が発行されます。
application/x-www-form-urlencoded
multipart/form-data
text/plain
preflight request は OPTIONS
メソッドで行われ、サーバ側の許可するメソッドやヘッダが応答の HTTP ヘッダ内の情報として返されます。問題文のように Content-Type: application/json
のリクエストを送る場合は、サーバ側で prefilght request への応答の HTTP ヘッダに Access-Control-Allow-Headers: Content-Type
と設定しておく必要があります。
一方で、メソッドがGET
, HEAD
, POST
の場合は preflight request への応答の HTTP ヘッダに Access-Control-Allow-Methods
をつける必要はありません。
したがって、設定するべきヘッダは Access-Control-Allow-Origin
と Access-Control-Allow-Headers
となります。
参考: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Preflighted_requests
問3
問題文
選択肢に示すHTTPメソッドのうち、いかなる場合においてもpreflight requestが行われるものを選んでください。
- GET
- POST
- HEAD
- DELETE
解答
DELETE
CORS のプリフライトリクエストは CORS のリクエストの一つであり、サーバーが CORS プロトコルを理解していて準備がされていることを、特定のメソッドとヘッダーを使用してチェックします。
プリフライトリクエストはブラウザーが自動的に発行するものであり、通常は、フロントエンドの開発者が自分でそのようなリクエストを作成する必要はありません。これはリクエストが"to be preflighted"と修飾されている場合に現れ、単純リクエストの場合は省略されます。
「単純リクエスト」は、以下のすべての条件を満たすものです。
解説
preflight request の概要については問2の解説で説明したとおりです。メソッドが以下に挙げるものの場合は、必ず preflight request が発行されます。
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
したがって正解は DELETE
となります。
問4
問題文
preflight requestについて示した文章のうち、正しいものを全て選んでください。
- リクエスト元のドメインとリクエスト先のドメインが同じ場合は、いかなる場合においてもpreflight requestは行われない。
- クロスオリジンで独自HTTPメソッド
TEST
を発行するためには、Access-Control-Allow-Methods
に*
を追加することで必ず正しく動く。 - preflight requestに対する応答は、
Access-Control-Allow-*
ヘッダの内容が正しいHTTP responseであれば他の内容はなんでもよい。 Access-Control-Allow-Origin
に*
を設定しておけば、他のヘッダが適切である限りいかなる場合でも動作する。
解答
x x x o
- Access-Control-Allow-Methods
*
(ワイルドカード)- "
*
" の値は、資格情報のないリクエスト (HTTP Cookie や HTTP 認証情報のないリクエスト) の特殊なワイルドカードです。
解説
リクエスト元のドメインとリクエスト先のドメインが同じ場合は、いかなる場合においてもpreflight requestは行われない。
ドメインが同じであってもOriginが同じであるとは限りません。ポート番号やプロトコルが異なる場合は異なるOriginとなります。Originが異なる場合、特定の条件を満たせばpreflight requestが行われるため、この文章は間違っています。
クロスオリジンで独自HTTPメソッド
TEST
を発行するためには、Access-Control-Allow-Methods
に*
を追加することで必ず正しく動く。
Access-Control-Allow-Methods: *
と設定した場合の独自メソッドの動作は実装に依存しています。Ubuntu 18.0.4 上で Python3.6.8 の Bottle v0.12.17 によりHTTPサーバをhttp://localhost:8090
, http://localhost:8080
に立てて、前者から後者に JavaScript の fetch でリクエストを送って検証しました。Chromium 76.0.3809 で TEST
リクエストを行ってみると成功しますが、FireFox 68.0.1 では失敗しました。 したがって、「必ず正しく動く」とするこの文章は間違っています。
参考: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS/Errors/CORSMethodNotFound
preflight requestに対する応答は、
Access-Control-Allow-*
ヘッダの内容が正しいHTTP responseであれば他の内容はなんでもよい。
preflight request に対する応答のステータスコードが200番台でない場合、リクエストを送ることができません。上記と同様の検証環境で、preflight requestに対するHTTP responseのステータスコードを404にするとPOSTリクエストが飛ばないことを確かめられました。したがってこの文章は間違っています。
参考: https://fetch.spec.whatwg.org/#cors-preflight-fetch
Access-Control-Allow-Origin
に*
を設定しておけば、他のヘッダが適切である限りいかなる場合でも動作する。
リクエストにCookieなどのリクエスト情報が含まれている場合、Access-Control-Allow-Origin: *
というワイルドカードの指定ではなく、具体的なOriginの指定が必要です。したがって、「いかなる場合でも動作する」とするこの文章は間違っています。
参考: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Requests_with_credentials
以上より、全ての文が間違っているので、何も選択しないのが正解です。
参照した問題・解説のサイト: https://blog.icttoracon.net/2019/12/10/
君k8s得意って言っていたよね?
解いた人:とり
参照した問題・解説のサイト:君k8s得意って言っていたよね?
使用環境・ツール
- Kubernetes
- Redmine
- MariaDB
Redmine
プロジェクト管理ができるオープンソースソフトウェア。Dockerに公式イメージが存在する。 Redmine
MariaDB
MariaDBは、MySQL派生として開発されている、オープンソースの関係データベース管理システムである。RDBMS。
https://mariadb.org/
問題文でされた操作
- Redmineは指定のManifest(Redmine_Manifest)でデプロイしてください。
- Redmine_Manifestは変更出来ません。
- Redmine_Manifest内のコンテナイメージはcontainer-registryから取得してください。
- マニフェストの再適用, OSの再起動の操作は可能です。
- 誤操作等で競技続行不可の場合は出題時環境への復元のみ承ります。 Kubernetes上にRedmineサービスを稼働させる問題です。 出題時にはRedmineを構成するRedmine-Pod, MariaDB-PodがPendingとなっており、利用不可の状態です。 コンテナが稼働しない原因を突き止め対処することでRedmineサービスを稼働させることができます。
問題解決のために以下の原因を解決する必要があります。
- masterへpodのデプロイに関するtaints(テインツ)の削除
- コンテナランタイムcri-oにinsecure-registryの設定を追加
- MariaDBのPersistentVolumeのディレクトリ権限(Permission)を修正
理想の終了状態
- VNCクライアントのブラウザからRedmineが閲覧できること。
http://192.168.0.100:30000
- Redmineのデータがコンテナ再起動時にも保持されていること。
情報
- Server:
- k8smaster1:
- ip: 192.168.0.100
- userid: root
- password: USerPw@19
- container-registry:
- ip: 192.168.0.101
- 備考: 操作不可
- Redmine_Manifest:
- path: “/root/ictsc_problem_manifests/*.yaml”
- Redmineログイン情報
- userid: ictsc
- password: USerPw@19
コメント
manifestファイルが欲しい〜〜〜〜〜〜
考えられる検証、修正手順
バグの原因を特定する案
kubectl describe pods Redmine-Pod
kubectl describe pods MariaDB-Pod
で何が原因で動かないかをはじめに調べる- 問題文にこのような文章があったため、これに沿って設定を行ってみることにする
1. masterへpodのデプロイに関するtaintsの削除
2. コンテナランタイムcri-oにinsecure-registryの設定を追加
3. MariaDBのPersistentVolumeのディレクトリ権限(Permission)を修正
masterへpodのデプロイに関するtaintsの削除
Taintsとは
- Tolerationsとセットで扱う
taintは"汚れ"という意味。tolerationは"容認"という意味。つまり"汚れ"を"容認"できるならscheduleできる仕組み toleration はPodに適用され、一致するtaintが付与されたNodeへpodが不適当なnodeにscheduleされないようにする。 Node Affinityなどの場合には、それらが未指定の場合はどこのNodeにでもscheduleされてしまうが、TaintsとTolerationsの場合は指定しない限りそのNodeにscheduleされることはない。 1つもしくは複数のTaintsをnodeに設定することができ、podには1つもしくは複数のtolerationsを設定することができる。
kubectl taint nodes node1 key1=value1:NoSchedule
node1にはこんな感じで適用させる。
kubectl taint nodes node1 key1=value1:NoSchedule-
外すには以上のコマンドを利用する。
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
こんな感じでマニフェストファイルの書かれているため、削除自体はファイルを参考にできそう。
コンテナランタイムcri-oにinsecure-registryの設定を追加
CRI-Oとは
CRI-Oとは、コンテナ型仮想化で使われる技術の1つで、Kubernetesとコンテナランタイムが通信するための仕様として規定されているCRI(Container Runtime Interface)と、OCI Runtime Specificationに基づいて作られたKubernetesやDockerの高レベルなランタイム。CNCFで開発が行われ、オープンソースソフトウェア。コード見たらGoで書かれてた。
insecure-registryの設定を追加
insecure-registry ... Registryとの非セキュアな通信を許可するオプションとして--insecure-registryオプションが存在する。Registryが暗号化されていないhttp通信の場合に必要な設定。
一つ目のドキュメントをみると、cri-oはdaemon.json
を使っているみたいなので、二つ目のドキュメントの参考をそのまま使うことができそう。
$ vi /etc/docker/daemon.json
{ "insecure-registries":["172.16.1.100:5000"] } # プライベートレジストリを指定 このリンクは任意
$ systemctl restart docker
$ systemctl restart crio # これもやっておいた方がいいかも?
# 起動確認例
$ docker pull 172.16.1.100:5000/ubuntu:16.04
$ docker run \
> -it \
> --rm \
> --name c1 \
> 172.16.1.100:5000/ubuntu:16.04 cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.2 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.2 LTS"
VERSION_ID="16.04"
参考:
- https://kubernetes.io/ja/docs/setup/production-environment/container-runtimes/
- https://www.itmedia.co.jp/enterprise/articles/1708/25/news014_2.html
MariaDBのPersistentVolumeのディレクトリ権限(Permission)を修正
これはよくわかんないけど、yamlを読んで修正をするのか、PersistentVolumeのhostPath
の権限を見直すのかなのかな。よくわかんない。
kind: PersistentVolume
apiVersion: v1
metadata:
name: my-pv-hostpath
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data
解説
流れまで書いてあって丁寧だなと思った。
解決手順
masterへpodのデプロイに関するtaintsの削除
kubectl get pod
でコンテナの状態を見ます。
[root@k8smaster1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
my-release-mariadb-0 0/1 Pending 0 9d
my-release-redmine-859cf77958-n95j5 0/1 Pending 0 9d
→ Pendingになっていることがわかる
kubectl describe pod <pod名>
で各Podを確認する- あってた
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 9d (x5 over 9d) default-scheduler 0/1 nodes are available: 1 node(s) had taints that the pod didn't tolerate.
- nodeのtaintsをpodが許容できないということなので、nodeのtaintsを
kubectl describe nodes
で確認します。
[root@k8smaster1 ~]# kubectl describe nodes
Name: k8smaster1
Roles: master
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=k8smaster1
kubernetes.io/os=linux
node-role.kubernetes.io/master=
Annotations: kubeadm.alpha.kubernetes.io/cri-socket: /var/run/crio/crio.sock
node.alpha.kubernetes.io/ttl: 0
volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp: Sat, 23 Nov 2019 19:58:55 +0900
Taints: node-role.kubernetes.io/master:NoSchedule
- 一番最後の行で
node-role.kubernetes.io/master:NoSchedule
とあるため、Podがスケジューリングできない。 - Taintsを削除する
[root@k8smaster1 ~]# kubectl taint nodes k8smaster1 node-role.kubernetes.io/master:NoSchedule-
node/k8smaster1 untainted
⏫ こんな感じで-
つけるだけでも削除できるんだね。便利〜〜〜。
コンテナランタイムcri-oにinsecure-registryの設定を追加
kubectl get pod
で確認する
[root@k8smaster1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
my-release-mariadb-0 0/1 ImagePullBackOff 0 9d
my-release-redmine-859cf77958-n95j5 0/1 ImagePullBackOff 0 9d
- また
describe
する
Failed to pull image "private-registry.local/bitnami/mariadb:10.3.20-debian-9-r0": rpc error: code = Unknown desc = pinging docker registry returned: Get https://private-registry.local/v2/: dial tcp 192.168.0.101:443: connect: no route to host
- ホストへのルートがない?のかな
MariaDBのPersistentVolumeのディレクトリ権限(Permission)を修正
kubectl get pod
をする
[root@k8smaster1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
my-release-mariadb-0 0/1 Error 5 9d
my-release-redmine-859cf77958-n95j5 0/1 Running 1 9d
- mariadbのエラー
- ログを確認できるらしい
[root@k8smaster1 ~]# kubectl logs my-release-mariadb-0
16:43:21.98
16:43:21.98 Welcome to the Bitnami mariadb container
16:43:21.98 Subscribe to project updates by watching https://github.com/bitnami/bitnami-docker-mariadb
16:43:21.98 Submit issues and feature requests at https://github.com/bitnami/bitnami-docker-mariadb/issues
16:43:21.98 Send us your feedback at containers@bitnami.com
16:43:21.99
16:43:21.99 INFO ==> ** Starting MariaDB setup **
16:43:22.04 INFO ==> Validating settings in MYSQL_*/MARIADB_* env vars
16:43:22.04 INFO ==> Initializing mariadb database
mkdir: cannot create directory '/bitnami/mariadb/data': Permission denied
create
ができない権限不足- これ見たらmkdirしようと思っちゃうけど解法は違う
/root/ictsc_problem_manifests
にあるk8sManifestを読み解くと、/var/opt/pv{1,2}
にPersistentVolumeがある- そこに権限を渡せばいい。
kubectl get pv
のmariadb
の対応するPathに権限を付与するkubectl get pv
で
[root@k8smaster1 ictsc_problem_manifests]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv0001 20Gi RWO Recycle Bound default/data-my-release-mariadb-0 9d
pv0002 20Gi RWO Recycle Bound default/my-release-redmine 9d
[root@k8smaster1 ]# chmod -R 777 /var/opt/pv1/
終わりに
kubectl describe
は偉大。Taints
理解した。
MySQLの復旧をお願いします!!
解いた人:Hunachi
参照した問題・解説のサイト:MySQLの復旧をお願いします!!
使用環境・ツール
環境
- IPアドレス: 192.168.0.1
- ユーザー: admin
- パスワード: USerPw@19
- DBユーザー: root
- DBパスワード: root
状況
- このMySQLは毎日定時に
sysbench database
のバックアップを取得していて(コンテスト問題の作成上truncate table文が実行された日まで)、偶然truncate文が実行される(数分)前にこの日のバックアップが完了していた - バックアップは以下のコマンドで取得されている
mysqldump --opt --single-transaction --master-data=2 --default-character-set=utf8mb4 --databases sysbench > /root/backup/backup.dump
mysql -u root -p < /root/backup/backup.dump
でバックアップが取得された時点に復旧できる- adminユーザからsudo suすることでrootユーザから操作してください
問題
問1
truncate table sbtest3;
というクエリが実行された日時をyymmdd HH:MM:SS
のフォーマットで報告してください。
また、どのようにこの日時を特定したかを説明してください。
問2
truncate table
が実行される直前の状態(truncate table
が実行される1つ前のクエリが実行された状態)にデータを復旧し、復旧後checksum table sysbench.sbtest3;
の結果を報告してください。
また、データの復旧に必要な手順を説明してください。
技術調査
問1について
- 実行されたmysqlコマンドの履歴を表示する方法
cat ~/.mysql_history
で実行されたmysqlコマンドの履歴を表示することができるはず。参考
これで試した結果(dockerで環境作ってクエリを適当に打った)
# cat ~/.mysql_history
_HiStOrY_V2_
show\040databases;
quit;
\040はASCIIコードのスペースだけど、見づらいのでこれはスペースで表示させるようにする。
# sed "s/\\\040/ /g" ~/.mysql_history
_HiStOrY_V2_
show databases;
quit;
日時が表示されてないのでだめ!
どうやらデフォで日時を取得する方法ないかも。。? https://forums.mysql.com/read.php?10,400933 https://forums.mysql.com/read.php?10,400933,401057#msg-401057
そこで、
general_log
が有効になっているかを確認する。
show variables like 'general_log';
もし、
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| general_log | ON |
+---------------+-------+
1 row in set (0.00 sec)
ならラッキー、いける。 OFFなら自分にはお手上げ😭🙌
ONだったら、
show variables like 'general_log_file';
でわかるファイルをみてみる。
実験した
- デフォではOFFだったので、
mysql> set global general_log = 'ON';
を設定。 - 色々コマンドを打つ。
cat /var/lib/mysql/${general_log_file's value}.log
- 表示される例。
/usr/sbin/mysqld, Version: 8.0.25 (MySQL Community Server - GPL). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
2021-08-16T08:48:21.048791Z 15 Query SELECT DATABASE()
2021-08-16T08:48:21.049349Z 15 Init DB hunadb
2021-08-16T08:48:21.052633Z 15 Query show databases
2021-08-16T08:48:21.053892Z 15 Query show tables
2021-08-16T08:48:21.055342Z 15 Field List piyo
2021-08-16T08:48:32.444434Z 15 Quit
2021-08-16T08:48:34.020718Z 16 Connect root@localhost on using Socket
2021-08-16T08:48:34.021351Z 16 Query select @@version_comment limit 1
2021-08-16T08:48:36.401359Z 16 Query show variables like 'general_log'
2021-08-16T08:48:46.839490Z 16 Query create table hunadb.piyo (id int, cost int)
2021-08-16T08:49:06.218438Z 16 Query create table hunadb.hiyo (id int, cost int)
2021-08-16T08:49:43.064247Z 16 Query truncate table hunadb.piyo
2021-08-16T08:49:48.068001Z 16 Quit
↑の日時を解答として提出すれば良さそう!!
問2について
状況にて、バックアップの復旧方法を教えてくれてるのでそれをする。(括弧内は自分が打ったコマンド。ログも実際も私の物。)
mysqldump --opt --single-transaction --master-data=2 --default-character-set=utf8mb4 --databases sysbench > /root/backup/backup.dump
(を打つべきだけどこの時は手を抜いてmysqldump hunadb > dump.sql
)mysql -u root -p < /root/backup/backup.dump
(実際に打ったのは、mysql hunadb < dump.sql
).mysql_history
を確認してみる。
/usr/sbin/mysqld, Version: 8.0.25 (MySQL Community Server - GPL). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
2021-08-16T08:48:21.048791Z 15 Query SELECT DATABASE()
2021-08-16T08:48:21.049349Z 15 Init DB hunadb
2021-08-16T08:48:21.052633Z 15 Query show databases
2021-08-16T08:48:21.053892Z 15 Query show tables
2021-08-16T08:48:21.055342Z 15 Field List piyo
2021-08-16T08:48:32.444434Z 15 Quit
2021-08-16T08:48:34.020718Z 16 Connect root@localhost on using Socket
2021-08-16T08:48:34.021351Z 16 Query select @@version_comment limit 1
2021-08-16T08:48:36.401359Z 16 Query show variables like 'general_log'
2021-08-16T08:48:46.839490Z 16 Query create table hunadb.piyo (id int, cost int)
2021-08-16T08:49:06.218438Z 16 Query create table hunadb.hiyo (id int, cost int)
2021-08-16T08:49:43.064247Z 16 Query truncate table hunadb.piyo
2021-08-16T08:49:48.068001Z 16 Quit
2021-08-16T09:43:42.989880Z 17 Connect root@localhost on using Socket
2021-08-16T09:43:42.990214Z 17 Query select @@version_comment limit 1
2021-08-16T10:08:24.906898Z 17 Query SELECT * FROM msdb.dbo
2021-08-16T10:08:34.540831Z 17 Query show databases
2021-08-16T10:10:22.024284Z 17 Quit
--- ここでバックアップを取った --- ( mysqldump hunadb > dump.sql )
2021-08-16T10:10:31.893768Z 18 Connect root@localhost on using Socket
2021-08-16T10:10:31.893799Z 18 Connect Access denied for user 'root'@'localhost' (using password: NO)
2021-08-16T10:11:05.935347Z 19 Connect root@localhost on using Socket
2021-08-16T10:11:05.935406Z 19 Connect Access denied for user 'root'@'localhost' (using password: NO)
2021-08-16T10:11:29.099579Z 20 Connect root@localhost on using Socket
2021-08-16T10:11:29.099723Z 20 Query /*!40100 SET @@SQL_MODE='' */
2021-08-16T10:11:29.099984Z 20 Query /*!40103 SET TIME_ZONE='+00:00' */
2021-08-16T10:11:29.100167Z 20 Query /*!80000 SET SESSION information_schema_stats_expiry=0 */
2021-08-16T10:11:29.100304Z 20 Query SET SESSION NET_READ_TIMEOUT= 86400, SESSION NET_WRITE_TIMEOUT= 86400
2021-08-16T10:11:29.101544Z 20 Query SHOW VARIABLES LIKE 'gtid\_mode'
2021-08-16T10:11:29.103106Z 20 Query SELECT LOGFILE_GROUP_NAME, FILE_NAME, TOTAL_EXTENTS, INITIAL_SIZE, ENGINE, EXTRA FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'UNDO LOG' AND FILE_NAME IS NOT NULL AND LOGFILE_GROUP_NAME IS NOT NULL AND LOGFILE_GROUP_NAME IN (SELECT DISTINCT LOGFILE_GROUP_NAME FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'DATAFILE' AND TABLESPACE_NAME IN (SELECT DISTINCT TABLESPACE_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA IN ('hunadb'))) GROUP BY LOGFILE_GROUP_NAME, FILE_NAME, ENGINE, TOTAL_EXTENTS, INITIAL_SIZE ORDER BY LOGFILE_GROUP_NAME
2021-08-16T10:11:29.110332Z 20 Query SELECT DISTINCT TABLESPACE_NAME, FILE_NAME, LOGFILE_GROUP_NAME, EXTENT_SIZE, INITIAL_SIZE, ENGINE FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'DATAFILE' AND TABLESPACE_NAME IN (SELECT DISTINCT TABLESPACE_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA IN ('hunadb')) ORDER BY TABLESPACE_NAME, LOGFILE_GROUP_NAME
2021-08-16T10:11:29.112213Z 20 Query SHOW VARIABLES LIKE 'ndbinfo\_version'
2021-08-16T10:11:29.114053Z 20 Init DB hunadb
2021-08-16T10:11:29.114225Z 20 Query show tables
2021-08-16T10:11:29.115254Z 20 Query LOCK TABLES `hiyo` READ /*!32311 LOCAL */,`piyo` READ /*!32311 LOCAL */
2021-08-16T10:11:29.116322Z 20 Query show table status like 'hiyo'
2021-08-16T10:11:29.117349Z 20 Query SET SQL_QUOTE_SHOW_CREATE=1
2021-08-16T10:11:29.117507Z 20 Query SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.117621Z 20 Query show create table `hiyo`
2021-08-16T10:11:29.117877Z 20 Query SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.118037Z 20 Query show fields from `hiyo`
2021-08-16T10:11:29.119264Z 20 Query show fields from `hiyo`
2021-08-16T10:11:29.120115Z 20 Query SELECT /*!40001 SQL_NO_CACHE */ * FROM `hiyo`
2021-08-16T10:11:29.120363Z 20 Query SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.120561Z 20 Query use `hunadb`
2021-08-16T10:11:29.120795Z 20 Query select @@collation_database
2021-08-16T10:11:29.121025Z 20 Query SHOW TRIGGERS LIKE 'hiyo'
2021-08-16T10:11:29.122437Z 20 Query SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.122713Z 20 Query SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.122975Z 20 Query SELECT COLUMN_NAME, JSON_EXTRACT(HISTOGRAM, '$."number-of-buckets-specified"') FROM information_schema.COLUMN_STATISTICS WHERE SCHEMA_NAME = 'hunadb' AND TABLE_NAME = 'hiyo'
2021-08-16T10:11:29.123610Z 20 Query SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.123849Z 20 Query show table status like 'piyo'
2021-08-16T10:11:29.124940Z 20 Query SET SQL_QUOTE_SHOW_CREATE=1
2021-08-16T10:11:29.125141Z 20 Query SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.125352Z 20 Query show create table `piyo`
2021-08-16T10:11:29.125741Z 20 Query SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.125958Z 20 Query show fields from `piyo`
2021-08-16T10:11:29.127051Z 20 Query show fields from `piyo`
2021-08-16T10:11:29.128547Z 20 Query SELECT /*!40001 SQL_NO_CACHE */ * FROM `piyo`
2021-08-16T10:11:29.128818Z 20 Query SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.128976Z 20 Query use `hunadb`
2021-08-16T10:11:29.129195Z 20 Query select @@collation_database
2021-08-16T10:11:29.129379Z 20 Query SHOW TRIGGERS LIKE 'piyo'
2021-08-16T10:11:29.130227Z 20 Query SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.130402Z 20 Query SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.130667Z 20 Query SELECT COLUMN_NAME, JSON_EXTRACT(HISTOGRAM, '$."number-of-buckets-specified"') FROM information_schema.COLUMN_STATISTICS WHERE SCHEMA_NAME = 'hunadb' AND TABLE_NAME = 'piyo'
2021-08-16T10:11:29.131010Z 20 Query SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.131201Z 20 Query UNLOCK TABLES
2021-08-16T10:11:29.132686Z 20 Quit
---- 多分ここまでがバックアップのためのコード ----
2021-08-16T10:12:17.133740Z 22 Connect root@localhost on using Socket
2021-08-16T10:12:17.134045Z 22 Query select @@version_comment limit 1
2021-08-16T10:12:27.437396Z 22 Query SELECT DATABASE()
2021-08-16T10:12:27.437700Z 22 Init DB hunadb
2021-08-16T10:12:27.438758Z 22 Query show databases
2021-08-16T10:12:27.439753Z 22 Query show tables
2021-08-16T10:12:27.440834Z 22 Field List hiyo
2021-08-16T10:12:27.442166Z 22 Field List piyo
2021-08-16T10:12:31.924121Z 22 Query show tables
2021-08-16T10:12:40.044363Z 22 Query truncate table hiyo
2021-08-16T10:12:42.415083Z 22 Query show tables
2021-08-16T10:13:24.213758Z 22 Query drop table hiyo
2021-08-16T10:13:26.758317Z 22 Query show tables
2021-08-16T10:13:30.474725Z 22 Quit
---- ここで復元を始めた ---- ( mysql hunadb < dump.sql )
2021-08-16T10:13:58.825694Z 23 Connect root@localhost on hunadb using Socket
2021-08-16T10:13:58.826015Z 23 Query select @@version_comment limit 1
2021-08-16T10:13:58.826260Z 23 Query /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */
2021-08-16T10:13:58.826450Z 23 Query /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */
2021-08-16T10:13:58.826626Z 23 Query /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */
2021-08-16T10:13:58.826805Z 23 Query /*!50503 SET NAMES utf8mb4 */
2021-08-16T10:13:58.827039Z 23 Query /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */
2021-08-16T10:13:58.827220Z 23 Query /*!40103 SET TIME_ZONE='+00:00' */
2021-08-16T10:13:58.827404Z 23 Query /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */
2021-08-16T10:13:58.827586Z 23 Query /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */
2021-08-16T10:13:58.827784Z 23 Query /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */
2021-08-16T10:13:58.827967Z 23 Query /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */
2021-08-16T10:13:58.828183Z 23 Query DROP TABLE IF EXISTS `hiyo`
2021-08-16T10:13:58.832544Z 23 Query /*!40101 SET @saved_cs_client = @@character_set_client */
2021-08-16T10:13:58.832736Z 23 Query /*!50503 SET character_set_client = utf8mb4 */
2021-08-16T10:13:58.832935Z 23 Query CREATE TABLE `hiyo` (
`id` int DEFAULT NULL,
`cost` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
2021-08-16T10:13:58.846132Z 23 Query /*!40101 SET character_set_client = @saved_cs_client */
2021-08-16T10:13:58.846305Z 23 Query LOCK TABLES `hiyo` WRITE
2021-08-16T10:13:58.847102Z 23 Query /*!40000 ALTER TABLE `hiyo` DISABLE KEYS */
2021-08-16T10:13:58.848155Z 23 Query /*!40000 ALTER TABLE `hiyo` ENABLE KEYS */
2021-08-16T10:13:58.849204Z 23 Query UNLOCK TABLES
2021-08-16T10:13:58.849372Z 23 Query DROP TABLE IF EXISTS `piyo`
2021-08-16T10:13:58.857241Z 23 Query /*!40101 SET @saved_cs_client = @@character_set_client */
2021-08-16T10:13:58.857442Z 23 Query /*!50503 SET character_set_client = utf8mb4 */
2021-08-16T10:13:58.857621Z 23 Query CREATE TABLE `piyo` (
`id` int DEFAULT NULL,
`cost` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
2021-08-16T10:13:58.869241Z 23 Query /*!40101 SET character_set_client = @saved_cs_client */
2021-08-16T10:13:58.869410Z 23 Query LOCK TABLES `piyo` WRITE
2021-08-16T10:13:58.870143Z 23 Query /*!40000 ALTER TABLE `piyo` DISABLE KEYS */
2021-08-16T10:13:58.871175Z 23 Query /*!40000 ALTER TABLE `piyo` ENABLE KEYS */
2021-08-16T10:13:58.872237Z 23 Query UNLOCK TABLES
2021-08-16T10:13:58.872430Z 23 Query /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */
2021-08-16T10:13:58.872583Z 23 Query /*!40101 SET SQL_MODE=@OLD_SQL_MODE */
2021-08-16T10:13:58.872726Z 23 Query /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */
2021-08-16T10:13:58.872864Z 23 Query /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */
2021-08-16T10:13:58.873030Z 23 Query /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */
2021-08-16T10:13:58.873174Z 23 Query /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */
2021-08-16T10:13:58.873287Z 23 Query /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */
2021-08-16T10:13:58.873427Z 23 Query /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */
2021-08-16T10:13:58.873506Z 23 Quit
---- ここまでが復元された時のログ ----
2021-08-16T10:14:05.000746Z 24 Connect root@localhost on using Socket
2021-08-16T10:14:05.001258Z 24 Query select @@version_comment limit 1
2021-08-16T10:14:12.021449Z 24 Query SELECT DATABASE()
2021-08-16T10:14:12.021944Z 24 Init DB hunadb
2021-08-16T10:14:12.023879Z 24 Query show databases
2021-08-16T10:14:12.025276Z 24 Query show tables
2021-08-16T10:14:12.026755Z 24 Field List hiyo
2021-08-16T10:14:12.027480Z 24 Field List piyo
2021-08-16T10:14:13.866961Z 24 Query show tables
2021-08-16T10:14:16.344213Z 24 Quit
ログの見方がよくわからないけど、これのバックアップが始まってそうな部分を見つけて、それ以外の部分のlogに書いてあるコマンドを打っていけばいいのでは?(筋肉で解決)
解説
公式の解説が丁寧なのでそちらを参照。
解説に対するメモ
- 'DML' = Data Manipulation Language(select文、insert文など)参考
--base64-output=DECODE-ROWS
はバイナリログが元々ROW形式なので読めるようにするためにつける。-vv
=--verbose --verbose
(詳細なメッセージを表示)-- CHANGE MASTER TO MASTER_LOG_FILE='binlog.000018', MASTER_LOG_POS=34626719;
のbinlog.000018
と34626719
が大事。binlog.000018
からバックアップ後のデータを復旧する。- startpositionは
34626719
になる。 mysqlbinlog
は MySQLのバイナリログの解析に使われる。
試してみた
(私の環境に合わせたコマンドにしてる)
mysqldump --password=passwordh --opt --single-transaction --master-data=2 hunadb > dump.sql
--master-data=2が大事だったとは(ちゃんと調べてなきゃ。。)参考
dump.sql
の-- CHANGE MASTER TO
のとこの情報をバックアップ以降の更新の始まりを知るべく確認。
binlog
もあったのでこれに対して
mysqlbinlog --no-defaults --base64-output=DECODE-ROWS -vv --start-position=$MASTER_LOG_POS binlog.000002 | grep -B 10 truncate
で、出てきた。
truncateを打った時のtimestampがわかったので、
問1は、select from_unixtime(timestamp);
を打てば良さそう。
mysqlbinlog --no-defaults --start-position=$MASTER_LOG_POS --stop-position=$timestamp binlog.000018 | mysql -u root -p
で、復旧の手順は行えることが確認できたと思う。(最初の設定ミスってて、データのバックアップをとってすぐにtrancateしたので確認できなかった😂)
感想
全然違くて🥺 DBの授業取ってたのに🥺
採点基準
- 問1: 30%
- 問2: 70%
Welcome to Nginx のページを表示したい!
解いた人:momom-i
参照した問題・解説のサイト:Welcome to Nginx のページを表示したい!
問題
あなたはローカルネットワーク上に 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 アドレスが設定されていますので必要に応じて使用してください。 予選終了後に実環境で採点されるので、スコアサーバでの解答は不要です。
接続環境
Host | Protocol | IPv4 address | User/Pass |
---|---|---|---|
CSR1000V | telnet | 192.168.0.1 | admin/admin |
nginx | ssh | 192.168.1.2 | admin/admin |
問題文でされた操作
- CSR1000V と nginx のホストに固定で IPv6 アドレスを割り当て
- CSR1000V には SLAAC の設定
バグの内容
http://nginx.icttoracon.net
にアクセスしても Welcome to Nginx のページを表示させることができない
理想の終了状態
Welcome to Nginx のページを表示
補足事項
- 各ノードには ssh/telnet 用に IPv4 アドレスが設定されていますので必要に応じて使用
- 設定変更は CSR1000V と nginx ホストのみ
考えられる原因
まず下記コマンドでクライアントからnginx.icttoracon.net
が引けるか、IPv6 アドレスが引けるか調べる
# AAAAレコードはホスト名に対するIPv6アドレスが登録されている(ちなみにAAAAレコードはクアッドエーレコードって読むらしい!)
# +shortオプションで簡単にIPv6アドレスが登録されてるかどうか以外の情報を省ける
dig -t AAAA nginx.icttoracon.net +short
- もし IPv4 でも引けないなら DNS の設定問題。(問題文的に IPv4 では引けそう?)IPv4 では引けるが IPv6 アドレスが引けなかったら、DNS のゾーンファイルに IPv6 の設定がなされてない。もしどちらも登録されていたら、nginx の問題で config の書き方不備。
クライアントから ping で IPv6 のアドレスにつながるかやってみる
# ping6は `ping -6`と同じ
ping6 nginx.icttoracon.net
- もし通らなければ、SLAAC の自動 ip 割り当てあたりやパケットフィルタリングが問題。通るなら上記の問題になってくる気がする。
考えられる解決方法
DNS のゾーンファイルの不備を修正
# IPv6は省略法で書けるらしい
nginx.icttoracon.net. IN AAAA fc01::2
Nginx の config を修正
/etc/nginx/nginx.conf のlisten 80;
の下に以下のような記述があるか確認
# IPv6のポート80でリッスンするよ〜という記述
listen [::]:80;
SLAAC の自動 IP 割り当て確認
基本的にこのページを参照した
show ipv6 address
で確認できる。もし割り当てがおかしい場合は、Cisco のグローバルコンフィグレーションモード(Cisco のルーターの特権モード。全体の設定に関わるようなことを行う時に入るモード)にconfigure terminal
で入って、
(config)#ipv6 unicast-routing
# インターフェイス名を指定する。スロットは固定で0みたい
(config)#interface gigabitethernet 0/1
(config-if)#ipv6 enable
※Cisco のインターフェイスについてはこちら
パケットフィルタリング
ip6tables という IPv6 の iptables コマンドで確認ができるらしい!(参照)
ip6tables -L
もし 80 番ポートの設定がなければ、今回はクライアント側は変更しないから nginx で以下のコマンドで 80 番ポートの接続を許可する
# TCP80番ポートのアクセス許可
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
※参考※
-
CSR1000V(Cisco Cloud Services Router 1000V):ソフトウェアルータ(参照)
-
SLAAC(スラーク):
IPv6 アドレスを自動設定する技術の一つで、アドレスを発行するサーバなどを用意しなくても当該ネットワーク内のアドレスをホスト自身が設定する方式(概要説明参照 URL, 具体的な挙動のわかりやすい参照 URL(このページの「SLAAC による IPv6 アドレスの設定」)) -
VNC: ネットワークを通じて別のコンピュータに接続し、そのデスクトップ画面を呼び出して操作することができるリモートデスクトップソフトの一つ(参照)
-
IPv6 の記述法について:
例. fc01::2 = fc01:0000:0000:0000:0000:0000:0000:0002/64(参照)
解説
本問題には 3 つの原因があります。 順を追って調べてみましょう。
疎通性確認
まずはクライアントマシンから nginx ホストまで IPv6 で疎通性があるか確認してみます。 簡単な確認ではありますが、トラブル原因のレイヤをある程度限定できます。
# ping6 ドメイン名ってやってたけど、普通にIPv6アドレスでpingするべきだったな^^;
ubuntu@ICTSC-VNC:~$ 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 レコードを取得するコマンドを以下に示します。
ubuntu@ICTSC-VNC:~$ 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
ubuntu@ICTSC-VNC:~$
AAAA レコードは取得できていません。 問題文で DNS サーバは CSR とされていますが、クライアントはどこを参照しているのでしょうか。
# grep -v: invert match
# grep -v "^#": #で始まるもの以外を取り出す = コメントアウト部分以外を取り出す
ubuntu@ICTSC-VNC:~$ cat /etc/resolv.conf | grep -v "^#"
nameserver 127.0.0.53
options edns0
search localdomain
# この/run/systemd/resolve/resolv.confは/etc/resolv.confのシンボリックリンク先
# `nameserver fc00::1`があるはず
ubuntu@ICTSC-VNC:~$ cat /run/systemd/resolve/resolv.conf | grep -v "^#"
nameserver 133.242.0.3
nameserver 133.242.0.4
search localdomain
ubuntu@ICTSC-VNC:~$
IPv4 で DNS サーバを受け取っているようですが、CSR の IP アドレスではありません。 1つ目の原因は DNS サーバ(CSR)が参照できていないことです。
問題文からクライアントの設定変更ではなくルータの設定変更で対応する方針であることがわかります。 クライアントの設定と CSR の設定を確認すると、クライアントの IPv6 アドレスは RA を用いた自動設定であることがわかります。 ただし DNS サーバのアドレスが配布されていません。 RA で IPv6 アドレスが設定されている場合は、以下の 2 つの方法で DNS サーバを配布することができます。
- ステートレス DHCPv6
- RA を用いた DNS 配布(RFC8106)
例として RA のみで DNS の配布を行います。 IOS-XE(Cisco の OS(参考))のコマンドリファレンスを参照すると、以下の設定で DNS の配布が行えそうです。
# conf t: さっきのconfigure terminalの省略形
csr1000v#conf t
Enter configuration commands, one per line. End with CNTL/Z.
# int gi 1 = interface gigabitethernet 0/1と同じ
csr1000v(config)#int gi 1
csr1000v(config-if)#ipv6 nd ra dns server fc00::1
(ipv6 nd ra dns server fc00::1 の説明) クライアントで確認してみます。
ubuntu@ICTSC-VNC:~$ cat /run/systemd/resolve/resolv.conf | grep -v "^#"
nameserver 133.242.0.3
nameserver 133.242.0.4
nameserver fc00::1
search localdomain
ubuntu@ICTSC-VNC:~$ 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
ubuntu@ICTSC-VNC:~$
DNS サーバとして fc00::1 が設定され、AAAA レコードが正しく参照できています。
nginx 設定
IPv6 の疎通性があり、名前解決も行えているので一旦クライアントの作業を終え、nginx ホストを確認してみます。 まずは 80 ポートの使用状況を確認してみます。
[admin@nginx ~]$ 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 ---
# reload忘れてた^^;
[admin@nginx ~]$ sudo nginx -s reload
[admin@nginx ~]$ sudo nginx -s reload
[admin@nginx ~]$ 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 ホストのディストリビューションを確認してみます。
[admin@nginx ~]$ ls /etc | grep release
centos-release
redhat-release
system-release
system-release-cpe
[admin@nginx ~]$ cat /etc/centos-release
CentOS release 6.10 (Final)
[admin@nginx ~]$
CentOS6.10 であるため、フィルタリングは iptables で行っていると予想されます。(ここまで考えてなかった^^;) iptables のルールを確認してみましょう。
[admin@nginx ~]$ 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
一見すると問題が無いように見えますが、クライアントは Welcome to Nginx のページにアクセスできません。 それもそのはず、iptables は IPv4 のフィルタリング設定だからです。 実は初期状態から IPv4 で 80 ポートは許可されており、クライアントは IPv4 を用いて Welcome to Nginx のページを表示させることはできてました。
IPv6 のフィルタリングは ip6table で行います。 ip6table のルールを確認すると、80 ポートのアクセスを許可しているルールが無いことがわかります。
[admin@nginx ~]$ 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 ポートを許可します。
# -m state: パケットの状態
# -m state NEW: 新規接続を対象にするという意味
# -I INPUT 6: 6番目に挿入
[admin@nginx ~]$ sudo ip6tables -I INPUT 6 -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
[admin@nginx ~]$ sudo ip6tables -I INPUT 6 -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
[admin@nginx ~]$ 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 のページが表示されます。
すごく匿名ダイヤリー
解いた人:maimai-y
参照した問題・解説のサイト:すごく匿名ダイヤリー
問題文
匿名で日記が投稿できるサービス「すごく匿名ダイヤリー」を運営しています。 従来、フロントエンドとバックエンドを同じドメインで運用していましたが、 構成変更のため、バックエンドをサブドメインに変更する作業を行っています。
変更前: https://old-diary.ictsc.net/ https://old-diary.ictsc.net/api/
変更後: https://new-diary.ictsc.net/ https://api.new-diary.ictsc.net/
※VNCサーバのWebブラウザからのみ閲覧可能です
ソースコード内のドメインやパスは適切に書き換えましたが、何故か正常に動作しません。 変更前と同じように各機能が動作するよう、サーバにログインして原因調査 及び 修正を行ってください。
なお、サービスはメンテナンス中で限定公開としているため、対応中にサービス断が生じても問題ありません。 また、投稿データについてもバックアップから復元するので、(変更前/変更後環境共に)日記の追加・削除・スター追加は任意に実施して問題ありません。
今後の運用・開発を考慮し、変更は問題解決に必要な箇所に絞り、出来るだけ他に影響を与えないように直してください。 全てを直しきれない場合でも、可能なところまで直してください。
サービス仕様
- 誰でも匿名で日記が投稿・閲覧できる
- 投稿されている日記に対して誰でもスターを付けることができる
- 日記は投稿したブラウザで閲覧すると削除ボタンが表示され、削除が可能 (期間/個数に制限あり)
- フロントエンドはSPA(Single Page Application)として構築されている
- 日記の取得/投稿/削除/スター追加はWebAPI経由でバックエンドと通信して実現する
// SPAとは(https://qiita.com/takanorip/items/82f0c70ebc81e9246c7a):
JavaScriptでDOMを操作しページを切り替える
解答方法
- 修正 と 報告 の両方が必要です
- 「変更後」のURLでサービスが正常に動作するよう、実際にサーバ上で修正を行ってください。
- 解答から「原因と実施した修正内容」を報告してください。
- 報告は最終的に行った内容のみで問題ありません (途中の試行錯誤は記載不要)
- 具体的に記載してください (例: XXXを直した、ではなく XXXがXXXなので、XXXファイルのXXX部分にXXXXXXXXXを追加した 等)
ログイン情報
VNCサーバから $ ssh 192.168.0.80 -l admin → PW: USerPw@19
※ $ sudo su – にて rootユーザに昇格可能です
考えられる検証、修正手順
何故か正常に動作しません。
どこが正常に動いてないのか調べて、エラーメッセージを読めたら読む。
ソースコード内のドメインやパスは適切に書き換えましたが
DNS周りは大丈夫なのか調べる。
解説
この問題はICTSC2019 一次予選にて出題された APIが飛ばないんですけど… の実技出題を目的として作成しました。
// 完全に気づかなかった /(^O^)\
機能ごとに必要な対処が異なり、CrossOrigin通信におけるCORS, CSP, Cookieの取り扱いを把握していないと完答出来ない構成としています。
// CSPとは:セキュリティの為のHTTPレスポンスヘッダー (https://techblog.securesky-tech.com/entry/2020/05/21/)
STEP1, 日記一覧と日記を閲覧可能にする 前半 (CSPによる許可)
// https://new-diary.ictsc.net/しか信用できませんという設定になっているため、https://api.new-diary.ictsc.netを信用するようにする
https://new-diary.ictsc.net/ を閲覧するとブラウザアラートでError: Network Error
と表示されます。
これだけでは原因がわからないので、開発者ツール(F12)のコンソールを表示すると以下のエラーが表示されています。
Content Security Policy: ページの設定により次のリソースの読み込みをブロックしました: https://api.new-diary.ictsc.net/list (“connect-src”)
→ CSPの “connect-src” で https://api.new-diary.ictsc.net/list への接続が禁止されていることが分かります。
ページのソースを表示するとmetaタグでCSPが指定されている為、このhtmlを修正する必要があります。
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com;">
// connect-src <ドメイン>で、<ドメイン>のスクリプトだけを信用するという意味になる
修正すべきファイルの場所は動作しているWebサーバの設定ファイルから特定します。
# netstat -ntelpo | grep -e :443
// 接続待ち(Listen)のプロセスを調べる(?) | ポート番号は、httpsのデフォルトのポート番号である443に絞る
tcp6 0 0 :::443 :::* LISTEN 0 26477 2044/httpd off (0.00/0/0)
// httpdとは:dはデーモン Webサーバとしてのお仕事をしている常駐プログラム
# ps auxww | grep http[d]
// 全ユーザの全プロセス | httpdに絞る []で自分自身を避けてるらしい(https://webkaru.net/linux/ps-grep-exclude/)
root 2044 0.0 1.3 286180 13864 ? Ss 17:59 0:00 /usr/sbin/httpd -DFOREGROUND
apache 2778 0.0 0.9 298736 9104 ? S 18:48 0:00 /usr/sbin/httpd -DFOREGROUND
apache 2779 0.0 1.5 1356304 15728 ? Sl 18:48 0:00 /usr/sbin/httpd -DFOREGROUND
apache 2780 0.0 1.6 1356172 16760 ? Sl 18:48 0:00 /usr/sbin/httpd -DFOREGROUND
apache 2781 0.0 1.7 1487424 18028 ? Sl 18:48 0:00 /usr/sbin/httpd -DFOREGROUND
apache 2993 0.0 1.6 1356308 17012 ? Sl 18:48 0:00 /usr/sbin/httpd -DFOREGROUND
# /usr/sbin/httpd -S 2>&1 | grep port
// バーチャルホストの設定を一覧表示した上でシンタックスチェック 標準エラー出力を標準出力に流す |
port 443 namevhost fe80::9ea3:baff:fe30:1584 (/etc/httpd/conf.d/ssl.conf:40)
port 443 namevhost old-diary.ictsc.net (/etc/httpd/conf.d/virtualhost.conf:6)
port 443 namevhost new-diary.ictsc.net (/etc/httpd/conf.d/virtualhost.conf:32)
port 443 namevhost api.new-diary.ictsc.net (/etc/httpd/conf.d/virtualhost.conf:51)
// Webサーバの設定ファイルは/etc/httpd/conf.d/virtualhost.conf
# grep Root -B1 /etc/httpd/conf.d/virtualhost.conf
ServerName old-diary.ictsc.net
DocumentRoot /var/www/old-front
--
ServerName new-diary.ictsc.net
DocumentRoot /var/www/new-front
--
ServerName api.new-diary.ictsc.net
DocumentRoot /var/www/new-api/public
/var/www/new-front/index.html
に該当のmetaヘッダが含まれている為、
connect-src 'self';
をconnect-src https://api.new-diary.ictsc.net;
に編集すると、問題のエラーが解消します。
STEP2, 日記一覧と日記を閲覧可能にする 後半 (CORSによる許可)
// https://new-diary.ictsc.net オリジンを許可する
STEP1でCSPによるエラーは解消しましたが、まだ閲覧可能にはなりません。 再び https://new-diary.ictsc.net/ を開いてコンソールを確認すると、以下のエラーが表示されます。
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://api.new-diary.ictsc.net/list にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Origin’ が足りない)。
記載の通り、CORSヘッダーの設定が必要となります。 https://developer.mozilla.org/ja/docs/Web/HTTP/CORS 設定場所についてはいくつか考えられますが、作問者の想定は以下の2通りです。
アプリケーション側に追加
/var/www/new-api/public/index.php
の FastRoute\Dispatcher::FOUND
以下等に追加する
case FastRoute\Dispatcher::FOUND:
$handler = $routeInfo[1];
$vars = $routeInfo[2];
header('Access-Control-Allow-Origin: https://new-diary.ictsc.net'); ★ 追加
Webサーバ(Apache)側に追加
/etc/httpd/conf.d/virtualhost.conf
の <Directory /var/www/new-api/public>
内等に追加し、httpdをreloadする
<Directory /var/www/new-api/public>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
Header set Access-Control-Allow-Origin https://new-diary.ictsc.net ★ 追加
</Directory>
※ 本問題ではブラウザ上で各機能が正しく動作していれば、追加場所や細かい記載方法等は不問としました。 ※ ただし、アプリケーションを1から作り直すような大幅な変更は認めていません。
以上の変更を行うと、日記一覧 及び 日記が閲覧可能となります。
STEP3, 日記の投稿を可能にする
// ヘッダを追加
「日記を書く」から日記を投稿すると、ブラウザアラートで投稿後の日記URLが受け取れませんでした。
と表示されます。
また、コンソールにはsubmit_article https://new-diary.ictsc.net/app.js:109
と表示されます。
ただし、日記の投稿は正常に完了しており、その後のページ遷移のみ失敗しているようです。
エラーメッセージだけでは情報が足りないので、https://new-diary.ictsc.net/app.js
の該当処理を確認すると、
res.headers.location
、つまりレスポンスのLocationヘッダが正常に取得出来ていないようです。
axios.post(api_url + 'article', params)
.then(res => {
if (!res.headers.location) { throw `投稿後の日記URLが受け取れませんでした。` }
router.push(res.headers.location)
})
.catch(err => { console.error(err); alert(err) })
}
一方、開発者ツールのネットワークタブでAPIサーバからの応答を確認すると、
日記投稿後、Location: /article/21
のようにLocationヘッダを含むレスポンスが得られていると確認出来ます。
この解決には知識が必要となりますが、CORSでセーフリスト以外のレスポンスヘッダを利用する場合、
Access-Control-Expose-Headers
ヘッダにて明示的に許可する必要があります。
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
Locationヘッダはセーフリストに含まれていない為、STEP2の設定に以下のヘッダも追加する必要があります。
Access-Control-Expose-Headers: Location
ヘッダを追加すると、日記投稿後のエラーが解消し、投稿された日記ページにリダイレクトされるようになります。
STEP4, スターの追加を可能にする
// 定義されていないメソッドに対応
各記事のスター追加ボタン[★+]をクリックするとError: Network Error
が表示されます。
開発者ツールのコンソールには以下のように表示されます。
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://api.new-diary.ictsc.net/article/26/star にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Origin’ が足りない)。
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://api.new-diary.ictsc.net/article/26/star にあるリモートリソースの読み込みは拒否されます (理由: CORS 要求が成功しなかった)。
また、開発者ツールのネットワークタブで通信を確認すると、
OPTIONS
メソッドのリクエストが送信され、HTTP/1.1 405 Method Not Allowed
のレスポンスが得られています。
しかし、https://new-diary.ictsc.net/app.js
にて利用されているメソッドはPUT
です。
add_star: function () {
axios.put(api_url + 'article/' + this.$route.params.id + '/star')
.then(res => {
this.article.star_count++
})
.catch(err => { console.error(err); alert(err) })
},
これは一次予選でも出題された プリフライトリクエストによる挙動です。 https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Preflighted_requests
OPTIONS
メソッドに対して適切なCORSヘッダを応答する必要がありますが、
/var/www/new-api/public/index.php
内でOPTIONS
メソッドが定義されていない為、METHOD_NOT_ALLOWED
として405の応答が発生しています。
作問者の想定解法は以下の2通りです。
ダミールートの追加
/var/www/new-api/public/index.php
に ダミーのルートを追加する
$base = '/';
$dispatcher= FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $router) use ($base) {
$router->addRoute('GET' , $base.'list' , 'get_list');
$router->addRoute('GET' , $base.'article/{id:\d+}' , 'get_article');
$router->addRoute('POST' , $base.'article' , 'post_article');
$router->addRoute('DELETE' , $base.'article/{id:\d+}' , 'delete_article');
$router->addRoute('PUT' , $base.'article/{id:\d+}/star' , 'put_article_star');
$router->addRoute('OPTIONS', $base.'{path:.*}' , 'dummy'); ★ 追加
});
function dummy($vars, $pdo) { ★ 追加
return;
}
合わせてCORSヘッダの設定箇所に以下を追加する必要があります。
Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS
METHOD_NOT_ALLOWED発生時の処理に追加
/var/www/new-api/public/index.php
でMETHOD_NOT_ALLOWED
が発生時した場合も、OPTIONSメソッドについては応答するように追加する
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
$allowedMethods = $routeInfo[1];
if ($httpMethod == 'OPTIONS') { ★ 追加
header('Access-Control-Allow-Methods: OPTIONS, '.implode(', ', $allowedMethods));
header('Access-Control-Allow-Origin: https://new-diary.ictsc.net');
header('Access-Control-Expose-Headers: Location');
break;
}
header('Allow: '.implode(', ', $allowedMethods));
header('HTTP/1.1 405 Method Not Allowed');
break;
上記どちらかの修正を行うと、スターの追加が可能となります。
STEP5, 日記の削除を可能にする
// Cookieに正しいsecret(パスワード)が保存されるように設定する
ここまでの対処でブラウザ操作で発生するエラーは解消しました。 しかし、問題文に書かれている日記の削除機能が見当たりません。
- 日記は投稿したブラウザで閲覧すると削除ボタンが表示され、削除が可能 (期間/個数に制限あり)
https://new-diary.ictsc.net/app.js
を確認すると、UI自体は存在するようですが、
article.authored
がtrue
にならなければ表示されないようです。
<div><span v-if="article.authored" class="delete_btn" v-on:click="delete_article()">この日記を削除する</span></div>
https://new-diary.ictsc.net/app.js
にはarticle.authored
を変更する処理が含まれておらず、
APIからの結果をそのまま受け入れています。
mounted: function () {
axios.get(api_url + 'article/' + this.$route.params.id)
.then(res => {
if (!res.data) { throw `日記が見つかりませんでした。` }
this.article = res.data;
})
.catch(err => { console.error(err); alert(err) })
},
API側の処理を /var/www/new-api/public/index.php
から確認すると、
Cookieに正しいsecret(パスワード)が保存されている場合のみ、article.authored
がtrue
となることが分かります。
function get_article($vars, $pdo) {
$articleid = $vars['id'];
$stmt = $pdo->prepare('SELECT id, title, content, star_count, secret_hash FROM article WHERE id = :id');
$stmt->execute(array(':id' => $articleid));
$result = $stmt->fetch();
if (isset($_COOKIE['__Secure-article-'.$articleid])) {
$secret_hash = $result['secret_hash'];
$client_secret = $_COOKIE['__Secure-article-'.$articleid];
$authored = password_verify($client_secret, $secret_hash);
} else {
$authored = false;
}
記事の投稿時にはsetcookie
が行われており、レスポンスヘッダからも確認できますが、
実際に投稿してもブラウザのCookieには保存されません。※ 開発者ツールのストレージタブにて確認出来ます。
function post_article($vars, $pdo) {
...
header('HTTP/1.1 201 Created');
header('Location: /article/'.$articleid);
setcookie('__Secure-article-'.$articleid, $secret, time() + (365 * 86400), '
CrossOriginでCookieを設定させる場合は、リクエスト側でwithCredentials
の指定と、
レスポンス側でAccess-Control-Allow-Credentials
の指定が必要となります。
https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest/withCredentials
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
レスポンス側はこれまでのCORSヘッダと同様に以下のヘッダを追加します。
Access-Control-Allow-Credentials: true
リクエスト側については、/var/www/new-front/app.js
からaxiosを利用して通信している為、
個別にwithCredentials: true
を指定するか、以下のようにデフォルト値を設定します。
axios.defaults.withCredentials = true;
双方を追加後に記事を投稿すると「この日記を削除する」ボタンが表示されるようになります。 実際の削除についてはDELETEメソッドを許可する必要があるため、追加していない場合はヘッダに追加します。
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
以上で全ての機能が正常に動作するようになりました。 動作確認の上、「原因と修正内容」を解答すれば完了です。
採点結果について
本問題は「各機能の正常な動作」及び「修正箇所への言及」にて点数を加算しています。
各工程の正答率は「STEP1/2 41%」「STEP3 21%」「STEP4 23%」「STEP5 17%」となり、完答は「12%」でした。 STEP1/2までの修正についてはWebブラウザの開発者ツール(コンソール)で修正箇所が示されていますので、 普段から使い慣れている方は比較的容易に解決できる想定でした。 一方、STEP3/4/5についてはCORS/Cookieの知識 及び PHP/JavaScriptの読解が必要となる為、 Web技術に関するチームの実力差が顕著に出る結果となったように感じます。 特に上位チームは解答内容が丁寧かつ明確な内容で、完全に理解している様子でした。 (拙いコードを読解いただきありがとうございました……)
なお、全ての問題に対処出来たと思われるチームでも、 「解答で一部修正に言及していない」「デバッグ用のalertが削除されないまま残っている」 「解答では修正されているはずのファイルがサーバ上では修正されていない」等の理由で減点が発生しました。 また、STEP1/2の解決のみで問題クリアと判断した様子のチームも見受けられました。
いずれも解答提出前後の見直しで防げる内容となりますので、 今一度落ち着いて問題文と解答、修正後のサービス状況を確認いただければと思います。
参照した問題・解説のサイト: https://blog.icttoracon.net/2021/03/16/
いつの間にか復活している君
解いた人:とり
参照した問題・解説のサイト:いつの間にか復活している君
使用環境・ツール
- docker
- docker-compose
前提条件
~/web-server/docker-compose.yml
があり編集可能- わざわざ明記しているあたり、これを編集して問題を解決する必要がありそう
- ⇨ ひっかけポイントだったらしいw そういうパターンもあるのね。
docker ps -a
などで確認するとコンテナが起動している
問題文でされた操作
- docker-composeを使用してWEBサーバを構築した
- 無事に起動し、WEBページも確認できたのですが、 再起動するとアクセスできなくなってしまった
- 自動的に再起動するように記述してあるので不可解
- トラブルシュートしているといつの間にかアクセスできるようになります
バグの内容
踏み台から $ curl 192.168.17.1
をしても応答がない
理想の終了状態
再起動しても踏み台から $ curl -I 192.168.17.1 をするとステータスコード200のレスポンスが返ってくる
考えられる検証、修正手順
docker build -t test:local .
と Docker でビルドしてみて永遠と転送しているか確認- 大量のファイルを裏で Docker daemon が読み込んでいる
.dockerignore
で、大量ファイルのあるディレクトリを除外しておき、docker-compose.json 内で別途マウント- 起動の時間によりそう
ref:docker-compose で一向にビルドがはじまらない、もしくは起動しない。はたまた忘れたころに起動する。
- Webサーバーにアクセスできない時の確認ポイント(なさそう)
- コンテナ内のWebサーバーがちゃんと起動して動いているか
- 以下のようにして確認
docker exec -it <起動したコンテナ名> bash curl http://localhost:8080/
- エラーが返ってくる、レスポンスが帰ってこない場合はWebサーバーの起動がうまくいっていない
- 今回の問題は時間が経てば直る問題なのでこれはなさそう
- localhost以外ではアクセスできるか
- localhostではなく、実IPアドレスでアクセスしたらどうか?
- 以下のコマンドでコンテナに割り振られているネットワークアドレスがわかる
docker inspect –format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}’ con_name curl http://172.17.0.2:8080/
- これでうまくいった場合は、ポートなどの問題になる。
- コンテナで特定のポートが公開されているか
- ポートの公開を
# Dockerfileファイル中に以下のような記述を追加し、8080番ポートを公開する EXPOSE 8080 # composeファイル中にならこんな感じ expose: - '3306' - '8080'
- yamlも修正可能なので、なくはないかもしれない。簡単すぎるが。
- でも、時間が経てば直る問題なのでなさそう
- 公開されたポートにつながるように設定できているか(ポートフォワード)
- コンテナで公開されたポートにホストOSから「localhost:8081」のように接続するには、ホストOSの8081番ポートとコンテナの8080番ポートをつないであげる必要があります。
- localhost:8081 -> コンテナ:8080
- 「localhost:8081」に来たリクエストを「コンテナ:8080」に転送してあげるようにする
docker run -p 8081:8080 –name <起動するコンテナ名>
- これも、時間が経てば直る問題なのでなさそう ref: Dockerコンテナで起動したサーバにアクセスできないときの確認と対処方法
- コンテナ内のWebサーバーがちゃんと起動して動いているか
Docker Compose restart の挙動
- ホストOSを起動したタイミングであるアプリケーションを自動で立ち上げたい、 あるいは何らかの問題で落ちた時に、自動で再起動して欲しいというときにrestart使うらしい。
- Docker 及び Compose では、 run/upの restart policy の設定することにより、 コンテナが停止した際の再起動にまつわる設定ができる。
オプション | 意味 |
---|---|
no | 再起動しない (デフォルト) |
on-failure[:max-retries] | プロセスが 0 以外のステータスで終了した場合、 最大:max_retries の分だけ再起動を行う |
always | 明示的に stop がされない限り、終了ステータスに関係なく常に再起動が行われる |
unless-stopped | 最後にdocker daemon が起動していた際に ステータスが終了状態だった場合は再起動しない。それ以外はalwaysと同じ。 |
ちゃんと再起動の設定はしないといけない。
Q. 従属 コンテナのプロセスはそのまま? それとも再起動される?
A. そのまま
Q. コンテナ間の接続は再開される?
再起動後も接続は問題なさそう
ref: Docker Compose restart の挙動
Dockerデーモン、Dockerコンテナ、及びコンテナ内のサービスアプリの自動起動について
デフォルトは手動なのかな? 再起動の時には自動設定する必要があるっぽい。
Dockerデーモンの自動起動
ブート時に自動起動する
$ sudo systemctl enable docker
# 他のディストリビューションでは、次のように実行します
$ sudo chkconfig docker on
通常時の起動
$ sudo systemctl start docker
# 他のディストリビューションでは、次のように実行します
$ sudo service docker start
設定確認・状態確認は以下のコマンドで行います。
$ systemctl status docker
Dockerコンテナの自動起動
- restartオプションを利用
- Dockerが以上終了した場合に自動的に再起動させることが可能
- さっきの上の表を参考
- 「always」「unless-stopped」がコンテナの自動起動に利用可能
- 「always」を指定した場合は、Dockerデーモン終了時のDockerコンテナの状態に関係なく自動起動されます
- 「unless-stopped」は、Dockerデーモン終了時に停止状態(例えば「docker stop」コマンドにて停止)のコンテナは自動起動されません
- Dockerホストのsystemdを利用する
- 「docker start」と「docker stop」をsystemdに登録するだけ
- systemdって起動するときに動いてくれるやつらしい
Dockerコンテナ内のサービスの自動起動
- composeファイルに設定がちゃんと書いてあれば特に問題はなさそう
ref:
修正手順案詳細
$ curl 192.168.17.1
とdocker ps -a
を試してみる- 自動起動されてないようなら、
systemctl status docker
で自動起動を調べる。 - yamlの
restart
オプションを確認して、もしオプションがうまく機能してなさそうなら設定してみる
解説
原因
デーモンの起動はできているが、dockerの自動起動が出来ていない
原因究明方法
初期状態を確認
$ curl 192.168.17.1
curl: (7) Failed to connect to 192.168.17.1 port 80: Connection refused
コンテナの様子を確認
user@docker:~$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4e7db3e7cc97 nginx:latest "/docker-entrypoint.…" 3 minutes ago Up Less than a second 0.0.0.0:80->80/tcp web-server_nginx_1
もう一度curlを確認する
$ curl 192.168.17.1 -I
HTTP/1.1 200 OK
Server: nginx/1.19.6
Date: Sun, 07 Mar 2021 03:33:11 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 15 Dec 2020 13:59:38 GMT
Connection: keep-alive
ETag: "5fd8c14a-264"
Accept-Ranges: bytes
実はdockerコマンドを叩くとデーモンが起動するという罠があります.先ほどのdocker ps -aを見てみるとUp Less than a secondとあり,起動したてなのがわかります
さらにsystemctl status docker
で詳しく確認してみる。
ictsc@docker:~$ systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; disabled; vendor preset: enabled)
Active: active (running) since Sun 2021-03-07 12:32:53 JST; 9min ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 2176 (dockerd)
Tasks: 17
Memory: 114.7M
CGroup: /system.slice/docker.service
├─2176 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
└─2362 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.18.0.2 -container-port 80
ictsc@docker:~$ systemctl is-enabled docker
disabled
↑ でdisabledになっているのが悪かった。
解決手順
解放は単純にenableするだけ。
systemctl enable docker
途中で上手くいっちゃうし、めっちゃ引っかかりそう。restartしてすぐ直るからググってもあんまり出てこなくてこういう問題は意外と厄介かも😅
その他リンク
時間上説明しなかったものたち。
Webが繋がらないという内容で調べたやつ
- 汎用的に使えそうなやつ: https://web.plus-idea.net/on/docker-web-server-access-denied/
- 全般的に使えそうなやつ: https://qiita.com/amuyikam/items/ef3f8e8e25c557f68f6a
Dockerデーモンが気になって調べたやつ
- さわって理解する Docker 入門
- Dockerデーモンは Linux のデーモンプロセスで、Docker Engine API が呼び出されるのを待ち受けています。Dockerデーモンは、呼び出された Docker Engine API に応じて、イメージのビルドやコンテナの起動などを行います。
- Linux リテラシ - 第4回 デーモン
- デーモンはユーザーが意識することがないような裏の部分で動いており、システムを維持したりユーザーにサービスを提供したりといったことを行っています
名前解決ができなくなった?
解いた人:momom-i
参照した問題・解説のサイト:名前解決ができなくなった?
前提条件
$ dig @192.168.2.131 pc1.ictsc.net
で名前解決ができない- 権威サーバーを使っているキャッシュサーバーは一つしかない
問題文でされた操作
- 権威サーバで KSK ロールオーバーを行った
$ dig @192.168.2.131 pc1.ictsc.net
を実行した
バグの内容
クライアントからdig @192.168.2.131 pc1.ictsc.net +dnssec
を実行しても,名前解決ができない.
理想の終了状態
クライアントから dig @192.168.2.131 pc1.ictsc.net +dnssec を実行して,dnssec の検証が成功して名前解決ができる.
考えられる原因
- 権威サーバが落ちてるなど DNS サーバ自体に異常がある(KSK の話が出ているので可能性は低い)
- DNSSEC 検証を有効にしているキャッシュサーバ等のトラストアンカーの更新がされていない
- KSK ロールオーバーで一時的に DNS の応答サイズが大きくなっていて応答を正しく受け取れない
解決方法
基本的にここを参照した。
原因 1. DNS サーバ自体に異常がある
- dig コマンドの
+norec
あるなしで結果がどう変わるかを観察する。+norec
をつけるとキャッシュ DNS サーバから権威サーバへの問い合わせ結果を表示するため、このオプションをつけた時だけ失敗する場合は、権威サーバに問題がある。逆にオプションをつけない時に失敗する場合は、キャッシュ DNS サーバの方に問題がある。
原因 2. トラストアンカーの更新がされていない
- 上記リンク「トラストアンカーの更新」を、みて DNSSEC 検証が有効かどうか、named.conf の option などで dnssec-validation が no など設定されているかを確認する。特に設定されてない場合 BIND9.5 以前のデフォルトは無効、9.5 以降は有効になってる。無効の場合、トラストアンカーの更新が原因ではない。
- 更新後の権威サーバ KSK があるか確認。(更新した、ってあるから多分ある)named.conf の trusted-keys ディレクティブを書き換える。
原因 3. 一時的に DNS の応答サイズが大きくなっていて応答を正しく受け取れない
- これが原因の場合、dig コマンドで flags に tc が立ってるか UDP では扱えません!みたいなエラーが出ると思われる。「DNS 応答サイズ増大への対応」に沿って確認しながら受け取れるようにする。
※参考※
-
DNS:
DNS ( Domain Name System ) は、ドメイン名(コンピュータを識別する名称)を IP アドレスに自動的に変換してくれるアプリケーション層プロトコル(参照) -
トラストアンカー:
インターネットなどで行われる、 電子的な認証の手続きのために置かれる基点。(参照) -
権威サーバー:
(DNS コンテンツサーバともいう) DNS において、あるゾーンの情報を保持し、他のサーバーに問い合わせることなく応答を返すことができるサーバーのこと(参照) -
KSK ロールオーバー:
KSK(鍵署名鍵)のロールオーバー(更新)。KSK(key string key)は DESSEC にて、公開鍵に署名するための鍵(参照) -
DESSEC:
電子署名の仕組みを応用し、DNS 応答の出自および DNS 応答の完全性を検証することができるもの(わかりやすい参照 URL) -
dig コマンド:
ip アドレスやネームサーバを DNS に問い合わせる BIND のコマンド。nslookup コマンドと似ていて、結果の出力が dig の方が詳細(参照)
+dnnsec: DNSSEC 付きでの問い合わせ
+cd: DNSSEC なし問い合わせ
+norec: キャッシュ DNS サーバから権威サーバへの問い合わせ
解説
- まず、クライアントから権威サーバで名前解決をできるかを確認します。
# 192.168.2.20は権威サーバのIPアドレス
$ dig @192.168.2.20 pc1.ictsc.net
; <<>> DiG 9.11.20-RedHat-9.11.20-5.el8 <<>> @192.168.2.20 pc1.ictsc.net
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49438
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: fb2922914c429f5fa65713d2603b3d6486e5d706011553e6 (good)
;; QUESTION SECTION:
;pc1.ictsc.net. IN A
;; ANSWER SECTION:
pc1.ictsc.net. 86400 IN A 192.168.2.151
;; AUTHORITY SECTION:
ictsc.net. 86400 IN NS master-ns.ictsc.net.
;; ADDITIONAL SECTION:
master-ns.ictsc.net. 86400 IN A 192.168.2.20
;; Query time: 1 msec
;; SERVER: 192.168.2.20#53(192.168.2.20)
;; WHEN: Sun Feb 28 15:51:16 JST 2021
;; MSG SIZE rcvd: 126
- 次に,キャッシュサーバで名前解決ができるかを確認します.
$ dig @192.168.2.131 pc1.ictsc.net +dnssec
; <<>> DiG 9.11.20-RedHat-9.11.20-5.el8 <<>> @192.168.2.131 pc1.ictsc.net +dnssec
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 27168
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 956
; COOKIE: 6fbba7982beede01a541f72f603b3d788b89e7b7d3c4fbc1 (good)
;; QUESTION SECTION:
;pc1.ictsc.net. IN A
;; Query time: 3 msec
;; SERVER: 192.168.2.131#53(192.168.2.131)
;; WHEN: Sun Feb 28 15:51:36 JST 2021
;; MSG SIZE rcvd: 70
- キャッシュサーバで名前解決ができないことがわかったので、コマンド引数に+cd を追加して DNSSEC の検証をせずに名前解決を行います
$ dig @192.168.2.131 pc1.ictsc.net +cd
; <<>> DiG 9.11.20-RedHat-9.11.20-5.el8 <<>> @192.168.2.131 pc1.ictsc.net +cd
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25924
;; flags: qr rd ra cd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 956
; COOKIE: 67453de86c9abd57bee0655c603b3d852c5c6647f116587d (good)
;; QUESTION SECTION:
;pc1.ictsc.net. IN A
;; ANSWER SECTION:
pc1.ictsc.net. 86329 IN A 192.168.2.151
;; AUTHORITY SECTION:
ictsc.net. 86338 IN NS master-ns.ictsc.net.
;; Query time: 0 msec
;; SERVER: 192.168.2.131#53(192.168.2.131)
;; WHEN: Sun Feb 28 15:51:49 JST 2021
;; MSG SIZE rcvd: 110
名前解決が成功するため,DNSSEC の検証に失敗していることがわかります。
- クライアントから署名の有効期限を確認します.2 月 10 日に署名の有効期限が切れていることが確認できます
$ dig @192.168.2.20 pc1.ictsc.net +dnssec
pc1.ictsc.net 86400 IN RRSIG A 8 3 86400 20210210004000 20210111004000 ...
- 権威サーバで新しい KSK 鍵のみを用いて、ゾーンに再署名を行います. (ゾーンの再署名も必要だったのか!)
$ sudo su
$ cd /var/named
$ vim ictsc.net.zone
$TTL 1D ;
@ IN SOA master-ns.ictsc.net. root.master-ns.ictsc.net. ( ;
2020123101 ;
3H ;
1H ;
1W ;
1H ) ;
IN NS master-ns.ictsc.net. ;
cache-ns IN A 192.168.2.131 ;
master-ns IN A 192.168.2.20 ;
pc1 IN A 192.168.2.151 ;
pc2 IN A 192.168.2.152 ;
pc3 IN A 192.168.2.153 ;
$INCLUDE "Kictsc.net.+008+63885.key"
$INCLUDE "Kictsc.net.+008+63439.key"
$INCLUDE "Kictsc.net.+008+63829.key" # 古いksk鍵を削除
# -x: KSKの更新のみ
# -o: ゾーンの起点
# -k: 署名鍵
$ dnssec-signzone -x -o ictsc.net -k Kictsc.net.+008+63439.key ictsc.net.zone
Verifying the zone using the following algorithms: RSASHA256.
Zone fully signed:
Algorithm: RSASHA256: KSKs: 1 active, 0 stand-by, 0 revoked
ZSKs: 1 active, 0 present, 0 revoked
ictsc.net.zone.signed
※ dnssec-signzone: (参照) 6. ゾーンのリロードを行います.
$ rndc reload ictsc.net
- クライアント側で KSK 公開鍵を取得し,古いトラストアンカーを削除し新しいトラストアンカーの設定を行います.
# 257は現在の有効なKSKを示す
$ dig @192.168.2.20 ictsc.net dnskey | grep 'DNSKEY.*257'
ictsc.net. 86400 IN DNSKEY 257 3 8 AwEAAePGnJDVqiEhjCRcnYYNP+Pf2DFnJwoj3sTlJwkh2aM1LZR4ajtR sxidDJi59Hf/lcwCBiEnW8eNvpuHz5NfrUTuc/hI/jKI38VkH4m+b68B feNyJtS9IUn8Naln/9r4hQBFCCEHJNmiMo5XnKdD3oEuDSgIsCeP8IOJ c1tlEcimy
fBfijuQleTr7MyoxW3iK0Q7kUuy8kIGelWKMogbUwrFFeBV CNvIAiofQOy7UDkjuGe9UpEXozZ5LNQkrBONzkUvr8Dt3YlhhWWYAjbX W5WzrLiQS9PTr3HMRlOOvTk4XlxQu0LDyqalyuBQnvMMg0AleQ7Q5c+M LU3l96yAg50=
$ sudo vim /var/named/chroot/etc/named.conf
trusted-keys {
"ictsc.net." 257 3 8 "AwEAAePGnJDVqiEhjCRcnYYNP+Pf2DFnJwoj3sTlJwkh2aM1LZR4ajtR sxidDJi59Hf/lcwCBiEnW8eNvpuHz5NfrUTuc/hI/jKI38VkH4m+b68B feNyJtS9IUn8Naln/9r4hQBFCCEHJNmiMo5XnKdD3oEuDSgIsCeP8IOJ c1tlEcimyfBfijuQleTr7MyoxW3iK0Q7kU
uy8kIGelWKMogbUwrFFeBV CNvIAiofQOy7UDkjuGe9UpEXozZ5LNQkrBONzkUvr8Dt3YlhhWWYAjbX W5WzrLiQS9PTr3HMRlOOvTk4XlxQu0LDyqalyuBQnvMMg0AleQ7Q5c+M LU3l96yAg50=";
};
$ sudo systemctl restart named-chroot
- 最後に、クライアント側でキャッシュサーバを使用して,DNSSEC の検証が成功するかを確認します.
$ dig @192.168.2.131 pc1.ictsc.net +dnssec
; <<>> DiG 9.11.20-RedHat-9.11.20-5.el8 <<>> @192.168.2.131 pc1.ictsc.net +dnssec
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50967
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 956
; COOKIE: 11c4d3fa9bdf7a63dc315b95603de72eaeb7725d8de23282 (good)
;; QUESTION SECTION:
;pc1.ictsc.net. IN A
;; ANSWER SECTION:
pc1.ictsc.net. 86258 IN A 192.168.2.151
pc1.ictsc.net. 86258 IN RRSIG A 8 3 86400 20210401061624 20210302061624 63885 ictsc.net. K2/YRHfbne6RIqBzkt0/bZ7T62QTNr52S/SGuD9omC5ClbsJjOydBvXm THSSR0BtxlzbSGVzhggkBDzXcfc7lS8Iv8tbUWDKlvNp+heAo+PhDnWY 8VZWolD2Y2n9HNuBNXhidvIXHyrJbuhtzbamelgnEDx9zKlazeGrbjSZ Kqo=
;; Query time: 0 msec
;; SERVER: 192.168.2.131#53(192.168.2.131)
;; WHEN: Tue Mar 02 16:20:14 JST 2021
;; MSG SIZE rcvd: 255
flags に ad が含まれているため成功。ad は DNSSEC の検証に成功していることを表す。
DNSサーバを作りたかったらしい
解いた人:nonnonno
参照した問題・解説のサイト:DNSサーバを作りたかったらしい
概要
あなたは同僚から助けを求められた。彼は社内のDNSサーバの構築ログに基づいて環境構築を試みたが、テストとして実行したコマンドでは期待していた出力が行われなかったらしい。原因を調査して、エラーを解決してあげよう。
前提条件
- ns01はmaster、ns02はslaveサーバとして機能させたい
- トラブルに関係しない要素については変更しない
- ns01,ns02についての接続情報が与えられている
初期状態
ns02でdig @localhost red.prob.final.ictsc.net
が解決できない
終了状態
- ns02で
dig @localhost red.prob.final.ictsc.net
が解決できる - 原因が特定されて報告される
- トラブル解決前に期待されていた動作をしている
考えられる原因・解決方法
digコマンドについて
参考Qiita digコマンドは、DNSサーバに対して問い合わせを行い、その結果を表示する。
ちなみに
DNSサーバにはキャッシュDNSと権威DNSの二種類がある。 権威DNSにアクセスが集中しないよう、しばらくの間はキャッシュDNSサーバが権威DNSから受け取った応答を保持して、その内容を使ってクライアントに応答する。
digコマンドの話に戻るが、digコマンドはBIND 9に付属するコマンドである。
nslookupと機能が似ているが、digコマンドによって得られる情報量が格段に多い。
BIND 9とは
Berkley Internet Name Domainの略で、世界中で最も多く利用されているDNSサーバのこと。
他にもDNS機能を提供するソフトウェアは存在するが、BINDを使うサーバがほとんどである。
DNSサーバの構築トラブルシューティング
本問題のdigの実行結果がわからないので、考えられる要因が多岐にわたる
解法
今回の問題文に、『彼は社内のDNSサーバの構築ログに基づいて環境構築を試みたが、テストとして実行したコマンドでは期待していた出力が行われなかった』とあるので、ログを見る必要がある。
どのログを見るかについては、コマンドを起動するための設定ファイルに関するものを見る必要がありそう。
例えば、今回であればdigコマンドがうまくいっておらず、digコマンドの設定ファイルはBINDのものなので、
systemctl status bind9
とする。
このログを見ると、BINDが設定ファイルのエラーによって起動していないことがわかる。
/etc/bind/named.conf.prob
についてエラーが出ているので、この設定部分を変更する必要がある。
viewとzoneとは
参考リンク https://ygg-tech.com/2020/09/bind_view/
viewは、一言で言ってしまえば「接続条件によってDNSクエリ結果を変える機能」である。
Bindの Ver.8 から使用可能であり、CentOS7であれば Ver.9 なので問題なく利用可能。
viewステートメントによって、アクセス元のIPアドレスに応じて、検索クエリに応答するゾーン情報を分けることができる。
活用する状況としては以下が挙げられる。
- 社内にWebサーバがあり、複数のLANに足を出している
- 各LANのクライアントには、自身が所属するLANのWebサーバのIPを返したい
- DNSは1台で済ませたい
参考リンク https://ygg-tech.com/2020/09/bind_view/の画像の中では、
- Secure LANに所属するクライアントには192.168.0.1を返す
- Remote Access LANに所属するクライアントには、172.24.0.1を返す というフローが実現できる。
設定ファイルの書き方は参考リンク参照。
ちなみに、zoneをドメイン名や単一DNSサーバと関連づけることは間違いである。
DNSゾーンは複数のサブドメインを含むことができ、複数のゾーンが同じサーバに存在できる。
ゾーンファイルに、DNSサーバに保存されているゾーン内の全てのドメインの全てのレコードが含まれ、記載される。
解法に戻る
viewの説明箇所で記述したように、複数のviewを使う際に、同じ名前がついた実態は異なるzoneを含んでいても、クライアント毎に異なるレスポンスを返すことが可能である。
しかし時には、複数のviewが同一のzoneを持つことが望まれる場合もある。
そこで用いられるのが in-view
zone オプションである。
in-view公式解説
これは一言で言うと、viewをまたいだゾーンの共有である。
in-view
オプションによって、事前に設定されたviewの中で定義されたzoneを、viewが参照することができるようになる。
これは設定ファイルにおいて後の部分に記述されているviewを参照することはできない。
2つ以上のviewで同じzone(type slave)を設定するとエラーになるので、2回目以降に使用する際にはin-viewオプションを使うとエラーが回避できる。
BINDの設定方法
DNSサーバ構築手順参考リンク
全体設定ファイルnamed.confは、デフォルトでは/etc/named.confに配置されている。
ちなみに、BINDをchrootしておくと、bind サービスは /var/named/chroot より上のディレクトリには行けなくなり、仮に bind を乗っ取られても他のディレクトリには被害が及ばない。
つまり、セキュリティを確保することができる。
chrootの場合には、自動的に/var/named/chroot/etc/named
配下に named.conf などの設定ファイルができている。
systemctl restart named
として再起動ができる。
ちなみに、named-chrootをインストールした場合、
- named → 自動起動 disable
- named-chroot → 自動起動 enable
の設定をしないと、namedが二つ起動している状態になってしまうので注意が必要である。
Nginxが展開できない
解いた人:とり
参照した問題・解説のサイト:Nginxが展開できない
使用環境・ツール
- Kubernetes
- Nginx
- Rook (解説に書いてあった)
Rookとは
kubernetes用のストレージオペレータ。ストレージをストレージソフトウェアを自己管理、自己スケーリング、および自己修復のストレージサービスに変えます。(公式ドキュメントから) なんとなくだけど、ストレージにPodの要素を加えることで、k8sの便利機能(スケーリングや修復)を扱えるようにしたサービスなのではないかと思った。(これはあくまで私の意見)
前提条件
/home/user/manifest
にあるファイルの内容を変更してはいけない
問題文でされた操作
- KubernetesのNginx用のマニフェストファイルをApplyした
VM名 | ホスト名 |
---|---|
kzz-k8s-master | 192.168.12.1 |
kzz-k8s-node1 | 192.168.12.2 |
kzz-k8s-node1 | 192.168.12.3 |
kzz-k8s-node1 | 192.168.12.4 |
バグの内容
- NginxのDeploymentがPendingになっている
理想の終了状態
- pendingとなっていたDeploymentが正常に稼働している。
- 正常化したDeploymentによって稼働するNginxで、解答するチームの名前が書かれたWebサイトが確認できるようになっている。
- 開始時と同じマニフェストのみが適用されている。
考えられる検証、修正手順
バグの原因を特定する案
- ログがあるならログ的なのを確認する → k8s側では特になかった
- 公式ドキュメントにデバッグ方法があった URL
- deploymentやserviceのファイルを読んでみる
- TCP80ポートが接続できる設定になっていない可能性もあると考えたが、その場合はrunning状態で外部IPに接続した時に画面が表示されないという現象が見られるはずなので今回のケースでは除外した。
- Googleで「nginx Pending k8s」と検索してみた
- k8sの公式ドキュメントのPodがPending状態に止まっている原因
- リソースが不十分
- hostPortの使用
- k8sの公式ドキュメントのPodがPending状態に止まっている原因
→ k8sの問題だしこの辺りがあり得そう。今回はこの場合だと考えて答えを作っていく。
修正手順案詳細
デバッグを頑張る
※ ここではhostnamesがnamespaceの名前
- サービスの存在確認
$ kubectl get svc hostnames
- サービスはDNS名によって機能しているか
$ nslookup hostnames
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: hostnames
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local
> これが失敗した場合、おそらくPodとServiceが異なるNamespaceにあるため、ネームスペースで修飾された名前を試す
DNSについて色々試す。
$ nslookup hostnames.default
> クロスネームスペース名を使用するようにアプリケーションを調整するか、同じNamespaceでアプリとServiceを実行する必要がある
$ nslookup hostnames.default.svc.cluster.local
$ nslookup hostnames.default.svc.cluster.local 10.0.0.10
> 完全修飾名では検索できるのに、相対名ではできない場合、Podの/etc/resolv.confファイルが正しいことを確認する必要があります。Pod内から実行します
いっぱいあって全部キャッチアップするのは無理な気がした。
リソースが不十分な場合
- クラスターにノードを追加する
- マニフェストをいじらなくてはいけないのかと思ったけど、
kubeadm
というコマンドを使うと実行中のk8sに対して追加できるらしい
- マニフェストをいじらなくてはいけないのかと思ったけど、
ノードの追加
kubeadm join --token <token> <control-plane-host>:<control-plane-port> --discovery-token-ca-cert-hash sha256:<hash>
トークン一覧を見てみる
kubeadm token list
トークンは一時的なので、トークンの発行もできる。24hできれる
kubeadm token create
- 不要なPodを削除する
- これは問題的にできるのかな🤔 可能性としては低そう
- Podがノードよりも大きくないことを確認する
cpu: 1
の場合、cpu: 1.1
を要求するPodは決してスケジュールされない- キャパシティを調べるコマンド
kubectl get nodes -o yaml | egrep '\sname:|cpu:|memory:' kubectl get nodes -o json | jq '.items[] | {name: .metadata.name, cap: .status.capacity}'
hostPortの使用
- PodをhostPortにバインドすると、Podをスケジュールできる場所の数が制限される
- この辺りも微妙。おそらくこんな感じで設定するのだと考えられるが、これってマニフェストファイルをいじらないとダメな気がする🤔
ports: - name: liveness-port containerPort: 8080 hostPort: 8080
解説
めちゃ調べたんだけど全然違った!😭 むずい。
原因
Rookを削除する際にコンフィグ情報やログデータなどが保存されているdataDirHostPathを削除しなかったこと。
原因究明方法
/home/user/manifest
には、Flannel 、MetalLB、Rook、NginxのDeploymentが書かれたtest-nginx.yamlなどのマニフェストが保存されています。test-nginx.yamlはPVC 、そのPVCを/usr/share/nginx/htmlにマウントするNginx のDeployment 及び、Nginx を公開するためのLoadBalancer Serviceを定義します。
ここで、なんのサービスを使っているのかを確認する。PVCはPersistentVolumeClaimの略でユーザーによって要求されるストレージのことらしい。(詳細)。ちなみにFlannelはネットワーキングをいい感じにするやつ。MetalLBはベアメタル用のロードバランサー。ベアメタルってなんだよと思って調べたら「OSやソフトウェアなどがインストールされていないまっさらなハードディスク(物理サーバー)」のことらしいです。ベアメタル覚えた!
問題環境では、問題名にあるNginx だけでなくPVC もPendingとなっています。 PendingになっているPVCを
kubectl describe pvc cephfs-pvc
で確認すると、failed to provision volume with StorageClassとエラーが出ています。
問題文だけでは知らんがな問題。kubectl describe pvc cephfs-pvc
でpvcの状態をみれるのは覚えておいた方がいいかも。基本的にはkubectl describe <サービス> <namespace>
でいけるのではないかと。覚えとけば、ぐぐりで乗り越えられそう・
このKubernetesクラスタではRookを利用し、CephをPersistent Volumeとして使っています。このことから、Rookの設定の異常等を予想し、Rookのエラーを確認する必要があります。そのため、
/home/user/manifest
にあるRookのマニフェスト(cluster.yaml)では、無効化されているcrash-collectorを有効化し適用します。立ち上がったcrash-collectorをkubectl describe
で確認すると、Unable to attach or mount volumesとなっています。
ここでcrash-collectorを有効化し適用とあるが、crash-collector知らないとできない気がするな〜〜。無効化することができるのかは微妙。無効以外できたら60%くらいの点は入りそう。
Rookがマウントしてそうな場所を探すと、 cluster.yamlの33行目にdataDirHostPath: /var/lib/rookなる行があります。 そして、この直前の31行目のコメントに、 Important: if you reinstall the cluster, make sure you delete this directory from each host or else the mons will fail to start on the new cluster. と書かれています。
日本語に訳すと「クラスタを再インストールする場合は、各ホストからこのディレクトリを削除してください。そうしないと、新しいクラスタでのmonsの起動に失敗します。」みたいです。
解決手順
解決方法としては、RookのDocumentに書かれた通りにクラスターの清掃を行うだけ。
- 適用されているRookを削除(ここでは6つのyamlに対して
kubectl delete -f hoge.yaml
をしている) - 該当するディレクトリ(
/var/lib/rook
)を削除 crash-collector
を無効化- crashモジュールが提供する機能をRookから使う用のコントローラーらしい
- 無効にするのに設定がいるのかどうなのかよくわからなかった。
- https://zenn.dev/satoru_takeuchi/articles/33560b2e753972
kubectl apply -f hoge.yaml
をするkubectl exec -it ”Pod名” -- bash
でpodの中に入る- その中のShellで、
echo -e "<a>”チーム名”</a>" > /usr/share/nginx/html/index.html
をする
便利そうなツールのメモ
何かがおかしい。。。。
解いた人:Hunachi
参照した問題・解説のサイト:何かがおかしい。。。。
使用環境・ツール
- Go言語
ライブラリ
- Cobra
- モダンなCLIアプリケーションを簡単に作れるようにしてくれるライブラリ。
バグの内容
何かがおかしい。。。。の1枚目の画像を参照する。 R1にて自作のルーティングアプリケーションを作ったが、そのアプリケーションのミスでそれぞれのHostにpingを飛ばしてもパケットロスを起こしてしまう。
それぞれのHostにpingを飛ばしても、パケットロスを起こしてしまう。 (pingができる時と出来ないときがある。)
前提条件
- kernelパラメーターの変更をしてはならない。
理想の終了状態
- それぞれのHostにpingを飛ばして、パケットロスが起こらない状態にする。
その他、公開してくれている情報
ーー起動方法ーー
cd ~/go/src/github.com/yoneyan/ictsc-ace/cmd/routing go get . go build . sudo ./routing start eth1 eth2 eth3 eth4
(メモ:eth1とかの部分は書き換えてあげる必要がある)
技術調査
調査方法
ログの仕込み
log.Println("label", "出力させたいもの")
コードを読んでみる
-
CLIアプリケーションを作るためのライブラリとしてCobraが使われてた。
-
buildとかはできた
- NICを用意したりパケットを飛ばし合うのが無理だったので、実際に動かして確認はできなかった。
-
コマンドとしてあるもの
- routor init
- routor start ←今回叩いているコマンド
- routor recieve
- 未実装
- routor init
- routor store
- routor router
-
ファイル別の実装されている内容
- cmd/routing/routing.go
- メイン関数が書いてあるだけ。
- pkg/routing/arp/*
- Macアドレスを取得するためのコード(arp.go)とMacアドレスの情報を入れるための構造体(interface.go)
- pkg/routing/cmd/*
- CLIのコマンドの実装
- pkg/routing/route/*
- 今回使われていない
- pkg/routing/router/*
- ルーティングの処理が書かれている部(router.go)とルーティング情報を詰めるための構造体(interface.go)
- pkg/routing/tool/*
- 今回使われていない
- pkg/routing/interface.go
- 今回使われていない
- cmd/routing/routing.go
考えられる検証手順
- コードを読む(読んだ)
- Logを埋め込む
- どんなパケットが落ちてるかみる
原因の候補
- コードを読んだ感想
- Ipv6に対応してないから?
- これだったらトラコンじゃない。
- MacアドレスとIPアドレスの組みが変わったときに対応できないから?
- Ipv6に対応してないから?
解説
原因
原因はGolangにてルーティングの実装に問題があることが原因です。 Golangにて、Channelを使った処理にミスが生じており、適切なNICにパケットを送り出せていないため起きている問題です。
HostA => HostD宛にPingを飛ばした場合でも、Channelの実装ミスによりHostA,B,C,Dのどれかにパケット転送してしまうというバグによって引き起こされる問題です。
解決方法
pkg/routing/router/router.go
func routerReceive(device string) error {
if err != nil {
log.Println(err)
}
+ } else {
+ packets <- msg
}
}
}()
解説に対するメモ
router.go
内の、
msg := <-packets
if msg.Interface == mac.String() {
err := handle.WritePacketData(msg.Packets)
if err != nil {
log.Println(err)
}
}
の部分で、あるスレッド(goroutine)が指定したネットワークデバイス(NIC)で送信しないパケットを受け取った場合にパケットを破棄してしまっていることが原因。 本当は、自分が送信するパケットじゃなかったら他のNICを指定したスレッド(goroutine)に渡してあげないといけない。 と言うことに気が付けなくてピエン🥺
採点基準
パケットロスをせずにpingが飛ぶこと(100%)
ボク わるいフレームワークじゃないよ
解いた人:Hunachi
問題
参照した問題・解説のサイト:ボク わるいフレームワークじゃないよ
使用環境・ツール
- Python
- Django
バグの内容
curl 192.168.7.2:8000/message/
ではエラーが返ってくる。
理想の終了状態
192.168.7.2:8000/message/
にアクセスし、メッセージを確認できるようにすること
初期状態
/home/ictsc
でpython3 manage.py runserver 192.168.7.2:8000
を実行し、curl http://192.168.7.2:8000/message/
実行してもメッセージが見れない。
考えられる検証、修正手順
- curlを叩いた時のエラーの内容を確認する。
curl -v
などする。
python3 manage.py runserver
でググるdjango
に関する情報ばっかり出てくるので,djangoが使われている?
- 以下の技術調査をしたときの構成と比較してみておかしなとこがないかを確認🔍🔍
技術調査
Djangoわからんので,とりあえず使ってみる
- Djangoとは
- Webアプリを簡単に作れるようにするためのPythonフレームワーク
- https://www.djangoproject.com/
Pycharm
でプロジェクトを作った。- ぼたんぽちぽちするだけ。
- セットアップするためのコマンドをぽちぽち
python3 manage.py migrate
python3 manage.py createsuperuser
- 動いてるかの確認
python3 manage.py runserver
-
Starting development server at http://127.0.0.1:8000/
とのことなので,curl http://127.0.0.1:8000/
- なんかエラーもテキストも返ってこなかった.
- アクセスすると
- 寂しいので,
urlpatterns
にpath('', admin.site.urls),
を追加した。(←なぜかadminページを表示させる人。。) - verboseオプションつけてリベンジだぜ!
< HTTP/1.1 302 Found < Date: Mon, 23 Aug 2021 10:00:00 GMT < Server: WSGIServer/0.2 CPython/3.9.6 < Content-Type: text/html; charset=utf-8 < Location: /admin/login/?next=/
とのことなので,curl http://127.0.0.1:8000/admin/login/
で結果を見てみる。- いい感じにhtmlが返ってきていた。
- せっかくなのでアクセスもしてみる。
- なんかいい感じに表示されていた。動いてそう。
- /message/時の動きも知りたい。
- とりあえず
http://127.0.0.1:8000/message/
ができるようにしする。 - 公式チュートリアル https://docs.djangoproject.com/en/3.2/intro/tutorial01/#the-development-server を参考に進めていく。
- 王道を行きたいので公式の真似して(公式ではpollsだけど)message appを作ってみる。
python3 manage.py startapp message
- なんかmessageディレクトリができた。楽ちんだ。
- あとはチュートリアルを真似て
views.py
のコード書いた。- https://docs.djangoproject.com/en/3.2/intro/tutorial01/#write-your-first-view
def index(request): return HttpResponse("This is Message's View.")
urls.py
のurlpatterns
にpath('message/', message.views.index),
追加.(チュートリアルより最短経路なので真面目に何か作るときはmessage.urls.py
も作るなどちゃんとしたがいいと思われる。)python3 manage.py runserver
し直す。curl http://127.0.0.1:8000/message/
- 結果:
This is Message's View.%
- 結果:
- とりあえず
- ホストを指定して実行してみる
- ループバックアドレス以外を指定してみたいので,自分の環境で使えるIPアドレスを探した
172.16.0.21
が自分のPCに割り当てられてたのでこれを使う- もっといい方法色々ありそうだなぁ。
python3 manage.py runserver 172.16.0.21:8000
curl http://172.16.0.21:8000/message
- なんかさっきと違う。。
- サイトを見てみる
settings.py
でALLOW_HOSTS = ['172.16.0.21']
- 同じエラーを起こしてブログ書いてる人がいた。
- 治った!
http://172.16.0.21:8000/message
curl http://172.16.0.21:8000/message/
This is Message's View.%
http://172.16.0.21:8000
もうまく動いてる!
- ループバックアドレス以外を指定してみたいので,自分の環境で使えるIPアドレスを探した
解説
原因
Django
のsettings.py
の中のALLOWED_HOSTS
に何も指定されていなかったため、トラブルが発生していた。
解決方法は,↑の技術調査と同じ◎!
curl
した時に,
<div id="explanation">
<p>
You're seeing this error because you have <code>DEBUG = True</code> in your
Django settings file. Change that to <code>False</code>, and Django will
display a standard page generated by the handler for this status code.
</p>
</div>
をみるとdebug
がTrueになっているからバグってると言われるので,これをFalseにする。つまりsettings.py
のDEBUG=False
にする。
すると,
CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
というエラーが出るので,ALLOW_HOSTS = ['172.16.0.21']
としてあげる。
ふなちの感想
:100:
curl
の結果だけで解決できるようになりたい!
採点基準
- curl http://192.168.7.2:8000/message/ 実行してもメッセージが見れない。(0%)
- curl http://192.168.7.2:8000/message/ でメッセージが確認でき、かつ回答時にメッセージを記載している(80%)
- curl http://192.168.7.2:8000/message/ でメッセージが確認でき、かつ回答時にメッセージを記載している(100%
インターネット壊れた
解いた人:nonnonno
参照した問題・解説のサイト:インターネット壊れた
使用環境・ツール
BGP(プロトコル)
ルータ
PC
問題文でされた操作
- BGPの検証ネットワークを構築してみたところ、PC1<–>PC2間でpingが飛ばない
- ただしR3実環境ではとある組織の所有物なので設定の変更はできないものとし、R3は何かしらのダイナミックのルーティングプロトコルが動作している
バグの内容
手元のPC(おそらくPC1)から $ ping 192.168.19.30 をしても応答がない。
理想の終了状態
PC1からPC2までpingが正しく飛ぶ。
考えられる原因とその検証・修正手順
pingが飛ばない原因について
参考
PCのNICの状態、IPアドレス設定など、PC側に問題がないかどうか
- PCのNICにLANケーブルは接続されているか
- PCのNICのLANアダプタの設定は有効か
ifconfigコマンドで(aオプションとか付けるとよさそう)NIC情報を確認
4行目冒頭あたりにUPと書いてあれば正常に稼働していることを意味する また、同じくifconfigコマンドで、割り振られているIPアドレスやサブネットマスク、デフォルトゲートウェイの設定情報を確認する
デフォルトゲートウェイへのpingを飛ばし、L2,L3デバイス設定の確認
デフォルトゲートウェイとなるL3スイッチorルータでICMPをブロックする設定がないかどうか確認
ACLの定義参考リンク参考、以下のように設定可能
Cisco(config)# access-list 101 permit icmp any any echo-reply
Cisco(config)# access-list 101 permit icmp any any time-exceeded
Cisco(config)# access-list 101 permit icmp any any unreachable
-
PCに設定しているIPアドレス、サブネットマスクの値が間違っている。
-
デフォルトゲートウェイのL3デバイスのIPアドレスの設定が間違っている。
-
デフォルトゲートウェイのL3デバイスのLANポートを有効化(no shut)していない。
-
デフォルトゲートウェイのL3デバイスのLANポートにストレートケーブルまたはクロスケーブルの適切なLANケーブルが接続されておらず、ポートLEDがグリーン状態ではない。
-
PCとデフォルトゲートウェイのL3デバイスの間にあるL2スイッチで、適切なVLANが設定されていないなど、L2スイッチに問題がある。
宛先デバイスに到達するまでの経路の確認
tracerouteコマンドで、経路を確認し、どのL3デバイスまで意図したようにルーティングできているか確認できる。
意図しないルーティングテーブルがないか確認
routeコマンドで、ルーティングテーブルの表示・設定を行う。
MTU値を確認
今回の問題では、ping自体も通らない。
ルータ間でMTUの値が異なると、OSPF(Open Shortest Path First、最短経路を割り出すためのプロトコル)が機能せず、ネイバー関係になることができない。
一方で、pingは通るのに他の通信データの送受信ができない、というケースもあるそうで、pingのデータサイズは通れるが、通信データのパケットサイズはMTUを超過しているために破棄されることがある。
解説
原因
R2とR3のルータ間のMTUが合っておらず、通信ができない
解決方法
今回の問題では、R3にアクセスすることができない。
よって、PC1からR2の192.168.19.26までpingを飛ばして、その経路上で、パケットがMTUを超過した際に返ってくるエラーメッセージからR3の192.168.19.25からR2の192.168.19.26までの間のMTU値を調べることができる。
具体的には以下。
ping -s MTU値 -M do IPアドレスとするため、今回はping -s MTU値 -M do 192.168.19.26
ちなみに、MTU値を変えて複数回pingをする場合、linuxがMTU値をキャッシュするためpingをする前にsudo ip route flush cache
を実行してキャッシュを削除する必要がある。
R3のMTU値が解ればR2のR3側のインターフェースに同じMTU値を設定すればOSPFのネイバーが確立できるのでEBGPのネイバーも張ることができ、PC1<–>PC2間でpingが通る。
MTUの設定は、#ip mtu 1400
とすればできるはず。
なお、R2のVyOS上でtcpdump(ネットワーク通信の生のデータをキャプチャし、その結果を出力してくれるキャプチャツール)を行って、OSPFのパケットの中身からMTU値を割り出す方法もある。
とんとんとんねる
解いた人:nonnonno
参照した問題・解説のサイト:とんとんとんねる
使用環境・ツール
コントロールプレーンがないユーザープレーン環境
コントロールプレーン・ユーザプレーンとは
コントロールプレーンとは、ネットワークの制御を担うもののこと。
単一のソフトウェアによってネットワークの構成や制御を柔軟かつ動的に行う技術の総称のことを指す。
ユーザプレーン機能は、無線アクセスネットワークとデータネットワークとの間のデータパケットを処理する。
ちなみにデータプレーンとは、あるインタフェースから別のインタフェースにパケット・フレームを転送する全ての機能とプロセス。
5Gをテーマとする上でコントロールプレーン・ユーザプレーンの働きを考える。
参考リンク
そもそも端末は、通信するときに2種類の信号を基地局とやり取りしている。1つは、端末がどの基地局と接続するのか、端末が通信できる状況にあるかどうかを判断する制御信号。この信号を「コントロールプレーン(Control-Plane)信号」といい、その頭文字をとってCプレーン信号と呼ぶ。
もう1つは、写真をダウンロードしたり、ウェブサイトでショッピングをしたりといった、実際のデータを伝送する信号。これを「ユーザーデータ(User-Plane)信号」、頭文字をとってUプレーン信号という。
CとUは端末と基地局の間でやりとりする信号のこと。
従来、端末が通信するとき、2つの信号を必ず同じ基地局とやりとりしていた。
5Gネットワークでは、このCプレーンとUプレーンを分離して、別の基地局と通信できるようにしている。
なお、4Gの基地局がカバーするエリアの中に5Gの基地局のエリアが含まれていることは事前知識。
Cプレーンは広範囲をカバーする4Gの基地局に接続し、Uプレーン信号は速度で勝る5Gの基地局に接続する。
そうすることで、基地局の切り替えがスムーズになり、まだ5Gに対応していないエリアでも、4G->5Gへの切り替えによる通信切断が防げる。
トポロジ
とんとんとんねるの一枚目の画像。
UPFとは
UPFはUser Plane Functionの略称で、スマホなどで利用するデータパケットの転送を担うルータのこと。
DNとは
DNはData Networkの略で、インターネットや何らかのサービスを提供するサーバが接続されたネットワークの総称である。
問題文でされた操作・バグの内容
5G環境を構築する実験環境のためのコントロールプレーンがないユーザープレーン環境で、手元のgNodeBから $ ping 192.168.21.50
をしても応答がない。
理想の終了状態
手元のgNodeBから $ ping 192.168.21.50
をすると応答が返ってくる。
通信はトンネルインターフェースで行うこと。
トンネルインタフェースとは
間に複数のネットワーク機器が挟まれた状態のネットワーク機器同士をあたかも直結しているかのように見せる、論理インタフェース。
考えられる原因とその検証・修正手順
今回はこのケースには当てはまらないが、一般的に、VPN接続自体が失敗する場合の原因のほとんどはVPNルータの設定ミスである。
宛先となる対向ルータのアドレスや事前共有鍵などの通信に必要な情報を合わせる必要があり、それらが揃っていない場合が多い。
以下に、VPN接続が確立しているがpingなど実際の通信ができない場合に考えられる原因を挙げる。
参考
パソコンのファイアウォールの設定
悪意のある第三者に端末の存在やOSなどの情報を知られて攻撃に悪用されることを防ぐため、ファイアウォールがpingを通さない設定になっている可能性がある。
そのため、一旦ファイアウォールをオフにしてみることが有効である。
パケットフィルタリングの設定ミス
ルータの設定として、pingなどのICMPのプロトコルを利用するものは通過するようにフィルターで定義されていないと、pingパケットは通過できない。
経路設定ミス
VPNでは、宛先ネットワークとして対向の拠点のネットワークアドレスを設定する必要があり、ミスが起きやすい箇所である。
解説
原因
GTPv1-Uと呼ばれる、モバイルにおけるトンネリングプロトコルのユーザプレーン通信を題材とした問題。
これはGTPv1-UにはTEIDと呼ばれるトンネリングのアンカーになるIDが存在するが、それらが設定されていないことに起因する。
GTPv1-Uとは
GTPとは、General Packet Radio System Tunneling Protocol というトンネリングプロトコルである。
GTPプロトコルにはU-planeとC-planeがある。
TEIDとは
Tunnel Endpoint Identifierの略称。
GTPで各セッションを認識するための識別子である。
本問題の解き方の鍵としては、GTPv1-Uがどのようなフォーマットで行われるかを理解する必要がある。
解決方法
tcpdumpを使いこなして解く。
サービスの障害やエラーを表示するロギング・サービスであるjournalctlで、実行コンフィグがあるディレクトリを確認できる。
すると、systemdの挙動のコンフィグが /opt/netprob/init.sh
で展開されていることがわかり、実行コンフィグがここにあることがわかる。
gNBというホストにおけるinit.shのコンフィグの --f-tid 200
として、 hdr-creation 0 200 192.168.21.33 2152
として期待されている200のTEIDに揃える。
#!/bin/sh
sleep 0.1
ip addr add 10.0.0.1/24 dev lo
sh /home/adam/libgtp5gnl/tools/gtp5g-link add gtptun --ran &
sleep 0.1
sh /home/adam/libgtp5gnl/tools/gtp5g-tunnel add far gtptun 1 --action 2
sh /home/adam/libgtp5gnl/tools/gtp5g-tunnel add far gtptun 2 --action 2 --hdr-creation 0 100 192.168.21.34 2152
sh /home/adam/libgtp5gnl/tools/gtp5g-tunnel add pdr gtptun 1 --pcd 1 --hdr-rm 0 --ue-ipv4 10.0.0.1 --f-teid 200 192.168.21.33 --far-id 1
sh /home/adam/libgtp5gnl/tools/gtp5g-tunnel add pdr gtptun 2 --pcd 2 --ue-ipv4 10.0.0.1 --far-id 2
ip r add 192.168.21.48/28 dev gtptun
受け取り側のTEIDと送られる側のTEIDを期待通りにしておく必要がある。
Server Sent Eventsが動作しない‼
解いた人:maimai-y
参照した問題・解説のサイト:Server Sent Eventsが動作しない‼
使用環境・ツール
- nginx
nginxとは(https://wa3.i-3-i.info/word12859.html):
Apacheのライバル。「Webサーバのソフト」。
普通のコンピュータに「Webサーバのソフト」を入れることによって、Webサーバとしてのお仕事ができるコンピュータになります。
nginxの一番の特徴は、リバースプロキシの機能があることでしょうか。
問題文でされた操作
reverse proxyとして、nginxを構築した。
reverse proxyとは(https://wa3.i-3-i.info/word1755.html):
Webサーバさんの身代わりになってホームページのファイルを返してくれるサーバさんのこと。
ちなみに、リバースプロキシを使うメリットは
(1).身元を隠せる (2).負荷分散ができる
バグの内容
webページ自体は見れるが、sse(server sent events)はうまく動作しない。原因を糾明してsseが動作するようにしてほしい。
server sent eventsとは(qiita):
いわゆる "リアルタイム" イベントを サーバ -> クライアント に向けてプッシュできる技術です。
Websocketsとの違い:
Websockets は SSE に比べてより柔軟で強力な機能を持っていますが、実装の複雑さは SSE とは対象的に非常に高コストになりがちです。また大きな違いとして、Websockets では サーバ <-> クライアント 間での双方向のリアルタイム通信が可能ですが、SSE はあくまでも サーバ -> クライアント (一方向) のデータのプッシュのみをサポートしています。
理想の終了状態
Reverse Proxy上で $ curl -N localhost/event
をしたら、
$ curl -N localhost/event
data: 1
data: 2
data: 3
...以下Ctrl+Cが入力されるまで続く
考えられる検証、修正手順
「sse nginx」だけでググったら1番目から6番目までバッファに注意であることがどこかしらに書いてあった。
-
https://qiita.com/okumurakengo/items/cbe6b3717b95944083a1#nginx%E3%81%A7%E5%AE%9F%E8%A1%8C
-
apacheだと特に問題なく動いたのですが、nginxだとうまく行きませんでした、 下のコードだと1秒おきに出力して欲しいのですが、バッファされているようだけのようでタイムアウトしてしまいました。
-
PHPのファイルに
X-Accel-Buffering: no
を追加するとうまく行きました。 -
nginxの設定ファイルをいじるという方法でも大丈夫なようです。
gzip off; proxy_buffering off; fastcgi_keep_conn on; fastcgi_max_temp_file_size 0; fastcgi_buffering off;
-
-
https://qiita.com/willow-micro/items/5b245076101460d9dfd6
-
Nginxに接続すると,SSEの処理だけが絶望的に遅れる
原因:Nginxのリバースプロキシによってバッファリングが行われるため
-
FlaskからJavascriptの
EventSource
に渡すレスポンスに、ヘッダX-Accel-Buffering: no
を付与する -
Nginx側の設定に以下を追記すれば良いとの情報がみられる
proxy_set_header Connection ''; proxy_http_version 1.1; chunked_transfer_encoding off; proxy_buffering off; proxy_cache off;
-
解説
この問題でServer Sent Eventsがうまく動作しなかった原因は、/etc/nginx/nginx.conf内のproxy_buffering on;
だったことです。nginxのproxy_bufferingが有効な状態だとnginxがバッファリングしてしまい、クライアントがレスポンスを受け取れません。
想定解
/etc/nginx/nginx.conf内のproxy_buffering on;
をproxy_buffering off;
に置き換えて、nginxを再起動させること
その他解法
nginxがデフォルトで使用するhttp/1.0
はkeep-aliveを使用することは非推奨とされているので、
/etc/nginx/nginx.conf内のproxy_buffering on;
をproxy_http_version 1.1;
に置き換えて、nginxを再起動させることでもServer Sent Eventsを動作させることができます。
採点基準
部分点は付けず、
Reverse Proxy上で $ curl -N localhost/event
をしたら、
$ curl -N localhost/event` `data: 1` `data: 2` `data: 3` `...以下Ctrl+Cが入力されるまで続く
と返ってくるような解答に満点をつけました。
参照した問題・解説のサイト: https://blog.icttoracon.net/2020/11/02/
hostnameでつながらない!!
みんなでワイワイ解いた。
参照した問題・解説のサイト:hostnameでつながらない!!
使用環境・ツール
- docker-compose
- wordpress
問題文でされた操作
DockerでWordpressの構築を試みる
それにあたって、docker-compose.ymlファイルで指定したホスト名でデータベースに接続したい
バグの内容
~/wordpress/docker-compose.ymlを用いてdocker-compose up
をした時にwordpressのコンテナがデータベース接続エラーのログを残して立ち上がらない。
理想の終了状態
curl localhost:8000 -Lで正常に200レスポンスが返ってくる。
考えられる検証、修正手順
バグの原因を特定する案
docker-compose.yml
を確認する
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:
-
docker composeでwordpressを立ち上げる方法のymlファイルとの違いを確認する(特にDB周り)
- depends_on:で依存関係を指定しているか?
- mysqlの存在する場所を確認する。位置とか
volumes: - db_data:/var/lib/mysql
-
mysqlのパスワードを確認
- https://qiita.com/go_glzgo/items/3520818659a07bd17839
-
データベース自体が破損している
-
データベースサーバの異常
- トラフィック急増による過負荷
-
wp-config.php ファイルのユーザー名とパスワードの情報が正しくない
- WordPressでデータベースに接続する場合、設定情報はwp_config.phpに記述してある。この内容が、docker-compose.ymlで指定している内容と一致しているかを確認する。
-
db:3306 のデータベース サーバーに接続できない
バグの原因が見つかった際の手順
修正手順案
- 今回のエラーは、
hoge
コマンドを打つ
使用した修正手順(壊れた場合ように間違えて踏んでしまった手順は消さずに残しておく)
docker-compose.yml
ファイルを変更した。
****
解説
原因
- 任意のホスト名でコンテナの名前解決を行うにはエイリアスの設定が必要だった
networks:
default:
aliases:
- database
解決方法(もっと詳しい説明を書き加える)
docker-compose.ymlを以下のように書き換える。
(# DO NOT CHANGE THIS LINE
はヒントになるかもしれない)
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
networks:
default:
aliases:
- database #☆
wp:
depends_on:
- db
image: wordpress:latest
hostname: wordpress
ports:
- "8000:80"
restart: always
environment:
# db:3306でないので動かない ☆
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:
ダイエットしようぜ
解いた人:とり
参照した問題・解説のサイト:問題文
使用環境・ツール
- Docker
- Go
問題文でされた操作
- GoでSHA256する
hash.go
を作成し、コンテナが欲しかったためDockerfileを作成 - ビルドしてイメージを作成した
バグの内容
- 作ったDockerImageが大きすぎてテンションが上がらない。
- ヒント:
あなたには、Dockerfileを編集したりビルドコマンドを変えたり、あるいはビルド後のイメージに対してなにかしたりしてイメージを小さくしてほしい。
条件
$ docker images
で当該イメージを見ると796MBである。- Dockerのコマンドはsudoなしで実行できる。
- hash.goを編集してはならない。
- もしよければ
$ make build
でDocker imageを作って欲しい。(任意)
理想の終了状態
$ docker images
で当該イメージを見ると796MBより小さくなっている- 可能な限り小さくしてほしい
考えられる原因
Dockerfile
に必要ないものを記述している- 軽量化を頑張っていない
解決方法
- Multi-stage builds
- Alpine Linux
- 不要なパッケージやファイルの削除 https://qiita.com/ytanaka3/items/8c308db2ee58ea63626a
参考: YouTube
- Small Base image
- デフォルトで軽量化しているimageを提供している場合がある
- node.jsならslimバージョンが存在する(Docker側から公式で)
- 存在しない場合は
Alpine Linux
をスタートにしてコンテナを作る- Alpine Linux?
- デフォルトで軽量化しているimageを提供している場合がある
- Builder Pattern
- インタープリター -> 直接コードを実行する
- コンパイラ言語 -> コンパイルして実行する
- コンパイラした時のツールは実際のDocker imageを実行する際には必要ない
重いやつ
FROM go:onbuild
EXPOSE 8080
軽めにしたやつ
FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp
EXPOSE 8080
ENTRYPOINT ./goapp
変更した点:
- オンビルドイメージからAlpine Linuxに変更
- いつも見るやつだ!!!
さらに軽く変更
FROM golang:alpine AD build-env
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp
FROM alpine
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=build-env .app/goapp /app
EXPOSE 8080
ENTRYPOINT ./goapp
変更した点:
- 未処理のimegeをFROM alpineで使った
- Alpine LinuxではSSL認証が入ってなくてHTTPSが失敗するため、ルートCA認証をインストールする
- copyでコンパイル済みのコードを2こ目のコンテナにコピーする
これをマルチステージビルドというらしい! 700MB -> 12MBになる。わんちゃん問題はこれ参考にした説までもあるな🤔
解説
解法
- ベースイメージをalpineベースのものに変える(312MB)
- docker-slimを使う(57.9MB)
- マルチステージビルドを使う(8.14MB)
得点
- (ベースイメージをAlpineにして)サイズを400MB以下にした: 40%
- (Docker-slimを使って)サイズを100MB以下にした: 70%
- (Multi Stage Buildを使って)サイズを10MB以下にした: 100%
Multi Stage Buildをすべきみたいだ
解決方法(もっと詳しい説明を書き加える)
docker-slimを使う
- 導入方法
$ curl -L -O https://github.com/docker-slim/docker-slim/releases/download/1.22/dist_linux.tar.gz
$ tar zxvf dist_linux.tar.gz
$ cd dist_linux/
$ ls
docker-slim docker-slim-sensor
# 多分pathを通す。まあ通さなくても実行できるが
$ docker-slim [version|info|build|profile] [--http-probe|--remove-file-artifacts] <IMAGE_ID_OR_NAME>
https://qiita.com/ryuichi1208/items/c96d39a57e11d54f02bf
マルチステージビルドを使う
さっき説明した感じのやつ
FROM golang:1.11
WORKDIR /work
COPY ./hash.go /work
RUN go build -o app hash.go
FROM alpine:latest # FROM scratchでもいいっぽい。
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /work/app .
CMD ["./app"]
最初の4行でビルドし、次の5行でビルドした実行ファイルのみalpineにcopyするスタイル。
その他
-
go build -ldflags '-s -w'
を使うと実行ファイルが小さくなるらしい-ldflags '[pattern=]arg list'
- arguments to pass on each go tool link invocation.(ツールリンクの起動時に渡される引数です。)
- DWARF とシンボルテーブルの情報をバイナリに残さずコンパイルできる
- https://qiita.com/kitsuyui/items/d03a9de90330d8c275c8
-ldflags="-w"
でDWARFのシンボルテーブルを生成しないようにする-ldflags="-s"
によってデバッグのために使われるシンボルテーブルを全部生成しないようにする- http://tdoc.info/blog/2016/03/01/go_diet.html
- このサイトにはUPXの話も載っている
-
UPX圧縮
- ちょっとだけ(1MB)圧縮される
- バイナリは半分くらいになる
- コマンドでできるっぽい
- https://future-architect.github.io/articles/20210520b/
備品は何処へ
解いた人:Hunachi
参照した問題・解説のサイト:備品は何処へ
使用環境・ツール
- MySQL
初期状態
- MySQLが起動しroot権限にて正常動作することを確認している
- 使用するデータベースはListである
- データベースListのテーブルには以下がある
- Equipment_list(備品リスト)
- Order_company_list(注文会社リスト)
- Manufacturing_company_list(製造会社リスト)
- Location_list(所在地リスト)
終了状態
- 提出されたCSVデータは値段(priceカラム)の高い順位にソートされている
- 提出時に付随するカラムは以下の通り
- Equipment_list.ID
- Equipment_list.Name
- Order_company_list.Name
- Manufacturing_company_list.Name
- Equipment_list.Price
技術調査
MySQLの使い方
CSVを生成する方法
SELECT * FROM {DB_NAME} INFO OUTFILE 'hoge.csv'
INTO OUTFILE 'hoge.csv'
のところで出力するファイルを指定している。
参考:https://qiita.com/catatsuy/items/9fdf4423d5f4885b9bf9 csvからデータのimportをする方法も書いてるサイト: https://proengineer.internous.co.jp/content/columnfeature/6776#section100
テーブルの結合方法
外部結合
条件に一致するもの同士を結合させるのに加えて、どちらかのテーブルにしか存在しないレコードも残して結合する。 https://zenn.dev/naoki_mochizuki/articles/60603b2cdc273cd51c59
LEFT OUTER JOIN
の場合は、先に書いたテーブル側のカラムが消えない。
RIGHT OUTER JOIN
の場合は、後に書いたテーブルのカラムが消えない。
SELECT * FROM hoge
LEFT OUTER JOIN piyo ON hoge.id = piyo.id;
↑hogeのうち、どのpiyo.idとも一致しないhoge.idのレコードも残されてpiyoの方の値はnullになる。どのhoge.idとも一致しないレコードは消される。
内部結合
テーブルの指定したカラムが一致するものだけを結合する方法。 条件に合う相手がいないレコードは消される。 https://zenn.dev/naoki_mochizuki/articles/60603b2cdc273cd51c59
SELECT * FROM hoge
INNER JOIN piyo ON hoge.id = piyo.id;
自然結合
結合条件として、同じ名前のカラム同士で結合する場合、NATURAL
を付けることで結合条件を省略することができる方法。 https://www.dbonline.jp/sqlite/join/index4.html
SELECT * FROM hoge
NATURAL INNER JOIN piyo ON hoge.id = piyo.id;
複数テーブルの結合方法
SELECT * FROM hoge
INNER JOIN piyo ON hoge.id = piyo.id
INNER JOIN fuga ON piyo.f_id = fuga.id;
のように書けばいい。
解決方法
コーディングテストのようにいい感じのSELECT文を書けば良さそう。(トラブルシューティングではなさそう)
STEP 0.
DB(List)に接続する。
sudo mysql
mysql> use List;
一応テーブルの構造も確認する
mysql> desc List;
STEP 1.
各テーブルのカラムを確認するために、それぞれに対して
mysql> SELECT * FROM {DB_NAME}
をする。
IDの指定が被っている部分を確認して結合する時の条件を考える。
STEP 2.
SELECT文をかく。
- 必要なカラムを出力するようにする
SELECT e.id, e.name, o.name, m.name, e.price
- テーブルの結合を行う どういうデータが入ってるのかよくわからないので、内部結合で行うべきか外部結合で行うべきかわからないぞ。(内部結合で書いてみるぞい。)
FROM Equipment_list as e
INNER JOIN Order_company_list as o ON {条件}
INNER JOIN Manufacturing_company_list as m ON {条件}
INNER JOIN Location_list as l ON {条件}
- 並べ替える条件を入れる
ORDER BY e.price DESC
- CSVにするように
INTO OUTFILE 'submit.csv'
- 今までのを結合
SELECT e.id, e.name, o.name, m.name, e.price
INTO OUTFILE 'submit.csv'
FROM Equipment_list as e
INNER JOIN Order_company_list as o ON {条件}
INNER JOIN Manufacturing_company_list as m ON {条件}
INNER JOIN Location_list as l ON {条件}
ORDER BY e.price DESC;
解説
複数のテーブルから必要な情報を結合し情報を出力できるかという問題です。 余分なデータを表示させたくないため内部結合を使います。
Equipment_list
のIDを各テーブルのIDと結合させ表示、そして条件に合わせて検索、ソートを行うという問題でした。
答え
select Equipment_list.ID,Equipment_list.Name,Order_company_list.Name,Manufacturing_company_list.Name,Equipment_list.Date,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
inner join Location_list on Equipment_list.Use_place = Location_list.ID
where Manufacturing_company_list.ID=5 and Location_list.ID<=5
order by Price DESC
で確認した後、
$(sudo) mysql -u root -p -D List -e " 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_company = Manufacturing_company_list.ID inner join Location_list on Equipment_list.Use_place = Location_list.ID where Manufacturing_company_list.ID=5 and Location_list.ID<=5 order by Price DESC;" |sed 's/\t/,/g'> re.csv
CSV形式で出力↑。
$cat re.csv
ID,Name,Name,Name,Price
24,pc_x,Order_company_E,Manufacturing_company_E,1600
17,pc_q,Order_company_J,Manufacturing_company_E,800
解説に対するメモ
コマンドラインのオプションたち 参考:https://qiita.com/yulily@github/items/54cb6ccaacf39977455c
自分が、特定の製造会社のパソコン(Manufacturing_company_E)、特定の建物(HQ)で相性が悪く交換するためリストを作成してほしい
という条件をつけろという部分の意味が理解できてなくて、 where Manufacturing_company_list.ID=5 and Location_list.ID<=5
をつけ忘れたことが判明した。
本来すべきだったと思われる追加の作業
特定の条件を見つける。
SELECT * FROM Manufacturing_company_list WHERE Manufacturing_company_list.Name = 'Manufacturing_company_E';
SELECT * FROM Location_list WHERE Location_list.Name = 'HQ';
これらの結果から、以下を見出す必要があった。
where Manufacturing_company_list.ID=5 and Location_list.ID<=5
採点基準
- 上記のカラムが表示できるSQL文を作成できている。[40%]
- 正しく出力できている。[20%]
- 売り上げ1位から順表示ができている。[40%]
jsonが壊れた!!
解いた人:maimai-y
参照した問題・解説のサイト:jsonが壊れた!!
使用環境・ツール
- Go
- json
問題文でされた操作
GoでAPIサーバを作った。
バグの内容
json文字列をparseするときにデータがlossしてしまう。
初期状態
VM名 gogo には、golang 1.14.6がインストールされている。
VMのホームディレクトリにはtestcode.go
が置かれている。
$ go run testcode.go
を実行すると以下の結果が得られる。
- 実行結果
{10 Gopher 0}
理想の終了状態
コマンド $ go run testcode.go
を実行すると
{10 Gopher passwordnohash 99}
上記の結果が得られる。
また、問題の解決が永続化されている。
考えられる検証、修正手順
バグの原因を特定する案
- testcode.goの問題
- jsonの問題
- 形式がおかしくないか確認する
バグの原因が見つかった際の手順
- testcode.goの問題
- testcode.goを修正
- jsonの問題
- jsonを修正
修正手順案
-
エラーメッセージを読むと、testcode.goの問題のポツで書いた4つの原因のどれか、またはそれ以外かが分かる
-
もしエラーメッセージに情報が無ければ、ひとまずtestcode.goを確認した後、jsonを地道に確認
解説
原因
この問題はUserという構造体のtag指定が2点間違っていることが原因でした。
コンパイルでは見つけられない。
1
=を:に修正する。
変更前
json=""
変更後
json:""
実行結果
{10 Gopher passwordnohash 0}
2
“が抜けているため足す。
変更前
"access_count`
変更後
"access_count"`
・2だけを修正した場合
{10 Gopher 0}
・1と2を修正した場合
{10 Gopher passwordnohash 99}
解法2
go vet
コマンドによる静的解析
- go vetとは?
本当に問題になるかは分からないし、ビルドは通せるんだけど、こういう懸念があるかもね
的な問題を コンパイラとは別の観点でチェックしサジェストをしてくれる、賢い子- qiita
networkが作成できない?
参照した問題・解説のサイト:networkが作成できない?
使用環境・ツール
- docker-compose
- NetBox
問題文でされた操作
- 運用サーバに新しくインターフェイスを追加した。
バグの内容
- NetBoxうまく起動できなくなった。
考えられる原因
使われている技術について調べてみる
解説
原因
解決方法
解説で出てきたでバック方法や解決方法について調べてみる
経路が受け取れない!
解いた人:nonnonno
参照した問題・解説のサイト:経路が受け取れない!
使用環境・ツール
- ルータ
- BGP (経路制御プロトコル)
問題文でされた操作
経路が受け取れない!の一枚目の画像を参照
AS65000, AS65001, AS65002に所属するルータを3つ作成し、これらのルータ全てからフルルートをAS65010宛に流した。
バグの内容
なぜかAS65010に所属するルータで経路を受け取れない
理想の終了状態
AS65010に所属するルータにおいて、3つのルータ全てからのフルルートを受信したい
そもそもBGPとは
Border Gateway Protocolの略称で、パケットの宛先を正確に把握し、維持していくための経路制御を行うための代表的なプロトコル。
BGPは、経路制御を行う組織ごとにインターネットの世界で唯一の番号が割り当てられ、個々の経路を識別するという特徴があるため、組織内での設計と機器の準備だけでは利用することができない。
その唯一の番号はAS (Autonomousu System) 番号と呼ばれ、IANA (Internet Assigned Numbers Authority) が管理する。
AS番号とIPアドレスの割り当てを受けた後、上流ISPや経路情報を持つ相互接続点であるIXに接続が必要である。
その後に、自組織のBGPルータと接続先ルータ間でピアと呼ばれる経路交換を行う設定を行う。
BGPの経路制御はこのピアを通して行われる。
ピア設定後、自組織のIPアドレスを接続先へピアを通して通知する必要があり、このことを「アドレスをアナウンスする」と言う。
ちなみに、アナウンスだけでなく、上流ISPからインターネット上の経路を取得することも必要。
BGPルータはピアを確立するために、メッセージ形式で機能情報を交換し続ける。
なお、BGPはマルチキャストで動的にネイバーを自動検出することはできず、自分のAS内のネットワークをBGPルートとしてアドバタイズするときはnetworkコマンドを使用する。
networkコマンドで通知するためには、前提としてそのルートをルーティングテーブルで学習していることが必要。
自分のAS内のネットワークを通知する必要がない場合には、networkコマンドの設定は不要。
考えられる検証、修正手順
-
ハードが正常に動いていない
- dmesgコマンドなどでハードウェアの動作を確認する
- BGPのデーモン自体が動いていない可能性がある BGP有効化など設定
-
経路情報が間違っており、通信ができていない/ルーティングテーブルが間違っている
-
その他トラブルシューティングの原因切り分け手順は以下のリンク
BGPルートがアドバタイズされない場合のトラブルシューティング、Cisco
解説
解法
- /var/log/messageを確認し、ルータのエラーメッセージが表示されていることを確認
- vyos(ルータなどの機器が利用するネットワークOS)によるBGPプロセス全体が停止していることを
$ show ip route
でBGPが有効ならば、対向ルータとのBGP stateがEstablishedであり、広報されている経路情報を新しく受け取っていることがわかる- VyosによるBGP設定、ISP構築手順のコマンド参考、朝日ネット
- (今回はBGPが無効になっており、)外部からあまりにも経路広告が行われ、メモリがオーバーフローしていることを認識(予備知識?)
得点ポイント
- BGPデーモンが停止している
- 経路が多すぎてメモリがオーバーフローしている
またビルド失敗しちゃった~…
解いた人:Hunachi
参照した問題・解説のサイト:またビルド失敗しちゃった~…
使用環境・ツール
- Docker
- Dockerfile
- Go
バグの内容
Dockerのマルチステージビルドを使って、Goのバイナリをコンテナ上で実行しようとしたらうまく立ち上がらない. Dockerfileが間違えている.
~/app/Dockerfile
を用いてdocker image build -t ictsc2020:0.1 .
したあとに、docker run -p 80:1323 [コンテナID]
をするとエラーが表示され、コンテナ上のバイナリが正常に実行できない.
理想の終了状態
curl localhost
でWelcome to ICTSC2020!
が返ってくる.
また、問題の解決が永続化されている。
技術調査
Dockerのマルチステージビルドとは?
- https://docs.docker.com/develop/develop-images/multistage-build/
- ↑の訳らしい: https://qiita.com/carimatics/items/01663d32bf9983cfbcfe
- 必要なバイナリorファイルだけをコピーしてきたイメージを楽に作ることができるようになる。
Dockerfileの内容をみてみる
// ~/app/Dockerfileの内容
1:FROM golang:1.15.0 AS builder
2:ENV GO111MODULE=on
3:ENV GOPATH=
4:COPY ./server/main.go ./
5:RUN go mod init ictsc2020
6:RUN go build -o /app ./main.go
7:FROM alpine:3.12
8:COPY --from=builder /app .
9:EXPOSE 1323
10:ENTRYPOINT ["./app"]
-
1行目: 新しい(1つ目の)ビルドステージの開始
- 1つ目のコンテナの作成
- golang:1.15.0を親イメージとして指定
- ビルドステージに builder という名前をつけている。(デフォルトでは 0 から連番で付けられる。)
-
2行目: Go modulesを使うという指定
- 何も指定しないor auto指定だとgo.modファイルがある時だけGo modulesを使うとみなされる。
- Go modulesを使うと、importしたいライブラリと、ライブラリの依存ファイルをgo.modに書いてあげるとダウンロードしてくれてビルドしてくれるようになる。
-
3行目:Goのファイルたちが置いてあるパスの指定
- 特に指定してない。
- import文の解決に使うために設定するらしい。
-
4行目:./server/main.go を ./以下にコピー
-
5行目:go moduleの作成
-
6行目:main.goをビルドしてappバイナリファイルを生成する
-
7行目: 新しい(2つ目の)ビルドステージの開始
- 2つ目のコンテナの作成
- alpine:3.12を親イメージとして指定
- https://alpinelinux.org/
-
8行目: builderという名前のビルドステージ(1つ目に作ったもの)の/appを.以下に持ってくる
-
9行目: 1323番ポートを公開する
- https://zenn.dev/suiudou/articles/5e1dfd1008bf29
-
10行目: ./appを必ず実行する
- https://pocketstudio.net/2020/01/31/cmd-and-entrypoint/
考えられる検証、修正手順
- エラーの内容を確認する
- main.goのコードがおかしい
- main.goのコードを確認してみる
docker multistage build golang runtime error
でググって一番に出てきたやつ:https://stackoverflow.com/questions/56057688/issue-with-docker-multi-stage-builds- 解決手順
ldd
コマンドを使ってバイナリファイルの依存関係の確認RUN
コマンドにCGO_ENABLED=0
を付け加えて実行し直してみる
- 解決手順
原因ではなさそうなこと
- imageは作成できており実行時エラーのため、Dockerファイル内のコマンドが途中で転けているとかではない
- ポートの指定方法
解説
前提
golangで書いたプログラムを
go build
すると、基本的には静的リンクになるがnet
パッケージを使用していた場合、自動的に動的リンクになる。(go1.4から)
原因
Dockerのマルチステージビルドをする際に、ビルドされたバイナリが動的リンクなせいでコンテナ間を移動させたあとに実行しようしてもうまくいかなかったため。
解決方法
~/app/Dockerfile
の6行目のRUN go build -o /app ./main.go
をRUN CGO_ENABLED=0 go build -o /app ./main.go
に変更- コンテナをbuildし直す。
docker image build -t ictsc2020:0.1
- 実行し直す
docker run -p 80:1323 [コンテナID]
想定外解放としてあったもの
- 動的リンクのまま、lddコマンドを使用し依存しているライブラリを特定して、そのライブラリをbuilderコンテナから実行用のコンテナにコピー・配置する
解説に対するメモ
-
実行時に
Not Found
っていうエラーが起きた時は、動的リンクがうまくいっていないパターンも考えるといいっぽい。動的リンク -
バイナリを静的にコンパイルさせる為には
CGO_ENABLED=0
を付ける。- https://www.docker.com/blog/containerize-your-go-developer-environment-part-1/
-
ldd
コマンド- すべての依存関係のパス名をリストで表示してくれる。
- 動的リンクやファイルの依存関係のエラーの時に役に立ちそう?
- 多分
ldd app
で調べることができる。 - https://www.ibm.com/docs/ja/ssw_aix_71/l_commands/ldd.html
- https://wa3.i-3-i.info/word13813.html
採点基準
- Dockerfileに正しい改善がなされている: 50%
- curl localhostをして、正しいレスポンスが返ってくる: 50%
WEB ページが見れない
解いた人:momom-i
参照した問題・解説のサイト:WEB ページが見れない
使用環境・ツール
- apache
問題文でされた操作
- apacheのDocumentRootを/var/www/htmlから/home/user/htmlに変更
$curl http://127.0.0.1/home.html
を実行
バグの内容
webサーバ上で$curl http://127.0.0.1/home.html
をすると403エラーが返ってくる
理想の終了状態
- webサーバ上で
$curl http://127.0.0.1/home.html
をするとページが返ってくる。 - 再起動後も問題が解決している
考えられる原因
- 権限がない
- ドキュメントルートを宣言してるhttpd.confの記述に問題あり
解決方法
- home.htmlの読み取り権限を付与
- ドキュメントルートまでのディレクトリにも読み取り権限を付与
- httpd.confのDirectoryセクションの設定で
/home/user/html
などのパスがRequire all denied
になっていたら変更する - SELinuxの無効化
getenforceコマンドを打って
Enforcing
と出る場合は、有効になっているらしい
setenforce 0
上記のコマンドを打つことで一時的に無効化することができるが、サーバの再起動後も無効化し続けるには、/etc/selinux/config
において、SELINUX=enforcing
と書かれているところをSELINUX=disabled
とする
- SELinuxの有効化の状態で
/home/user/html
にhttpd_sys_content_t
タイプを付与 SELinuxはもしユーザが乗っ取られても、例えばhttpd_sys_content_t
というタイプがファイルやディレクトリについていたら、httpd
が実行するスクリプトでの読み込みしか許可しないので安全だよ〜という風に強固なセキュリティを提供するツールである。
ls -Z
まず上のコマンドで現在のディレクトリのSELinuxのタイプを確認することができる。
chconコマンドでも、実行後新たにrestoreconでタイプ再付与などしない限り、付与状態が続く。が、永続的に付与する場合は以下のコマンドの通りにする。chcon
コマンドで一時的な付与ができるが、今回は再起動しても付与した状態にしたいので、
semanage fcontext -a -t httpd_sys_content_t /home/user/html
restorecon -RF /home/user/html
(https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/security-enhanced_linux/sect-security-enhanced_linux-selinux_contexts_labeling_files-persistent_changes_semanage_fcontext 参照)
※参考※
-
ドキュメントルート:
リクエストに対してどのファイルを送信するかを決定するときの Apache のデフォルトの動作は、リクエストの URL-Path (URL のホスト名と ポート番号の後に続く部分) を取り出して設定ファイルで指定されている DocumentRoot の最後に追加する、というものです。ですから、 DocumentRoot の下のディレクトリやファイルがウェブから見える基本のドキュメントの木構造を なします。(参考URLより) -
SELinux:
通常のパーミッションなどで防ぎきれないセキュリティを提供するもの。有効化していると余計な挙動をすることもあって、無効化することも多いらしいヨ。詳しいことは公式参考URLへ。(結構難しそう) -
[SELinux参考URL]
- https://eng-entrance.com/linux-selinux
- https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/security-enhanced_linux/chap-security-enhanced_linux-working_with_selinux#sect-Security-Enhanced_Linux-Working_with_SELinux-SELinux_Packages (公式)
解説
- エラーログを見る (さっきの回答でログを確認すること忘れてた〜〜)
(13)Permission denied: [client 127.0.0.1:45546] AH00035: access to /home.html denied
- /home/user/htmlに一般ユーザの実行権を与える
sudo chmod o+x -R /home/user/html
- 再びエラーログを見る
SELinux policy enabled; httpd running as context system_u:system_r:httpd_t:s0
- SELinuxコンテキストのラベル付けをする
sudo chcon -R -t httpd_sys_content_t /home/user/html
(今回は無効化はなしという制約があったっぽい。semanage & restoreconコマンドの方でも○みたい。)
chconはSELinuxコンテキスト変更のコマンド(参考)
-R
: ディレクトリの変更
-t
: タイプの変更。タイプとはSELinuxが定めているパーミッション制御方法のこと!httpd_sys_content_t
の詳細は上に書いてあって、他のタイプについては、ここ
- またまたエラーログを見る
[authz_core:error] [pid 18739] [client 127.0.0.1:45548] AH01630: client denied by server configuration: /home/user/html/home.html
- /etc/httpd/conf/httpd.confに追記する
<Directory "/home/user/html">
AllowOverride None
# Allow open access:
Require all granted
</Directory>
7. curl成功
※変更後のapache再起動は省略してるWEB ページが見れない
Docker 勉強会
書いた人:Hunachi
*第一回勉強会*
開催日時:6/21 (月)21:30~23:00
内容(予定)
- 自己紹介と雑談
- Dockerとは何か
- コンテナを起動させてみる
- 今回使った
docker run
のオプション等について - コンテナが起動されるときの挙動
docker run
のオプションを追加で調べてみる- Docker composeについて少し
次までの宿題or時間が余ったら(任意だけど、次回使うかも)
- Docker(Docker compose)を使ってWordpressを動かしてみよう!
*第二回勉強会*
Docker について学ぶ
書いた人:Hunachi
Docker の使われ方
以下のような時に使われる
- 基盤システム(インフラ)から切り離して、アプリケーションを実行したいとき
- 基盤システムが異なる環境でもアプリケーションに同様の動作をさせたいとき
- 同じ環境をささっと作りたいとき
などなど..
もっと詳しくDockerの基礎を学ぶ
http://docs.docker.jp/get-started/overview.html# を読んでいく。
仮想化とコンテナ
(ホスト型)仮想化とは、CPUやメモリ、I/Oまでも仮想化し、複数のOSを単一のシステム上で実行できるようにする技術。 これを実現するソフトウェアのことをハイパーバイザー(hypervisor)という。
コンテナ(型仮想化)とは、カーネルランドよりも上を仮想化する技術。つまり、コンテナが作られる土台となる基盤システムと互換性のあるコンテナしか作れない。
Docker の挙動
コンテナが起動される時の挙動
https://docs.docker.jp/engine/reference/commandline/run.html
https://docs.docker.jp/engine/introduction/understanding-docker.html の画像がわかりやすい。
コマンドたちの説明
docker run
指定したイメージ(image)からコンテナの作成、起動を行う。
使い方
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
オプション
[OPTIONS]の部分で指定できること
-
-d オプション
- デタッチド・モードで起動する
- このオプションを付けない時はフォアグラウンド・モードで起動される(コンソールに表示させている間だけコンテナが起動するようになる)
-
--name オプション
- コンテナに名前をつけるためのオプション
- --name container-name のように使う。
-
-p オプション
- どのホストのポートでどのコンテナのポートを公開するか指定するためのオプション
- -p host-port:container-port のように使う。(例:-p 8080:80)
Docker Composeとは
https://docs.docker.com/compose/
複数のコンテナを使って、アプリケーションを起動させたい時に使われる。 yamlファイルを使ってアプリケーションのサービス(コンテナたち)の設定を書いて、その設定でサービス(コンテナたち)を起動する。 複数のコンテナを使う場合はこの方法を取らないと通信の設定がとても面倒になったりする。
docker-compose.yaml
があるディレクトリ内(同じ階層)で
docker compose up
コマンドを実行すると起動することができる。
バックグラウンドで動かしたい場合は、-d
をつけるのを忘れないように。
Docker を触ってみる
書いた人:Hunachi
Docker を使うための環境構築
Docker DesktopのDownloadとinstallをする。 https://www.docker.com/get-started
Dockerチュートリアルのコンテナを作る
- https://www.docker.com/101-tutorial の以下のコマンドを打つ。
docker run -dp 80:80 docker/getting-started
http://localhost
でチュートリアルが表示されているかを確認する。