Go な Web Server を Systemd で良い感じに動かすメモ
ざっと設定するタイミングがあったのでメモがわりに残しておく。
環境は以下の通り。
lib | version |
---|---|
Ubuntu | 22.04 |
Go | 1.19 |
systemd | 249 |
Go で Web Server 書く
動作確認するだけのコードなのでなるだけシンプルに書く。んで、 Port 80 を Listen したいけどプロセスは root で走らせたくないので、 systemd でポート開いてそれをプログラム側で使うようにする。
ディスクリプタごにょる部分は coreos/go-systemd がドンピシャなのでこれを使う。
package main
import (
"errors"
"fmt"
"log"
"net"
"net/http"
"github.com/coreos/go-systemd/activation"
)
func main() {
// Mux を設定する
m := http.NewServeMux()
m.HandleFunc("/", handler)
// Server を設定する
s := &http.Server{
Handler: m,
}
// systemd で開いたディスクリプタを取得する
l, err := newListener()
if err != nil {
log.Fatal(err)
}
//
log.Println("starting server.")
log.Fatal(s.Serve(l))
}
// リクエストが来るたびログに書き込む
func handler(w http.ResponseWriter, r *http.Request) {
// stderr に書き込む
log.Printf("stderr: method=%v url=%v", r.Method, r.URL)
// stdout に書き込む
fmt.Printf("stdout: method=%v url=%v\n", r.Method, r.URL)
//
fmt.Fprintln(w, "ok")
}
// Systemd からソケットをひとつも受け取れなかった場合のエラー
var ErrNoListenerFound = errors.New("can't find any listeners.")
// Systemd からソケットを受け取る。
// 複数受け取った場合は先頭のものを返す。
func newListener() (net.Listener, error) {
ls, err := activation.Listeners()
if err != nil {
return nil, err
}
// ひとつも受け取れなかった場合でもエラーにならないため
// len で別途確認する必要がある
if len(ls) == 0 {
return nil, ErrNoListenerFound
}
return ls[0], nil
}
これをビルドして適当な場所に置く。今回は /usr/local/bin/test-server
に置くことにしてみた。
$ go mod init example.com
$ go mod tidy
$ go build -o test-server
$ sudo mv ./test-server /usr/local/bin/
systemd の Unit 書く
今回は .socket
と .service
のふたつの Unit ファイルを /etc/systemd/system
に配置する。
それぞれ test-server.socket
と test-server.service
という名前で作成する。今後 systemctl で操作するときにこの名前で行うので分かりやすに名前にしておくほうが良い。
# test-server.socket
[Unit]
Description=Socket for test server.
[Socket]
ListenStream=0.0.0.0:80
NoDelay=true
[Install]
WantedBy=sockets.target
# test-server.service
[Unit]
Description=test server.
After=network.target
Requires=test-server.socket
[Service]
ExecStart=/usr/local/bin/test-server
User=www-data
Group=www-data
Restart=on-failure
[Install]
WantedBy=multi-user.target
いろいろ書いてあるけど、そこまで意味不明な項目はないし分からなくてもググれば速攻で答え出てくるから問題ないと思う。
.service
側で User
と Group
を指定することで root 以外で走らせることができる。が、その状態で Go 側で Port 80 を Listen しようとすると Permission denied で怒られてしまうのでそこを .socket
で担ってる。
ファイルが配置できたら起動・停止をテストしてみる。
$ sudo systemctl start test-server
$ ps aux | grep test-server
www-data 2081 0.0 0.2 1085020 4692 ? Ssl 11:35 0:00 /usr/local/bin/test-server
ちゃんと起動していたら停止も。
$ sudo systemctl stop test-server
Warning: Stopping test-server.service, but it can still be activated by:
test-server.socket
なんか Warning でた。今止めたのは test-server.service
だけで、 test-server.socket
はアクティブなままらしい。試しにこの状態でブラウザからアクセスすると、ちゃんとページは表示されるしプロセスも復活してくる。なので、止めるときは .socket
のほうをちゃんと指定する。
$ sudo systemctl stop test-server.socket
エラーなくプロセスが止まっていれば ok 。
サーバ起動時に自動起動してほしければ enable して登録しておくと良い。
$ sudo systemctl enable test-server.socket test-server.service
ログを吐く
現状だとログまわりを何も設定していないので journald に記録されている。試しに Unit でフィルタリングしてみると内容が確認できるはず。
$ journalctl -u test-server
...
そして Ubuntu 22.04 時点だとデフォルトの設定では journald に送られた内容は rsyslog にも送られるようになっている。 /var/log/syslog
を確認してみると、他から送られてきたメッセージの中に test-server からのものが確認できると思う。
見つからない場合は journald の設定が変更されている可能性があるので /etc/systemd/journald.conf
を確認してみると良いかも。
このままだといろんな情報がごちゃまぜになってて追いかけづらいので、必要なものだけフィルタリングして個別のファイルに書き出してみる。 /etc/rsyslog.d/
以下に設定ファイルを追加すると起動時に読み込まれるようになっているので、ファイル名に適当な優先順位を加えて追加する。
# /etc/rsyslog.d/40-test-server.conf
:programname, isequal, "test-server" /var/log/test-server.log
& stop
今回は単純に test-server.service
から送られてきたもの全てを /var/log/test-server.log
に書き出すようにした。プラス & stop
をつけることでフィルタリングしたものがココで止まるようにしている。
ファイルが保存できたら rsyslog を再起動する。
$ sudo systemctl restart rsyslog
エラーなく再起動できたら、ブラウザで一度アクセスしてみたあとに /var/log/
を確認してみると test-server.log
が作られて吐き出された内容が書かれているはず。
ログをローテートさせる
ここまで出来たらあとは logrotate 使ってログをぐりぐりさせるだけ。
/etc/logrotate.d/
に test-server
という名前で適当な設定を書く。
# for test-server
/var/log/test-server.log
{
rotate 7
daily
nocreate
missingok
notifempty
compress
postrotate
systemctl restart test-server
endscript
}
rotate
の数や daily
or weekly
なのかは実際のアクセス数やリソースに合わせて変えてもらえればと思う。
念の為、テストで走らせてみる。
sudo logrotate /etc/logrotate.conf --debug
test-server
まわりでエラーがなければ ok 。あとは数日開けて確認してみると良いと思う。
おわりに
やってることシンプルだけど文字に起こすと長い。。。
次からこーゆーのはスクラップにしたほうが相性が良い気がした。
Discussion
.socket 使うとroot 以外のPermission denied 回避出来るのですね
スクラップだと気付けなかったと思うので記事に書いてもらえて良かったです😆
そう言っていただけると書いた甲斐がありました。ありがとうございます。
これからもなるだけ記事で書いていこうと思います :-)