phpでlocalstackのs3にファイルをアップロード

AWS周りのものをローカルで開発する場合 localstack を使うと便利だというのを聞いたので試してみました。

github.com

localstackは、GUIはあまり提供されていない用で、主にawscliコマンドを使います。
s3にファイルをアップロードしたい場合は、 --endpoint-url=http://localhost:4572 を付けてあげてアップロード先を変えることができます。

試しに awscli で test というバケットを作ってファイルをアップロードしてみます。

$ aws --endpoint-url=http://localhost:4572 s3 mb s3://test
make_bucket: test
$ aws --endpoint-url=http://localhost:4572 s3 ls s3://
2006-02-04 01:45:09 test
$ touch hoge.txt
$ aws --endpoint-url=http://localhost:4572 s3 cp hoge.txt s3://test/hoge.txt
upload: ./hoge.txt to s3://test/hoge.txt
$ aws --endpoint-url=http://localhost:4572 s3 ls s3://test/
2017-12-08 20:00:00          0 hoge.txt

localstack上のs3にファイルがアップロードされていることが確認できます。

phpを使ってlocalstackのs3にファイルをアップロードしてみます。
awscliのときと同様に、endpointを localstack のs3用エンドポイントを指定してみます。
aws-sdk-php は composer を使って入れてます。

put.php

<?php
require_once 'vendor/autoload.php';
use Aws\S3\S3Client as S3Client;

$s3 = S3Client::factory(array(
    'endpoint' => 'http://localhost:4572',
    'credentials' => array(
        'key' => '',
        'secret' => '',
    ),
    'version' => 'latest'
));
$pamans = array(
    'Bucket' => 'test',
    'Key' => 'hello.txt',
    'Body' => 'Hello World',
    'CacheControl' => 'max-age=1',
);
$s3->putObject($pamans);

実行してみるとエラーが出ます。

Could not resolve host: test.localhost [url] http://test.localhost:4572/hello.txt

hostsが解決できないようなので、/etc/hosts

127.0.0.1 test.localhost

を追記します。

再度実行してみるとエラーが出ずに終了し、ファイルが作られていることが確認できました。

$ php put.php
$ aws --endpoint-url=http://localhost:4572 s3 ls s3://test/
2017-12-08 21:00:00         11 hello.txt

javascriptやgoを使う方法を参考にしていると

  • s3ForcePathStyle
  • endpoint

のオプションを設定してあげればいいと出るのですが、 aws-sdk-php には s3ForcePathStyle というオプションが無いです。
s3ForcePathStyle というオプションを設定しないでアップロードしようとすると、先ほどのエラーにも出てましたが、s3バケット名.localhost:4572 となり、hostsが解決できなくなってしまいます。

php と localstack を使って開発したい場合は hosts を書いてあげて localstack が提供する各URLを指定してあげないといけなさそうです。

参考

Testable Lambda: Working Effectively with Legacy Lambda // Speaker Deck

Testable Lambda|AWS Summit Tokyo 2017 - YouTube

LaravelでS3への画像アップロードをLocalStackを使って開発 - Qiita

S3互換のストレージソフトMinioを試してみる | モテモテテクノロジーブログ

docker exec で cd する方法

dockerを使っていて、普段は↓のコマンドでコンテナ内に入って作業してます。

$ docker exec -it {container-name} /bin/bash

最近はコンテナ内でお決まりの作業(特定のディレクトリに移動してコマンド実行など)をしたいときに

$ docker exec -i {container-name} "cd /path/to && ls"

の様な形で実行できると思っていたのですが、実際は違っていて上手くいきませんでした。
↓の様なエラーが出ました。

$ docker exec -i {container-name} "cd /var/log && ls"
oci runtime error: exec failed: container_linux.go:265: starting container process caused "exec: \"cd /var/log && ls\": stat cd /var/log && ls: no such file or directory"

cd する方法

$ docker exec -i {container-name} /bin/bash -c "cd /path/to && ls"

という風に、 /bin/bash -c としてその後に実行したいコマンドを書いてあげると動く。

参考

docker-exec failed: "cd": executable file not found in $PATH - Stack Overflow

ISUCON7予選に参加しました

id:Maco_Tasu に、ISUCONに一緒に出ませんかと誘ってもらえたのでいい機会だしと思い参加しました。

LabALICEというチームで、メンバーはid:Maco_Tasu, id:karia です。

