WSLでCドライブがマウントされない場合

概要

  • WSL2を使っていて、Cドライブが自動でマウントされなくてハマりましたので、その対応方法をメモします

詳細

前提

バージョン
エディション   Windows 10 Pro
バージョン 20H2
WSL2で使用しているディストリビューション

Ubuntu-20.04

チェックするポイント

そもそも本来は(デフォルトでは)/mnt/cにCドライブが自動でマウントされます

まずは現状確認

$ ls /mnt/c

何も表示されない

/etc/wsl.confの確認
  • おそらく初期状態ではwsl.confは存在しません
    • /etc/wsl.confの確認をします
  • 存在しない場合、以下のようなファイルを作ります(あくまで一例)

(参考)

docs.microsoft.com

# Enable extra metadata options by default
[automount]
enabled = true
root = /mnt/
options = "metadata,umask=22,fmask=11"
/etc/fstabの確認
  • fstabの記載を確認します
  • おそらくCドライブをマウントする、というような記述がないので、以下を追加
C: /mnt/c drvfs metadata,notime,gid=1000,uid=1000,defaults 0 0

確認

パワーシェルから

$ wsl --shutdown

実行

WSL2上で

$ ls /mnt/c

これで表示されればOK

おまけ

マウントの状態を確認するには

$ mount | grep -i ^C

で確認できる

C: on /mnt/c type 9p (rw,relatime,dirsync,aname=drvfs;path=C:;metadata;gid=1000;uid=1000;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=3,wfd=3)

こういうの出てくればOK

Windowsのセットアップメモ

Windowsの初期セットアップのメモ

install時のメモ

初期セットアップ時

  • 初期セットアップ時にMicrosoftアカウントでログインするとそのユーザ名が勝手につけられる
    • これは問題ないのだが、ディレクトリの名称が変な名前になってしまった
    • 対応策として、インストール時にはMSアカウントは使わず、オフラインアカウトで登録して、あとからMSアカウントを紐付け

installあとの作業

WSLのインストール

  • 公式ページを参考にインストール docs.microsoft.com

  • これは時々刻々とアップデートされていそうなので、そのたびに最新を調べたほうが良さそう

  • Debianをいれた

WindowsTerminalのインストール

WindowsTerminalの設定の変更

  • WindowsTermianlで起動するのをWSL2に設定(Debianが起動するようにする)

docs.microsoft.com

VSCodeの設定同期

serip39.hatenablog.com

キーボードの設定

英字キーボードにしたので、それをMacと違和感ないように(違和感少ないように)設定するのが大変だった

  • 英字キーの対応 note.com

  • 日本語入力

  • 日本語入力の切り替え
    • Ctrl+Spaceでやれるようにしたかったので、その対応

susukinosu.hatenablog.com

  • カナ変換をMacと同じようにしたかったのでCtrl+Kでカナ文字に変換されるように設定

CapsLockを左のCtrlキーに変更

  • CapsLockは不要なので(この位置にCtrlがいてほしい)、Ctrlを設定

pouhon.net

AWS Timestreamの基本と、Go言語によるサンプル

Timestreamドキュメント

docs.aws.amazon.com

Timestream使用方法(プログラムからの使用)

  • SDKから使用することを推奨
  • しかしながら、REST APIも公開されているようで、SDKが対応していない言語や、SDKを使いたくない事情(全部マネージしたいとか?)があればREST APIを使うのもありらしい。
    • REST APIを使用するにはEndpoint Discovery Patternを使ってEndpointを取得してから使うようです

docs.aws.amazon.com

主な使用用途

  • 時系列データを格納するのに向いている
    • 株価の時間ごとの遷移
    • 気温、気圧等時間で変化していくもの

などなど

用語

  • Time series
    • 時間ごとに記録された1つ以上のデータのレコード
      • 株価遷移
      • CPU,メモリ使用率
  • Record
    • time seriesの1つのレコード
  • Dimension
  • Measure
    • 実際のデータ
      • 株価の値そのもの
      • CPU,メモリ使用率
  • Timestamp
  • Table
    • 関係のある時系列のセット。(RDBのテーブルと思ってOKかな)
  • Database
    • トップレベルのデータ構造(RDBのデータベースと同じかなと)

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

