フィボナッチ数列のうち、各桁の数字を足した数で割り切れる(ry

たまたま見つけたネタ。

 cf. フィボナッチ数列 – utthi_fumiの日記

タイトルが長くなったので省略しちゃったけど、ちゃんと引用すると、こう。


フィボナッチ数列のうち、各桁の数字を足した数で割り切れる数を以下の例に続けて小さいほうから5個求めてください。

2 → 2 ÷ 2

3 → 3 ÷ 3

5 → 5 ÷ 5

8 → 8 ÷ 8

21 → 21 ÷ 3・・・2 + 1= 3で割る

144 → 144 ÷ 9・・・1 + 4 + 4 = 9で割る

引用してもわかりにくいな。要するに、フィボナッチ数列を {Fn} (n=0,1,2…)として、Fn の各桁の数を足し合わせたものを S(Fn) とすると、S(Fn) で割り切れる Fn を小さい順に5つ(ただし、例に出ている 114 までを除く)求めよ、ってことだ。

「 これはメモ化と大きな桁を扱う事についての問題です 」なんて書いてあるけど、大きな桁はその通りだけど、n から Fn を求めようとするからメモ化なんかが必要になるんであって、この問題では小さい順に調べればいいんだから素直にフィボナッチ数列の頭から生成すればいい。

というわけで、Go でやってみた。ただやってみるだけじゃなくて、あんまり使ったことのないゴルーチンとチャネルを使ってみたよ。

まずは単純にフィボナッチ数列を生成するところから。

package main

import (
	"fmt"
)

func main() {
	s := makeFib()
	for i := 0; i < 10; i++ {
		f := <- s
		fmt.Println(f)
	}
}

type Stream chan int

func makeFib() Stream {
	s := make(Stream)
	go func() {
		a, b := 0, 1
		for {
			f := a
			a, b = b, a + b
			s <- f
		}
	}()
	return s
}

関数 makeFib は、チャネルを返す。で、このチャネルからフィボナッチ数列を一つずつ取り出せる。makeFib の中のゴルーチンは無限ループになっているので、(アルゴリズム上は)無限に取り出すことができる(実際はどうだかわからない)。実行結果は次の通り。

^o^ > go run fib_chan.go
0
1
1
2
3
5
8
13
21
34

さて、それじゃ本題に入ろう。といってもフィボナッチ数列はもう得られているんだから、あとは条件に合う数 Fn を5つ出力するだけだ。こうなった。

package main

import (
	"fmt"
)

func main() {
	s := makeFib()
	i := 0
	for i < 5 {
		f := <- s
		if f > 144 && isDivisible(f) {
			i++
			fmt.Println(f)
		}
	}
}

type Stream chan int

func makeFib() Stream {
	s := make(Stream)
	go func() {
		a, b := 0, 1
		for {
			f := a
			a, b = b, a + b
			s <- f
		}
	}()
	return s
}

func sumOfPlaces(n int) int {
	sum := 0
	for n > 0 {
		sum += n % 10
		n /= 10
	}
	return sum
}

func isDivisible(n int) bool {
	return (n % sumOfPlaces(n)) == 0
}

実行結果。

^o^ > go run fib_divisible.go
2584
14930352
86267571272
498454011879264
160500643816367088

OK。ちゃんとできた。

Pythonでwheelパッケージとその中のコマンドのバージョンを合わせる方法

タイトルが長いな。

コマンドを wheel パッケージにしたときに、そのコマンドとパッケージのバージョンを合わせたい、という当然の欲求を満足するための、(現在のところ)最良と思われる方法を見つけたのでメモしておく。

コマンドでバージョンを出力するには、コマンドのスクリプト中でバージョンがわからなければならないし、パッケージングするためには setup.py の中でバージョンがわからなければならない。当然のことながら、両方にバージョンを書くのは愚の骨頂。だからどこか1ヵ所でバージョンを定義して、それをコマンドと setup.py から参照することにする。

で、それはどこなのかというと、モジュールの __init__.py の中だ。この定義をコマンドと setup.py の両方から参照するんだけど、ちょっと工夫が必要だった。

ここでは、簡単な Hello world コマンドを例に説明する。ファイル構成はこんな感じ。

^o^ > tree /f .
フォルダー パスの一覧
ボリューム シリアル番号は 666A-93A9 です
C:\USERS\TAKATOH\DOCUMENTS\SANDBOX\HELLOPY
│ setup.py

└─hello
hello.py
__init__.py

hello ディレクトリがモジュールになっていて、コマンドの実体(関数)は hello/hello.py ファイルに書いてある。

バージョンの定義は、次の通り、hello/__init__.py の中。

__version__ = '0.3.2'

バージョン番号はいろいろ試行錯誤した結果を反映している。

これを setup.py から参照するのは簡単。モジュールをインポートして変数を参照するだけ。

import setuptools
import hello


setuptools.setup(
    name='hellopy',
    description='hello app.',
    version=hello.__version__,
    author='takatoh',
    author_email='takatoh.m@gmail.com',
    packages=setuptools.find_packages(),
    classifiers=[
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
    ],
    entry_points={
        'console_scripts': [
            'hellopy=hello.hello:main',
        ],
    }
)

コマンドから参照するにはもうちょっと工夫が必要。こうなった。

import os
import argparse


here = os.path.abspath(os.path.dirname(__file__))
about = {}
with open(os.path.join(here, '__init__.py')) as f:
    exec(f.read(), about)
VERSION = 'v' + about['__version__']


def main():
    parser = argparse.ArgumentParser(description='Hello tool.')
    parser.add_argument('name', metavar='NAME', type=str, nargs='?', default='world')
    parser.add_argument('-v', '--version', action='version', version=VERSION)
    args = parser.parse_args()

    print('Hello, {name}!'.format(name=args.name))


if __name__ == '__main__':
    main()

hello/__init__.py を読み込んで exec で評価して変数への参照を得ている。直接 hello モジュールをインポートできれば楽なんだけど、このコマンド(スクリプト)自体が hello モジュールに属しているので、それはできないみたいだ。

というわけで、この方法が一番いいようだ。もっといい方法が見つかるまではこれで行こうと思う。

最後に、実際に wheel パッケージを作って、インストールして試してみよう。

^o^ > python setup.py bdist_wheel
^o^ > pip install dist/hellopy-0.3.2-py3-none-any.whl
^o^ > hellopy --version
v0.3.2

^o^ > hellopy
Hello, world!

^o^ > hellopy Andy
Hello, Andy!

うまくいってる。

UbuntuにRMagickをインストール

環境

takatoh@apostrophe $ cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.5 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.5 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial
takatoh@apostrophe $ ruby -v
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]

