2018|sitateru tech blog

sitateru tech blog

シタテルの技術やエンジニアの取り組みを紹介するテックブログです。

2018年12月28日金曜日

WOVNさんのオフィスに遊びに行ってみた!

こんにちは!
シタテル株式会社CTOの和泉です。

WOVNさんのオフィスに遊びに行ってみた!

https://wovn.io/

images/image-1.png

WOVNはウェブページなどの多言語化ソリューションを提供している企業です。自社のホームページを多言語化しようと思うと何かと考えないといけないことが多くて大変なのですが、WOVNは特許を持つ技術なども組み合わせてスマートに解決するソリューションを提供しています。

先日、CTOのジェフさんにお会いして、遊びに行かせてください!と押しかけました。最近オフィスに押しかけるのが小さなマイブーム。

オフィス

第一印象。ピザパーティーができそう!

images/image-4.png

写真のセンスがなさすぎて申し訳ないぐらい伝わらないのですが、、、

写っている場所は中央にあるフリーアドレスの 執務スペースです。この両側にも執務スペースがあり、そこはオフィススタイルにテーブルが並んでいます。

左下のテーブルにお菓子がありますね。
気になって横に振り向くと、、、

images/image-3.png

社内セルフコンビニ!!
頭使うと糖分が欲しくなったりするのでこれは嬉しい。

僕は食べ過ぎるタイプなのでご利用は計画的にしないといけなくなりそうですが。

多言語化をサポートしている企業ですので、社員にも外国籍出身の方が多くおられます。

images/image-5.png

クリスマスツリーもしっかりと飾ってありました

教えてもらったこと

WOVNが提供するサービスについて、営業の長谷川さんにいろいろと教えてもらいました。

まず冒頭にも紹介したとおり、ウェブページの多言語化をクラウド上で一元管理しながら行えるツール群を提供してくれています。

このツール群がスグレモノで、ページの文章を抜き出して翻訳しやすい単位に分割してくれたり、HTMLのタグで分断されている文字列を再構成してくれたりします。分割した文字列は機械翻訳で一括翻訳。もちろん翻訳を独自に修正することも出来ます。また、企業独自の 言葉を優先的に翻訳する辞書の定義もできます。

ページへの組み込みは、Javascriptで動的に 他の言語に書き換える方法だけでなく、検索エンジン最適化(SEO)ができる形で言語ごとのプラグインも提供されていて、ユーザーのニーズに合わせた方法が選べるようになっています。不勉強で知らなかったのですが、実はネイティブアプリにも対応しています。そしてそれらは全てクラウド上の単一リソースとして管理できる。素晴らしい!

そしてそして、単にツールを提供してくれるだけではなく、実は人手による翻訳もツールに統合された形で簡単に依頼できるようになっています。翻訳会社を探して対訳表のエクセルを作ってといった煩雑な作業が無くなるのはすごく嬉しい !

気になるお値段

価格についても教えていただきました。

今は基本的には固定の値段表は無く、使用するサイトの規模感に合わせて見積もりをする形を取られているとのこと。スタートアップで資金に不安がある場合は、こういうふうな使い方すると費用が抑えられますよとか、アドバイスまでしてくれる親切対応。

実はシタテルでもリアルに導入を検討しているのでとても参考になりました。

まとめ

機能から詳しい使い方、金額感までとても詳しく教えていただきました。
訪問ブログの公開もご快諾いただけて感謝感謝。

冒頭に紹介したCTOのジェフさんを始め国外の方も多くおられのも理由かなと思うのですが、
やはり インターナショナルでオープンな雰囲気のあるオフィスでした。

シタテルのウェブページが多言語対応する日も近いかも!
(自分やれっていう戒め)

繰り返しになりますが、ご対応いただいたジェフさん、長谷川さん、ありがとうございました!!

, , , , , ,

2018年12月27日木曜日

コンソール上でディレクトリの中身を樹形図表示する - tree コマンドの紹介

こんにちは! シタテルエンジニアの諏訪です。

今日はコンソール画面で使う tree コマンドについてご紹介します。

コンソールでディレクトリの中身をみる

ファイルの一覧表示

みなさんはコンソールを使っていてあるディレクトリ内のファイルの一覧がほしいときに
どんなコマンドを打ちますか?

たいていは ls コマンドを使うと思います。

……コンソールを使う人が最初に覚えるコマンドの一つですね!

$ ls
Gemfile        Rakefile    config        lib        public        tmp
Gemfile.lock    app        config.ru    log        storage        vendor
README.md    bin        db        package.json    test

これだと一覧性に欠けてちょっと見づらいので -l オプションを付ける方も多いでしょう。

$ ls -l
total 56
-rw-r--r--   1 p-san  wheel  2210 12 27 12:34 Gemfile
-rw-r--r--   1 p-san  wheel  5360 12 27 12:34 Gemfile.lock
-rw-r--r--   1 p-san  wheel   374 12 27 12:34 README.md
-rw-r--r--   1 p-san  wheel   227 12 27 12:34 Rakefile
drwxr-xr-x  10 p-san  wheel   320 12 27 12:34 app
drwxr-xr-x   9 p-san  wheel   288 12 27 12:34 bin
drwxr-xr-x  16 p-san  wheel   512 12 27 12:34 config
-rw-r--r--   1 p-san  wheel   130 12 27 12:34 config.ru
drwxr-xr-x   3 p-san  wheel    96 12 27 12:34 db
drwxr-xr-x   4 p-san  wheel   128 12 27 12:34 lib
drwxr-xr-x   3 p-san  wheel    96 12 27 12:34 log
-rw-r--r--   1 p-san  wheel    67 12 27 12:34 package.json
drwxr-xr-x   9 p-san  wheel   288 12 27 12:34 public
drwxr-xr-x   3 p-san  wheel    96 12 27 12:34 storage
drwxr-xr-x  11 p-san  wheel   352 12 27 12:34 test
drwxr-xr-x   5 p-san  wheel   160 12 27 12:34 tmp
drwxr-xr-x   3 p-san  wheel    96 12 27 12:34 vendor

ディレクトリ直下のファイルを確認するにはこれでよいのですが

時として、サブディレクトリの中のファイルも同時に確認できると便利なことがあります。

新米エンジニアPさんの奮闘記を見てみましょう。

ファイルを探せ!

登場人物

  • 【Pさん】
      開発チームにジョインしたばかりの新米エンジニア。
      シェルコマンドは cd と ls しか知らないくせに
      やたらとコンソール画面を使いたがる、ちょっとクセのある新人。

Pさん初めてのタスク

Pさんの仕事初日。ソースコードの構成もよくわかっていない中で最初のタスクが飛び込んできました。

── Pさん、早速で悪いけど、404.html をちょっと修正してもらえる?

── は〜い、わかりました〜!

元気よく返事をしたものの、その 404.html とやらは一体どこにあるのでしょう?

開封したての PC にまだエディタすら入れていないPさん。
とりあえずコンソール画面で ls コマンドを打ちまくって手当たり次第に探すことにしました。

── えーっと、エラー画面のソースだから…… view ディレクトリの中にあるんだよねきっと。

$ cd app/views/
$ ls -l
total 0
drwxr-xr-x  2 p-san  wheel  64 12 27 12:34 item
drwxr-xr-x  5 p-san  wheel 160 12 27 12:34 layouts
drwxr-xr-x  2 p-san  wheel  64 12 27 12:34 message
drwxr-xr-x  2 p-san  wheel  64 12 27 12:34 user

ありません。

── おや、、、フォルダしかないじゃん。。。すると layouts の中かな。。。

$ cd layouts/
$ ls -l
total 24
-rw-r--r--  1 p-san  wheel  347 12 27 12:34 application.html.erb
-rw-r--r--  1 p-san  wheel  229 12 27 12:34 mailer.html.erb
-rw-r--r--  1 p-san  wheel   13 12 27 12:34 mailer.text.erb

ありません。

── あれれ。。。あー、さては assets の中だな(錯乱)

$ cd ../../assets/
$ ls -l
total 0
drwxr-xr-x  3 p-san  wheel   96 12 27 12:34 config
drwxr-xr-x  3 p-san  wheel   96 12 27 12:34 images
drwxr-xr-x  8 p-san  wheel  256 12 27 12:34 javascripts
drwxr-xr-x  6 p-san  wheel  192 12 27 12:34 stylesheets

やっぱりありません。

── これはもう素直に find コマンドを使うしか…

