patorashのブログ

方向性はまだない

CircleCI 2.0でparallel_testsとknapsack_proを使って爆速化

前回は、こんな記事を書きました。

patorash.hatenablog.com

チーム内で、「多少は速くなりましたよ」という報告をしていたところ、「でもknapsack_proを使ってテスト時間の均等化したほうがまだ高速だね」という話に。knapsack_proはお試しで使ったことはありましたが、確かに実行時間の均等化ができて、速くなります。

knapsack_proとparallel_testsの合わせ技ができたらいいのになぁ〜と思っていたら、なんとknapsack_proのサイトに連携方法が書いてありました。これは…いけるのではないか…。

knapsack_proとは?

knapsack_proはテストの実行時間の最適化を行える有料サービスです。有料サービスですが、14日間のお試し期間があります。このお試し期間がユニークで、knapsack_proを使った日をカウントして14日間になってます。実験して一旦やめて、数日後に実験しても2日しかカウントされません。素晴らしい配慮です。

Speed up your tests with optimal test suite parallelisation

knapsack_proがどういうふうにしてテストの実行時間を最適化しているかというと、

  1. ファイル毎のテストの実行時間をknapsack_proに保存
  2. テストの実行時間が均等になるようにテストファイルを割り振って配布

ということをしているんだと思います。

テストの実行方式について

テストの実行方式は、regularqueueがあります。regularは、一度にテストファイルのリストを割り振って実行させます。それに対してqueueは、テストが終わったノードがknapsack_proに対して次のテストを取得しにいく方式です。regularは、それぞれに一気に仕事を割り振るイメージ、queueは手が空いてる人に小出しに仕事を割り振るようなイメージです。

regularだと、早くテストが終わってもそのノードのテストはもう終わりなので、ノード毎にテストの実行時間がばらつく可能性があります(それでもランダムに割り振るよりはだいぶマシ)。queueだと、通信は都度発生するものの、テストファイルがなくなるまで割り振り続けるので、暇になるノードが発生する可能性が低くなります。queueのほうがオススメです。

knapsack_proの導入

knapsack_pro側

まずは knapsack_proにアカウント登録してください。 その後、プロジェクトを登録し、APIトークンを取得してください。

CircleCI側

knapsack_proのAPIトークンをCircleCIのプロジェクトの環境変数に登録します。環境変数名は、KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPECです。

Railsプロジェクト側

基本的には、githubに書いてある通りにやっていきます。

github.com

gemのインストール

Gemfileに追記します。

group :development, :test do
  gem 'knapsack_pro'
end

そして、bundle installを行います。

spec/spec_helper.rbに設定を追加

CircleCIでknapsack_proを使う場合、テスト結果の集計などを行うために色々やります。形式はrspec_junit_formatterを使うのを想定しています。parallel_testsと組み合わせて使うので、テスト結果のファイルを上書きしてしまわないようにするため、rspec#{ENV['TEST_ENV_NUMBER']}.xmlというふうにしてプロセス毎に分けて保存するようにしてみました。

rspec_helper.rbの先頭に追記します。

CircleCI 2.0だと、環境変数 CIRCLE_TEST_REPORTS は存在しないため、自分で定義する必要があるところに注意してください。

# @see https://github.com/KnapsackPro/rails-app-with-knapsack_pro/blob/master/spec/spec_helper.rb
require 'knapsack_pro'

KnapsackPro::Hooks::Queue.before_queue do |queue_id|
  print '-'*20
  print 'Before Queue Hook - run before test suite'
  print '-'*20
end

TMP_RSPEC_XML_REPORT = "tmp/test-reports/rspec#{ENV['TEST_ENV_NUMBER']}.xml"
# move results to FINAL_RSPEC_XML_REPORT so the results won't accumulate with duplicated xml tags in TMP_RSPEC_XML_REPORT
FINAL_RSPEC_XML_REPORT = "tmp/test-reports/rspec_final_results#{ENV['TEST_ENV_NUMBER']}.xml"

KnapsackPro::Hooks::Queue.after_subset_queue do |queue_id, subset_queue_id|
  if File.exist?(TMP_RSPEC_XML_REPORT)
    FileUtils.mv(TMP_RSPEC_XML_REPORT, FINAL_RSPEC_XML_REPORT)
  end
end

KnapsackPro::Hooks::Queue.after_queue do |queue_id|
  # Metadata collection
  # https://circleci.com/docs/1.0/test-metadata/#metadata-collection-in-custom-test-steps
  if File.exist?(FINAL_RSPEC_XML_REPORT) && ENV['CIRCLE_TEST_REPORTS']
    FileUtils.mkdir_p(ENV['CIRCLE_TEST_REPORTS']) unless FileTest.exist?(ENV['CIRCLE_TEST_REPORTS'])
    FileUtils.cp(FINAL_RSPEC_XML_REPORT, "#{ENV['CIRCLE_TEST_REPORTS']}/rspec#{ENV['TEST_ENV_NUMBER']}.xml")
  end

  print '-'*20
  print 'After Queue Hook - run after test suite'
  print '-'*20
end

KnapsackPro::Adapters::RSpecAdapter.bind

# 略
bin/parallel_tests を作成

これは以下のURLに書いてあるのとほぼ同じです。

https://github.com/KnapsackPro/knapsack_pro-ruby#how-to-run-knapsack_pro-with-parallel_tests-gem

やっていることは、

  1. KNAPSACK_PRO_CI_NODE_TOTALをparallel_testsの並列数を掛け算して上書き
  2. PARALLEL_TESTS_CONCURRENCY_INDEXにparallel_testsの並列化の何番目なのかを設定
  3. KNAPSACK_PRO_CI_NODE_INDEXに、knapsack_proの並列化の何番目なのかを上書き
  4. ちゃんと並列設定できているかをechoで表示
  5. knapsack_pro経由でrspecを実行

となります。

#!/bin/bash
# This file should be in bin/parallel_tests

# updates CI node total based on parallel_tests concurrency
KNAPSACK_PRO_CI_NODE_TOTAL=$(( $PARALLEL_TESTS_CONCURRENCY * $KNAPSACK_PRO_CI_NODE_TOTAL ))

if [ "$TEST_ENV_NUMBER" == "" ]; then
  PARALLEL_TESTS_CONCURRENCY_INDEX=0
else
  PARALLEL_TESTS_CONCURRENCY_INDEX=$(( $TEST_ENV_NUMBER - 1 ))
fi

KNAPSACK_PRO_CI_NODE_INDEX=$(( $PARALLEL_TESTS_CONCURRENCY_INDEX + ($PARALLEL_TESTS_CONCURRENCY * $KNAPSACK_PRO_CI_NODE_INDEX) ))

# logs info about ENVs to ensure everything works
echo KNAPSACK_PRO_CI_NODE_TOTAL=$KNAPSACK_PRO_CI_NODE_TOTAL KNAPSACK_PRO_CI_NODE_INDEX=$KNAPSACK_PRO_CI_NODE_INDEX PARALLEL_TESTS_CONCURRENCY=$PARALLEL_TESTS_CONCURRENCY