↓このページによると、ImageMagick がバージョン7以降だと RMagick は使えないらしい。

 cf. RMagickのインストールで奈落に落ちた – Qiita

takatoh@apostrophe $ convert -version
Version: ImageMagick 6.8.9-9 Q16 x86_64 2018-09-28 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2014 ImageMagick Studio LLC
Features: DPC Modules OpenMP
Delegates: bzlib cairo djvu fftw fontconfig freetype jbig jng jpeg lcms lqr ltdl lzma openexr pangocairo png rsvg tiff wmf x xml zlib

6.8.9 だ、ラッキー。

依存パッケージとRMagickのインストール

まずは依存パッケージから。

takatoh@apostrophe $ sudo apt install libmagickcore-dev libmagickwand-dev

そして、RMagick。

takatoh@apostrophe $ sudo gem install rmagick
Building native extensions. This could take a while…
Successfully installed rmagick-2.16.0
Parsing documentation for rmagick-2.16.0
Installing ri documentation for rmagick-2.16.0
Done installing documentation for rmagick after 4 seconds
1 gem installed

完了。

さくらのVPSのサイトをHTTPS化

さくらの VPS で運用しているサイト(Railsアプリ)を Let’s Encrypt で HTTPS 化した。言っとくけどこのブログじゃないよ。

HTTPのポートを開放

まず、HTTPS のポート 443 を開放する。/etc/sysconfig/iptables ファイルを編集。

[takatoh@tk2-254-36564 ~]$ cd /etc/sysconfig
[takatoh@tk2-254-36564 sysconfig]$ sudo vim iptables

