DRYな備忘録

Don't Repeat Yourself.

CWLでHello, World。

CWLって何

的なものについては、このあたりをご参考ください。Web系プログラマ諸兄の99%は縁が無いかと思います。

参考

ログ

% mkdir -p ~/tmp/cwl-playground/hello-world
% cd ~/tmp/cwl-playground/hello-world
% vi hello.cwl
% vi job.yml

hello.cwl の中身

#!/usr/bin/env cwl-runner

cwlVersion: v1.0
class: CommandLineTool
baseCommand: echo
inputs:
    message:
        type: string
        inputBinding:
            position: 1
outputs: []

job.yml の中身

message: Hello World!!

ほんで、

% cwltool ./hello.cwl ./job.yml

とすると

/usr/local/bin/cwltool 1.0.20180622214234
Resolved './hello.cwl' to 'file:///root/hello-world/hello.cwl'
[job hello.cwl] /tmp/tmpGt_fgK$ echo \
    'Hello, World!!'
Hello, World!!
[job hello.cwl] completed success
{}
Final process status is success

となる。

まとめ

これ

github.com

雑感

  • alpineでcwltoolインストールしてもなんかエラー吐いてるので調査

DRYな備忘録として

【Go言語】aws-sdk-goで Instance Profile の作成

CLIでやるのはこれ↓でやって概念理解したので、

otiai10.hatenablog.com

SDK使って同じことをコードからやる。

package main

import (
    "bytes"
    "encoding/json"
    "flag"
    "fmt"
    "time"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/iam"
)

const (
    arnPolicyAmazonS3FullAccess = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
)

var (
    region string
    name   string
)

func init() {
    flag.StringVar(&region, "region", "ap-northeast-1", "Region")
    flag.StringVar(&name, "role-name", "testtest", "Role and profile name")
    flag.Parse()
}
func main() {

    // 0) APIクライアントの初期化
    sess := session.New(&aws.Config{
        Region: aws.String(region),
    })
    client := iam.New(sess)

    // 1) Roleをつくる
    assumepolicy := map[string]interface{}{
        "Version": "2012-10-17",
        "Statement": []map[string]interface{}{
            {
                "Sid":       "",
                "Effect":    "Allow",
                "Action":    "sts:AssumeRole",
                "Principal": map[string]string{"Service": "ec2.amazonaws.com"},
            },
        },
    }
    buf := bytes.NewBuffer(nil)
    if err := json.NewEncoder(buf).Encode(assumepolicy); err != nil {
        panic(err)
    }

    createRoleOutput, err := client.CreateRole(&iam.CreateRoleInput{
        Path:                     aws.String("/"),
        RoleName:                 aws.String(name),
        AssumeRolePolicyDocument: aws.String(buf.String()),
        Description:              aws.String(time.Now().Format(time.RFC3339)),
    })
    if err != nil {
        panic(err)
    }
    role := createRoleOutput.Role

    // 2) Policyをつくる
    //    ... というのは割愛します。今回は既存のもの使おう。
    //        具体的には、 arn:aws:iam::aws:policy/AmazonS3FullAccess です。

    // 3) Role に Policy を attach する
    _, err = client.AttachRolePolicy(&iam.AttachRolePolicyInput{
        PolicyArn: aws.String(arnPolicyAmazonS3FullAccess),
        RoleName:  role.RoleName,
    })
    if err != nil {
        panic(err)
    }

    // 4) Instance Profile をつくる
    instanceprofileCreateOutput, err := client.CreateInstanceProfile(&iam.CreateInstanceProfileInput{
        InstanceProfileName: role.RoleName, // めんどくさいのでRole名と同じにします
    })
    if err != nil {
        panic(err)
    }
    instanceprofile := instanceprofileCreateOutput.InstanceProfile

    // 5) Instance Profile に Role を追加する
    _, err = client.AddRoleToInstanceProfile(&iam.AddRoleToInstanceProfileInput{
        InstanceProfileName: instanceprofile.InstanceProfileName,
        RoleName:            role.RoleName,
    })
    if err != nil {
        panic(err)
    }

    // 成果物
    fmt.Printf("%+v\n", instanceprofile)
}