データの書き込み

デフォルトの書き込みポリシーとしては先勝ちらしい。 追加のみ許可して、重複レコードはrejectのようす

基本は先勝ちルールなので、データの更新はできないが、後勝ちの書き込みルールに変更することもできる様子。 Upsertを有効にすることは可能のよう。

docs.aws.amazon.com

サポートしているデータタイプ

タイプ 概要
BIGINT 64ビットのint
BOOLEAN bool
DOUBLE 64bit, IEEE754準拠
VARCHAR 文字列。最大2KB

ストレージ

データは新しいものはメモリに乗っているが、古いものは磁気メディアに移される。

  • Memory store retention
    • メモリ→磁気メディアへ移動する基準時間
  • Magnetic store retention
    • 磁気メディアからも削除される期間

暗号化

Timestreamはデフォルトでデータを暗号化する AWS Key Management Serviceのキーによって暗号化をすることも可能。

データの読み込み(Query)

SQLを使ってデータを取得することができる データをいくらあげようとも大丈夫、というレベルでスケールしてくれるらしい。 1日に数百TBあげても大丈夫だし、数百万の大小のクエリをなげても大丈夫、とのこと。

データModel

  • flat modeltime series modelの2種類がある
Flat Model
  • Timestreamのデフォルトのデータ形式
    • 表形式でのデータ表現が可能
      • dimension, time, measureなどを表敬式で表現する
タイプ 概要
BIGINT 64ビットのint
time region az vpc instance_id measure_name measure_value::double measure_value::bigint
2019-12-04 19:00:00.000000000 us-east-1 us-east-1d vpc-1a2b3c4d i-1234567890abcdef0 cpu_utilization 35 null
2019-12-04 19:00:01.000000000 us-east-1 us-east-1d vpc-1a2b3c4d i-1234567890abcdef0 cpu_utilization 38.2 null
Time series model
  • 時系列データの解析用に使えるデータタイプだとか
    • 表形式ではない
region az vpc instance_id cpu_utilization
us-east-1 us-east-1d vpc-1a2b3c4d i-1234567890abcdef0 [{time: 2019-12-04 19:00:00.000000000, value: 35}, {time: 2019-12-04 19:00:01.000000000, value: 38.2}, {time: 2019-12-04 19:00:02.000000000, value: 45.3}]

Go言語における制御

  • sample

github.com

DBの接続情報取得

   tr := &http.Transport{
        ResponseHeaderTimeout: 20 * time.Second,
        // Using DefaultTransport values for other parameters: https://golang.org/pkg/net/http/#RoundTripper
        Proxy: http.ProxyFromEnvironment,
        DialContext: (&net.Dialer{
            KeepAlive: 30 * time.Second,
            DualStack: true,
            Timeout:   30 * time.Second,
        }).DialContext,
        MaxIdleConns:          100,
        IdleConnTimeout:       90 * time.Second,
        TLSHandshakeTimeout:   10 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
    }

    // So client makes HTTP/2 requests
    http2.ConfigureTransport(tr)

    sess, err := session.NewSession(
        &aws.Config{
            Region:     aws.String("us-east-1"),
            MaxRetries: aws.Int(2),
            HTTPClient: &http.Client{Transport: tr},
        },
    )
    if err != nil {
        panic(err)
    }

    // write service
    writeSvc := timestreamwrite.New(sess)

    // 事前に作ってあるdatabase名を取得する
    // Describe database.
    describeDatabaseInput := &timestreamwrite.DescribeDatabaseInput{
        DatabaseName: aws.String("sampleDB"),
    }
    describeDatabaseOutput, err := writeSvc.DescribeDatabase(describeDatabaseInput)

    if err != nil {
        fmt.Println("Error:")
        fmt.Println(err)
    } else {
        fmt.Println("Describe database is successful, below is the output:")
        fmt.Println(describeDatabaseOutput)
    }

実行結果

