sitateru tech blog: AWS

sitateru tech blog

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

ラベル AWS の投稿を表示しています。 すべての投稿を表示
ラベル AWS の投稿を表示しています。 すべての投稿を表示

2021年10月14日木曜日

Amazon EFSを使ってEKSに永続ボリュームを導入した

10月 14, 2021

AWS EKSで動かしているアプリケーション用にEFSを使って永続ボリュームを用意してみたので、その方法をまとめてみようと思います。

とあるテスト用Kubernetes環境でMySQLのイメージを使ってデータベースを動かしているのですが、何かの拍子にpodが一旦削除などされてしまうとデータベースの中身のデータが全て無くなってしまいます。
そこで永続ボリュームを確保しておいて、MySQLデータ用のディレクトリをマウントしたボリュームにすればOKというわけです。

手順はほぼこのドキュメントどおりですが、一部変えないと動かなかった箇所があるのでその点も触れつつ書いてみようと思います。
Amazon EFS CSI ドライバー

ドキュメントに従っても動かないよ!という件のissueはこちら。
Missing permission in the example IAM policy file · Issue #489 · kubernetes-sigs/aws-efs-csi-driver · GitHub

また、今回やってみた環境は以下のようになっています。
Kubernetes v1.21
EKSプラットフォーム eks.2
EFS CSI ドライバー v1.3.4


  1. Amazon EFS CSI ドライバー

まずはIAMポリシーを作成します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "elasticfilesystem:DescribeAccessPoints",
                "elasticfilesystem:DescribeFileSystems",
                "elasticfilesystem:DescribeMountTargets",
                "ec2:DescribeAvailabilityZones"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticfilesystem:CreateAccessPoint"
            ],
            "Resource": "*",
            "Condition": {
                "StringLike": {
                    "aws:RequestTag/efs.csi.aws.com/cluster": "true"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": "elasticfilesystem:DeleteAccessPoint",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/efs.csi.aws.com/cluster": "true"
                }
            }
        }
    ]
}

AmazonEKS_EFS_CSI_Driver_Policy というポリシーを作成してこのjsonでアクセス権限を設定します。

AWSのドキュメントからリンクされているポリシーと比べると"elasticfilesystem:DescribeMountTargets", "ec2:DescribeAvailabilityZones"が増えているのですが、これを追加しないと権限不足のエラーが出てドライバーが動きませんでした。🤔

このポリシーでサービスアカウントを作成します。<cluster-name><Account ID>, <region>はお使いの環境に合わせて置き換えてください。

$ eksctl create iamserviceaccount \
    --name efs-csi-controller-sa \
    --namespace kube-system \
    --cluster <cluster-name> \
    --attach-policy-arn arn:aws:iam::<Account ID>:policy/AmazonEKS_EFS_CSI_Driver_Policy \
    --approve \
    --override-existing-serviceaccounts \
    --region <region>
$ eksctl create iamserviceaccount \
    --name efs-csi-node-sa \
    --namespace kube-system \
    --cluster <cluster-name> \
    --attach-policy-arn arn:aws:iam::<Account ID>:policy/AmazonEKS_EFS_CSI_Driver_Policy \
    --approve \
    --override-existing-serviceaccounts \
    --region <region>

Helmを使ってドライバーをクラスターにインストールします。
クラスターのリージョンによってimage.repositoryは変えないといけないようなので↓を参照してください。
Amazon EKS アドオンコンテナイメージのアドレス - Amazon EKS

$ helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/
$ helm repo update
$ helm upgrade -i aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \
    --namespace kube-system \
    --set image.repository=602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/eks/aws-efs-csi-driver \
    --set controller.serviceAccount.create=false \
    --set controller.serviceAccount.name=efs-csi-controller-sa \
    --set node.serviceAccount.create=false \
    --set node.serviceAccount.name=efs-csi-node-sa

AWSのドキュメントでは efs-csi-node-sa は出てこないのですが、こちらも試した環境ではこのサービスアカウントを作成してhelmで指定しないと動きませんでした。🤔


  1. EFS ファイルシステム作成

ここはインフラ管理の都合上terraformで行いました。
セキュリティグループ、ファイルシステム、マウントターゲット(AWSコンソールでは「ネットワーク」という表示名になっています)を作成します。
VPCに複数のプライベートサブネットがある場合は aws_efs_mount_target も同様に複数作っておきましょう。

