積極的後進

後ろ向きに全力ダッシュ

GulpでCloudFront + S3環境へのフロントエンドデプロイを自動化する

CloudFrontに紐付いたS3に静的ファイル一式を置いた構成はテッパンだと思う. この場合,静的ファイルを更新するためには 1. S3にファイルをアップロードする 2. CFをInvalidateする の手順が必要となる. 滅多に更新が発生しないならともかく,絶賛開発中のプロジェクトの場合にこの操作を何度も手動で繰り返したくない.

本来ならばCIを設定したりwebpackでbuildついでに実行するなどありそうだが,オーソドックスにgulpタスクを書いてみた.

パッケージインストール

$ yarn add gulp gulp-awspublish gulp-cloudfront-invalidate

gulpタスクの記述

const gulp = require('gulp');
const awspublish = require('gulp-awspublish');
const cloudfront = require('gulp-cloudfront-invalidate');

// S3にデプロイするタスク
// dev環境にデプロイする
gulp.task('deploy', function() {
  const key = {
    accessKeyId: '...',
    secretAccessKey: '...',
    region: "ap-northeast-1",
    params: {
      "Bucket": "bucket-name",
    }
  }
  const publisher = awspublish.create(key);

  // deployするもの
  gulp.src(['**/*', '!node_modules/**/*', '!gulpfile.js',  '!package.json', '!README.md', '!yarn.lock', '!.babelrc' ])
    .pipe(publisher.publish())
    .pipe(awspublish.reporter());
});

// CFをInvalidateする
const cfInvalidateSettings = {
  distribution: '...',
  paths: ['/*'], 
  accessKeyId: '...', 
  secretAccessKey: '...',
  wait: true   
}


gulp.task('invalidate', function() {
  return gulp.src('*')
    .pipe(cloudfront(cfInvalidateSettings));
})

実行

$ gulp deploy && gulp invalidate

これで,指定したS3に静的ファイルを設置(存在しないファイルは新規に設置,存在するファイルは差分があれば更新)した後に指定したCFをinvalidateしてくれる. あとは個々の案件に併せてタスクをカスタマイズして,package.jsonのscriptsに良い感じで書いておく. 上記の設定だとinvalidateの完了までwaitする設定になっているが,外すことも出来る.個人的にはしっかりinvalidateが完了したかを見届けたいのでwaitすることにしている.

VSCodeでRiot.jsのtagファイルのシンタックス・コメントアウトを有効化する

VSCodeを使っていてなおかつRiot.jsを扱おうとすると,シンタックスが死んだりコメントアウトが上手くできなかったりしたので対応備忘録.

プラグインインストー

Riot-Tagを入れる.

settings.jsonを編集する

このエントリを参考にする.

すると,HTMLやJS,CSSが混在するtagファイルでも問題なくシンタックスコメントアウトが出来るようになる.

艦これをelectronでwrapしてアプリめいて起動する

Windowsがメイン環境な提督たちは,提督業も忙しい!を使うと快適にプレイできる. 一方でMacな提督たちは,(僕が知らないだけだと思うけど)基本的にブラウザ上 or Extensionな環境でプレイしていると思う.

ガチ勢ではない僕にとってはそれほど不便ではないのだけれど,Macを使っていてCommand + Tabでアプリケーションを切り替えたりすると, 艦これだけが立ち上がっているWindowも一緒に最前面に出てきてしまう. じゃあSafariとか使えよ.という話ではあるが.

せっかくなのでElectronでWrapして,アプリめいて起動させてやろうかなと思ってやってみた. Electron使いたかっただけ.Electronで単純にWrapしてやるだけ.

基本的なことは下記Qiitaに全てまとまっていたので,なぞっていけば楽勝. ただし,FlashPlayerを使うためのPepperFlashPlayer.pluginの箇所は僕の環境だと下記になった.

/Users/user-name/Library/Application Support/Google/Chrome/PepperFlash/25.0.0.127/PepperFlashPlayer.plugin

なんだかんだこのへんに注意しながらやるとあっさりWrapできた. 自ドメインに置いてみたので,雑に使ってみたい人はためしてください. 気が向いたら改良していきます.

