モヒカンは正義

プログラマー風林火山で言う「風のエンジニア」になりたい(比較的)若者Webエンジニアの備忘録

Makefileを使ってcomposerをスマートにダウンロードする

やりたいこと

  • composerをダウンロードしたい
  • 繰り返し実行できるように、composerが既にDL済みのときは何もしたくない (毎回DLするのは無駄なのでやりたくない)

f:id:pinkumohikan:20181015132436p:plain

やりたいことを叶えるMakefile

.PHONY: setup
setup: composer.phar
    # ここにアプリケーションをセットアップするためのアレコレを書く。composer installとか。

composer.phar:
    curl -sSfL -o composer-setup.php https://getcomposer.org/installer
    php composer-setup.php --filename=composer.phar
    rm composer-setup.php

実行例

# 最初はcomposer.pharが無いので、ダウンロードが走る
$ make setup
curl -sSfL -o composer-setup.php https://getcomposer.org/installer
php composer-setup.php --filename=composer.phar
All settings correct for using Composer
Downloading...

Composer (version 1.7.2) successfully installed to: /Users/pinkumohikan/products/jinsoku/composer.phar
Use it: php composer.phar

rm composer-setup.php

# composer.pharが使える状態になる
$ ./composer.phar | head -n 7
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.7.2 2018-08-16 16:57:12