つぎの行を追加。

-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT

iptables を再起動。

[takatoh@tk2-254-36564 sysconfig]$ sudo service iptables restart
iptables: Setting chains to policy ACCEPT: filter [ OK ]
iptables: Flushing firewall rules: [ OK ]
iptables: Unloading modules: [ OK ]
iptables: Applying firewall rules: [ OK ]
iptables: Loading additional modules: ip_conntrack_ftp [ OK ]

certbot-auto のセットアップと証明書の自動生成

「certbot-auto」は Let’s Encrypt が提供しているクライントツールで、SSL証明書を取得したり延長したりできる。ここでは、最新版を取得する。

[takatoh@tk2-254-36564 bin]$ sudo wget https://dl.eff.org/certbot-auto
--2019-01-13 09:41:36-- https://dl.eff.org/certbot-auto
dl.eff.org をDNSに問いあわせています… 151.101.72.201, 2a04:4e42:36::201
dl.eff.org|151.101.72.201|:443 に接続しています… 接続しました。
HTTP による接続要求を送信しました、応答を待っています… 200 OK
長さ: 63562 (62K) [application/octet-stream]
`certbot-auto' に保存中

100%[======================================>] 63,562 --.-K/s 時間 0.002s

2019-01-13 09:41:39 (24.3 MB/s) - `certbot-auto' へ保存完了 [63562/63562]

[takatoh@tk2-254-36564 bin]$ sudo chmod 700 /usr/bin/certbot-auto

いまインストールした certbot-auto を使って、証明書を自動生成する。

[takatoh@tk2-254-36564 ~]$ sudo certbot-auto certonly --webroot -w /var/www/lathercraft/public -d www.lathercraft.net --email lathercraft@gmail.com

途中、いろんなパッケージをインストールしたりするのでy(yes)を入力する。しばらくすると利用規約が表示されるので、A(agree)を入力。最後に↓こんなメッセージが出ればOK。

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/www.lathercraft.net/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/www.lathercraft.net/privkey.pem
Your cert will expire on 2019-04-12. To obtain a new or tweaked
version of this certificate in the future, simply run certbot-auto
again. To non-interactively renew all of your certificates, run
"certbot-auto renew"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
- If you like Certbot, please consider supporting our work by:

Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le

- We were unable to subscribe you the EFF mailing list because your
e-mail address appears to be invalid. You can try again later by
visiting https://act.eff.org.

……じゃないな、e-mail が invalid だと言ってる。あとから試せるとも言ってるので、とりあえず先に進む。この時点で証明書類はこんな感じになっている。

[takatoh@tk2-254-36564 ~]$ sudo ls -l /etc/letsencrypt/live/www.lathercraft.net/
合計 4
-rw-r--r-- 1 root root 692 1月 13 09:53 2019 README
lrwxrwxrwx 1 root root 43 1月 13 09:53 2019 cert.pem -> ../../archive/www.lathercraft.net/cert1.pem
lrwxrwxrwx 1 root root 44 1月 13 09:53 2019 chain.pem -> ../../archive/www.lathercraft.net/chain1.pem
lrwxrwxrwx 1 root root 48 1月 13 09:53 2019 fullchain.pem -> ../../archive/www.lathercraft.net/fullchain1.pem
lrwxrwxrwx 1 root root 46 1月 13 09:53 2019 privkey.pem -> ../../archive/www.lathercraft.net/privkey1.pem

Nginxの設定

SSL 対応にする。こんなふうに書き換える。

    #listen      80;
    listen      443 ssl;

    ssl_certificate      /etc/letsencrypt/live/www.lathercraft.net/cert.pem;
    ssl_certificate_key  /etc/letsencrypt/live/www.lathercraft.net/privkey.pem;

Nginx を再起動。

[takatoh@tk2-254-36564 conf.d]$ sudo service nginx restart
Stopping nginx: [ OK ]
Starting nginx: [ OK ]

これでOKのはず。まだアプリが SSL 対応していないのでバナー画像に https でアクセスしてみたら、ちゃんと表示された。なのでここまではOKのようだ。

RailsアプリのSSL対応