Electronも随分便利だなあ.

参考

vue-cliで生成したVue.js 2.0 なアプリケーションをherokuにデプロイする

Vue.js 2.0

最近のWebフロント界隈といえばReact + Redux一強だが,Vue.js 2.0もなかなか使いやすい. 公式が作成しているジェネレータvue-cliを用いてプロジェクトを生成すると,環境構築を考えることなく一通り試すことができる.

Vue.js 2.0はReact.jsほどガチガチのコンポーネント指向ではなく,Angular.js 1.x系のバインディングコンポーネント指向を合わせたイメージ. vue-loaderが強力なので,html, js(es5), sassを一つのvueファイルにコンポーネントとして持つことが出来る. また,vue-routerも結構直感的なので本当にあっさりSPAを構築できる. webpack, webpack-dev-serverと合わせると爆速でページが量産されていく.

しばらくはVue 2.0で作っていきたい.

herokuへデプロイ

さて,Vue 2.0でページをサクサク作ったらどこかで公開しておきたくなる. herokuへのデプロイをしたいが,手順で若干詰まったのでメモ.

ローカルでの準備

本番用ファイルの準備

% npm run build

すると,本番用のファイルがdist以下に生成される. デフォルトだと.gitignoreによってリポジトリに乗らないようになっているので,これを削除してリポジトリに乗るようにしておく.

サーバーファイルの準備

heroku上で動作するサーバーファイル(node.js)を作っておく. 下記をserver.jsとして, dist以下に作成する.

var express = require('express');
var path = require('path');
var serveStatic = require('serve-static');
app = express();
app.use(serveStatic(__dirname));
var port = process.env.PORT || 5000;
app.listen(port);
console.log('server started '+ port);

ためしに

% node server.js

すると,localhost:5000で作成したページが表示されるはずだ.

package.jsonの準備

heroku用にpackage.jsonを作成する.とはいえ,元々ルートディレクトリにあったpackage.jsonをコピーして手を加えるだけだ.

% cp package.json dist/

した後,下記をコピー先のpackage.jsonに追記する.

scripts: {
    ...
    "postinstall": "npm install express",
    "start": "node server.js"
}

ここまで準備できたら,一度gitにpushしておこう(heroku以外でも管理している場合).

herokuの準備

herokuのアプリを新規で作成しておく. 作成して,herokuのremoteを登録しておく. また,buildpacksのnodejsを指定する.

% heroku git:remote -a vue-application
% heroku buildpacks:set heroku/nodejs

pushする

いよいよpushとなる. ここで重要なのは,distディレクトリのみをpushすることだ. subtreeを用いて,下記のように叩く.

% git subtree push --prefix dist heroku master

heroku openする

出来上がり.

あとがき

手順を確立してしまうと簡単だが,少しはまってしまった. このように本番ビルドの結果 + 動作用のサーバーアプリケーションのみをデプロイする方法はvue以外でも活用できそうなので,覚えておきたい.

参考

ほとんどのやり方はこちらを参考にした.ありがとうございます. https://medium.com/@sagarjauhari/quick-n-clean-way-to-deploy-vue-webpack-apps-on-heroku-b522d3904bc8#.752sfjdmv

AnsibleとPackerを使ってAWS上にWordPressを展開するDocker Imageを作成する

概要

Ansible入門として,Amazon Linuxイメージを展開したDockerコンテナ上にWordPress環境を構築するplaybookを作成する. また,ローカルでのDockerコンテナ生成からplaybook適用後のDocker Imageの生成までを,Packerを用いて一括して行うようにする.

今回はAWS上に展開することを想定して各種ファイルを作成したが,実際にWordpressAWS上に展開する場合はAWS Marketplaceを利用した方が圧倒的に楽なのでそちらを使うべきだろう.

Docker

amazonlinuxイメージをpullしておく. テスト環境として,

% docker run -i -t -d --name aws -p 3000:80 amazonlinux

としてコンテナを生成し,ここに向けてansibleを実行する.

Ansible

下準備