resource "aws_security_group" "efs" {
  name   = "eks-${var.cluster_name}-efs"
  # ↓クラスターがあるVPCのid
  vpc_id = var.vpc_id
  ingress {
    from_port   = 2049
    to_port     = 2049
    protocol    = "tcp"
    # ↓VPCのCIDR
    cidr_blocks = [var.vpc_cidr]
  }
}
resource "aws_efs_file_system" "fs" {
  creation_token = "eks-${var.cluster_name}-fs"
  tags = {
    "Name" = "eks-${var.cluster_name}-fs"
  }
}
resource "aws_efs_mount_target" "az" {
  file_system_id  = aws_efs_file_system.fs.id
  # ↓プライベートサブネットのID
  subnet_id       = var.subnet_private.id
  security_groups = [aws_security_group.efs.id]
}

  1. クラスターでStorageClassを追加

まずはストレージクラスの宣言です。StorageClassはnamespaceに属していないので、一度applyしておけばOKです。
parametersについてはこちらを参考にしてください👇

aws-efs-csi-driver/examples/kubernetes/dynamic_provisioning at master · kubernetes-sigs/aws-efs-csi-driver · GitHub

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
parameters:
  provisioningMode: efs-ap
  # EFSで作成したファイルシステムのID
  fileSystemId: fs-00abcdef123456789
  directoryPerms: "700"

  1. アプリケーションで永続ボリュームを確保、マウント

これで準備ができたので、PersistentVolumeを使うアプリケーションでのyamlを書いていきます。

まずはPVC(PersistentVolumeClaim)です。
storageClassName は3.で宣言したStorageClassのnameですね。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 2Gi

このPVCで確保したボリュームをコンテナにマウントします。
以下のyamlはdeploymentでやる場合のvolumeに関するところを書き出したものです。 claimName がPVCリソースのnameですね。

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: mysql
          image: mysql:8.0.25
          volumeMounts:
            - name: persistent-storage
              mountPath: /var/lib/mysql
      volumes:
        - name: persistent-storage
          persistentVolumeClaim:
            claimName: efs-claim

と、このような作業で永続ボリュームを利用できました。

冒頭で触れたドキュメント不完全問題もあって少し手間取りましたが、振り返ってまとめてみるとそう難しいことはなさそうですね。

EKSで永続ボリュームが欲しくなったときはお試しください👐

2021年8月27日金曜日

AWS App Mesh をEKSで試してみた(仮想ゲートウェイ)

8月 27, 2021

前回に続き、EKS上でのAWS App Meshをやっていきます。

今回は、仮想ゲートウェイです。
ゲートウェイがあれば、メッシュ上のリソースにメッシュ外のpodやWebなどからアクセスできるようになります💁‍♂️

今回の内容もドキュメント等いまいち見つからなかったので正確さなどは怪しいところがありますがご了承ください🙇‍♂️

Virtual gateways - AWS App Mesh


  1. 仮想ゲートウェイ作成

まずは仮想ゲートウェイとそのルートを作成します。
リスナーのポートは後で作るゲートウェイの実体と番号を合わせれば何番でもよさそうです。ルートは、全てのリクエストを前回作った仮想サービス(svc-v)へ流すよう指定してみました。

apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualGateway
metadata:
  namespace: mesh-test
  name: gateway-v
spec:
  namespaceSelector:
    matchLabels:
      mesh: mesh-1
  podSelector:
    matchLabels:
      for: gateway
  listeners:
    - portMapping:
        port: 8088
        protocol: http
      connectionPool:
        http:
          maxConnections: 1024
  logging:
    accessLog:
      file:
        path: /dev/stdout
---
apiVersion: appmesh.k8s.aws/v1beta2
kind: GatewayRoute
metadata:
  namespace: mesh-test
  name: gateway-v-route-v
spec:
  httpRoute:
    action:
      target:
        virtualService:
          virtualServiceRef:
            name: svc-v
    match:
      prefix: /
  1. ゲートウェイ作成

ゲートウェイの実体となる、envoyイメージのコンテナと type: LoadBalancer な サービスを作成します。
envoyのcontainerPortとサービスのtargetPortは仮想ゲートウェイのリスナにしているポートと等しくする必要があります。
また、envoyの環境変数としてリージョンと仮想ゲートウェイARNを与える必要があるようです。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gateway-v
  namespace: mesh-test
spec:
  replicas: 1
  selector:
    matchLabels:
      for: gateway
  template:
    metadata:
      labels:
        for: gateway
    spec:
      serviceAccountName: appmesh-proxyauth
      containers:
        - name: gateway
          image: public.ecr.aws/appmesh/aws-apmesh-envoy:v1.19.0.0-prod
          env:
            - name: AWS_REGION
              value: ap-northeast-1
            - name: APPMESH_RESOURCE_ARN
              value: arn:aws:appmesh:ap-northeast-1:123456780000:mesh/mesh-1/virtualGateway/gateway-v_mesh-test
          ports:
            - containerPort: 8088
          resources:
            limits:
              cpu: 10m
              memory: 32Mi
