helmを使ったK8s用yamlのテンプレートの利用

helmを導入してK8syamlをテンプレート記載したい

  • helmとは
  • なぜhelmがほしいのか

helmとは

Kubernetes(K8s)に関する技術で、K8s用のyamlを記載しやすいようにするモノ、という理解
そもそもパッケージ管理ソフトなので、wordpressK8sで入れたい!とかいうときに世の中にあるパッケージを使ってインストールということも可能(ぽい)

なぜhelmがほしいのか

どういうシーンでほしいかというと
例えば、iamgeのpullするUrlを隠蔽したい場合、です。(もしくは環境毎にimageの情報が変わる、など)

  • imageはECRからpullしたい
    • 例: <i>aws_account_id</i>.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latestからpullしたい
  • でもGitHubのpublicなリポジトリに直接AWSアカウントをpushしたくない
    • 上記例でいうとaws_account_idの部分は秘匿情報なのでGitHubにPushしたくない
結論

結論はこちら

例えば、

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
  labels:
    app: sample
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: sample
        image: aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest
        ports:
        - containerPort: 80

こんなふうに、直接imageを記載するのを避けたい。
aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latestを直接記載したくない)

version情報

$ helm version
version.BuildInfo{Version:"v3.3.3", GitCommit:"55e3ca022e40fe200fbc855938995f40b2a68ce0", GitTreeState:"dirty", GoVersion:"go1.15.2"}
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0", GitCommit:"70132b0f130acc0bed193d9ba59dd186f0e634cf", GitTreeState:"clean", BuildDate:"2019-12-13T11:52:32Z", GoVersion:"go1.13.4", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"16+", GitVersion:"v1.16.6-beta.0", GitCommit:"e7f962ba86f4ce7033828210ca3556393c377bcc", GitTreeState:"clean", BuildDate:"2020-01-15T08:18:29Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"linux/amd64"}

helm

上記問題点をhelmを使うことによって解決できます

helmはyamlの一つの機能として、テンプレーティングを行えます。
上記sample-deploymentというdeploymentを例にすると

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
  labels:
    app: sample
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: sample
        image: {{ .Values.app.imageUrl }}
        ports:
        - containerPort: 80

このように{{ .Values.app.imageUrl }}と記載し、この部分をhelmに別ファイルで渡すことができます
そしてその別ファイルをGitHubにpushしない運用によって、秘匿情報を隠蔽できたり、環境毎にyamlを用意しなくても済む、というもの。

helmを使うと、K8syamlを直接記載せずに、
上記テンプレートだけをひたすら記載するような運用になる(と思います)

helm使い方

  • helmのインストール
$ brew install helm
  • helmの初期化(プロジェクト作成)
$ helm create mychart

mychartフォルダにサンプルとなるchart類が作成されます
フォルダ構成は以下の感じ

./mychart
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

基本的にいじるのが、templatesディレクトリの中身と、values.yamlの中身

最小限の構成への変更

シンプルな構成とするために、上記helm create mychartで作ったchartから、不要部分を削除します
以下のファイルを削除

  • mychart/charts/削除
  • mychart/templates/の中身削除
  • mychart/values.yamlの中身を全削除
mychart/values.yamlを記入
app:
  imageUrl: aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest
mychart/tempates/deployment.yamlを記入
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
  labels:
    app: sample
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: sample
        image: {{ .Values.app.imageUrl }}
        ports:
        - containerPort: 80

上記2つのファイルを記載した状態でheml installしていきます

helm installのチェック
$ helm install --dry-run --debug --generate-name ./mychart

これで、このhelmを実行した際に何が起こるか(どのようなK8sリソースが作成されるか)がわかる

$ helm install --dry-run --debug --generate-name ./mychart

NAME: mychart-1600747915
LAST DEPLOYED: Tue Sep 22 13:11:56 2020
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
USER-SUPPLIED VALUES:
{}

COMPUTED VALUES:
app:
  imageUrl: aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest

HOOKS:
MANIFEST:
---
# Source: mychart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
  labels:
    app: sample
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: sample
        image: aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest
        ports:
        - containerPort: 80

このように表示されます
肝心のimage部分image: aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latestとなっているので想定通り

helm install

helm installにてリソースをK8sに反映していきます。
以下のようなコマンド構成です。

$ helm install <chart-name> <chart-directory>