これは簡単。config/envronments/production.rb ファイルのつぎの箇所を修正すればいい。

# config.force_ssl = true
config.force_ssl = true

コメントアウトしてあるのを外すだけだ。そして再起動。

[takatoh@tk2-254-36564 environments]$ sudo service unicorn_lathercraft restart
reloaded OK

OK。大丈夫そうだ。

httpをhttpsにリダイレクト

最後に、http でのアクセスを https にリダイレクトする設定をする。Nginx の設定に戻ってつぎの設定を追加する。

server {
    listen       80;
    server_name  www.lathercraft.net;
    return       301 https://www.lathercraft.net;
}

Nginx を再起動して完了。

証明書の自動更新設定

Let’s Encrypt の証明書は有効期限が3ヶ月と短いので、自動更新するように設定する。更新は certbot-auto renew コマンドでできるので、これを root ユーザの cron に登録する。

[takatoh@tk2-254-36564 ~]$ sudo -s
[root@tk2-254-36564 takatoh]# crontab -l
no crontab for root
[root@tk2-254-36564 takatoh]# crontab -e
0 4 * * 0 certbot-auto renew --post-hook "service unicorn_lathercraft restart" > /dev/null 2> /dev/null

これで毎週月曜日の午前4時に更新するようになる。

[追記]

上の、Nginx のリダイレクトの設定では、http でのアクセスはどんなパスでも全て https のルートにリダイレクトされてしまう。なので、つぎのように変更した。

server {
    listen       80;
    server_name  www.lathercraft.net;

    location ~ ^/(.*)$ {
        return 301 https://www.lathercraft.net/$1;
    }
}

これで各ページも https にリダイレクトされるようになった。

Windows10その後

というか iTunes のその後、というか。

結局、Windows を再インストールして、アプリを一つずつインストールしながら iTunes がちゃんと動くかどうかを確認していった。ついでに、先週のエントリを書いた時点では気づかなかったけど、Dropbox の同期もできていなかったので、これも確認しながら。

で、どうやら RLogin という SSH 接続に使っているターミナルクライアントが怪しいということになって、これを除いた構成で様子を見ることにした。ところが、再インストール直後は大丈夫だった iTunes と Dropbox がしばらく経つといつの間にかやっぱり繋がらなくなっている。どうも、少なくとも RLogin 単独のせいではないようで、なぜつながらないのかさっぱりわからない。

そういうわけなので、montana で iTunes と Dropbox を動かすのは諦めて、ノートPCの sofa で動かすことにした。sofa のストレージは SSD で容量が少ないので、samba サーバ(wplj)に共有フォルダを新しく作って、それを sofa の M:ドライブに割り当てて音楽保管用のフォルダを作った(なにせアルバム1000枚、60GBもあるので)。この構成だと、家の WiFi の外では使えないけど、それは諦めることにした。

というわけで、montana は WEB ブラウジングとゲーム用のマシンになった。ゲームのセーブデータは全部消えたけどな(←こないだも言った)!

iTunes Storeにつながらない

Windows10 のセットアップが終わって、一息ついたと思ったら、iTunes Store につながらない。「ネットワークの接続を確認して、やり直してください。」と言われる。CD からインポートした曲は聴けるんだけど(当然)、Store から購入した曲が聴けない。これじゃ、何のために Windows の再インストールをしたんだかわからないじゃないか。

たしか昨日、Windows のインストールが終わったあと、最初にインストールしたのが iTunes だ。その時はちゃんとつながったはずだ。ということは、そのあとにインストールしたアプリが悪さをしてるか、何か設定が変わってしまったか。

はぁ、一つずつ消して確かめてみるか……

[追記]

iTunes 以外のアプリを一つずつ消しながら確かめてみた。結果、iTunes 以外の、あとからインストールしたアプリを全部消しても、さらには iTunes 自体を再インストールしてもつながらなかった。たぶん、アプリのインストールをしてるどこかで何かの設定が変わっちゃったんだ。

結局、Windows の再インストールしかないか。

Windows10を再インストールした

昨日のことだけど、時間が取れなかったので今日書く。