$ cd ../../
$ find . -name '404,html'

(何も表示されない)

── 見つから…ない…… あっ、見つからないから 404 なんだ。なんつってハハハ(錯乱)

(注)錯乱したPさんはタイプミスをしていますが、本人気づく様子がありません。

コンソールの敗北

結局Pさんは、cd コマンドを使って 404.html を探すのを諦め
GUI なエディタをインストールしました。

エディタにはファイルを樹形図表示する機能があって
それを使うと一瞬で目的のファイルが見つかったのでした。

── はぁ〜意外と浅いところにあったのね。。。もうコンソールきらい。使うのやめよ。。。

やっぱり GUI ソフトにあるようなファイルの樹形図表示機能はとても便利です。
サブディレクトリも含めてファイルを一覧表示してくれるから。

Mac や Windows を使っていれば Finder や エクスプローラその他 GUI ソフトを使えばいいのですが
クラウドサービス上の仮想マシンに SSH でつなぐ場合など
シェルだけで操作を完結させないといけない場面も多いです。

……上の物語に出てきた新人Pさん、今後の仕事でちょっと苦労しそうですね。

コンソール画面でも、こんなふうに樹形図表示することができたらいいのに。。。

コンソールでディレクトリの中身をみる(再)

ファイルの一覧表示 (樹形図)

問題提起したばっかりですが結論です。樹形図表示、 実はできるんです

ご覧いただきましょう。tree コマンドです。

$ tree
.
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
│   ├── assets
│   │   ├── config
│   │   │   └── manifest.js
│   │   ├── images
│   │   ├── javascripts
│   │   │   ├── application.js
│   │   │   ├── cable.js
│   │   │   ├── channels
│   │   │   ├── item.coffee
│   │   │   ├── message.coffee
│   │   │   └── user.coffee
│   │   └── stylesheets
│   │       ├── application.css
│   │       ├── item.scss
│   │       ├── message.scss
│   │       └── user.scss
│   ├── channels
│   │   └── application_cable
│   │       ├── channel.rb
│   │       └── connection.rb
│   ├── controllers
│   │   ├── application_controller.rb
│   │   ├── concerns
│   │   ├── item_controller.rb
│   │   ├── message_controller.rb
│   │   └── user_controller.rb
│   ├── helpers
│   │   ├── application_helper.rb
│   │   ├── item_helper.rb
│   │   ├── message_helper.rb
│   │   └── user_helper.rb
│   ├── jobs
│   │   └── application_job.rb
│   ├── mailers
│   │   └── application_mailer.rb
│   ├── models
│   │   ├── application_record.rb
│   │   └── concerns
│   └── views
│       ├── item
│       ├── layouts
│       │   ├── application.html.erb
│       │   ├── mailer.html.erb
│       │   └── mailer.text.erb
│       ├── message
│       └── user

      :
    (省略)
      :

├── public
│   ├── 404.html
│   ├── 422.html
│   ├── 500.html
│   ├── apple-touch-icon-precomposed.png
│   ├── apple-touch-icon.png
│   ├── favicon.ico
│   └── robots.txt

      :
    (省略)
      :

300 directories, 1311 files

どうですかこの見事な樹形図!!! (グルメリポート風に)

深さを指定する

デフォルトだと上のように、カレントディレクトリから 3-階層目まで表示されます。
これだとちょっと多すぎる(or 少なすぎる)という場合は -L オプションで階層の深さを指定できます。

例: 2-階層目まで表示

$ tree -L 2
.
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
│   ├── assets
│   ├── channels
│   ├── controllers
│   ├── helpers
│   ├── jobs
│   ├── mailers
│   ├── models
│   └── views

      :
    (省略)
      :

├── public
│   ├── 404.html
│   ├── 422.html
│   ├── 500.html
│   ├── apple-touch-icon-precomposed.png
│   ├── apple-touch-icon.png
│   ├── favicon.ico
│   └── robots.txt
├── storage

      :
    (省略)
      :

33 directories, 35 files

変更時間順にソートする

-t オプションを指定すればファイルの変更時間順に並べ替えて表示することができます。

$ tree -t
.
├── Gemfile
├── README.md
├── Rakefile
├── config.ru
├── app
│ ├── assets
│ │ ├── config
│ │ │ └── manifest.js
│ │ ├── images
│ │ ├── javascripts
│ │ │ ├── application.js
│ │ │ ├── cable.js
│ │ │ ├── channels

      :
    (省略)
      :

300 directories, 1311 files

ディレクトリを指定する

ls コマンドがそうであるように、対象ディレクトリを指定して表示させることもできます。

$ tree app/assets/
app/assets/
├── config
│   └── manifest.js
├── images
├── javascripts
│   ├── application.js
│   ├── cable.js
│   ├── channels
│   ├── item.coffee
│   ├── message.coffee
│   └── user.coffee
└── stylesheets
    ├── application.css
    ├── item.scss
    ├── message.scss
    └── user.scss

5 directories, 10 files

このほかにもさまざまなオプションを指定できます。

インストール・Tips

For Linux

Linux の場合は yum でインストールします。

yum install tree

インストールできたことの確認:

$ tree --version

バージョンが表示されれば OK 。

For Mac

Mac の場合は homebrew でインストールできます。

$ brew install tree

インストールできたことの確認:

$ tree --version

バージョンが表示されれば OK 。

Tips (文字化けするとき)

Linux だと罫線が文字化けすることがあります。

$ tree
.
tqq 404.html
tqq 422.html
tqq 500.html
tqq apple-touch-icon-precomposed.png
tqq apple-touch-icon.png
tqq favicon.ico
mqq robots.txt

そんなときは --charset=C を指定すると OK です。

$ tree --charset=C
.
├── 404.html
├── 422.html
├── 500.html
├── apple-touch-icon-precomposed.png
├── apple-touch-icon.png
├── favicon.ico
└── robots.txt

おわりに

tree コマンドによるコンソールでの樹形図表示、いかがでしたでしょうか。

便利だなと思った方、ぜひ今日インストールして使ってみてくださいね。

一度インストールしておけばずっと使えます。

, , , , ,

2018年12月26日水曜日

プロジェクトの振り返りをやってみた

kaizen
こんにちは、シタテルの藤本です。
主にSCS(Sitateru-Control-System)という生産管理システムのバックエンド(Rails)を担当しています。

シタテルに入って小さい機能の追加、改修を行ってきたのですが
最近要件定義からリリースまでが3ヶ月程度のプロジェクトが終了しました。
そのプロジェクトに対して振り返りを行ってみたので内容について書いてみたいと思います。

やり方

シタテルの開発では2週間のタイムボックスで動いており、最終日にKPTにて振り返りを行っています。
今回のプロジェクトの振り返りでは繰り返し性がないことから
KPTの拡張版でやってみました。参考にさせていただいたのは以下です

プロジェクトの振り返り(KPTの様な何か)

各種情報

参加人数:4人
場所:熊本(2拠点)、東京
テレビ電話:Zoom
ボード:Google スプレッドシート

KPTから追加されている項目

KPTとの違いとして以下の項目が追加しております

  • Good
    よかったことをあげる、続けたいものをKeepにする
  • Issue
    Problemから導き出された課題
  • Risk
    今回具体的には発生していないが危ないと考えられること、将来起こりそうな問題
  • Solution
    IssueとRiskに対しての解決策を考える
  • Task(今回は対象外の項目とした)
    Solutionから導かれる具体的なアクション

進行順序

  1. Good、Problem、Riskを考えて、付箋に記載
  2. 各々の内容を確認してグルーピング
  3. GoodをKeepにするものを選択
  4. Problem(Risk)をIssueにするものを選択
  5. 4.で選択したものからIssueを考える
  6. Issueを共有する
  7. Solutionを話し合いながら出し合う
  8. 次に対応するSolutionを決める

付箋を張っていったボードは以下のような形です
board

やってみて

アウトプット

Good:14
Keep:4(Goodからグルーピングしている)
Problem:21
Risk:5
Issue:22

対象としたProblemとRiskは共に1つで
Solutionを合わせて6つ出し合った

よかったこと

  • みんなが考えていたことが共有できる
    自分の考えだけだと狭い範囲になりますがみんなの考えていること出してもらい共有することで
    各々の考えが掛け算になりより良いSolutionを出すことができた
  • 単発ものの振り返りの形式としては良い
    KPTだと繰り返すことで仕分けを流れになっていますが今回の形式では
    「続けたいこと」と「原因となったこと」を明確にして解決策を導くことができた
  • Solutionを話し合いながら考えることができて前向きになる
    みんなが思っていた問題はたくさん出たけど最後にSolutionについて議論をしていると
    前向きに次はもっと良くしよう、良くできると思うことができた