chartに名前を付ける必要があります
(省略できるようですが、省略した場合ランダムな名前がつけられてめんどくさいので、原則名前つけないといけない)

$ helm install chartname ./mychart
NAME: chartname
LAST DEPLOYED: Tue Sep 22 13:20:16 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

このように出ます。
kubectlで確認

$ kubectl get all 
NAME                                    READY   STATUS         RESTARTS   AGE
pod/sample-deployment-b978cbc69-lczpd   0/1     ErrImagePull   0          32s
pod/sample-deployment-b978cbc69-tswdg   0/1     ErrImagePull   0          32s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   9d

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/sample-deployment   0/2     2            0           32s

NAME                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/sample-deployment-b978cbc69   2         2         0       32s

imageがめちゃくちゃ適当な名前(aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest)なので起動は失敗していますが、なんかそれっぽい。

helm list
$ helm listNAME            NAMESPACE       REVISION        UPDATED                                     STATUS          CHART               APP VERSION
chartname       default         1               2020-09-22 13:20:16.082367 +0900 JST        deployed        mychart-0.1.0       1.16.0     

インストール済みのhelmのlistが出ます

helm upgradeの準備

インストール済みのchartを更新したい場合にはhelm upgradeです

$ helm upgrade <chart-name>

今回以下の内容を変更します

  • deploymentのコンテナ名称をmyappにする
  • imageをnginx:latestにする
values.yamlの変更
app:
  imageUrl: nginx:latest
  appName: myapp

このように変更しました

deployment.yamlを変更
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
  labels:
    app: sample
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: {{ .Values.app.appName }}
        image: {{ .Values.app.imageUrl }}
        ports:
        - containerPort: 80

{{ .Values.app.appName }}を追記

helm upgradeの実行

helm upgradeによってchartが更新できるわけですが、その前に内容を確認したいです。
その場合には--dry-runを使います。
ついでに--debug表示もします

$ helm upgrade --dry-run --debug chartname ./mychart
upgrade.go:121: [debug] preparing upgrade for chartname
upgrade.go:129: [debug] performing update for chartname
upgrade.go:287: [debug] dry run for chartname
Release "chartname" has been upgraded. Happy Helming!
NAME: chartname
LAST DEPLOYED: Tue Sep 22 13:29:21 2020
NAMESPACE: default
STATUS: pending-upgrade
REVISION: 2
TEST SUITE: None
USER-SUPPLIED VALUES:
{}

COMPUTED VALUES:
app:
  appName: myapp
  imageUrl: nginx:latest

HOOKS:
MANIFEST:
---
# Source: mychart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
  labels:
    app: sample
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: myapp
        image: nginx:latest
        ports:
        - containerPort: 80

これで結果を確認できます(まだ適応されてはおらず、結果を表示しただけです)

pluginについて

helm upgradeを実行する前に、diffというプラグインを使ってみます
helmの差分を表示できるpluginのようです github.com

hlem用のプラグインが多く開発されており、いろいろなものがあるようです。

  • diff pluginのinstall
$ helm plugin install https://github.com/databus23/helm-diff
diffの確認

--valuesでvalues.yamlを指定しています(これしないとうまく差分が出ない)

helm diff upgrade chartname ./mychart --values ./mychart/values.yaml
default, sample-deployment, Deployment (apps) has changed:
  # Source: mychart/templates/deployment.yaml
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: sample-deployment
    labels:
      app: sample
  spec:
    replicas: 2
    selector:
      matchLabels:
        app: sample
    template:
      metadata:
        labels:
          app: sample
      spec:
        containers:
-       - name: sample
-         image: aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest
+       - name: myapp
+         image: nginx:latest
          ports:
          - containerPort: 80

内容は問題なさそうなので、helm upgradeをやっていきます

helm upgradeの実行

helm upgradeの実行をします
コマンドの実行結果は以下のような感じでした。

-values./mychart/values.yamlを指定しています(diffのコマンドからdiff部分を削除しただけ)

$ helm upgrade chartname ./mychart --values ./mychart/values.yaml 
Release "chartname" has been upgraded. Happy Helming!
NAME: chartname
LAST DEPLOYED: Tue Sep 22 13:58:40 2020
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
upgradeの結果の確認
$ kubectl get all
NAME                                     READY   STATUS    RESTARTS   AGE
pod/sample-deployment-787c75d69d-99gk7   1/1     Running   0          112s
pod/sample-deployment-787c75d69d-x6krf   1/1     Running   0          106s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   9d

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/sample-deployment   2/2     2            2           4m7s