# you can customize your knapsack_pro command here to use regular or queue mode
#bundle exec rake "knapsack_pro:queue:rspec[--format progress --format RspecJunitFormatter --out tmp/test-reports/rspec${TEST_ENV_NUMBER}.xml]"
bundle exec rake "knapsack_pro:rspec[--format progress --format RspecJunitFormatter --out tmp/test-reports/rspec${TEST_ENV_NUMBER}.xml]"

違うところは、最後の2行と、そこで渡しているオプションです。

queueモードをコメントアウトして、regularモードでテストを行なっています。これは、初回はregularモードでテストを行い、テスト実行時間をknapsack_pro側に教える必要があるからです。ガイドに書いてありました(RSpecとCircleCIを選択すると最後の方に書いてあります)

docs.knapsackpro.com

まずはregularモードでテストが通るようになったのを確認した後、queueモードに移行しましょう。

.circleci/config.ymlの修正

前回の.circleci/config.ymlとの差分だけ載せます。

- run:
    name: Run rspec in parallel
    command: |
        KNAPSACK_PRO_CI_NODE_TOTAL=$CIRCLE_NODE_TOTAL \
        KNAPSACK_PRO_CI_NODE_INDEX=$CIRCLE_NODE_INDEX \
        KNAPSACK_PRO_LOG_LEVEL=info \
        bundle exec parallel_test -n $PARALLEL_TESTS_CONCURRENCY -e './bin/parallel_tests'

./bin/parallel_testsで使う環境変数を設定しながら、parallel_testでプロセス毎に./bin/parallel_testsを実行させています。環境変数は、CircleCIの全ノード数であるCIRCLE_NODE_TOTALと、何番目のノードなのかを示すCIRCLE_NODE_TOTALをそのまま渡しています。

CircleCIで実行

私の環境では、CircleCIのコンテナが5つ、parallel_testsの並列化がCPU2つなので2つに設定しているので、10並列になれば成功です。

では結果を見てみましょう!

f:id:patorash:20170922130705p:plain f:id:patorash:20170922130718p:plain f:id:patorash:20170922130730p:plain f:id:patorash:20170922130744p:plain f:id:patorash:20170922130750p:plain

KNAPSACK_PRO_CI_NODE_TOTALが10に、KNAPSACK_PRO_CI_NODE_INDEXが0,1,2,...,9まで設定されています。成功です!

regularモードでテストが通ったら、bin/parallel_testsを修正してqueueモードにして再びテストを実行しましょう。

# you can customize your knapsack_pro command here to use regular or queue mode
bundle exec rake "knapsack_pro:queue:rspec[--format progress --format RspecJunitFormatter --out tmp/test-reports/rspec${TEST_ENV_NUMBER}.xml]"
# bundle exec rake "knapsack_pro:rspec[--format progress --format RspecJunitFormatter --out tmp/test-reports/rspec${TEST_ENV_NUMBER}.xml]"

Before

以前は18分かかると書いていましたが、docker imageの最適化など行なったのと、CircleCI側でのテストの均等化がまぁまぁうまくいくようになっていたので、parallel_testsとknapsack_proを導入する前でもテストの結果は14分台になっていました。これはかなり調子がいいときの結果です。

f:id:patorash:20170922131731p:plain

では、parallel_testsとknapsack_proを使って、queueモードで実行したらどうなるか…。

After

f:id:patorash:20170922132055p:plain

な、なんと7分台で終わってしまいました!!しかもテスト時間の最適化具合がすごい!!ほぼ横一線です。parallel_testsすごい!knapsack_proすごい!ほぼ倍の速さでテストが終わりました!

気づき

CircleCIのコンテナ数を増やすにしても上限がありますし、無闇にコンテナ数を増やすよりは、knapsack_proを使ってテスト時間の最適化を行なったほうがコストパフォーマンス的にいいかもしれません。思った以上に効果が出てよかったです!knapsack_proはまだお試し期間で利用中ですが、契約したほうがいいなと実感できました。

追記(2017-10-02)

正式に弊社で採用することが決定しました。よかった!

Circle CI 2.0でparallel_testsを使ってお金をかけずに高速化する

CircleCI 2.0でだいぶテストが速くなったものの、1回のテストが20分くらいかかっているので、もっと速くしたいなぁと思っていました。お金を払えば並列化は簡単にできるのですが、CircleCIの並列化にも今のところ上限があり、1度のテストで16コンテナまでしか使えません(例え20コンテナ契約していたとしても)。しかし、CircleCIの1コンテナには、2CPU 4GBのメモリがあります(デフォルトでは)。

そこで目をつけたのが、gem parallel_testsです。

parallel_testsとは?

github.com

parallel_testsは、マシンにあるCPUの数だけテストのプロセスを起動して並列実行するgemです。Hyper Threadingが有効な場合は、論理コア数で数えるので、CPU数*2のプロセスが起動することになります。以前はCIを使わずローカル環境でrspecを流していたのでよく使っていたのですが、最近はめっきり使っていませんでした。

ライバルとしては、test-queueというgemがあります。test-queueは私が既存のプロジェクトで動かそうとしたときがうまくいかなかったので、あんまり深追いしていませんのでここでは割愛します。

インストール

Gemfileに追加します。

group :development, :test do
  gem 'parallel_tests'
end

そしてbundle installしときます。

設定

parallel_testsは1台のマシンで並列でテストを実行するため、それぞれが他のテストの影響を受けないようにするために、複数のデータベース、E2Eテストでは複数のブラウザを起動する必要があります。 parallel_tests関連の処理を起動すると、環境変数 TEST_ENV_NUMBERにそれぞれ番号が降られた状態でプロセスが起動するので、それを使って設定していきます。

config/database.yml

これはローカルでテストを実行するための設定になります。CircleCIで動かす場合は、database.circleci.ymlなどを作っている場合があると思うので、そちらにも設定を反映しておいてください。

test:
  database: project_name_test<%= ENV['TEST_ENV_NUMBER'] %>
spec/rails_helper.rb

私のやってるプロジェクトでは、E2EテストでヘッドレスブラウザにPhantomJSを、JavaScriptドライバにpoltergeistを利用しています。poltergeistを起動するときに、各プロセス毎にポートを分けたいので、設定に追記します。

Capybara.javascript_driver = :poltergeist
Capybara.register_driver :poltergeist do |app|
  Capybara::Poltergeist::Driver.new(app,
                                    # 並列数だけportを分ける
                                    port: 51674 + ENV['TEST_ENV_NUMBER'].to_i
  )
end
.rspec_parallel

RSpecを起動するときのオプションは.rspecに書くことができますが、parallel_testsを使う場合は、.rspec_parallelを利用します。テストの結果を取得するために--outの設定をそれぞれのプロセス毎に出力するようにします。

