AWS IoT Core にRaspberry Piから赤外線モーションセンサのログを送る

クラウド事業部エンジニアの川勝です。

わけあって人感センサで在室確認したいという要望がありまして、やってみることになりました。
ちょっとIoTブームが個人的にきているのもあってAWS IoT Coreをつかってやってみたいと思います。

今回はAWS IoTに送信までやってみます。
使うのサービスとモノは以下。

● AWS IoT
https://aws.amazon.com/jp/iot-core/

AWS IoT Core は、インターネットに接続されたデバイスから、クラウドアプリケーションやその他のデバイスに簡単かつ安全に通信するためのマネージド型クラウドサービスです。

簡単とうたうだけあってチュートリアルのnode.jsとかpythonのデバイス用sdkを使ったものでmacでも簡単に接続まで試すことができました。

● Raspberry Pi
会社に眠っていたもの。多分Raspberry Pi Type B 

● 赤外線モーションセンサ
ググってでてきたRsbeeの3個セットのもの。安い。
Raspberry Piと接続するのにジャンパーワイヤーもいります。

●言語
チュートリアルはnode.jsでやったのですがgoで実装していきます。

Rasberry Pi と モーションセンサ

Raspberry Piのセットアップ

microSD

Raspberry Piを触るのは初めてだったのですが、microSDにosをインストールして使用します。眠っていた機器にもともと8GBのmicroSDは刺さっていたのですが、ちょっと怪しそうだったので家にあった東芝の16GBのものを使いました。
最低8GBは必要みたいです。

OS

CLIでしか操作しなにのでこちらからRaspbian Buster LiteをDLして使用しました。Ubuntu CoreっていうのもIoT向けで良さそうだったのですが今回使うRaspberry Piだと型が古くて対応外みたいでした…残念。

インストール

公式サイトにインストール方法あります。
今回はmac上でフォーマット、OSインストールした後にSSHできるようにします。(Raspberry Piにつなぐようのディスプレイが手元になかったので…)

microSDを接続したら以下コマンドで確認。

$ diskutil list

/dev/disk0 とか /dev/disk1 とかでてくるので要確認。間違うとmac本体がやられます。自分の環境では /dev/disk2 でした。

/dev/disk2 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme *7.9 GB disk2
1: Windows_FAT_32 boot 58.7 MB disk2s1
2: Linux 7.9 GB disk2s2

確認できたらフォーマット。

$ diskutil eraseDisk MS-DOS RPI disk2

フォーマットが完了したら一旦アンマウントして…

$ diskutil unmountDisk /dev/disk2DLしたOSのイメージを書き込みます。sudo dd bs=1m if=path_of_your_image.img of=/dev/rdisk2 conv=sync

終わったら、sshできるようにもうひと手間かけます。
もしbootディレクトリがでてこなかったら一度SDカードを取り出して入れるとでてきました。

$ touch /Volumes/boot/ssh

ちなみに Raspberry PiがWifiモデルだったらwifiの設定ファイルを用意しておくと有線で繋がなくても接続できるようです。
wpa_supplicant.confというファイルで以下のような内容をboot以下に保存します。

country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
    ssid="hoge"
    psk="fuga"
}

mac上でここまでできたらSDカードを取り出してRaspberry Piにセットします。

Raspberry Pi へSSH

Raspberry PiはmicroUSB端子の電源アダプタでつなぐと起動します。
が、電源アダプタがなかったのでAmazon Echoの電源で起動しました。
とりあえず動きはするみたい。
ちなみにRaspberry Pi 4では5V 3Aが推奨のようです。
モニターもないのでLANケーブルもつないで電源ライトの赤と緑のランプが付けばOKとしてmacからsshします。
portがわからないので調べます。

$ arp -a
? (192.168.3.1) at 30:::::** on en0 ifscope [ethernet]
? (192.168.3.3) at ac:::::** on en0 ifscope [ethernet]
? (192.168.3.6) at fc:::::** on en0 ifscope [ethernet]
? (192.168.3.8) at 14:::::** on en0 ifscope [ethernet]
? (224.0.0.251) at 1:::::** on en0 ifscope permanent [ethernet]
? (239.255.255.250) at 1:::::** on en0 ifscope permanent [ethernet]

上みたいにIPアドレスとMACアドレスがでてきます。b8:27:e1 から始まるMACアドレスがRaspberry Piらしいのででてくるまで arp -a を連打しましたw
IPアドレスがわかったらSSHします。

$ ssh pi@192.168.xx.xx

初期ユーザはpiでパスワードはraspberryでSSHできます。
パスワードはログイン後に変更しておきましょう。
その他いろいろ設定したほうがよさそうですが、ひとまず接続できたのでAWSの設定と実行プログラムを設置していきます。

AWS IoT

AWSコンソールから作成していきます。

AWS IoTの 管理 > モノ からデバイスの作成をします。

単一のAWS IoT モノの登録を選択します。

Things Registryにデバイスを追加
とりあえず名前だけつけて次へ

証明書を作成します。

ボタンをクリックするだけで新規に証明書が作成されました。
忘れずに証明書、プライベートキー、パブリックキーをダウンロードしましょう。またAWS IoT のルートCAも必要ですのでリンク先からダウンロードします。
ダウンロードしたら証明書を有効化します。
ポリシーをアタッチは先にポリシーを作成しておく必要がありますのでここでは一旦完了します。

モノの一覧にもどるとTestDeviceが作成されていますね!

続いて 安全性 > ポリシー からポリシーの作成をします。

アクションは iot:* としておきます。リソースARNは画像はデフォルトで挿入されている値ですが、接続確認だけなら * にしておいてもよいです。

ポリシーを作成したら 安全性 > 証明書 から証明書にポリシーをアタッチします。

最後に モノ詳細画面からHTTPSエンドポイントを確認しておきます。

センサ接続

ちょっと見にくいですが以下のPINに接続しています。
接続は電源を入れる前に。
1ピン  :5V電源(Power)
6ピン  :0Vアース(GND)
12ピン:GPIO18  (Output)

実行プログラム

ソースはこちら。
goのライブラリでgobotというのを使いました。使いやすくてよかったです。

● Raspberry Pi 用のdriver
https://gobot.io/documentation/platforms/raspi/
● モーションセンサ用
https://gobot.io/documentation/drivers/pir-motion-sensor/

大体そのまま使っています。
モーションセンサで使っているドライバーはRaspberry Pi用のものじゃなくて最初ちょっとハマりました…
あとモーションセンサをつないだpin番号が最初謎でした…
物理ピン番号とGPIOのBCMピン番号があるのですが、物理ピン番号を設定するようです。
Raspberry Piの設定は以下の様になりました。