反省点

  • 予定した時間が甘すぎた
    1時間は流石に少なすぎました、大幅にオーバーしてしまい倍の2時間かかってしまいました
    (参加した方には申し訳なかったです、かつ快く続行していただき、ありがとうございました!)
    単発での実施かつ対象とする期間が大きいので最初から時間を長めに取っておくべきでした
  • 各項目の意味がパッとわからない
    ProblemとRiskってどう違うということが出たりしました、事前にちょっと時間をとり
    認識を合わせておいたほうがスムーズに入ることができたかなと思いました
  • 思い出す時間がなかった
    Good、Problem、Riskについて事前にちょっと考えておくとかのアナウンスをしていなかったので
    思い出す時間がちょっと少なすぎかなと思いました

まとめ

プロジェクトを振り返ることでよかったこと、問題となったことを再認識しその解決策まで
出すことができたことはとてもよかったと思います。
ただし出してよかったで終わると何も変わっていかないので次やる時に
今回上がったことを見直して実際の行動に移して、少しでも良いプロジェクトにしていきたいと思います。

, , , , , , ,

2018年12月25日火曜日

[第一回]SansanとHubSpotの連携

こんにちは、シタテルエンジニアの工です!
今回は、SansanとHubSpotの連携の仕方について書いていきます 🚀

はじめに

シタテルでは、衣服生産をSCS、マーケティング・営業の部分をHubSpotを使って管理しています。

HubSpotを導入する際に、Sansanの情報をHubSpotに同期したいという要望があったので、SansanのAPIとHubSpotのAPIを組み合わせて同期処理を実装しました。

AWSLambdaを使って、定期的に同期処理を実行しています。

HubSpot公式にもSansanとハブスポットの連携の仕方という記事も公開されていますので参考にしてみてください。

Sansanの名刺情報をHubSpotにインポートする

使うもの

余談ですが実装したときAWSLambdaにはRubyの選択肢がなかったのですが、今は使用できるみたいですね。
普段はRubyを使うことが多いので、次にLambdaを使うときはRubyで実装してみたいです。

Sansanの情報を取得

公式ドキュメント Sansan Open API

handler.js

const axios = require('axios')
const moment = require('moment')
axios.defaults.headers.common['X-Sansan-Api-Key'] = process.env.SANSAN_API_KEY
axios.defaults.headers.get['Content-Type'] = 'application/json'

module.exports.importSansanData= async (event, context, callback) => {
  var updatedFrom = moment().subtract(2, 'hours').format("YYYY-MM-DDTHH:mm:ss")+"Z"
  var updatedTo = moment().format("YYYY-MM-DDTHH:mm:ss")+"Z"

  await axios.get(`https://api.sansan.com/v2.0/bizCards?updatedFrom=${updatedFrom}&updatedTo=${updatedTo}&range=all`)
      .then(function (response) {
        importHubspot(response)
        callback(null, response.data)
      })
      .catch(function (error) {
        callback(error)
      });
};

詳しくはこのあたりを参考にしてください。
AWSLambdaとServerlessを使ってみる[第1回]
AWSLambdaとServerlessを使ってみる[第2回]

日時取得のところがとてもブサイクです... 😢
なぜかタイムゾーンZにしないとうまくいかなっかった 🤔
本当は+09:00に設定したい。

var updatedFrom = moment().subtract(2, 'hours').format("YYYY-MM-DDTHH:mm:ss")+"Z"
var updatedTo = moment().format("YYYY-MM-DDTHH:mm:ss")+"Z"

HubSpotにインポート

取得したSansanデータをインポート。
propertyは数が多いのでほとんど割愛して記載します。

公式ドキュメント HubSpot Developers

handler.js

function importHubspot (response) {
  response.data.data.forEach(createContact)
}

function createContact (value, index) {
  var properties = [
      {
        property: 'email',
        value: value.email
      },
      {
        property: 'firstname',
        value: value.firstName
      },
      {
        property: 'lastname',
        value: value.lastName
      }
    ]

  axios.post(`https://api.hubapi.com/contacts/v1/contact?hapikey=${process.env.HUBSPOT_API_KEY}`, {
    properties: properties,
  })
  .then(function (response) {
    console.log(response.data)
  })
  .catch(function (error) {
    console.error(error.response.data)
  });
}

上の例はひとつひとつPOSTしていますが、こちらのAPI使えば1回のPOSTでまとめてCreateできます。
Create or update a group of contacts | Contacts API

CloudWatchでスケジュールを設定

serverless.ymlにスケジュールを設定します。
2時間おきにインポート 🚀

serverless.yml

functions:
  importSansanData:
    handler: handler.importSansanData
    events:
      - schedule: cron(0 */2 * * ? *)

これで2時間おきに、Sansanの名刺情報がHubSpotのコンタクトに同期されます!👏

最後に、deploy 🚀

serverless deploy -v

まとめ

今回はSansanとHubSpotのAPIを使って、Sansanの名刺情報をHubSpotのコンタクトにインポートする方法について書きました。

SCSと外部サービスとの連携は今後もどんどん加速していくと思います!
マーケティング・営業 -> 衣服生産 -> 請求この流れがシステムでシームレスに実現できるように開発中です 💻

, , , , , , ,

2018年12月21日金曜日

金沢で開催された IVS CTO Night & Day に参加してきました!

こんにちは!
シタテル株式会社CTOの和泉です。

12月17〜19の3日間、金沢で開催された IVS CTO Night and Day 2018 Winter powered by AWS に参加してきました。

images/image1.jpg

どんな会なのか? 詳しくはこちら!
CTO Night and Day 2017
(リンク先は昨年のレポートです)

新旧大小さまざまな企業のCTOがずらり100人以上。

前夜祭に始まり、AWSの最新技術や文化紹介、選ばれた15社の技術やマネジメント紹介(登壇しました)、先輩CTOとの公開メンタリング、ダンスパーティーまで盛りだくさん。

「CTOがいなくなって8ヶ月がたった話」「CTOの休み方」など、CTOにまつわる、CTOならではの話題や悩み相談が随所で繰り広げられていました。

事業を進めていくのは本当にエネルギーが必要で、でも皆さん熱量高く真摯に取り組んでおられて本当に刺激になりました。

40名を超える方と名刺を交換して事業について意見交換させていただいて素晴らしい出会いがたくさんありました。

そして、本当にAWSの運営の方々のホスピタリティが半端ない!

私も事前に伝えていた事業内容に対してこの人が最適だろうという方を紹介いただいて、有意義なディスカッションが出来ました。

来年もできたら参加したい!頑張ろう!とめちゃくちゃテンションが上りました。

反省

話すのに忙しすぎて写真を全然撮っていませんでした、、、

, , , ,

2018年12月20日木曜日

プロジェクトごとに環境変数/クレデンシャルを管理する

shell
unsplash-logoLauren Abrahall

sitateruでサービス・プロダクトを担当している北爪です。
インフラを見ることもあり、今日はShellでの環境変数の扱い方について書きます。

プロジェクトごとに環境変数/クレデンシャルを管理する

複数のプロジェクトに関わっているときに、プロジェクトごとの環境変数やAWSのkey/secretsなど環境変数に設定し管理する必要があることがあります。

Shellを使って管理する一つの方法を記述します。

準備

プロジェクトごとにクレデンシャルフェイルを作る

クレデンシャルファイル置き場を作ります

$ mkdir ~/.crds/

プロジェクトごとにプロジェクトファイルを作ったディレクトリ以下に置きます。
ディレクトリはinvisibleファイルにしておきます。

$ touch ~/.crds/pj-1

ファイルに必要なクレデンシャルを記述します

export AWS_ACCESS_KEY_ID==XXXXX
export AWS_SECRET_ACCESS_KEY==XXXXX
export AWS_DEFAULT_REGION=ap-northeast-1
export TF_VAR_secret_key=XXXXX
export TF_VAR_rds_password=XXXXX

読み込みと切り替え

設定した環境変数群を以下の方法でプロジェクトごとに読み込みます。
リセットはされないので、別プロジェクトに変える際は、あらたにshellを立ち上げるようにします。