実行

% go run main.go
{
  Arn: "arn:aws:iam::111222333444555666:instance-profile/testtest",
  CreateDate: 2018-06-05 08:18:09.494 +0000 UTC,
  InstanceProfileId: "AIPAJDNEGOMABCDEFGHIJK",
  InstanceProfileName: "testtest",
  Path: "/"
}

雑感

DRYな備忘録として

Goならわかるシステムプログラミング

Goならわかるシステムプログラミング

Amazon Web Services パターン別構築・運用ガイド 改訂第2版 (Informatics&IDEA)

Amazon Web Services パターン別構築・運用ガイド 改訂第2版 (Informatics&IDEA)

aws cli で IAM Instance Profile をつくってEC2に適用する

Role つくる

% aws iam get-role --role-name foobar
% aws iam create-role --role-name foobar --assume-role-policy-document file://assume-role-policy.json
% aws iam get-role --role-name foobar

assume-role-policy.json というのは、

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Policy つくる

arn:aws:iam::aws:policy/AmazonS3FullAccess など既存のものを使ってもよい。

% aws iam create-policy --policy-name testpolicy --policy-document file://testpolicy.json
% aws iam list-policies --query Policies[?PolicyName==\'testpolicy\']
% export POLICY_ARN=`aws iam list-policies --query Policies[?PolicyName==\'testpolicy\'].Arn --output text`
% echo $POLICY_ARN

testpolicy.json というのは、

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:List*"
            ],
            "Resource": [
                "arn:aws:s3:::*"
            ]
        }
    ]
}

Role に Policy を attach する

% aws iam attach-role-policy --role-name foobar --policy-arn $POLICY_ARN
% aws iam list-attached-role-policies --role-name foobar

Instance Profile をつくる

% aws iam create-instance-profile --instance-profile-name hogefuga
% aws iam get-instance-profile --instance-profile-name hogefuga

Instance Profile に Role を attach する

% aws iam add-role-to-instance-profile --instance-profile-name hogefuga --role-name foobar
% aws iam get-instance-profile --instance-profile-name hogefuga

以上でいけてるはず。

確認: 作成した Instance Profile を利用してみる

Negative Control: 作成したInstance Profileを使わずにEC2をローンチする

% aws ec2 run-instances \
--region ap-northeast-1 \
--instance-type t2.nano \
--associate-public-ip-address \
--key-name otiai10-test.tokyo \
--image-id ami-92df37ed \
--security-group-ids sg-8fec68f7
[ec2-user@ip-172-31-26-230 ~]$ aws s3 ls s3://otiai10-test-bucket
Unable to locate credentials. You can configure credentials by running "aws configure".

Positive: 作成したInstance Profileを付与してEC2をローンチする

% aws ec2 run-instances \
--region ap-northeast-1 \
--instance-type t2.nano \
--associate-public-ip-address \
--key-name otiai10-test.tokyo \
--image-id ami-92df37ed \
--security-group-ids sg-8fec68f7 \
--iam-instance-profile Name=hogefuga
[ec2-user@ip-172-31-21-74 ~]$ aws s3 ls s3://otiai10-test-bucket
                           PRE aaa/
                           PRE bbb/
                           PRE xxx/
[ec2-user@ip-172-31-21-74 ~]$ touch perm-test-example.txt
[ec2-user@ip-172-31-21-74 ~]$ aws s3 cp perm-test-example.txt s3://otiai10-test-bucket/example.txt
upload failed: ./perm-test-example.txt to s3://otiai10-test-bucket/example.txt An error occurred (AccessDenied) when calling the PutObject operation: Access Denied

当該 Instance Profile を付与したインスタンスだけが、S3へ指定した権限を持つことが確認できた。

後片付け