func newRobot(c MQTT.Client) *gobot.Robot {
// raspiと接続するアダプター
r := raspi.NewAdptor()
// モーションセンサ。pin番号はgpioのpin番号ではない
sensor := gpio.NewPIRMotionDriver(r, pirMotionDriverPin)

// センサの振る舞い
work := func() {
// センサONになったら
sensor.On(gpio.MotionDetected, func(data interface{}) {
fmt.Println(gpio.MotionDetected)
token := c.Publish(fmt.Sprintf(shadowUpdateEndpoint), 0, false, sensorOnMessage)
if token.Wait() && token.Error() != nil {
fmt.Printf("error: %+v", token.Error())
} else {
fmt.Print("Message Publish Success\n")
}
})
// センサOffになったら
sensor.On(gpio.MotionStopped, func(data interface{}) {
fmt.Println(gpio.MotionStopped)
token := c.Publish(shadowUpdateEndpoint, 0, false, sensorOffMessage)
if token.Wait() && token.Error() != nil {
fmt.Printf("error: %+v", token.Error())
} else {
fmt.Print("Message Publish Success\n")
}
})
}

return gobot.NewRobot("motionBot",
[]gobot.Connection{r},
[]gobot.Device{sensor},
work,
)
}

センサのON/OFF時にAWS IoTにMTQQでメッセージを送信します。
あとからきづいたのですがgobotにもMTQQのクライアントがありましたが( https://gobot.io/documentation/platforms/mqtt/ )、違うパッケージを使用しています。
https://github.com/eclipse/paho.mqtt.golang
プログラムでいうと以下あたり

func newTLSConfig() (*tls.Config, error) {
certpool := x509.NewCertPool()
// AWS IoTのルートCA
pemCerts, err := ioutil.ReadFile(caCertificate)
if err == nil {
certpool.AppendCertsFromPEM(pemCerts)
}
// ダウンロードした証明書とプライベートキーのチェック
cert, err := tls.LoadX509KeyPair(clientCertificate, privateKey)
if err != nil {
log.Printf("error: cert file mismatch. clientCertificate: %s, privateKey: %s\n", clientCertificate, privateKey)
return nil, err
}

cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
log.Print("error: parse certificate.\n")
return nil, err
}

return &tls.Config{
RootCAs: certpool,
ClientAuth: tls.NoClientCert,
ClientCAs: nil,
InsecureSkipVerify: true,
Certificates: []tls.Certificate{cert},
}, nil
}

var f MQTT.MessageHandler = func(client MQTT.Client, msg MQTT.Message) {
fmt.Printf("TOPIC: %s\n", msg.Topic())
fmt.Printf("MSG: %s\n", msg.Payload())
}

func mqttClient() (MQTT.Client, error) {
// 証明書の読み込み
tlsconfig, err := newTLSConfig()
if err != nil {
log.Print("error: newTLSConfig error.\n")
return nil, err
}
// AddBrokerでAWS IoTのエンドポイント設定
opts := MQTT.NewClientOptions().
AddBroker(fmt.Sprintf(awsIotHostName, hostName)).
SetClientID(clientID).
SetTLSConfig(tlsconfig).
SetDefaultPublishHandler(f)

return MQTT.NewClient(opts), nil
}

プログラムのbuildはmac上でしてscpでRaspberry Piに転送して動かしました。
実行時のコマンド

./build/deploy/cmd/${PROJECT_NAME} \
-host-name xxxxxxxx.iot.ap-northeast-1.amazonaws.com \
-client-id testDevice-1 \
-client-certificate certificate.pem.crt \
-ca-certificate root-CA.crt \
-private-key private.pem.key \
-thing-name testDevice

引数で証明書のパスとかを指定します。センサのPIN番号も引数にしてもいいかもですね。

このあたりのコマンドはMakefile用意しておいて実行できるようにしています。(引数わすれるので。)
https://github.com/kawakattsun/iot-motion-sensor-go/blob/master/Makefile_go

動かしてみた

(圧縮したので見づらい…)

左がRaspberry Piにsshしてターミナルからコマンド実行した画面。
右はAWS IoTの 管理 > モノ からシャドウという項目をみています。
ちょっと見にくいですが連動して更新されていますね!

ターミナルの表示は
●センサが検知
motion-detected
Message Publish Success

●検知後
motion-stopped
Message Publish Success

どうも検知したあと一定時間で必ずstoppedになるようですね…

この後

次はシャドウの更新をさらにAWS上のサービスに流せるので最終的にLambdaで状態判定して在室検知判定しようと思います。
当初AWS IoTからLambda直で流す予定でしたが、On Offが必ずセットでくるのでkinesis にデータを流して一定期間のレコードからLambdaで判定にしようかな、と思案中。

続きは次回ブログで公開したいと思います。
乞うご期待!

Amazon Comprehend を使って様々な文章を感情分析してみました

CTOの原口です。
Amazon Comprehend が昨年の11月に日本語にも対応した、という事で少し触ってみました。

Amazon Comprehendとは?

Amazon Comprehendとは 機械学習を使用してテキスト内でインサイトや関係性を検出する自然言語処理 (NLP) サービス です。
機械学習の知識なく使えるサービスで文章を投げると 「エンティティ分析」「キーフレーズ分析」「感情分析」の結果が返ってくるというお手軽なサービスになります。

今回、この中でも特に面白そうだな、と思った感情分析でいろいろな文章の感情分析を行ってみました。

Amazon Comprehend で 文章の感情分析

Amazon Comprehendは現在(2020/01/22)は以下のリージョンでしか提供されておらず、残念ながら東京リージョンでの提供はされておりませんので以下のリージョンで試してみます

EU (ロンドン)
EU (アイルランド)
アジアパシフィック (シンガポール)
アジアパシフィック (シドニー)
EU (フランクフルト)
米国東部 (バージニア北部)
米国東部 (オハイオ)
カナダ (中部)
米国西部 (オレゴン)

とりあえず試してみる…というのはすごく簡単で、 Amazon Comprehend のサービストップに移動して「Real-time analysis」をクリックし、解析したい文章を入力し、「Analyze」をクリックするだけで結果が出てきます

Amazon Comprehend の公式ページの文章を解析してみました
大枠として4つの感情に分類されて分析されるようです
Neutral ,Positive ,Negative,Mixed とありますがMixedだけどういうものかよくわかりません…

・Neutral -> ニュートラル(感情→)
・Positive -> ポジティブ (感情↑)
・Negative -> ネガティブ (感情↓)
・Mixed -> 上記が混じった文章?

ひとまずこの文章は94%がニュートラルで5%だけのポジティブ要素がある、と分析されてようですね

いろんな文章を感情分析してみる

まずは弊社代表、西垣の「社長ブログ」から分析してみました

社長ブログ – 内定式
https://www.seeds-std.co.jp/blog/24gk/education/376/
Neutral 94% Positive 5%


ほぼニュートラル!後半の懇親会の文章などから数ある記事の中でも結構ポジティブ要素が多いのではないかなー?と思って選んだ記事でしたが、ニュートラルでした

社長ブログ – AWSの盾が届きました
https://www.seeds-std.co.jp/blog/24gk/aws/291/
Neutral 92% Positive 7%

