やーまんぶろぐ

気が向いた時にだけ書くブログ

How Google Works (ハウ・グーグル・ワークス) ―私たちの働き方とマネジメント メモ

Googleの働き方に対する考え方がわかる本です。

How Google Works (ハウ・グーグル・ワークス)  ―私たちの働き方とマネジメント

How Google Works (ハウ・グーグル・ワークス) ―私たちの働き方とマネジメント

Googleのように優秀なエンジニアが成り立たないような考え方も多かった気もしましたが、新しいものを生み出すためには参考になる考え方がたくさんありました。

文化

スマートクリエイティブという言葉が頻繁に出て来ます。
自分の専門分野の深い知識を知性・ビジネス感覚やさまざまなクリエイティブな資質と組み合わせる人物のことです。

既存のビジネスではなく、新しいビジネスを生み出すためには、このスマートクリエイティブのような人材が求められているのだと感じました。

7つのルール(マネジャーは最低7人の直属の部下を持つこと) というのが印象的でした。
階層構造ではなく意思決定者を近くにおくことで組織をフラットに保つことが重要なようです。

マネージャーを少なくして、スマートクリエイティブ達が素早い意思決定を行えるようになっているのがGoogleの強みなんだと感じました。

戦略

市場調査はしない、技術的アイデアを中心に考える、顧客には聞かない。というように完全なプロダクト主義のようです。
個人的にはプロダクト主義こそ顧客に細かくぶつけながら開発していかないと市場と乖離したものが出て来てしまう気がするのですが、、

Dogfoodingな製品なら可能なのか?それともスマートクリエイティブならそれくらいできて当たり前なのか?

この部分はそのままは参考にできないかなと感じました。

人材

大事なのは「何を知っているか」ではなく、「これから何を学ぶか」というのが共感できました。環境変化が激しく、自分も1年間同じ働き方をしたことがないので、これからも「何を学ぶか」を大事にしていきたいと感じました。

人材を簡単にスケールさせる方法として、全社員が自分より優秀だと思う人を1人連れて来ればよい。というのが印象的でした。

新入社員のリクルーター活動をやったことがあるのですが、あれはある程度の人材をある程度の人数集める活動でしかないと感じました。
その活動の中で甲子園に同じ高校が毎年出られるのは、リクルーティングと教育がしっかりしているからというのを聞きました。

そのときはリクルーティングに力を入れる具体策は思いつかなかったですが、Googleのように全社員に人を集める権利持たせるというのも面白いと思いました。

意思決定

データに基づいて意思決定すると良いみたいです。

コミュニケーション

どんな情報でもオープンにする。

なんとなくですがGoogleはメール文化なのかな?と感じました。
個人的には入社してからずっとチャットでのコミュニケーションをとってきたので、メールが多いだけでがっかりしてしまいます。

場所を問わずリアルタイムにコミュニケーションを取れるチャットは便利ですね。

イノベーション

有名な20%ルールの話もでてきます。
個人的には20%で集まってきた人は、日が経つにつれて既存の80%の業務を優先して参加しなくなる傾向があると思っています。

新しいことをやるには、100%それに力をかけれる環境作りが大事だと思っています。
新しいことに力を入れているときは、余計な業務を振らないでほしい。

How Google Works (ハウ・グーグル・ワークス)  ―私たちの働き方とマネジメント

How Google Works (ハウ・グーグル・ワークス) ―私たちの働き方とマネジメント

インシデント対応ボードゲームの参加メモ

社内で行われたインシデント対応ボードゲームに参加しました。

sp.trendmicro.co.jp

インシデント対応ボードゲームとは、実際にインシデントが発生したと仮定し、各プレイヤー同士がどのようにインシデント対応を行うべきか、それぞれの立場・視点で議論するというものです。

私のグループは以下の4つの役割になりきって実施しました。

  • CIO
  • システム管理者
  • セキュリティ担当者
  • 広報担当者

私はセキュリティ担当者でした。

ゲームを進める上で、前提を明確にしないと判断できない部分が多々あり、認識をすり合わせながら進めるのが難しいと感じました。

