モヒカンは正義

渋谷で働く怪しいWebエンジニアの生きた証と備忘録

インスタンスが再起動するとホスト名の設定がリセットされる原因と対策

症状

ConoHa のVPSで /etc/hostname へホスト名を書いても、インスタンスが再起動すると初期値へ戻っている。

原因

インスタンスの初期化に使われている cloud-init というソフトウェアの「ホスト名を変更する機能」が有効になっているため。

なので、ConoHaのVPS特有の問題という訳ではない。

対策

シンプルに、「ホスト名を上書きする」という設定を無効にすれば良い。

Modules — Cloud-Init 18.2 documentation

This module will update the system hostname and fqdn. If preserve_hostname is set, then the hostname will not be altered.

設定ファイルは /etc/cloud/cloud.cfg にある。

$ ls -al /etc/cloud/
合計 24
drwxr-xr-x   4 root root 4096  5月 10 23:48 .
drwxr-xr-x 107 root root 4096  5月 30 23:14 ..
-rw-r--r--   1 root root 3100  5月 10 23:01 cloud.cfg
drwxr-xr-x   2 root root 4096  5月 10 22:54 cloud.cfg.d
-rw-r--r--   1 root root  447 12月 27  2016 cloud.cfg.dpkg-old
drwxr-xr-x   2 root root 4096  5月 10 22:54 templates

今回検証に使った Ubuntu 16.04.4 LTS では、documentに記載の通り

preserve_hostname: false

となっている行を

preserve_hostname: true

とすると、確かに再起動してもホスト名が勝手に変わることは無くなった。

ヤッタネ

AlpineLinuxでdigコマンドを使えるようにする

Dockerイメージを作る際のベースイメージとしてよく使われるOSに AlpineLinux がある。超軽量なのが特徴で、イメージサイズはわずか4MByte強!参考までに、CentOS7.4のイメージは約200MBある。

$ docker images alpine
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
alpine              latest              3fd9065eaf02        4 months ago        4.15MB
$ docker images centos
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              e934aafc2206        8 weeks ago         199MB

そんなAlpineLinuxでdigコマンドを使いたくなったが、当然のように入っていなかった。

/ # dig
sh: dig: not found

パッケージの探しかた

AlpineLinuxで使えるパッケージは下記で探せる。

Alpine Linux packages

file欄に dig と入力して検索すると、下記のとおり bind-tools というpackageに入っていることがわかる。

f:id:pinkumohikan:20180602153721p:plain

インストールする

AlpineLinuxのパッケージマネージャは apk

Alpine Linux package management - Alpine Linux

apk is the tool used to install, upgrade, or delete software on a running sytem.

bind-tools パッケージをインストールする場合、下記のようなコマンドとなる。

apk add --no-cache bind-tools

--no-cache を指定している理由は、最下部の補足を参照

実行結果

/ # apk add --no-cache bind-tools
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz
(1/3) Installing libgcc (6.4.0-r5)
(2/3) Installing bind-libs (9.11.3-r0)
(3/3) Installing bind-tools (9.11.3-r0)
Executing busybox-1.27.2-r7.trigger
OK: 21 MiB in 35 packages

インストールが終わると、digコマンドが使えるようになる。

/ # dig

; <<>> DiG 9.11.3 <<>>
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22199
;; flags: qr rd ra; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;.              IN  NS

;; ANSWER SECTION:
.           57071   IN  NS  a.root-servers.net.
.           57071   IN  NS  b.root-servers.net.
.           57071   IN  NS  c.root-servers.net.
.           57071   IN  NS  d.root-servers.net.
.           57071   IN  NS  e.root-servers.net.
.           57071   IN  NS  f.root-servers.net.
.           57071   IN  NS  g.root-servers.net.
.           57071   IN  NS  h.root-servers.net.
.           57071   IN  NS  i.root-servers.net.
.           57071   IN  NS  j.root-servers.net.
.           57071   IN  NS  k.root-servers.net.
.           57071   IN  NS  l.root-servers.net.
.           57071   IN  NS  m.root-servers.net.

;; Query time: 11 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Fri Jun 01 13:25:43 UTC 2018
;; MSG SIZE  rcvd: 239

めでたしめでたし

補足

例示のインストールコマンドは、Dockerfile内で叩くことを想定して可能な限りサイズが小さくなるように --no-cache を指定している。

「Dockerfile内から叩くわけじゃないし --no-cache していしなくていっかー」と思った人に読んで欲しいが、 apk add コマンドはインストール対象のパッケージ情報をローカルのpackage indexから検索しようとする。そのため、いきなり apk add bind-tools と叩くと下記のように「bind-tools?知らない人ですね」とエラーになってしまう。

