ICTSC9 予選問題 問1

皆様こんにちは。

この記事ではICTSC9の予選問題 問1で出題された100台のWebサーバーマイグレーションをいかに芸術的に行うかを考察したいと思います。

Write Upとしては、ShellScriptその{１,2}をご覧ください。

__完全なネタ記事ですのでご了承ください。__

また、私の発想力では至らなかった解法などは今後の勉強課題になるので是非とも別の回答をお持ちの方は記事にしていただくなどをお願いします。

## 着想に至る動機
ICTSC9の予選問題は全部で900点ですが、問1の配点は200点でした。

しかし、200点にしては問題の難易度が釣り合っていないと感じたために実行速度やツールの選定など目的ではなく手段が点数に影響するのではないかと考えました。

そこで(予選が終わった後ですが)様々な回答方法を考えてみようということになりました。また手段を芸術的なものにすれば得点が上がるのではないかということで、より芸術的な回答を考えたいと思います。

## 芸術点について
この記事では、芸術点を以下の3つで考慮します。

- 処理時間の短さ
- 単純さ
- 独創性
処理時間の短さはスケールアップした時に実行時間の差が生まれてくるため、短いほうがよいものになります。

単純さはいかにスクリプトの行数が少なくシンプルか、実行手順が少ないかということになります。長ったらしいソースをみて「(　´_ゝ｀)ﾌｰﾝ」ということになってしまっては美しくないですよね(?)

独創性はいかに他の人が思いつかないかということになります。採点者がこの解き方は面白いとなってしまっても他の回答にあれば得点は下がってしまうかもしれません。結局裁量次第ですね。

## 出題環境
参加者は踏み台サーバー(teamNN-c000)にアクセスします。(NNは参加チームのチーム番号)

WebサーバーはteamNN-c001からteamNN-c100の100台あり、teamNN-cNNNで名前解決ができる状態です。

また、全てのサーバーはUbuntu16.04、ユーザー:パスワードは共通で、sudo実行にパスワードは必要ありません。

構築に際してのソフトウェアのインストールも許可されています。

問題は踏み台サーバーからWebサーバー(teamNN-c001 ~ teamNN-c100)にnginxをインストールし、

例えばc001サーバーであれば `http://teamNN-c001/hostname.json` にアクセスするとjsonで`{"hostname":"teamNNc001"}` を返すjsonを配置する。となっています。

## 回答
### 1. shell script その1
私はこれで回答しました。

bashは最初から入っているものであり、だいたいの環境で動作するだろうということでshellで攻めました。

動作させるにはsshpassが必要なので、apt-get update後にinstallします。

```bash
#!/bin/bash
 teamNo="NN"
 password="password"
cd ~/
 mkdir .tmp
 cd .tmp

for i in $(seq -f %03g 1 100);do
 echo team${teamNo}-c${i}
 #roop start
 if [ ! -e team${teamNo}-c${i} ] ; then
 ssh-keygen -f "team${teamNo}-c${i}" -t rsa -N ""
 sshpass -p $password ssh-copy-id -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i team${teamNo}-c${i}.pub ubuntu@team${teamNo}-c${i}
 fi
 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i "team${teamNo}-c${i}" ubuntu@team${teamNo}-c${i} "sudo apt-get install -y nginx"

cat << EOF > team${teamNo}-c${i}.conf
 server{
 listen 80;
 server_name team${teamNo}-c${i};
 location /{
 }
 }
 EOF

scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i "team${teamNo}-c${i}" team${teamNo}-c${i}.conf team${teamNo}-c${i}:~/team${teamNo}-c${i}.conf
 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i team${teamNo}-c${i} ubuntu@team${teamNo}-c${i} sudo cp team${teamNo}-c${i}.conf "/etc/nginx/conf.d/team${teamNo}-c${i}.conf"

cat << EOF > team${teamNo}-c${i}.json
 {"hostname":"team${teamNo}-c${i}"}
 EOF

scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i team${teamNo}-c${i} team${teamNo}-c${i}.json team${teamNo}-c${i}:~/hostname.json
 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i team${teamNo}-c${i} ubuntu@team${teamNo}-c${i} sudo cp hostname.json "/usr/share/nginx/html/hostname.json"

#rm
 rm team${teamNo}-c${i}.conf
 rm team${teamNo}-c${i}.json
 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i team${teamNo}-c${i} ubuntu@team${teamNo}-c${i} sudo rm hostname.json
 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i team${teamNo}-c${i} ubuntu@team${teamNo}-c${i} sudo rm team${teamNo}-c${i}.conf

ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i team${teamNo}-c${i} ubuntu@team${teamNo}-c${i} sudo systemctl restart nginx
 done;
```

動作としては

1. 宛先のサーバーに生成した公開鍵を登録
1. 宛先でnginxをinstall
1. nginxのコンフィグを踏み台から宛先にコピー
1. 同様にhostname.jsonをコピー
1. 踏み台にあるコンフィグとhostname.jsonを削除
1. 宛先サーバーでnginxをrestart
となっています。

見直すと無駄な部分が多いです。