% aws ec2 terminate-instances --instance-ids i-00dd9b9d9310be741 i-0f78bdb6bf62cc5c5
% aws iam remove-role-from-instance-profile --instance-profile-name hogefuga --role-name foobar
% aws iam delete-instance-profile --instance-profile-name hogefuga
% aws iam detach-role-policy --role-name foobar --policy-arn $POLICY_ARN
% aws iam delete-role --role-name foobar

雑感

  • IAM Instance Profile まわりをCLIでやるのはたいへんだるい

DRYな備忘録として

合格対策 AWS認定ソリューションアーキテクト - アソシエイト

合格対策 AWS認定ソリューションアーキテクト - アソシエイト

Amazon Web Services パターン別構築・運用ガイド 改訂第2版 (Informatics&IDEA)

Amazon Web Services パターン別構築・運用ガイド 改訂第2版 (Informatics&IDEA)

AWSによるサーバーレスアーキテクチャ

AWSによるサーバーレスアーキテクチャ

Rの日付・時間のパースと、時間差分の計算

# Datetime文字列のパース
> t1 <- strptime("May 11 08:42:20", format = "%b %d %H:%M:%S"
1. )
> t2 <- strptime("May 11 08:47:23", format = "%b %d %H:%M:%S")

# 時間の差分
> t2 - t1
Time difference of 5.05 mins

# 秒でくれ
> as.numeric(t2 - t1, units = "secs")
[1] 303
> 

追記 2018/05/16

Rによるやさしい統計学

Rによるやさしい統計学

DRY

2分で Google Cloud Functions を使ってみる

GAE/Goが好きなのでだいたいGAEでやっちゃおうとするんですけど、せっかくなのでCloud Functions使ってみたいじゃないですか。

すげー雑に、かんたんなことはGCFでやりましょう、となる。知らんけど。ある1つのトリガーに対して連鎖的に外部APIをトリガーしたいとき、みたいなシンプルで軽い用途に向いている、的な雰囲気だと思う。

% mkdir -p ~/tmp/gcftest
% cd ~/tmp/gcftest
% gcloud components update
% gcloud projects create otiai10-gcftest
# ファイルつくります
% vi hello.js
% cat hello.js
exports.hello = (req, res) => {
    res.send(`Hello World!`);
}

# デプロイします
% gcloud functions deploy hello --trigger-http --project otiai10-gcftest
API [cloudfunctions.googleapis.com] not enabled on project
[otiai10-gcftest]. Would you like to enable and retry?  (y/N)?  y
# 中略
Deploying function (may take a while - up to 2 minutes)...failed.
ERROR: (gcloud.functions.deploy) OperationError: code=3, message=Function load error: File index.js or function.js that is expected to define function does not exist in the root directory.
# は?

# function.js にする
% ls -la
% mv hello.js function.js

# 再度デプロイを試みる
% gcloud functions deploy hello --trigger-http --project otiai10-gcftest
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
entryPoint: hello
httpsTrigger:
  url: https://us-central1-otiai10-gcftest.cloudfunctions.net/hello
# 中略
status: ACTIVE
timeout: 60s
updateTime: '2018-04-27T06:15:24Z'
versionId: '2

regionがus-central1になってるのがアレですが、

% curl https://us-central1-otiai10-gcftest.cloudfunctions.net/hello
Hello World!%                                                                                                                                                                                                                                %

やるじゃん... 2分でできた。

以下、いろいろためす

とりあえずprojectの指定めんどいので

% gcloud config set project otiai10-gcftest
% gcloud functions --help

% gcloud functions call --help
# callでcurl相当のことができる
% gcloud functions call hello
executionId: dnaermtmefak
result: Hello World!
%

% gcloud functions list
NAME   STATUS  TRIGGER       REGION
hello  ACTIVE  HTTP Trigger  us-central1
% gcloud functions describe hello

# 削除
% gcloud functions delete hello
Resource
[projects/otiai10-gcftest/locations/us-central1/functions/hello] will
be deleted.