# 既にcomposer.pharがあるので、composerのダウンロードは走らない
$ make setup
make: Nothing to be done for `setup'.

Makefileを解説

.PHONY: setup

.PHONY にターゲット名を羅列しておくと同名のファイル/ディレクトリが存在してもターゲットを実行する。逆にいうと、ここに composer.phar を書くと、 make setup を実行すると毎回ダウンロードが走るようになる。

setup: composer.phar

アプリケーションを使える状態にするためのアレコレを書くターゲット。

  • 依存モジュールのインストール
  • コンフィグファイルをテンプレからコピーしておく

など。依存ファイルとして composer.phar を指定しているので、 composer.phar が無ければ先に make composer.phar が実行される。

composer.phar:

composerを composer.phar としてダウンロードする処理を書いている。 .PHONY に指定していないので composer.phar というファイルが無かったら一度だけ実行される。

Introduction - Composer を参考にした。

IntelliJに "ext-json is missing in composer.json" って怒られた

背景

phpプラグインを有効にしているJetBrains IntelliJで、とあるphp拡張を利用した実装をしたとき下記のような警告がでた

ext-json is missing in composer.json

f:id:pinkumohikan:20181012200158p:plain

「ext-jsonって確か組み込みモジュールだよなー。足りないはずはないし、なんで警告でるねん!」と思って調べたことをまとめておく

ext-json

ext-jsonは、json_encode()やjson_decode()などを提供する、phpでjsonを扱うための拡張モジュールのこと

PHP: JSON - Manual

ドキュメントに記載の通りphp5.2以降はデフォルト組み込みとなっている

ちなみに、自分はphp5.3以降しか触ったこと無いのでjson系関数が拡張モジュールであることは今回調べるまで知らなかった

なぜ警告が出るのか

「エラー調査の基本はグーグル先生に聞く!」

シンプルに ext-json is missing in composer.json というエラーメッセージでググったところ、JetBrainsの記事がヒットしてドンピシャだった

f:id:pinkumohikan:20181012200723p:plain

PhpStorm 2018.2 EAP 182.3458.35 | PhpStorm Blog

The inspection detects the usages of functions, constants, and classes from PHP extensions that are not listed in composer.json.

とある通り、新機能として、php拡張由来の定数や関数などに依存しているときにcomposer.jsonにその依存が明示されていることを検査するらしい

この仕様の根拠としては、composerの下記の考え方によるものらしい

getcomposer.org

Note: It is important to list PHP extensions your project requires. Not all PHP installations are created equal: some may miss extensions you may consider as standard (such as ext-mysqli which is not installed by default in Fedora/CentOS minimal installation systems). Failure to list required PHP extensions may lead to a bad user experience: Composer will install your package without any errors but it will then fail at run-time. The composer show --platform command lists all PHP extensions available on your system. You may use it to help you compile the list of extensions you use and require. Alternatively you may use third party tools to analyze your project for the list of extensions used.

ざっくり意訳すると

  • 依存パッケージを明確にすることって大事だよね
  • phpと一言で言っても実行環境 (configure option) によって利用可能な拡張モジュールが違うこともあるよね
    • アプリを動かすぞ!って段になって、実は○○モジュール足りないよ!ってランタイムエラーになったらキレそうだよね
  • そこんところ、composer.jsonに書いといてくれればアプリの実行前にこれ足りへんでって警告してあげられるよ

実際、composer.jsonに必要な依存を明記しておくと composer install したときに指定されたphp拡張が入ってなければ「○○が足りへんで!」みたいに怒ってくれる

組み込みphp拡張も明示しろっていうのは面倒だなとは思うけど、composerもIntelliJも便利に使わせてもらっているのでそのぐらいの手間は惜しむべきではないか

まとめ

  • composerは、組み込みを含むすべてのphp拡張をcomposer.jsonで明示するべきと考えている
  • 上記のcomposerの考え方に従って JetBrains PhpStorm 2018.2 EAP build (182.3458.35) 以降、composer.jsonで明示されていないphp拡張を利用した場合に警告を出す

ブログをSSL対応させましたッ

時代はフルSSLなので追従しました

staff.hatenablog.com

SSL化してURL Schemeが変わるとソーシャルブックマークが(はてなブックマーク以外は)リセットされるし、Google Search ConsoleやAnalyticsの設定変更なども必要になってvery 面倒なので腰が重く、着手できていませんでした

が、最近Webエンジニアの知人に「え?まだSSL対応させてないの?」と煽られたのでこの度SSL対応させました(白目)

時代的にも、問答無用でフルSSLがスタンダードですしね

SSL対応手順

下記の通りですんなり行けました

help.hatenablog.com

短時間のダウンタイムが発生

SSL対応の操作を行った直後から5秒程度は、SSL証明書エラーで閲覧できない状態になりました

技術的には、blog.hatenablog.com用の証明書が使われていて、 invalid common name とchomeさんお怒りw

まあでも、ダウンタイム5秒程度なら個人用ブログなら「えいや」で変更できるレベルだと思います

証明書はLet's Encrypt

個人的にもLet's Encryptには大変お世話になっています

f:id:pinkumohikan:20181007050909p:plain

f:id:pinkumohikan:20181007051008p:plain

はてなブログぐらいの規模で1サイトごとに証明書発行しているとLet's Encryptのrate limitに掛かりそうですが、どういう回避方法を取っているんでしょうか。

わたし、気になります。

LumenでCache DriverにRedisを使う

Laravelベースなマイクロフレーム LumenでキャッシュドライバとしてRedisを使おうとしていくつかハマったのでメモ (改めて公式ドキュメントみたら大体書いてたw)。

f:id:pinkumohikan:20180928214447p:plain

必要なこと

  1. Redisを使えるようにする
    • Redisサーバの用意 (割愛)
    • .envREDIS_ 系環境変数を設定
  2. パッケージの追加
    • composer require predis/predis
    • composer require illuminate/redis
  3. RedisServiceProviderの登録
    • bootstrap/app.php$app->register(Illuminate\Redis\RedisServiceProvider::class); を記述
  4. Cacheファサードを使えるようにする
    • bootstrap/app.php 内の $app->withFacades() のコメントアウトを解除
  5. Cache driverを redis へ変える
    • .envCACHE_DRIVER=redis を設定

動作確認

Lumenだとデフォルトではtinkerが使えないので php artisan cache:clear コマンドで代用する。

手順

  1. .envREDIS_HOST を適当に書き換えてコマンドを叩いてエラーがでることを確認
    • Cache driverがRedisになっていることの確認
  2. ↑を正規の情報に戻した上で、コマンドを叩いてみてエラーが出ないことを確認
    • Redisへの疎通とAuthが通っていること (あるいはno-passwordであること) の確認

しっかりやるなら、キャッシュをsetしてgetするようなエンドポイントを用意して、http経由で叩くのが良さそう。

おまけ

参考にした資料

#isucon8 予選参加して、無事爆死してきたンゴ

Iikanjini Speed Up Contestへ人生2回目の参加をして、無事爆死してきました

▼ チームメンバー f:id:pinkumohikan:20180916201706p:plain

ISUCONってなんやねん

ISUCON = いい感じにスピードアップさせるコンテストです!!!

(インフラ 〜 アプリ全域に渡るパフォーマンスチューニングのコンテストで、お題となるアプリを極限までパフォーマンス改善していくエクストリームスポーツなコンテスト)

isucon.net

戦いをkwsk

チームメンバーの @zoe が既にブログ書いてるのでそちらを参照下さい

blog.zoe.tools

▼ 草 f:id:pinkumohikan:20180916200740p:plain

今回よかったこと

脱 雰囲気profiling

2015年ぐらいに初めて参加したときは「この辺なんとなく遅そうだからここから直していこう」みたいな雰囲気profilingで挑んでいた

今回はdstat, mysqldumpslow, kataribe, pprof, siegeなどの計測 / プロファイリングツールを使って きちんと計測した上で 明確な根拠をもってここを改善しよう、と取り組めたのはとても良かった

▼ dstatはcpu, disk, network i/oの状況をひと目で確認できて便利 f:id:pinkumohikan:20180916201135p:plain

スコアにもある程度手応えがあった

前回までは予選通過ラインに到底及ばないほど悲しいスコアで反省しかなかったが、今回は予選通過ラインに及ばなかったものの、このまま頑張って練習を続ければ予選通過の可能性を感じられるようになった

▼ 初期スコア f:id:pinkumohikan:20180916200941p:plain

▼ 一瞬だけ暫定5位に躍り出たこともあった f:id:pinkumohikan:20180916202527p:plain

自衛隊体操

なんと言ってもこれ!

来年も頑張ろーん

名前をつけてgit stashする

f:id:pinkumohikan:20180912233529p:plain

gitには、今作業中なんだけどすぐに別の作業したくなって一時的に変更を退避させたいんだよねーという時に使える git stash という便利コマンドがあります。

gitを使い始めて6年ぐらいの先日、stashに名前をつけられることを知りました (厳密には名前じゃあくてメッセージをつける機能ですけど、名前みたいなもんなんで名前って呼びます)。

git stashの構文

git stash save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [-u|--include-untracked] [-a|--all] [<message>]

使ってみる

前提

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

何も考えずstashすると...

$ git stash
Savd working directory and index state WIP on master: xxxxxxx いい感じの直前コミットのメッセージ

$ git status
On branch master
Your branch is up-to-date with 'origin/maser'.

nothing to commit, working tree clean

と変更の退避はできるものの、stashには「branch名」と「直前コミットのメッセージ」しか表示されないので時間が空いてからstash listを眺めるとそのstashが何を示すものかもうのわかりません。

$ git stash list
stash@{0}: WIP on master: xxxxxxx いい感じの直前コミットのメッセージ

僕もそうやっていくつものstashで 「良く分からんからgit stash pop😲 -> ガリガリにコンフリクト😱 -> これもう要らんやつや...😂」 みたいな悲劇を起こしました。

だがしかし! stashに名前をつけることが出来ると、ほらこの通り!

$ git stash save "俺が考えた最強のREADME"
Saved working directory and index state On master: 俺が考えた最強のREADME
$ git stash list
stash@{0}: On master: 俺が考えた最強のREADME

そのstashが「俺が考えた最強のREADME (wip)」であることがひと目で分かります!便利!

参考にした資料

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

症状

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 データベースが存在しない