反対にインシデント対応の意志決定をするためには、いくつか情報が必要というのが気づきになります。

例えば、自社のトップページ画像が改ざんされたときに、前提によってインシデント対応が変わってしまいます。

現在改ざんの影響がわかっていない、社内にセキュリティのことがわかるエンジニアがいない、外部ベンダーを呼ぶ費用はこれくらい、停止したときの被害がこれくらいなどを決めておいて初めて意思決定できて、議論することが可能になります。

実際にインシデントが起きたときには余裕ないと思うので、
普段からシミュレーションしておくことを心掛けたいと思いました。

RDSを定期的に停止するスクリプトを作成してみた メモ

ちょっと前ですがRDSが停止をサポートしました。
Amazon RDS でデータベースインスタンスの停止と開始をサポート

以下のリンクを参考に定期的に停止するスクリプトを作成しました。
(テストしづらかったので、環境変数はCloudwatch EventではなくLambda側に持たせることにしています。)
dev.classmethod.jp

試しに停止中に停止コマンドを実行してみると以下のエラーが発生しました。
operation: Instance hoge is not in available state.

7日より使用頻度が少なかったので延長してくれないかなと思ったのですが、さすがに無理でしたね。
(最大7日間の停止で、7 日後に自動的に開始されてしまうという制約があります)

しょうがないので毎日定時で停止するようにして、起動時間をなるべく短縮することにしました。

2017/11/3 追記
シングルAZ構成のRDSインスタンスのみが対象で、マルチAZ構成では停止/開始できないので注意が必要。

Amazon Dash ButtonとMacを連携させてみた

Amazon Dash ButtonとMacを連携させてみました。

基本的には下の記事を参考にさせてもらってます。

qiita.com

Node.jsとnpmのアップデート

Node.jsとnpmが古い場合は事前にアップデートしておきましょう。
yamano3201.hatenablog.jp

$ node -v
v7.4.0

$ npm -v
4.0.5

Amazon Dash Button の購入

以前、購入したときの記事です。
yamano3201.hatenablog.jp

このとき購入した「食器用洗剤」の他に、「トイレットペーパー」と「ティッシュ箱」と今回の「Mac連携用」の計4つを購入しました。

Amazon Dash Button のセットアップ

1.Amazonアプリの左上ハンバーガーメニューより「アカウントサービス」

2.下部にあるDush端末の「新しい端末をセットアップ」

3.表示される通りDushボタンの電源を入れ、Wifiに接続

4.Wifiに接続完了すると商品を設定する画面が表示されるのでここでやめる。

ここは参考サイトそのままの手順になります。
ボタンを押したときのスマホへのPush通知をオフにするために、
Amazonアプリの「端末を管理」から、「このDash Buttonを無効にする」で無効にしておきます。

node-dash-buttonを使ってAmazon Dash ButtonのMACアドレスを取得

$ npm install node-dash-button
$ sudo node bin/findbutton
Watching for arp & udp requests on your local network, please try to press your dash now
Dash buttons should appear as manufactured by 'Amazon Technologies Inc.' 

# ここでAmazon Dash Button を押します

Possible dash hardware address detected: XX:XX:XX:XX:XX:XX Manufacturer: Amazon Technologies Inc. Protocol: arp

XX:XX:XX:XX:XX:XX の部分が取得できたMACアドレスになります。

node-applescript を使って Apple Scriptを動かす

ここではsayコマンドでMacに喋らせるScriptを用意しています。

$ npm install applescript
$ cat << EOF > script.applescript
tell application "System Events"
    say "test"
end tell
EOF

続いて、Amazon Dash Buttonが押されたタイミングで Apple Script を実行するJavaScriptを用意します。
XX:XX:XX:XX:XX:XX には事前に取得しておいたMACアドレスを入力します。

