あすたぴのブログ

astap(あすたぴ)のブログ

CircleCi2.0が最高かもしれん

ローカルの環境をdockerで整えたので、次はCI環境を整備する。 dockerで構築したのだから、テストもdockerにしたい。 ちょうどCircleCi2.0がBetaテスト中で、さらにネイティブでdockerサポートが入っている。 これは試すしかないということでやってみた。

CricleCi2.0を使う

プロジェクトのホームディレクトリに .circleci ディレクトリを作成。 config.yml を作成する

config.yml の中身

version: 2
jobs:
  build:
    working_directory: ~/app
    docker:
      - image: hogehoge/mogemoge:latest
    steps:
      - checkout
      - run: echo "hello world"

これでCircleCiを通すだけの準備は完了する。 imageはDockerHubのpublicディレクトリであれば普通に取ってこれる。privateリポは別途設定が必要。 config.yml の設定は、try & error で直して、pushして、CircleCI流して〜ってやるとかなり消耗する。 複雑なテストをやろうとすると死ねる。

CircleCi2.0ではローカルでほぼ同様のことが可能になっている。 (docker executorのみ使える。従来のCircleCiの主流であったmachine executorは使えない)

circleci コマンド

install

vagrant-ubuntu-trusty-64% sudo curl -o /usr/local/bin/circleci https://circle-downloads.s3.amazonaws.com/releases/build_agent_wrapper/circleci && sudo chmod +x /usr/local/bin/circleci

usage はこんな感じ

vagrant-ubuntu-trusty-64% circleci
Receiving latest version of circleci...
The CLI tool to be used in CircleCI.

Usage:
  circleci [flags]
  circleci [command]

Available Commands:
  build       run a full build locally
  config      validate and update configuration files
  tests       collect and split files with tests
  version     output version info

Flags:
  -c, --config string   config file (default is .circleci/config.yml)
      --taskId string   TaskID
      --verbose         emit verbose logging output

config fileのvalidate

vagrant-ubuntu-trusty-64% circleci config validate

  config file is valid

buildのhelp

vagrant-ubuntu-trusty-64% circleci build --help

run a full build locally

Usage:
  circleci build [flags]

Flags:
      --branch string         Git branch
      --checkout-key string   Git Checkout key (default "~/.ssh/id_rsa")
      --config string         config file (default ".circleci/config.yml")
      --index int             node index of parallelism
      --job string            job to be executed (default "build")
      --parallelism int       parallelism level (default 1)
      --repo-url string       Git Url
      --revision string       Git Revision
      --skip-checkout         use local path as-is (default true)
  -v, --volume value          Volume bind-mounting (default [])

Global Flags:
      --taskId string   TaskID
      --verbose         emit verbose logging output

circleci build をふつうに叩いたらふつうに落ちた。 どうやら working_directroyが相対パスだとダメらしい。最初のymlで相対パスで設定させといてそれ?w

working_directory: /app に変更して通った。

最終的なかたち

version: 2
jobs:
  build:
    working_directory: /app
    docker:
      - image: docker image name
        environment:
          RACK_ENV: 'test'
      - image: mysql:5.7.17
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
          MYSQL_ROOT_HOST: '%'
    steps:
      - checkout
      - run: bundle install
      - run: bundle exec rails db:create
      - run: bundle exec rails db:migrate
      - run: bundle exec rspec
      - deploy:
          name: run deploy
          command: |
            if [ "${CIRCLE_BRANCH}" == "master" ]; then
              apk add curl
              curl --user ${CIRCLE_API_TOKEN}: \
                --data build_parameters[CIRCLE_JOB]=deploy \
                --data revision=$CIRCLE_SHA1 \
                https://circleci.com/api/v1.1/project/github/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/tree/$CIRCLE_BRANCH \
                >> /dev/null
            fi
  deploy:
    working_directory: /app
    docker:
      - image: docker image name
        environment:
          RACK_ENV: 'production'
    steps:
      - checkout
      - run: bundle install
      - setup_remote_docker
      - deploy:
          name: deploy
          command: echo 'hello'