プロジェクトのクレデンシャルを読み込む

source コマンドをつかってシェルに読み込めば(dot operatorでもOK)

$ source ~/.crds/pj-1

or

$ . .~/.crds/pj-1

別のプロジェクトのクレデンシャルを読み込む

複数のプロジェクトファイルを作ることで、クレデンシャルを別で読み込むことができます。

$ source ~/.crds/pj-2

Tips

初期読み込みが必要な場合は、 bashrcやzshrcなどに、読み込みコマンドを記述しておけます

まとめ

クレデンシャルファイルを特定のディレクトリに作り、基本コマンドで呼び出すだけです。一覧性も高く、管理しやすくなります。

, , , , ,

2018年12月19日水曜日

circleciでビルドしたあとにnetlifyへデプロイしてみた

こんにちは!

シタテルでエンジニアをしている建山です。

主に工場向けのマイオペというシステムの開発を行っています。

マイオペでは、インフラとして本番、ステージング、開発、feature環境を用意して開発していますが、新機能をテストする環境としてfeature環境を使用しています。
そして、feature環境にnetlifyというサービスを使用しています。

netlifyとは

Netlifyは静的なサイトをすばやく提供できるWebサービスです。フロントエンドのビルド、デプロイ、ホスティングのすべてを行ってくれます。

今回の概要

デプロイの流れとしては、github -> circleci ➔ netlify
となっています。netlifyにはビルドする機能が備わっているので、github -> netlify
が可能なのですが、マイオペでは、ビルドの際にjavaを使用していて、netlifyのビルドで対応されていないので、circleciでビルドを行ったあと、netlifyにデプロイすることにしました。

今回の前提

  • ビルドはcircleciでおこなう
  • ビルドが終わったものをnetlifyにデプロイする
  • 指定のサイトURLにデプロイする

「それ、netlifyじゃなくてAWS S3でよくない?」っていうツッコミはあるとおもいますが、まずは使ってみたということでご容赦ください。

方法

事前にnetlifyにサイトを作成してください。(画面やcliを使って作成できます)

netlify cliをインストールします。今回は、プロジェクトに
npm install --save netlify-cliでいれました。circleciのconfig.ymlのstepsにnpm install -g netlify-cliでインストールしてもいいと思います。

次に、circleciのconfig.ymlでデプロイの設定をします。
こんな感じで、インストールされたnetlify cliを使って、デプロイします。

<<他省略>>
# run deploy
- run:
    name: deploy
    command: |
    if [[ "$CIRCLE_BRANCH" =~ ^.*-netlify.* ]]; then
        node_modules/netlify-cli/bin/cli.js deploy --site-id $NETLIFY_SITE_ID -p ${SC_DIR}/ --access-token $NETLIFY_ACCESS_TOKEN
    fi

${SC_DIR} にはデプロイしたいディレクトリをいれてください。
$NETLIFY_SITE_ID$NETLIFY_ACCESS_TOKEN
はcircleciの環境変数にセットしてお使い頂いたほうがいいかと思います。

上の例だと、ブランチ名に"-netlify"が含まれているとnetlifyにデプロイされるようにしています。
実際には、利用状況にあわせてカスタマイズしてください。

たとえば、"abcd-netlify"というブランチでgithubにpushすると、

デプロイされます!

今回は同じURLにデプロイしていますが、デプロイのたびに違うサイトURLを発行してデプロイもできるようです。
netlifyの中で、ビルドもいろいろできるようになっているので、今回のように限定的な使い方ではなく、もっと活用できると思います。

とりあえず使ってみましたということで、またいい感じのCI/CDを実現できたら紹介します!

詳しくは公式ドキュメントをご覧ください。https://www.netlify.com/docs/

, , , , , , ,

2018年12月18日火曜日

FirebaseのSDKのプルリクを追ってみた

こんにちは、シタテルの鶴巻です。 devops&インフラ担当です。

最近、firebase/firebase-admin-goの実装で気になったところがあり、プルリクエストから経緯を知ることができました。

GitHub上のソースコードで気になった箇所の経緯を追うことは今後もあると思うので、整理がてら方法を紹介します。

要約

  • firebase/firebase-admin-goの実装で気になったところがあった
  • git blameコマンドで、ソースコードが最後に編集されたhashを特定
  • 対象のソースコードが追加されたプルリクエストで経緯が分かった

firebase-admin-sdk-goの実装で気になった箇所

firebase-admin-sdk-goは、名前の通りFirebaseサービスをGolangから扱うためのSDKです。

Firebase Authenticationを試してみた際に、弊社で使用しているRubyのSDKがなかったため、GolangのSDKの実装を少し読んでみました。

そこで、Googleのサーバから公開鍵を取得するメソッドの中で排他制御が行われていることに着目しました。
私はここで排他制御を実施している理由がわからなかったので、経緯を調べることにしました。

該当ソースコードは以下です。
https://github.com/firebase/firebase-admin-go/blob/master/auth/token_verifier.go#L66-L76

func (k *httpKeySource) Keys(ctx context.Context) ([]*publicKey, error) {
    k.Mutex.Lock()
    defer k.Mutex.Unlock()
    if len(k.CachedKeys) == 0 || k.hasExpired() {
        err := k.refreshKeys(ctx)
        if err != nil && len(k.CachedKeys) == 0 {
            return nil, err
        }
    }
    return k.CachedKeys, nil
}

プルリクエストを見つける手順

1. git blameコマンドで、対象のソースコードが最後に編集されたcommitのHash値を特定

  • 対象のソースコードが最後に変更されたcommiのHash値はf3174984ということが特定できます
    $ git blame auth/token_verifier.go | grep -e "k.Mutex.Lock()" -e "defer k.Mutex.Unlock()"
    f3174984 auth/crypto.go         (Hiranya Jayathilaka 2017-05-18 13:45:30 -0700  67)     k.Mutex.Lock()
    f3174984 auth/crypto.go         (Hiranya Jayathilaka 2017-05-18 13:45:30 -0700  68)     defer k.Mutex.Unlock()

2. GitHubの対象レポのプルリクエストをHash値で検索

実装の理由は?

  • プルリクエストのコメントにありました

  • 複数のスレッドが共有している公開鍵のキャッシュを同時に更新するのを防ぐためにmutexを導入したみたいです。

Introduced a mutex to prevent multiple threads from updating the shared cache concurrently.

  • ソースコードを追うと、公開鍵取得のメソッドは httpKeySource構造体に定義されており、さらにhttpKeySourceはClient構造体に定義されています。SDKを使う際に最初にClientの初期化を行いますが、初期化した同一のClientで公開鍵取得メソッドがconcurrencyに実行されても安全なように実装されているのかなと考察しました。

firebase-admin-sdk-goはgit-flow

firebase-admin-sdk-goはgit-flowで開発されているようです。

そのため、masterのソースコードのcommitのHash値がプルリクエストがマージされた時のHash値と同じで、プルリクエストが見つけやすかったです。

おまけ(nodejsの実装)

Node.jsのSDKの同じ箇所の実装についても少し覗いてみました。

Node.jsを書いたことはありませんが、見る限り排他制御はしていないように見えます。

Node.jsはシングルスレッド推奨だから排他制御しなくていいのか・・・など思い、言語の特性の違いが垣間見えて面白いですね。

まとめ

  • オープンソース文化いいですね。今回の例だとfirebaseの中の人が書いたソースコードや、そのプルリクを見ることができています(もしかしたら中の人じゃないかもしれませんが)。エンジニアとしては当たり前な文化ですが、ブラックボックス化されている業界が多いなか改めて良い文化だと思いました。
  • シタテルはRailsを使用しているので、RubyのSDKがあればFirebase Auth導入してみたかったのですが残念です。
, , , , , ,

2018年12月17日月曜日

【備忘録】Visual Studio Codeで文字検索範囲が広すぎるときに疑う箇所

こんにちは!
シタテル株式会社エンジニアのいしづかです。

みなさんはコードを書く時、どんなエディタを使ってらっしゃるでしょうか。

現在開発が続いているシタテルコントロールシステムのバックエンドはRailsで書かれています。

開発メンバーはそれぞれ自分が合うエディタを使っています。Ruby Mine使っている人もいますし、Atomもいたような・・・。

わたしはVisual Studio Codeを使っています。え?Vimであらずは人であらず?いや、まぁ、えへへ。

コードを書いているとコード内検索をかけたくなります。その際にはnode_modules配下とかは最初から外しておきたいものです。

