systemd 概要

これはなに

systemdってなに?と思って調べたことのまとめ。

systemdとは

UNIX系OSにおいて、自身を含む全てのバックグラウンドプロセス(サービス)のデーモン(メモリ上で待機している常駐プログラム)を管理するシステム管理デーモン。

OS起動直後にカーネルにより実行されるinitプロセス(PID 1)に当たる。
全てのプロセスの元となるプロセスになる。

$ pstree # プロセスをツリー状に表示するコマンド
systemd─┬─VBoxService───8*[{VBoxService}] 
        ├─accounts-daemon───2*[{accounts-daemon}]
        ├─agetty
        ├─atd
        ├─cron
        ...
        ├─systemd-network
        ├─systemd-resolve
        ├─systemd-udevd
        └─uuidd

systemdが目指したもの

  • システム起動時間を短縮
    • 必要なサービス群の起動の並列実行によりinitプロセスを時短できる
  • システム構成の動的変更に対応
    • システム起動時だけでなく、変更に応じて動的にサービス起動/停止を行う
  • プロセス停止処理を標準機能として提供
  • デーモンの実行環境を制御
    • サービスごとにデーモンの実行環境(リソース割り当てやアクセス可能なディレクトリ)やログ出力を管理

管理方法 - Unit

管理単位

処理はUnitという単位で管理される。

Unit file の構成

Unitfileは3つのセクションから構成される。

Section description
Unit Unitの説明や依存/順序関係など、基本となる設定を記述
Service 起動,停止コマンドや環境変数ファイル、起動判定方法や再起動条件、起動するユーザや指定ディレクトリのアクセス制御など、そのデーモン固有の設定を記述
Install systemctl enable/disable(後述)時の挙動を記述

Unit間の依存/順序関係

  • 依存関係
    • AというUnitを有効化するなら、BというUnitも有効化すべき、という関係
    • Unitセクションにおいて、Wants=Requires=,Conflict=で定義する。
    • <UNIT>.wants,<UNIT>.requiresディレクトリに依存関係のあるUnitファイルへのシンボリンクリンクを張ることでも定義できる。
  • 順序関係
    • AというUnitを有効化する前に、BというUnitを有効化すべき、という関係
    • Unitセクションにおいて、After=Before=で定義する。

Unitの種類

Unitファイルのファイル名末尾で見分ける。

種類 作成方法 description
.service 明示的に定義 有効化すると対応するデーモンが起動する。
.target 明示的に定義 何もしない。Unit間の依存/順序関係を定義する時、複数のUnitをグループ化するのに使用する。
.mount /ect/fstabから自動作成 有効化するとマウントされる。
.swap /ect/fstabから自動作成 有効化するとSwap領域が有効になる。
.device udevから自動作成 udevがデバイスを認識すると有効化される。
.socket 明示的に定義 systemdが特定のソケットをListenし、接続があると指定のデーモンを起動してソケットを受け渡す。

管理場所

  • /etc/systemd/system
    • こちらが優先実行される。サーバー管理者がカスタマイズできるところ。
  • /usr/bin/lib/systemd/system
    • デフォルト設定。基本は触らない。触りたい時は/etc/systemd/systemに同名でコピーして触る。

主要コマンド

$ systemctl list-unit-files # Unitの一覧表示

$ systemctl list-units # 有効(であるべき)Unitとその状態の一覧表示

$ systemctl enable/disable <UNIT> # Unitの自動起動,無効化

$ systemctl start/stop/reload <UNIT> # Unitの手動起動,停止,再起動(Unit fileにreload動作が設定されている場合のみ)

$ systemctl status <UNIT> # Unitの実行状態の表示(関連するデーモンプロセスや直近のログなど)

$ systemctl deamon-reload # Unit fileを変更した際にsystemdに認識させる

参考

ISUCON9予選問題をVagrantfileからVirtualBox上で動かす

概要

ISUCON9 予選問題の環境を、手元のMacOSで再現します。
ローカル環境でISUCON9予選の問題を動かす : ISUCON公式Blogにあるように、アプリケーション自体はホストOSで動かすことができますが、今回はより本番に近い環境を再現するため、matsuuさんが提供してくれているVagrantfileを使用し、VirtualBoxで構築した仮想OS上に構築します。

環境

準備

VirtualBoxVagrantをインストール

VirtualBoxとは

Oracleが提供する、クロスプラットフォームの仮想化アプリケーション。
WindowsMacOS,Linuxなどにインストールすると、VirtualBoxを通して別OSを動かすことができる。

Oracle VM VirtualBox is a cross-platform virtualization application.
ref. Oracle® VM VirtualBox®

Vagrantとは

HashiCorpが提供する、仮想マシンの環境構築ツール。
VirtualBoxVMwareなど、Vagrantプラグインをサポートしている仮想化ソフトウェア上で動作する。