お次は同じく社長ブログからAWSからパートナーの盾が届いたこちらの記事を解析。これも結構喜んでおられたので結構ポジティブがいくか!?と思いましたが多少ポジティブが増えましたがそれでも7%でした。
さすが社長。公平があり感情的に揺さぶられないしっかりとした文章をかかれているようです。(持ち上げ)

広報ブログ 忘年会(2019 ver.)を開催しました
https://www.seeds-std.co.jp/blog/pr/6559/
Neutral 65% Positive 9% Mixed 22%

それでは、今度は比較的くだけた文章も書ける広報ブログの文章を分析してみました。やはりニュートラルが基本になりますね。ですがここで初めて Mixedというよくわからない項目が22%に。
実はこのブログ内容には「各事業部の来年の抱負」という部分があるのですが、この項目は各部長の抱負をベースにした文章がありますので、そういう意味で1/3の純情な感情が入り乱れた結果Mixedとなったのかもしれません。

広報ブログ  社長のHappyBirthday!!
https://www.seeds-std.co.jp/blog/pr/3794/
Neutral 35% Positive 64%

同じく広報ブログより、社長の誕生日を社内でお祝いしたブログ記事です。
これはもうポジティブに振り切れるはずだ!と思ったらやっぱりポジティブに振り切れてました。

“これを納期までにやるにはどうしたらいいのかを考えなさい”
Neutral 33% Negative 64%

ではブログ記事ではなく、もっと短い文章での解析はできるのか、とやってみました。こちらは上司からのお叱り文章という例文があったので解析してみました。
やっぱりネガティブが強く出ますね。

Positive 99%

というわけで短文といえばtwitter。僕の好きな、さしこの適当なツイートを分析してみました。ポジティブ99%!さすがアイドルですね。

“雨と雷やべー。。あと5分帰るの遅かったらヤバかった。。 飛行機心配しましたが明日は普通に大丈夫そうですね”
Neutral 48% Positive 2% Negative48%

短文といえば、Slack!今度は社内のslackでの僕の発言を解析。これはどちらかといえば「よかったー」という意味で書いてたのですが、ネガティブが上がってしまっていますね。
やはりこういった複雑な文章を読むのは苦手なのかもしれないです

どんな事に使えそうだろうか

{
    "Sentiment": {
        "Sentiment": "NEUTRAL",
        "SentimentScore": {
            "Positive": 0.05136203020811081,
            "Negative": 0.000046045330236665905,
            "Neutral": 0.9485709071159363,
            "Mixed": 0.000021009738702559844
        }
    }
}

さて、文章をAPIに投げると実際には上記のようなJSONが返ってきて”Sentiment”の項目に最も高かった感情が返ってきます。
これを踏まえて以下のような事をすると面白いのではないかと思いました

・サポート窓口からのご連絡のお客様の返信を解析
お客様から弊社サポートからのご連絡に対して、お客様からの返信を感情分析する事で、サポート品質向上の一つの指針になるのではないかと思いました。

・slackの過去発言から感情の状態でステータス表示を変える
たとえば自分の過去100メッセージから感情の平均を出して、最も多い環状をslackのステータス部分を絵文字とかで表すと面白いかもw

・twitterなどでいっぱい感情分析してその中でネガティブ値が多いものから単語を抜き出しNGワードを作る
やってみないとわからないですがそれなりの結果が出そうです。

おわりに

かんたんに文章の感情分析ができて面白いサービスでした。
可能であればネガティブ感情は「怒り」「悲しみ」くらいには分かれていると嬉しいですね。
機械学習も作るのではなく利用していくフェーズに入ったのだなーという事を改めて思いました。
時間があれば面白いもののデモを作ってみたいですね。

Re:Invent2019で発表されていたAWS CLI V2 について触ってみました

クラウド事業部の原口です。
今年は初めて AWS Re:Invent に参加する事ができました。
いろいろ書きたい事はあるのですがクリエイターズブログなのか?という事もあり、技術的なネタで、本日はAWS CLI v2 を触ってみたという内容となります。

ちなみにコネクションハブの OVERFLOW 枠で参加してました

AWS CLI v2の新機能をレビュー

インストール

インストールにpythonが必要がなくなり非常に簡単になりました!
MacOSやWindowsではインストーラーポチでよいようです。
ひとまずはAmazon Linuxにインストールしてみました

cd /usr/local/src
curl "https://d1vvhvl2y92vvt.cloudfront.net/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

現在はプレビューという事もあり aws2 というコマンドが追加されていてv1のawsコマンドと共存が可能なようです

設定が対話式で行えるようになりました

aws2 configure wizard というコマンドが追加され対話式で設定が可能となっていました

こういった対話式で設定を行うものは多数増やしていく、との事。対話式で行えるのはawsコマンドの設定だけではなく、各種コマンドについても増やしていく、との事。
例えばDynamoDBでテーブルを作る時は一生懸命ドキュメントを見ながら設定していましたが…

このように `aws2 dynamodb wizard new-table` とする事で対話式で新規テーブル作成が可能でした。便利

コマンド補完がすごくなっている

complete -C aws2_completer aws2

こちらでタブ補完を有効化したのちに以下のようにタブで補完される事が確認できました。こちらがアップデートされて、各種コマンドの引数はもちろんの事、なんとそのリソースの補完も行えるようになりました。
先程つくったdynamoDBのtestというテーブルを削除したい場合の動きです。

[root@ip-10-0-0-134 ~]# aws2 dynamodb delete-
delete-backup  delete-item    delete-table   

[root@ip-10-0-0-134 ~]# aws2 dynamodb delete-table --ta<TAB>
↓
[root@ip-10-0-0-134 ~]# aws2 dynamodb delete-table --table-name <TAB>
↓
[root@ip-10-0-0-134 ~]# aws2 dynamodb delete-table --table-name test 

わざわざ実際のリソースをlistで一覧してから確認したりする必要がないです!とてもすごいと思いました

AWS SSOとの連携

AWS CLI v2だけの機能としてAWS SSOとの連携が可能なようで試してみました。
AWS SSOについては「AWS SSOを使って複数AWSアカウントのログインを簡単にする!」で試してみましたのでこの環境にログインしてみたいと思います。

まずはaws2 configure sso コマンドでssoの設定を行います。
といっても SSOのstartページとSSOを設定しているリージョンを設定するだけです。

すると
https://device.sso.us-east-1.amazonaws.com/
へのアクセスをアクセスして、出力されているcodeを入力せよ、となります。

ここでブラウザで上記URLに接続するとcodeを記述する画面が出てきます。

CLI側で出力されているコードを入力し、ログインすると、「CLIでログインするか?」というポップアップが出てくるのでSignします。

するとCLI側はsingした瞬間にずらずらと動きだし、ログインできるAWSアカウントが一覧されます。こちらもインタラクティブモードで選択可能です。

ここでアカウントを選択すると、そのaws2コマンドはもうそのAWSアカウントの世界になっています。
一旦ブラウザを挟んでログインするとCLI側が動く、というのは新鮮でしたが、SSOログインと連携する事で余計なAWSシークレットキー/アクセスキーを出力する機会を減らせるかもしれないですね。

