あいつの日誌β

あいつの日誌です。

ISUCON7 に向けて ISUCON6 の予選を復習するために環境を用意する

あらすじ

というわけで ISUCON6 予選の環境を再現したいと思います。当日行われた環境とはディレクトリ構成などが違うと思いますがそのあたりはご容赦ください。

あと PHP はよくわからなかったので除外しました。

Azure で以下の VM を用意

私は今回 Azure を用意しましたが良いスペックの PC をお持ちの方は VirtualBox でも良いのかもしれません。

OS: Ubuntu Server 16.04 LTS
サイズ: Standard DS2 v2
ユーザー名: isucon
IP: 静的に変更
受信規則: HTTP を追加

ansible を読み解く

おそらく起点はこちら。 https://github.com/isucon/isucon6-qualify/blob/master/provisioning/image/init.sh

なんですが https://github.com/isucon/isucon6-qualify/issues/6 にあるように現在は少し Ansible が想定とは違う構造になっているようです。issue にあるように target を dev にする必要があるのと、それでもエラーが出る場所があるので修正をします。

エラーが出る場所1: bin の位置

https://github.com/isucon/isucon6-qualify/blob/master/provisioning/image/ansible/04_deploy.yml#L10

エラーが出る場所2: PHP

https://github.com/isucon/isucon6-qualify/blob/master/provisioning/image/ansible/04_deploy.yml#L10

構築手順

最初に最低限必要なコマンドを入手します。ack-grep は私の趣味です。

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install -y --no-install-recommends ansible git aptitude ack-grep

Ansible

git clone

cd /home/isucon
git clone https://github.com/isucon/isucon6-qualify.git

bin が当初想定していた場所とは異なっているようです。あとPHPがなぜかエラーになる。ということで以下の修正を加えます。

edit isucon6-qualify/provisioning/image/ansible/04_deploy.yml

--- a/provisioning/image/ansible/04_deploy.yml
+++ b/provisioning/image/ansible/04_deploy.yml
@@ -7,7 +7,7 @@
     - dev
   tasks:
     - synchronize: src=../../../webapp/ dest=/home/isucon/webapp owner=no group=no links=yes
-    - synchronize: src=../../../bin/ dest=/home/isucon/bin owner=no group=no
+    - synchronize: src=../../../webapp/bin/ dest=/home/isucon/bin owner=no group=no
     - synchronize: src=../files/env.sh dest=/home/isucon/env.sh owner=no group=no
     - file: path=/home/isucon owner=isucon group=isucon state=directory recurse=yes
       become_user: root
@@ -35,19 +35,19 @@
       shell: PATH=/home/isucon/.local/go/bin:$PATH GOROOT=/home/isucon/.local/go GOPATH=/home/isucon/webapp/go make all
       args:
         chdir: /home/isucon/webapp/go
-    - name: composer

-      get_url:
-        url: http://getcomposer.org/composer.phar
-        dest: /home/isucon/webapp/php/composer.phar
-        mode: 0755
-    - name: php
-      shell: PATH=/home/isucon/.local/php/bin:$PATH php composer.phar install
-      args:
-        chdir: /home/isucon/webapp/php
-    - name: php-fpm (isuda)
-      copy: src=../files/isuda.php-fpm.conf  dest=/home/isucon/.local/php/etc/isuda.php-fpm.conf  owner=isucon group=isucon mode=644
-    - name: php-fpm (isutar)
-      copy: src=../files/isutar.php-fpm.conf dest=/home/isucon/.local/php/etc/isutar.php-fpm.conf owner=isucon group=isucon mode=644
+#    - name: composer
+#      get_url:
+#        url: http://getcomposer.org/composer.phar
+#        dest: /home/isucon/webapp/php/composer.phar
+#        mode: 0755
+#    - name: php
+#      shell: PATH=/home/isucon/.local/php/bin:$PATH php composer.phar install
+#      args:
+#        chdir: /home/isucon/webapp/php
+#    - name: php-fpm (isuda)
+#      copy: src=../files/isuda.php-fpm.conf  dest=/home/isucon/.local/php/etc/isuda.php-fpm.conf  owner=isucon group=isucon mode=644
+#    - name: php-fpm (isutar)
+#      copy: src=../files/isutar.php-fpm.conf dest=/home/isucon/.local/php/etc/isutar.php-fpm.conf owner=isucon group=isucon mode=644
 
 - hosts: all
   gather_facts: no