Vagrant is a tool for building and managing virtual machine environments in a single workflow.
ref. Introduction - Vagrant by HashiCorp

install

Homebrewからインストール。

$ brew cask install virtualbox
$ VBoxManage -v
6.1.4r136177

$ brew cask install vagrant
$ vagrant -v
Vagrant 2.2.7

公式ページから直接ダウンロードしてもOK。

ISCON9 予選問題環境構築のためのVagrantfileを取得する

Vagrantfileとは

仮想マシンの環境構築に必要なマシンタイプと、そのマシンの設定及びプロビジョニング (準備)方法を記載したファイル。Vagrantがサポートする全てのプラットフォーム間で移植可能。
通常、プロジェクトに必要な各種ソースコードと一緒にVCS(Gitなど)にコミットされる。

The primary function of the Vagrantfile is to describe the type of machine required for a project, and how to configure and provision these machines.
ref. Vagrantfile - Vagrant by HashiCorp

取得

githubから取得する。

$ ghq get git@github.com:matsuu/vagrant-isucon.git
# 普通に
# git clone git@github.com:matsuu/vagrant-isucon.git
# などでもOK

起動

環境構築

Vagrantfileの提供元READMEに書いてある通りに起動してみる。

$ cd ./isucon9-qualifier-standalone
$ vagrant up #up: starts and provisions the vagrant environment

初回はbento/ubuntu-18.04のダウンロードを挟むので時間がかかる。

ダウンロード終了後にエラーで起動が失敗してしまった時の奮闘記はこちら。
moeka.hatenablog.jp

ベンチマーク起動

$ vagrant ssh
vagrant@vagrant$ sudo -i -u isucon
isucon@vagrant$ cd isucari
isucon@vagrant$ bin/benchmarker

この状態でのスコアは以下。

{"pass":true,"score":2310,"campaign":0,"language":"Go","messages":["GET /items/47204.json: リクエストに失敗しました(タイムアウトしました)"]}

思ってたよりちゃんと出た。

ホストからアプリケーションにアクセス

ホストオンリーネットワークをDHCPで構築しているため、ゲストOS側に割り当てられているIPアドレスを調べる。