macotasu.hatenablog.jp

karia.hatenablog.jp

全員のスコアと順位を出して頂けて運営の方には感謝です。

isucon.net

最終スコアは 79,590 で、43位でした。

当日私は、画像周りの修正をしたのでそのあたりについて記録を残しておこうと思います。

事前準備

チームで3回集まって過去問を解きました。
解いた過去問は

  • isucon6予選
  • isucon4予選
  • pixiv社の社内向けisucon

です。

過去問を解くことで全体的な流れを掴むことができました。
そこで一番感じたのが、競技中にNginxの設定などを動作確認しながら作成してると時間がもったいないということでした。
NginxやMySQLなど、あらかじめ使うであろう物の設定ファイルはひな形を作っておこうとチームで話あって事前準備をしていました。

当日

最初の役割分担は id:Maco_Tasugithubへのソースコード登録、id:karia がインフラの整備となっていました。
私は、DBテーブルの確認をしてからアプリを触って動作確認をしていました。
今回のお題はチャットアプリで、画像の数が多いためNginxから返せれば速くなるのかなと思いながら触ってました。

一通りアプリを触ったので、ソースコードの確認に入りました。
ソースコードを見ると、pixiv社の社内向けisuconと同様に画像をMySQLに入れている部分が目に付きました。

予選の環境はDBサーバー1台とAPサーバー2台の構成でした。
画像はMySQLにいれるのを止めてローカルに保存してNginxから返す様にするというのはすぐに思いつきました。
問題はどのサーバーで保存するかというところで少し考えました。

  1. APサーバーからDBサーバーのMySQLに画像を保存するのを止める
  2. DBサーバーのローカルに画像を保存する

と考えたときに、DBサーバーのローカルに保存したいならDBサーバーをフロントにして、画像の保存はDBサーバーがやってそれ以外の処理は後ろのAPサーバーに流してあげれば良いんじゃないかと考えてインフラの構成を変更しました。

インフラの変更は id:karia にお願いをして、アプリの改修をしました。
改修内容は下の2つです。

  • 画像の登録/更新があったらMySQLには保存せずにローカルに保存する
  • 初期にDBに登録されている画像をローカルに書き出しておく

この後ベンチを回したりして動作を確認していたのですが、どうもDBサーバーの負荷が高くフロントと兼任させるのは良くないという事がわかってきました。
ここで、構成を変更することにしました。

次に試した方法は AP1台をフロントにして、フロント・AP・DBがそれぞれ1台づつの構成です。

  • フロントサーバーは、追加された画像のローカル保存
  • APは、画像処理以外の実行

に担当を分けてみました。

この構成だとDBに余裕ができたので、スコアを上げることができました。
その後は、GET /message GET /history/:channel_id のクエリを直したりということをしていました。

結果は予選敗退でしたが、気づきがとても多く参加して良かったと思います。
運営の皆様ありがとうございました。
id:Maco_Tasu, id:karia チームを組んでくれてありがとうございました。

学び/勘違いしていたこと

  • DBの接続設定

リクエストごとにコネクションが作られる