docker-composeのように、imageがリンクされるわけではないので 127.0.0.01 でアクセスが出来るようにする必要があった。MYSQL_ROOT_HOST: '%'

config/database.yml にはこんな感じにかく host: <%= ENV['DB_HOST'] || ENV['MYSQL_ROOT_HOST'] || '127.0.0.1' %>

以下のサポートを参照した。 https://discuss.circleci.com/t/rails-mysql-container-connection/10604

deploy

こちらを参考にさせていただきました。 http://h3poteto.hatenablog.com/entry/2017/03/31/231410

別ジョブで実行するという方式。 残念ながら別ジョブはCLIコマンドではテストが出来ないので、 pushして try & error になってしまった。 ハックだし、いずれより良い形で公式にサポートがされるだろう。 現時点では docker executorでdeployするimageをbuildするいい方法がないかもしれない。

deployタスクは1.0のときのように、デフォルトで branchで別けれるようにしてほしかった。

【追記】 公式ドキュメントに増えた?昨日はなかった気がするのにw

circleci.com

This approach is a temporary workaround for the current features available during Beta. Soon we’ll be adding a much more elegant way to manage multiple jobs.

より良い方法をそのうち公開するっていってるのでよかったね

まとめ

deploy抜きで考えるならば、テスト、構築ともに速くなって非常にいいと思った。 そもそもここでdeploy用のimageをbuildするという方式も違うのかもしれない。 testが通ったwebhookで別のところでやってもいいかもしれない。 それも考えてみよう。

Rail5.1 rc1+Dockerでいい感じの環境を構築する

目的

いい感じに使える、Dockerの開発環境を構築する。 最終的な目的はProductionでDockerを使うイメージを固めること。

環境

  • Vagrant
  • Docker
    • Docker version 17.03.1-ce, build c6d412e
  • Rails5.1 rc1

docker for mac はいい噂を聞かないのでVagrant上でDockerを使用する。 当然、docker for mac でも動く。

Railsアプリ作成

bundle init
gem 'rails', '~> 5.1.0.rc1'
bundle instlal --path vendor/bundle

sprockets, turbolinksを抜いています。 DBはmysqlを使用

bundle exec rails new --skip-bundle -d mysql --skip-sprockets --skip-turbolinks .

ローカルにbundle設定があるとダメなので消します。

rm -rf vendor/bundle
rm -rf .bundle

Docker install

sudo apt-get update
sudo apt-get install \
    linux-image-extra-$(uname -r) \
    linux-image-extra-virtual

sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

sudo apt-get install docker-ce

dockerグループが出来ているので、vagrantユーザーをdockerグループに追加する必要があります。 その後、vagrantからlogoutし再度sshで入ります。

sudo gpasswd -a vagrant docker

docker-compose install

sudo curl -L "https://github.com/docker/compose/releases/download/1.11.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Dockerfile

Dockerfileを作成します。 alpine linuxをベースにRuby2.4.1の入っているイメージを元にします。 必要なパッケージをインストール後、不要なファイルを消したりします。 mysql2 gem をbuildするために必要なmariadb-devを入れるとmariadb本体も付いてきてしまうため、mariadb本体のファイルを削除しています。

cmd, entrypointはrun時に指定するので付けていません。

Dockerfile

FROM ruby:2.4.1-alpine

ENV LANG ja_JP.UTF-8
ENV BUILD_PACKAGES="curl-dev build-base" \
    DEV_PACKAGE="mariadb-libs mariadb-client mariadb-client-libs tzdata"

RUN gem install bundler \
  && apk --update --upgrade add $BUILD_PACKAGES \
  && apk add mariadb-dev tzdata linux-headers postgresql-dev sqlite-dev git nodejs \
  && rm /usr/lib/libmysqld* \
  && echo 'gem: --no-document' > /etc/gemrc