$ export MAC_ADDRESS=XX:XX:XX:XX:XX:XX
$ cat << EOF > app2.js
var applescript = require('applescript');
var dash_button = require('node-dash-button');
var fs = require('fs');
var dash = dash_button("${MAC_ADDRESS}", null, null, 'all'); 
dash.on("detected", function (){
    console.log("test");
fs.readFile('./script.applescript', 'utf8', function (err, script) {
        applescript.execString(script, function(err, rtn) {
            if (err) {
                // Error
                console.log(err);
            }
        });
    });
});
EOF

このJavaScriptも参考サイトそのままです。

動作確認

$ sudo node app.js

# ここでAmazon Dash Button を押します

test

ターミナルに「test」と出力され、Macからも声として出力されればOKです。

最後に

思ったより参考にしていた記事と変わらなくなってしまった。。
あとは好きなコマンドに変えれば面白いことできそうですね。アイデアは考え中です。

LINEスタンプに「写真データ」が使えるようになりました。

けっこう前の話ですが、LINEスタンプに「写真データ」が使えるようになりました。

creator-mag.line.me

ということで、今回は人物の写真に挑戦してみました。

人物写真使用の注意点

人物写真を使用する場合は、同意書が必要になります。
同意書なしでリクエストすると同意書のテンプレートをもらえるので、まずはリクエストしてみましょう。

以下、注意点です。

  • 人物写真を使用する場合はスタンプに使用されている人数分の同意書が必要です。
  • ご本人(肖像が使用されている人)が満15歳以上の場合は、ご本人の署名が必要です。
  • ご本人が満15歳未満の場合は、ご本人と、その親権者など法定代理人の署名が必要です。
  • ご本人が年少者など署名できない場合には、ご本人の親権者による代筆と、その親権者など法定代理人の署名が必要です。

新作スタンプ

赤ちゃんスタンプを作成しました。スタンプに使用されている人物は1名なので、同意書を1枚作成してリクエストしました。
赤ちゃんは署名できないので、本人署名の代筆と法定代理人の署名が必要になります。
store.line.me

過去スタンプ

過去にも2つほどスタンプを作成しました。
yamano3201.hatenablog.jp
yamano3201.hatenablog.jp

最後に

親バカですが、赤ちゃんかわいいです。

Serverless Web Application Workshop をCLIでやってみた Lab 4: Product Update Blog

サーバーレスアーキテクチャ Advent Calendar 2016 の25日目の記事です。

「Serverless Web Application Workshop をCLIでやってみた Lab 3: Administrative Interface」 の続きになります。
yamano3201.hatenablog.jp

大枠はこちらを参考。
qiita.com

README記載のコンソール作業をCLIで置き換えて構築を確認していくというものです。
github.com

ワークショップは全部で4つあります。

  • Lab 1: Static Website Hosting
  • Lab 2: Beta Sign-up Mailing List
  • Lab 3: Administrative Interface
  • Lab 4: Product Update Blog

今回は最後のLab 4: Product Update Blog について書いていきます。

やること

管理者がアップデートできるブログ機能の構築。ブログ記事を保存するテーブルをDynamoDBで作成し、記事一覧を取得するLambdaと記事をポストをするLambdaとIAM roleを作成し、API Gateway(GET/POST resource)でLambda Proxy連携とCognito user pool認証を設定してウェブアプリから呼び出せるAPI endpointを作成してウェブアプリ側に埋め込む。ウェブアプリで管理者としてログイン後、記事のポストと最新記事一覧が表示される事を確認。

ブログ記事を保存するためのテーブルをAmazon DynamoDBで作成

$ export BLOG_TABLE=$(aws dynamodb create-table --table-name Wildrydes_Posts --attribute-definitions AttributeName=Blog,AttributeType=S AttributeName=Timestamp,AttributeType=N --key-schema AttributeName=Blog,KeyType=HASH AttributeName=Timestamp,KeyType=RANGE --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 | jq -r '.TableDescription.TableArn')

ブログ記事を保存するLambdaと、ブログ記事一覧を取得するLambdaを作成

  • ロジックはREADMEに書かれているコードをコピーします。
  • DynamoDBにブログ記事を保存するコードと、ブログ記事一覧を取得するコードになっています
  • コードはzip化して渡します。
$ cat << EOF > index.js
var aws = require('aws-sdk');
var dynamodb = new aws.DynamoDB({});

