20201024のAWSに関する記事は7件です。

MinIOを使ったAWS S3利用アプリのテストバケット・オブジェクトの準備

目的

AWS S3を利用したアプリのローカルのデバッグのモックやユニットテストなどで、

  • バケットはすでにインフラ側の設定として準備されている
  • バケット内のオブジェクト/データは別処理が作成している

の様な構成の場合、MinIOのDockerコンテナーを使うのが良さそう。クライアント側からバケットを作り必要なデータをputするというのもあるかと思うが、コンテンテナー立上たら必要がデータが入っていてるのが便利かなぁと。

こちらのQiita記事 docker-composeでMinio起動時にデフォルトのbucketを作成する を参考にバケットの用意はできたが、テスト用のデータも仕込んでみた。

ディレクトリ構成

.
├── docker-compose.yml
├── entrypoint.sh
└── test_data
    ├── README.md             # テストデータの説明などを書いておく
    ├── foobucket             # テストデータ入りバケット
    |   ├── myobject1.csv
    |   └── myobject2.json
    └── barbucket             # 空バケットの準備はディレクトリだけ掘っておく

Docker Compose

エントリーポイントの差替え

参考にした記事の様にdocker-compose.ymlに展開して書いても良いのでしょうが、長くなるので別ファイルにした。この中で事前データを準備する。

内容的にはホスト側の./test_dataをさらってコンテナ側の/dataにMinIOが認識できる形にコピーしMinIO serverを立ち上げる。

#!/bin/sh

SRC=/test_data
DATA=/data