その他の追加項目

以下はさらっと試したレベルですが以下の内容が新規に追加されているようです

・出力にyamlが追加された
・Paging機能の追加。

正式発表が楽しみですね!

AWS SSOを使って複数AWSアカウントのログインを簡単にする!

クラウド事業部の原口です。

皆様は複数あるAWSアカウントの管理、どのように管理されているでしょうか?
一般的にある1つのサービスに対して以下のように環境に応じたアカウントを用意する事が多いのではないでしょうか。

「本番環境用アカウント」
「ステージング環境用アカウント」
「テスト環境用アカウント」

このように複数の環境を作ってくると問題となってくるのがログインにかかるコストになります。
MFAなどの設定をしていったり…といった事をすべての環境ぶん行う必要があったり、新しい人がジョインした、プロジェクトを抜けた、というたびにすべての環境のIAM設定が必要。またそれぞれの権限の範囲など…となってくると本当にバカにできないコストとなってきます。

これまで複数AWSアカウントの管理といえばIAMのスイッチロールを使うという方法がとられてきましたが、
今回、AWS SSOがMicrosoft AD以外も選択できるようになった、、具体的にはAWS SSO の ID ストアでユーザー ID を作成および管理ができるようになった、という事を知り、スイッチロールの代替になるのでは!?という事で検証してみました。
もう一度繰り返しますと ADが不要でAWS SSOの機能だけで利用可能となってました。
しかも無料なんですよね。すごい。

AWS SSOを利用する

0.AWS SSOに必要な環境

・AWS SSOを設定するアカウントがAWS Organizationを利用しており、その親アカウントである事
・バージニアリージョンでのみ利用可能(ですが特に問題はないかと)

①AWS SSOを有効化

まずはリージョンをバージニアに変更して AWS Single Sign-Onのページへ移動し、「AWS SSOを有効にする」をクリックします。
これだけで AWS SSO の ID ストアでユーザー ID を作成および管理ができる状態でセットアップされます

② ID ストアでユーザー ID を作成

IDソースを選択、もしくは左メニューのユーザをクリックして新規にユーザを登録します。
ユーザはパスワード認証の他にもMFA認証も対応していますので安心ですね。
ユーザ作成すると作成時に設定したメールアドレスに招待メールが送られますのでAcceptしてください。

③AWS アカウントでOrganizationに属するアカウントに対してユーザとログイン権限と認証の設定を行う

AWS アカウントを選択すると AWS Organizationで属している組織のアカウントがすべて表示されます。これらの中でログインさせたいアカウントをチェックし「ユーザの割り当て」を行います。ここで先程のIDプールで作成したユーザが一覧として出てきます。

ユーザーの設定を行ったあとは、該当組織の子アカウントに対するアクセス権限セットを設定します。IAMポリシーとほぼ同じようなイメージとなります。
これらはアカウント毎(グループ毎)に設定できる為、きめ細かな権限設定が可能です。
ちなみにここでポリシーを設定すると子アカウント側にはそのIAMロールが作られる形でした。
つまり内部的な技術仕様としてはスイッチロールを使用しているのとほぼ変わらない動きとなっているようです。
ちなみにこのIAMロールは子アカウントのrootアカウントでも削除はできませんでした。 また、cloudtrailではIDプールで設定したユーザ名で記録されていました。(完璧!)

④作成したユーザでログインする

SSOを作成した際に同時に作成されるユーザポータルよりログインします。
https://xxxxxxx.awsapps.com/start のものですね。
xxxxxxxの部分はカスタマイズ可能ですが変更は1度しかできないようですのできちんと意味のある名前にしたほうがよいです。
上記のURLにログインするとログイン画面が出てきます

ログインするとこんな形で設定したアカウントが一覧で表示されます。アタッチしている権限で Management console をクリックするとその権限でアカウントにログインできました!

感想

AWS SSO自身がIDプールを持つ事ができるようになったおかげで、Microsoft ADを使用せずとも気軽に複数アカウントへのログインが可能となりました。
内容としてはスイッチロールと大きく変わらないようなので、「スイッチロールの設定が簡単にできるようになった」と捉えて、スイッチロールの代替として使ってみてもいいのではないかと思います。
以下はAWS公式ブログより転載している画像ですがこれをきっかけに複数の他アプリへのログインやカスタムSAMLのアプリケーションへのSSOログインが可能です

まずは簡単にAWSアカウントの管理から初めてみて、今後組織のユーザのslackなどのアプリケーションもすべてこちらでログイン管理してしまうと大変便利になっていくのではないかという可能性を感じました。
AWS CLI v2 ではこのAWS SSOに対応しているという事でそのあたりもとても便利になりそうに思います。

現時点での不満点としては以下となります
・AWS Organizationが必須なので請求を一緒にできないアカウントでは厳しい
・スイッチしたロール名を変えれないので地味にどの環境にログインしているかわからなくなる

前半は仕方がないように思いますが、ログイン後のアカウントがどのAWSアカウントであるかはすぐにわかるようになると嬉しいな、、、と感じました。

IAMユーザを使ってEC2のLinuxユーザを管理する

クラウドソリューション事業部 インフラエンジニアの上野です。

皆さんはEC2(Linux)のユーザ管理をどのようにされていますか?
個別の管理シートのようなものを用意して管理をされていたりするのでしょうか?あるいはディレクトリサービスと連携させて管理しているのでしょうか?

人の出入りが激しいプロジェクトや、そもそもの管理するインスタンス数が多い場合に 個別にユーザを作成・削除したり、現在のユーザの状況を管理するのは非常に運用コストがかかります。

このようなユーザ作成・削除の運用コストやユーザの管理の問題を解決するのにおすすめのaws-ec2-sshというツールを紹介します。

aws-ec2-ssh
https://github.com/widdix/aws-ec2-ssh

aws-ec2-sshはIAMユーザとEC2のLinuxユーザを紐付けし、IAMユーザの作成・削除に連動して、Linuxユーザも作成・削除されるという便利ツールです。

それでは早速導入していきましょう。

aws-ec2-sshの導入方法(AWS側)

まずはAWS側の設定を行います。
EC2インスタンスがaws-ec2-sshを使えるようにするためのIAMロールを用意します。 下記のポリシーを持つIAMロールを作成し、連携を行うEC2インスタンスにロールを割り当てます。

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "iam:ListUsers",
      "iam:GetGroup"
    ],
    "Resource": "*"
  }, {
    "Effect": "Allow",
    "Action": [
      "iam:GetSSHPublicKey",
      "iam:ListSSHPublicKeys"
    ],
    "Resource": [
      "arn:aws:iam::<YOUR_USERS_ACCOUNT_ID_HERE>:user/*"
    ]
  }, {
      "Effect": "Allow",
      "Action": "ec2:DescribeTags",
      "Resource": "*"
  }]
}

※<YOUR_USERS_ACCOUNT_ID_HERE>にはAWSのアカウントIDを記入してください。