この状態で ansible を実行します。 target に dev を指定する事によって冒頭に説明した問題を回避しています。

cd ~/isucon6-qualify/provisioning/image/ansible/
PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ansible-playbook -i localhost, *.yml --connection=local -t dev

DB setup

DB を初期化します。

cd ~/isucon6-qualify/provisioning/image/
sh db_setup.sh

この状態で IP を直接叩くと Isuda が表示されると思います。

bench tool を用意

ベンチマークツールを同一ホスト上に設置する

.bashrc に以下を設定します。

echo 'export PATH=~/.local/go/bin:$PATH' >> ~/.bashrc
echo 'export GOROOT=/home/isucon/.local/go' >> ~/.bashrc
echo 'export GOPATH=/home/isucon/gocode' >> ~/.bashrc
echo 'export PATH=$GOROOT/bin:$PATH' >> ~/.bashrc
echo 'export PATH=$GOPATH/bin:$PATH' >> ~/.bashrc
echo 'export PATH=$HOME/isucon6-qualify/webapp/bin:$PATH' >> ~/.bashrc

環境変数を適用させる

exec $SHELL -l

edit ~/.gitconfig

[ghq]
    root = ~/gocode/src

isntall

go get github.com/motemen/ghq
ghq get https://github.com/isucon/isucon6-qualify.git

ビルド

cd /home/isucon/gocode/src/github.com/isucon/isucon6-qualify/bench
go get
go build

最初のベンチ結果

$ ./bench -datadir=data -target=http://localhost
{"pass":false,"score":0,"success":0,"fail":0,"messages":["初期化リクエストに失敗しました"]}

もう一度実行

$ ./bench -datadir=data -target=http://localhost
2017/10/18 15:46:39 start pre-checking
2017/10/18 15:46:45 pre-check finished and start main benchmarking
2017/10/18 15:47:39 benchmarking finished
{"pass":true,"score":3233,"success":1568,"fail":4,"messages":["リクエストがタイムアウトしました (POST /keyword)","リクエストがタイムアウトしました (POST /login)","リクエストがタイムアウトしました (POST /stars)"]}

おしまい。

Bitbukcet で Access key を登録して Read only な repository に対して git clone する

もう何度も同じ失敗をしているのでメモ

鍵の登録は問題ないかを確認

ssh -T git@bitbucket.org

git clone は git@bitbucket.orgユーザ名/リポジトリ名.git: で繋ぐ

git clone git@bitbucket.org:ユーザ名/リポジトリ名.git

ubuntu で useradd したときに password をデフォルトで設定したいので expect を使う

ubuntuadduser を実行すると対話モードが発生します。

USER_NAME=okamuuu # 適宜変更してください
sudo adduser $USER_NAME

こんな感じの対話モードが始まります。