NAME                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/sample-deployment-787c75d69d   2         2         2       112s
replicaset.apps/sample-deployment-b978cbc69    0         0         0       4m7s

imageをnginxにしたので動き出しました。

一部設定の隠蔽

今回の目的はGitHubにPushしたくないことでした。

以下のような方針で秘匿情報を隠蔽します

  • values.yamlにはデフォルト値を書いておく
  • override.yamlというファイルを作り、ここに秘匿情報を書く
    • 秘匿にしたい情報のみこのファイルに書く
    • ファイル名はなんでもOKです
  • override.yaml.gitignore登録しておく
override.yaml

以下のように記載します(今回はapp.imageUrlを隠蔽したい)

app:
  imageUrl: aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest

app.imageUrlのみを上書きします

この状態でdiffしてみます
--valuesコマンドを2つ以上指定することができます

helm diff upgrade chartname ./mychart --values ./mychart/values.yaml --values ./mychart/override.yaml 
default, sample-deployment, Deployment (apps) has changed:
  # Source: mychart/templates/deployment.yaml
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: sample-deployment
    labels:
      app: sample
  spec:
    replicas: 2
    selector:
      matchLabels:
        app: sample
    template:
      metadata:
        labels:
          app: sample
      spec:
        containers:
        - name: myapp
-         image: nginx:latest
+         image: aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest
          ports:
          - containerPort: 80

こうなります。
想定通り。

upgradeします
$ helm upgrade chartname ./mychart --values ./mychart/values.yaml --values ./mychart/override.yaml 

無事にupgradeが完了

helm delete

最後は削除します

$ helm delete chartname

まとめ

  • K8syamlをGit管理する際に秘匿情報の管理、もしくは環境毎に変化する情報の管理、というつらみがある
  • 一つの解決策として、helmのテンプレート機能による値の注入が可能
  • values.yamlにデフォルトの情報を記載し、別のyaml(override.yaml等)に可変情報を記載する
    • override.yamlは必要に応じて.gitignoreする

terraformで特定のmoduleだけapplyする方法メモ

結論

$ terraform apply -target=module.s3
  • module追加しているときにはモジュールのインストールしないといけない(エラーメッセージでる)
$ terraform init

おまけ : S3の静的ホスティングのterraform

  • main.tf
module "s3" {
  source = "./modules/s3"

  app_name = "app-name"
}
  • modules/s3.tf
resource "aws_s3_bucket" "bucket" {
  bucket = "${var.app_name}-app-s3-endpoint"
  acl    = "public-read"

  website {
    index_document = "index.html"
  }
}

resource "aws_s3_bucket_policy" "source" {
    bucket = aws_s3_bucket.bucket.id
    policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "${aws_s3_bucket.bucket.arn}/*",
            "Condition": {}
        }
    ]
}
EOF
}
  • /module/variables.tf
variable "app_name" {}

おまけ2 : s3へのコピー

$ aws s3 ls
  • フォルダ内のファイル全部コピー
$ aws s3 cp build s3://<bucket-name>/ --recursive
$ aws s3 rm s3://<bucket-name>/ --recursive
  • 最初は以下のコマンドでやった・・・(xargs)
$ ls | xargs -I{} aws s3 cp {} s3://<bucket-name>/

おまけ3 : S3の静的ホスティングについて

AWS EKSでLoadBalancerを使い、https通信する方法メモ

目的

  • EKSで公開したサービスをhttpsにしたい!

目次

httpsを行う核心部分

結論をさっさとみたい方はこちら

前提

  • ドメインを取得済み
    • 便宜上example5.comを持っているとします
    • Route53でのドメインが簡単
  • ドメインの証明書はAmazon Certificate Manager(ACM)を使ってる
    • 便宜上*.example5.comに対する証明書を持っているとします
    • ぽちぽちクリックするだけで取得可能

参考

EKSへのログイン方法

f:id:y-ni-shi:20200912151939p:plain クラスタをEKS作成している状態で、aws cliを使います

$ aws sts get-caller-identity

これで自分のawsアカウントにログインできている状態で(aws cliのセットアップは割愛)

$ aws eks --region ap-northeast-1 update-kubeconfig --name sample

EKSでnginxをデプロイします

  • deployment
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      app: my-nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

確認