次にIAMグループを作成します。
ここで作成したグループを元に連携させるIAMユーザであるか、連携させるIAMユーザは管理者権限を付与する対象であるかを判別します。
今回は linux-member を連携対象とするグループ、linux-adminを特権ユーザとするグループとします。

  • linux-member( 連携対象とするグループ )
  • linux-admin(特権ユーザ用のグループ )

それでは連携させるIAMユーザを作成します。
今回はテストのために、次の3つのユーザを登録します。

  • user-member
    Linuxの一般ユーザとして登録するユーザ
    linux-member のIAMグループに 所属
  • user-admin
    Linuxの管理者ユーザとして登録するユーザ
    linux-memberとlinux-admin のIAMグループに 所属
  • no-linux-user
    Linuxには同期させないユーザ
    いずれのIAMグループにも所属しない

IAMユーザ作成後に
IAM > ユーザ > 対象のユーザ > 認証情報 > SSHパブリックキーのアップロード
から公開鍵認証用の公開鍵をアップロードします。

作成したIAMユーザとグループの状況はこのような形です。

aws-ec2-sshの導入方法(Linuxサーバ側)

ここからはLinuxサーバ側の作業になります。
まずはLinuxサーバにaws-ec2-sshをインストールします。

cd /usr/local/src
git clone https://github.com/widdix/aws-ec2-ssh.git
cd aws-ec2-ssh
./install.sh

aws-ec2-sshの設定ファイルを作成します。

cd /usr/local/src/aws-ec2-ssh
cp aws-ec2-ssh.conf aws-ec2-ssh.conf
vi aws-ec2-ssh.conf

aws-ec2-ssh.confの内容は以下のように設定します。

IAM_AUTHORIZED_GROUPS="linux-member" #管理対象するIAMグループ
LOCAL_MARKER_GROUP="iam-synced-users" #IAMユーザと連携していることを示すグループ
LOCAL_GROUPS=""
SUDOERS_GROUPS="linux-admin" #特権を付与するIAMグループ
ASSUMEROLE=""
# Remove or set to 0 if you are done with configuration
# To change the interval of the sync change the file
# /etc/cron.d/import_users
DONOTSYNC=0 #0の場合に同期機能が有効化

一通り設定ができたので、手動でIAMユーザと連携を行います。
なお、インストール時点で同期処理がcronに登録されており、デフォルトでは10分毎に同期されるように設定されています。

sh /opt/import_users.sh

実行後にユーザが作成されていることが確認できます。
同期対象外であったno-linux-userはLinux上にはユーザを作られていません。

# egrep "user-member|user-admin|no-linux-user" /etc/passwd
user-admin:x:1004:1005::/home/user-admin:/bin/bash
user-member:x:1005:1006::/home/user-member:/bin/bash

user-adminにはsudoの実行権限があります。

# ls /etc/sudoers.d/
90-cloud-init-users  user-admin
# cat /etc/sudoers.d/user-admin
user-admin ALL=(ALL) NOPASSWD:ALL

結果はこのようになりました。

  • Linux上にuser-adminとuser-memberというユーザが追加された
  • user-adminには管理者権限が付与されていた
  • 同期対象外のno-linux-userはLinux上にユーザは作成されなかった

意図したとおりに動作していますね。
今回は作成を試しましたが、IAMユーザから削除を行うと、Linuxサーバ上のアカウントも削除されます。

aws-ec2-ssh の仕組み

ここからはaws-ec2-sshの仕組みについて少し説明します。

Q.ユーザをどのように判別しているか?
/etc/aws-ec2-ssh.conf 内の設定をもとに判断して処理しています。

  • IAM_AUTHORIZED_GROUPS
    IAM上でこのグループに所属しているユーザがLinuxサーバへの同期の対象となります。
  • SUDOERS_GROUPS
    IAM上でこのグループに所属しているユーザが管理者権限(sudoの実行権限)を付与されます。
    同期の対象となるにはIAM_AUTHORIZED_GROUPSに指定されたグループに所属している必要があり、 このグループだけに所属している場合はLinuxサーバへの同期対象にはなりません。
  • LOCAL_MARKER_GROUP
    Linuxサーバ上でIAMと同期しているユーザを判別するためのグループです。 このグループに所属しているLinuxサーバ上のユーザがIAMユーザとの同期対象になります。


Q. 同期処理はいつ・どのように実施されるか?
インストール時に/etc/cron.d/import_usersが追加されます。
このcronの設定により10分おきに同期処理が実行されます。
インストール直後からcronが動き出してしまうので注意が必要です。

SHELL=/bin/bash
PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/opt/aws/bin
MAILTO=root
HOME=/
*/10 * * * * root /opt/import_users.sh

Q. 公開鍵認証はどのように行われているのか?
aws-ec2-sshをインストールすると /etc/ssh/sshd_config に下記の設定が追加されます。

AuthorizedKeysCommand /opt/authorized_keys_command.sh

この設定によりsshで接続時に /opt/authorized_keys_command.sh の実行結果を公開鍵として渡されています。
authorized_keys_command.shではaws cliのコマンドを発行し、対象ユーザと一致するIAMユーザに登録されている公開鍵の情報を取得しています。
この仕組みによりEC2インスタンス上に各ユーザの公開鍵を設置せずに公開鍵認証ができます。

さいごに

いかがでしたでしょうか。

AIMユーザを作成するだけで対象のLinuxサーバに自動的にユーザが追加され、ユーザの公開鍵をサーバに設置するなどの手間も省けます。

また、ユーザ作成時は厳密な手続きのもと作成を行われますが、 作成されたユーザに関しては厳密に管理が行われていないということもあります。
よくある見られるのは利用されていないユーザが削除されずに残っているというパターンです。 削除手続きが正しく行われていない場合もあれば、対象サーバが多く漏れていたということも考えられます。

このような問題を解決してくれる非常に便利なツールでした。
導入も容易なので、ぜひご活用ください。

ブラウザ上でJavaScriptを利用したクリップボードへのコピー機能の実装Clipboard API版

エンジニアの中氏です。

以前、ブラウザ上でクリップボードへのコピー機能の実装という記事を投稿しましたが、より便利なブラウザの Clipboard API で実装出来る方法がありますでその方法を紹介します。

Clipboard API はクリップボードの内容を読み書きするために使用する Clipboard オブジェクトを返します。 Clipboard API は切り取り、コピー、貼り付け機能をウェブアプリケーションに実装するために使用することができます。
今回は、下記にクリップボードにテキストをセットするサンプルプログラムを記載いたします。

/**
 * 指定したテキストをクリップボードにセットする
 *
 * @text {string|HTMLElement} subject
 */
function copyTextToClipboard(text) {
     navigator.clipboard.writeText(text).then(function() {
         console.log('Async: Copying to clipboard was successful!');
     }, function(err) {
         console.error('Async: Could not copy text: ', err);
     });
 }

copyTextToClipboard(文字列)
で使用します。

文字列を指定するだけで、クリップボードにセットできます。