--profile 10
--format progress
--format RspecJunitFormatter
--out tmp/rspec<%= ENV['TEST_ENV_NUMBER'] %>.xml

ローカルで実行

まずはローカルで実行してみましょう。テスト実行時間が長いようだったら、ここは飛ばしてもらっても構いません。

データベースの準備

データベースの作成

以下を実行すると、CPUの数だけデータベースが作られます。

bundle exec rake parallel:create

CPUを使い切ると、ローカルで他の作業が全くできないくらい遅くなるので、敢えて数を減らすこともできます。例えば4CPUあるけれど、2CPUだけ使いたい場合は、以下のようにします。

bundle exec rake parallel:create[2]

以降のrake taskは、引数に並列数を設定可能なので、変えたい場合は上記と同じように記述してください。

schemaのロード

config/application.rbで、config.active_record.schema_format = :rubyならば、以下を。

bundle exe rake parallel:load_schema

config.active_record.schema_format = :sqlならば、以下を。

bundle exec rake parallel:load_structure

もし、schema.rbやstructure.sqlが存在しない場合は、最初にbundle exec rake db:migrateを実行してそれらのファイルを生成しておく必要があります。

やり直したい場合

マイグレーションしてテーブル定義が変わった場合などは、rake parallel:dropして上記を繰り返せばいいのですが、面倒だと思います。それらを一括でやってくれるrake taskがあります。

bundle exec rake parallel:prepare

rspecを実行する

以下を実行すれば、CPUの数だけ並列で動きます。

bundle exec rake parallel:spec

うまくいけば、テスト実行時間が半分〜50%程度スピードアップするかと思います(絶対ではありません)。

CircleCI 2.0でparallel_testsを動かす

さて、上記を踏まえて、CircleCI 2.0でparallel_testsを動かしてみましょう。実際は動かすのにめちゃくちゃハマったので、みなさんが二の轍を踏まないようにするためにハマったポイントを書いておきます。

ハマりポイント

並列数を省略すると動かない!

今までの説明では、「CPU数だけ並列実行します」と書いていました。放っておいたら、Dockerコンテナに割り当てられているCPU数で並列実行してくれるだろう、と安易に考えていたのですが、いざ実行してみると36並列で実行されました。それもテストのファイルが分割されて36個になっていたからで、もしかしたらファイル数がそれ以上あったら、もっと分割されるかもしれません。これは恐らくホストOSのCPU数を取得してしまっているのだと思います。そして、そんなに並列にした状態でrake parallel:createを実行すると、postgresqlのDockerコンテナが落ちます。結果、テストは実行できません。

よって、CircleCI 2.0でparallel_testsを動かす場合は、並列数を指定しましょう。

parallel:prepareは使えない

ローカルの説明でparallel:load_structureを使うように書いていたので、はまらないかもしれません。最初、DBのセットアップに以下の順番で処理を呼んでいました。

  1. rake parallel:create
  2. rake db:migrate
  3. rake parallel:prepare

これは、structure.sqlをgitで管理していないため、一旦db:migrateで生成した後、parallel:prepareでDBを再生成してschemaをロードしようとしたのですが、db:migrateで使用したDBが使用中と言われてしまい、dropできずに落ちました。なので、load_structureを使うように修正しました。

rake parallel:specは使えない

CircleCI自身の並列化によって、specファイルのリストを引数で取得することができますが、rake parallel:specは、テスト対象がパターンでしか認識できません(spec/modelsのようなディレクトリ指定とか)。そのため、引数にspecファイルのリストを渡しても無視して全部のテストを実行します。

これは、直でparallel_rspecというコマンドを呼ぶようにすれば、それはspecファイルのリストを受け取ることができるので、それを呼び出します。

ハマりポイントを回避した.circleci/config.yml(の一部)

以上を踏まえて、書き直したのが以下のようになります。config.ymlを全部載せるのは長いので、要点部分だけ載せます。

肝は、PARALLEL_TESTS_CONCURRENCYという環境変数を定義することです。 また、CircleCIのテストファイルの分割をtimingからfilesizeに変更しました。これは、timingでテスト実行結果を保存する処理が、parallel_testsで並列化されたことで結果がお互いに上書きしているのか、何度実行してもテスト実行時間の均等化がほとんど行われなかったからです。filesizeのほうがマシな結果になるんじゃないか?と思ったら、そうなったので、こうしています。

version: 2
jobs:
  build:
    working_directory: ~/project_name
    docker:
      - image: circleci/ruby:2.4.2-node
        environment:
          RAILS_ENV: test
          TZ: "/usr/share/zoneinfo/Asia/Tokyo"
          CIRCLE_TEST_REPORTS: /tmp/test-results
          PARALLEL_TESTS_CONCURRENCY: 2
      # Postgresql 9.5 + PostGIS2.3
      - image: circleci/postgres:9.5-alpine-postgis-ram
        environment:
          TZ: "/usr/share/zoneinfo/Asia/Tokyo"

    parallelism: 5
    steps:
      - checkout
      - run: cp config/{database_circleci,database}.yml

      # gemやnode_modulesをキャッシュする。
      # 今回は省略

      # Database setup
      - run:
          name: Database setup
          command: |
            bundle exec rake parallel:create[$PARALLEL_TESTS_CONCURRENCY]
            bundle exec rake db:migrate
            bundle exec rake parallel:load_structure[$PARALLEL_TESTS_CONCURRENCY]

      - run:
          name: Run rspec in parallel
          command: |
            bin/parallel_rspec -n $PARALLEL_TESTS_CONCURRENCY $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=filesize)
       # 以降は省略

実行結果

18〜23分くらいかかっていたテストが、14分くらいになりました。お金をかけずに20%以上の高速化ができました。ただ、テストファイルの配布状況次第では、処理の重いファイルが1つのコンテナに集中すると、遅くなる可能性もありますのでご注意ください。

気づき

parallel_tests、まだまだ使えるなぁと思えました。とはいえ、結構ハマったので、それは大変でしたが、テストが速くなると、チーム全体の時間の節約ができるので、やってみる価値はあると思います。

ちなみに、お金をかけてさらにテストを速くする方法があります。knapsack_proを使う方法です。CircleCI 2.0で、parallel_testsとknapsack_proを合わせて使ったら、18分かかっていたテストが8分になりました。これについては別の記事で導入方法を書こうと思います。お楽しみに。

oh-my-fishからfishermanに乗り換えた

Macではshellにfishを使っているのですが、fishをさらに便利にするためにoh-my-fishを使っていました。ansibleでもoh-my-fishを入れるようにしていたのですが、ansibleの記事を公開後に、ひむひむ氏からfishermanイイよーというアドバイスをもらいました。

fishermanとは?

fishermanは、fishのプラグイン管理ツールです。

github.com

oh-my-fishとの比較

shellの起動が速い