先日こわれて(こわして)しまった montana の Windows10、アマゾンで頼んだインストールメディア(DVD)が届いた。届いた Windows には LAN アダプタがついていた、というか LAN アダプタに Windows10 がついていたというのか。そういえば注文した時にバンドル版だと書いてあったような気がする。それにしてもこの LAN アダプタ、10/100Mbps なんだけど今時こんなのあるんだね。誰が使うんだろう?

それはともかく、Windows10 の再インストールだ。インストール自体はインストーラに従っていけばすんなり終わるので、詳細は割愛する。ただ、デフォルトでは「ドキュメント」ほかいくつかのフォルダが OneDrive 上に保存されるようになっている。この点だけ、ローカルドライブに保存するように変更した。

まず、画面右下の OneDrive のアイコン(雲のアイコン)を右クリックして「設定」を選ぶ。「自動保存」タブの「フォルダの更新」ボダンをクリックして出てくるウィンドウに「デスクトップ」と「写真」、「ドキュメント」があるので、それぞれ「保護の停止」をクリックする。これで OneDrive じゃなくてローカルドライブに保存するようになる。

あとはアプリのインストールだけど、使ってるのはほとんどフリーソフト(例外は iTunes と MS Office、B’s Recorder くらい)なので、Chocolatey を使ってインストールした。コマンド一発でダウンロードからインストールまでしてくれるので楽でいい。

ただ、Ruby だけは別にインストールした。というのは Chocolatey でインストールすると Devkit が含まれていないものがインストールされるからだ。これだとネイティブなライブラリ(RDiscount とか)がインストールできない。なので、RubyInstaller のダウンロードページから Devkit を含んだインストーラをダウンロードしてインストールした。

さて、これで一通りのセットアップは終了。アイコンがごちゃごちゃしていたデスクトップもさっぱりしたので、そこはよかったかも。

ゲームのセーブデータは全部消えたけどな!

Windows10がこわれた

というかこわしちゃった、というか。新年早々、なんて運が悪いんだ。

こわれたのは Windows10 マシンの montana なんだけど、経緯はこうだ。しばらく前からちょっと調子が悪かった。DropBox が同期できないし、iTunes のアップデートにも失敗する。どうもネットにうまくつながらなくて失敗してるらしいんだけど、一方でブラウザ等ではなんの問題もなく使えてた。とはいえ、不便もあったし、連休中でちょうどいいので、以前作ってあった回復ドライブ(USBメモリ)をつかって、インストールし直そうとしたわけだ。

で、いったん電源を落として、回復ドライブの USBメモリをさしてスイッチオン。F12キーを押してブートメニュー画面に入ってから、「USB Storage Drive」を選択。これで回復ドライブから起動する。あとはメニューに従っていくと、「このPCを回復しています」というメッセージと一緒に進捗を示すパーセンテージが表示される。

あとは待つだけかとおもったら、5%くらいの進捗のところで、「PCを初期状態に戻すときに問題が発生しました。変更は行われませんでした。」というメッセージが!

この時点で選択肢は「キャンセル」しか無い。仕方なくキャンセルすると、起動したあとの最初の方の画面に戻ってやり直し。ところが結局は同じ結果になる。何度繰り返しても同じ。

試しに USBドライブを抜いて起動してみたけど、ファイルが削除されてしまったらしくハードディスクからは起動できず。ここで手詰まり。

というわけで、Windows10 がこわれてしまった。何が回復ドライブだ。ふざけんな!

Gem in a Boxでオレオレgemサーバをたてる

自分でしか使わない、外部には公開できないような gem がいくつかあるので、gem サーバをたてることにした。Gem in a Box (geminabox)は、プライベートな gem サーバだ。gem コマンドでインストールして、ちょっと設定ファイルを書くだけで使えるようになる。

じゃ、いってみよう。

環境

  • Ubuntu 16.04 LTS
  • Ruby 2.3.1p112

インストールと設定

前述したように gem コマンドでインストールできる。

takatoh@wplj $ sudo gem install geminabox

~/geminabox ディレクトリを作って、設定ファイルを書く。

takatoh@wplj $ mkdir ~/geminabox
takatoh@wplj $ cd ~/geminabox
takatoh@wplj $ vim config.ru