Clipboard API ブラウザの対応状況について、現状は、Chrome と FireFox のみの対応となっています。
よって、ブラウザを判別して前回の方法と今回の方法を両方使用すれば、ほとんどのブラウザで実装可能となります。

S3静的ホスティングの次の一歩、https対応をCloudFrontとACMで行う

こんにちは、西山です。
最近、AWS認定ソリューションアーキテクト プロフェッショナルを取得しました!

既に合格したメンバー達が「やってやろうぜ」って雰囲気で挑戦していたのを見て、影響を受けました。人生やったもん勝ちです。

ただ、やはり触って使って身に付けるが基本です。
試験受かっただけのテスト野郎にならないように、今後も手を動かすのを意識して進んでいきます。

今回はS3で静的なサイトを公開した後に、サイトをhttpsに対応する方法を説明してまいります。

この記事の対象者

  • AWSのサービスを使い始めたばかり
  • S3で静的サイトを公開して、次に何か挑戦しようと考えている
  • httpのサイトをhttpsにしたい

■1. 【ACMを使い証明書を取得する】

ACM は AWS Certificate Manager の略です。
SSL/TLS 証明書を管理し、AWSのサイトに簡単に設定する事ができます。

ちなみに、サービス名の英語3文字の略語は、正式名称で覚えた方が混乱せず各AWSのサービス内容を把握できるのでおすすめです。

それでは、ACMの設定に進んでまいりましょう。
まずはAWS マネジメントコンソールでACMの画面を開きます。
「ACM」と入力すれば「Certificate Manager」と表示されますので選択します。

AWS Certificate Manager の画面が開きましたら
まず最初に行う事があります。

注意点

画面右上のリージョン選択で
米国東部 (バージニア北部) us-east-1」を選択してください。
今回はCloudFrontにACMで作成した証明書を関連付ける為です。

その後で、「証明書のプロビジョニング」より「今すぐ始める」をクリックして設定を開始します。

補足

Amazon CloudFront で ACM 証明書を使用するには、米国東部(バージニア北部) リージョンで証明書をリクエストまたはインポートする必要があります。CloudFront ディストリビューションに関連づけられたこのリージョンの ACM 証明書は、このディストリビューションに設定されたすべての地域に分配されます。

https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/acm-regions.html

順番に設定を進めてまいります。
「パブリック証明書のリクエスト」を選択し「証明書のリクエスト」をクリック

ドメイン名の追加の入力箇所に来ました。
今回は静的なサイト http://odenge.jp/ をhttpsに対応する例で説明します。

ドメイン名は下記の2つを設定します。
odenge.jp
*.odenge.jp

※「*.odenge.jp」を設定したのは、今後、test.odenge.jp や sample.odenge.jp などでもhttps対応を考慮する為です。

注意点

「*.odenge.jp」だけでは、「odenge.jp」の設定は反映されないので
「odenge.jp」も記載してください。

以前は「Eメールの検証」のみだったとの事ですが
「Route 53」を使いS3で静的サイトをホスティングしている場合には
簡単に行う事ができる「DNSの検証を」選択します。

補足

DNS検証はRoute 53 でなくても行うことは可能です。

ここは特に何も入力しなくても大丈夫ですので
そのまま「確認」をクリック

確認し「確定とリクエスト」をクリック

「検証保留中」と表示されますが、そのまま「続行」ボタンを押します。

「状況」の「ドメイン」の箇所を開くと
「Route 53でのレコードの作成」ボタンが表示されますのでクリック

下記の情報でRoute 53 にACM設定の為のレコードが追加されます。
「作成」をクリックします。

「検証保留中」とまだ表示されていますが
DNSレコードの追加は成功しました。

「Route 53」で追加されたレコードを確認します。
「Route 53」では右上のリージョンは「グローバル」になります。

再度、「ACM」で右上のリージョンが「米国東部 (バージニア北部) us-east-1」を選択します。
少しだけ待つと「状況」が発行済みとなり証明書が発行されました。
私の場合は数分でしたが、最大で30分以上かかる場合もあるとの事でお待ちください。

これでACMで証明書の取得を行う事ができました。

■2. 【CloudFrontに証明書を設定する】

CloudFrontの画面に行き、右上のリージョンは「グローバル」しかないのでそのまま設定を進んで行き、「Web」の方の「Get Started」をクリック

補足

Q: ACM 証明書は、どの AWS のサービスで使えますか?

パブリックとプライベートの ACM 証明書は、以下の AWS のサービスで使えます。
• Elastic Load Balancing
• Amazon CloudFront
• Amazon API Gateway
• AWS Elastic Beanstalk
• AWS CloudFormation – 現時点では、E メール検証を使うパブリック証明書のみをサポートしています。

「Origin Domain Name」の箇所、手動で入力が必要かと思いましたが
空白にするとプルダウンメニューから選択できるようになり、静的サイトを公開しているS3を選択します。

「Origin Domain Name」と「Origin ID」が自動入力されます。
その他の項目は初期設定のままで下の項目に進みます。

「Alternate Domain Names」に設定するドメインを記載しました。
(www付きでもアクセスできるようにwww付きのドメインも記載しました)

「Custom SSL Certificate」を選択し
先ほどACMで作成した内容がプルダウンメニューで選択できるので選択します。

注意点

「Custom SSL Client Support」は
「Clients that Support Server Name Indication(SNI)」を選択します。
※下の項目を選択すると「$600 / month」と金額がかかってしまいます。

上記に記載した箇所以外は初期設定のまま作成すると
下記の画面のように追加された状態となります。

注意点

作成完了まで30分近くかかりました。結構時間がかかった印象ですので気長に待ちます。「Status」が「Deployed」になれば完成です。

「CloudFront」が「Deployed」になった後
「Route 53」に行き
レコードのタイプ「A」に設定されている「エイリアス先」を「CloudFrontディストリビューション」で作成した内容を選択し、「レコードセットの保存」をクリック

これで https://odenge.jp/ にアクセスすれば完成と考えていましたがエラーが起こりページが表示されませんでした。

「CloudFront」の設定を修正します。
一覧の「ID」に記載されているリンクテキストをクリックし
「Edit」ボタンを押して編集します。

S3の静的ホスティングではトップページをindex.htmlで作成していました。
「Default Root Object」に「index.html」と記載し「Yes, Edit」をクリック

問題なく表示され完成です!
いかがでしたでしょうか?

■3. 【追加のもう一歩 OAI設定】

CloudFrontを使ってアクセスする事ができるようになりましたが
S3のエンドポイントのURLを使っても、現時点ではアクセスできる状態です。

origin access identity(OAI)を使えば、S3に直接アクセスしても閲覧できなくする事が可能です。明確にCloudFrontを通してサイトアクセスを行わせる事を考慮したシステム構成にする事で、CloudFrontによりレイテンシーを低くする事や様々な構成・仕様を考慮した上でシステムを構築する事が可能です。

「CloudFront」の修正方法を見て行きましょう。
作成した内容をチェックし「Distribution Settings」をクリック