---
apiVersion: v1
kind: Service
metadata:
  name: gateway-v
  namespace: mesh-test
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
  type: LoadBalancer
  ports:
    - name: http
      port: 80
      targetPort: 8088
  selector:
    for: gateway

以上をapplyすると、ロードバランサーが作られて仮想ゲートウェイとして動くようになります。

$ kubectl get service
NAME        TYPE           CLUSTER-IP       EXTERNAL-IP                                                                          PORT(S)
gateway-v   LoadBalancer   10.100.184.34    aae2598dca4e348b2a934f4cd88b7982-607b093417cc2f4d.elb.ap-northeast-1.amazonaws.com   80:31248/TCP

AWSコンソールでロードバランサーを見てみると、NLBができていました。


コンソールのApp Meshでもゲートウェイが表示されています。


ゲートウェイへのアクセスですが、クラスター内からは
$ curl gateway-v.mesh-test.svc.cluster.local
外からは
$ curl <NLBのドメイン名>
でリクエストを送って、仮想サービスsvc-vに送られることが確認できました。



ということで、App Mesh作ってみるシリーズでした。
今回はごく基本的な部分しか試していませんが、各リソースで設定できる機能はいろいろあるので公式ドキュメントやCustomResourceDefinitionを参照してみてください🙏

eks-charts/crds.yaml at master · aws/eks-charts

ちなみに、App Meshには(他のサービスと同様に)作れるリソース数の制限があります。
ほとんどはサポートセンターに頼めば引き上げ可能なようですが、メッシュ設計の際はちょっと気にしておいたほうがいいかもしれないですね。
AWS App Mesh エンドポイントとクォータ - AWS 全般のリファレンス

2021年6月17日木曜日

AWS App Mesh をEKSで試してみた(仮想ルーター、仮想サービス)

6月 17, 2021

前回に続き、EKS上でのAWS App Meshをやっていきます。
今回は、仮想ルーターと仮想サービスです。
前回作った仮想ノードにアクセスするには仮想ルーターか仮想ゲートウェイが必要になるので、いよいよメッシュができていきます💪

あまりしっかり説明されたドキュメントが見つからず経験ベースな部分が多いので、正しい説明になっているかどうか怪しい部分もありますがご了承ください🙇‍


  1. 仮想ルーター作成

仮想ノードへトラフィックを振り分ける仮想ルーターを作成します。
振り分け先は weightedTargetsvirtualNodeRef.name で仮想ノード名を指定します。
プロトコルはHTTPにしましたが、他にもGRPC, HTTP2, TCPが設定できるようです。

apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualRouter
metadata:
  namespace: mesh-test
  name: vrouter-1
spec:
  listeners:
    - portMapping:
        port: 80
        protocol: http
  routes:
    - name: route-1
      httpRoute:
        match:
          prefix: /
        action:
          weightedTargets:
            - virtualNodeRef:
                name: vnode-1
              weight: 1
            - virtualNodeRef:
                name: vnode-2
              weight: 1

  1. 仮想サービス用サービス作成

仮想サービスの実体にするserviceを作成します。
selecter は、仮想サービスを介してアクセスする先のpodが全て含まれるように指定する必要があるみたいです。
今回の例だと、仮想サービス → 仮想ルーター(vrouter-1) → 仮想ノード(vnode-1, vnode-2) というトラフィックの流れになるので、vnode-1とvnode-2の実体になるpodに app: sample1 のラベルをつけてあります。

apiVersion: v1
kind: Service
metadata:
  name: svc-v
  namespace: mesh-test
  labels:
    app: sample1
spec:
  type: ClusterIP
  selector:
    app: sample1
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

  1. 仮想サービス作成

先の svc-v を実体とする仮想サービスを作成します。
name は上で仮想サービス用に作ったサービス名と等しくする必要があるみたいです。
provider には仮想サービスを通してアクセスする先を指定を指定します。単一のvirtualNodeかvirtualRouterを選択できるので、トラフィックを複数ノードに分けたいならvirtualRouter、分けなくていいならvirtualNodeにするとよさそうです。

apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualService
metadata:
  namespace: mesh-test
  name: svc-v
spec:
  provider:
    virtualRouter:
      virtualRouterRef:
        name: vrouter-1

  1. 仮想サービスをバックエンドに設定

作った仮想サービスにアクセスするには、「仮想ノードのバックエンド」にその仮想サービスを指定する必要があります。
仮想ノードのyamlで、backendsに仮想サービスを追加しておきます。

apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualNode
metadata:
  name: vnode-x
  namespace: mesh-test
spec:
  # 主要部分は省略
  backends:
    - virtualService:
        virtualServiceRef:
          name: svc-v