exports.handler = (event, context, callback) => {
 var body = JSON.parse(event.body);
 var params = {
   TableName: 'Wildrydes_Posts',
   Item: {
     Blog: { S: 'product-updates' },
     Timestamp: { N: body.Timestamp },
     Title: { S: body.Title },
     Body: { S: body.Body }
   }
 };

 dynamodb.putItem(params, (err, data) => {
   if (!err) {
      context.succeed({
       statusCode: '200',
       headers: { 'Access-Control-Allow-Origin': '*'},
       body: JSON.stringify({})
      });
   } else {
      context.fail(err);
   }
 });
};
EOF
$ zip -r create_post.zip index.js
$ export ACCOUNT=$(aws sts get-caller-identity | jq -r ".Account")
$ export CREATE_POST=$(aws lambda create-function --region us-east-1 --function-name CreatePost --role arn:aws:iam::${ACCOUNT}:role/LambdaDynamoDBRole --runtime nodejs4.3 --handler index.handler --zip-file fileb://create_post.zip | jq -r '.FunctionArn')
$ cat << EOF > index.js
var aws = require('aws-sdk');
var dynamodb = new aws.DynamoDB({});

exports.handler = (event, context, callback) => {
 var params = {
   TableName: 'Wildrydes_Posts',
   ScanIndexForward: false,
   KeyConditions: {
        Blog: {
            ComparisonOperator: 'EQ',
            AttributeValueList: [ { S: 'product-updates' } ],
        }
   }
 };

 dynamodb.query(params,(err, data) => {
   if (!err) {
     var posts = [];

     data.Items.forEach((item) => {
       posts.push({
         Timestamp: item.Timestamp.N,
         Title: item.Title.S,
         Body: item.Body.S
       })             
     });

     context.succeed({
       statusCode: '200',
       headers: { 'Access-Control-Allow-Origin': '*'},
       body: JSON.stringify({ Posts: posts })
     });
   } else {
     context.fail(err);
   }
 });  
};
EOF
$ zip -r get_all_posts.zip index.js
$ export GET_ALL_POSTS=$(aws lambda create-function --region us-east-1 --function-name GetAllPosts --role arn:aws:iam::${ACCOUNT}:role/LambdaDynamoDBRole --runtime nodejs4.3 --handler index.handler --zip-file fileb://get_all_posts.zip | jq -r '.FunctionArn')

Lambda連携とCognito user pool認証を設定したAPI Gateway作成

リソース作成

  • Wildrydes APIはすでに作成されているので、そこにpostsリソースを作成します。
$ export API=$(aws apigateway get-rest-apis | jq -r '.items | map(select(.name == "Wildrydes"))' | jq -r '.[].id') 
$ export ROOT_ID=$(aws apigateway get-resources --rest-api-id ${API} | jq -r '.items | map(select(.path == "\/"))' | jq -r '.[].id')
$ export RESOURCE=$(aws apigateway create-resource --rest-api-id ${API} --parent-id ${ROOT_ID} --path-part posts | jq -r '.id')

OPTIONSメソッド作成

  • CORSを有効化するためにemailsリソースにOPTIONSメソッドを作成します。
$ aws apigateway put-method --rest-api-id ${API} --resource-id ${RESOURCE} --http-method OPTIONS --authorization-type NONE --no-api-key-required --request-parameters '{}'
$ aws apigateway put-integration --rest-api-id ${API} --resource-id ${RESOURCE} --http-method OPTIONS --integration-http-method POST  --type MOCK --request-templates '{ "application/json": "{\"statusCode\": 200}" }' --passthrough-behavior WHEN_NO_MATCH
$ aws apigateway put-method-response --rest-api-id $API --resource-id $RESOURCE --http-method OPTIONS --status-code 200 --response-models '{"application/json": "Empty"}' --response-parameters "method.response.header.Access-Control-Allow-Headers=false,method.response.header.Access-Control-Allow-Methods=false,method.response.header.Access-Control-Allow-Origin=false"
$ aws apigateway put-integration-response --rest-api-id ${API} --resource-id ${RESOURCE} --http-method OPTIONS --status-code 200 --response-templates '{"application/json": ""}' --response-parameters '{"method.response.header.Access-Control-Allow-Headers": "'"'"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"'"'" , "method.response.header.Access-Control-Allow-Methods": "'"'"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"'"'", "method.response.header.Access-Control-Allow-Origin": "'"'"'*'"'"'"}'

POSTメソッド作成

  • postsリソースに対するPOSTメソッドを作成します。
  • 認証の設定でuser poolを設定したAuthorizerを設定します。
  • Lambda のCreatePost関数を POSTメソッドの送信先に設定します。
$ export AUTHORIZER=$(aws apigateway get-authorizers --rest-api-id ${API} | jq -r '.items | map(select(.name == "Wildrydes_Admin"))' | jq -r '.[].id')
$ aws apigateway put-method --rest-api-id ${API} --resource-id ${RESOURCE} --http-method POST --authorization-type COGNITO_USER_POOLS --authorizer-id ${AUTHORIZER} --no-api-key-required
$ aws apigateway put-integration --rest-api-id ${API} --resource-id ${RESOURCE} --http-method POST --integration-http-method POST --type AWS_PROXY --uri "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/${CREATE_POST}/invocations" --content-handling CONVERT_TO_TEXT
$ aws apigateway put-integration-response --rest-api-id ${API} --resource-id ${RESOURCE} --http-method POST --status-code 200 --response-templates '{"application/json": ""}'
$ aws apigateway put-method-response --rest-api-id ${API} --resource-id ${RESOURCE} --http-method POST --status-code 200 --response-models '{"application/json": "Empty"}'
$ aws lambda add-permission --function-name CreatePost --statement-id prod --action lambda:InvokeFunction --principal apigateway.amazonaws.com --source-arn "arn:aws:execute-api:us-east-1:${ACCOUNT}:${API}/*/POST/posts"

GETメソッド作成

  • postsリソースに対するGETメソッドを作成します。
  • Lambda のGetAllPosts関数をGETメソッドの送信先に設定します。
  • prodステージにAPI を再デプロイします。
$ aws apigateway put-method --rest-api-id ${API} --resource-id ${RESOURCE} --http-method GET --authorization-type "NONE" --no-api-key-required
$ aws apigateway put-integration --rest-api-id ${API} --resource-id ${RESOURCE} --http-method GET --integration-http-method POST --type AWS_PROXY --uri "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/${GET_ALL_POSTS}/invocations" --content-handling CONVERT_TO_TEXT
$ aws apigateway put-integration-response --rest-api-id ${API} --resource-id ${RESOURCE} --http-method GET --status-code 200 --response-templates '{"application/json": ""}'
$ aws apigateway put-method-response --rest-api-id ${API} --resource-id ${RESOURCE} --http-method GET --status-code 200 --response-models '{"application/json": "Empty"}'
$ aws lambda add-permission --function-name GetAllPosts --statement-id prod --action lambda:InvokeFunction --principal apigateway.amazonaws.com --source-arn "arn:aws:execute-api:us-east-1:${ACCOUNT}:${API}/*/GET/posts"
$ aws apigateway create-deployment --rest-api-id ${API} --stage-name prod

ワークショップ用に用意されたlab4のファイルをS3にsync

  • アプリコードは用意されたものをsyncします。
$ aws s3 sync lab4/ s3://${BUCKET_NAME}
$ open https://$(cat cloudfront.json | jq -r '.Distribution.DomainName')/admin

動作確認

  • ログインしてNew Blog Postに進みます。
  • TitleとBodyを記入してPOSTボタンをクリックします。
  • ブログページにリダイレクトされ、作成した投稿が表示されていることを確認します。

最後に

これで全4つのServerless Web Application Workshopが終わりになります。
コンソールから自動で作られてた設定を一つ一つCLIで確認できたので良い勉強になりました。
今回のように構築全てをCLIで行うのは現実的ではないので、中身を理解できたらCloudFormationなどの他の仕組みで構築するのが良いと思います。