/ # apk add bind-tools
WARNING: Ignoring APKINDEX.70c88391.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.5022a8a2.tar.gz: No such file or directory
ERROR: unsatisfiable constraints:
  bind-tools (missing):
    required by: world[bind-tools]

そういうときは、 apk add を叩く前に apk update を叩いて、package indexを更新しておく。

/ # apk update && apk add bind-tools
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz
v3.7.0-190-g3da66f61a4 [http://dl-cdn.alpinelinux.org/alpine/v3.7/main]
v3.7.0-184-ge62f3c3781 [http://dl-cdn.alpinelinux.org/alpine/v3.7/community]
OK: 9054 distinct packages available
(1/4) Installing libgcc (6.4.0-r5)
(2/4) Installing libxml2 (2.9.7-r0)
(3/4) Installing bind-libs (9.11.3-r0)
(4/4) Installing bind-tools (9.11.3-r0)
Executing busybox-1.27.2-r7.trigger
OK: 9 MiB in 15 packages

ここまで読むと「例示のコマンドでは apk update なんて書いて無かったじゃないか」と思うかもしないが、実はそれで良くて、apk add--no-cache オプションをつけておくと、勝手に apk update したあとに apk add を実行し、最後にpackage indexをclean (rm) してくれる。 なぜそんな設計にしたのか、不思議だ。

CircleCI: docker_layer_cachingを有効にしてdocker buildでキャッシュを効かせる

f:id:pinkumohikan:20180528235406p:plain

CircleCIでdocker buildする際、初期状態ではbuild cacheが無効になっている。documentを漁っていたら、オプションでbuild cacheを有効にできるようだったので試してみた。

By default, the Remote Docker Environment doesn’t provide layer caching, but you can enable this feature with a special option

circleci.com

設定方法

Remote Dockerの場合

- setup_remote_docker:
    docker_layer_caching: true

see: https://circleci.com/docs/2.0/docker-layer-caching/#docker-layer-caching-in-remote-docker

Machine Executorの場合

machine:
  docker_layer_caching: true

see: https://circleci.com/docs/2.0/docker-layer-caching/#docker-layer-caching-in-machine-executor

実行時間の変化

Engine: Machine Executor

before

f:id:pinkumohikan:20180528232537p:plain

after

f:id:pinkumohikan:20180528232553p:plain

2分以上 早くなったΣ

build cacheを使いたくないときは、 --no-cache オプションをつけてdocker buildすればキャッシュを使わずにbuildしてくれる

Laravel tinkerで、DBへつながっていることを確認する

f:id:pinkumohikan:20180503165946p:plain

Laravel tinkerからデータベースへつながっていること (SQLを発行出来る状態になっていること) を確認したい!そんなとき

コマンド

DB::select('select 1');

実行するとこんな感じ

>>> DB::select('select 1');
=> [
     {#2300
       +"1": 1,
     },
   ]

赤くハイライトされたエラーが出なければ問題🍆

エラー例

下記のようなエラーが出たら、何かがおかしいので直す

>>> DB::select('select 1');
Illuminate/Database/QueryException with message 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name does not resolve (SQL: select 1)'

-> DBのホスト名が間違っている。

>>> DB::select('select 1');
Illuminate/Database/QueryException with message 'SQLSTATE[HY000] [2002] Connection refused (SQL: select 1)'

-> DBサーバが起動していない or ファイアウォールの設定が足りない or DBのポート番号が間違っている

>>> DB::select('select 1');
Illuminate/Database/QueryException with message 'SQLSTATE[HY000] [1044] Access denied for user 'some-db-user'@'%' to database 'some-database' (SQL: select 1)'

-> DBのID/Passが間違っている or 権限がない or データベースが存在しない

プライベートプロダクトのpackage.jsonのlicenseフィールドには "license: UNLICENSED" と指定すればいいらしい

f:id:pinkumohikan:20180426233035p:plain

package.jsonのlicenseフィールドに何も指定しないと、下記のような警告が出てしまい目障りである

warning package.json: No license field

プライベートプロダクトでpackage managerとしてnpm / yarnを使っているだけで、publishしたりオープンソースにするつもりはない。そういう時にどうすれば良いんだろうかと思ってモヤモヤしていたが... 普通に公式に書いてあったw

docs.npmjs.com

Finally, if you do not wish to grant others the right to use a private or unpublished package under any terms: { "license": "UNLICENSED" }

雑訳: 他人に権利を譲らないプライベートな奴だったら "license": "UNLICENSED" を指定してね

Consider also setting "private": true to prevent accidental publication.

雑訳: "private": true を指定しとくと、意図しない公開を防げるよ (誤npm publish対策)

{
  ...snip...
  "license": "UNLICENSED",
  "private": true
}

みたいな感じか

$ yarn install
yarn install v1.6.0
[1/5] 🔍  Validating package.json...
[2/5] 🔍  Resolving packages...
success Already up-to-date.
✨  Done in 0.35s.

確かに出なくなった、これで良さそう

2つの設定ファイルから空行やコメントを除いていい感じにdiffを表示するプチテクニック

▼ アイキャッチ画像は適当です

f:id:pinkumohikan:20180212222252p:plain

ミドルウェアなどのバージョンアップやマイグレーションなどの際に、「今の設定ってデフォルトからどこがどう変わってるんだっけ?」って気になることありますよね? でも、単純に diff コマンドで差分を出すだけだと改行やコメントが邪魔をして差分が分かりづらいですよねー。キレそう。

そういうときに使えるおまじないを教えます。

ドン

diff <(grep -v '^;' setting-a.ini | grep -v '^$') <(grep -v '^;' setting-b.ini | grep -v '^$')

(空行や ; で始まるコメント行を除いた結果をdiffしてるだけなんですけど、案外grepした結果でdiff取るの知られてなさそうだったので記事にしてみました)

動かしてみる

前提

こんな感じの設定ファイルがあったとして

setting-a.ini

; owner's name
owner = pinkumohikan

; owner's age
age = 24

setting-b.ini

; ie-i, miteru---?



; owner's name
owner = pinkumohikan

; owner's age
;
age = 25

単純にdiff取ると

$ diff setting-a.ini setting-b.ini
0a1,4
> ; ie-i, miteru---?
>
>
>
5c9,10
< age = 24
---
> ;
> age = 25

こんな感じで空行やコメントの差分が邪魔ですね、減給。

でもさっきのコマンドなら

$ diff <(grep -v '^;' setting-a.ini | grep -v '^$') <(grep -v '^;' setting-b.ini | grep -v '^$')
2c2
< age = 24
---
> age = 25

なるほど、ageが24から25に変わってるんやなーっていうのがひと目でわかりますね。

(1月18日の) 誕生日おめでとうありがとうございます!

dockerコンテナ内から /dev/stdout へ書き込もうとすると "Permission denied" になる

PHP Laravel 5.5系のアプリをdockerコンテナにして、所定のログファイルpathから /dev/stdout へのsymlinkを貼って動かしたところ Permission denied エラーが出た。原因を調べたところそれなりにめんどくさかったので調べたことをまとめておく。

症状

  • dockerコンテナ内のアプリから /dev/stdout へログを吐こうとしたときに Permission denied エラーになる
  • コンテナに入ってディレクトリとパーミンションを確認すると、パット見問題無さそう

原因

/dev/stdout の最終的な向き先が自分のpts (仮想端末) ではないことがあり (バグ?)、自分のものでないptsには当然書き込めないのでエラーとなる。

▼ ptsってなんやねん的な人向け

takuya-1st.hatenablog.jp

▼ 神記事

qiita.com

どうすればいいか

アプリから /dev/stdout へ直接ログを吐かず、プログラミング言語が用意している標準出力用streamへ向けてログを吐くようにする (phpでいうと php://stdout ってやつ)。

そうするといい感じに実行ユーザのptsを解決して、自分のptsへ向けて標準出力を吐いてくれる。


Laravelの場合、 bootstrap/app.php に下記のような設定を加えればOK。

$app->configureMonologUsing(function ($monolog) {
    $monolog->pushHandler(
        new \Monolog\Handler\StreamHandler(
            'php://stdout',
            \Monolog\Logger::WARNING // TODO: 適切なログレベルを設定
        )
    );
});

▼ configureMonologUsingってなんやねん的な人向け

laravel.com

memo

▼ 同じような事象を相談していた stack overflow。この人はSymfonyだけど同じ原因。

stackoverflow.com

DockerHubのnginx:latestイメージでpsコマンド叩いたら「そんなもの無い」って言われたのでインストールする

背景

DockerHubにある nginx:latest イメージから起動したコンテナ内で ps を叩いたら

root@de0ff7733246:/# ps
bash: ps: command not found

と言われてしまったので、インストールする

インストール

apt update && apt install -y procps

エイヤ

root@de0ff7733246:/# ps
  PID TTY          TIME CMD
   11 pts/0    00:00:00 bash
  340 pts/0    00:00:00 ps

やったね

余談

apt update って、文字からはインストール済みパッケージを文字通りupdateするかのような印象を受けるけど、実はパッケージ情報を取得するだけの模様

# apt
apt 1.4.8 (amd64)
Usage: apt [options] command

apt is a commandline package manager and provides commands for
searching and managing as well as querying information about packages.
It provides the same functionality as the specialized APT tools,
like apt-get and apt-cache, but enables options more suitable for
interactive use by default.

Most used commands:
  list - list packages based on package names
  search - search in package descriptions
  show - show package details
  install - install packages
  remove - remove packages
  autoremove - Remove automatically all unused packages
  update - update list of available packages
  upgrade - upgrade the system by installing/upgrading packages
  full-upgrade - upgrade the system by removing/installing/upgrading packages
  edit-sources - edit the source information file

See apt(8) for more information about the available commands.
Configuration options and syntax is detailed in apt.conf(5).
Information about how to configure sources can be found in sources.list(5).
Package and version choices can be expressed via apt_preferences(5).
Security details are available in apt-secure(8).
                                        This APT has Super Cow Powers.

インストール済みパッケージを文字通りupdateしたければ upgradefull-upgrade を使うらしい

PHP用HTTPクライアント「HTTP_Request2」をcomposerでinstallする

PHPで使える 古参 HTTPクライアント、 HTTP_Request2 をcomposerを使ってインストールする方法を紹介します。

今時composerを使っていないPHPプロジェクトなんて有るわけないですからね!

f:id:pinkumohikan:20170719230314p:plain

pearライブラリをcomposerでinstallするには事前にリポジトリを追加したりする必要がある聞いていたので面倒だなーと思っていたのですが、v2.2.0以上なら普通に composer require で入るようです。

packagist.org

手順

1. composerをダウンロードする

getcomposer.org

$ ls
composer.phar

ちなみに、 programmaticにdownloadしたいときはこうするのが良いらしい (今日知った)

getcomposer.org

2. composer requireする

$ ./composer.phar require pear/http_request2
Using version ^2.3 for pear/http_request2
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 3 installs, 0 updates, 0 removals
  - Installing pear/pear_exception (v1.0.0): Loading from cache
  - Installing pear/net_url2 (v2.2.1): Loading from cache
  - Installing pear/http_request2 (v2.3.0): Loading from cache
Writing lock file
Generating autoload files

↑が正常に終わると、こんな感じに仲間が増えているはず

$ ls
composer.json  composer.lock  composer.phar  vendor

3. 使える

$ php -r "require_once __DIR__.'/vendor/autoload.php'; var_dump(class_exists('HTTP_Request2'));"
bool(true)

あとはいい感じにDocumentを見て使って下さい。

Manual :: HTTP_Request2

/etc/cron.dへ置くファイルにはownerとpermissionに制約があるっぽい

crontabファイルを etc/cron.d へ置いても期待通り動かなかったことがあって調べたメモ

f:id:pinkumohikan:20170708185525p:plain

まとめ

/etc/cron.dへ置くファイルは

  • ownerは、rootでなければならない
  • permissionは、owner以外に書き込み権限があってはならない

検証

ownerがroot、かつowner以外のwrite権限無しなら怒られない

$ sudo tee /etc/cron.d/hoge << EOM
> * * * * * root echo 'hoge' > /dev/null
> EOM
* * * * * root echo 'hoge' > /dev/null

$ sudo chmod 600 /etc/cron.d/hoge

$ sudo ls -al /etc/cron.d/hoge
-rw------- 1 root root 39  7月  8 18:41 /etc/cron.d/hoge

結果

$ sudo tail -f /var/log/cron
...
Jul  8 18:42:01 marlin CROND[10186]: (root) CMD (echo 'hoge' > /dev/null)
...

permissionがgroup write有りだと怒られる

$ sudo chmod 660 /etc/cron.d/hoge

$ sudo ls -al /etc/cron.d/hoge
-rw-rw---- 1 root root 39  7月  8 18:42 /etc/cron.d/hoge

結果

$ sudo tail -f /var/log/cron
...
Jul  8 18:43:01 marlin crond[7153]: (root) BAD FILE MODE (/etc/cron.d/hoge)
...

ownerがroot以外だと怒られる

$ sudo chmod 600 /etc/cron.d/hoge

$ sudo chown h-shinoda /etc/cron.d/hoge

$ sudo ls -al /etc/cron.d/hoge
-rw------- 1 h-shinoda root 39  7月  8 18:42 /etc/cron.d/hoge

結果

$ sudo tail -f /var/log/cron
...
Jul  8 18:48:01 marlin crond[7153]: (root) WRONG FILE OWNER (/etc/cron.d/hoge)
...

参考にした資料