仮想ノード化されたPodから仮想サービスへリクエストを送ると、
ルーティングに従ったレスポンスが返ってくるはずです。

$ kubectl exec -it somepod-524b1761b4-vg8kw -- bash
root@somepod-524b1761b4-vg8kw:/# curl svc-v.mesh-test.svc.cluster.local

いろいろ試してみたところ、「バックエンドにその仮想サービスを指定したpod」で「仮想サービス名.名前空間.svc.cluster.localへトラフィックを送る」場合に仮想サービスで設定したトラフィック振り分けが行われる仕様のようです。
それ以外の条件、例えばバックエンドを指定していないpodからのリクエストや仮想サービス名だけのホスト名にトラフィックを送る($ curl svc-v)場合は(仮想ではない)Serviceの svc-v に行くようです。
なかなかややこしいですね。

2021年6月3日木曜日

AWS App Mesh をEKSで試してみた(準備~仮想ノードまで)

6月 03, 2021

突然ですが、AWS App Meshは皆さんお使いでしょうか?
AWS製のサービスメッシュ構築サービスですね。

サービスメッシュといえばIstioが有名ですが、AWS App MeshはAWS製だけあってEKSはもちろんEC2やECSのインスタンス・コンテナもメッシュに組み込むことができるのが強みという印象です。

そんなApp MeshをEKS上でいろいろと実験してみたので、自分の理解の反芻のためにもまとめてみたいと思います。

試した環境は kubernetes 1.20, App Meshコントローラ 1.3.0 でした。


  1. 準備

ドキュメントに従ってコントローラをインストールします。

Getting started with AWS App Mesh and Kubernetes - AWS App Mesh

手順はドキュメントの通りなのでここでは書きませんが、完了するとappmesh-systemネームスペースでコントローラのポッドができていました。

$ kubectl get pod -n appmesh-system
NAME                                  READY   STATUS    RESTARTS   AGE
appmesh-controller-6c55b46558-8s363   1/1     Running   0          1d

  1. mesh作成

メッシュを作成します。
egressFilter はメッシュ内部から外部へのアクセスを許可するかどうかですね。許可しない場合はDROP_ALLにします。
namespaceSelecter も何でもいいので書いておきましょう。

apiVersion: appmesh.k8s.aws/v1beta2
kind: Mesh
metadata:
  name: mesh-1
spec:
  egressFilter:
    type: ALLOW_ALL
  namespaceSelector:
    matchLabels:
      mesh: sitateru-mesh

  1. namespace作成

メッシュのリソースを配置するネームスペースを宣言します。
上のMeshで指定した namespaceSelector とラベルを合わせておきましょう。
App Meshの機能を使うにはappmesh.k8s.aws/sidecarInjectorWebhook をenabledにしてEnvoyサイドカーが入れられるようにしておく必要があります。

apiVersion: v1
kind: Namespace
metadata:
  name: mesh-test
  labels:
    mesh: mesh-1
    appmesh.k8s.aws/sidecarInjectorWebhook: enabled

  1. サービスアカウント作成

IAMで以下のポリシーを作成します。
今回は動作検証なので mesh-1 メッシュの全ての仮想ノードと仮想ゲートウェイに対して StreamAggregatedResources を許可していますが、実使用の際には適宜リソース名を指定するほうが望ましいですね。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "appmesh:StreamAggregatedResources",
            "Resource": [
                "arn:aws:appmesh:ap-northeast-1:111122223333:mesh/mesh-1/virtualNode/*",
                "arn:aws:appmesh:ap-northeast-1:111122223333:mesh/mesh-1/virtualGateway/*"
            ]
        }
    ]
}

続いてこのポリシーをアタッチしたサービスアカウントをEKSクラスターに作成します。

$ eksctl create iamserviceaccount \
    --cluster test-cluster \
    --namespace mesh-test \
    --name appmesh-proxyauth \
    --attach-policy-arn arn:aws:iam::111122223333:policy/appmesh-proxyauth \
    --override-existing-serviceaccounts \
    --approve

  1. 仮想ノード用サービス作成

仮想ノードの実体にするdeploymentとserviceを作成します。コンテナイメージは何でもいいのですが、今回はただのnginxにしています。
serviceAccountName には👆で作成したサービスアカウント名を指定します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-1
  namespace: mesh-test
  labels:
    app: sample0
spec:
  selector:
    matchLabels:
      app: sample0
  template:
    metadata:
      labels:
        app: sample0
        server: nginx
    spec:
      serviceAccountName: appmesh-proxyauth
      containers:
        - name: nginx
          image: nginx:1.19.0
          ports:
          - containerPort: 80
          resources:
            limits:
              cpu: 10m
              memory: 10Mi