# go run main.go
Describe database is successful, below is the output:
{
  Database: {
    Arn: "arn:aws:timestream:us-east-1:<aws-id>:database/sampleDB",
    CreationTime: 2020-12-12 06:34:42.529 +0000 UTC,
    DatabaseName: "sampleDB",
    KmsKeyId: "arn:aws:kms:us-east-1:<aws-id>:key/<kms-key-id>",
    LastUpdatedTime: 2020-12-12 06:34:42.52 +0000 UTC,
    TableCount: 1
  }
}

ちょっと解説

RDSなどのサービスと違い、databaseごとのエンドポイントが明示されるわけではない
SDKの内部的にはREST APIを操作していると思われるので、エンドポイントというのは存在するはずだが、それをSDKが隠してくれている

   writeSvc := timestreamwrite.New(sess)

これで書き込み用のtimestreamとのセッションを作る
(今回書き込みをしたいわけではなく、DBの接続情報を表示したいだけだが、どうやらtimestreamwriteというパッケージ内にあるっぽい)

   describeDatabaseInput := &timestreamwrite.DescribeDatabaseInput{
        DatabaseName: aws.String("sampleDB"),
    }
    describeDatabaseOutput, err := writeSvc.DescribeDatabase(describeDatabaseInput)

DB情報の取得。これをprintlnすることによって、上記のような出力結果を得ている

5件のレコード取得

package main

