CircleCIのyamlのメモと、TerraformをCircleCIでapplyするメモ

目的

  • GitHubにpushしたソースをCircleCIを用い、Terraformをapplyしたい
    • Terraformをapplyする際にはユーザの確認を行いたい

参考

  • まさにこのページがそれでした(公式のサンプル)

learn.hashicorp.com

準備

Terraformの環境変数

  • TerraformからAWSにアクセスする必要があるので、環境変数にアクセスキーを登録する必要がある
  • Project Settings -> Environment Variables

Project Settingsのメニューから
f:id:y-ni-shi:20201121165528p:plain

Environment Variablesを選択して登録画面を表示
f:id:y-ni-shi:20201121165613p:plain

  • 環境変数を設定する
    • AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEYを設定する(画像では他のenvも入っている) f:id:y-ni-shi:20201121165829p:plain

.circleci/config.yml

  • サンプルファイル
    • とりあえず公式のサンプルをそのまま使用

github.com

解説(jobについて)

  • 以下がjobの部分。
    • 各種jobが定義されており、実際に何を行うかを記載する
    • 複数のjobを記載することが可能
version: 2 # version 2の指定(現在は2.1が主流?2.1にしても動いた)

jobs:
  plan-apply:
    working_directory: /tmp/project #  VM(Docker)上でどこの作業スペースで行うか
    docker:
      - image: hashicorp/terraform:light
    steps:  # 作業の核心部分(以下参照)
      - checkout
      - run:
          name: terraform init & plan
          command: |
            terraform init -input=false
            terraform plan -out tfapply -var-file variables.tfvars
      - persist_to_workspace:
          root: .
          paths:
            - .

stepsについて

  • これがCIの核心部分(実際になにの作業を行うかを記載している)
    steps:
      - checkout  # GitHubからソースファイルをpullしてきてworking_dirに展開する
      - run:
          name: terraform init & plan  # タスクの名称
          command: |
            terraform init -input=false
            terraform plan -out tfapply -var-file variables.tfvars  # terraform planした結果を tfapplyという名前にして出力
      - persist_to_workspace:  # 以下のrootの内容を保存する(CircleCiの次のステップに持ち越せる)
          root: .
          paths:
            - .
name

nameの部分がCircleCIの各セクションの名前になる f:id:y-ni-shi:20201121162732p:plain

checkout
  • GitHubからソースファイルをダウンロードして展開する
    steps:
      - checkout
persist_to_workspace
  • このstepで作成した何かしらの成果物を次のステップに持ち越す

解説(workflowsについて)

  • 実際にどのジョブをどのように流していくかを記載するのがworkflow
    • 今回はterraformでapplyしたいわけだが、terraform planした内容が正しいのかどうか、を人がチェックしてからterraform applyしたい
      • そういうときはtype: approvalがいいらしい

circleci.com

  • workflows抜粋
workflows:
  version: 2
  plan_approve_apply:  # workflowの名前。好き名前つけられる
    jobs:
      - plan-apply # plan-applyを実行する
      - hold-apply:
          type: approval # approvalというのはユーザが許可したら次へいく、という機能のようで、人がチェックしたら次へいける
          requires:
            - plan-apply # plan-applyが終わってからやる、という意味。つまり失敗したら実行されない
      - apply:
          requires:
            - hold-apply # hold-applyの完了を待っている。
jobs
  • type: approval
    • これがユーザがチェックする部分
  • このように、holdと出てユーザの入力待ちになる f:id:y-ni-shi:20201121164027p:plain
  • このような画面が出てapprovをクリックしたら次に進む f:id:y-ni-shi:20201121164327p:plain

まとめ

  • terraformのplanとapplyを行う場合にはtype: approval
    • ユーザが確認してクリックするまでブロッキングされる機能がある
    • 実際には、このplanの内容をslackとかに投げたいなあ。そしてslackからクリックしたらCI進んで欲しい、とかありそうだなと。
      • これ↓とかまさにそれ?

qiita.com

  • CircleCIでterraformするためにはAWSのクレデンシャルをCircleCIの環境変数に入れる必要がある
    • これは仕方ないんだろうけど、ちょっと気持ち悪いけど、仕方ないんでしょう
      • terraform applyするなら結構権限強いユーザになってると思うので、なおさら書きたくないですが・・・

embulkのチュートリアル程度やってみたメモ