設定ファイルはこんな感じ、っていうかこれだけ。

require "geminabox"

Geminabox.data = "/home/takatoh/geminabox/data"
run Geminabox::Server

gem ファイルが格納される data ディレクトリを作る。

takatoh@wplj $ mkdir data

これで Gem in a Box 自体のインストールと設定は完了。

Supervisord で管理

さて、これをデーモンにする必要があるんだけど、昨日 Supvervisord を試したので今日も同じようにしてみる。設定ファイル /etc/supervisord.d/geminabox.ini をつぎのように書いた。

[program:geminabox]
command=/usr/local/bin/rackup -p 9999
process_name=geminabox
user=takatoh
directory=/home/takatoh/geminabox
autostart=true
autorestart=true
stdout_logfile=/home/takatoh/geminabox/geminabox.log
stdout_logfile_maxbytes=1M
stdout_logfile_backups=7
stdout_capture_maxbytes=1M
redirect_stderr=true

これで OK のはず。動かしてみよう。

takatoh@wplj $ supervisorctl reload
Restarted supervisord
takatoh@wplj $ supervisorctl status
geminabox RUNNING pid 26835, uptime 0:00:08
sulaiman RUNNING pid 26836, uptime 0:00:08

ちゃんと動いてるようだ。

Nginxのバーチャルホスト設定

gembox というホスト名でアクセスできるように Nginx を設定する。/etc/nginx/sites-available/gembox が設定ファイルだ。

upstream geminabox {
    server 127.0.0.1:9999;
}

server {
    # port
    listen      80;

    # server name
    server_name gembox;

    # document root
    root        /home/takatoh/geminabox;

    # index
    #index       index.php index.html index.htm;

    # log files
    access_log /var/log/nginx/gembox/access.log combined;
    error_log  /var/log/nginx/gembox/error.log  warn;

    keepalive_timeout     60;
    proxy_connect_timeout 60;
    proxy_read_timeout    60;
    proxy_send_timeout    60;

 
    location / {
        proxy_set_header Host $http_host;
        proxy_pass http://geminabox;
    }
}

/etc/nginx/sites-enabled/gembox にリンクをはる。

takatoh@wplj $ sudo ln -s /etc/nginx/sites-available/gembox sites-enabled/gembox

ログファイル用のディレクトリを作成。

takatoh@wplj $ sudo mkdir /var/log/nginx/gembox

そして、Ngixn をリロード。

takatoh@wplj $ sudo systemctl reload nginx

これで完了。ほかのマシンからブラウザで http://gembox/ にアクセスすると、期待どおり Gem in a Box のページにアクセスできた。

[追記]

アクセスはできたけど、試しに gem ファイルを1つアップロードしてみたら「uninitialized constant Gem::Util」というエラーが出てしまった。なんだこりゃ?

[追記2]

解決編。rubygems 自体が古いのが原因だったようだ。

takatoh@wplj $ sudo gem update --system
takatoh@wplj $ gem -v
3.0.1

念のため、データ置き場を作りなおす。

takatoh@wplj $ cd ~/geminabox
takatoh@wplj $ rm -rf data
takatoh@wplj $ mkdir data

リスタート。

takatoh@wplj $ supervisorctl restart geminabox
geminabox: stopped
geminabox: started

これで無事、オレオレgemをアップロードできるようになった。

Supervisorで自作プログラムをデーモン化

自作のプログラムやスクリプトをデーモン化したい場合がある。でも、デーモンとして動作するようには作っていない……そういう時には Supervisor の出番だ。

Supervisor は Python 製のツールで、デーモンとして動作するように作られていないプログラムでもデーモン化できる。Supervisor 自体は root 権限でデーモンとして動かす必要があるけど、その下にぶら下がるプログラムはユーザー権限で管理できるのがいい点だ。

今日は、Ubuntu マシンの wplj に Supervisor をインストールして、以前 Go で作った画像アップローダを動かしてみる。

環境

  • Ubuntu 16.04 LTS
  • Python 2.7.12

Supervisorのインストールといろいろ下準備

Supervisor 自体は pip でインストールできる。

takatoh@wplj $ sudo pip install supervisor

/etc/supervisord.conf にデフォルトの設定ファイルを生成。