タブ「Origins and Origin Groups」を選択し該当の箇所をチェックし
「Edit」をクリック

「Restrict Bucket Access」を「Yes」
「Origin Access Identity」を「Create a New Identity」
「Comment」は自動で記入されるのでそのまま
「Grant Read Permissions on Bucket」を「Yes, Update Bucket Policy」

そして「Yes, Edit」をクリック

修正した「CloudFront」の「Status」が「Deployed」になった後で
S3のバケットポリシーを修正します。

※上記で「Grant Read Permissions on Bucket」を「Yes, Update Bucket Policy」を選択しましたが更新されなかったので直接、S3のバケットポリシーを修正します。

「CloudFront」の「Origin access identity」で下記の図の
「Amazon S3 Canonical User ID」をコピーします。

S3バケットポリシーの「Principal」を変更します。

"Principal":{"CanonicalUser":"コピーした乱数"},

参考

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/example-bucket-policies.html#example-bucket-policies-use-case-6

上記の内容で「Principal」の箇所を修正しバケットポリシーを更新しました。すると「Principal」の記載内容が違う記述に書き換わりましたが、それまでアクセスできていたS3エンドポイントのURLからのアクセスが拒否され、CloudFrontからのアクセスのみ許可されている事が確認できました。

あとがき

証明書の設定と聞くと、少し難しい印象を持たれるかもしれません。
しかし、マネジメントコンソールの操作で簡単に設定する事ができます。
ぜひ、試していただければ幸いです。

証明のない現代にドロップキック

以上、西山でした。

LaravelでCakePHP 2.xのfind(‘list’)のようなこと(インデックスつきの配列の取得)をやってみる

こんにちは。CakePHP が好きな小國です。業務では Laravel を触っています。

突然ですが、CakePHP 2.x では Model::find(‘list’) というメソッドがあり、セレクトボックスなどで使うインデックス付きの配列を取得でき重宝していました。

https://book.cakephp.org/2/ja/models/retrieving-your-data.html#find-list

Laravel でも同じようなものがほしいなと思って見ていたのですが、デフォルトの機能にはないようでしたので、作成しましたという内容の記事になります。

やってみます

  • App\Traits\Selectable.php
<?php

namespace App\Traits;

use Illuminate\Database\Eloquent\Model;

trait Selectable
{
    public static function toSelectArray()
    {
        $instance = new static;
        $key = $instance->getKeyName();
        $value = $instance->getDisplayFieldName();

        /** @var Model $this */
        return static::all([$key, $value])
            ->pluck($value, $key)->toArray();
    }

    public function getDisplayFieldName()
    {
        if (property_exists($this, 'displayField')) {
            return $this->displayField;
        }
    }
}

使い方

Selectableトレイを使い、displayFieldプロパティで表示用のカラムを指定します。

<?php

namespace App;

use App\Traits;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use Selectable;

    public $displayField = 'name';
}

toSelectArray() でインデックス付きの配列が取得できます。

User::toSelectArray();

その他

こちらにも同じソースを置いています。https://gist.github.com/tsmsogn/e6d8af0ad182b40ac512eaf1aa4d58fb

新人Webプログラマーとして過ごした2ヶ月半の振り返り

はじめまして!新人webプログラマーの石田ゆかです。

シーズに入社して2ヶ月半が経ちました。
シーズに入社するまでは電機メーカーで回路設計をしていたため、仕事内容も仕事環境も大きく変わりました。

そこで今回はシーズに入社して感じた変化や、この2ヶ月半に新人プログラマーとして取り組んで来たことをご紹介したいと思います。

シーズに入社して感じた変化

シーズに入社して感じた変化はたくさんありますが、会社生活する上で特に負担が減ったと思えることを3つご紹介します。

連絡ツールがslackとチャットワーク

シーズに入社してまず驚いたことが連絡ツールに社内はslack、社外はチャットワークをメインに使っていることです。
メールのように毎回宛先を選んだり挨拶を書く必要がないので、連絡に使う時間が圧倒的に短くなりました。
ちなみに先日久々にメールを使ったところ、ビジネスメールの書き方を忘れていました笑

会議が少ない

会議が少ないため会議の準備や出席に取られる時間が少なく、作業に充てる時間を日々十分に確保出来ています。

出社時間やお昼休憩の時間が自由

出社時間やお昼休憩の時間が自由なため電車の混んでいる時間を避けて出社したり、お店の混んでいる時間を避けてランチが出来ます。

新人プログラマーとして取り組んで来たこと

ツールの使い方を覚える

まずツールが使えないと始まらないということで、ツールの使い方を覚えることから始めました。
PHPStormやbacklogそして本ブログを書いているWordPressなど仕事で使う一通りのツールについて、YouTubeに上がっているチュートリアルを見たりググったりしながら操作を覚えました。

プログラミングスタート

私の所属する印刷事業部では主に既存サイトの保守を行なっているため、既存サイトの機能追加や変更などを行なってきました。
部長直々にサイトやプログラムの仕様、プログラムの流用方法などを教えてもらいながらプログラムの実装を行いました。


今までプロの書いたプログラムに触れたことの無かった私にとって、既存サイトのプログラムを読むことは良い勉強となっています。
また既存プログラムの思想をなるべく丁寧に理解して、私が実装した部分だけ違和感のあるプログラムとならないよう気を付けています。

JavaScript(以下JS)でも苦労

シーズに入社するまでJSを全く勉強していなかったのですが、思っていた以上にサーバーサイドエンジニアでもJSに触れる機会が多く苦労しています。
JSについては同じ印刷事業部の先輩に教えてもらったり、公式ドキュメントを読んだりUdemyの講座を受けながら学習しています。

2ヶ月間の感想とこれからの意気込み

入社して最初の2週間くらいはプログラマーの仕事が難しく気持ちに全く余裕がありませんでしたが、今はほんの少し余裕が持てるようになりました。
まだまだ勉強することだらけ、これから経験していくことだらけですが、1日でも早く一人前のプログラマーになれるよう引き続き頑張っていきたいと思います!

LaravelからS3互換のMinIOを使えるように、docker-compose環境を整える

こんにちは。小國です。最近は Laravel を触っています。

AWS で Laravel アプリケーションを運用する際、ステートレスにするために画像などのファイルを S3 に保管することがあるかと思います。

一方、弊社では Docker を使ってローカルの開発環境を整えており、そこでは S3 の代わりに S3 互換の MinIO を使用しています(使用していこうと思います)。

本記事では、Docker で MinIO の設定、および Laravel から MinIO へファイルの作成・削除・ダウロードをご紹介します。

なお、前提として、すでに Docker(docker-compose)で Laravel アプリケーションが動いているものとします。

環境

  • Laravel 6.1.0

Docker で MinIO を立ち上げる

まずは、docker-compose を使って MinIO を起動します。

  • .env