WORKDIR /app
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install

EXPOSE  3000

docker-compose.ymlを作成します。

spring server起動用のコンテナをwebと同一Dockerfileから作っています。 springについては以下を参考にさせていただきました。

参考

https://github.com/jonleighton/spring-docker-example http://tech.degica.com/ja/2016/06/14/dockerized-rails-development/

user指定時に、ホストOSのユーザーIDとグループIDを指定している。 ユーザーを指定しない場合、rootで実行されるため、Railsコマンドで生成したファイルがrootの所有となって、ホストOSのエディタから編集が出来なくなる。

version: '2'
services:
  db:
    image: mysql:5.7.17
    volumes:
      - ./store:/var/lib/mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'

  web:
    build: .
    command: bundle exec rails s
    user: "${uid}:${gid}"
    working_dir: /app
    environment:
      RACK_ENV: 'development'
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - db

  spring:
    build: .
    command: docker/run.sh spring
    volumes:
      - .:/app

mysqlの設定

※追記 mysqlコンテナを用意するようにしたのでこちらは不要になりました。

DockerコンテナからホストOSのMySQLに接続するためにホストOS側のMySQLの設定を修正します。

sudo vi /etc/mysql/my.cnf

bindするアドレスに0.0.0.0を追加します。

bind-address    = 127.0.0.1
bind-address    = 0.0.0.0

MySQLを再起動します。

sudo service mysql restart

bindアドレス同様に、 rootアカウントがlocalhostからしかログインできないためユーザーを追加します。

mysql -u root
CREATE USER 'root'@'172.17.0.2';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'172.17.0.2';
FLUSH PRIVILEGES;

開発

docker-compose系のコマンドを叩くのが面倒な為、scriptを用意しています。

  • 引数なしでヘルプ表示
  • 第一引数がupなら、docker-compose up
  • 第一引数がrspecならRACK_ENV=testでrspec起動
  • 第一引数がsならフォアグラウンドでRails起動 port指定
  • 第一引数がspringならspringサーバーをバックグラウンドで起動
  • 引数ありの場合は引数をそのまま渡す。

docker/bundle.sh

#!/bin/bash
ENV=$RACK_ENV
export uid=$UID
export gid=$GID

if [ "$ENV" = 'production' ]; then
  docker-compose run --entrypoint=$entrypoint -e RACK_ENV=$ENV web s
  exit 0
fi

entrypoint=docker/run.sh
if [ "$1" = 'up' ]; then
  docker-compose up
elif [ "$1" = 'rspec' ]; then
  docker-compose run --entrypoint=$entrypoint -e RACK_ENV=test web $@
elif [ "$1" = 'spring' ]; then
  docker-compose run -u "$UID:$GID" --entrypoint=$entrypoint -d spring spring
elif [ "$1" = 's' ]; then
  docker-compose run -p 3000:3000 -u "" --entrypoint=$entrypoint -e RACK_ENV=development web s
else
  docker-compose run -u "$UID:$GID" --entrypoint=$entrypoint -e RACK_ENV=development web $@
fi

docker/run.sh

alpine linuxにはbash等が入っていなく、わざわざ入れる必要もないコードなのでデフォルトのシェルであるashで書いています。

#!/bin/ash