isucon@vagrant$ ifconfig
# ...
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.28.128.3  netmask 255.255.255.0  broadcast 172.28.128.255
        inet6 fe80::a00:27ff:fe3f:84dc  prefixlen 64  scopeid 0x20<link>
        ether 08:00:27:3f:84:dc  txqueuelen 1000  (Ethernet)
        RX packets 21  bytes 5788 (5.7 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 30  bytes 4428 (4.4 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
# ...

以下のURLでホストOSのブラウザからアクセス
https://172.28.128.3

無事画面が表示できたので、接続成功。

後記

次は計測ツールの導入などしていきたいです。 ただ、提供されているvagrantfileを使うとansibleやソースコードgithubからの入手になってしまっていて手軽にコード修正&反映ができないので、ローカルのものを見るように改造するところからかな。

VagrantでのDHCPを有効にしたhost only networkの作成に失敗する

これはなに

表題の件でvagrant upに失敗したので、原因究明の軌跡と解決策をメモしておく。

環境

問題

あるvagrantfileがあるディレクトリ下でvagrant upを実行したところ、下記のエラーがおきた。

$ vagrant up
# 前略
==> default: Clearing any previously set network interfaces...
A host only network interface you're attempting to configure via DHCP
already has a conflicting host only adapter with DHCP enabled. The
DHCP on this adapter is incompatible with the DHCP settings. Two
host only network interfaces are not allowed to overlap, and each
host only network interface can have only one DHCP server. Please
reconfigure your host only network or remove the virtual machine
using the other host only network.

DHCPを有効にして設定しようとしているホストオンリーネットワークインターフェースには、すでにDHCPが有効なホストオンリーアダプターが作成されており、競合しています。
このアダプターが接続しているDHCPは、DHCP設定との互換性がありません。 ホストオンリーネットワークインターフェースは重複できません。また、それぞれのホストオンリーネットワークインターフェースは1つのDHCPサーバーとしか接続できません。 ホストオンリーネットワークを再構成するか、他のホストオンリーネットワークを使用している仮想マシンを削除してください。

原因究明

ref. dhcp private_network fails on virtualbox · Issue #3083 · hashicorp/vagrant
こちらのIssueで原因の解説がしてあった。
以下は上記PRの解説を、補足を交えながら日本語訳したものである。

Vagrantfileでの設定

vagrantfileを覗いてみると、ホストオンリーネットワークをDHCPを有効にして設定しようとしている。

 # Create a private network, which allows host-only access to the machine
 # using a specific IP.
 # config.vm.network "private_network", ip: "192.168.33.10"
 config.vm.network "private_network", type: "dhcp"

VirtualBoxのネットワーク設定

VirtualBoxには組み込みDHCPサーバーを持っており、デフォルトで以下の設定がされている。

$ VBoxManage list dhcpservers
NetworkName:    HostInterfaceNetworking-vboxnet0
Dhcpd IP:       192.168.56.100
LowerIPAddress: 192.168.56.101
UpperIPAddress: 192.168.56.254
NetworkMask:    255.255.255.0
Enabled:        Yes
Global Configuration:
    minLeaseTime:     default
    defaultLeaseTime: default
    maxLeaseTime:     default
    Forced options:   None
    Suppressed opts.: None
        1/legacy: 255.255.255.0
Groups:               None
Individual Configs:   None

$ VBoxManage list hostonlyifs
# empty

この状態でvagrant upをした際、type: "dhcp"のprivate_networkに遭遇すると、ホストオンリーネットワークが作成され、インターフェースに以下が設定される。

$ VBoxManage list hostonlyifs
Name:            vboxnet0
GUID:            786f6276-656e-4074-8000-0a0027000000
DHCP:            Disabled
IPAddress:       172.28.128.1
NetworkMask:     255.255.255.0
IPV6Address:
IPV6NetworkMaskPrefixLength: 0
HardwareAddress: 0a:00:27:00:00:00
MediumType:      Ethernet
Wireless:        No
Status:          Up
VBoxNetworkName: HostInterfaceNetworking-vboxnet0

ホストオンリーネットワークとDHCPサーバーは同じネットワークHostInterfaceNetworking-vboxnet0にあるにもかかわらず、ホストオンリーアダプタのIPは172.28.128.1、対してDHCPサーバーのIPは192.168.56.100と、別のネットワークのIPが設定されてしまっている。
ここで、アダプターのネットワーク外のアドレスをDHCPサーバーには割り当てられないぞ!と怒られているらしい。

解決策

デフォルトで設定されてしまっているdhcpserverの設定を削除し、そのままvagrant upを再試行したところ、仮想マシンが無事に起動した。

 $ VBoxManage dhcpserver remove --netname HostInterfaceNetworking-vboxnet0
 $ VBoxManage list dhcpservers
 # empty
 
 $ vagrant up
 # success!

念のため再度DHCPサーバーの設定をみると、

$ VBoxManage list dhcpservers
NetworkName:    HostInterfaceNetworking-vboxnet0
Dhcpd IP:       172.28.128.2
LowerIPAddress: 172.28.128.3
UpperIPAddress: 172.28.128.254
NetworkMask:    255.255.255.0
Enabled:        Yes
Global Configuration:
    minLeaseTime:     default
    defaultLeaseTime: default
    maxLeaseTime:     default
    Forced options:   None
    Suppressed opts.: None
        1/legacy: 255.255.255.0
Groups:               None
Individual Configs:   None

今度はちゃんとホストオンリーネットワークと同じネットワーク内のIPが設定されているのがわかる。

後記

VirtualBoxの初期設定とVagrantのホストオンリーネットワーク作成の仕様が若干マッチしないことによるバグのようでした。
一度対応すればおそらく二度と起こらない問題な気はしますが、バグの直接原因とエラーメッセージの内容も若干齟齬っているのでめちゃくちゃハマりポイントですね…
こういうIssueの作成者には頭が下がるばかりです。

ISUCON9 予選問題用のMySQLをDockerで立てる

これはなに

isucon.net ISUCON9予選問題の環境構築をするために上記の公式記事を参考にぽちぽちやっていた最中、 mysqlサーバーをホストに立てるのが環境を汚しそうで嫌だったので、Dockerで立ててみることにしました。

職場でdocker-compose upくらいはするものの、ちゃんと自分でDockerで何かを構築するのは初めてだったので いろいろ調べながらとりあえず課題のWebアプリが普通に動く最低ラインを目指してやったことの作業ログです。

環境

MacOS Catalina MacBook Pro 2017 3.1GHz デュアルコアIntel Corei5 メモリ 16GB

手順

CLIから起動

docker runで、imageからcontainerを起動する。
imageはdocker hubからダウンロードしてくる。

$ docker run --name cli-mysql -e MYSQL_ROOT_PASSWORD=pass -d mysql:latest

ref. mysql - Docker Hub

Starting a MySQL instance is simple: bash $ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag ... where some-mysql is the name you want to assign to your container, my-secret-pw is the password to be set for the MySQL root user and tag is the tag specifying the MySQL version you want. See the list above for relevant tags.

$ docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                 NAMES
9e3b15e64c77        mysql:latest        "docker-entrypoint.s…"   5 seconds ago       Up 4 seconds        3306/tcp, 33060/tcp   cli-mysql

起動はしており、3306と33060でmysqldの待ち受けはしているが、portのバインドができていないのでローカルからの接続はできない。

docker run --name cli-mysql -e MYSQL_ROOT_PASSWORD=pass -p 3306:3306 -d mysql:latest

--publish , -p : Publish a container’s port(s) to the host

ref. docker run | Docker docs

$ docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                 NAMES
9e3b15e64c77        mysql:latest        "docker-entrypoint.s…"   5 seconds ago       Up 4 seconds        0.0.0.0:3306->3306/tcp, 33060/tcp   cli-mysql

公開されたようだ。接続してみる。

$ mysql --port=3306 --host=127.0.0.1 --user=root -p

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.19 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

OK!

DockerfileからImageを作って起動

docker build PATH | URLでimageを自作する。 PATH(a directory on your local filesystem)やURL(Git repository location)はcontextと呼ばれる。
docker buildは、Docker deamonにcontext内のファイル(.dockerignoreで指定されたファイルは除く)を送信し、contextルートに配置されたDockerfileの手順にしたがって、Docker deamon内でimageをbuildする。(そのため、buildに必要のないファイルはきちんと.dockerignoreで指定しておいた方がbuildの速度は早くなる)

Docker deamonは、Dockerfileに記載された命令文を1つずつ独立した状態で実行しながら新しいimageを都度作っていき、最終的に得られたimageを出力する。 DockerfileのCOPY命令などでcontext内のファイルを参照しつつbuildを行う。

FROM mysql:latest
ENV MYSQL_ROOT_PASSWORD=pass
$ docker build .

Successfully built 855977a52a9d

$ docker images

REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
<none>                         <none>              855977a52a9d        7 seconds ago       547MB

$ docker run --name file-mysql -p 3306:3306 -d 855977a52a9d # -eオプションの指定が消えた
$ mysql --port=3306 --host=127.0.0.1 --user=root -p

imageのREPOSITORY, TAGの指定は docker build -t ${REPOSITORY}:${TAG}で行う。Dorckerfileに記述するものではない。

$ docker build -t moeka-m/mysql:latest .
$ docker images

EPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
moeka-m/mysql                  latest              855977a52a9d        16 hours ago        547MB

初期データを自動で読み込むように設定

Initializing a fresh instance

When a container is started for the first time, a new database with the specified name will be created and initialized with the provided configuration variables. Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order. You can easily populate your mysql services by mounting a SQL dump into that directory and provide custom images with contributed data. SQL files will be imported by default to the database specified by the MYSQL_DATABASE variable.

ref. mysql - Docker Hub

containerを起動させると、/docker-entrypoint-initdb.dに配置された.sh,.sql,.sql.gzをアルファベット順に実行してDababaseを初期化しますよ、の意。

FROM mysql:latest
ENV MYSQL_ROOT_PASSWORD pass
# 文字コードの指定がないとinitial.sqlの実行で Data too long for column と言われて失敗する
ENV LANG C.UTF-8
COPY . /docker-entrypoint-initdb.d

データの初期投入をするshellは要らなくなったので.dockerignoreに書いておく

init.sh

これで実行してみる。

$ docker build -t moeka-m/mysql:latest .
$ docker run --name file-mysql -p 3306:3306 -d moeka-m/mysql:latest
$ mysql --port=3306 --host=127.0.0.1 --user=isucari -p
mysql > SHOW TABLES FROM isucari;
+-----------------------+
| Tables_in_isucari     |
+-----------------------+
| categories            |
| configs               |
| items                 |
| shippings             |
| transaction_evidences |
| users                 |
+-----------------------+
6 rows in set (0.02 sec)

mysql> SELECT id, name, price FROM isucari.items LIMIT 1;
+----+---------------------------------------------------------------------------------------------------------------------+-------+
| id | name                                                                                                                | price |
+----+---------------------------------------------------------------------------------------------------------------------+-------+
|  1 | 浮くことなく世界中の4段階採用したっぷり入れロースタイルをスタッキング曲げ木の                                       |   100 |
+----+---------------------------------------------------------------------------------------------------------------------+-------+
1 row in set (0.00 sec)

Webappから接続

Hostの環境変数に接続情報を入れておく

$ export MYSQL_HOST=127.0.0.1
$ export MYSQL_PORT=3306
$ export MYSQL_USER=isucari
$ export MYSQL_DBNAME=isucari
$ export MYSQL_PWD=isucari

あとは冒頭リンクの記事通りにWebアプリを起動すると無事に起動!

後記

ちなみにこの状態でベンチマーカーを起動させると

2020/03/28 21:59:01 main.go:180: === final check ===
2020/03/28 21:59:01 main.go:212: 410 0
{"pass":true,"score":410,"campaign":0,"language":"Go","messages":[]}

これくらいのスコアになりました。 公式記事では初期スコアでも3020くらいいってたのでだいぶ遅いです。やっぱdockerで立てると遅くなるのかも。