---
apiVersion: v1
kind: Service
metadata:
  name: svc-1
  namespace: mesh-test
  labels:
    app: sample0
spec:
  selector:
    app: sample0
    server: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

  1. 仮想ノード作成

今作ったsvc-1 を実体とする仮想ノードを作成します。
serviceDiscovery で仮想ノードとする実体を指定するのですが、ここでは目的のサービスのDNSホスト名( サービス名.名前空間.svc.cluster.local )を指定します。

ServiceとPodに対するDNS | Kubernetes

ちなみにDNSではなくCloud Mapを使って指定することもできるみたいです。

apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualNode
metadata:
  name: vnode-1
  namespace: mesh-test
spec:
  podSelector:
    matchLabels:
      app: sample0
  listeners:
    - portMapping:
        port: 80
        protocol: http
  serviceDiscovery:
    dns:
      hostname: svc-1.mesh-test.svc.cluster.local

うまくできていればAWSコンソールでもメッシュや仮想ノードが表示されているはずです。

できていない場合は appmesh-controller ポッドのログを見れば何かわかるかもしれないのでチェックしてみてください。


そこそこ長くなってきたのでここまでにしようと思います。
ここまで作ったものだけではぜんぜんサービスメッシュになっていないのですが、続きは近いうちにまた書きます!

ちなみに、メッシュのリソース作成はkubernetes的にはカスタムリソースの作成になっています。
そのカスタムリソースの定義は👇なので、yamlをどう書けばいいのかこちらを見つつやるのがいいのではないかと思います!

eks-charts/crds.yaml at master · aws/eks-charts


2020年11月12日木曜日

AWS ECRのライフサイクルポリシーを設定して自動クリーンアップ

11月 12, 2020

今回は軽く、AWS ECR (Elastic Container Registry) の小ネタです。

AWSでコンテナを使って何かする場合ECRにコンテナイメージを保存しておくことが多いと思いますが、日ごろからイメージをよくビルド&プッシュしているとどんどんイメージが増えていきますね。


イメージサイズがものすごく大きいとかでなければそれほど料金を食うわけでもないのですが、まず使わない古いイメージをずっと保存しておくこともないよなーと思ったのでそのあたりを設定してみました。

ECRではライフサイクルポリシーという機能があり、いろいろな条件を定めてイメージを自動クリーンアップすることができるんですね。
Amazon ECRのライフサイクルポリシーでコンテナイメージのクリーンアップ | Amazon Web Services ブログ

設定できる条件は

  • タグ
  • イメージ数
  • プッシュされてからの経過日数
を組み合わせることができます。

今回は、「タグが latest ではない」かつ「プッシュされて90日以上」の条件に合うイメージを削除するポリシーを設定してみました。

AWSコンソールで設定する場合は、ECRのコンソールでリポジトリを選択して左のメニューの Lifecycle Policy をクリックすると設定画面があります。


「テストルールの編集」ボタンを押すとポリシーを適用した結果が確認できる(実際に保存されているイメージには何もしない)テスト用ページに移動するので、まずはそこで試してみるのがいいですね。

さて、ライフサイクルポリシーは「ルール」を優先順位つきで好きな数設定することで構成します。ルール作成画面はこうなっていて、一致条件は「イメージをプッシュしてから」「次の数値を超えるイメージ数」が選べます。


今回作りたい条件は「タグが latest ではない」かつ「プッシュされて90日以上」ということで、

  • タグ付け済("latest")、次の数値を超えるイメージ数(1)
  • すべて、イメージをプッシュしてから(90日)

という2つのルールを作ってこのようになります。


ルールを作ればあとは自動でそれに従ってイメージが毎日掃除されるので、とても楽ですね😊


ここまではコンソールで操作してきましたが、リポジトリが多い場合やコンソール面倒だと言う場合はもちろんCLIが使えます。

put-lifecycle-policy — AWS CLI 2.1.0 Command Reference

まずは先ほどのルールをJSON化して policy.json とでもファイルを作ります。

{
    "rules": [
        {
            "rulePriority": 1,
            "description": "keep latest image",
            "selection": {
                "tagStatus": "tagged",
                "tagPrefixList": ["latest"],
                "countType": "imageCountMoreThan",
                "countNumber": 1
            },
            "action": {
                "type": "expire"
            }
        },
        {
            "rulePriority": 2,
            "description": "expire images older than 90 days",
            "selection": {
                "tagStatus": "any",
                "countType": "sinceImagePushed",
                "countUnit": "days",
                "countNumber": 90
            },
            "action": {
                "type": "expire"
            }
        }
    ]
}