playbook.ymlをローカルで作成し,上記で作成したdockerコンテナ上に流し込む. 実行前にansibleで使用するhostsファイルを作成する. Ansible 2.0以降はDocker Connectionが使用できるので,下記のようなファイルをローカルに作成する.

[docker]
aws

上記をdockerHostsというファイル名で保存しておく.ansible-playbook時に使用する.

playbook

実際のplaybookを作成する. 今回は下記のようなplaybookになる. wordpress周りの各種設定はAWSガイドラインそのままなので,適宜変更・追加が必要になる.

- hosts: all
  connection: docker
  # 初期設定
  # mysql周りのパラメーター
  vars:
    mysql_user: "wordpress-user"
    mysql_password: "hogehogehoge"
    mysql_database: "wordpress_db"
  tasks:
  # 各種パッケージのインストール(vimは趣味)
  - name: install yum packages
    yum: name={{item}} state=latest
    with_items:
    - vim
    - httpd
    - mysql
    - mysql-devel
    - mysql-server
    - php
    - php-devel
    - php-mysql
    - php-mbstring
    - php-gd
    - MySQL-python27
    - MySQL-python
  # wordpressのダウンロードと展開
  - name: wordpress download
    get_url: url="https://wordpress.org/latest.tar.gz" dest=/wordpress-latest.tar.gz
  - name: unarchive wordpress
    command: tar -xzf /wordpress-latest.tar.gz chdir=/var/www/html
  # mysqld起動のためのネットワーク設定,mysqld起動
  - name: network settings for mysql
    shell: echo "NETWORKING=yes" >/etc/sysconfig/network
  - name: enable mysqld
    service: name=mysqld state=started enabled=yes
  # dbとuserの作成,権限設定
  - name: create mysql database
    mysql_db:
        name: "{{mysql_database}}"
        state: present
  - name: create mysql user
    shell: |
      mysql << " _EOF_"
      CREATE USER "{{mysql_user}}"@'localhost' IDENTIFIED BY "{{mysql_password}}";
      GRANT ALL PRIVILEGES ON `{{mysql_database}}`.* TO "{{mysql_user}}"@"localhost";
      FLUSH PRIVILEGES;
      _EOF_
  # wordpress設定ファイルの作成
  - name: config wordpress
    lineinfile:
      dest: /var/www/html/wordpress/wp-config.php
      create: yes
      insertafter: yes
      line: |
        <?php
          define('DB_NAME', '{{mysql_database}}');
          define('DB_USER', '{{mysql_user}}');
          define('DB_PASSWORD', '{{mysql_password}}');
          define('DB_HOST', 'localhost');
          define('DB_CHARSET', 'utf8');
          define('DB_COLLATE', '');
          define('AUTH_KEY',         'put your unique phrase here');
          define('SECURE_AUTH_KEY',  'put your unique phrase here');
          define('LOGGED_IN_KEY',    'put your unique phrase here');
          define('NONCE_KEY',        'put your unique phrase here');
          define('AUTH_SALT',        'put your unique phrase here');
          define('SECURE_AUTH_SALT', 'put your unique phrase here');
          define('LOGGED_IN_SALT',   'put your unique phrase here');
          define('NONCE_SALT',       'put your unique phrase here');
          $table_prefix  = 'wp_';
          define('WP_DEBUG', false);
          if ( !defined('ABSPATH') )
                  define('ABSPATH', dirname(__FILE__) . '/');
          require_once(ABSPATH . 'wp-settings.php');
  # apacheの設定
  - name: "modify httpd.conf"
    shell: >-
      c='/etc/httpd/conf/httpd.conf' &&
      k='<Directory "\/var\/www\/html">' &&
      s='AllowOverride None' &&
      r='AllowOverride All' &&
      mv $c $c.backup &&
      awk "/$k/{f=1} f==1&&/$s/{sub(/.+/,\"$r\"); f=0} 1" $c.backup > $c
  # apache userのグループ設定
  - name: creat a group
    group:
      name:  www
      state: present
  - name: add user to group
    shell: usermod -aG www apache
  - name: change user auth
    file: path=/var/www/html/ owner=apache group=www mode=2755 state=directory recurse=yes
  # httpd起動
  - name: enable apache
    service: name=httpd state=started enabled=yes