oh-my-fishはプラグイン管理もできますが、本家にもfishshell frameworkと書いてある通りで、専用のomfコマンドや設定などを起動時にロードするため、起動がややモッサリします。

それに比べ、fishermanは~/.config/fish/functions/fisherman.fishに本体を持ちます。functions以下はautoload functionが使われるので、

  1. shell起動時にはfisherコマンドは定義されない
  2. fisherコマンドを打つ
  3. コマンドが見つからないのでfunctions以下の関数をチェックしにいく
  4. ここで初めてfisher関数がロードされて実行される

という手順になるので、shellの起動が高速です。

oh-my-fishのプラグインが利用できる

fishermanはoh-my-fishのプラグインの管理もできます。これはつまり…「あれ…oh-my-fish必要ないんじゃ…?」という気持ちになります。実際、必要なさそうです。

fishermanを使ってみる

インストール

githubのREADMEに従ってやってみましょう。

curl -Lo ~/.config/fish/functions/fisher.fish --create-dirs https://git.io/fisher

インストールはこれで終わりです。

プラグインのインストール

fishermanを使ってプラグインのインストールをするのは簡単です。fishermanのコマンドは、fisherになります。manはどこいった!

fisher [プラグイン名]

と入力するだけです。プラグイン以外にも、gistやローカルの野良プラグインなども、fisherコマンドでインストール可能です。

利用可能なプラグインの検索
fisher ls-remote
インストール済みのプラグインの一覧
fisher ls
プラグインの削除
fisher rm [プラグイン名]
プラグインの全削除

fisher lsの結果をfisher rmにパイプで渡すだけです。

fisher ls | fisher rm

fishermanのアンインストール方法

fisherman自身の削除もシンプルです。

fisher self-uninstall

fishermanでoh-my-fishのthemeをインストールする

fishermanをインストールしただけだと、素のfishなので、見た目が寂しいです。そこで、oh-my-fishのdefualtのテーマをインストールしてみました。ググったら、普通に入れただけではダメという情報を見つけました。git_utilプラグインを入れておかなければならないそうです。

fisher git_util
fisher omf/theme-default

これで、見た目もoh-my-fishを使ってた頃と同じになりました。

気付き

MacでAnsibleを使う記事を投稿したことで、いいアドバイスをもらうことができました。アウトプットしたからこそアドバイスがもらえたわけで、何も書かなかったらfishermanのことをよく知らないままだったので、書いてよかったなと思います。ひむひむ氏のおかげで、さらに快適なfish shell生活が送れそうです。

oh-my-fishの削除

そうそう、oh-my-fishも忘れないように削除しておきましょう。

omf destroy

参考URL

oh-my-fish は古い!fisherman で置き換えられる。 | Futurismo

Macの環境構築を自動化してOS再インストールに備える

会社で新しいMacBookProが支給されたので、開発環境のセットアップを行いました。その際に、何を入れたらいいかなどをチェックするのが大変だし、毎度毎度手間がかかっても面倒です。やることはだいたい決まっているのに、セットアップに1日か2日費やすことになるのは勿体ないなと思いました。また、OSを更新するときにはクリーンインストールしたいと思いまして(High Sierraも出るし)、Macの環境構築の自動化に乗り出すことを決意しました。

これができたら、他のメンバーの環境構築も自動化できるし、githubに構築スクリプトをアップしといたらPull Reqがくるかもしれないしなぁという算段です。

開発環境の自動化は、Ansibleを使うことにしました。Ansibleはサーバの構築の自動化ツールとしてよく使われていますが、Macなどの環境構築でもよく使われていて実績がインターネット上にたくさんあります。なお、実験台となったPCはスペック的に厳しくなってきたMBA11インチ 64GBのマシンです。

要件を決める

自動化とやみくもにいっても、自動化するのに時間がかかってしまうので、まず要件を決めます。

最低要件

まずはこれだけは最低でも行いたいという要件を決めてから作業しました。

  • homebrew経由で必要なライブラリなどのインストールが完了していること
  • homebrew caskでの必要なソフトウェアのインストールが完了していること
  • Ruby, Node.jsが使えるようになっていること

すぐに開発に取り掛かれるという意味では、ここまでの自動化でも十分かなと思います。

個人的要件

これらは、私がどのMacでもやっていることなので、ついでに自動化できたらしたいなぁ、という程度。だけれど、いつも面倒だったので是が非でも自動化したい気持ち。

  • shellがfishになっていること
  • oh-my-fishをインストールすること
  • 便利なエイリアスコマンドの設定が終わっていること
  • gitの設定が終わっていること
  • pecoとそれを使った関数の設定が終わっていること
  • ghqが使えること

fishは便利なshellで、oh-my-fishはコンソール画面を便利にカスタマイズしたり、fishのプラグインを簡単に入れられるようにするものです。 便利なエイリアスコマンドは、例えばbundle installをbiだけで済むようにするショートカット的なやつの集合です。

pecoは、標準出力結果を選択して次の関数に渡せるやつで、pecoを絡めて独自に関数を作ると超便利です。例えば、pidの一覧から選択してプロセスを終了させるpeco_killコマンドがよく使われるものとして有名です。

ghqは、githubリポジトリのcloneを決められたルールで行ってくれるものです。いろんなプロジェクトのソースコードがどこにあるかわからなくなったりすることがなくなり、とても便利です。

あとで追加した要件

Ansibleを使っている途中で、OSXのデフォルトの定義の変更も手軽に行えるというのを見つけたので、それらも自動化するようにしました。

  • Finderで隠しファイルを表示できるようにすること
  • Finderで拡張子を表示できるようにすること
  • Dockは自動的に隠すようにすること
  • Safariデバッグツールを有効にすること

その他、まだいろいろやりたいことはあるのですが、やり方がわからないものとかがあったので、まだやってません。

自動化の準備

XCodeのインストール

Homebrewを入れるために、XCodeのインストール。下記のコマンドを入力するだけでXCodeのCommand Line Toolのインストールが終わりました。

sudo xcodebuild -license

Homebrewのインストール

homebrewのインストールを行います。

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Ansibleのインストール

homebrew経由でpythonとansibleを入れておきます。

brew install python
brew install ansible

Playbookを作っていく

Ansible、というか構成管理ツールは冪等性(べきとうせい)が重要になってきます。冪等性とは、何度同じ命令を実行しても結果が同じになることです。冪等性を確保するためにPlaybookを作るのに試行錯誤したので、最後にgithubリポジトリへのリンクを貼っておくので、細かいことは書きません。ざっくりと、やったことと、Ansibleで便利だったモジュールについて書いていきます。

inventoryの作成

inventoryとは、作業対象のホストの情報になります。サーバの管理の場合、web, dbなどのグループ分けに対応していて、グループ毎に設定処理を分けたりすることができます。今回は、作業対象はMac自身なので、hostsファイルに以下のように設定します。

[localhost]
127.0.0.1

Playbookを書く