今回はVisual Studio Code限定ですが、除外設定でハマったので備忘録としてその内容を書こうと思います。

Visual Studio Codeの除外設定

Visual Studio Codeの除外設定は大きく2つあり、 Files ExcludeSearch Exclude があります。

Files Excludeにパスを登録すると、その配下はエクスプローラー上にすら表示されません。Visual Studio Codeからは完全に管理対象外となり、当然検索にも引っかからなくなります。

Search Excludeにパスを登録すると、エクスプローラー上には表示されますが、検索に引っかからなくなります。

よって、検索の除外対象は「Files Exclude」+「Search Exclude」ということになります。

わたしがSearch Excludeに登録しているのは

の4つです。もうちょっと増やしてもいいかなと思うんですが、必要最低限で運用しています。

設定完了であるはずが、、、アレ、logの中身とか検索対象になってる。。。

パターンの書き方が間違っているのか?
設定する箇所が間違っているのか?

アレコレ試した挙げ句、最終的にたどり着いたのは

このボタンがONになっていないだけでした。。。

デフォルトではONでなにかのときにOFFにしちゃっていたのかもしれませんが、こんなの気づかないよVisual Studio Codeさん。。。

まとめ

何かを検索したとき、ほしいものがほしい分だけパッと手に入るっていいですよね。

検索範囲を適度なものに調整しておくのはコードを書くときのストレスを軽減してくれると思います。

かく言うわたしが、この除外設定が効いていなかった数十分間はストレスフルな状態でした。

他の開発メンバーがどんなチューニングを行っているかちょっと気になるところです。どんなプラグイン入れているかとか聞けたらレポートしたいと思います。

, , ,

2018年12月14日金曜日

いま話題の0円タクシーに乗ってみた!

こんにちは!
シタテル株式会社UI/UXデザイナーの堤です。

先日ずっと気になっていた0円タクシーを利用したのでレポートします。

0円タクシーとは

DeNAさんが運営する次世代タクシー配車アプリ「MOV(モブ)」と日清のどん兵衛とのコラボレーションと なっており、2018年12月5日から31日までで都内の対象エリアでのみ運行されています。

利用方法

  1. MOV(モブ)アプリをダウンロード
    https://m-o-v.jp/campaign/donbei/
  2. アカウント情報を登録
  3. 乗る場所と行き先を指定
  4. タクシー会社の 中から「0円タクシーby日清のどん兵衛」を選択
  5. 「タクシーを呼ぶ」であとは待つだけ

タクシー会社選択画面。



目的地選択画面。
目的地を入力したりピンを移動すると瞬時に距離やかかる時間が表示されます! すごい!!



いま呼べるタクシーが地図に プロットされてる画面。
このときは 10 〜15分ぐらい に 1、2台呼べる状態 となっていました。



手配中の画面。
一度だけこの状態からキャンセルとなってしまったので 競争率も激しそうです。



手配完了の画面。
地図上で普通のタクシーとの差別化がうまくできていて、なにより不安を与えず待ち時間も楽しめる画面になっているので素晴らしいです。



外装だけかと思いきや内装も凝ってました!MOVのロゴもかわいい。



移動中は終始CMが 流れていました。



乗車中の画面。
添えられてるメッセージも良い!



乗車完了後の画面。
0円!!!



乗るときも 降りるときもドライバーさんに写真撮っていいですか?と聞くと快くOKと 言っていただいたので10枚ほど写真を撮り、移動中は「どんな人が利用してますか?」や「ドライバーさん自身は損しない仕組みなんですか?」などの質問にも笑顔で答えていただき 、どん兵衛もタクシーも より好きになりました。

12月下旬に乗車すると、実際にどん兵衛をいただけるようなのでまた利用したいと思います!

, , , , , ,

2018年12月13日木曜日

ec2のインスタンスログインを簡単に管理する ~EC2 with Peco ~

sitateruでサービス・プロダクトを担当している北爪です。
インフラを見ることもあり、ec2サーバーのアドレス管理についてCLIで行う方法を紹介します。

目的

ec2のインスタンスはアドレスが構築するたびに変わり、負荷や新しいプロジェクトが立ち上がると増えたり減ったりします。
そのため、動的にサーバーアドレスを取得する必要があります。

AWS consoleに入ることなく、awsコマンドからサーバーリストを取得し、CLI filterのpecoを使って、SSHまでおこないます。

使い方

peco
Command+\ でpecoを起動

filtered
やじるしの ↑↓ か、キーワードで絞る
SSHするサーバーを選んでEnterを押す

ssh
SSHコマンドがはかれる

準備

zshを使っているのを前提に説明します。

必要ソフトのインストール

  • peco をインストールする
  • aws cli をインストールする
  • AWSのクレデンシャルを用意する

Shellの設定

ec2 コマンドを作る

AWS CLIから、ec2をリスト化するコマンドを作成し、shellに設定します。(zshrc)

alias ec2="aws ec2 describe-instances --query 'Reservations[].Instances[].{PublicDnsName:PublicDnsName,InstanceId:InstanceId,Tags:Tags[?Key==\`Name\`].Value|[0],InstanceType:InstanceType,State:State.Name}' --output table"

peco-ec2コマンドを作る

  • zshrcpeco-ec2 をつくります
  • ログインユーザーをdeployにしています
function peco-ec2 () {
  local selected=$(ec2 | peco --query "$LBUFFER")
  if [ -n "$selected" ]; then
    BUFFER="ssh deploy@$(echo $selected | awk 'match($0, /ec2.*\.amazonaws\.com/) {print substr($0, RSTART, RLENGTH)}')"
    zle accept-line
  fi
  zle clear-screen
}
  • さらに、好きなキーにバインドします。
  • ここでは、Command-\ につけています
zle -N peco-ec2
bindkey '^\' peco-ec2

まとめ

ec2をリスト化し、すぐにそこからsshを選べるようにしました。

, , , ,

2018年12月12日水曜日

Alexaスキルを作ろう

こんにちは。シタテルでエンジニアをしている熊谷です。

主にSPECというサービスのバックエンド開発(Rails)を行っています。

少し前にAlexaスキルの作成を試したので、その流れを簡単に説明していきます。

手順

1.Amazonアカウントを用意する

  • すでにamazon.co.jpの開発者アカウントがある場合は、それを使います。

  • お買い物用のAmazonアカウントを使用して、開発を開始できます。

  • Amazon Developerから、サインインして、情報を入力すると、開発用アカウントが登録されます。

    Amazon Developer

    ※ 画像のページで、ログインをして進みます。[Amazon Developerアカウントを作成]から進むと、amazon.com用のアカウントになってしまい、US向けのスキルになってしまうようです。

2.Alexa Developer Consoleにアクセス

3.[スキルの作成]をクリックし、スキル名などを入力する

スキルページ

4.スキルのページで、[呼び出し名]を設定する

5.エンドポイントを設定する

  • AWS Lambdaが推奨されているので、Lambdaでエンドポイントを作成するのが良いと思います。
  • リクエスト、レスポンスはJSON形式で送信します。
  • 作成したLambdaのARNを指定します。

6.インテントを作成する

  • [インテントを追加]をクリックし、カスタムインテントを作成します。

7.作成したインテントのページで、スキルの発話時に使用する、スロット(変数のようなもの)を設定する

  • スロットには、日付、数値、検索クエリー(文字列)などの型を指定できます

8.インテントを設定する

  • 作成したインテントの設定で、サンプル発話を登録します。
  • サンプル発話には上で作成したスロットを{スロット名}のような形で使用できます。

これで簡単なスキルを作成できました!

今回はざっくりした流れの紹介なので、ここまでにしたいと思います。

次回はLambdaの内容や、作成したスキルの公開、テストなどを説明していこうと思います。

, , , , ,

2018年12月11日火曜日

Active Storage移行記:データ移行編

こんにちは、あさのです。

前にActive Storageの記事を書きましたが、今回もそのネタで行こうと思います。

Active Storage移行記:バリデーション編|sitateru tech blog

今回は、Paperclip管理のデータをActive Storage管理に移行したときのお話です。

データ構造

Paperclipではファイルのデータは各モデルのカラムに保存されています。

userクラスのiconというファイルであれば、userテーブルに以下のようなカラムがあります。

  t.string "icon_file_name", comment: "ファイル名"
  t.string "icon_content_type", comment: "ファイルのcontent type"
  t.integer "icon_file_size", comment: "ファイルサイズ"
  t.datetime "icon_updated_at", comment: "ファイル更新日時"