Adding user `okamuuu' ...
Adding new group `okamuuu' (1002) ...
Adding new user `okamuuu' (1002) with group `okamuuu' ...
Creating home directory `/home/okamuuu' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:  # パスワード入力
Retype new UNIX password: # パスワード入力(確認)
passwd: password updated successfully
Changing the user information for okamuuu
Enter the new value, or press ENTER for the default
        Full Name []:    # Enter連打
        Room Number []: 
        Work Phone []: 
        Home Phone []: 
        Other []: 
Is the information correct? [Y/n] Y  # Yes
# 終了

こんな感じの createuser.sh を追加するとよいでしょう。

if [ $# -ne 1 ]; then
  echo "実行するには1個の引数が必要です。" 1>&2
  exit 1
fi

USER_NAME=$1
PASSWORD=hogehogehoge

echo ${USER_NAME}

sudo expect -c "
spawn adduser ${USER_NAME}
expect \"Enter new UNIX password:\"
send -- \"${PASSWORD}\n\"
expect \"Retype new UNIX password:\"
send -- \"${PASSWORD}\n\"
expect \"Full Name\"
send -- \"\n\"
expect \"Room Number\"
send -- \"\n\"
expect \"Work Phone\"
send -- \"\n\"
expect \"Home Phone\"
send -- \"\n\"
expect \"Other\"
send -- \"\n\"
expect \"Is the information correct?\"
send -- \"Y\n\"
interact

こんな感じで実行

sh createuser.sh okamuuu

おしまい。

docker build でやたらと時間がかかるのは .wercker が原因だったっていうお話

こんな感じのコマンド実行したら生成される Docker Image のサイズがおかしい

docker build -t myapp .
Sending build context to Docker daemon  4.321GB

echo "node_modules" >> .dockerignore したけど相変わらず Image のサイズが大きい。なんだろうとおもったら wercker-cli 操作した時にできた .wercker が原因だった

% du -d1 -x .
28768   ./.git
16      ./.storybook
9176296 ./.wercker
685064  ./node_modules
72      ./public
360     ./src
16      ./stories
5928    ./storybook-static
9899200 .

du -s * だと dotfiles が表示されないので du -d1 -x . がおすすめです。というお話です。

Nginx + Node.js + React.js. + Mongodb を Docker で構築する

あらすじ

Nginx + Node.js + React.js + Mongodb で Web Application の構築手順を説明する必要があるのですが、Dockerfile をドキュメントとして扱う事になりました。

% docker --version
Docker version 17.06.2-ce, build cec0b72

% node -v
v8.1.0

% create-react-app --version
1.4.0

% mongo --version | head -n 1                                                                           MongoDB shell version v3.4.9

準備

一応 Todos アプリのような感じの Web API を backend で用意して React がそのデータを描画する、までの簡単なサンプルアプリを作成しつつ Docker について説明します。

先ずは以下のコマンドを実行して雛形を作っておきます。

cd 
mkdir practice-docker-provision && cd $_
mkdir backend mongodb mongo_seed nginx
create-react-app front
touch docker-compose.yml

mongodb

create docker-dompose.yml

version: "3"
services:
  mongodb:
    image: mongo:latest
    environment:
      - MONGO_DATA_DIR=/data/db
      - MONGO_LOG_DIR=/dev/null
    ports:
      - 27017:27017
    command:
      - mongod

docker-compose run --build を実行して mongod を起動します。起動したらアクセスできる事を確認します。

% mongo --quiet                                                                                                                   > show dbs
admin  0.000GB
local  0.000GB
> exit

mongo_seed

次に mongod を起動する度に database へ初期データを import するようにします。

mongod を起動した後、mongod へ初期データをインサートします。

mkdir mongo_seed/json
touch mongo_seed/Dockerfile mongo_seed/json/todos.json

create mongo_seed/json/todos.json

[
  { title: "todo 1", done: false},
  { title: "todo 2", done: false},
  { title: "todo 3", done: false}
]

create mongo_seed/Dockerfile

FROM mongo:latest

COPY json json

CMD mongoimport --host mongodb --db myapp --collection todos --drop --jsonArray --file ./json/todos.json

edit docker-compose.yml

     command:
       - mongod
+
+  mongo_seed:
+    build: mongo_seed
+    links:
+      - mongodb
+    depends_on:
+      - mongodb

docker-compose up --build を実行してから動作確認をします。

% mongo --quiet
> show dbs
admin  0.000GB
local  0.000GB
myapp  0.000GB
> use myapp
switched to db myapp
> db.todos.find()
{ "_id" : ObjectId("59d1fd6e73e8aa5ae594a04c"), "title" : "todo 1", "done" : false }
{ "_id" : ObjectId("59d1fd6e73e8aa5ae594a04d"), "title" : "todo 3", "done" : false }
{ "_id" : ObjectId("59d1fd6e73e8aa5ae594a04e"), "title" : "todo 2", "done" : false }
> exit
%

Node.js

cd ~/practice-docker-provision/backend
yarn init -y
yarn add express mongoose --save
touch app.js models.js

create app.js

var express = require('express');
var app = express();
var mongoose = require('mongoose');
var databaseUrl = process.env.MONGO_DATABASE || "mongodb://localhost/myapp"
var Todo = require('./models').Todo;

mongoose.connect(databaseUrl, {useMongoClient: true});

app.get('/api/todos', function(req, res) {
  Todo.find().exec((err, todos) => {
    if (err) {
      res.send(err)
      return
    }   
    res.json(todos)
  })  
});

app.listen(3000);

edit models.js

var mongoose = require('mongoose');

const Todo = mongoose.model('Todo', {
  title: {
    type: String,
    default: "", 
  },  
  done: {
    type: Boolean,
    default: false
  }
});

module.exports = { Todo: Todo }

edit backend/Dockerfile

FROM node:8

WORKDIR /usr/src/app

COPY package.json .
RUN yarn install

COPY . .

edit package.json

{
  "name": "backend",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "node app.js"
  },  
  "dependencies": {
    "express": "^4.16.1",
    "mongoose": "^4.11.14"
  }
}

edit docker-compose.yml

version: "3"
services:
  mongodb:
    image: mongo:latest
    environment:
      - MONGO_DATA_DIR=/data/db
      - MONGO_LOG_DIR=/dev/null
    ports:
      - 27017:27017
    command:
      - mongod

  mongo_seed:
    build: mongo_seed
    links:
      - mongodb
    depends_on:
      - mongodb

  backend:
    build: "backend"
    environment:
      - NODE_ENV=production
      - MONGO_DATABASE=mongodb://mongodb/myapp
    ports:
      - "3000:3000"
    links:
      - mongodb
    depends_on:
      - mongodb

docker-compose up --build を実行してから動作確認をします。

% curl -s http://localhost:3000/api/todos | jq .                                                 [
  {
    "_id": "59d201da7e61015c1651b11f",
    "done": false,
    "title": "todo 1"
  },
  {
    "_id": "59d201da7e61015c1651b120",
    "done": false,
    "title": "todo 3"
  },
  {
    "_id": "59d201da7e61015c1651b121",
    "done": false,
    "title": "todo 2"
  }
]

React.js + Nginx

React.js を build します。

cd practice-docker-provision/front
yarn run build

front/build にファイルが生成されている事を確認して下さい。

次に Nginx の config と Dockerfile を準備します。

create nginx/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    # include /etc/nginx/conf.d/*.conf;

    server {
      listen 80;

      # server_name localhost;

      gzip on;
      gzip_types *;

      location /api/ {
        proxy_pass http://backend:3000/api/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection ‘upgrade’;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
      }

      location / {
        root /app;
        index index.html;
        try_files $uri $uri/ /index.html;
      }

   }
}

create nginx/Dockerfile

FROM nginx:1.13.0

RUN mkdir /app

COPY ./nginx.conf /etc/nginx/nginx.conf

CMD ["nginx", "-g", "daemon off;"]

edit docker-compose.yml

       - mongodb
     depends_on:
       - mongodb
+
+  nginx:
+    build: "nginx"
+    ports:
+      - "8080:80"
+    volumes:
+      - ./front/build:/app:ro

React.js から backend の API への通信をする

すでに Docker の設定は完了していますが、最後に React.js を修正します

cd ~/practice-docker-provision/front
yarn add axios --save

edit src/App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import axios from "axios";

class App extends Component {

  constructor() {
    super();
    this.state = {
      todos: []
    };
  }

  async componentDidMount() {
    const res = await axios.get("/api/todos")
    this.setState({todos: res.data || []})
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <ul>
        {this.state.todos.map((todo, index) => (
          <li key={index}>{todo.title}</li>
        ))}
        </ul>
      </div>
    );
  }
}

export default App;

再度 yarn build して動作確認で終了です。

Docker で Nginx を起動して proxy させようとしたら host not found in upstream と言われる件

この書き方だとそうなった

FROM nginx:1.13.0

RUN mkdir /app

COPY ./nginx.conf /etc/nginx/nginx.conf

RUN service nginx start

正しくはこう。service は Docker の中で使ったらダメ、絶対。

FROM nginx:1.13.0

RUN mkdir /app

COPY ./nginx.conf /etc/nginx/nginx.conf

CMD ["nginx", "-g", "daemon off;"]

こちらからは以上です。

福岡へ4泊5日で働きながら旅をしてきた(3) その他

カフェと柳川と糸島とお土産。これで旅の報告はおしまい。

カフェ

今回は主にカフェで作業しましたので内訳を紹介します。

お店 働いた回数 作業のしやすさ
FUK COFFEE 0回 ×
スターバックスコーヒー メディアモール天神店 2回
スターバックスコーヒー 福岡赤坂門店 0回
スターバックスコーヒー 天神西通り店 1回
スターバックスコーヒー IWATAYA本店本館店 0回 ×
manu coffee 大名店 1回
manu coffee 春吉店 0回 ×
CARBON COFFEE 0回 ×

FUK COFFEE

おしゃれな外観のコーヒー屋さん小さい机が3つとあとは椅子だけの小さな店舗なので残念ながら作業には向きませんが酸味のあるコーヒーが好きな私が飲んでも酸味強いなと感じました。美味しかったです。フォトジェニックな店なので写真とコーヒー好きな人におすすめです。と言いながら外観を撮り忘れました。

f:id:okamuuu:20170924152650j:plain

スターバックスコーヒー

早朝の散歩などでお手洗いに行きたい時はスターバックスについ頼ってしまいます。そんなわけでスターバックスの入店回数が多いです。

実際に作業したのはメディアモール天神店、天神西通り店で午前中のみですが、両方とも席に余裕がありました。ただ、メディアモール天神店は席数が広いのですが、天神西通り店では席がやや少ないので時間帯によっては席がないかも。福岡赤坂門店は作業はしてないのですが席数もそこそこあるので作業しやすいんじゃないかと思います。

manu coffee

ここはなかなかお洒落なカフェなのですが午前中に行くとコーヒーブラックが200円です。福岡はちょいちょい価格がおかしい(?)店があります。

大名店は朝8:00から営業していてお洒落な空間をしばらく独り占めしていました。席数もそこそこあるのでもし福岡でノマドするならおすすめです。

春吉店はコンパクトな作りなので作業には向いていませんが一度行かれる事をおすすめします。柳橋連合市場という魚屋ばかりの商店街のような通りの端っこ一軒だけお洒落なカフェが存在していて、そのコーヒーを商店街で働くおばちゃんがテイクアウトして仕事場に向かうという、なかなか東京では見られない光景が見れました。

あとここも外観がすごいイケてます。うまく言えないんですが店内でコーヒー飲んでいるお客さんも絵になってしまうような、そんな感じの店です(撮影してこい)

CARBON COFFEE

ここも大名にあるお洒落なカフェです。作業には向いてないですがここもお洒落なカフェでした。

f:id:okamuuu:20170927123412j:plain

カフェまとめ

というわけで作業しながら旅をしていた関係上スタバ頻度が高いのですが、もしこの中で一軒だけノマドするなら私は manu coffee 大名店がおすすめです。ノマドしなくてもクリエイターの人とかにもおすすめです。うまく言えないんですが福岡で流行に敏感な人たちがふらっと入ってくる店、みたいな感じですたぶん。

柳川

f:id:okamuuu:20170925175112j:plain

滞在期間が割と長いので少し遠出することにしました。で、川下りをしたかったのと柳川のうなぎを食べたかったので行ってきました。

なんですが到着時間が結構遅れて最終の17:00 の船に乗ることになりました。結果的に日差しがなくて良かったです。ちなみに平日だったこともあって船頭さんとマンツーマンです。船頭さんは川を下りながら地元にちなんだ話をしてくれます。

川といってももともとは柳川城の掘だったので立花宗茂の話をしてくれます。地元の人たちは彼を主役にした大河ドラマを熱望しているそうです。ちなみに父親が武名の高い高橋紹運で母親の父親立花道雪だそうです。血統がすごいしなんだか実現しそう。

というわけで無事川下りを楽しんでうなぎを食べて帰りました。

糸島

f:id:okamuuu:20170926160031j:plain

どこまでも続く青い海と空を求めてレンタカーを借りて行って参りました。本来でしたら福岡市内で車を借りるのですが、私はペーパードライバーだったので筑前前原駅まで電車で移動してそこから車をレンタルしました。目的はただ一つ PALM BEACH RESTAURANT に行って来て、無事帰ってくる事です。観光も目的ですが今度に関わるのでこの機会に運転の練習がしたい(東京だと通行人が多すぎて練習するの嫌だ)

というわけで車を借りて北上するとイオンがあります。私はここで駐車の練習をしてから先に進みました。同じ境遇の人が居たらそうするとよいでしょう。ここにセルフ式の GS があるので帰りが遅くなる場合はここで給油する事をおすすめします。帰宅ラッシュの時間帯だと駅前がかなり渋滞するので駅前の GS へ入るのと出るのが結構大変でした。

糸島では制限速度が大体時速40km の場所がほとんどなので初心者でも割と運転しやすかったです。

で、 PALM BEACH RESTAURANT に行ってご飯を食べました。ちなみにこういうスポットがあります。

そして帰りが遅くなってしまい(帰りもイオンの駐車場で練習に励んだ)、帰宅ラッシュの最中 GS を目指しました。なんですが途中でカーナビが転倒してしまい、GS の場所を確認できない。今手を離したら死ぬどうしよう。と混乱した私は一方通行の道に迷いこみます。

そうすると地元のおじさんたちがここから先は通れないですよと教えてくれました。狼狽する私は「あの、東京から来た運転初心者なんですけどGSに行くにはどうしたらいいですかあうあう..」と助けを求めました。おじさんは「そこの駐車場でUターンすれば良か。GS はすぐそこ」と教えてくれました。礼を述べ、駐車場で気を落ち着かせているといつまでも発進しない私を心配したのか、先ほどのおじさんがわざわざ駆けつけてくれてもう一度丁寧に道順を教えてくれました。

糸島の人は優しかったとです...おかげさまで無事生きて帰れました。

おみやげ

f:id:okamuuu:20170928215838j:plain

冷凍の明太子を買って帰りました。飛行機で荷物預けるのが面倒なので機内持ち込みでお土産を買って帰りたい。おいしい明太子は一度も冷凍しないものでぷりぷりというかぷちぷちした食感があって美味しいのですが冷凍でもかなり美味しいので冷凍の明太子を機内に乗り込む前に購入しました。私は博多駅のマイングというお土産がいっぱい置いてあるエリアで買いました。

福岡へ4泊5日で働きながら旅をしてきたまとめ

福岡にはチャンスがいっぱいあって、精力的に活動している人や支援する人がたくさんいる事が分かりました。ごはんもおいしいし、お洒落なカフェもたくさんあります。あと自然までの距離も近いですし、住んでいる人も優しかったです。

街自体はコンパクトですがその分魅力が濃縮されていると思います。空港までの距離も近いので海外滞在をするにも便利な拠点だと思います。

また、視察するために現地で働きながら旅行をする、というのは結構楽しかったです。また近々どこかの街で働きながら旅をする、に挑戦したいと思います。

こちらからは以上です。