次に、Playbookを作ります。今回は、localhost.ymlというplaybookを作り、taskをrole毎に分けて指定していく方式にします。タスクの量が大したことがないとか、わざわざ分ける方がわかりにくい場合は、1つのファイルに全て書いてもいいと思います。

---
- hosts: localhost
  connection: local
  gather_facts: no
  roles:
    # 略

このPlaybookを実行するには、以下を実行します。

ansible-playbook -i hosts localhost.yml --ask-become-pass

--ask-become-passオプションは、playbook内でbecomeを使わなければ、別に渡す必要はありません。

roles以下は定義したroleを追記していきます。roleはrolesディレクトリ以下に、role名で作成します。roles/[role_name]/tasks/main.ymlに、そのroleのタスクを定義します。

Role: homebrew_tapの作成

homebrew_tapモジュールを使い、brew tapの自動化を行います。今回はcaskとversionsを使うことになったので2つです。with_itemsを使うと、処理を繰り返すことができます。{{ item }}に配列の要素が1つずつ入って処理されます。

---
- name: homebrewのtapリポジトリの追加
  homebrew_tap:
      tap="{{ item }}"
      state=present
  with_items:
    - caskroom/cask
    - caskroom/versions

Role: homebrewの作成

次に、homebrewモジュールを使って、brew installの自動化を行います。

まずはhomebrewのアップデート。

---
- name: homebrewのアップデート
  homebrew:
    update_homebrew=yes

次に、brewパッケージのインストール。自分がよく使うものを入れます。ここは好きに変えてください。自分はRailsアプリケーションの開発とHerokuをプラットフォームとしてよく使うので、それらに関連するもののみ、とりあえず入れておきました。

Railsアプリ開発者として、自分的に一番肝なのは、imagemagick@6を入れるところです。これは、Rubyimagemagickを扱うgem rmagickがimagemagickのバージョン7に対応していないためです。imagemagickのみの指定だと7系がインストールされます。2017年9月現在、それだとrmagickを使っているプロジェクトでbindle installがコケてしまいます。ちなみに、imagemagick@6を入れるために、tapのところでcaskroom/versionsを指定していました。

- name: brewパッケージのインストール
  homebrew:
    name="{{ item.name }}"
    state="{{ item.state | default('latest') }}"
    install_options="{{
      item.install_options | default() | join(',')
      if item.install_options is not string
      else item.install_options }}"
  with_items:
    - { name: ghq }
    - { name: git }
    - { name: heroku }
    - { name: imagemagick@6 }
    - { name: libxml2}
    - { name: openssl }
    - { name: peco }
    - { name: phantomjs }
    - { name: postgresql }
    - { name: readline }
    - { name: wget }
    - { name: yarn }

そして、brewパッケージのリンクの張り替えを強制的に行っておきます。これをしていないと、imagemagick@6などが、システム的に認識されません。

- name: brewパッケージのリンクの貼り替え
  homebrew:
    name="{{ item.name }}"
    state="{{ item.state | default('latest') }}"
    install_options="{{
      item.install_options | default() | join(',')
      if item.install_options is not string
      else item.install_options }}"
  with_items:
    - { name: imagemagick@6, state: linked, install_options: force}
    - { name: openssl, state: linked, install_options: force}
  changed_when: False

Role: homebrew_caskの作成

次に、homebrew_caskモジュールを使って、アプリをインストールしていきます。caskを使うと、Google ChromeMicrosoft Officeなどのインストールをコマンドラインで行うことができます。/Applications/直下にインストールされます。

ここでの肝は、become: yesを指定しておくことです。become: yesをすると、sudoコマンドで処理を実行してくれます。brew cask installを行うと、かなりの頻度でパスワードを聞かれるので、そこで自動化が止まるのを防ぎます。

failed_when: Falseを指定するのは、インストールが失敗しても無視するためです。あえて無視させてます。インストールが失敗することがあるのか?という疑問が湧くと思いますが、例えばansibleを実行する前に既にChromeをインストールしていた場合、このroleを流すとChromeのインストールが失敗します(既に/Applications/Chrome.appがあるから)。GUIアプリケーションは冪等性の担保がしづらいので、まぁなければ入れるくらいでいいやというノリで管理します。

---
- name: caskパッケージのインストール
  homebrew_cask:
    name="{{ item.name }}"
    state="{{ item.state | default('installed')}}"
  with_items:
    - { name: atom }
    - { name: docker }
    - { name: docker-toolbox }
    - { name: firefox-esr }
    - { name: iterm2 }
    - { name: google-chrome }
    - { name: java }
    - { name: kitematic }
    - { name: microsoft-office }
    - { name: rubymine }
    - { name: skitch }
    - { name: thunderbird }
    - { name: vagrant }
    - { name: virtualbox}
    - { name: visual-studio-code }
  become: yes
  failed_when: False

Role: anyenvの作成

anyenvは、Rubyのバージョン管理のrbenvやNodeのバージョン管理のndenvなど、様々なプログラミング言語の**envを管理するためのツールです。インストールとPATHを通すのを自動化します。

ここでの肝は、lineinfileモジュールを使うところです。lineinfileモジュールは、指定された文字列を追加または置換することができます。

lineinfile - Ensure a particular line is in a file, or replace an existing line using a back-referenced regular expression. — Ansible Documentation

置換する場合は、正規表現で対象行を指定します。追記ならば、特に指定なくてもいいです。実行するたびに毎度毎度追記されるんじゃないか?と思っていたのですが、そんなことはありませんでした。

---
- name: anyenvをcloneする
  git:
    repo: 'https://github.com/riywo/anyenv.git'
    dest: ~/.anyenv

- name: anyenvへのPATHを通す
  lineinfile:
    dest: ~/.bash_profile
    line: "{{ item }}"
  with_items:
    - export PATH="$HOME/.anyenv/bin:$PATH"
    - eval "$(anyenv init -)"

Role: rbenvの作成

anyenv経由でrbenvを入れます。shellモジュールを使うと毎回実行されてしまいますが、createsオプションを渡すと、既にファイルが存在すれば実行しなくなります。今回は既にrbenvディレクトリが作られていたら実行しないようにしています。Rubyのインストールのところも同様です。

また、Rubyのインストールのところでは、with_itemsにruby_versionsという変数を渡しています。そして、whenにruby_versions is definedという条件を渡しています。これは、インストールするRubyのバージョンを色々変えられるようにです。特に指定がない場合はこの処理自体が無視されます。

最後に、rbenv rehashを行います。これは必ずchangedになってしまうので、changed_when: Falseを指定しています。

---
- name: rbenvのインストール
  shell: anyenv install rbenv
  args:
    creates: ~/.anyenv/envs/rbenv/bin/rbenv

- name: Rubyのインストール
  shell: rbenv install {{ version }}
  args:
    creates: ~/.anyenv/envs/rbenv/versions/{{ version }}
  with_items: "{{ ruby_versions }}"
  loop_control:
    loop_var: version
  when: ruby_versions is defined