embulkとは

  • Fluentdのコミッターの古橋さんが作成されているfluentdのバッチ版のようなソフトウェアらしいです

github

github.com

参考サイト

qiita.com

なぜembulk

  • ETL処理を行いたいというニーズがあり、そのETLの手段としてembulkにたどり着きました
    • クラッチでETL処理を書く前に、単なるETLならembulk試してみたら?というようなサイト見つけて。

所感

  • pluginの開発が盛んで、例えばcsvファイルをRDBに入れたい程度なら一瞬で対応できます
  • 当然ながら、embulk(ETLバッチ処理)を動かすためのPCが必要(コンテナにしてしまえばECRとかで実行できそう)

使い方メモ

インストール(Java)

  • EC2 Amazon Linux2へのインストール
$ sudo yum install -y java-1.8.0-openjdk-devel.x86_64
$ sudo alternatives --config java

1 プログラムがあり 'java' を提供します。

  選択       コマンド
-----------------------------------------------
*+ 1           java-1.8.0-openjdk.x86_64 (/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.265.b01-1.amzn2.0.1.x86_64/jre/bin/java)

Enter を押して現在の選択 [+] を保持するか、選択番号を入力します:1
$ java -version
openjdk version "1.8.0_265"
OpenJDK Runtime Environment (build 1.8.0_265-b01)
OpenJDK 64-Bit Server VM (build 25.265-b01, mixed mode)

インストール(embulk本体)

公式を参考にインストール(コンテナにしてもいいと思いますが、今回はEC2に直接入れました)

公式の手順はホームディレクトリの.embulkディレクトリにbinも入れる手順です
なんか気持ち悪い方は/usr/lcoal/binとかに移動すればいいかなと。

$ curl --create-dirs -o ~/.embulk/bin/embulk -L "https://dl.embulk.org/embulk-latest.jar"
$ chmod +x ~/.embulk/bin/embulk
$ echo 'export PATH="$HOME/.embulk/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrc

www.embulk.org

実際のETL処理

こちらのQiitaを参考に実施(S3からRDBにETLする処理)
qiita.com 上記のサイトとまったく同じことをやったので、内容については上記Qiita参照

embulk用yaml

例えば以下のようなyamlになります

in:
  type: s3
  bucket: bucket-name
  path_prefix: 20201115
  region: us-east-1
  access_key_id: id
  secret_access_key: key
out:
  type: postgresql
  host: rds-endpoint
  user: etl_user
  password: secretsecret
  database: etl
  table: target
  mode: insert

実行

  • guess
    • yamlの内容からembulkが構造を解析して詳細なyamlを作ってくれる機能
$ embulk guess ./sample.yml -o ./output.yml
  • run
    • yamlの内容を実行する
$ embulk run output.yml

テンプレーティング

上記のようなyamlを記載すれば動くわけですが、
S3のエンドポイントやらRDSのエンドポイントやらを直接記載したくないケースは多いと思います(Githubにプッシュするとか)
そういう場合にliquidテンプレートを作る

用意するファイル

とりあえず2種類用意(末尾は必ずyml.liquidである必要があるようです)
- _env.yml.liquid - こちらに設定を記載します(注入したい環境変数を記載するようなイメージ) - hogehoge.yml.liquid - 上記のenvの内容を注入される側のファイル(ETLの内容そのもの)

_env.yml.liquid
{% assign bucket_name = 'bucket-name' %}
{% assign aws_key_id = 'id' %}
{% assign aws_access_key = 'key' %}
{% assign rdb_endpoint = 'rds-endpoint' %}

このように記載可能です
記載の通りbucket_nameとかをenvファイルから注入できます

hogehoge.yml.liquid
{% include 'env' %} # _envから読み込む設定
in:
  type: s3
  bucket: {{ bucket_name }}
  path_prefix: 20201115
  region: us-east-1
  access_key_id: {{ aws_key_id }}
  secret_access_key: {{ aws_access_key }}
out:
  type: postgresql
  host: {{ rdb_endpoint }}
  user: etl_user
  password: secretsecret
  database: etl
  table: target
  mode: insert
実行
$ embulk guess ./hogehoge.yml.liquid -o ./output.yml
$ embulk run output.yml

digdagとの連携

  • embulkの実行をコントロールするためにdigdagと組み合わせるのが定石のようです

github.com