def db
  return @db_client if defined?(@db_client)

  @db_client = Mysql2::Client.new(

コネクションの使い回しができる

def db
  return Thread.current[:isuconp_db] if Thread.current[:isuconp_db]
  client = Mysql2::Client.new(
  • MySQLのコネクションがちゃんと閉じられていることを確認するのは大切

javascriptのスプレッド演算子(...)

javascriptで関数を呼び出すところで、引数に ... と書いているソースコードを見かけるがこれが何か調べてみた。

ES2015から追加された、スプレッド演算子というらしい。

参考

「…」←これ、ただの省略記号かと思ってました。(Spread operatorのお話)|もっこりJavaScript|ANALOGIC(アナロジック)

スプレッド演算子 - JavaScript | MDN

TypeScript2からのReactとnon-null assertion

TypeScript2からコンパイルオプションの strictNullChecks で、nullability をチェックできるようになった。
最近になってこのオプションを有効にしたら意外と修正するところが多かったのでメモ。

TypeScript1系の時は↓の様に書いてました。

import * as React from 'react'
import * as ReactDom from 'react-dom'

interface IAProps {
}
interface IAState {
  hoge?: string;
  fuga?: number;
}
class A extends React.Component<IAProps, IAState> {
  constructor(props: IAProps) {
    super(props);

    this.state = {
      hoge: '',
      fuga: 0
    }
  }

  render() {
    return(
      <B
        hoge={this.state.hoge}
        fuga={this.state.fuga}
      />
    );
  }
}

interface IBProps {
  hoge: string;
  fuga: number;
}
interface IBState {
}
class B extends React.Component<IBProps, IBState> {
  constructor(props: IBProps) {
    super(props);

    this.state = {
    }
  }

  render() {
    return(
      <div>
        {this.props.hoge}
        <br />
        {this.props.fuga}
      </div>
    );
  }
}

TypeScriptでstate管理するときは、毎回全部のパラメータを更新したいわけではないので

interface IAState {
  hoge?: string;
  fuga?: number;
}

という感じに? を付けてオプショナルにしていました。

そうすると、stateで管理しているhoge fugaの型が

hoge: string | undefined
fuga: number | undefined

と、それぞれ undefined になる可能性があるということになる。

Component B に値を渡すとき、Component B側では

interface IBProps {
  hoge: string;
  fuga: number;
}

というようにundefinedは許容されないので、

  render() {
    return(
      <B
        hoge={this.state.hoge!}
        fuga={this.state.fuga!}
      />
    );

!を付けて、non-nullであることを明示してあげないといけない。

最終的にソースコードはこうなりました。

import * as React from 'react'
import * as ReactDom from 'react-dom'

interface IAProps {
}
interface IAState {
  hoge?: string;
  fuga?: number;
}
class A extends React.Component<IAProps, IAState> {
  constructor(props: IAProps) {
    super(props);

    this.state = {
      hoge: '',
      fuga: 0
    }
  }

  render() {
    return(
      <B
        hoge={this.state.hoge!}
        fuga={this.state.fuga!}
      />
    );
  }
}

interface IBProps {
  hoge: string;
  fuga: number;
}
interface IBState {
}
class B extends React.Component<IBProps, IBState> {
  constructor(props: IBProps) {
    super(props);

    this.state = {
    }
  }

  render() {
    return(
      <div>
        {this.props.hoge}
        <br />
        {this.props.fuga}
      </div>
    );
  }
}

参考

15K 行のアプリを TypeScript 1.8 から 2.0 に移行してみた - はやくプログラムになりたい

go言語をインストールしてvimで保存時にコードフォーマットを走らせるまでの設定

Macの環境でgo言語をインストールして、vimソースコード保存時に自動でコードフォーマットが走るまでの環境設定です。

普段はIDEを使うので、vimでファイル保存時に go fmt を実行させる設定に手間取ってしまったのでメモしておきます。
vimプラグイン追加方法がわからなかっただけです。

go言語のインストール

$ brew install go

ここまでで、go言語を書いてコンパイルして実行まではできるようになります。

go言語は標準で go fmt でコード整形できるのですが、vimでファイル保存時に go fmt を実行して欲しいのでその設定方法についてです。

vimで go fmt するまでの設定

1. vim-plugをインストールする

$ curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

2. ~/.vimrcプラグインを読み込むようにする

~/.vimrc に下記3行を追加

call plug#begin('~/.vim/plugged')

Plug 'fatih/vim-go'

call plug#end()

3. vim をリロードして :PlugInstall を実行

ここまでの設定で vimでファイル保存時に自動で go fmt が実行されるようになります。

AWS CloudFormationでECSを管理するときにハマったこと

CloudFormationでECSを構築したときにハマったメモです。

CloudFormationのテンプレートを書いて

  • AWS::ECS::Cluster
  • AWS::ECS::Service
  • AWS::ECS::TaskDefinition

あたりの設定をして、構築しました。

動作確認ができたので、AWS::ECS::Service の設定を更新しました。
更新をしてみたところ、更新中のステータスから一向に進みませんでした。

テンプレートを確認すると、↓のようになっていました。
(下のテンプレートは余分な部分は書いてません)

  Service:
    Type: AWS::ECS::Service
    Properties:
      DeploymentConfiguration:
        MaximumPercent: 100
        MinimumHealthyPercent: 50

MaximumPercent: 100 ということで、新しいコンテナを追加できないのに追加しようとして動かない状態でした。 この状態だと、CloudFormationのタイムアウトを設定しないとずっと、更新中のステータスになってしまいます。

管理画面から手動でサービスの更新でタスク数を0にしてあげれば解決できました。