- name: rbenv rehash
  shell: rbenv rehash
  changed_when: False

Role: ndenvの作成

こちらもrbenvと同様です。

---
- name: ndenvのインストール
  shell: anyenv install ndenv
  args:
    creates: ~/.anyenv/envs/ndenv/bin/ndenv

- name: Nodeのインストール
  shell: ndenv install {{ version }}
  args:
    creates: ~/.anyenv/envs/ndenv/versions/{{ version }}
  with_items: "{{ node_versions }}"
  loop_control:
    loop_var: version
  when: node_versions is defined

- name: ndenv rehash
  shell: ndenv rehash
  changed_when: False

Role: osx_defaultsの作成

osx_defaultsモジュールを使うと、Mac OSXの設定の変更が容易にできるということだったのでやってみました。

osx_defaults - osx_defaults allows users to read, write, and delete Mac OS X user defaults from Ansible — Ansible Documentation

まぁGUIから変えても大した時間かからなかったりもしますが、Finderの設定は毎回面倒なので自動化しとくと楽ですね。どうせ拡張子と隠しファイルは見れないと開発者的に仕事が捗らないし。

---
- name: Safariのデバッグメニューを有効化
  osx_defaults:
    domain: com.apple.Safari
    key: IncludeInternalDebugMenu
    type: bool
    value: true
    state: present
  notify: Restart Safari

- name: Finderで隠しファイルを表示
  osx_defaults:
    domain: com.apple.finder
    key: AppleShowAllFiles
    type: bool
    value: true
    state: present
  notify: Restart Finder

- name: Finderで拡張子を表示
  osx_defaults:
    domain: com.apple.finder
    key: AppleShowAllExtensions
    type: bool
    value: true
  notify: Restart Finder

- name: FinderでPathBarを表示
  osx_defaults:
    domain: com.apple.finder
    key: ShowPathbar
    type: bool
    value: true
  notify: Restart Finder

- name: Finderでステータスバーを表示
  osx_defaults:
    domain: com.apple.finder
    key: ShowStatusBar
    type: bool
    value: true
  notify: Restart Finder

- name: Dockを自動的に隠す
  osx_defaults:
    domain: com.apple.dock
    key: autohide
    type: int
    value: 1
    state: present
  notify: Restart Dock

Role: fishの作成

fishのインストールとoh-my-fishのインストールとpecoとanyenvのfish対応設定のダウンロードとか諸々やってます。

ここでの肝は、get_urlモジュールを使って、ファイルのダウンロードを行っています。私がgistに書いているpecoの便利関数のfish版などを取得しています。よくある手法では、dotfilesなどを作って、いろいろな設定ファイルを管理するというのがありますが、oh-my-fishやプラグインのインストールとかをすることもあるだろうから、あえて管理をdotfilesから分けてgistにしました。

---
- name: homebrewのアップデート
  homebrew:
    update_homebrew=yes

- name: fishのインストール
  homebrew:
    name: fish
    state: latest

- name: fishを選択可能なshellとして追加
  lineinfile:
    dest: /etc/shells
    line: /usr/local/bin/fish
  become: yes

- name: 既存のshellを確認する
  shell: echo $SHELL
  register: current_shell
  changed_when: false

- name: fishをデフォルトのshellにする
  shell: chsh -s /usr/local/bin/fish
  become: yes
  when: current_shell.stdout != '/usr/local/bin/fish'

- name: oh-my-fishをclone
  git:
    repo: https://github.com/oh-my-fish/oh-my-fish
    dest: ~/oh-my-fish

- name: oh-my-fishをlocalからインストールする
  shell: bin/install --offline --noninteractive
  args:
    chdir: ~/oh-my-fish
    creates: ~/.config/omf

- stat: path=~/.anyenv
  register: anyenv

- name: anyenvの設定を行うfishファイルをコピーする
  get_url:
    url: https://gist.githubusercontent.com/patorash/b5a1033c08d2c4df103457866b2dcaa2/raw/9a6c7e2111d95db0fdb07d43a41fa2eea5352b5f/anyenv.fish
    dest: ~/.config/fish/conf.d/anyenv.fish
  when: anyenv.stat.exists

- name: pecoの便利関数のfishファイルをコピーする
  get_url:
    url: https://gist.githubusercontent.com/patorash/1de94fcb7efeb847501d2fb7900c2deb/raw/7251e11091b388dd527e03c172913e188d1432f6/peco.fish
    dest: ~/.config/fish/conf.d/peco.fish

- name: aliasがまとめられたfishファイルをコピーする
  get_url:
    url: https://gist.githubusercontent.com/patorash/ca18e28b22f12a55e2539477abcda26d/raw/1ecc260dad5779be94a4304f2980a114ea85e7ac/alias.fish
    dest: ~/.config/fish/conf.d/alias.fish

- name: functionsディレクトリを作る
  file:
    path: ~/.config/fish/functions
    state: directory

- name: カスタムキーバインディングのfishファイルをコピーする
  get_url:
    url: https://gist.githubusercontent.com/patorash/47e7e179fdd80fe7e9517b93cb2f3d82/raw/8f74b8614eb0ddc3f3f3ce0c1964117cdbb08fdf/fish_user_key_bindings.fish
    dest: ~/.config/fish/functions/fish_user_key_bindings.fish
    force: true

Role: dotfilesの作成

dotfilesも使います。fishの設定は他の管理にしましたが、gitの設定ファイルなどはdotfilesを使います。 単純に、.gitconfigと、.global_gitignoreというファイルを含むリポジトリをcloneして、ホームディレクトリにシンボリックリンクを作成しているだけですが、これも自分でやると結構面倒なので自動化しておくと楽ですね。

---
- name: dotfilesをgit cloneする
  git:
    repo: https://github.com/patorash/dotfiles.git
    dest: ~/dotfiles

- name: シンボリックリンクの作成
  file:
    src: ~/dotfiles/{{ item }}
    dest: ~/{{ item }}
    state: link
    force: true
  with_items:
    - .gitconfig
    - .global_gitignore

変数を定義する

RubyとNodeのインストールで変数を渡すようにしていたので、その辺りも記述します。vars/main.ymlに定義してみました。

---
ruby_versions:
  - 2.3.4
  - 2.4.2
node_versions:
  - v8.5.0

localhost.yml

完成したのがこちら。作ってきたroleを順番に実行させていっているだけですが。

---
- hosts: localhost
  connection: local
  gather_facts: no
  vars_files:
    - vars/main.yml
  roles:
    - homebrew_tap
    - homebrew
    - homebrew_cask
    - anyenv
    - rbenv
    - ndenv
    - osx_defaults
    - fish
    - dotfiles

気付き