takatoh@wplj $ sudo echo_supervisord_conf > /etc/supervisord.conf
-bash: /etc/supervisord.conf: 許可がありません

あれ、sudo つけてるのになんでだ?よくわからないけど sudo -s してからやってみよう。

takatoh@wplj $ sudo -s
root@wplj # echo_supervisord_conf > /etc/supervisord.conf

今度はできた。つぎ、include 設定ファイル用のディレクトリを作成。

root@wplj # mkdir /etc/supervisord.d

supervisord 自体のはくログ周りの設定。デフォルトでは /tmp/supervisord.log に吐き出すことになっているので、設定ファイル /etc/supervisord.conf を修正して /var/log/supervisord.log に吐くようにする。

[supervisord]
;logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log
logfile=/var/log/supervisord.log

PID ファイルの設定。/var/run 以下に置くように変更。

;pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
pidfile=/var/run/supervisord.pid

/etc/supervisord.d 以下のファイルを include するように変更。これで設定ファイルの修正は終了。

[include]
files = supervisord.d/*.ini

supervisord自体のサービス登録

/etc/systemd/system/supervisord.service ファイルを作成する。

[Unit]
Description=Supervisor process control system for UNIX
Documentation=http://supervisord.org/
After=network.target

[Service]
ExecStart=/usr/local/bin/supervisord -n -c /etc/supervisord.conf
ExecStop=/usr/local/bin/supervisorctl $OPTIONS shutdown
ExecReload=/usr/local/bin/supervisorctl $OPTIONS reload
KillMode=process
Restart=on-failure
RestartSec=50s

[Install]
WantedBy=multi-user.target

起動と自動起動の登録

root@wplj # systemctl start supervisord
root@wplj # systemctl enable supervisord.service
Created symlink from /etc/systemd/system/multi-user.target.wants/supervisord.service to /etc/systemd/system/supervisord.service.

自作プログラムの配置

Go で作った画像アップローダ sulaiman を配置する。こんなふうにした。

takatoh@wplj $ tree ~/sulaiman
/home/takatoh/sulaiman
├── config.json
├── photos
│   ├── img
│   └── thumb
├── static
│   ├── css
│   │   └── style.css
│   ├── html
│   │   └── index.html
│   └── js
│   └── sulaiman.js
└── sulaiman

試しに動かしてみる。

takatoh@wplj $ ./sulaiman
_ _ / // /
/ // / \/ _ \
//__////_/ v3.3.5
High performance, minimalist Go web framework
https://echo.labstack.com
_________________________________O/_

O\
⇨ http server started on [::]:9099

大丈夫そうだ。

プロセス管理登録と起動確認

配置したプログラムを supervisord の管理下に登録する。/etc/supervisord.d 以下に設定ファイル sulaiman.ini を作る。

[program:sulaiman]
command=/home/takatoh/sulaiman/sulaiman
process_name=sulaiman
user=takatoh
directory=/home/takatoh/sulaiman
autostart=true
autorestart=true
stdout_logfile=/home/takatoh/sulaiman/sulaiman.log
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=7
stdout_capture_maxbytes=1MB
redirect_stderr=true

これで OK のはずだ。確認してみよう。

takatoh@wplj $ supervisorctl start sulaiman
error: , [Errno 13] Permission denied: file: /usr/lib/python2.7/socket.py line: 228

はぁ?どういうこと?

問題解決

ソケット周りでエラーが出てるようだ。権限がないとかいってる。supervisord の設定ファイルを眺めてみると、ソケットファイルのモードを設定する項目が見つかった。これを変更してみよう。

[unix_http_server]
file=/tmp/supervisor.sock ; the path to the socket file
;chmod=0700 ; socket file mode (default 0700)
chmod=0777

supervisord をリロード。

takatoh@wplj $ sudo systemctl reload supervisord

確認。

takatoh@wplj $ supervisorctl status sulaiman
sulaiman RUNNING pid 19356, uptime 0:00:22

こんどは OK。

これで、プログラムやスクリプトを簡単にデーモン化できるようになった。

参考ページ

 cf. Supervisorで簡単にデーモン化 – Qiita