Jaybanuan's Blog

どうせまた調べるハメになることをメモしていくブログ

Docker Composeをsystemdのサービスにする

はじめに

サーバの簡易な運用をする場合、Docker Composeを利用してコンテナを立ち上げると簡単に環境を構築できる。 さらにDocker Composeをsystemdのサービスにすることで、起動や停止などのライフサイクルも管理できる。

ここでは、コンテナを利用した簡易なサーバ運用のために、Docker Composeをsystemdのサービスにする方法をメモしておく。

環境

$ cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Ubuntu 18.04.4 LTS"

$ docker --version
Docker version 19.03.5, build 633a0ea838

$ docker-compose --version
docker-compose version 1.25.4, build 8d51620a

ユニットファイルの作成

基本的には、どのようなコンテナを利用する場合もdocker-compose updocker-compose downを実行するだけなので、ユニットファイルのテンプレート化が可能である。 ここでは、次に示すテンプレート化したユニットファイル/etc/systemd/system/docker-compose-service@.serviceを作成する。

[Unit]
Description=%i managed by docker-compose
Requires=docker.service

[Service]
Type=simple

Environment=COMPOSE_FILE=/opt/docker-compose-service/%i/docker-compose.yml

ExecStartPre=-/usr/local/bin/docker-compose -f ${COMPOSE_FILE} down --volumes
ExecStart=/usr/local/bin/docker-compose -f ${COMPOSE_FILE} up
ExecStop=/usr/local/bin/docker-compose -f ${COMPOSE_FILE} down --volumes 

[Install]
WantedBy=multi-user.target

ExecStartPredocker-compose downを実行しているのは、ExecStartの前に念のためゴミ掃除をしておくため。 また、読み込むComposeファイルを指定するために、このユニットファイルの%iを活用してパスを作成している。 例えば、jenkinsnginxredmineの3つのサービスを運用する場合は、次のようなファイル構成になる。