import (
    "fmt"

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

func main() {
    sess, err := session.NewSession(&aws.Config{Region: aws.String("us-east-1")})
    querySvc := timestreamquery.New(sess)
    query := `SELECT * FROM sampleDB.IoT limit 5`

    fmt.Println("Submitting a query:")
    queryInput := &timestreamquery.QueryInput{
        QueryString: aws.String(query),
    }
    fmt.Println(queryInput)

    // submit the query
    queryOutput, err := querySvc.Query(queryInput)

    if err != nil {
        fmt.Println("Error:")
        fmt.Println(err)
    }

    fmt.Println(queryOutput)
}

実行結果

# go run main.go
Submitting a query:
{
  QueryString: "SELECT * FROM sampleDB.IoT limit 5"
}

// (説明のためにこれを追記)ここから下がQueryの結果
{
  ColumnInfo: [
    {
      Name: "fleet",
      Type: {
        ScalarType: "VARCHAR"
      }
    },
    {
      Name: "truck_id",
      Type: {
        ScalarType: "VARCHAR"
      }
    },
    {
      Name: "fuel_capacity",
      Type: {
        ScalarType: "VARCHAR"
      }
    },
    {
      Name: "model",
      Type: {
        ScalarType: "VARCHAR"
      }
    },
    {
      Name: "load_capacity",
      Type: {
        ScalarType: "VARCHAR"
      }
    },
    {
      Name: "make",
      Type: {
        ScalarType: "VARCHAR"
      }
    },
    {
      Name: "measure_value::double",
      Type: {
        ScalarType: "DOUBLE"
      }
    },
    {
      Name: "measure_value::varchar",
      Type: {
        ScalarType: "VARCHAR"
      }
    },
    {
      Name: "measure_name",
      Type: {
        ScalarType: "VARCHAR"
      }
    },
    {
      Name: "time",
      Type: {
        ScalarType: "TIMESTAMP"
      }
    }
  ],
  QueryId: "AEBQEAMY4TDJOQEQSLWL5HDJ6IQMPWG5OOJ62Q3RTFOSJZ64Z5OB7QBLWOXWECA",
  Rows: [
    {
      Data: [
        {
          ScalarValue: "Alpha"
        },
        {
          ScalarValue: "368680024"
        },
        {
          ScalarValue: "100"
        },
        {
          ScalarValue: "359"
        },
        {
          ScalarValue: "1000"
        },
        {
          ScalarValue: "Peterbilt"
        },
        {
          ScalarValue: "309.0"
        },
        {
          NullValue: true
        },
        {
          ScalarValue: "load"
        },
        {
          ScalarValue: "2020-12-12 02:13:08.917000000"
        }
      ]
    },
    {
      Data: [
        {
          ScalarValue: "Alpha"
        },
        {
          ScalarValue: "368680024"
        },
        {
          ScalarValue: "100"
        },
        {
          ScalarValue: "359"
        },
        {
          ScalarValue: "1000"
        },
        {
          ScalarValue: "Peterbilt"
        },
        {
          ScalarValue: "134.0"
        },
        {
          NullValue: true
        },
        {
          ScalarValue: "load"
        },
        {
          ScalarValue: "2020-12-12 02:24:02.498000000"
        }
      ]

...略
    }
  ]
}

ちょっと解説

   query := `SELECT * FROM sampleDB.IoT limit 5`

    fmt.Println("Submitting a query:")
    queryInput := &timestreamquery.QueryInput{
        QueryString: aws.String(query),
    }

    queryOutput, err := querySvc.Query(queryInput)

これがクエリを実行している部分。
クエリはSQLとして記載ができて、とてもわかりやすい。

データの追加

もちろんデータを追加することも可能
データの追加(wirte)するためには、"github.com/aws/aws-sdk-go/service/timestreamwrite"を使うらしい。

制約(2020/12/13追記)

  • 1度にinsertできる最大のレコード数は100まで
   // write service
    writeSvc := timestreamwrite.New(sess)

    databaseName := "sampleDB"
    tableName := "IoT"

    now := time.Now()
    currentTimeInMilliSeconds := now.UnixNano()
    currentTimeInMilliSeconds = int64(currentTimeInMilliSeconds / (1000 * 1000))
    fmt.Println(currentTimeInMilliSeconds)

    writeRecordsInput := &timestreamwrite.WriteRecordsInput{
        DatabaseName: aws.String(databaseName),
        TableName:    aws.String(tableName),
        Records: []*timestreamwrite.Record{
            &timestreamwrite.Record{
                Dimensions: []*timestreamwrite.Dimension{
                    &timestreamwrite.Dimension{
                        Name:  aws.String("region"),
                        Value: aws.String("us-east-1"),
                    },
                    &timestreamwrite.Dimension{
                        Name:  aws.String("az"),
                        Value: aws.String("az1"),
                    },
                    &timestreamwrite.Dimension{
                        Name:  aws.String("hostname"),
                        Value: aws.String("host1"),
                    },
                },
                MeasureName:      aws.String("cpu_utilization"),
                MeasureValue:     aws.String("13.5"),
                MeasureValueType: aws.String("DOUBLE"),
                Time:             aws.String(strconv.FormatInt(currentTimeInMilliSeconds, 10)),
                TimeUnit:         aws.String(timestreamwrite.TimeUnitMilliseconds),
            },
            &timestreamwrite.Record{
                Dimensions: []*timestreamwrite.Dimension{
                    &timestreamwrite.Dimension{
                        Name:  aws.String("region"),
                        Value: aws.String("us-east-1"),
                    },
                    &timestreamwrite.Dimension{
                        Name:  aws.String("az"),
                        Value: aws.String("az1"),
                    },
                    &timestreamwrite.Dimension{
                        Name:  aws.String("hostname"),
                        Value: aws.String("host1"),
                    },
                },
                MeasureName:      aws.String("memory_utilization"),
                MeasureValue:     aws.String("40"),
                MeasureValueType: aws.String("DOUBLE"),
                Time:             aws.String(strconv.FormatInt(currentTimeInMilliSeconds, 10)),
                TimeUnit:         aws.String(timestreamwrite.TimeUnitMilliseconds),
            },
        },
    }

    _, err = writeSvc.WriteRecords(writeRecordsInput)

    if err != nil {
        fmt.Println("Error:")
        fmt.Println(err)
        return
    } else {
        fmt.Println("Write records is successful")
    }

解説

2つのレコードを追記している。
なんとなく雰囲気でわかる気がする。
Dimensionsは、あとでqueryに使用することができる。このDimensionsの設計がかなり重要そうな予感。

Insertの結果の確認

SELECT * FROM sampleDB.IoT ORDER BY time DESC LIMIT 10

これをAWSコンソール上から実施してみる

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

なんかいい感じ。
sampleで追加されたデータと、今回いれたデータが混在しているのがわかる。
非常に柔軟にデータを入れられる(NoSQLのように)くせに、SQLでクエリできるってのがすごい。

memory retention policyの範囲内の時間しかInsertできないぽい。
仮にmemory retention policyが1 dayだった場合、2日前のデータを入れようとすると時間がおかしいということでエラーがでるっぽい。
どうしても過去のデータを入れたい場合にはどうするんだろう?

複数テーブルのクエリについて

複数テーブルのクエリは許可されていない様子

Only one table is allowed per query. Tables found: 2

このようなエラーがでる
JOIN命令は使えるものの、1つのテーブルのJOINでしか使わないってことかな

Sample Queryにあるクエリ例
これも結合しているのはやはり1つのテーブルのみ("sampleDB".IoT

-- Identify trucks that have been running on low fuel(less than 10 %) in the past 48 hours.
WITH low_fuel_trucks AS (
    SELECT time, truck_id, fleet, make, model, (measure_value::double/cast(fuel_capacity as double)*100) AS fuel_pct
    FROM "sampleDB".IoT
    WHERE time >= ago(48h)
    AND (measure_value::double/cast(fuel_capacity as double)*100) < 10
    AND measure_name = 'fuel-reading'
),
other_trucks AS (
SELECT time, truck_id, (measure_value::double/cast(fuel_capacity as double)*100) as remaining_fuel
    FROM "sampleDB".IoT
    WHERE time >= ago(48h)
    AND truck_id IN (SELECT truck_id FROM low_fuel_trucks)
    AND (measure_value::double/cast(fuel_capacity as double)*100) >= 10
    AND measure_name = 'fuel-reading'
),
trucks_that_refuelled AS (
    SELECT a.truck_id
    FROM low_fuel_trucks a JOIN other_trucks b
    ON a.truck_id = b.truck_id AND b.time >= a.time
)
SELECT DISTINCT truck_id, fleet, make, model, fuel_pct
FROM low_fuel_trucks
WHERE truck_id NOT IN (
    SELECT truck_id FROM trucks_that_refuelled
)

まとめ

  • Memory store retentionによって高速読み出しをできるデータ区間を設定できる
  • Magnetic store retentionによって削除される期間を設定できる
  • Dimensionという属性を設定することができる(後でクエリするときに役立つ)
  • 基本的にデータは入れたら更新はできない(先勝ちのポリシーっぽい)
  • SDKが存在していて、基本はSDKで操作を行う(今回はGoでやった)
  • テーブルをまたがるクエリは許可されていない
    • 1つのテーブルに関するクエリならOKなので、1つのテーブル内でのクエリは可能
  • 1度にinsertできるレコードは100個まで

AWS GlueでS3→RDSへのETLを実施するメモ

目的

  • AWS Glueを使ってS3からRDSへのETLを実施したい
    • 結構ハマったので解決方法のメモ

AWS Glueとは

  • マネージドな(サーバレスな)AWSのETLサービス
  • データソースからデータターゲットにいい感じにデータを入れてくれる(かなり自動でやってくれる)
  • スクリプトを書いて微調整することも可能

aws.amazon.com

扱えるデータソース、データターゲット

ETL処理について

  • AWSのデータをETLする際、上記データソース、データターゲットであればAWS Glueは選択肢に入ってくる
  • (最近まで知らなかったけど)ETLの代表的なソフトウェアembulkも検討したい(AWSのサービスではなく、fluentdのバッチ版のようなモノ)
    • マネージドじゃないけど。これも便利らしい

github.com

AWS GlueでRDSへのETLする際、個人的ハマりポイント

  • AWS GlueからVPC内にあるRDSにアクセスする場合、RDSが存在するVPCに対して、S3のエンドポイントが必要
    • 以下参考

stackoverflow.com

docs.aws.amazon.com

概要

  • S3に入っているデータをRDSにETLしたい
    • S3に入っているCSVファイルをETLしてRDSにデータを入れたい!
  • 単にこれがやりたかったが、RDSにアクセスできなくて色々つまった
  • ちなみに、S3→S3のETLはすんなりいけた。しかしS3→RDSがだめ。
    • 以下、そのときのエラー
      • ちなみに、GlueのUIがちょっと不便で、エラー文章が一部見れなかったりして結構苦戦した f:id:y-ni-shi:20201129231009p:plain

エラー内容

  • つまり、「S3にアクセスできない」って言ってる
  • RDSにデータを入れたいのに、なんでS3にアクセスできないというエラーなのか?(個人的な予想です)

S3へのVPCエンドポイントを追加

  • AWSの公式に倣って、VPCのエンドポイントを追加 f:id:y-ni-shi:20201129232426p:plain
  • 対象のVPC(RDSが設置されているVPC)を選ぶ f:id:y-ni-shi:20201129232531p:plain
  • S3へのアクセスはとりあえずフルアクアスを付与(VPCなら誰でもアクセス可能) f:id:y-ni-shi:20201129232633p:plain

ETLする

  • 今回はCovid-19のデータを使用 www.stopcovid19.jp

  • 上記サイトからダウンロードしたCSVファイルをS3に入れ、AWS Glueを起動

    • RDSにデータが入っていることを確認
      • Glueが勝手に入れてくれた。
etl=> select * from results;
   name    | name_jp  | n_patients | n_currentpatients | n_exits | n_deaths | n_heavycurrentpatients | n_unknowns | n_inspections | year | month 
-----------+----------+------------+-------------------+---------+----------+------------------------+------------+---------------+------+-------
 Hokkaido  | 北海道   |       7766 |              2300 |    5302 |      164 |                     21 |          0 |        135619 | 2020 | 11
 Aomori    | 青森県   |        285 |                16 |     263 |        6 |                      2 |          0 |          6480 | 2020 | 11
 Iwate     | 岩手県   |        162 |                98 |      76 |        1 |                      1 |         13 |          8650 | 2020 | 11
 Miyagi    | 宮城県   |       1147 |               193 |     945 |        9 |                      8 |          0 |         18374 | 2020 | 11
 Akita     | 秋田県   |         84 |                13 |      70 |        1 |                      0 |          0 |          3476 | 2020 | 11
 Yamagata  | 山形県   |        111 |                13 |      97 |        1 |                      1 |          0 |          7374 | 2020 | 11
 Fukushima | 福島県   |        486 |                60 |     420 |        6 |                      5 |          0 |         38699 | 2020 | 11
 Ibaraki   | 茨城県   |       1397 |               337 |    1041 |       19 |                      8 |          0 |         16254 | 2020 | 11
 Tochigi   | 栃木県   |        599 |                79 |     520 |        2 |                      6 |          2 |         52797 | 2020 | 11
 Gunma     | 群馬県   |       1117 |               125 |     941 |       21 |                      2 |         30 |         35056 | 2020 | 11
 Saitama   | 埼玉県   |       8002 |              1175 |    6690 |      137 |                     27 |          0 |        228115 | 2020 | 11
 Chiba     | 千葉県   |       6644 |               775 |    5783 |       86 |                     11 |          0 |        159758 | 2020 | 11
 Tokyo     | 東京都   |      39079 |              3781 |   34813 |      485 |                     60 |          0 |        736409 | 2020 | 11
 Kanagawa  | 神奈川県 |      11894 |              1301 |   10404 |      189 |                     64 |          0 |        246225 | 2020 | 11
 Niigata   | 新潟県   |        325 |               101 |     224 |        0 |                      0 |          0 |         21833 | 2020 | 11
 Toyama    | 富山県   |        450 |                18 |     406 |       26 |                      0 |          0 |         16763 | 2020 | 11
 Ishikawa  | 石川県   |        843 |                14 |     780 |       49 |                      0 |          0 |         21257 | 2020 | 11

まとめ

  • S3→RDSへのETLをAWSコンソールだけで(何もコードをかかずに)実行できた。
  • RDSへアクセスするためにはVPCエンドポイントを利用する必要がある
  • Glueは便利

参考にさせていただいたサイト

system.blog.uuum.jp

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としてクエリを投入することが可能

感想

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