Do you want to continue (Y/n)?  y

Waiting for operation to finish...done.
Deleted [projects/otiai10-gcftest/locations/us-central1/functions/hello].
% gcloud functions call hello
ERROR: (gcloud.functions.call) ResponseError: status=[404], code=[Not Found], message=[Function hello in region us-central1 in project otiai10-gcftest does not exist]
%

# deployのオプションいろいろある
% gcloud functions deploy --help

# これとか
     --entry-point=ENTRY_POINT
        By default when a Google Cloud Function is triggered, it executes a
        JavaScript function with the same name. Or, if it cannot find a
        function with the same name, it executes a function named function. You
        can use this flag to override the default behavior, by specifying the
        name of a JavaScript function that will be executed when the Google
        Cloud Function is triggered.

# deployのargに渡すNAMEが、HTTPのendpointになって、
# デフォルトではその名前が、exportされてる関数名と紐づくっぽい。
# --entry-pointでは、エントリポイントとなる関数名を指定できるようだ。

% gcloud functions deploy testtest \
--region ap-northeast1 \
--entry-point hello \
--trigger-http

ERROR: (gcloud.functions.deploy) ResponseError: status=[400], code=[Bad Request], message=[The request has errors
Problems:
region asia-northeast1 is not supported.

# マジか

f:id:otiai10:20180427153413p:plain

あ、Tokyoにまだ来てないのか...

% gcloud functions deploy testtest \
--entry-point hello \
--trigger-http


% curl https://us-central1-otiai10-gcftest.cloudfunctions.net/testtest
Hello World!

やるじゃん...!

シークレットの受け渡しどうする

外部APIを叩かせるにはそのAPIシークレットをもたせる必要があるよなー、と思い。環境変数かな?と思ったけどそうではないのかな。

めんどくさそう。jsonでいいや。

const secrets = require("./secrets.json");

exports.leton = (req, res) => {
    res.send(secrets);
}
% gcloud functions deploy leton --trigger-http
% curl https://us-central1-otiai10-gcftest.cloudfunctions.net/leton
{"name":"Hiromu OCHIAI","age":17}
%

秘密が漏れてしまった。ソースコード管理・共有時はsecrets.jsonをignoreするなりすればよいのだろうけど、アーキテクチャ的にプロジェクトのrootディレクトリをGCSに上げてるっぽいし、なんかちょっと気になるなと思った。ので質問してみた。

既出だったらすみません。GCFに、なんらかのシークレットを渡したい(だいたいの場合たぶん外部APIトークンとか)場合のベストプラクティスって紹介されてますか? 環境変数かなと思ってググったらRuntime-Config とかいうやつにぶちあたったんですが、betaっぽいし、今の所普通にデプロイ対象ディレクトリ配下にsecrets.jsonみたいなの作ってdeployしてます。

Functionかー。多少悩ましいねー 最近、適当にSecret ManagerのDemoアプリみたいなのを作ってみたりしていた https://github.com/sinmetal/gcpsm IAP, App Engine, Cloud KMS, Cloud Datastoreを使っている App Engineの時は、インスタンス起動時に取りに行って、メモリに持っておけばいいやーと思っていたけど、Functionですごい細かく起動する場合にこういうことするとLatencyとコストが多少気になるかも?

意外と無い。

意外とふわふわしてるんですね。KMSとかoverkillなので当面 secrets.json でいいや、という気持ちにおちつきました :wink: ありがとうございます :bow:

認証などどうする

世界中にオープンでいくらでもリクエストできるのはおかしい。限られた者からの呼び出しだけ許すにはどうすればよいか。

「CloudStorageへの権限をチェックする」という機構を使って、CloudFunctionsにおける認証機構を実現している。だるい。 これもsecrets.json的な感じでまずは実装してしまおうと思う。

雑感

  • 慣れたらサクッと作れてよさそう
  • とりあえず東京来てくれたのむ

DRYな備忘録として