対してActive Storageでは全クラスの全ファイルをまとめて2つのテーブルで管理します。

create_table "active_storage_attachments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
  t.string "name", null: false
  t.string "record_type", null: false
  t.bigint "record_id", null: false
  t.bigint "blob_id", null: false
  t.datetime "created_at", null: false
  t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
  t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end

create_table "active_storage_blobs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
  t.string "key", null: false
  t.string "filename", null: false
  t.string "content_type"
  t.text "metadata"
  t.bigint "byte_size", null: false
  t.string "checksum", null: false
  t.datetime "created_at", null: false
  t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end

たとえば、

  • userクラスのicon
  • user.id=15
  • ファイル名 prof.jpg

というファイルだと以下のようなレコードになります。
2つのテーブルは blob_id で関連づけられるんですね。

id    name    record_type    record_id    blob_id    created_at
15    icon    User    3    16    2017-12-28 19:23:03

id    key    filename    content_type    metadata    byte_size    checksum    created_at
16    3e0ca647-18e3-43ee-85c4-4de552bac46f    prof.jpg    image/jpeg    {}    44202    isFyYNJIs/6pyptfRUMUlA==    2017-12-28 19:23:03

データ移行

さて、それぞれのデータ構造がわかったのでマイグレーションをしていきます。

このあたりの記事を参考にさせてもらい、rakeタスクで実装しました。

namespace :import_to_active_storage do
  desc 'Import to ActiveStorage'
  task run_all: :environment do
    models = ActiveRecord::Base.descendants.reject(&:abstract_class?)
    import_data(models)
  end

  private

  def import_data(models)
    conn = ActiveRecord::Base.connection
    models.each do |model|
      attachments = model.column_names.map do |c|
        c =~ /(.+)_file_name$/ && Regexp.last_match(1)
      end.compact

      model.find_each do |instance|
        attachments.each do |attachment|
          puts "Process #{model.name}/#{instance.id}"

          next unless instance.send("#{attachment}?")
          next if conn.select_one("SELECT `id` FROM active_storage_attachments WHERE `record_type` = '#{model.name}' AND `record_id` = #{instance.id}").present?

          puts "Migrate #{instance.send(attachment.to_s)}"

          ActiveRecord::Base.transaction do
            execute_statement(conn, model, instance, attachment)
          end
        end
      end
    end
  end

  def execute_statement(connection, model, instance, attachment)
    active_storage_blob_statement(connection).execute(
      key,
      instance.send("#{attachment}_file_name"),
      instance.send("#{attachment}_content_type"),
      instance.send("#{attachment}_file_size"),
      checksum(instance.send(attachment)),
      instance.updated_at.strftime('%Y-%m-%d %H:%M:%S')
    )

    active_storage_attachment_statement(connection).execute(
      attachment,
      model.name,
      instance.id,
      instance.updated_at.strftime('%Y-%m-%d %H:%M:%S')
    )
  end

  def active_storage_blob_statement(connection)
    connection.raw_connection.prepare(<<-SQL)
      INSERT INTO active_storage_blobs (
        `key`, `filename`, `content_type`, `metadata`, `byte_size`, `checksum`, `created_at`
      ) VALUES (?, ?, ?, '{}', ?, ?, ?)
    SQL
  end

  def active_storage_attachment_statement(connection)
    connection.raw_connection.prepare(<<-SQL)
      INSERT INTO active_storage_attachments (
        `name`, `record_type`, `record_id`, `blob_id`, `created_at`
      ) VALUES (?, ?, ?, LAST_INSERT_ID(), ?)
    SQL
  end

  def key
    SecureRandom.uuid
  end

  def checksum(image)
    uri = URI.parse("https:#{image.url}")
    body = Net::HTTP.get(uri)
    Digest::MD5.base64digest(body)
  end
end

そこそこ長いですが、結局は各モデルの各ファイルごとにPaperclipのカラム情報とファイルのハッシュ値を取り、
Active Storageのテーブルに入れるという繰り返しです。

なお、各モデルの実装をActive Storage用に書き換えた後ではこのタスクは動かないので、
Paperclip時に実行する必要があります。

実際にやったところ数万件のデータがあったために5~6時間かかるという大引っ越しになってしましましたが、
なんとか乗り切りました。


ということで今回はActive Storageのデータ移行について書きました。

まだもう少しActive Storageネタで書けるかもしれません。

それでは。

, , ,

2018年12月10日月曜日

縫製工場の類似度算出を深層学習のモデルでやってみた

こんにちは!
シタテル株式会社CTOの和泉です。

今回は研究開発的に、「縫製工場の類似度算出を深層学習のモデルでやってみた」について書きます。
まず初めに結果から。
['loss', 'acc']
[273999.25, 0.695652186870575]
69.6% 成功?
なにが?って話ですよね。
結果的には全然精度出ませんでした。まぁ、のべ1週間ぐらいでやってみた感じですし、サーベイも全然できてないので結果出るわけないのですが。
せっかくやってみたのでブログに恥を晒しておきます。
その道の専門の方にはお目汚しかと思いますがご容赦ください。

やりたかったこと

弊社コンシェルジュが手動で類似分類(グループ化)した縫製工場 のデータを教師データとして学習し、同様の類似分類を行える学習済みモデルを作成する。
類似分類ができれば、これまでのアイテム作成履歴から代替となる工場の提案ができる。かも。

希望的仮定

類似度の判定に用いられる特徴はある程度絞られているのではないかと想定し、学習によって効果の高い特徴が反映されるモデルが作成されれば、人の判断に近づけるのではないか。

結果

教師データに対しては98.8%の正答率に達したが、テストデータについては69.6%にとどまった。残念ながら実用に足るような結果は得られなかった。
images/image-3.png
図1:教師データに対する推論結果のプロット(青が教師データ、点線先のオレンジが対応する推測結果)
images/image-4.png
図2:テストデータに対する推論結果のプロット(青が教師データ、点線先のオレンジが対応する推測結果)
images/image-5.png
図3:学習の経過

何をやったのか

データベースに保有する123の縫製工場を2次元平面上で類似しているものを近くに配置するように、弊社コンシェルジュに依頼して教師データを作成。(図1の青い点が各工場を表す)この際、「類似している=あるアイテムを相談するときに変わりに相談できる工場であるか」とした。作成された教師データが含む縫製工場データは114件。9件は情報不足により配置できなかった。
作成した教師データは各工場ごとに位置ベクトルが与えられた状態になっている。この位置ベクトルを各工場がもつ特徴ベクトル(全313次元のうち、名前などヒューリスティックに取り除けるものを除いて、108次元)から求めるモデルを学習により獲得した。

DNNモデルや条件などのメモ

model = Sequential()
model.add(Dense(DIM, activation='relu', input_dim=DIM))
model.add(Dropout(0.25))
model.add(Dense(DIM*2, activation='relu'))
model.add(Dense(2))

model.compile(optimizer='adam',
              loss='mean_squared_error',
              metrics=['accuracy'])
images/image-6.png
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 108)               11772     
_________________________________________________________________
dropout_1 (Dropout)          (None, 108)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 216)               23544     
_________________________________________________________________
dense_3 (Dense)              (None, 2)                 434       
=================================================================
Total params: 35,750
Trainable params: 35,750
Non-trainable params: 0
_________________________________________________________________
history = model.fit(X_train, Y_train,
            batch_size=50,
            epochs=5000,
            validation_split=0.1, 
            verbose=1)

考察

代替工場を考える際には、アイテムによって代替として扱うものが変わることが想定されるため、全体を2次元平面上に次元縮退してプロットすることが難しいであろうことは当初予定していたとおりであった。
今回は、比較的手軽に作成できる教師データを用いて試してみたが、十分な結果は得られなかった。
統計によるリコメンドではデータの量が問題になるが、良質で量のあるデータが十分にないのが現状。シタテルの取扱量が増えることによりある程度のデータは蓄積されていくが、滝行との連携も含めて扱えるデータ量を増やしていくことが必要である。

工場データベースについて

シタテルは500を超える縫製工場やサプライヤーと連携して衣服づくりサービスを提供しています。
それぞれの縫製工場に対して、設備や人員、アイテムごとの金額感、繁閑の目安など300を超える情報をデータベースに格納しています。
images/image-1.png
工場を検索して絞り込み

