# 用 Docker 架設班級 git-it 練習環境
###### COSCUP 2016
###### By 和風信使 ( [@taichunmin](https://github.com/taichunmin) )
---
#### 投影片 <http://goo.gl/3aHqNd>
#### 原檔 <https://goo.gl/EiUL61>
###### 等等會有一些程式碼
###### 在投影片上面不容易複製
###### 建議可以在原檔複製
---
# DEMO!
---
# 緣起
---
## 網路上有很多 git 教學
---
## 但是幾乎大多數
---
## 都是模擬的系統
---
## 偶然之間
## 得知有 git-it
## 這套教學軟體
---
## 除了要在終端機下
## 實機操作 git
---
## 還要實機操作 GitHub
---
## Very Good
---
## BUT
---
## 但是社團跟系上借的
## 電腦教室
---
## 全部都有裝還原卡
---
## 一堂課的時間很寶貴
---
## 光安裝系統就飽了
---
## 練習 git-it 根本來不及
---
## 而且電腦教室的網路
## 所有人一起抓軟體時
---
## 就會慢到跟烏龜一樣
---
## 於是希望
## 可以省掉 git-it 安裝
---
## 還希望能有即時成績看板
### ~~ACM 中毒~~
---
# 使用 docker 架設 git-it 環境
---
## 首先 我們需要
---
## 能執行 Docker 的主機
---
## 以下是我用過的虛擬主機
### DigitalOcean
* `Create Droplet`→`One-click Apps`→`Docker 1.x.x on 14.04`
### AWS EC2
* `Ubuntu Server 14.04 LTS (HVM)`→自己安裝 Docker
---
## 如果你需要安裝 Docker
* [官方 Linux 安裝腳本](https://get.docker.com/)
* [Ubuntu](https://docs.docker.com/engine/installation/linux/ubuntulinux/)
* [其他版本](https://docs.docker.com/engine/installation/)
> 如果你用 DigitalOcean 的 `Docker 1.x.x on 14.04` 就可以跳過啦。
---
## 實做時間
* 請大家完成到可成功測試 Docker
```sh
sudo service docker start
sudo docker run hello-world
```
---
## 接下來我們
## 要來寫 Dockerfile
---
# Dockerfile 是什麼?
---
#### Dockerfile 包含創建映像檔所需要的全部指令。
#### 我們可以使用 `docker build` 指令來創建映像檔。
#### 通過減少映像檔和容器的創建程序來簡化部署。
---
## 先來看幾個
## Dockerfile 的訣竅
---
## 撰寫 Dockerfile 訣竅
1. 先開一個基礎的容器
```sh
# 開啟一個基礎的 ubuntu:14.04 指令
sudo docker run -ti ubuntu:14.04 /bin/bash
```
2. 按照安裝教學安裝一次
3. 將指令改成 non-interactive (不須互動,自動安裝)
4. 再把指令複製到 Dockerfile
---
## git-it 安裝教學
#### <https://github.com/jlord/git-it/blob/master/original-readme.md>
---
## 系統更新
```sh
sudo apt-get update
sudo apt-get upgrade
```
* non-interactive
```sh
apt-get update -qq
apt-get upgrade -y
```
---
## git-it 需求
* Git
* Node.js
* 純文字編輯器 (Text Editor)
* 英文語系 (English locales)
---
## git 安裝
```sh
sudo apt-get install git
```
* non-interactive
```sh
apt-get install -y git
```
---
## Node.js & npm 安裝
```sh
sudo apt-get install nodejs npm
# 由於 git-it 預設使用 node 執行,故須 link
sudo ln -s /usr/bin/nodejs /usr/bin/node
```
* non-interactive
```sh
apt-get install -y nodejs npm
ln -s /usr/bin/nodejs /usr/bin/node
```
---
## 純文字編輯器 (Text Editor)
```sh
sudo apt-get install vim nano
```
* non-interactive
```sh
apt-get install -y vim nano
```
---
## git-it 安裝
```sh
sudo npm install -g git-it
```
* non-interactive
```sh
npm install -g git-it
```
---
## Dockerfile 小結
```dockerfile
FROM ubuntu:14.04
RUN apt-get update -qq
RUN apt-get upgrade -y
RUN apt-get install -y git nodejs npm vim nano
RUN ln -s /usr/bin/nodejs /usr/bin/node
RUN npm install -g git-it
```
---
## 接下來
---
## 該怎麼讓學員
## 進到自己的蘿蔔坑呢
---
## 我們只有很少的主機
---
## 一台主機上有很多個 client
---
## 不能讓學員
## 從 host 進入 client
#### ~~r m - r f /~~
---
# SSH
###### Secure Shell
---
## 然後再把
## 每個 client 的 SSH 埠
---
## port forwarding 出來
---
# BUT
---
## Docker 容器
## 通常預設沒有 ssh
---
## 架設 ssh 教學
* Docker 官方範例 <https://goo.gl/edqX2W>
---
# Dockerfile 小結
```dockerfile
# ... 略
RUN apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:git-it' | chpasswd
RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
```
---
# 現在
---
## 你的 Dockerfile
## 已經足以練習 git-it
---
# BUT
---
## 一次面對一個班級的時候
## 還是有很多問題
---
## 該怎麼快速開很多 client?
---
# ~~docker start~~
# docker-compose
---
## 安裝 docker-compose
請注意 Docker Compose 的版本。
```bash
curl -L https://github.com/docker/compose/releases/download/1.8.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version
```
> 參考網址
>
> 1. <https://docs.docker.com/compose/install/>
> 2. <https://github.com/docker/compose/releases>
---
# docker-compose.yml
```yml
version: '2'
services:
client:
build: client/
ports:
- "22"
```
> YML 的縮行很重要
---
## 開多台 client
* 可自由指定數量
```sh
sudo docker-compose up -d
sudo docker-compose scale client=5
```
---
# VPS 的 RAM 太小?
---
## 設定 swap (選擇性)
如果開的機器 RAM 較小,則建議手動新增 swap 以供 Docker 使用。
* <https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04>
---
## 該怎麼指派
## SSH PORT 給學員?
---
## 查詢 port forwarding 列表
```sh
docker-compose ps
```
```
Name Ports
--------------------------------------------------------------------
gititcoursedocker_client_1 0.0.0.0:32794->22/tcp
scoreboard 0.0.0.0:22000->22/tcp, 0.0.0.0:80->80/tcp
```
---
## client 的密碼
## 所有人都知道
## 該怎麼辦?
---
## 強制登入後更改密碼
在 Dockerfile 加上
```Dockerfile
RUN chage -d 0 root
```
---
## 實做時間
* 使用 SSH 進入 client
* 登入後立即要求更改 root 密碼
* 輸入 `git-it` 後進入選題選單
---
## 接下來是比較進階的主題
---
# scoreboard
---
# scoreboard
## 能幫助我們什麼呢?
---
## scoreboard 能幫我們...
* 顯示 client 的 SSH PORT
* 顯示哪個 client 還沒有人使用
* 顯示學員的名字
* 顯示學員的解題數
* 使用網頁公開資料
---
## scoreboard 該怎麼用程式
## 抓取 client 的 SSH PORT?
---
#### 先幫 client 的 Dockerfile 加上 LABEL
```Dockerfile
# Dockerfile
LABEL role="git-it-client"
```
---
## 使用 LABEL 進行過濾
* 在 Docker Remote API [文件](https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#list-containers) [範例](https://github.com/taichunmin/git-it-course-scoreboard/blob/master/app/Console/Commands/DockerRemotePortsUpdate.php)
```sh
curl http://host/containers/json?filters={"label":["role=git-it-client"]}
```
* docker ps [範例](https://github.com/taichunmin/git-it-course-docker/blob/master/port-reporter.sh)
```sh
docker ps --filter "label=role=git-it-client" --format '"{{.ID}}":{{.Ports}}'
```
---
## 接下來是 client 的回報程式
---
## client 回報是否有人使用
1. 查詢登入密碼是否已被更改
```sh
OWNED=`[ "never" = "$(chage -l root | grep 'Password expires' | sed -r 's/^[^:]+: //')" ] && echo '1' || echo '0'`
```
2. client 定期 (cron) 回報給 scoreboard
---
## client 回報學員的名字
git-it 的題目會要求幫 git 設定使用者的名字和 GitHub 的帳號,所以直接抓 git 的 `user.name` 和 `user.username` 就好了。
```sh
NAME=`git config --global user.name || hostname`
GITHUB=`git config --global user.username || echo ''`
```
---
## 顯示學員的解題數
git-it 會把解題數存放在 `~/.config/git-it/completed.json`
```sh
COMPLETED=`test -r /root/.config/git-it/completed.json && cat /root/.config/git-it/completed.json || echo []`
```
---
## client 需安裝 curl
```sh
sudo apt-get install curl
```
* non-interactive
```sh
apt-get install -y curl
```
---
## 回報資料給 scoreboard
```
#!/bin/bash
COMPLETED=`test -r /root/.config/git-it/completed.json && cat /root/.config/git-it/completed.json || echo []`
MID=`hostname`
NAME=`git config --global user.name || hostname`
GITHUB=`git config --global user.username || echo ''`
OWNED=`[ "never" = "$(chage -l root | grep 'Password expires' | sed -r 's/^[^:]+: //')" ] && echo '1' || echo '0'`
curl
--data-urlencode "completed=${COMPLETED}"
--data-urlencode "mid=${MID}"
--data-urlencode "name=${NAME}"
--data-urlencode "github=${GITHUB}"
--data-urlencode "owned=${OWNED}"
http://scoreboard/completed/update
```
---
## 為什麼剛剛的回報程式
## 可以用 scoreboard 當作主機?
---
## 因為 Docker 的 LINK
## 會很貼心的幫我們設定
## DNS 解析 `/etc/hosts`
---
## 於 client 中
## 架設 crontab 定期回報
---
## 撰寫 `/etc/crontab`
每分鐘回報 client 的資料給 scoreboard,以 root 身份執行。
```cron
* * * * * root /usr/bin/scoreboard-reporter.sh >> /dev/null 2>&1
```
---
## client Dockerfile 加入 cron
* 參考 <https://github.com/aptible/docker-cron-example>
```Dockerfile
RUN apt-get -y install rsyslog
ADD crontab /etc/crontab
ADD client-start.sh /usr/bin/client-start.sh
ADD scoreboard-reporter.sh /usr/bin/scoreboard-reporter.sh
RUN chmod +x /usr/bin/client-start.sh
RUN chmod +x /usr/bin/scoreboard-reporter.sh
RUN touch /var/log/cron.log
```
---
## client 啟動腳本
由於我們需要執行的程式已經超過一個了,為了避免 Dockerfile 的 CMD 指令太過複雜,所以我們要開新 client 要執行的指令都寫在 `client-start.sh`
```sh
#!/bin/sh
# client-start.sh
sleep 5
rsyslogd
cron
/usr/sbin/sshd # 刪掉 -D 參數
/usr/bin/scoreboard-reporter.sh # 開機時先回報一次
tail -F /var/log/syslog /var/log/cron.log
```
---
## 修改 Dockerfile CMD 指令
```Dockerfile
# 要把舊的刪除,因為只有最後一個 CMD 有效
CMD /usr/bin/client-start.sh
```
---
## 實作時間
* 請參考 <https://github.com/taichunmin/git-it-course-docker/tree/master/client>
* client 完成到可以回報資料給 scoreboard
---
# scoreboard
# [scoreboard Dockerfile](https://github.com/taichunmin/git-it-course-docker/tree/master/scoreboard)
---
## 主要接收 client 的回報
## 還會去跟 Docker Remote API
## 取得每個 client
## 所對應的 PORT
---
## 單純就是一個 HTTP Server
---
## 由於不是每個人都會 PHP
## 所以就不深入談了
---
## 我已經事先把 scoreboard
## build 到 Docker Hub
---
## 可以直接用 Docker Hub
## 來寫 `docker-compose.yml`
---
#### scoreboard 的 `docker-compose.yml`
---
```yml
version: '2'
services:
scoreboard:
# build: scoreboard/
image: taichunmin/git-it-course-docker:scoreboard
ports:
- "80:80"
- "22000:22"
container_name: scoreboard
volumes:
- /var/run/docker.sock:/var/run/docker.sock
```
* port 80 是網頁伺服器
* port 22 是 SSH,為了方便就固定對應到 22000
* 設定了 `container_name` 可以避免誤開多台
* 設定 `volumes` 可以讓 scoreboard 存取 Remote API
---
## 須開放 Docker Remote API
## 給 scoreboard
---
## 開放 Docker Remote API
利用 Volumes
```Dockerfile
version: '2'
services:
scoreboard:
# 省略
volumes:
- /var/run/docker.sock:/var/run/docker.sock
```
---
## 然後我們需要修改 client 的
## `docker-compose.yml`
---
```yml
version: '2'
services:
client:
build: client/
ports:
- "22"
links:
- scoreboard
depends_on:
- scoreboard
```
* 讓 docker-compose 自動對應 port 22 出來
* LINK 到 scoreboard,讓 client 可以回報資料
* `depends_on` 讓 docker-compose 確保有 scoreboard
---
# 到此結束
# 歡迎提問
{"metaMigratedAt":"2023-06-14T15:44:56.836Z","metaMigratedFrom":"YAML","title":"用 Docker 架設班級 git-it 練習環境","breaks":true,"contributors":"[{\"id\":\"0d9a5e06-1f92-4142-b9df-fed4c8873573\",\"add\":17,\"del\":0}]"}