k8sクラスタからしかアクセスできないので、クラスタ内にbusyboxコンテナを立てて確認する f:id:y-ni-shi:20200912144627p:plain

まずはipアドレスを確認(EKSクラスタ内のプライベートなIPアドレス

kubectl get po -o=wide
NAME                        READY   STATUS    RESTARTS   AGE   IP             NODE                                             NOMINATED NODE   READINESS GATES
my-nginx-75897978cd-kxmhn   1/1     Running   0          96s   172.31.9.104   ip-172-31-7-95.ap-northeast-1.compute.internal   <none>           <none>
my-nginx-75897978cd-sxs6k   1/1     Running   0          96s   172.31.7.192   ip-172-31-7-95.ap-northeast-1.compute.internal   <none>           <none>

Podが2個たっていて、172.31.9.104172.31.7.192だということがわかる
busyboxをたてる

$ kubectl run --rm -it --image=busybox --restart=Never nginx-test sh

これでコンテナに入れるので、ここからwgetで確認する

/ # wget -O - 172.31.9.104
Connecting to 172.31.9.104 (172.31.9.104:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...略

無事にnginxのメッセージが帰ってきました

serviceをたてる

とりあえずはhttpで通信できるサービスをたてる

$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-nginx
  ports:
  - name: http
    port: 80
    targetPort: 80
  type: LoadBalancer
EOF
$ kubectl get svc
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP                                                                    PORT(S)        AGE
kubernetes   ClusterIP      10.100.0.1     <none>                                                                         443/TCP        15m
my-service   LoadBalancer   10.100.4.181   a127d8af5450f4f43bfb21d5fe3a7800-1476400784.ap-northeast-1.elb.amazonaws.com   80:31724/TCP   5m17s

EC2のロードバランサをAWSのコンソールから確認
自動的にロードバランサが作られています
f:id:y-ni-shi:20200912161923p:plain

a127d8af5450f4f43bfb21d5fe3a7800-1476400784.ap-northeast-1.elb.amazonaws.comこれがELBのエンドポイント
この名前が伝搬するまでちょとっとかかりますので、watchしながらIPアドレスが帰ってくるまで待つ

$ watch nslookup a127d8af5450f4f43bfb21d5fe3a7800-1476400784.ap-northeast-1.elb.amazonaws.com

** server can't find hogehoge.ap-northeast-1.elb.amazonaws.com: NXDOMAINという表示ではなく、
こんなふうにレスポンスが来たら準備完了

Non-authoritative answer:
Name:   a127d8af5450f4f43bfb21d5fe3a7800-1476400784.ap-northeast-1.elb.amazonaws.com
Address: 52.196.124.185
Name:   a127d8af5450f4f43bfb21d5fe3a7800-1476400784.ap-northeast-1.elb.amazonaws.com
Address: 52.198.121.155
$ curl a127d8af5450f4f43bfb21d5fe3a7800-1476400784.ap-northeast-1.elb.amazonaws.com

さきほどのnginxと同じレスポンスがあるはず

名前をつける

Route53でルーティングをする 以下のように設定する

  • レコードセットを作成を選択する f:id:y-ni-shi:20200912155939p:plain

  • 名前をnginx.example5.comとして登録

  • エイリアスを「はい」
  • 先程作られたCLBを選択
  • [作成]をクリック f:id:y-ni-shi:20200912155944p:plain

確認する

しばらく待ってみると

$ curl nginx.example5.com

でnginxの返答がある

httpsにする

httpsにする

上記を参考に、serviceyamlを変更

$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: my-service
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012
spec:
  selector:
    app: my-nginx
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: https
    port: 443
    targetPort: 80
  type: LoadBalancer
EOF

このような感じになりました。

解説

  annotations:
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012

このannotationsの部分がキモのようで
この情報にあわせてLoadBalancerを作ってくれます

service.beta.kubernetes.io/aws-load-balancer-backend-protocol

  • バックエンド(今回の例でいうとnginx)がどのプロトコルで受けるか
    • 今回はこのLoadBalancerでTLSを終端して、nginxへはhttpでつなぎたいので、httpとしています

service.beta.kubernetes.io/aws-load-balancer-ssl-ports

  • ELBが何で受けるか、という指定
    • httpsで受ける指定

service.beta.kubernetes.io/aws-load-balancer-ssl-cert

  • ACMのarnを指定する
    • 指定した証明書を使ってTLS通信を行ってくれる

確認

$ curl nginx.example5.com

これでnginxのレスポンスがあればOK

まとめ

  • serviceanotationsで色々書くとLB設定に反映される

mongoDBをGoで操作するときのメモ

MongoDBのライブラリ

  • 以前はmgoというライブラリが主流だったようです
    • 最初にググったときにmgoの記事が見つかったので、ちょっと混乱しました。
  • 現状だとmongo-go-driverらしいです github.com

Mongoの接続

  • import
import (
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)
  • 接続
    connectionURI := fmt.Sprintf(
        "mongodb://%s:%s@%s",
        "username",
        "password",
        "mongodbendpoint:27017",
    )

    client, err := mongo.NewClient(options.Client().ApplyURI(connectionURI))
    if err != nil {
        return err
    }

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    err = client.Connect(ctx)
    if err != nil {
        return err
    }

    err = client.Ping(ctx, nil)
    if err != nil {
        return err
    }

Collectionへの書き込み

  • collectionは、RDBでいうところのテーブルのようなもののようです
    • 以下はdbというDBのcolというcollectionに1レコード(ドキュメントという)追加する例です
    // clientは上で取得したclient
    col := client.Database("db").Collection("col")
    document := bson.D{
        {"type", "hogehoge"},
        {"fuga", "gogo"},
    }
    _, err := col.InsertOne(context.Background(), document)
  • bsonという構造体によってdocumentを表現します
    • 検索する条件等でもbsonは活躍します
    • bson.Dbson.Mがあり、書き方の違い?

FindOne(検索して一件取得する)

  • 最新の一件を取得して、それをmap[string]interface{}に加工して取得しています
    • bson.D{{"_id", -1}}この-1でSortを降順にできる模様
    col := client.Database("db").Collection("col")

    // 最新1件を取得する
    findOptions := options.FindOne().SetSort(bson.D{{"_id", -1}})
    var doc bson.M
    err := col.FindOne(
        context.Background(),
        opt,
        findOptions,
    ).Decode(&doc)

    if err == mongo.ErrNoDocuments {
        return nil, nil
    } else if err != nil {
        return nil, err
    }

    result := make(map[string]interface{})
    for key, val := range doc {
        typ := reflect.TypeOf(val)
        fmt.Printf("type:%s, key:%s, val:%v\n", typ, key, val)

        result[key] = val
    }

    return result, nil

参考にした記事

AWS documentDBにLambdaから書き込むメモ(terraform)

目的

  • documentDBにLambdaから書き込みたい
  • 構成はterraformにて行う

LambdaのVPC接続について

VPC接続

  • LambdaはVPC接続して動かすという方法がある模様
    • LambdaがVPC(自分で作ったVPC)にアクセスして動作するイメージ

利点

  • VPC内のエンドポイント等にLamdaからアクセスできる
  • VPC内のリソースにLamdaがアクセスする際、Security Groupによるアクセス制御が可能(これが非常に嬉しい)

昔は遅かった

  • VPC内接続のLambdaは昔は非常に遅く、一回の実行10秒とかかかってたらしい(ENIをまいどまいど作っていた)

DocumentDBへのLamdaからのアクセス(VPC接続)

  • DocumentDBはパブリックなエンドポイントを持たない
    • VPCのリソースからのアクセスしかできない(もしくはLB等でのアクセス)
  • LambdaをVPCに接続して、DocumentDBへアクセスさせる

DocumentDBへのLamdaからのアクセス(VPC接続しない場合)

  • LBを使ったアクセス方法もあるらしい(ちょっと古い記事だけど)

dev.classmethod.jp

DocumentDBを作る

  • docdb.tf
resource "aws_docdb_subnet_group" "service" {
  name       = "docdb-${var.base_name}-subnet"
  subnet_ids = [var.subnet_for_docdb1.id, var.subnet_for_docdb2.id]
}

resource "aws_docdb_cluster_instance" "service" {
  count              = 1
  identifier         = format("docdb-%s-instnace-%02d", var.base_name, count.index + 1)
  cluster_identifier = aws_docdb_cluster.service.id
  instance_class     = var.docdb_instance_class
}

resource "aws_docdb_cluster" "service" {
  skip_final_snapshot             = true
  db_subnet_group_name            = aws_docdb_subnet_group.service.name
  cluster_identifier              = "docdb-cluster-${var.base_name}"
  engine                          = "docdb"
  master_username                 = var.docdb_admin_user
  master_password                 = var.docdb_password
  db_cluster_parameter_group_name = aws_docdb_cluster_parameter_group.service.name
  vpc_security_group_ids          = [aws_security_group.docdb.id]
}

resource "aws_docdb_cluster_parameter_group" "service" {
  family = "docdb3.6"
  name   = "docdb-parameter-group-${var.base_name}"
}
  • variable.tf
# name
variable "base_name" {}

# vpc information.
variable "vpc_main" {}

# subnet for docdb
variable "subnet_for_docdb1" {}
variable "subnet_for_docdb2" {}

# instance class
variable "docdb_instance_class" {
  default = "db.t3.medium"
}

# admin
variable "docdb_admin_user" {}
variable "docdb_password" {}

# allowd security group
variable "allowed_security_group" {}
  • security-group.tf
resource "aws_security_group" "docdb" {
  name   = "docudb-${var.base_name}"
  vpc_id = var.vpc_main.id

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group_rule" "allowd_secutiry" {
  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = "-1"
  source_security_group_id = var.allowed_security_group.id
  security_group_id        = aws_security_group.docdb.id
}

ポイント

  • security groupを指定している
resource "aws_docdb_cluster" "service" {
  ...
  vpc_security_group_ids          = [aws_security_group.docdb.id]
}
  • このaws_security_group.docdbにて特定のsecurity groupからしかアクセスを許可しない設定にしている(これがLambdaのsecurity group)
resource "aws_security_group_rule" "allowd_secutiry" {
  ...
  source_security_group_id = var.allowed_security_group.id
}

Lambdaを作る

  • lambda.tf
resource "aws_lambda_function" "lambda" {
  ...
  vpc_config {
    subnet_ids = [
      var.subnet_for_lambda1.id,
      var.subnet_for_lambda2.id
    ]
    security_group_ids = [aws_security_group.for_lambda.id]
  }

  environment {
    variables = {
      DOCDB_CLUSTER_ENDPOINT = var.docdb_cluster_endpoint
      DOCDB_ADMIN_USER       = var.docdb_admin_user
      DOCDB_ADMIN_PASSWORD   = var.docdb_password
    }
  }
}

このようにvpc_configをつけてやればVPC接続で動いてくれるらしい

Lambdaのロールについて

  • VPC接続で実行する場合、ENIを作ったり、アクセスしたりするので、そのあたりの権限が必要
    • このあたりが必要らしい参考
ec2:CreateNetworkInterface
ec2:DescribeNetworkInterfaces
ec2:DeleteNetworkInterface
  • AWSLambdaVPCAccessExecutionRoleこれを使えばいいとか。

github.com

Lambdaソース

  • 公式を参考に

プログラムによる Amazon DocumentDB への接続 - Amazon DocumentDB

実行

X-rayで見てみました

  • 初回起動時(コールドスタート)

    • 結構遅い f:id:y-ni-shi:20200830001810j:plain
  • 2回め起動時

    • 1回目よりは速い(しかし全体で400ms.遅い) f:id:y-ni-shi:20200830001814j:plain
  • メモリを大きくしてもそこまで速度変わらず

まとめ

  • DocumentDBはパブリックなエンドポイントを持たないので、Lambdaからアクセスするには工夫が必要
    • LambdaをVPCアクセス
    • LambdaからLBでアクセス
  • LambdaのVPC接続は昔は遅かったけど、今は速い
    • ENIにアクセスするコストはかかっていると思われるが、大したこと無い
  • documentDBへのアクセスはほどほどに時間かかりそう(単なる通信のコストだろうか)
  • VPC接続なしのdocumentDBアクセスでどれくらいの速度かやってみたい(LBでのアクセス)

AWS documentDBメモ

Amazon documentDB

メモの内容

AWSにdocumentDBをたてる

  • awsコンソールからdocumentDBの画面から「作成」 f:id:y-ni-shi:20200829131510p:plain

AWS documentDBにアクセスする前の準備

  • EC2などのAWSリソースからしかアクセスできない(パブリックなエンドポイントはない)
Amazon DocumentDB は VPC のみのサービスであり、パブリックエンドポイントをサポートしていません。その結果、AWS 外部の環境から Amazon DocumentDB クラスターに直接接続することはできません

今回はEC2を立ててそこからアクセスする

EC2のインスタンスを起動してSSH

  • ここは割愛

EC2にmongo shellをインストールしたい

  • 参考(公式ページ) aws.amazon.com

  • 以下のコマンドでmongo shellインストール

$ echo -e "[mongodb-org-3.6] \nname=MongoDB Repository\nbaseurl=https://repo.mongodb.org/yum/amazon/2013.03/mongodb-org/3.6/x86_64/\ngpgcheck=1 \nenabled=1 \ngpgkey=https://www.mongodb.org/static/pgp/server-3.6.asc" | sudo tee /etc/yum.repos.d/mongodb-org-3.6.repo
$ sudo yum install -y mongodb-org-shell

証明書のダウンロード

  • 証明書で暗号化をして通信をする必要がある
    • デフォルトでは暗号化通信が有効になっているので、この証明書を使うことが必須のよう
  • 以下のコマンドでダウンロードしておく
$ wget https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem

AWSのdocumentDBクラスタに接続する

これがよくわからん・・・(接続失敗することが当初多発した)

f:id:y-ni-shi:20200829131733p:plain

  • 上記のように接続情報が出る
  • mongo シェルでこのクラスターに接続するの部分に記載のある情報でアクセスしたい
    • しかし、なぜか失敗することがある(起動直後はだめだったので、クラスタが立ち上がるまで結構かかる?)
  • 以下がエラーメッセージ
$ mongo --ssl --host <cluster-endpoint>.ap-northeast-1.docdb.amazonaws.com:27017 --sslCAFile rds-combined-ca-bundle.pem --username masteruser --password secretsecret
MongoDB shell version v3.6.19
connecting to: mongodb://<cluster-endpoint>.ap-northeast-1.docdb.amazonaws.com:27017/?gssapiServiceName=mongodb
2020-08-29T03:58:48.208+0000 I NETWORK  [thread1] getaddrinfo("<cluster-endpoint>.ap-northeast-1.docdb.amazonaws.com") failed: Name or service not known
2020-08-29T03:58:48.208+0000 E QUERY    [thread1] Error: couldn't initialize connection to host <cluster-endpoint>.ap-northeast-1.docdb.amazonaws.com, address is invalid :
connect@src/mongo/shell/mongo.js:263:13
@(connect):1:6
exception: connect failed

Failed: Name or service not known

  • クラスタの名前が解決できていない?(DNSへの反映?)
    • クラスタの準備ができるまでは接続できないのか

クラスタにアクセスするのではなく、インスタンスに直接アクセスも可能

  • とりあえずトラブルシュートするときにはインスタンスへのアクセス方法も知っておいていいと思う
    • クラスタエンドポイントではなく、インスタンスにならとりあえず直接可能
  • 以下に手順。

AWSCLIを使ってdocumentDBのクラスタとかインスタンスを確認する

docs.aws.amazon.com

$ aws docdb describe-db-instances \
   --db-instance-identifier sample-instance \
   --query 'DBInstances[*].[Endpoint.Address,Endpoint.Port]'
  • ここで取得したエンドポイントに対してmongo shellで接続!
$ mongo --ssl --host <インスタンスエンドポイント>:27017 --sslCAFile rds-combined-ca-bundle.pem --username masteruser --password <insertYourPassword>

mongo shellでの接続後

  • DB表示
$ show dbs
  • DB使う(勝手に作る)
$ use <db name>
  • collection表示
$ show collections
  • myというコレクション作る
$ db.createCollection('my');
  • myというコレクションの全件表示
$ db.my.find()
  • myというコレクションにインサート
$ db.my.insert({name: 'myname',})

mongodbをDockerで起動するメモ

Dockerにてmongodbを起動したい

$ docker run --rm -it -d mongo
  • このコマンドでmongodbがバックグラウンドで起動します

バージョン指定したい場合

$ docker run --rm -it -d mongo:version
  • こんな感じでバージョン指定

mongodbのコンテナ内に入る

  • まずはdockerのコンテナIDを確認(docker ps)
  • 以下のは例ですが、94f49d3d4626と確認できます
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
94f49d3d4626        mongo               "docker-entrypoint.s…"   2 seconds ago       Up 1 second         27017/tcp           kind_pascal
  • このコンテナに入ります(bash
$ docker exec -it 94f49d3d4626 bash
root@94f49d3d4626:/#
  • こんな感じでrootユーザで入れるはず

mongodbの操作をする

  • 上記のような要領でまずはコンテナに入る
# mongo
  • これでmongoシェルが起動