(工場名や取引先は非公開のため消してあります)
images/image-2.png
いくつかの工場を並べて比較
弊社コンシェルジュはこれらのデータを使ってご相談いただいたアイテムごとに最適な工場に問い合わせを行います。
, , ,

2018年12月7日金曜日

CircleCIでAWS ECSに自動デプロイする(おまけで環境面の切り替えも)

こんにちは、シタテルの茨木です。

今回はCircleCIでDockerをビルドし、そのままAWS ECR/ECSにデプロイする為の設定例を紹介します。

Nuxt.jsアプリ用に作ったものですが、言語やAPサーバが違っても共通の部分がほとんどです。

PRマージからの自動デプロイ、いいですよね。

https://circleci.com/docs/2.0/ecs-ecr/
https://github.com/CircleCI-Public/circleci-demo-aws-ecs-ecr

基本的にはCircleCI公式ガイドの上記リンクを参考にしていますが、元ネタはterraform前提だったりでなかなか大仰です。(下記に説明漏れがあったら原典を見てください…)

今回紹介するのはリンク先のスクリプト類を簡素化し、少し機能を追加(リポジトリ名からデプロイ先の環境面を切り替えできるように)したものです。

以下で省略しているけれど必要なモノ

  • ECS/ECRの設定

これだけで別の記事が書けてしまうので割愛します

ecsTaskExecutionRoleの割り当てとか忘れがちですかね

  • githubリポジトリ

言わずもがな。CircleCIとの接続設定はされているものとします

  • Dockerfile

リポジトリのルートに。Dockerfileの書き方は解説しません

  • requirements.txt

リポジトリのルートに。awscliを使えるようにします

https://github.com/CircleCI-Public/circleci-demo-aws-ecs-ecr/blob/master/requirements.txt

  • CircleCIの環境変数設定

github上にaccess_keyを晒さなくていいように、入れておきます

https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-project

この手順で設定した環境変数はCircleCIのジョブから参照できます

.circleci/config.yml:build

version: 2
jobs:
~省略~
  build:
    docker:
      - image: circleci/node:10-stretch
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Setup common environment variables
          command: |
            echo 'export ECR_REPOSITORY_NAME="YOUR-ECR-REPO-NAME"' >> $BASH_ENV
            echo 'export FULL_IMAGE_NAME="${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${ECR=REPOSITORY_NAME}:${CIRCLE_SHA1}"' >> $BASH_ENV
      - run:
          name: Build image
          command: |
            docker build -t $FULL_IMAGE_NAME .
      - run:
          name: Save image to an archive
          command: |
            mkdir docker-image
            docker save -o docker-image/image.tar $FULL_IMAGE_NAME
      - persist_to_workspace:
          root: .
          paths:
            - docker-image
~省略~

さっそくですがビルドジョブです。

  • YOUR-HOGEHOGE箇所はご自身のECS/ECRの設定に合わせて置き換えてください
  • - image: circleci/node:10-stretchnode:10を使っていますが、これはビルドする対象がNuxt.jsアプリだからですね。Dockerビルド時に必要なモノに合わせて修正してください

.circleci/config.yml:deploy

~省略~
  deploy:  
    docker:
      - image: circleci/python:3.6.1
    environment:
      AWS_DEFAULT_OUTPUT: json
    steps:
      - checkout
      - setup_remote_docker
      - attach_workspace:
          at: workspace
      - restore_cache:
          key: v1-{{ checksum "requirements.txt" }}
      - run:
          name: Install awscli
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install -r requirements.txt
      - save_cache:
          key: v1-{{ checksum "requirements.txt" }}
          paths:
            - "venv"
      - run:
          name: Load image
          command: |
            docker load --input workspace/docker-image/image.tar
      - run:
          name: Setup target environment variables
          command: |
            echo 'export TARGET=`echo $CIRCLE_BRANCH | sed -e s/develop.*/dev/ -e s/release.*/stg/ -e s/master/prd/`' >> $BASH_ENV
            source $BASH_ENV
      - run:
          name: Setup common environment variables
          command: |
            echo 'export ECR_REPOSITORY_NAME="YOUR-ECR-REPO-NAME"' >> $BASH_ENV
            echo 'export ECS_CLUSTER_NAME="YOUR-ECS-CULSTER-NAME-PREFIX${TARGET}"' >> $BASH_ENV
            echo 'export ECS_SERVICE_NAME="YOUR-ECS-SERVICE-NAME-PREFIX${TARGET}"' >> $BASH_ENV
      - run:
          name: Push image
          command: |
            . venv/bin/activate
            eval $(aws ecr get-login --region ap-northeast-1 --no-include-email)
            docker push $AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/$ECR_REPOSITORY_NAME:$CIRCLE_SHA1
      - run:
          name: Deploy
          command: |
            . venv/bin/activate
            export AWS_DEFAULT_REGION="ap-northeast-1"
            export ECS_TASK_FAMILY_NAME="YOUR-ECS-TASK-NAME-PREFIX${TARGET}"
            export ECS_CONTAINER_DEFINITION_NAME="YOUR-ECS-CONTAINER-DEF-NAME-PREFIX${TARGET}"
            export EXECUTION_ROLE_ARN="arn:aws:iam::$AWS_ACCOUNT_ID:role/ecsTaskExecutionRole"
            bash ./deploy.sh
~省略~

デプロイジョブです。

  • ブランチ名から環境名へ

echo 'export TARGET=`echo $CIRCLE_BRANCH | sed -e s/develop.*/dev/ -e s/release.*/stg/ -e s/master/prd/`' >> $BASH_ENV

ブランチ名に応じた値(developブランチなら"dev")を環境変数に入れています。これをsuffixとして使い、デプロイ先のクラスタ名等に反映しています

  • YOUR-HOGEHOGE箇所はご自身のECS/ECRの設定に合わせて置き換えてください

余談ですが、ECSはクラスタ・サービス・タスク・タスク定義といろいろ設定しないといけないのですが、なかなか直感的にわかりにくくてイマイチな印象です

.circleci/config.yml:workflows

~省略~
workflows:
  version: 2
  build-deploy:
    jobs:
      - build:
          filters:
            branches:
              only:
                - develop
                - /release\/.*/
                - master
      - deploy:
          requires:
            - build
          filters:
            branches:
              only:
                - develop
                - /release\/.*/
                - master

この辺はよしなに

deploy.sh

#!/usr/bin/env bash
set -eo pipefail
# more bash-friendly output for jq
JQ="jq --raw-output --exit-status"

deploy_cluster() {
  make_task_def   
  register_definition

  if [[ $(aws ecs update-service --cluster $ECS_CLUSTER_NAME --service $ECS_SERVICE_NAME --task-definition $revision | \
      $JQ '.service.taskDefinition') != $revision ]]; then
    echo "Error updating service."
    return 1
  fi

  # wait for older revisions to disappear
  # not really necessary, but nice for demos
  for attempt in {1..30}; do
    if stale=$(aws ecs describe-services --cluster $ECS_CLUSTER_NAME --services $ECS_SERVICE_NAME | \
        $JQ ".services[0].deployments | .[] | select(.taskDefinition != \"$revision\") | .taskDefinition"); then
      echo "Waiting for stale deployment(s):"
      echo "$stale"
      sleep 30
    else
      echo "Deployed!"
      return 0
    fi
  done
  echo "Service update took too long - please check the status of the deployment on the AWS ECS console"
  return 1
}

make_task_def() {
  task_template='[
    {
      "name": "%s",
      "image": "%s.dkr.ecr.%s.amazonaws.com/%s:%s",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 80
        }
      ],
      "environment": [
        {
          "name": "TARGET",
          "value": "%s"
        }
      ]
    }
  ]'

  task_def=$(printf "$task_template" $ECS_CONTAINER_DEFINITION_NAME $AWS_ACCOUNT_ID $AWS_DEFAULT_REGION $ECR_REPOSITORY_NAME $CIRCLE_SHA1 $TARGET)
}

register_definition() {
  if revision=$(aws ecs register-task-definition --requires-compatibilities FARGATE --cpu 256 --memory 1024 --network-mode awsvpc --execution-role-arn $EXECUTION_ROLE_ARN --container-definitions "$task_def" --family $ECS_TASK_FAMILY_NAME | $JQ '.taskDefinition.taskDefinitionArn'); then
    echo "New deployment: $revision"
  else
    echo "Failed to register task definition"
    return 1
  fi
}