/opt
`-- docker-compose-service
    |-- jenkins
    |   `-- docker-compose.yml
    |-- nginx
    |   `-- docker-compose.yml
    `-- redmine
        `-- docker-compose.yml

Composeファイルの準備とサービスの登録

ここでは例として、ポート10080でHTTPを受け付けるNginxのサービスを登録する。 まずは、以下の内容でComposeファイル/opt/docker-compose-service/nginx/docker-compose.ymlを作成する。 これは、ポート番号10080でアクセス可能なNginxを実行するだけのComposeファイルである。

version: '3.7'
services:
  nginx:
    image: nginx:1.17
    ports:
      - 10080:80

サービスとして登録するには、以下の以下のコマンドを実行する。

$ sudo systemctl enable --now docker-compose-service@nginx

動作確認

まずはsystemdからステータスを確認してみる。

$ systemctl status docker-compose-service@nginx
● docker-compose-service@nginx.service - nginx managed by docker-compose
   Loaded: loaded (/etc/systemd/system/docker-compose-service@.service; indirect; vendor preset: enabled)
   Active: active (running) since Tue 2020-02-11 13:11:38 JST; 57min ago
  Process: 715 ExecStartPre=/usr/local/bin/docker-compose -f ${COMPOSE_FILE} down --volumes (code=exited, status=0/SUCCESS)
 Main PID: 2018 (docker-compose)
    Tasks: 4 (limit: 4637)
   CGroup: /system.slice/system-docker\x2dcompose\x2dservice.slice/docker-compose-service@nginx.service
           ├─2018 /usr/local/bin/docker-compose -f /opt/docker-compose-service/nginx/docker-compose.yml up
           └─2063 /usr/local/bin/docker-compose -f /opt/docker-compose-service/nginx/docker-compose.yml up

 2月 11 13:11:27 ubuntu1804 systemd[1]: Starting nginx managed by docker-compose...
 2月 11 13:11:37 ubuntu1804 docker-compose[715]: Removing nginx_nginx_1 ...
 2月 11 13:11:37 ubuntu1804 docker-compose[715]: [75B blob data]
 2月 11 13:11:38 ubuntu1804 systemd[1]: Started nginx managed by docker-compose.
 2月 11 13:11:39 ubuntu1804 docker-compose[2018]: Creating network "nginx_default" with the default driver
 2月 11 13:11:40 ubuntu1804 docker-compose[2018]: Creating nginx_nginx_1 ...

Nginxのコンテナがdocker-compose経由で正常に立ち上がっていることが分かる。 dockerコマンドからも確認してみる。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
b4f4c0a9b306        nginx:1.17          "nginx -g 'daemon of…"   57 minutes ago      Up 57 minutes       0.0.0.0:10080->80/tcp   nginx_nginx_1

dockerコマンドの結果からも、Ningxのコンテナが起動していることが確認できる。 では、curlでNginxにアクセスしてみる。

$ curl http://localhost:10080/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

(以下略)

正常にNginxにアクセスできたことが分かる。 最後に、サービスを停止してみる。

$ sudo systemctl stop docker-compose-service@nginx

systemdからステータスを確認してみる。

$ systemctl status docker-compose-service@nginx
● docker-compose-service@nginx.service - nginx managed by docker-compose
   Loaded: loaded (/etc/systemd/system/docker-compose-service@.service; indirect; vendor preset: enabled)
   Active: inactive (dead) since Tue 2020-02-11 14:16:05 JST; 23s ago
  Process: 14572 ExecStop=/usr/local/bin/docker-compose -f ${COMPOSE_FILE} down --volumes (code=exited, status=0/SUCCESS)
  Process: 2018 ExecStart=/usr/local/bin/docker-compose -f ${COMPOSE_FILE} up (code=exited, status=0/SUCCESS)
  Process: 715 ExecStartPre=/usr/local/bin/docker-compose -f ${COMPOSE_FILE} down --volumes (code=exited, status=0/SUCCESS)
 Main PID: 2018 (code=exited, status=0/SUCCESS)

 2月 11 13:11:40 ubuntu1804 docker-compose[2018]: Creating nginx_nginx_1 ...
 2月 11 14:07:24 ubuntu1804 docker-compose[2018]: [71B blob data]
 2月 11 14:07:24 ubuntu1804 docker-compose[2018]: nginx_1  | 172.18.0.1 - - [11/Feb/2020:05:07:24 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
 2月 11 14:16:02 ubuntu1804 systemd[1]: Stopping nginx managed by docker-compose...
 2月 11 14:16:03 ubuntu1804 docker-compose[14572]: Stopping nginx_nginx_1 ...
 2月 11 14:16:05 ubuntu1804 docker-compose[2018]: nginx_nginx_1 exited with code 0
 2月 11 14:16:05 ubuntu1804 docker-compose[14572]: [71B blob data]
 2月 11 14:16:05 ubuntu1804 docker-compose[14572]: [75B blob data]
 2月 11 14:16:05 ubuntu1804 docker-compose[2018]: 
 2月 11 14:16:05 ubuntu1804 systemd[1]: Stopped nginx managed by docker-compose.

正常にサービスが停止したことが分かる。 dockerコマンドからも確認してみる。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

dockerコマンドの結果より、起動しているコンテナがないことが分かる。 また、systemdの停止のコマンドとしてdocker-compose downを利用しているので、ゴミも後始末されていることが確認できる。

参考

Docker ComposeのComposeファイルのバージョンについて

はじめに

Docker ComposeのComposeファイル(docker-compose.yml)の冒頭でversion: '3'のようにバージョン表記があるが、バージョン番号に何を書くのが正しいかを調べてみた。

結論

Composeファイル中のversionで指定するのは、Composeファイルのフォーマットのバージョンである。 公式ドキュメントとGitHubのreleaseの情報をまとめると、各バージョンの対応表は以下の通り。

Compose file format Docker Engine
3.7 (Compose 1.22.0+) 18.06.0+
3.6 (Compose 1.20.0+) 18.02.0+
3.5 (Compose 1.18.0+) 17.12.0+
3.4 (Compose 1.17.0+) 17.09.0+
3.3 (Compose 1.14.0+) 17.06.0+
3.2 (Compose 1.12.0+) 17.04.0+
3.1 (Compose 1.11.0+) 1.13.1+
3.0 (Compose 1.10.0+) 1.13.0+
2.4 (Compose 1.21.0+) 17.12.0+
2.3 (Compose 1.16.0+) 17.06.0+
2.2 (Compose 1.13.0+) 1.13.0+
2.1 (Compose 1.9.0+) 1.12.0+
2.0 (Compose 1.6.0+) 1.10.0+
1.0 1.9.1.+

フォーマットのバージョン2.xと3.xは並行してアップデートされているようだ。 それぞれのバージョンのフォーマットのドキュメントは、以下にある。

Compose file format ドキュメントのURL
3.x https://docs.docker.com/compose/compose-file/
2.x https://docs.docker.com/compose/compose-file/compose-file-v2/
1.0 https://docs.docker.com/compose/compose-file/compose-file-v1/

Composeファイルのバージョンの指定方法について

ドキュメントには以下のようにある。

Note: When specifying the Compose file version to use, make sure to specify both the major and minor numbers. If no minor version is given, 0 is used by default and not the latest minor version.

日本語に訳すと以下のようになる。

注: 利用時にComposeファイルのバージョンを指定する場合は、メジャーとマイナーの番号を確実に指定してください。もしマイナーバージョンの指定がない場合は、最新のマイナーバージョンではなく、0が利用されます。

なので、明確にversion: '3.7'のように書くべきで、version: '3'という指定方法は避けたほうが良い。 公式ドキュメント中のサンプルのdocker-compose.ymlには、後者の表記が用いられていることがあるので、サンプルを流用する場合は注意が必要。

参考

systemdにおける/etc/sysconfigと/etc/default

はじめに

systemdのユニットファイルを調べた際に、/etc/sysconfig/etc/defaultについても調べたので、メモを残しておく。

/etc/sysconfigと/etc/default

ディレクトリとも、SysV initのスクリプトの設定ファイルをおいておく場所。 /etc/sysconfigRedHat(Fedora)系で利用されており、/etc/defaultDebian系で利用されている。

systemdの視点から

systemdの開発者のLennart Poetteringさんの以下のブログから掻い摘むと、「これらのディレクトリはSysV init用のレガシーなもので使うべきじゃないし、systemdのドロップインの仕組みを使えば事足りるよね」と言っているようだ。

「ドロップイン」とは、/etc/systemd/system/[サービス名].service.dというディレクトリの中に.confファイルを入れておけば、自動的に読み込んでくれる仕組みのこと。

参考

systemdのユニットファイルをテンプレート化する

はじめに

複数のサービスを自作してsystemdで管理するときに、作成するユニットファイルの中身が大体同じということがある。 こういう場合はユニットファイルのテンプレート化が有効なので、そのやり方をメモしておく。 動作確認は簡単にしたいので、ここではWebサーバのポート番号を変数化したユニットファイルを作成することにした。

環境

$ cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Ubuntu 18.04.4 LTS"

$ python3 --version
Python 3.6.9

事前準備

いろいろWebサーバを探してみたものの、ポート番号を引数に渡して起動できる手頃なWebサーバが見当たらなかったので、自前で作成することにした。 まずは、以下の内容で/opt/test-web-server/test-web-server.pyを作成する。

#!/usr/bin/env python3

from sys import argv
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer

class ServerAddressHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()

        text = 'server_address = ' + str(self.server.server_address) + '\r\n'
        self.wfile.write(text.encode('utf-8'))


if __name__ == '__main__':
    port = int(argv[1])

    httpd = HTTPServer(('0.0.0.0', port), ServerAddressHandler)
    httpd.serve_forever()

このファイルに実行権限をつけておく。

$ chmod a+x /opt/test-web-server/test-web-server.py

動作確認のために、待受ポートの10080を引数に指定して、このWebサーバを起動する。

$ /opt/test-web-server/test-web-server.py 10080

別のターミナルからcurlでWebサーバにアクセスすると、待ち受けているIPアドレスとポート番号を取得できる。

$ curl http://localhost:10080
server_address = ('0.0.0.0', 10080)

これで事前準備は完了。 起動したWebサーバを停止しておく。

ユニットファイルの作成と、ひとつめのサービスの登録

次の内容でファイル/etc/systemd/system/test-web-server@.serviceを作成する。

[Unit]
Description=Test Web Server with port %i

[Service]
Type=simple
ExecStart=/opt/test-web-server/test-web-server.py %i

[Install]
WantedBy=multi-user.target

具体化されたサービスの作成方法は、コマンドラインを見た方が分かりやすい。 例えば、ポート番号10080で待ち受けるサービスを登録するには以下のようにする。

$ sudo systemctl enable --now test-web-server@10080
Created symlink /etc/systemd/system/multi-user.target.wants/test-web-server@10080.service → /etc/systemd/system/test-web-server@.service.

つまり、ユニットファイルのファイル名、すなわちサービス名の@以降が変数%iに割り当てられる。 ここで登録したサービスの状態を確認するには、同様にサービス名の@以降に10080を指定してsystemctl statusを実行すればよい。

$ systemctl status test-web-server@10080
● test-web-server@10080.service - Test Web Server with port 10080
   Loaded: loaded (/etc/systemd/system/test-web-server@.service; indirect; vendor preset: enabled)
   Active: active (running) since Sat 2020-02-08 17:29:13 JST; 5min ago
 Main PID: 4632 (python3)
    Tasks: 1 (limit: 4637)
   CGroup: /system.slice/system-test\x2dweb\x2dserver.slice/test-web-server@10080.service
           └─4632 python3 /opt/test-web-server/test-web-server.py 10080

 2月 08 17:29:13 ubuntu1804 systemd[1]: Started Test Web Server with port 10080.

想定通り%i10080に置き換わっていることが分かる。 では、curlを実行してWebサーバの動作確認をしてみる。

$ curl http://localhost:10080
server_address = ('0.0.0.0', 10080)

事前準備の時と同様に、待ち受けているIPアドレスとポート番号が正しく表示された。

ふたつめのサービスの登録

次はポート番号20080で待ち受ける、ふたつめのサービスを登録してみる。

$ sudo systemctl enable --now test-web-server@20080
Created symlink /etc/systemd/system/multi-user.target.wants/test-web-server@20080.service → /etc/systemd/system/test-web-server@.service.

確認のため、名前がtest-web-server@から始まるサービスをリストする。

$ systemctl list-units -t service 'test-web-server@*'
UNIT                          LOAD   ACTIVE SUB     DESCRIPTION                    
test-web-server@10080.service loaded active running Test Web Server with port 10080
test-web-server@20080.service loaded active running Test Web Server with port 20080

(以下略)

想定通り、ポート番号が10080のサービスと20080のサービスが表示されている。 では、curlを実行してWebサーバの動作確認をしてみる。

$ curl http://localhost:10080
server_address = ('0.0.0.0', 10080)

$ curl http://localhost:20080
server_address = ('0.0.0.0', 20080)

ポート番号が10080のWebサーバも、20080のWebサーバも正しく動作していることが分かる。

参考

OSSの運用とライセンスについての情報源

はじめに

OSS運用のスタッフ的視点での情報減を少しずつまとめていく。 あとは、ライセンスやリーガルなことも。

とりあえずこれ

IPAが提供している情報

License

Contributor Agreements

wmctrlを利用したウィンドウのリサイズ

はじめに

とあるドキュメントの作成の都合上、Linuxでウィンドウのスクリーンショットを取る必要に迫られた。 ドキュメントのレイアウトの崩れを防ぐために、ウィンドウのサイズは統一しておきたかった。 そのためウィンドウを思い通りにリサイズできるコマンドを探してみた。 どうもwmctrlを使えばよいらしい。

使い方

まずウィンドウのリストを表示する。

$ wmctrl -l
0x0140000a  0 devpc デスクトップ
0x02800001  0 devpc Google - Google Chrome
0x02200003  0 devpc Mozilla Firefox
0x03a00006  0 devpc redj@devpc: ~
0x03c00001  0 devpc Untitled-1 - Visual Studio Code

表示される行のフォーマットは以下。

[ウィンドウID] [デスクトップ番号] [Xクライアントのホスト名] [ウィンドウタイトル]

例えば、座標(100, 100)を起点に、幅1280、高さ800でリサイズする場合は以下のようにする。 ここでは、対象のウィンドウはウィンドウタイトルで指定する。オプション-eのフォーマットはgravity,起点x,起点y,幅,高さで、gravityには通常は0を指定しておく。

wmctrl -r "Google - Google Chrome" -e 0,100,100,1280,800

また、ウィンドウIDを指定してリサイズする場合は、上記のコマンドラインのオプションに-iを加えて、以下のようにする。

wmctrl -i -r 0x02800001 -e 0,100,100,1280,800

参考

static int window_move_resize (Display *disp, Window win, char *arg) {
    // (略)

    if (wm_supports(disp, "_NET_MOVERESIZE_WINDOW")){
        return client_msg(disp, win, "_NET_MOVERESIZE_WINDOW", 
            grflags, (unsigned long)x, (unsigned long)y, (unsigned long)w, (unsigned long)h);
    }

    // 略

Jinja2のhello, world!

はじめに

JInja2のドキュメントの最初のサンプルコードが分かりにくくて、しばらくぶりだと色々調べ直しになる。。。 なので、最小限のコードを備忘録として残しておく。

環境

Ubuntu 18.04LTS

Jinja2のインストール

# pip3 install jinja2

hello, world!

以下の内容でgreeting.pyを作成する。

import jinja2

# create Jinja2 Environment
env = jinja2.Environment(loader = jinja2.FileSystemLoader('./'))

# load template
template = env.get_template('template.txt.j2')

# rendering
context = { 'name': 'world' }
result = template.render(context)

# print "hello, world!"
print(result)

以下の内容でtemplate.txt.j2を作成する。

hello, {{ name }}!

以下のように実行する。

$ python3 greeting.py 
hello, world!

参考