- nginxのconfigを書く必要がない
- sshする回数が多い
- cp後のrmならmvでよい
- etc...

そのため芸術点は低いのは必至、次に無駄な部分をそぎ落としたスクリプトを記述したいと思います。

### 2. shell script その2

```bash

#!/bin/bash
 teamNo="NN"
 password="password"

cd ~/
 mkdir .tmp
 cd .tmp

for i in $(seq -f %03g 1 100);do
 echo team${teamNo}-c${i}
 #roop start
 if [ ! -e team${teamNo}-c${i} ] ; then
 ssh-keygen -f "team${teamNo}-c${i}" -t rsa -N ""
 sshpass -p $password ssh-copy-id -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i team${teamNo}-c${i}.pub ubuntu@team${teamNo}-c${i}
 fi
 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i "team${teamNo}-c${i}" ubuntu@team${teamNo}-c${i} "sudo apt-get install -y nginx"
 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i "team${teamNo}-c${i}" ubuntu@team${teamNo}-c${i} 'echo [\"hostname\":\"`hostname -s`\"} | sudo tee /usr/share/nginx/html/hostname.json'
done
```

ワンラインがひどいですがそこそこすっきりしたように思えます。

scp後にsudo権限でコピーすることをやめ、ssh先で記述するので書き込み時間が短縮されることが大きいです。

`echo [\"hostname\":\"`hostname -s`\"} ` が通るかどうかを本番環境で行っていないため、そこはご了承ください。

実行速度としては悪くないと思いますがよくある解答だと思うので、芸術点としては低めになるかと思います。

### 3. Ansible,chefとか

順当にマイグレーションと考えればここらへんに行くと思います。

筆者は一度も触ったことがありません。

そのためイケてるエンジニアの方がWrite UPを書いててくれると信じています。

これもよくある解答になりがちですが、Ansibleなどは可読性が高く、上記の秘伝のソースよりは芸術点はそこそこ高めになると思われます。

ただ、環境にpythonが入ってなかったような気がするのでできるかどうかわかりません。

### 4. 人力
この方法はとても単純です。

単純以外の武器がないです。

サーバー100台に対して人の力でnginxをインストールし、hostname.jsonを配置し、nginxをリスタートする作業だけです。

1台5分としても1時間40分もかかるわけで、予選競技時間の41.6%の時間をインストール作業に費やすことになります。楽しいですね。

プログラムなんていう怠惰なものを使用するより、すべて人力でこなす根性のほうがすばらしいと思うような採点者であれば芸術点は高いと思います。( ~~芸術点の定義が変わっている~~

実際ヒューマンエラーを起こさずに100台こなすのは素晴らしいですが……

### 5. tmux

100台のサーバーにsshした後、synchronize-panes onにして100台同時に操作するという方法です。

人力とは違って、1回分の入力だけで済むため時間も早く、ヒューマンエラーが起こりにくいです。

また、tmuxは初期で入っていたので追加のソフトウェアのインストールがありません。

ただしミスした時に100台同時刻で変更が入るというのが個人的にはポイントです。

ここの記事から以下のスクリプトを拝借して踏み台サーバーに保存します。(以後tmux.sh)

```bash
#!/bin/bash
if [ -n "$SESSION_NAME" ];then
  session=$SESSION_NAME
else
  session=multi-ssh-`date +%s`
fi
window=multi-ssh
### tmuxのセッションを作成
tmux new-session -d -n $window -s $session
### 各ホストにsshログイン
# 最初の1台はsshするだけ
tmux send-keys "ssh $1" C-m
shift
# 残りはpaneを作成してからssh
for i in $*;do
  tmux split-window
  tmux select-layout tiled
  tmux send-keys "ssh $i" C-m
done
### 最初のpaneを選択状態にする
tmux select-pane -t 0
### paneの同期モードを設定
tmux set-window-option synchronize-panes on
### セッションにアタッチ
tmux attach-session -t $session
```

その後、`sh tmux.sh teamNN-c{001..100}`を実行し、

known_hostを承認し、パスワードを入力し、

あとは人力で

```
apt-get install nginx
echo [\"hostname\":\"`hostname -s`\"}  | sudo tee /usr/share/nginx/html/hostname.json
```
を打つだけです。

ねっ、簡単でしょ？

正直動作するかどうかはわからず、動作したとしても画面が見えるのかというのが疑問ではあります。

ただ独創性や単純さを考慮すると私の中では一番芸術点が高いかなと思います。



以上が私の思いついた解法でした。


## 終わりに

今回(ICTSC9)の予選問題 問1は単純なものの、振り返れば短時間では発想が至らない部分が多かったため、今後の課題にしたいと思います。

予選問題全体についてですが、問1、問2.{2,6,7}を解答しました。
難易度も程よく、問題を解いた達成感を得られたのでとても楽しいかったです。
また、他の問題を解いたチームメンバーの様子を見ても今回はチームの合計得点は全体的に高くなるかなと思いました。
より多くミスをしたチームが落ちるみたいな感じになりそうですね


最後にこの問1を含めた予選問題を作成した実行委員の方々、出題していただきありがとうございました。
とても楽しい4時間を過ごせました。