数日間に渡ってMacの環境構築の自動化に取り組んできたのですが、自分で確認しながら取り組むと、よりAnsibleへの理解度が深まり、やってよかったなと思いました。以前にAnsible本を読んでサーバの環境構築の自動化の写経はしたのですが、普段Herokuを使っているのでほとんど自動化する機会がなく、せっかく読んだのに忘れてしまっていました(それでもうっすら記憶にあって助かりましたが)。

また、fishのセットアップの自動化や、anyenvのセットアップの自動化のあたりでも色々調べて新しい知識が得られたりもしました。副次的な効果でしたが、そういうのもまとまって理解できたのもよかったです。

成果として、Playbookが完成した後にザーッと流すと、1日ちょっとかかっていたMacのセットアップが、1〜2時間程度でほとんど終わるようになりました。これでもうMacを初期化するのが億劫になることもなさそうです。

リポジトリ

自分用にブランチを分けたので、masterとは結構変わってます。

GitHub - patorash/ansible-mac-provisioning at for-patorash

masterのほうが作りが雑なので、あとあと直していきたいところです。

fishでanyenvを無理やり動かす

Node.jsのバージョン管理をどうやって行うかなーと考えてて、nvmかnodebrewか?などと呟いていたら、anyenvとndenvの組み合わせを教えてもらいました。anyenvは*env系のものを一緒に扱えるので、rbenvを入れるのも簡単そうだし、よさそう!と思って採用してみることに。最初はbashで作業していたのですが、やはりfishにしたいな〜と思い、fishにしたところ、ここに罠が待っていました。

fishでndenvが動かない

anyenv経由で入れたndenvが動きません。rbenvは動きます。

同様のことにハマっている人がいたので、追っていきました。

ja.stackoverflow.com

.config/fish/config.fishでanyenvの初期化処理を流すところで、ndenvがfishに対応していないようです…。

初期化処理を修正する

初期化処理をrbenvのようにfishに対応させればいけるんじゃないか?と思い、やってみることにしました。調査していたら、すでにそのようにしている方の記事を見つけたので、それに倣って修正していきました。

nemu-sou.hatenablog.com

事前処理

まずは、anyenvへPATHを通します。config.fishに以下を書きます。

set -x PATH $HOME/.anyenv/bin $PATH

一旦保存して、shellを再読み込みします。

exec fish -l

その後、anyenvでrbenvとndenvを入れます。

anyenv install rbenv ndenv

config.fishの修正

次に、rbenv, ndenvへの設定をconfig.fishに書いていきます。通常ならば、

eval (anyenv init - fish)

のみでよかったのですが、ndenvがこの引数のfishを理解できずにbashで処理を行おうとするため、文法エラーになって落ちていました。

rbenv

先ほどのブログを参考にしながら、まずはrbenvの設定を行っていきます。config.fishに追記します。

# rbenv
set -x RBENV_ROOT "$HOME/.anyenv/envs/rbenv"
set -x PATH $PATH "$RBENV_ROOT/bin"
# ここから下に、terminalで"rbenv init - fish"と打ったものを書き込む
...

ターミナルで、

rbenv init - fish

と打つと、rbenvの初期化スクリプトがfish形式で表示されます。それをconfig.fishに追記します。

これで、rbenvは終わりです。

ndenv

同様に、ndenvをやっていきましょう。

# ndenv
set -x NDENV_ROOT "$HOME/.anyenv/envs/ndenv"
set -x PATH $PATH "$NDENV_ROOT/bin"

ターミナルで、

ndenv init - fish

と打つと、ndenvの初期化スクリプトbash形式で表示されます。bash形式なので、fish形式に変更しなくてはなりません。rbenv側で出力されたものと同じになるように修正していくだけなので、そんなに難しくはありません。

完成品

で、完成品がこちらになります。rbenv, ndenv以外のものをいれた場合は、個別に**env init - fishを実行して追記していく形になると思います。うん、面倒ですねぇ…。あと、~/.config/fish/conf.d/以下にファイルを置いたら自動でロードされるので、anyenv.fishという名前に変えて置くことにしました。

fish and anyenv(rbenv, ndenv)

その後、シェルを再読み込みするなりして、rbenv, ndenvと打ってみましょう。ちゃんと出ると思います。

CloudGarageにオレオレHerokuを立ててみた

CloudGarage Release Tourに参加してからなんかやってみようと考えていたところで、本でDockerの勉強をしていたら、Dokkuの存在を知りました。

Dokkuとは?

Dokkuとは、自分でPaaSを作ることができるツールです。Dockerコンテナべースで組める他、Herokuのようにgit pushでのデプロイに対応しています。また、Herokuのbuildpackを使ったコンテナ作成が行われるので、動作環境としては本当にHerokuに近いんじゃないかなと思います。

Dokku - The smallest PaaS implementation you've ever seen

動作環境

UbuntuDebianがオススメで、メモリは最低でも1GB以上必要です。CloudGarageのお試しプランでは、メモリが1GBなのでギリギリ試すことができました。

やってみる

とりあえずやってみます。まず、CloudGarageのアカウント登録を済ませました。無料で14日試せます!

cloudgarage.jp

インスタンスを立てる

DokkuのサポートOSの、Ubuntu 16.04 64bitで立てました。噂通り、めちゃくちゃ起動が速いです。

Dokkuのインストール

SSHで立てたサーバにログインします。 今回はdokkuを試したいだけなのでrootでのログインを許可したままやってます。

ローカル側
ssh root@[サーバのグローバルIP]

そして、http://dokku.viewdocs.io/dokku/getting-started/installation/に従い、現時点の最新である、v0.10.4をインストールします。

サーバー側
wget https://raw.githubusercontent.com/dokku/dokku/v0.10.4/bootstrap.sh
sudo DOKKU_TAG=v0.10.4 bash bootstrap.sh

Dokkuのインストールは数分かかりますので適当に眺めながら待ちます。Dockerとか諸々のツールがこれでインストールされます。

Dokkuの初期設定を行う

ブラウザで、http://[サーバのグローバルIP]にアクセスすると、DokkuのSSHの公開鍵やVirtualHostの設定画面が出ます(1度しか表示されなかったのでスクショ取れず…)。とりあえず何も考えず設定完了に。

Dokkuを入れると、サーバのユーザーとしてdokkuが作られます。dokkuユーザーでgit pushするときにSSHの公開鍵登録をしておかないといけないので、まずはそれを行います(これが最初わからなくてハマった)。

ローカル側

サーバのグローバルIPとKEY_NAMEは適当に変えてください。KEY_NAMEは私はmbp_homeとか適当にしました。

cat ~/.ssh/id_rsa.pub | ssh root@[サーバのグローバルIP] dokku ssh-keys:add [KEY_NAME]
# その後、rootのパスワードを求められるのでパスワードを入力

アプリケーションのデプロイを行う

以下のチュートリアルに従って、Herokuのruby-rails-sampleというリポジトリをまずはデプロイしてみます。 http://dokku.viewdocs.io/dokku/deployment/application-deployment/

git clone

まずはデプロイするプロジェクトをgit clone。