deploy_cluster

デプロイジョブから呼び出すスクリプトです。

task_templateの内容を色々弄るとカスタマイズできます

設定可能な項目はこちら

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create-task-definition.html

ここでは、environmentで環境変数TARGETを設定しています

ECSがdocker runする際に引数として付与され、docker内のアプリから環境変数として参照可能になります

実際には、nuxt.config.js内でTARGETを参照し、APIサーバの向け先を切り替えたり等しています

以上、ご参考になれば幸いです。

, , , , , , ,

2018年12月6日木曜日

チームでのUI/UX改善のための取り組み「マイオペ触る会」のご紹介

こんにちは!

シタテルでエンジニアをしている建山です。

主に工場向けのマイオペというシステムの開発を行っています。

シタテルでは、チームがプロダクトなどでわかれており、今担当しているマイオペチームでは、
デザイナー、エンジニアが一緒になって開発をしています。

その中で、「マイオペ触る会」というのを隔週で行っています。

その取組の目的や方法をご紹介します。

マイオペ触る会って

普段開発しているプロダクトを、開発しているメンバー全員でユーザーの気持ちになって操作する時間を設けています。

目的としては、以下のような感じです。

・ユーザーの目線に改めてなって自分たちが作っているものを使うことで、気づいてなかったユーザーの課題やUI/UXの問題を洗い出す
・ユーザー目線を忘れないようにする意識付け

方法として、それぞれ行いたい時に非同期でやろうとしたのですが、開発の忙しさに追われてやらないことがあったため、みんなで同じ時間に集まって行うようにしています。

マイオペチームは拠点がバラバラなため、触る会の際は、zoomを繋ぎっぱなしにして話しながら進めています。

以下のような、googleスプレッドシートを活用し、どういうストーリーで操作したかの簡単な説明、画面ごとの問題や気になったことを各自書いていきます。

終わった後はイシュー化

終わった後はgithubでイシュー化して、議論検討したり、ユーザーインタビューしてみたり、開発計画に組み込んで進めたりしていきます。

やっていてよかったこと

目的にも書いていますが、開発しながら毎日自分たちのつくっているものには触れ合っていますが、あらためて、ユーザーの気持ちになっと触る時間を設けると、また初心に帰ることができるし、新鮮な気持ちでユーザーの課題が見えることもあります。
また、触る会をきっかけに チーム全体でもユーザー目線で活発な議論が発生していると感じていてやっていてよかったと思う点ばかりです。

まとめ

今回は、チームで取り組んでいる「触る会」をご紹介しました。チームでは他にも、いろいろな取組をしていますので、また追々紹介したいと思います。
チームや状況に合わせて、引き続きいいやり方を模索しながら改善していきたいと思います!
優秀なUI/UXデザイナーがチームにいますので、きっと記事を書いてくれると思います。笑

, , ,

2018年12月5日水曜日

Krispで雑音に気を取られずにWeb会議

こんにちは!
シタテル株式会社エンジニアのいしづかです。

シタテルは熊本と東京の2つ拠点があり、エンジニアもそれぞれいます。

また、週に2回はリモートワークも認められているため、物理的に人が集まらないことは、もはや日常です。

毎日の朝会や、様々なミーティングなどがオンラインビデオチャットで行われています。

シタテルではZoomというソフトを使っていますが、ここで悩まされるのが周囲の雑音です。

「発言時以外はマイクをミュートにする」というルールはあるものの、電話が鳴るとそっちの音が勝ってしまうこともしばしば。。。

「あ、ここのところなんですけd プルルルルルルル!!!」

ツライ。

そんな中、Krispというノイズキャンセリングのソフトがイイ!と聞いたので使ってみました。

結論から申し上げますと、なかなかよかったです。

Krisp

Krispは元TwilioのエンジニアだったDavitさんらが開発したアプリで、周囲のノイズ音を除去してくれます。ディープラーニングを用いて、ノイズ音をリアルタイムに除去しているそうです。

動画が出てましたが、パトカーのサイレンが鳴り響いていてもクリアに通話できているというスゴさ。

これはWeb会議の救世主ではなかろうか・・・!

残念ながら今現在ではmac版しか無いので、それ以外のOSユーザーはこのオドロキを体験頂けませんが、シタテルの開発陣は全員mac利用者なので問題ナシです。

Let's install !!

インストールはこちらのページより。
今ならDownload for freeなので無料で使えます。

pkgファイルが落ちてくるのでインストールします。

↑アイコンが表示されるので、SPEAK without noise をONにすれば準備完了です。

↑これはZoomの設定画面ですが、マイクとスピーカーにKrispがいるので、それを指定します。

これで準備完了です。

使ってみる

東京のエンジニアとZoomをつないだ状態で、以下のことをやってみました。

  • わざわざ談笑しているところにmacbook片手に近づく
  • 目の前で蛇口をひねって水をジャーっと出してみる
  • 会社の電話を鳴らしてもらう

結果は良好でした!!

すぐ周りは談笑しているのに「その部屋に他には誰もいないと思える」ほど静かだったそうです。

蛇口の水音、全然しない、と!!マジですか!!

もう手放せません。

まとめ

Web会議は環境がものすごく重要です。雑音が多くて声が聞き取れないとかなりのストレスになります。

Krisp入れたばっかりなのでまだまだ試さないといけないですが、みんな入れてこそ価値が発揮されるので、これから布教活動をしていこうと思います。

他にどのような工夫ができるか

快適なミーティングをやるために、飽くなき探究を続けていきます。

, , , , ,

2018年12月4日火曜日

シタテルで使っているデザインツールのご紹介

こんにちは!シタテル株式会社UI/UXデザイナーの田仲です。
今回はシタテルのデザイナーが使っている、デザインツールをご紹介します。

Sketch


デザインの作成はSketchをメインで使っています。去年まではPhotoshopがメインでしたが、下記で紹介するAbstract導入をきっかけに、Sketchをメインにしました。


Photoshopに比べると、とても軽いのでストレスなく使える点が気に入っています。アップデートが頻繁にあるため、その度に少し迷うこともありますが、アップデートでの変化を発見することが楽しみのひとつでもあります。
便利なプラグインもたくさん出てきているので、個人で試してチームにも導入していけたらと考えています。

Abstract


Sketchのバージョン管理ツールです。同じSketchファイルを複数のデザイナーが修正したり、サポートしあえるようにしています。


先日SketchのバージョンにAbstractが追いついていないことがあり、気がつかずSketchのバージョンを上げた時にSyncできない問題が起きてしまいました。(その時はSketchダウングレードして解決しました。)気軽にSketchのバージョンを上げられないという点は、微妙だなと感じています。
現在は複数人で作業しているプロジェクトは1つしかいない状態なので、もう少し共同で作業するプロジェクトを増やし、お互いの知見を増やすことにも活かしていけるような使い方をしていきたいなと考えています。

Adobe Creative Cloud


PhotoshopやIllustratorは、Sketchでは作業しにくい写真の加工、パネルや印刷物の作成を中心に使っています。


他にも、XDは気になる機能が次々に出ており、将来的にツールをAdobeに集約できると、データの互換性やコストなどの面でメリットがあると考えています。まだAbstractとSketchの方が便利だという結論から移行は考えていませんが、最新動向には日々注目しています。

Zeplin


デザイナーが作ったデザインをエンジニア確認できるツールです。開発内でのレビュー、最終的にエンジニアが実装するときに使っています。


コメントができる点はinVisionと変わりがないのですが、サイズやカラーコードがわかるだけでなく、アイコンのダウンロードなどもできるので、デザイナーとエンジニアのやりとりを減らすことができています。今後はよりエンジニアとどうやったらもっと便利になるかを話し合い、ガイドラインなどとしても活用できるようにしていけたらと考えています。

InVision


プロトタイピング・レビューに使用しています。


InVisionはコードなどの情報がないので、デザイナーとエンジニア以外の生産や営業などのメンバーがレビューに参加する際に使っています。シタテルでは開発以外の職種のメンバーともデザインの段階から頻繁に意見を交わしています。これからも部署問わず、よりよいプロダクトをつくり上げるためにコミュニケーションを活発にしていけたらと思います。

以上、シタテルで使っているデザインツールのご紹介でした。
シタテルでは、積極的に新しいデザインツールや機能を試し、今よりよりコミュニケーションしやすい&デザインしやすい環境となるものがあれば乗り換えていきたいと考えています

, , , , ,