files=$SRC/*

for file in ${files}; do
  if [ -d ${file} ] ; then
    bucket=$(basename ${file})
    mkdir -p ${DATA}/${bucket}
    mkdir -p ${DATA}/.minio.sys/buckets/${bucket}
    cp -pr ${SRC}/${bucket}/ ${DATA}/
  fi
done

minio server /data

カスタムポリシーとかは不要だったので準備してませんが、リンク先の記事のようにpolicy.jsonって用意したほうが良いのかなぁ?

docker-compose.yml

version: "3"

services:
  minio:
    image: minio/minio:latest
    ports:
      - "9000:9000"
    volumes:
      # - ./srv/data:/data
      - ./test_data:/test_data:ro
      - ./entrypoint.sh:/entrypoint.sh
    entrypoint: sh
    command: /entrypoint.sh
    environment:
      MINIO_ACCESS_KEY: AKIAIOSFODNN7EXAMPLE
      MINIO_SECRET_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Dockerホスト側にデータを残すならコメントアウトを削除。

起動

$ docker-compose up -d && docker-compose logs -f

テスト

# 事前にAWS_PROFILEとか設定済みで
$ aws --endpoint-url http://localhost:9000 s3 ls
2020-10-24 20:28:10 barbucket
2020-10-24 20:28:10 foobucket
$ aws  --endpoint-url http://localhost:9000 s3 ls s3://foobucket
2020-10-24 20:21:30         11 myobject1.csv
2020-10-24 20:22:28         12 myobject2.json
$ aws s3 ls s3://barbucket --endpoint-url http://localhost:9000
$

実はまだ現物とは組み合わせてないですがデバッグ・テストに使用する予定。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS SAM] Swaggerを使用したAPI定義

  1. [AWS SAM] 概要、Hello World
  2. [AWS SAM] Lambda関数からS3アクセス
  3. [AWS SAM] Swaggerを使用したAPI定義 (※本記事)

Swaggerについて

RESTful APIを構築するためのオープンソースのフレームワーク
記述形式はJSONまたはYAML
バージョン2と3があり記述方法が異なるため注意
Swaggerで定義したAPIをSAMテンプレートから参照して使用する

【SAM】API定義とSwaggerファイル連携

必須項目のみでのAPI定義

以下のようにAPI Gatewayを表すTypeと、デプロイするステージ名が必須項目

Resources:
  RestApi:
    Type: AWS::Serverless::Api # API Gateway
    Properties:
      StageName: Dev # デプロイするステージ名

このAPIをLambdaのトリガとする場合、Lambdaの定義は以下のようになる

  GetDataFunction:  
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: get-data/
      Handler: function.lambda_handler
      Runtime: python3.8
      Events:
        getData:
          Type: Api
          Properties:
            Path: /api/devices/{SerialNumber}
            Method: get
            RestApiId: !Ref RestApi # RestApiの定義を参照

SAMからSwaggerの参照

以下のようにDefinitionBodyでSwaggerファイルのパスを指定する

  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Dev
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: swagger.yaml # swaggerファイルのパス指定

【Swagger】API定義

パス/api/devices/{SerialNumber}のGET、POSTメソッドを以下で定義

openapi: 3.0.1
info:
  description: 'REST API 定義'
  version: '1.0.0'
  title: 
    Fn::Sub: ${AWS::StackName}_restApi # API Gatewayに生成されるAPI名
paths:
  /api/devices/{SerialNumber}: # APIのパス
    get: # GETメソッド
      summary: 'デバイス情報取得API'
      parameters:
        - name: SerialNumber
          in: path # パラメータの場所:パスパラメータ
          description: 'デバイスシリアル番号'
          required: true # 必須パラメータ
          schema: # パラメータ構造
            type: string
      responses:
        200:
          description: '成功時のレスポンス'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/device'
      x-amazon-apigateway-integration:
        uri: # APIからキックするLambda関数のARN
          Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetDataFunction.Arn}/invocations
        passthroughBehavior: when_no_templates
        httpMethod: POST # Lambda関数を呼び出す場合はPOST
        type: aws_proxy # Lambda関数を呼び出す場合はaws_proxy
    post: # POSTメソッド
      summary: 'デバイス情報設定API'
      parameters:
        - name: SerialNumber
          in: path
          description: 'デバイスシリアル番号'
          required: true
          schema:
            type: string
      requestBody: # bodyでデータを受け取る
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/device'
      responses:
        200:
          description: '成功時のレスポンス'
      x-amazon-apigateway-integration:
        uri:
          Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SetDataFunction.Arn}/invocations
        passthroughBehavior: when_no_templates
        httpMethod: POST
        type: aws_proxy

components:
  schemas:
    device: # {'SerialNumber': 'abc-001', 'type': 1, 'status': 'working', 'power': 50}を定義
      type: object
      properties:
        SerialNumber:
          type: string
        type:
          type: integer
        status:
          type: string
        power:
          type: integer
    deviceList: # deviceのListを定義
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/device'

x-amazon-apigateway-integration:はAPI Gateway, Lambda連携で必要な設定、詳細は公式ドキュメント参照

【SAM】その他API設定

API Gatewayへのアクセスのコントロール設定

今回はIAMアクセス許可を設定したいため以下のようにAuthを追加

  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Dev
      Auth:
        DefaultAuthorizer: AWS_IAM # IAMアクセス許可
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: swagger.yaml

その他のアクセスコントロールについては公式ドキュメントを参照

CORS対応

  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Dev
      Auth:
        DefaultAuthorizer: AWS_IAM
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: swagger.yaml
      Cors: # CORS追加
        AllowOrigin: "'*'"

Lambdaプロキシ統合を使用している場合は、
上記と合わせてLambda側でレスポンスヘッダを設定する必要がある

    return {
        'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
        }
    }

Stageにデプロイされる問題

勝手にStageステージにデプロイされてしまうため以下のようにOpenApiVersionを追加する

  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Dev
      Auth:
        DefaultAuthorizer: AWS_IAM
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: swagger.yaml
      Cors:
        AllowOrigin: "'*'"
      OpenApiVersion: 3.0.2 # 追加

参考記事

【初心者向け】SwaggerとAWS SAMを使ってWebAPIを簡単に作ってみた
API仕様書書くならswagger v2.0からv3.0に変更する際のポイント

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSコンソールにログインしてインスタンスをポチポチするのはもう嫌なのでターミナルに極振りしたいと思います

AWSを複数アカウント扱っていると、コンソールに入って〜インスタンスを起動・停止して〜別のアカウントに切り替えるてコンソールに入って〜インスタンス起動・停止して〜また別のアカウントに切り替えてコンソールに入って〜インスタンス起動・停止して〜・・・・・・

うわああああんっ!!んもぅっいい加減にしてっ!!!!

と言うくらいにもううんざりしてきたので、基本的な操作は全部ターミナルから行うことを固く決意しました。
ということで、EC2/RDS のインスタンス操作を行うための AWS CLI を使ったワンライナーをザザザッと並べていきたいと思います。

EC2

EC2 の操作から見ていきましょう。

確認

単体確認

まずは、基本の確認から。起動/停止するにも情報が必要になりますからね。

EC2単体確認
% aws ec2 describe-instances --profile [profile-name] --instance-ids <instance-id>

複数確認

複数インスタンスを確認したい場合は、--instance-ids オプションにインスタンスIDを複数並べていくだけですね。

EC2複数確認
% aws ec2 describe-instances --profile [profile-name] --instance-ids <instance-id> ... <instance-id>

全確認

次に、全インスタンスの確認。
といっても、全インスタンスの情報を垂れ流しても把握しづらいので、以下の情報だけを取得するようにしています。

  • Nameタグの値
  • インスタンスID
  • パブリックIP
  • プライベートIP
  • インスタンスタイプ
  • 起動時刻
  • インスタンスの状態


AWS CLI の --query オプションでフィルタリングしてもいいのですが、イマイチ分かりづらいので私は jq コマンドでフィルタリングしています。

EC2全インスタンス確認
% aws ec2 describe-instances --profile [profile-name] --instance-ids <instance-id> | jq -r '.Reservations[].Instances[] | { name: .Tags[] | select(.Key == "Name").Value, instanceId: .InstanceId, publicIP: .PublicIpAddress, privateIP: .PrivateIpAddress, InstanceType: .InstanceType, launchTime: .LaunchTime, status: .State.Name }'

describe-instances の詳細は以下を参照。
https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instances.html

起動

単体起動

まずは、基本的な単体起動から。

EC2単体起動
% aws ec2 start-instances --profile [profile-name] --instance-ids <instance-id>

複数起動

複数インスタンスを起動させたい場合は、--instance-ids オプションにインスタンスIDを複数並べていくだけですね。

EC2複数起動
% aws ec2 start-instances --profile [profile-name] --instance-ids <instance-id> ... <instance-id>

全起動

最後に、停止している全インスタンスの起動。

EC2全起動
% aws ec2 start-instances --profile [profile-name] --instance-ids $(aws ec2 describe-instances | jq -r '[.Reservations[].Instances[] | select(.State.Name == "stopped") | .InstanceId] | join(" ")')

start-instances の詳細は以下を参照。
https://docs.aws.amazon.com/cli/latest/reference/ec2/start-instances.html

停止

単体停止

まずは、基本的な単体停止から。

EC2単体停止
% aws ec2 stop-instances --profile [profile-name] --instance-ids <instance-id>

複数停止

複数インスタンスを停止させたい場合は、起動と同じように --instance-ids オプション にインスタンスIDを複数並べていくだけですね。

EC2複数停止
% aws ec2 stop-instances --profile [profile-name] --instance-ids <instance-id> ... <instance-id>

全停止

最後に、起動している全インスタンスの停止。

EC2全停止
% aws ec2 stop-instances --profile [profile-name] --instance-ids $(aws ec2 describe-instances | jq -r '[.Reservations[].Instances[] | select(.State.Name == "running") | .InstanceId] | join(" ")')

stop-instances の詳細は以下を参照。
https://docs.aws.amazon.com/cli/latest/reference/ec2/stop-instances.html

RDS

RDS の操作も見ていきましょう。

確認

単体確認

まずは、基本的な単体確認から。

RDS単体確認
% aws rds describe-db-instances --profile [profile-name] --db-instance-identifier <db-instance-identifier>

複数(全インスタンス)確認

単体確認で指定されている --db-instance-identifier オプション を外してやれば、複数インスタンスというか全インスタンス確認出来ます。

RDS全インスタンス確認
% aws rds describe-db-instances --profile [profile-name]

といっても、全インスタンスの情報を垂れ流しても把握しづらいので、以下の情報だけを取得するようにしています。

  • インスタンス名
  • インスタンスタイプ
  • DBエンジン
  • DBエンジンバージョン
  • MultiAZの有無
  • ステータス
RDS全インスタンス確認
% aws rds describe-db-instances --profile [profile-name] | jq -r '.DBInstances[] | { instanceIdentifier: .DBInstanceIdentifier, instanceType: .DBInstanceClass, engine: .Engine, engineVersion: .EngineVersion, multiAZ: .MultiAZ, status: .DBInstanceStatus }'

describe-db-instances の詳細は以下を参照。
https://docs.aws.amazon.com/cli/latest/reference/rds/describe-db-instances.html

起動

単体起動

基本的な起動はこちら。

RDS単体起動
% aws rds start-db-instance --profile [profile-name] --db-instance-identifier <db-instance-identifier>

全起動

停止している全インスタンスの起動はこちら。
--db-instance-identifier オプション では複数インスタンスIDを渡すことが出来ないため、私は for-in で回してます。

RDS全起動
% for i in $(aws rds describe-db-instances --profile [profile-name] | jq -r '[.DBInstances[] | select(.DBInstanceStatus == "stopped") | .DBInstanceIdentifier] | join(" ")'); do aws rds start-db-instance --profile [profile-name] --db-instance-identifier $i; done

start-db-instances の詳細は以下を参照。
https://docs.aws.amazon.com/cli/latest/reference/rds/start-db-instance.html

停止

単体停止

基本的な停止はこちら。

RDS単体停止
% aws rds stop-db-instance --profile [profile-name] --db-instance-identifier <db-instance-identifier>

全停止

起動している全インスタンスの停止はこちら。
こちらも起動の時と同じく for-in で回してます。

RDS全停止
% for i in $(aws rds describe-db-instances --profile [profile-name] | jq -r '[.DBInstances[] | select(.DBInstanceStatus == "available") | .DBInstanceIdentifier] | join(" ")'); do aws rds stop-db-instance --profile [profile-name] --db-instance-identifier $i; done

stop-db-instances の詳細は以下を参照。
https://docs.aws.amazon.com/cli/latest/reference/rds/stop-db-instance.html

さいごに

皆様のAWSライフがターミナルでフルになっていただければ幸いでございます。

参考

ec2 — AWS CLI Command Reference
rds — AWS CLI Command Reference
jq Manual (development version)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS認定SAP]アプリケーション移行戦略(6R)

6Rとは

2011年に調査会社であるGartner社が定義したクラウド移行の5つのアプローチに基づいて定義された以下の6つの移行戦略。

  1. Rehost ("lift and shift"):ホスト変更
  2. Replatform ("lift, tinker and shift"):プラットフォーム変更
  3. Repurchase ("drop and shop"):再購入
  4. Refactor / Re-architect:再設計
  5. Retire:リタイア
  6. Retain:保持

1. Rehost ("lift and shift")

現在の環境をそのままクラウド移行し、随時最適化する。

2. Replatform ("lift, tinker and shift")

コアの部分を変えず部分的に最適化した上で移行する。

3. Repurchase ("drop and shop")

現在のシステムやアプリケーションの使用を廃止し、新しいシステムやアプリケーションに移行す。

4. Refactor / Re-architect

現在のシステムやアプリケーションを見直し再設計した上で移行する。

5. Retire

不要なシステム・アプリケーションの使用を停止する。

6. Retain

移行しない。

参考

https://aws.amazon.com/jp/cloud-migration/

それぞれのメリット・デメリットの説明有り

https://blog.mmmcorp.co.jp/blog/2020/07/07/Six_Strategies_Migrating_to_AWS_ProsandCons/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Windows】通常の通信はVPNを経由したくないけどAWS向けはVPNを経由したい

はじめに

リモートワークでVPNを使っているんですけれども、Windowsで普通にVPNを設定するとすべての通信がVPNを経由してしまいます。
そこで、VPN先のネットワークへの通信だけVPNを経由し、普通にググったりツイッターで脱線情報収集したりするのは直接インターネットに出ていきたい。
しかしながら、特定のIPだけはVPNを経由したい。
そんな環境があって、特定の数個のIPアドレスだけ手動で登録して使ってました。

しかし、AWSのEC2スポットインスタンスをポコポコ立ち上げてアクセスする必要が出てきました。セキュリティ上、拠点以外からのアクセスは遮断しています。
スポットインスタンスを立ち上げるたびにいちいちパブリックIPを調べて手動でルーティング設定するのは面倒です。
そこで、AWS向けは全部VPN経由にする方法を考えました。
たまたまAWSが使われているサービスへの通信(例の52.68.96.58とか)もVPN経由になってしまいますが、まあ、許容範囲内でしょう。

前提条件

・AWS Tools for Windows PowerShell がインストールされている
  参考 AWS Tools for PowerShell ことはじめ
  (今回の使い方では、Credential情報の設定は不要です。)

・通常の通信はVPNを経由しない指定は設定済み
  参考 【Windows】指定した宛先の通信のみVPN経由にする

AWSのIP範囲取得

AWSのIP範囲って変わっていくんじゃないかと思ったのですが、プログラマブルに取得する方法が用意されていました。
公式ドキュメントの AWS IP アドレスの範囲 をご覧ください。

だけだと不親切なので、とりあえず取得するコマンドを

awsipv4.ps1
Get-AWSPublicIpAddressRange -Region ap-northeast-1 -ServiceKey EC2 | Where-Object {$_.IpAddressFormat -eq "Ipv4"} | Select-Object IpPrefix

これを応用して、ルーティング設定コマンドに流し込みます。

出来上がり

VPN接続をトリガにスクリプトを走らせられないかと調べたのですが、見つからなかったので、スクリプトを走らせると設定したうえでVPN接続をする方法にしました。

connectVpn.ps1
$vpnName = "MyVPN"
$vpnUser = "username"
$vpnPass = "password"

# 現状のルーティング設定を全部削除
(Get-VpnConnection $vpnName).Routes |
ForEach-Object {
    Remove-VpnConnectionRoute $vpnName -DestinationPrefix $_.DestinationPrefix
}

# AWSのIP範囲を取得(東京リージョンのEC2だけ)
Get-AWSPublicIpAddressRange -Region ap-northeast-1 -ServiceKey EC2 |
#IPv4に絞る
Where-Object {$_.IpAddressFormat -eq "Ipv4"} |
#おのおのルーティング設定
ForEach-Object {
    Add-VpnConnectionRoute $vpnName -DestinationPrefix $_.IpPrefix
}

#AWS以外でルーティングが必要ならここに記述
#Add-VpnConnectionRoute $vpnName -DestinationPrefix xxx.xxx.xxx.xxx

#VPN接続
C:\windows\system32\rasdial.exe $vpnName $vpnUser $vpnPass

実際に使うときは $vpnName $vpnUser $vpnPass をご自身の環境に合わせて変更してください。
(WindowsのUIから接続するときはVPN名だけで接続できるので、スクリプトからもVPN名だけでいけないか調べたのですが、どうも出来ないようで、ユーザー名・パスワードもスクリプト内に記述しております)

また、自分の利用範囲ということでAWSの東京リージョン・EC2・IPv4に絞ってますが、ここもご自身が利用するAWSのサービスに合わせて書き換えましょう。

PowerShellのお作法として、BOM付UTF-8で保存しましょう。(←それがベストプラクティスなのかはよくわかってない)

ルーティングテーブルが大量にあると速度低下の懸念がありますが、上記設定(40件ぐらい)では目立った速度低下は無いようです。(NURO光で変わらず800Mbpsぐらいでてます。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのbundle installで見かけないエラーが…それに対する解決法

はじめに

最近、講義やゼミでの研究も相まってAWS Cloud9でrailsを使う機会が増えている。
そんな中で初めて見たエラーだった。ネットで検索してもいまいち解決法が分かりにくかったので、ここにメモがてら残そうと思う

本題

では、どんなエラーが出たのか。以下の様なエラーだ。

$ budle install
There was an error while trying to write to
`/tmp/bundler-compact-index-xxxxxxxxx(date)-xxxxxx-xxxxxxx/versions`. There was insufficient space
remaining on the device.

ほほう…何だろう。最後の一文を見るとデバイスの容量が足りないという事みたいです。
という事は・・・デバイスのボリューム自体を増やす。もしくは使いそうにないファイルの中身を消去して、空きを作るなどが考えられます。では、前者を解決策1、後者を解決策2としたいと思います。

解決策1

これは、AWS Cloud9のボリュームを増やすという事です。記事がすでに上がっているので割愛したいと思います。

解決策2

では、何処のファイルの中身を消去しようとなりますが、私は/var/logを使う事が今回は無さそうなのでそちらを消去することにしました。その際に使用したコマンドが以下です。

$ sudo find /var/log/ -type f -name \* -exec cp -f /dev/null {} \;

最後に

という訳で、2つの解決策を提示しました。これで、容量が足りるようになると思うので、再度bundle installして貰えれば大丈夫です。
恐らくもっと良いやり方もあると思います。最後までお読みいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSとネットワーク(基礎)

はじめに

AWSはAmazon Web Servisesの略で、Amazonが提供しているクラウドサーバーのサービスです。
AWSへの理解度を高めるために、学んだことをアウトプットします。
今回はネットワークとの関係を記載します。

AWSを使う目的

AWSを使う目的は、簡単に言うとネットワークやサーバーを構築する手間を省くためです。
ネットワークやサーバーを構築するには、インターネット回線や複数のサーバー、接続するためのハブやルーターなどのネットワーク機器の用意が必要です。
AWSを使うことで仮想的なネットワークやサーバーを構築することが可能です。

リージョンとアベイラビリティーゾーン

  • リージョン
    AWSでは世界に数十箇所のデータセンター群があり、それぞれの地域に存在するデータセンター群のことをリージョンと言います。

  • アベイラビリティゾーン
    アベイラビリティゾーンとはリージョンをさらに分割したものです。
    アベイラビリティゾーンはそれぞれ独立した設備を用いているため、一つのアベイラビリティゾーンが災害などの被害を受けて使用できなくなっていても、別のアベイラビリティゾーンには影響を受けないようになっています。

Amazon VPC

AWSにて自由なネットワークを作れる領域です。
Amazon VPCを作成する時は、使用するIPアドレス範囲を指定します。

  • サブネット
    Amazon VPCを作成した後、いくつかのネットワークに分割して使用します。
    この分割したネットワークのことをサブネットと呼びます。

  • Amazon EC2 (Amazon Elastic Compute Cloud)
    サブネットの中に配置できるサーバー。
    各サーバーの個体はインスタンスと呼ばれます。
    インスタンスを作る時はCPUのスペックやディスクの容量を決めることができます。
    尚、1年間無償利用できるタイプは「t2.micro」

参考

「Amazon Web Services 基礎からのネットワーク&サーバー構築」
著者:大澤文孝、玉川憲、片山暁雄、今井雄太

最後に

本投稿が初学者の復習の一助となればと幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む