今回は全リポジトリに適用したかったのでシェルスクリプトを作りました。

#!/bin/sh
# ECRリポジトリにライフサイクルポリシーを設定する

for REPO in $(aws ecr describe-repositories --query "repositories[*].{name:repositoryName}" --output text)
do
  echo "repository: $REPO"
  aws ecr put-lifecycle-policy --lifecycle-policy-text "file://policy.json" --repository-name $REPO
done

これで一括適用ができますね💪

比較的簡単に設定ができるので、「うわっ…ECRのイメージ、多すぎ…?」と気づいた際には設定してみてはいかがでしょうか。

ちなみにterraformだとaws_ecr_lifecycle_policyリソースで設定できるようです。

aws_ecr_lifecycle_policy | Resources | hashicorp/aws | Terraform Registry

2019年2月21日木曜日

Alexaスキルを作ろう - 第2回

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

SPECというサービスのバックエンド開発などを行っています。

今回は前回の続きで、Alexaスキルの作成について書いていきます。


今回は、前回作成したスキルから呼び出されるLambdaを作成します。

まずAWSコンソールにログインして、Lambda - 関数の作成に行きます。

そこから「一から作成」、「設計図」などが選べます。

「設計図」でalexaを検索すると、
それぞれのスキルタイプに合わせたsdkが用意されています。
使用したいタイプのsdkがある場合は、それを使用しましょう。

今回はとても単純なスキルを作成したので、「一から作成」を選択します。
いろいろな言語が選べますが、今回はNode.jsで作成しました。

関数を作成すると、シンプルなレスポンスを返す関数が書かれているので、その中身を変更します。

今回作成したスキルは、「言葉を逆さまにする」です。
シンプル...!

コードを記述します。

exports.handler = async (event) => {
  const word = event.request.intent.slots.word.value;
  const response = {
    "version": "1.0",
    "response": {
      "outputSpeech": {
        "type": "PlainText",
        "text": word.split('').reverse().join('')
      },
      "shouldEndSession": false
    },
    "sessionAttributes": {}
  };
  return response;
};

こんな感じになりました。

responseの形式について、詳しくは応答の形式を参考にしてください。
「intent.slots.word」の部分の「word」は前回設定した「スロット名」に対応します。

次にこのLambdaを呼び出すトリガーを設定します。

「トリガーの追加」から、「Alexa Skills Kit」を選択して、スキルID(alexa developer consoleを参照)を設定します。

alexa developer consoleでテストしてみましょう。

前回、スキルの呼び出し名とサンプル発話を設定しました。

テスト画面で、

「<スキルの呼び出し名>を開いて、<サンプル発話>おはよう」と入力すると‥


ちゃんと逆さまにして返してくれましたね!

シンプルなスキルですが動くと面白いと思います。


今回はここまでにします。

次回は実機でのテストとスキルの公開について書ければと思います。

ありがとうございました!

2018年12月25日火曜日

[第一回]SansanとHubSpotの連携

12月 25, 2018

こんにちは、シタテルエンジニアの工です!
今回は、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 に参加してきました!

12月 21, 2018

こんにちは!
シタテル株式会社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日木曜日

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

12月 20, 2018

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月13日木曜日

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

12月 13, 2018

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スキルを作ろう

12月 12, 2018

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

主に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月7日金曜日

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

12月 07, 2018

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

今回は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年11月30日金曜日

AWSLambdaとServerlessを使ってみる[第2回]

11月 30, 2018

こんにちは、シタテルエンジニアの工です!

AWSLambdaとServerless第2回に入っていきます!
前回は、AWSLambdaとserverlessについてさらっと確認して
serverlessでServiceをcreateするところまでをやってきました。
AWSLambdaとServerlessを使ってみる[第1回]|sitateru tech blog

今回は実際にSansan OpenAPIを利用して名刺情報を取得してみます 🚀

SansanAPI


公式ドキュメント Sansan Open API

名刺API 名刺Set取得(期間指定)を使ってみます。
SansanAPIを利用するにはAPI Keyが必要です。
取得方法はこちらを参考にしてください。

作る

前回createしたServiceを利用して作っていきます。
現在この2ファイルが作成されている状態です。

  • serverless.yml
    • 各種設定を書いていきます
  • handler.js
    • 処理を書いていきます

axiosインストール

今回は、httpリクエストにaxiosを使用します。

$ npm install axios

インストールされました

.
├── handler.js
├── node_modules
│  ├── axios
├── package-lock.json
└── serverless.yml

serverless.yml

環境変数の設定

lambdaには環境変数を設定することができます。
SansanAPIKeyを環境変数として設定してみます。
serverless.yml

# you can define service wide environment variables here
  environment:
    SANSAN_API_KEY: xxx

