迫撃砲(簡単開発支援サーバ)って何?
迫撃砲(簡単開発支援サーバ)は以下のような機能を持つサーバです.
- Gitレポジトリ管理
- 自動テスト
ただのCIサーバです.しかし、最大の特徴はラズパイ上で動作する点です.小型で低価格なので以下のような場面で活躍します.
- CIサーバを購入できない貧乏チーム
- CIサーバを試してみたいチーム
- 出先で行った変更に対して自動テストを実行したい
所詮はラズパイなのでストレージ容量やCPU性能に物足りないところもあります.されど、小型なので迫撃砲(簡単開発支援サーバ)は鞄に忍びこませることができます.家でセットアップして職場に持っていくだけで速攻でCIサーバを展開できます.また、出先にも持っていけるので出張先から自社のサーバにアクセスするのに必要となる面倒なVPNの設定も必要ありません.稼働状況の表示やテストなどサーバの保守を支援する機能が搭載されているので少人数(というか一人?)での運用もできます.迫撃砲のように現場で迅速展開、仲間のために支援砲火をバカスカ打てます.
この記事では20人ほどの開発チームに低予算でラズパイを用いた迫撃砲(簡単開発支援サーバ)を導入することを目的に書きます.
ハードウェア
サーバのハードウェアにはラズパイを使用します.
ラズパイはamazonで購入できます.
基盤むき出しだと不慮の事故で壊れる可能性があるのでケースも買っておきましょう.
筆者はラズパイ4で構築しています.
ケースをつけた状態でタバコサイズです.胸ポケットに入るCIサーバです.
ラズパイにはラズパイ3とラズパイ4があります.検証はしていませんが、ラズパイ3でもこの記事に書かれた方法で構築できると思います.
ラズパイの基本設定
ラズパイをサーバとして動作させるには以下の設定をします.
- IPアドレスの固定
- ファイヤーウォールの設定
- パスワード変更
- ssh認証鍵の設定
IPアドレスの固定
IPアドレスの割当を行うdhcpcdデーモンの設定を変更して静的なIPをラズパイに割当てます.
/etc/dhcpcd.confの以下の行を変更してください.
interface eth0
static ip_address=<割り当てるIPアドレス/ネットマスク>
static routers=<ルータのアドレス>
static domain_name_servers=<ルータのアドレス(DNSサーバのアドレス)>
変更後、ラズパイを再起動します.
sudo reboot
ファイヤーウォールの設定
以下のコマンドでufwをインストールします.
sudo apt install ufw
ufwの設定、すなわち許可するポートの設定を行います.
許可すべきポートは以下のとおりです.
サービス | ポート |
---|---|
ssh | 22 |
ssh(For Gitea) | 222 |
http | 80 |
glance | 20000 |
testinfra | 30000 |
以下のコマンドで上記のポートを有効化できます.
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow 20000/tcp
sudo ufw allow 30000/tcp
sudo ufw allow 222/tcp
設定が終わったら以下のコマンドでufwを起動します.
sudo ufw enable
確認のため最後にsudo ufw status
で状態を表示しましょう.
Status: active
To Action From
-- ------ ----
22/tcp ALLOW Anywhere
80/tcp ALLOW Anywhere
20000/tcp ALLOW Anywhere
30000/tcp ALLOW Anywhere
222/tcp ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
20000/tcp (v6) ALLOW Anywhere (v6)
30000/tcp (v6) ALLOW Anywhere (v6)
222/tcp (v6) ALLOW Anywhere (v6)
22 (v6) DENY Anywhere (v6)
意図した通りにファイヤーウォールが設定されているでしょうか?上記のように表示されたら完了です.
パスワードの変更
デフォルトのパスワードはraspberry
です.これをそのまま使って置くことはセキュリティリスクとなります.忘れずに変更しましょう.
以下のコマンドで変更できます.
passwd
変更後のパスワードは忘れないように書き留めておきましょう.
ssh認証鍵の設定
パスワードを使ったログインは手軽ですが総当り攻撃や辞書攻撃によって簡単に破られる可能性があります.
そこで、認証鍵を用いたログインを行うようにします.
クライアント側で認証鍵を生成します.
ssh-keygen -t rsa
生成された**~/.ssh/id_rsa.pub**をラズパイにインストールします.
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@<ラズパイのIP>
sshでラズパイにログインしましょう.
ssh pi@<ラズパイのIP>
ログイン後、不要となったパスワードによるログインを禁止します.
ラズパイの**/etc/ssh/sshd_config**の以下の行を変更します.
ChallengeResponseAuthentication no # ここをyes -> noにする
...
PasswordAuthentication no # ここをyes -> noにする
...
UsePAM no # ここをyes -> noにする
ついでにROOTでのログインも禁止しておきましょう.
PermitRootLogin no # ここをyes -> noにする
変更を反映するためにsshdデーモンを再起動します.
sudo service sshd restart
システムの立ち上げ
これまででサーバとしての基本部分の設定が完了しました.
もちろんこのままでは迫撃砲(簡単開発支援サーバ)としては不十分です.
これからサーバとしての機能を構築していきます.
以下が迫撃砲(簡単支援サーバ)の全体像です.
迫撃砲のシステムは大きく2つの部分に大別されます.
-
サーバ管理システム
サーバの稼働状況の監視、テスト、サーバの起動を行うシステムです.以下のソフトから構成されます.- glances(サーバ稼働状況の表示)
- testinfra(単体テストの実行+ウェブページによる結果の表示)
-
Webアプリケーションシステム
レポジトリ管理ソフトとしてGitTea、自動テストソフトとしてJenkinsを稼働させます.
これらのソフトはDocker Engine上で動作します.
サーバの構築に使用したコードは以下のGithubレポジトリにアップロードしています.以降はこのレポジトリを参照しながら読み進めてください.
サーバ管理システムのセットアップ
glancesのセットアップ
glancesはサーバの稼働状況を表示するPythonアプリです.サーバの稼働状況を調べるのにいちいちsshでログインするのは面倒です.そこで、ウェブブラウザで使ってサーバにアクセスするだけでサーバの稼働状況を調べられるようにします.
まず、ラズパイにPython3とpipをインストールします.以下のコマンドでインストールできます.
sudo apt-get install -y python3 python3-pip
次にインストールしたpipを使ってglancesをインストールします.
sudo pip3 install glances
インストールが終わったらの以下のコマンドを起動してglancesウェブサーバを起動します.
glances -w --username --password --port 20000
デフォルトのパスワードの入力が求められるので入力して、その後の指示に従い保存してください.
ウェブブラウザでhttp://<ラズパイのIP>:20000
を開くとサーバ稼働状況が表示されます.
testinfra+Flaskのセットアップ
testinfraはPythonで実装されたサーババリデーションモジュールです.testinfraを使えばPythonスクリプトから
- ストレージ容量
- 実行しているDockerコンテナ
- コマンドの実行結果
取得することができます.この機能を使ってサーバが望ましい状態で稼働しているかをテストすることができます.
ただし、testinfraにはglancesのようにウェブページを介して表示する機能はありません.
そこで、Flaskと組み合わせることでウェブページからサーバのテスト結果を表示できるようにします.
まず以下のコマンドでtestinfraとFlaskをインストールします.
sudo pip3 install testinfra Flask
以下のようなPythonスクリプトを用意します.
from flask import Flask, render_template
import testinfra
from datetime import datetime
import humanfriendly
import io
app = Flask(__name__)
@app.route('/')
def index():
print("Test Executed!!")
contents = "Test Result:</br>"
return render_template('layout.html', title = 'Test', datetime =datetime.now() ,test_all = test_all)
def test_all():
result = {}
try:
host = testinfra.host.get_host("local://", sudo=True)
# test password file for root access rights
passwd = host.file("/etc/passwd")
result["Password Root"] = "OK" if passwd.contains("root") else "NG"
result["Password User"] = "OK" if passwd.user == "root" else "NG"
result["Password Group"] = "OK" if passwd.group == "root" else "NG"
result["Password Mode"] = "OK" if passwd.mode == 0o644 else "NG"
# test local host resolvable
localhost = host.addr("localhost")
result["localhost Resovable"] = "OK" if localhost.is_resolvable else "NG"
# test wan dns
wan_dns = host.addr("8.8.8.8")
result["WAN DNS Reachable"] = "OK" if wan_dns.is_reachable else "NG"
# test sshd
sshd = host.service("sshd")
result["sshd running"] = "OK" if sshd.is_running else "NG"
ssh_port = host.socket("tcp://0.0.0.0:22")
result["ssh port"] = "OK" if ssh_port.is_listening else "NG"
# test ufw
ufw = host.service("ufw")
result["ufw running"] = "OK" if ufw.is_running else "NG"
# test glances
glances_http = host.run("sudo curl -Is http://192.168.11.20:20000 | head -1")
result["glances working"] = "OK" if 'HTTP/1.0 401 Unauthorized' in glances_http.stdout else "NG"
# test docker
docker = host.service("docker")
result["docker running"] = "OK" if docker.is_running else "NG"
nginx = host.docker("nginx")
result["docker nginx"] = "OK" if nginx.is_running else "NG"
gitea = host.docker("gitea")
result["docker gitea"] = "OK" if gitea.is_running else "NG"
giteadb = host.docker("giteadb")
result["docker giteadb"] = "OK" if giteadb.is_running else "NG"
jenkins = host.docker("jenkins")
result["docker jenkins"] = "OK" if jenkins.is_running else "NG"
docker_df = host.run("sudo docker system df --format='{{.Size}}'").stdout
size = 0
for line in io.StringIO(docker_df):
size += humanfriendly.parse_size(line)
print(size)
result["docker size"] = "OK" if size < 8000000000 else "NG"
# test gitea
gitea_http = host.run("sudo curl -Is http://192.168.11.20/gitea/ | head -1")
result["gitea working"] = "OK" if 'HTTP/1.1 200 OK' in gitea_http.stdout else "NG"
# test jenkins
jenkins_http = host.run("sudo curl -Is http://192.168.11.20/jenkins/login | head -1")
result["jenkins working"] = "OK" if 'HTTP/1.1 200 OK' in jenkins_http.stdout else "NG"
# test directory size
gitea_file = host.file("/root/Server/Docker/Gitea/gitea-data/")
result["gitea file size"] = "OK" if gitea_file.size < 4000000000 else "NG"
giteadb_file = host.file("/root/Server/Docker/GiteaDB/giteadb-data/")
result["giteadb file size"] = "OK" if giteadb_file.size < 2000000000 else "NG"
jenkins_file = host.file("/root/Server/Docker/Jenkins/jenkins_home/")
result["jenkins file size"] = "OK" if jenkins_file.size < 4000000000 else "NG"
except:
err = sys.exc_info()[0]
result["Unit test bail out!!"] = err
return result
if __name__ == '__main__':
app.config['TESTING'] = False
app.config['DEBUG'] = False
app.run(host='0.0.0.0', port=30000)
server.pyはウェブページの描画にlayout.htmlを参照します.layout.htmlはテスト結果をhtmlの表として出力します.
layout.htmlの内容は割愛します.
server.pyは以下のコマンドで起動ます.
sudo python3 server.py
起動したらウェブブラウザで<ラズパイのIP>:30000にアクセスしましょう.
テスト結果が表示されます.
テストはserver.pyのtest_all
に実装されています.test_all
は以下のテストを実行しています.
- WANへのアクセスのチェック
- sshサービスの稼働チェック
- ufwサービスの稼働チェック
- Giteaコンテナの稼働状態のチェック
- Jenkinsコンテナの稼働状態のチェック
- curlコマンドを用いたGiteaページへのアクセス
- curlコマンドを用いたJenkinsページへのアクセス
- Giteaのストレージの容量チェック
- Jenkinsのストレージの容量チェック
忘れていましたがserver.pyの実行にはcurlが必要です.以下のコマンドでcurlをインストールしましょう.
sudo apt-get install -y curl
起動・停止スクリプトの作成
以上でサーバ管理システムのセットアップが完了しました.
簡単にglancesとtestinfra+Flaskを起動・停止できるようにスクリプトを用意しましょう.
githubのレポジトリのstart_server.shは起動スクリプト、stop_server.shは停止スクリプトです.
起動スクリプトの内容は以下のとおりです.
#! /bin/bash
pushd $(dirname ${BASH_SOURCE}) 1> /dev/null
# launch glances
printf "<設定したglancesのパスワード>" | glances -w --username --password --port 20000 1> /dev/null 2> /dev/null & disown
# launch testinfra
pushd ./testinfra 1> /dev/null
python3 server.py 1> /dev/null 2> /dev/null & disown
popd 1> /dev/null
popd 1> /dev/null
停止スクリプトは以下のとおりです.
#! /bin/bash
pushd $(dirname ${BASH_SOURCE}) 1> /dev/null
# stop glances
pkill glances
# stop testinfra
pkill -f server.py
popd 1> /dev/null
以下のコマンドでスクリプトを実行すればサーバの起動と停止ができます.
# 実行権限の付与
chmod +x start_server.sh
chmod +x stop_server.sh
# 起動
./start_server.sh
#停止
./stop_server.sh
自動起動の設定
ラズパイの電源をオフすると迫撃砲(簡単開発支援サーバ)は停止してしまいます.
電源オンオフの度にstart_server.sh
を実行するのは非常にたるいです.
そこで起動時にstart_server.sh
が自動的に実行されるようにします.
起動にはsystemdを使います.
systemdはLinuxのシステム管理デーモンです.systemdを使えば起動時や定期的にプログラムを実行できます.
設定のために新たなサービスを作成します.
以下の内容のsds.serviceファイルを/etc/systemd/system/に配置します.
# place this files in /etc/systemd/system/
[Unit]
Description = Software Development Server
Requires=docker.service
[Service]
Type=oneshot
ExecStart=<start_server.shへのパス>
ExecStop=<stop_server.shへのパス>
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
以下のコマンドで追加されたsds.serviceファイルをsystemdに反映します.
sudo systemctl daemon-reload
以下のコマンドで起動時にsds.serviceが起動時に実行されるようにします.
sudo systemctl enable sds
これにより、ラズパイの起動時に自動的にソフトウェア開発支援サーバが起動されます.
Webアプリケーションシステムの立ち上げ
Webアプリケーションシステムを立ち上げます.
立ち上げにはDockerを使います.Dockerはコンテナと呼ばれる仮想環境上でOSとアプリをまるごと動作させることができます.
これにより、一台のラズベリーパイの中であたかも複数のサーバが動いているかのように動作させることができます.
docker-composeの設定
サーバを起動するときはdocker-composeを介してDockerを呼び出します.
docker-coposeはDockerの補助スクリプトです.docker-compose.ymlに記述された設定の通りに複数のサーバを起動・停止します.
なくても良いのですがDockerの複雑な引数を簡単に呼び出せるようにしてくれるので、docker-composeを使ったほうがコマンドの実行が非常に楽になります.
Docker/docker-compose.ymlの内容は以下のとおりです.
version: "3"
networks:
giteanet:
external: false
jenkinsnet:
external: false
services:
nginx:
image: nginx:alpine
depends_on:
- gitea
- jenkins
container_name: nginx
volumes:
- ./Nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- giteanet
- jenkinsnet
ports:
- 80:80
- 222:222
giteadb:
image: arm32v7/postgres
container_name: giteadb
environment:
POSTGRES_DB: giteadb
POSTGRES_USER: gitea
POSTGRES_PASSWORD: gitea
logging:
driver: "json-file"
options:
max-file: "2"
max-size: "3m"
restart: unless-stopped
networks:
- giteanet
volumes:
- ./GiteaDB/giteadb-data:/var/lib/postgresql/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
gitea:
image: kunde21/gitea-arm
container_name: gitea
environment:
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=giteadb:5432
- GITEA__database__NAME=giteadb
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=gitea
logging:
driver: "json-file"
options:
max-file: "2"
max-size: "3m"
restart: unless-stopped
depends_on:
- giteadb
networks:
- giteanet
links:
- "giteadb"
volumes:
- ./Gitea/gitea-data:/data/
- ./Gitea/app.ini:/data/gitea/conf/app.ini
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
jenkins:
build:
context: ./Jenkins
container_name: jenkins
environment:
- JENKINS_OPTS="--prefix=/jenkins"
logging:
driver: "json-file"
options:
max-file: "2"
max-size: "3m"
restart: unless-stopped
networks:
- jenkinsnet
volumes:
- ./Jenkins/jenkins_home:/var/jenkins_home
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
volumes:
giteadb-data:
driver: local
driver_opts:
type: 'none'
o: 'bind'
device: './giteadb-data/'
gitea-data:
driver: local
driver_opts:
type: 'none'
o: 'bind'
device: './gitea-data/'
jenkins_home:
driver: local
driver_opts:
type: 'none'
o: 'bind'
device: './jenkins_home/'
ラズパイのポート80とポート222をNginxコンテナのポート80と222にバインドします.
JenkinsとGiteaとGiteaDBサーバはNginxを介して外部と通信行います.
さらにJenkinsとGiteaは個別のDockerネットワークに配置されます.
これにより、
- GiteaとJenkinsで同じポートが使える
- セキュリティの向上
ができます.
Nginxの設定
ラズパイ内で複数のサーバを動かしたい場合、ポートやURLでサーバへのアクセスを切り替える必要があります.
そこで、Nginxを使います.Nginxはnginx.confに記述されたルールに基づいてサーバへのアクセスを適切なサーバに転送します.
以下がnginx.confの内容です.
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
upstream jenkins {
keepalive 32; # keepalive connections
server jenkins:8080; # jenkins ip and port
}
# Required for Jenkins websocket agents
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80; # Listen on port 80 for IPv4 requests
server_name 127.168.11.20; # replace 'jenkins.example.com' with your server domain name
client_max_body_size 100M;
# pass through headers from Jenkins that Nginx considers invalid
ignore_invalid_headers off;
location /gitea/ {
proxy_pass http://gitea:3000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ "^/static/[0-9a-fA-F]{8}\/(.*)$" {
# rewrite all static files into requests to the root
# E.g /static/12345678/css/something.css will become /css/something.css
rewrite "^/static/[0-9a-fA-F]{8}\/(.*)" /$1 last;
}
location /userContent {
# have nginx handle all the static requests to userContent folder
# note : This is the $JENKINS_HOME dir
root /var/lib/jenkins/;
if (!-f $request_filename){
# this file does not exist, might be a directory or a /**view** url
rewrite (.*) /$1 last;
break;
}
sendfile on;
}
location / {
sendfile off;
proxy_pass http://jenkins;
proxy_redirect default;
proxy_http_version 1.1;
# Required for Jenkins websocket agents
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_max_temp_file_size 0;
#this is the maximum upload size
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffering off;
proxy_request_buffering off; # Required for HTTP CLI commands
proxy_set_header Connection ""; # Clear for keepalive
}
}
}
stream {
upstream ssh {
server gitea:222;
}
server {
listen 222;
listen [::]:222;
proxy_pass ssh;
}
}
nginx.confで行っている設定は以下のとおりです.
- ポート222のアクセスをGiteaコンテナに転送
- /gitea/パスへのアクセスをGiteaコンテナに転送
- それ以外はJenkinsコンテナに転送
Giteaの設定
Docker内で複数のサーバを起動します.先述した通りDocker内のサーバへのアクセスをパスで振り分けます.
GiteaはNginxから転送されたURLに対応するためにサーバ設定を変更する必要があります.
以下が変更内容です.
; 省略
[server]
DOMAIN = <ラズパイのIP>
SSH_DOMAIN = <ラズパイのIP>
; 省略
ROOT_URL = http://<ラズパイのIP>/gitea/
; 省略
Jenkinsの設定
セキュリティのためJenkinsでビルドをしたい場合はビルド環境をJenkinsノードという別のサーバにセットアップします.
しかし、これ以上ラズパイで起動するサーバを増やすと設定が複雑になるだけでなくCPUが限界を迎えそうです.
そこで、JenkinsコンテナにC/C++のビルド環境をセットアップして、Jenkinsだけでビルドを実行できるようにします.
ビルド環境のセットアップにはDocker/Jenkins/Dockerfileに記述します.
FROM jenkins4eval/jenkins
USER root
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y build-essential cmake astyle
RUN apt-get install -y python3 python3-pip
RUN pip3 install virtualenv
USER jenkins
EXPOSE 8080
EXPOSE 50000
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/jenkins.sh"]
jenkins4evalイメージに
- python3
- pip
- build-essential(gccやmakeなど)
- cmake
- astyle(コード整形ツール)
を追加して実行できるようにしています.
Dockerの起動
設定したDockerの起動にはdocker-compose.ymlがあるディレクトリで以下のコマンドを実行します.
sudo docker-compose up
自動起動・停止できるようにこの処理をstart_server.shとstop_server.shに追記します.
追記したあとのstart_server.shとstop_server.shは以下のとおりです.
#! /bin/bash
pushd $(dirname ${BASH_SOURCE}) 1> /dev/null
# launch glances
printf "youkan" | glances -w --username --password --port 20000 1> /dev/null 2> /dev/null & disown
# launch testinfra
pushd ./testinfra 1> /dev/null
python3 server.py 1> /dev/null 2> /dev/null & disown
popd 1> /dev/null
# ↓ added for Docker↓
# launch docker servers
pushd ./Docker 1> /dev/null
# set up data directories if not exist
mkdir -p "./Gitea/gitea-data/"
mkdir -p "./GiteaDB/giteadb-data/"
mkdir -p "./Docker/Jenkins/jenkins_home/"
docker-compose up -d & disown
popd 1> /dev/null
# ↑ added for Docker↑
popd 1> /dev/null
#! /bin/bash
pushd $(dirname ${BASH_SOURCE}) 1> /dev/null
# stop glances
pkill glances
# stop testinfra
pkill -f server.py
# ↓ added for Docker↓
# stop docker server
pushd ./Docker 1> /dev/null
docker-compose down
popd 1> /dev/null
# ↑ added for Docker↑
popd 1> /dev/null
サーバの使い方
初回起動時はJenkinsのパスワードを入力する必要があります.
パスワードファイルはjenkinsコンテナの/var/jenkins_home/secrets/initialAdminPasswordファイルに記述されています.
以下のコマンドでファイルの中身を読み出しましょう
sudo docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
Giteaにアクセスしたい場合はウェブブラウザを使って以下のURLにアクセスします.
http://<ラズパイのIP>/gitea/
Jenkinsにアクセスしたいときはウェブブラウザを使って以下のアドレスにアクセスします.
http://<ラズパイのIP>/jenkins/
あとは通常のGiteaとJenkinsです.
サーバの保守
できればサーバの稼働状況をメールで通達するような仕組みが望ましいのです.しかし、そうなるとラズパイにメールサーバの設定をしなければなりません.メールサーバの設定を行うと迫撃砲(簡単開発支援サーバ)の魅力である「持っていって設置するだけで支援砲火を打てる」ができなくなります.そこで、稼働状況の通知は諦めてかわりに簡単に確認できるようにしました.
ウェブブラウザで指定されたポートにアクセスするだけでサーバの状況を把握できるようになっています.
サーバの稼働状況の表示
<サーバのIP>:20000
にアクセスするとglancesのページが表示されます.
このページから
- 実行中のプロセスの状態
- CPUとメモリの消費量
- ストレージの消費量
が表示されます.
単体テストの実行
<サーバのIP>:30000
にアクセスるとtestinfraを使った単体テストが実行されます.
テスト結果はウェブブラウザに表示されます.
問題があった場合、表にNGが表示されます.NGとなったテストが現れたときはserver.pyの内容と照らし合わせながら原因を追求します.
サーバの移行
今回構築した迫撃砲(簡単開発支援サーバ)は少人数のチームのためのものです.
チームの人数が増えてストレージ容量が足りなくなって来たらサーバの移行を検討しましょう.幸い単体テストにサーバのストレージ容量をチェックする項目があります.定期的に単体テストの結果をチェックすればストレージ容量不足の前兆を見逃しません.単体テストのストレージ消費量の項目はストレージの消費量が70%を超えたらNGになります.NGが出たら移行を検討すべきです.
glancesとtestinfraはPythonで実装されているので普通の(x86アーキテクチャのCPUを搭載する)サーバで普通に動きます.
問題はDockerで動作するJenkinsとGiteaです.これらはarmアーキテクチャのためのイメージを使っています.
無理矢理x86アーキテクチャのサーバを動かそうとするとCPUアーキテクチャが違うので動作しません.
JenkinsとGiteaのイメージを移行先のサーバのCPUアーキテクチャに合わせたイメージに差し替えましょう.
サーバのデータは以下のディレクトリに格納されています.
- Docker/Gitea/gitea-data/
- Docker/GiteaDB/giteadb-data/
- Docker/Jenkins/gitea-data/
移行先にそのままこれらのディレクトリの中身をコピーすれば移行完了です.
データのバックアップもこれらのディレクトリの中身をバックアップするだけで良いです.
注意点
「隊長!!敵(ネットワーク管理者)にわれわれの位置がバレたようです!!」
「よし.潮時だな.位置を変えるぞ!!」
(使用に際してはちゃんと許可をとってください.)
参考文献
- How to set a Raspberry Pi with a static ip address?
- SSH公開鍵認証で接続するまで
- How to disable ssh password login on Linux to increase security
- Github:nicolargo/glances
- Testinfra test your infrastructure
- freedesktop.org:systemd
- Gitea
- Nginx
- Jenkins
- Docker
- Draw.io
- Wikipedia:迫撃砲
補足
この記事は
の応募のために作成されました.
Qiitanぬいぐるみがほしい!!