if [ "$RACK_ENV" = 'development' -o "$RACK_ENV" = 'test' -o "$RACK_ENV" = '' ]; then
  if [ $# -eq 0 ]; then
    bundle exec rails -h
  elif [ $1 = 'rspec' ]; then
    bundle exec $@
  elif [ $1 = 'spring' ]; then
    bundle exec spring binstub --all
    #bundle exec spring binstub --remove --all
    bin/spring server
  else
    time bundle exec rails $@
  fi
  exit 0
fi

bundle exec unicorn -c config/unicorn.rb -E $RACK_ENV

deploy

deploy時に使用するDockerfileを作る

FROM ruby:2.4.1-alpine

ENV LANG ja_JP.UTF-8
ENV BUILD_PACKAGES="curl-dev build-base" \
    DEV_PACKAGE="mariadb-libs mariadb-client mariadb-client-libs tzdata"

RUN gem install bundler \
  && apk --update --upgrade add $BUILD_PACKAGES \
  && apk add mariadb-dev tzdata linux-headers postgresql-dev sqlite-dev git nodejs \
  && rm /usr/lib/libmysqld* \
  && echo 'gem: --no-document' > /etc/gemrc

WORKDIR /app
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install

ENV APP_HOME /app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
COPY . $APP_HOME

EXPOSE  3000

circle_ciでbuildして、testを実行。 testが通ったら、imageをECRなり、DockerHubなり、GCRへpushする。

まとめ

当初、DockerfileでアプリのソースをCOPYしてイメージを作っていたが、 ソースを含めてイメージを作るのはdeploy時だけでいいのでやめた。 軽く開発してみたが上手く進められている。 deploy周り(CricleCI含め)を妄想しかしていないので、いざやってみたらまた変わるかもしれない。

問題点が1つあって、user指定で実行するとbundler関係のディレクトリがroot所有なのでwarningが出る。 解決策が思いつかない。どなたかいい解決策があれば教えてください。

`/` is not writable.
Bundler will use `/tmp/bundler/home/unknown' as your home directory temporarily.

今回のソースは以下にあります。 https://github.com/astapi/development_rails_with_docker

terraform で aws のいい感じの構成を作る(基盤編1)

対象読者

terraformが何かを知っていて、 terraformを使おうと考えている人。

terraform version 0.9.1

初期設定

適当にディレクトリを作成します。

mkdir terraform_test
cd terraform_test

0.9.1からstate environmentsを設定できるようになったので、 dev, stg, prod みたいに同一ソースで分岐ができるようになる。

vagrant-ubuntu-trusty-64% terraform env new dev
Created and switched to environment "dev"!

You're now on a new, empty environment. Environments isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.

env new をすると、作成したenvに移る。 gitのbranchみたいだな。

vagrant-ubuntu-trusty-64% terraform env list
  default
* dev

AWS 設定

provider "aws" {
  region = "ap-northeast-1"
}
variable "aws_account_id" {
  default = "1111111111"
}

IAMアカウントの作成 AWSのIAMベストプラクティスをちゃんとやる(見る) 上記エントリに沿って、初期設定を行う。

resource "aws_iam_user" "astapi" {
  name = "astapi"
  path = "/"
}

resource "aws_iam_access_key" "astapi" {
  user = "${aws_iam_user.astapi.name}"
}

plan

IAMのベストプラクティスでは、ルートアカウントのアクセスキーとか消さないといけないのだけど、 terraformでIAMユーザーから作りたいので、最初だけルートアカウントのキーを使う。

export AWS_ACCESS_KEY_ID="anaccesskey"
export AWS_SECRET_ACCESS_KEY="asecretkey"
export AWS_DEFAULT_REGION="ap-northeast-1"

planコマンドで作成予定のリソースを確認する。

terraform plan
中略

+ aws_iam_access_key.astapi
    encrypted_secret:  "<computed>"
    key_fingerprint:   "<computed>"
    secret:            "<computed>"
    ses_smtp_password: "<computed>"
    status:            "<computed>"
    user:              "astapi"

+ aws_iam_user.astapi
    arn:           "<computed>"
    force_destroy: "false"
    name:          "astapi"
    path:          "/"
    unique_id:     "<computed>"

apply

terraform apply

f:id:astap:20170326234644p:plain

IAMユーザーができました。

terraform show

このコマンドで作成したアクセスキーの情報が閲覧できます。

vagrant-ubuntu-trusty-64% terraform show
aws_iam_access_key.astapi:
  id = AKJLAKJDA
  secret = aslkfjaslfkjasljfas;ljfalskjdfalsjkfak
  ses_smtp_password = asdklfjasldkfjasdkfjasldjkfas;l
  status = Active
  user = astapi
aws_iam_user.astapi:
  id = astapi
  arn = arn:aws:iam::1111111111:user/astapi
  force_destroy = false
  name = astapi
  path = /
  unique_id = AAKLJALKFJAKDJALKJD

ポリシーのアタッチ

ルートアカウントを使わなくてもterraformで全ての権限が必要なので、AdministratorAccessが必要。

resource "aws_iam_policy_attachment" "admin" {
  name = "admin-policy"
  users = ["${aws_iam_user.astapi.name}"]
  policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

今後は作成したIAMユーザーのACCESS_KEYを使って実行していきます。 ルートアカウントのACCESS_KEYを削除しましょう。

まとめ

terraform でIAMユーザーのアクセスキーまで作成したが、terraform showでキーが参照できるため、 複数人開発ではこの方式だとNGになりますね。 また、Backend(terraformで作成したAWSの状態を保存しておくところ)がlocalのため、 そもそも自分のローカルでしか実行が出来ない。 backendとしては色々用意されているが、S3 backendでは、terraform envに対応していなかった為、 counsul等、自前サーバーを用意したりする必要がありそう。 そのconsulサーバーを用意するterraformが必要・・・。 うーん。

プログラミングを魔法だと思っていた

昔、すごいプログラムだったりすごいシステムを魔法のように感じていた。 自分には全くどうやっているのか想像がつかなかった。 今からすると、なぜ魔法だと思っていたのかなっと思って、考えてみた。

フレームワークなんて、よくわからずにアクションを書いたら、 なぜかテンプレートに移動して、テンプレートを書くとブラウザにHTMLが表示された。 特定のURLを叩いたときに、特定のActionが呼ばれる理由もわからない。 そこにあるのは命名ルールだけだった。 特に疑問も持たずにルールに沿ってプログラムを書いて、 なんとなく画面が表示されてた。 プログラミングは辛かった。 もともとあるプログラムをコピペしていじって、なんとなく作っていた。 新しいものは作れない。自分にはわからない魔法だから。

ここまで読んでわかったヒトもいると思うけど、おれが触っていたのはJavaだった。 正直いまでもJavaはわからない。あれ以降やってないから。 Javaが苦手だったのは、外部ライブラリのコードが読みにくいから。 jarに固められた状態で手元に渡ってきて、その状態でなんとなく使う。 ドキュメントもあんまりないイメージだった。 jarという圧縮ファイルのブラックボックスだった。 たまに、逆コンパイルでコードを読んだりもしたけど、全然楽しくなかった。 なんで、わざわざ固められているものを戻して、解読みたいなことしてんだろって。 たぶん、これは自分の知識不足なだけで、きっと、元ソースもどっかにあったんだろうし、きっと、ドキュメントも探せばあったんだと思う。Javadocが。

そういう経緯があって、phpを触ったときはコードを奥深くまで読み進められるのが嬉しかった。 この頃から少しずつ、魔法ではなくなってきたかもしれない。

あるときちゃんと魔法に向き合おうと思ってコンピュータサイエンスとかhttpの仕様とか学ぶようになった。 そこからは、いろいろなことがクリアになったと思う。 蓋を開けてみれば、魔法だと思っていたことが実は泥臭いコードの塊だったり、 簡単なプログラムをうまく組み合わせることで魔法のように見えていただけだったりした。

魔法が魔法ではなかったと気づいた時から、仕組みがわからないものは仕組みを理解するように心がけるようになった。 そうした事を続けていると面白いことに、初めて触るライブラリやツールでも、 こういう機能がたぶんあるはず。こういう風に使うといいはず。 内部的にはたぶんこうなってるんだろうな。 みたいな事がなんとなくわかるようになっていた。 今はプログラミングはすごく楽しい。

自分のように、わけもわからずコードを書いている人がこの世にはまだいっぱいいると思う。 そういう人たちにも、プログラミングが楽しめるようになってほしい。