+# Minio config
+MINIO_PORT=60007
+
+# AWS config
+AWS_URL=http://minio:9000
+AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
+AWS_SECRET_ACCESS_KEY=YYYYYYYYYYYYYYYYYYYY
+AWS_DEFAULT_REGION=us-east-1
+AWS_BUCKET=test
+AWS_PATH_STYLE_ENDPOINT=true
  • docker-compose.yml
+  minio:
+    image: minio/minio 
+    ports:
+      - "${MINIO_PORT}:9000"
+    volumes:
+      - ./.docker/minio/data:/export
+    environment:
+      MINIO_ACCESS_KEY: ${AWS_ACCESS_KEY_ID}
+      MINIO_SECRET_KEY: ${AWS_SECRET_ACCESS_KEY}
+    command: server /export

ホストマシンから MinIO が動いているか確認

docker-compose up -d 後、ホストマシンから http://localhost:60007 につながることを確認します。正しく起動できると以下のような画面が表示されると思います。

f:id:seeds-std:20191108194045p:plain

設定した $AWS_ACCESS_KEY_ID$AWS_SECRET_ACCESS_KEY でログインします。

f:id:seeds-std:20191108192902p:plain

右下の「+」ボタンより、test バケットを作成し(のちほどこのバケットを使用します)、ファイルがアップロードができるか確認しましょう。

Laravel から s3ドライバーで MinIO を使うように変更

s3ドライバー で MinIO を使うよう変更し、Tinker を使って Laravel から保存できることを確認します。

  • flysystem-aws-s3-v3 インストール
$ composer require league/flysystem-aws-s3-v3 ~1.0
  • config/filesystems.php
         's3' => [
             'driver' => 's3',
+            'endpoint' => env('AWS_URL'),
+            'use_path_style_endpoint' => env('AWS_PATH_STYLE_ENDPOINT', false),
             'key' => env('AWS_ACCESS_KEY_ID'),
             'secret' => env('AWS_SECRET_ACCESS_KEY'),
             'region' => env('AWS_DEFAULT_REGION'),
$ php artisan tinker
>>> Storage::disk('s3')->put('hello.json', '{"hello": "world"}')
=> true

MinIO にファイルが作成されているかと思います。

ファイルの作成・削除・ダウロード

Laravel から MinIO へファイルの作成・削除・ダウロードをやってみます。

  • 2019_11_11_020835_create_assets_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateAssetsTable extends Migration
{
    public function up()
    {
        Schema::create('assets', function (Blueprint $table) {
            $table->increments('id');
            $table->string('model')->nullable();
            $table->integer('foreign_key')->nullable();
            $table->string('name');
            $table->string('type');
            $table->integer('size');
            $table->string('disk');
            $table->string('path');
            $table->timestamps();
            $table->index(['model', 'foreign_key']);
        });
    }

    public function down()
    {
        Schema::dropIfExists('assets');
    }
}
  • app/Asset.php
<?php

namespace App;
use Illuminate\Database\Eloquent\Model;
class Asset extends Model
{
    protected $fillable = [
        'foreign_key',
        'model',
        'name',
        'type',
        'size',
        'disk',
        'path',
    ];
}
  • routes/web.php
+    // Asset Routes...
+    Route::get('assets/{asset}/download', 'AssetController@download')->name('assets.download');
+    Route::resource('assets', 'AssetController')->only(['index', 'create', 'store', 'destroy']);
  • app/Http/Controllers/AssetController.php
<?php

namespace App\Http\Controllers;
use App\Asset;
use App\Http\Requests\StoreAsset;
use Illuminate\Support\Facades\Storage;
class AssetController extends Controller
{
    public function index()
    {
        $assets = Asset::query()->paginate();
        return view('asset.index', compact('assets'));
    }

    public function create()
    {
        return view('asset.create');
    }

    public function store(StoreAsset $request)
    {
        $file = $request->file('file');
        $path = $file->store('assets', 's3');
        if (!$path) {
            abort(500);
        }

        $asset = new Asset([
            'model' => Asset:class,
            'name' => $file->getClientOriginalName(),
            'size' => $file->getSize(),
            'type' => $file->getMimeType(),
            'path' => $path
            'disk' => 's3'
        ]);
        if ($asset->save()) {
            return redirect()->route('assets.index')->with('success', __('messages.saved'));
        }

        return redirect()->route('assets.index')->with('error', __('messages.could_not_be_saved'));
    }

    public function destroy(Asset $asset)
    {
        if (Storage::disk($asset->disk)->exists($asset->path) && !Storage::disk($asset->disk)->delete($asset->path)) {
            abort(500);
        }

        if ($asset->delete()) {
            return back()->with('success', __('messages.deleted'));
        }

        return back()->with('error', __('messages.could_not_be_deleted'));
    }

    public function download(Asset $asset)
    {
        return Storage::disk($asset->disk)->download($asset->path);
    }
}
  • app/Http/Requests/StoreAsset.php
<?php

namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreAsset extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'file' => 'required'
        ];
    }
}
  • resources/views/asset/create.blade.php
@extends('layouts.app')
@section('content')
<div class="card">
    <div class="card-header">
      {{ __('Create New') }}
    </div>
    <div class="card-body">
        <form method="post" action="{{ route('assets.store') }}" enctype="multipart/form-data">
        @csrf
        <div class="form-group">
            <label for="name">{{ __('File') }}</label>
            <input type="file" class="form-control" name="file"/>
        </div>
        <button type="submit" class="btn btn-primary" dusk="upload">{{ __('Upload') }}</button>
        </form>
    </div>
</div>
@endsection
  • resources/views/asset/index.blade.php
@extends('layouts.app')
@section('content')
<div class="card">
    <div class="card-header">
        {{ __('Assets') }}
    </div>
    <div class="card-body">
        <table class="table">
        <thead>
            <th>{{ __('ID') }}</th>
            <th>{{ __('Image') }}</th>
            <th>{{ __('File Name') }}</th>
            <th>{{ __('File Size') }}</th>
            <th>{{ __('Created At') }}</th>
            <th>{{ __('Actions') }}</th>
        </thead>
        <tbody>
        @foreach($assets as $asset)
            <tr>
                <td>{{ $asset->id }}</td>
                <td><img src="{{ route('assets.download', $asset->id) }}" width="150"></td>
                <td>{{ $asset->name }}</td>
                <td>{{ number_format($asset->size) }} Bytes</td>
                <td>{{ $asset->created_at }}</td>
                <td>
                    <form action="{{ route('assets.destroy', $asset->id)}}" method="post" class="d-inline">
                    @csrf
                    @method('DELETE')
                    <button class="btn btn-danger" type="submit" dusk="delete">{{ __('Delete') }}</button>
                    </form>
                </td>
            </tr>
        @endforeach
        </tbody>
     </table>
    {{ $assets->->appends(request()->query())->links() }}
    </div>
</div>
@endsection

まとめ

ローカルの開発環境では s3ドライバーを使って MinIO に保存し、AWS で運用時には S3 にそのまま保存する環境を作りました。

参考サイト

Page 1 of 17

© SEEDS Co.,Ltd.