playbook実行と確認

先程作成したDockerコンテナに向けて,上記playbookを実行する.

% ansible-playbook -i dockerHosts playbook.yml

無事に展開が終わったら,localhost:3000/wordpress にむけてアクセスすると,wordpressの初期設定画面にアクセスできる.

Packer

上記で,playbookを用いてDockerコンテナ上にWordPress環境が構築できるところまで確認できた. 最後に,Packerを用いてコンテナ生成から,環境構築後のDocker Image生成まで行う.

下記のようなjsonファイルを作成する(ファイル名はpacker.jsonとする).

{
    "builders":[{
        "type": "docker",
        "image": "amazonlinux",
        "export_path": "controller.tar",
        "run_command": ["-d", "-i", "-t", "--name", "aws", "-p", "3000:80", "{{.Image}}"],
        "pull": false
    }],

    "provisioners":[{
        "type": "ansible",
        "playbook_file": "./playbook.yml",
        "extra_arguments": ["-i", "dockerHosts"]
    }],

    "post-processors": [{
        "type": "docker-import",
        "repository": "wordpress-aws",
        "tag": "aws"
    }]
}

上記のjsonファイルを用いて,次のように実行する.

% packer build packer.json

これで,Wordpress環境が構築されたDocker Imageが生成される.

さいごに

Wordpress環境を構築した状態のDocker Imageを一通り生成するところまでが確認できた. AnsibleとPackerは初体験だったが,まずは使ってみることができたので満足した. 上記で生成されたDocker Imageを,例えばECRに登録して適宜デプロイされるようにしておく,などが次のゴールになる.

間違いや,より効率的な書き方があると思われるので適宜ご指摘いただけますと幸いです.

2017 5/29 追記

ダウンロードしたwordpressを解凍する際にバージョンが固定になっていたので,最新バージョンをダウンロード・解凍するように変更しました.

tumblrAPI(tumblr.js)のoffset,before,after

tumblr.jsを使ってtumblrAPIを叩いて投稿データを取ってくるのは,認証周りでさえコケなければ難しくない. ただ,実際にAPIを叩く際には少し配慮が必要となる.

tumblrAPIのoffset,beforeとafter

tumblrAPIの仕様として,offsetは上限が1000件と決められているらしい. offset=1000以降は遡ることが出来ない.

そこで,beforeとafterというパラメータをAPIに渡してやる解決する. beforeとafterは共にtimestamp型のデータを渡して使う. beforeは○○以前,afterは○○以降のデータを返してくれる. よって,例えばアーカイブページを自前で作ろうとした場合には, beforeに現在のtimestampを渡してやって,その後追加でデータを読み込む際には,最初に受け取ったデータの中から一番古いタイムスタンプを渡してやれば良い.

注意点として,

  • before, afterを使う際にはoffsetは使用できない
  • before, afterは同時に併用できない
  • 取得件数はデフォルトで20件となる(limitを変えて色々試してみたが,最大でも50件だった)

このように,tumblrAPIを介して投稿データを取ってくる際にはパラメータに気を配る必要がある. 調べてもあまりこの辺の話は出てこなかったので,誰かの役に立てば幸い.

ブラウザから多重リクエストが来る問題

href, src, background-image

ブラウザから特定のページに遷移した場合,そのページに対して複数回リクエストが送られてしまう事例がある. どうやらブラウザのバグらしく,hrefやsrcが空だと多重リクエストが発生する.

<!-- example -->
<a href="">hogehoge</a>
<img src="" />

ここまではググるとよくヒットするが,上記の他にもbackground-imageで指定したurlが空だったりすると,こちらも多重リクエストが発生するようだ.

<!-- example -->
<div style="background-image:url()"></div>

まだ一回しか出くわしていないが,何かのヒントになるかもしれない. しかしこのバグ,5年以上前から既に報告されているのに各ブラウザが全く対応してくれない… 開発者が気をつけるしかないらしい.