ローカル側
git clone git@github.com:heroku/ruby-rails-sample.git

アプリケーションの作成

次に、Dokku側でアプリケーションを作っておきます。heroku apps:createに該当します。

サーバ側
dokku apps:create ruby-rails-sample

そして、postgresqlを使うので、それの設定も行います。Dokkuでは、ミドルウェアの導入方法はプラグインで提供されているので、それに従います。そして、rails-databaseというデータベースを作ります。Postgresは現時点では9.6.4が入りますが、環境変数を設定することで、異なるバージョンを入れることもできます。基本的にはDockerHubにあるオフィシャルのpostgresのタグを指定するべきなようです(もしくはオフィシャルをFROMに設定しているもの)。とりあえずバージョン変更せず。

dokku plugin:install https://github.com/dokku/dokku-postgres.git
dokku postgres:create rails-database

postgresとアプリをリンクさせます。

dokku postgres:link rails-database ruby-rails-sample

デプロイ

さて、ではいよいよデプロイです。デプロイはローカル側から行います。

ローカル側

まずはgit remoteにdokkuを登録します。

cd ruby-rails-sample
git remote add dokku dokku@[サーバのグローバルIP]:ruby-rails-sample

次に、git pushでデプロイします。

git push dokku master

すると、スルスルとHerokuのようにデプロイスクリプトが流れていきます。特に指定していないとHerokuのbuildpackを使うので本当にHerokuのように動きます。

そして、最終的に適当なポートで公開されました。DNS設定とか一切していないからでしょう。その辺りはまだ深堀していませんが、とりあえずそのURLにアクセスしてみましょう。URLを忘れても大丈夫!表示するコマンドがあります。

サーバ側
dokku urls ruby-rails-sample

ではそのURLにアクセスしてみましょう。

f:id:patorash:20170914000701p:plain
デプロイしたruby-rails-sampleにアクセスしたところ

はい、現在時刻を表示するアプリがちゃんとデプロイされました。よし、チュートリアル終わり!

まとめ

まとめというか、感想というか。

Dokkuを使っての感想

まだ使いこなしているわけではないのですが、Herokuみたいな感じで扱えるのは日々Herokuを使っている身としては便利でした。ただ、heroku cliのように見えるdokkuコマンド群ですが、これ全部サーバー側での作業なのでheroku cliのほうが圧倒的に便利です。ローカル側からdokkuが操作できるのはgit push(デプロイ)くらいなもんです。

postgresプラグインAWSへの定期バックアップ設定とかもできるみたいだし、Dockerなのでバージョンも融通ききそうだし、ちゃんと設定すれば問題はなさそうに思います。プラグインは豊富で、redisやelasticsearchもあったので、それらも今後試していきたいところです。

CloudGarageを使っての感想

起動めっちゃ速い。これに尽きます。3台あるうちの1台しか使ってないので、またネタ考え付いたらいろいろ試してみたいですねぇ。当分はDokkuの検証環境として使わせてもらいます!

キャッチフレーズ作りのスパイスになる一冊

会社のサイトを作ったり、製品専用のランディングページを作ったり、スライドの決め台詞を考えたりすることが多いので、結構前に買っていたキャッチコピーを考えるときの参考書を読み返していた。

キャッチコピー力の基本

キャッチコピー力の基本

77もあるテクニック

ほんまに77もあるんかいな?と思いながら読んだけれど、確かになぁと納得させられる切り口が多く、面白かった。とはいえ、途中途中こじつけというか、「それ前に出てきたのと同じじゃないか?」と感じるものもある。そういうものもあるけれど、微妙に切り口が違ったりするのでおおらかに読むといい。まぁ、この本の題名自体が、そのテクニックによってつけられていると思うので、キリのいい数字の77にして、77個ひねり出したんだと思う。敷居を下げるテクニックも使われている(キャッチコピーの基本というふうに)。

実践的なテクニック集

この本を読んでいると、書店に並ぶ本や、人気になっているWebページがどういう効果を期待してこんなタイトルにしたか、等がわかってくる。語呂がいい組み合わせ、ギャップのある組み合わせ、ハードルを下げる、ランキングを利用、対句にする、マジックワードを入れるなど。

読みすすめながら、「あ〜、このテクニックの切り口でうちの商品の押し方とか考えたことなかったなぁ〜」とか思い始めて、いろんなキーワードを組み替えていいキャッチコピーが浮かぶか実験していた。「ボツりそうだけれど、こういう表現も面白いな!」とか。

キャッチコピーの重要性

どんなに優れた商品だったとしても、認知してもらえなければ売れない。認知してもらえてようやくスタートライン。だからこそ、認知力を高めるキャッチコピーがとても重要である。

POPのキャッチコピーを変えただけでベストセラーになったりするのだ。それはもちろんその本が面白いというのが絶対条件ではあると思うけれど、話題にしやすいタイトル名とか、「自分のこと?」と思わせるタイトルとか、そういう工夫をちゃんとやるといいものは日の目を見る。逆にそこをサボると、いくらいいものを作っても認知されないから売れない…。

書籍の場合は、帯やPOPに「東大・京大で一番読まれた本」とか「読書家のアルバイト○○の今期のオススメ」とかのキャッチコピーをつけることができる。それがヒットして、全国的に売れるようになったりすることもあるという紹介がされていた。それらの手法を、他の製品や、イベントなどに応用するなどしていくと面白そうだと思った。

エンジニアも勉強会で発表するときはタイトルに気を遣う。誇張し過ぎてもよくないし、大人し過ぎても興味を引かれない。タイトルでそのスライドが見られるかどうかが決まるので、視聴者に刺さるタイトル付けを意識していかないと!と思った。

気づき

キャッチフレーズのパターン出しをしているときに、煮詰まってなかなか思いつかない時がある。そんなときにこの本をパラパラ捲っているとふっと湧いてきそうに感じた。辞書のように使ってほしいと書いてあったので、机に常時置いておこうと思った。

どんな言葉に触れているか

個人的に、いい発表をする人は、読書家であることが多いなと感じる。言葉の端々にセンスを感じるのだが、やはりそれは様々なインプットの量が多いからではないだろうか?この本の中でも、映画・小説・漫画・アニメの名言を利用するというテクニックが紹介されていたが、言葉を応用して自身のものとして取り込むのが上手い。

また、アウトプットする機会も多いから、インプットしつつも「あ、この表現今度使おう」みたいに備えているように思う。

キャッチコピーはセルフブランディングになる

「何を言ったかよりも、誰が言ったか」が重要という表現がよくあるけれど、セルフブランディングができていないと、その「誰」かがわからない。自分が何者であるのかをわかりやすくする肩書きを持つことで、周囲に認知されるようになる。ただ、そこをミスすると延々とその肩書きを引きずってしまって払拭できないので、決めるときには注意が必要だ。

自分もなにかしらの肩書きを考えてみるべきかな〜…。