環境変数を利用する場合は、このように取得できます。

process.env.SANSAN_API_KEY

handler.js

それでは処理を書いていきます。
lambdaの非同期処理とcallbackについてはこちらを参考にしてください。

名刺情報はお見せできないので、取得した件数を出力してみます。

'use strict';

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

module.exports.hello = async (event, context, callback) => {
  var updatedFrom = "2018-11-26T01:00:00Z"
  var updatedTo = "2018-11-27T21:00:00Z"

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

deploy

cliでdeployします。
serverlessを使わない場合、serviceを手動でzipにしてawsにdeployする必要があるのでとても面倒です...

$ serverless deploy -v

deployが完了したら情報が出力されます。

...
Serverless: Stack update finished...
Service Information
service: hello-sansan
stage: dev
region: us-east-1
stack: hello-sansan-dev
api keys:
  None
endpoints:
  None
functions:
  hello: hello-sansan-dev-hello

lambdaの関数のところにも表示されています。

環境変数も設定されています。

実行

こちらもcliで実行します。

$ sls invoke -f hello
16

件数が取得できました。

おわりに

今回はlambdaからSansanOpenAPIを利用して名刺情報をゲットしてみました!
lambdaはサーバーのこと気にせずに処理だけ書けば良く、serverlessはcliでごにょごにょできるし、とても便利です!
sitateruではlambdaを使って、SansanとHubSpotの同期を自動化しています。
どこかのタイミング第3回としてserverless-offlineを使用したlambdaの開発についても書こうと思います。

2018年11月22日木曜日

AWSLambdaとServerlessを使ってみる[第1回]

11月 22, 2018

こんにちは、シタテルエンジニアの工です!
AWS Lambdaを触る機会がありましたので、ServerlessFrameworkと一緒にいじいじしてみました!
シリーズで、
Serverlessをインストール
AWSLambdaでSansanの名刺データを取得してみる
serverless-offlineでAWSLambdaをローカルで実行
なんかを書いていこうと思います!

さらっとAWSLamdaとServerlessFrameworkについて

AWS Lambdaとは


AWS Lambda とは - AWS Lambda

AWS Lambda はサーバーをプロビジョニングしたり管理しなくてもコードを実行できるコンピューティングサービスです。

つまり、流行りのサーバーレスてやつで、サーバーを準備しなくてよいので、ちょっとした処理の実行など大変助かります。
しかも、LambdaはNode.js (JavaScript)、Python、Java (Java 8 互換)、C# (.NET Core)、 Goに対応しています!
様々な言語に対応しているので学習コストもかからないのがいいですね。

シタテルでは、HubSpotとSansanのデータ同期や、SlackCommandでリリースPRを作るなどで使っています。

Serverlessとは


Serverless Framework

The Serverless Framework is an open-source CLI for building and deploying serverless applications.

つまり、サーバーレスアプリケーションの開発を手助けをしてくれるやつです!
AWS Lambda単体で開発してみるとわかるのですが、ローカルでの開発やビルド、デバッグやらが面倒です。
このServerless Frameworkを使えばそのあたりのもやもやを解消してくれます!

作ってみる

Serverless Framework - AWS Lambda Guide - Introduction こちらがAWS用のServerlessFramework公式ドキュメントです。 基本的にこちらの内容に沿って進めていきます、

今回は、Sansanの名刺データを取得してみます。

まずはServerlessFrameworkをインストール

$ npm install -g serverless
$ serverless -v
1.33.2

正しくバージョンが表示されていたら、インストール完了です!

Serviceを作る

Serviceはプロジェクトのようなもの。
今回は、nodejsで作っていきますので、aws-nodejsを指定。
その他の言語を使いたい方はこちらを参考に。

$ serverless create --template aws-nodejs --path hello-sansan

実行すると

Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/mogura/work/sitateru/lambda/hello-sansan"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.33.2
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"

このような構成で、Serviceができました!

hello-sansan
   ├── handler.js
   └── serverless.yml

次は、実際に処理を書いていきます。
が、長くなりそうなので今回はここまでに。

おわりに

今回は、ServerlessFrameworkでserviceを作るところまでをやってみました。
次回は、実際にAWSLamdaでSansanから名刺データを取得するとことまでをやっていきます! 🚀

2018年11月14日水曜日

リリース作業を少し楽にした開発(Slack+Lambda+Golang)

11月 14, 2018

こんにちは、シタテルの鶴巻です。

devops&インフラ担当です。

シタテルのプロダクトのリリース作業を少し楽にするために、
Slack+Lambda+Golangを用いて開発した話をします。

リリースの流れ

シタテルでは、GitフローやGitHubフローを採用しています。

また、本番環境へのデプロイは、以下の流れで実施しています。

  1. developブランチからreleaseブランチを作成
  2. ステージング環境でreleaseブランチをデプロイ&動作確認
  3. 本番環境にデプロイ

ちなみに、1.でreleaseブランチを作成すると、CircleCIによって2.のステージング環境へのデプロイは自動で行われます。

地味に面倒なリリース作業

ステージング環境へのデプロイは自動化されているとはいえ、小さな手作業が合間に発生し、地味に面倒でした。

具体的には以下のような作業です。

  • developブランチからreleaseブランチの作成
    • ローカルPCでdevelopブランチを最新の状態にpullして、releaseブランチ作成してGitHubにpush
  • releaseブランチのプルリクエスト作成
    • GitHubのUIをポチポチ
  • リリースタグの作成
    • 前回のリリース以降でマージされたプルリクエストを見て、リリース内容を記述

手作業はSlackからコマンドを叩くだけにしました

Slackから以下のコマンドを叩くだけで、地味に面倒くさかった作業を自動で行うようにしました。

/release <repository> <release branch>

実際には、以下の内容を実施するGolangプログラムをLambdaで実行しています。

  • 対象のリポジトリをクローンし、developブランチからreleaseブランチを作成し、GitHubにpush
  • releaseブランチのプルリクエストを作成
  • 前回のリリース以降にマージされたプルリクエストのタイトルのリストを記述したリリースタグのドラフトを作成

構成

Slack -> API Gateway -> Lambda01(同期) -> Lambda02(非同期) -> GitHub
  1. Slackからコマンドを叩くと、Lambda01にリクエストを送信します。
  2. Lambda01は、Lambda02に対象レポジトリとブランチ名を付与してリクエストを送信します。
  3. Lambda02で作業を実行します。

Lambdaを2つ使用している理由

Slackはリクエスト送信後3秒でタイムアウトするためです。

実行したいプログラムは3秒以上かかるため、実際の作業は非同期で実施する必要がありました。

そのため、Lambda01はLambda02に処理を投げて、Slackに200を返し、実際の処理はLambda02で実施します。

Lambda01はLambda02を非同期実行で呼び出すので、リクエストをキューに入れた後は、Lambda01は関数を終了することができます。

Lambdaの呼び出しタイプ(同期・非同期)

Lambdaは、呼び出しタイプ(InvocationType)を指定することで、同期実行や非同期実行を選択することができます。

例:InvocationTypeで"Event"を指定することで、非同期実行でLambdaを呼び出せます。

input := &lambda.InvokeInput{
FunctionName: aws.String("xxxx"),
Payload: jsonBytes,
InvocationType: aws.String("Event"),
}

※同期実行は"RequestResponse"を指定します

今回使用したGitHub操作のためのGoライブラリ

その他ハマったポイント

  • APIGatewayの統合リクエストのマッピングテンプレートを使いこなせず。デバッグのやり方もわからず、使うのを諦めました。

まとめ

  • 少しの手間が自動化されるだけで、すごく楽になります。これからももっとやっていきたいです。
  • GitHub操作のライブラリはとても便利でした。
  • シタテルのサービスはRails + vue.jsが主ですが、自分の好きなGo言語を使えたので楽しかったです。

参考

slack slash-command

Lambda 呼び出しタイプ

2018年10月31日水曜日

s3の操作はaws-cliよりs3cmd使うほうが便利

10月 31, 2018

こんにちは!

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

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

シタテルではファイルをawsのs3でホスティングしています。
直接s3のファイルを操作することは少ないのですが、やはり時々は発生します。

そんなときにはaws-cliを使うこともできるのですが、s3cmdを使うとより簡単なコマンドでS3を操作できます。

いくつかよく使うコマンドをまとめました。

ディレクトリのファイル権限を操作

s3cmd setacl -r --acl-private s3://バケット名/ディレクトリ名/

-r は再帰的に配下すべてやってくれる

--acl-privateはprivate権限にする

--acl-publicはpublic権限にする

※バケットも指定できる

ディレクトリの中身を参照

s3cmd ls s3://バケット名/ディレクトリ名/

バケットの情報をみる

s3cmd info s3://バケット名

これ以外にもファイル/ディレクトリアップロードや削除などいろいろなことができます。

全てのコマンドリストはこちら

コマンドのインストール

インストールはmacの場合、

brew install s3cmd

でできます。

コマンドを使う前に、AWSのアクセスキー等を設定 する必要があります。

s3cmd --configure

ぜひ、お試ししてみてください。

2018年10月30日火曜日

AWS Loft Tokyoに行ってきました

10月 30, 2018

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

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

今回は、話題のAWS Loft Tokyoに行って来たのでレポートします。