まとめ

  • プラグインも有志によって豊富にあるようなので、大抵のETLはこれでいけるんだろうなと思います
  • 結構昔からあるようですが、さすがにまだ枯れてはいないのかなという印象
    • バージョンも現状0.9.23のようですし(2020/11/15現在)
  • pluginの開発も自分で開発可能なようなので、ちょっとやってみたいなと思っています(rubyで書く?)
  • digdagと一緒に利用することによって、ワークフローが制御しやすくなるようです
    • 失敗時のリトライ
    • 失敗後に処理をわける、などなど

AWS Timestreamを試す

目的

  • AWS Timestreamを触ってみる(本当にさわりだけでも)

お断り

  • 間違っている部分あったらごめんなさい。

AWS Timesteramとは

  • AWSマネージドな時系列DBのサービス
    • 一日一兆をこえるデータ投入をさばけるとか
  • マネージドなだけあってAWSのいろんなサービスと連携しやすいらしい
    • 動画で紹介されていたのはAWS IoT Coreを介してデータをTimestreamに入れる方法(クリックだけで可能)
  • SQLのような記述でデータのクエリが記載可能

まずは動画を確認して概要を確認

ざっくり使い方解説

www.youtube.com

公式の説明動画

www.youtube.com

基礎知識

用語

  • Database
    • その名の通りデータベース。
  • Table
    • データベース内のテーブル。SQLでいうところのテーブルと同義
  • Record
    • データ1つ1つのこと
  • Dimension
    • タグのようなイメージか(各データレコードにつけることができる)
    • レコード登録するときに好き勝手つけられるんだろうか?
  • Measures
    • 時系列のデータそのもの(数値、bool等)

サンプルデータの準備

  • サンプルデータが用意されているので、それを使ってみます

sample databaseを選んで作成

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

sample dataを選択する

IoTDevOps両方選びました
f:id:y-ni-shi:20201024143829p:plain

テーブルを見てみる

作られてます f:id:y-ni-shi:20201024144120p:plain

簡単なクエリを実行してみる

クエリエディタみたいなのが標準装備。
ここで好きにクエリを実行できるみたいですね
f:id:y-ni-shi:20201024144242p:plain

試しに以下のクエリを実行(sampledbは作ったDBの名前)

select count(*) as count from "sampledb".IoT

結果は

count
1600

適当にデータを取る

select * from "sampledb".IoT limit 5

結果 f:id:y-ni-shi:20201024144915p:plain

時系列なので、1分ごとの平均とかとってみる

select 
    bin(time, 1m) as binned_time,
    avg(measure_value::double) as avg
from "sampledb".IoT
group by bin(time, 1m)
order by binned_time desc
limit 5
binned_time
avg
2020-10-24 05:55:00.000000000   39.0
2020-10-24 05:54:00.000000000   110.0
2020-10-24 05:53:00.000000000   25.0
2020-10-24 05:52:00.000000000   159.5
2020-10-24 05:51:00.000000000   213.0

うーん・・あってるんだろうか。

検証

select 
    time,
    measure_value::double
from "sampledb".IoT
where time between '2020-10-24 05:53:00.000000000' and '2020-10-24 05:56:00.000000000'
order by time desc

この結果(53分〜56分くらいを出力してみた) f:id:y-ni-shi:20201024151724p:plain

05:55:00.000000000代のデータ

2020-10-24 05:55:00.000000000    39.0

計算された平均値は39.0
実際の生データ(平均取る前のデータ)

2020-10-24 05:55:26.124000000    42.0
2020-10-24 05:55:25.890000000   36.0

平均39.0と計算できます。あってそう

2020-10-24 05:54:00.000000000代のデータ

2020-10-24 05:54:00.000000000    110.0

54分代の平均は110.0 実際の生データ(平均取る前のデータ)

2020-10-24 05:54:58.994000000    322.0
2020-10-24 05:54:57.316000000   -
2020-10-24 05:54:48.847000000   1.0
2020-10-24 05:54:30.635000000   7.0
2020-10-24 05:54:16.049000000   -

デーが5件あって、2件はデータナシ(0ではない)ということで、データ3件の平均を求めると110になってぴったり

まとめ

  • AWS Timestreamは時系列データを扱えるマネージドなサービス
  • とりあえず触るだけならサンプルデータを作ってくれる機能がある
  • SQLとしてクエリを投入することが可能

感想

  • これは便利そう。何よりマネージドなサービスはやっぱいい

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

参考にした記事