web-technical-blog

web開発に関する技術メモ

LambdaでS3のイベントトリガーを設定しようとした場合にエラーする場合

LambdaでS3のイベントトリガーを設定しようとした場合に 以下のエラーが出る場合は、S3のプロパティのEventを確認して削除する

Configuration is ambiguously defined. 
Cannot have overlapping suffixes in two rules if the prefixes are overlapping for the same event type.

lambdaをローカル環境で開発する方法(mac)

python-lambda-localをインストールする

https://github.com/HDE/python-lambda-local

  • 実行コマンド
python-lambda-local -l venv/lib/ -f lambda_handler main.py event.json
  • event.json

    • lambdaでテストする際のテスト呼び出しコードを記入する
  • AWSサービスを使用する場合は、.aws/credentialsが必要

  • 以下のURLに書いてある「requirements.txt」は特に設定は必要ない https://kakakakakku.hatenablog.com/entry/2017/08/02/085839

  • windows環境ではSIGALRMをCallできないとかでエラーするので無理

signal.signal(signal.SIGALRM, signal_handler)
AttributeError: module 'signal' has no attribute 'SIGALRM'

https://qiita.com/tanj/items/70c57ed9ca742884c61e

コマンドプロンプト(windows10)でlinuxコマンドを使用できるようにする

virtualboxの共有フォルダの設定について

# ls -l /media
drwxrwx--- 1 root vboxsf 0 12月 11 17:09 2016 sf_vbox_share

このままだと権限がないので、 rootかvboxsfグループのユーザーしかアクセスできないため、 既存ユーザーをvboxsfグループに追加しておく。有効にするため一度再起動したら、 共有できるようになっているはず。単に共有さえできればいいならここまでで設定は終わり。

# gpasswd -a vagrant vboxsf
# reboot

http://itemy.net/?p=1355

windows10の場合はシンボリックリンクを設定が必要 everyoneを設定する

https://tokibito.hatenablog.com/entry/2018/02/28/012014

digdagServerの構築メモ

Centos7にdigdagサーバーを構築した際のメモ

久しぶりに自分のローカルPCにCentos7でサーバーを構築しようとしたが、 vagrant up しようとするとエラーが発生。。

下記組み合わせだとvagrant upがエラーする

  • virtual box:バージョン 5.2.6 r120293 (Qt5.6.2)
  • vagrant:Vagrant 2.0.4
D:\vagrant\labo>vagrant up
The version of powershell currently installed on this host is less than
the required minimum version. Please upgrade the installed version of
powershell to the minimum required version and run the command again.

  Installed version: 2
  Minimum required version: 3

上記エラーメッセージからPowerShellのバージョンを2系から3系以上に変更したが 次にWMF5.0(Windows Management Framework 5.0)をインストールする必要もあった。 詳細は以下のリンクから確認できる。

WMFのインストールが必要

digdagServerを構築する際にpostgreSQL

  • postgres9.6のインストールは以下のURLを参考にしてインストール
  • CREATE EXTENSIONしないとエラーになるので注意
digdag_db=> CREATE EXTENSION "uuid-ossp";
ERROR:  could not open extension control file "/usr/pgsql-9.6/share/extension/uuid-ossp.control": No such file or directory

## 拡張モジュールのインストールが必要
# yum -y install postgresql96-contrib.x86_64

## postgresユーザーでログインして
psql -U postgres -h 127.0.0.1 -d digdag_db

CREATE EXTENSION "uuid-ossp";

postgre9.6 install

digdag/embulkのインストール

  • java1.8がインストールされていることが前提
$ java -version
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (build 1.8.0_151-b12)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)

# インストールされていなければいれる
$ yum -y install java-1.8.0-openjdk
# Embulkの導入
$ curl --create-dirs -o ~/bin/embulk -L https://dl.embulk.org/embulk-latest.jar
$ chmod +x ~/bin/embulk

# Digdagの導入
$ curl --create-dirs -o ~/bin/digdag -L "https://dl.digdag.io/digdag-latest"
$ chmod +x ~/bin/digdag

# OSX環境の場合は次のように.bash_profileを編集する
$ cat << 'EOF' >> ~/.bash_profile
# User specfic environment and startup programs
export PATH=$PATH:$HOME/bin
EOF
$ source ~/.bash_profile

digdagはまりポイント

https://qiita.com/toyama0919/items/142d290c8dcb2c86851c

# vi /usr/lib/systemd/system/digdag.service
[Unit]
Description=digdag

[Service]
Type=simple
PIDFile=/run/digdag.pid
ExecStart="/usr/local/bin/digdag server -b 0.0.0.0 --config /opt/digdag/server.properties -O /opt/digdag/logs/tasklogs -A /opt/digdag/logs/accesslogs -L /opt/digdag/logs/server.log"
User=root
Group=root
WorkingDirectory=/opt/digdag
Restart=always
RestartSec=5
KillMode=process
TimeoutStopSec=1200
SyslogIdentifier=digdag

[Install]
WantedBy=multi-user.target

ExecStartは別ファイルにしないとエラーになるので注意

シェルは別ファイルにしないとエラーする

[Unit]
Description=digdag

[Service]
Type=simple
PIDFile=/run/digdag.pid
ExecStart=/opt/digdag/start.sh
User=root
Group=root

[Install]
WantedBy=multi-user.target

https://qiita.com/cmwig65/items/3386a061aeb3d2f81b81 https://qiita.com/toyama0919/items/142d290c8dcb2c86851c https://qiita.com/bwtakacy/items/ec3151644512ca65f6b6

Go言語でJSONのデコード処理を検証してみた

GOでJSONをデコードする際の方法

GoでJSONを処理する最も一般的な方法

  • Goの標準ライブラリには、JSONを扱うためのパッケージ(encoding/json)が含まれていて
    エンコード(構造体から文字列)およびデコード(文字列から構造体)の両方をサポートしている
package main

import "encoding/json"

type Message struct {
    Name string
    Body string
    Time int64
}

func main() {
    b := []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
    var m Message
    json.Unmarshal(b,&m)
    println(m.Name) // Alice
    println(m.Body) // Hello
    println(m.Time) // 1294706395881547000
}
  • bとして定義されたJSONjson.Unmarshalでデコード
  • ポイントは、デコード結果を受け取るために構造体を定義している点
  • 対象のJSONデータの構造を確認しつつ、実装時に構造体の定義を決める必要がある

複雑な構造のJSON処理で直面する問題

  • JSONデータの構造が複雑な場合、具体的には多重の入れ子を含むケースだと実装がだんだん大変になってくる

  • (例)Elasticsearchの検索APIは処理結果として複雑なJSONを返してくる

{
    "took": 1,
    "timed_out": false,
    "_shards":{
        "total" : 1,
        "successful" : 1,
        "failed" : 0
    },
    "hits":{
        "total" : 1,
        "max_score": 1.3862944,
        "hits" : [
            {
                "_index" : "twitter",
                "_type" : "tweet",
                "_id" : "0",
                "_score": 1.3862944,
                "_source" : {
                    "user" : "kimchy",
                    "message": "trying out Elasticsearch",
                    "date" : "2009-11-15T14:12:12",
                    "likes" : 0
                }
            }
        ]
    }
}

上記JSONデータをデコードする処理を、単純にコーディングすると

package main

import (
    "encoding/json"
    "net/http"
)

type ResultShards struct {
    Total      int `json:"total"`
    Successful int `json:"successful"`
    Failed     int `json:"failed"`
}

type ResultHitSource struct {
    User    string `json:"user"`
    Message string `json:"message"`
    Date    string `json:"date"`
    Likes   int    `json:"likes"`
}

type ResultHit struct {
    Index  string          `json:"_index"`
    Type   string          `json:"_type"`
    Id     string          `json:"_id"`
    Score  float32         `json:"_score"`
    Source ResultHitSource `json:"_source"`
}

type ResultHits struct {
    Total    int         `json:"total"`
    MaxScore float32     `json:"max_score"`
    Hits     []ResultHit `json:"hits"`
}

type Result struct {
    Took     int          `json:"took"`
    TimedOut bool         `json:"timed_out"`
    Shards   ResultShards `json:"_shards"`
    Hits     ResultHits   `json:"hits"`
}

func main() {
    resp, err := http.Get("http://127.0.0.1:9200/_search")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    var result Result
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        panic(err)
    }
    println(result.Hits.Total)
}
  • JSON入れ子構造になっているケースでは、結果を受け取る構造体も入れ子に定義する必要がある
  • 実装にかなり苦労する
  • 別のAPIを呼ぶたびにそのAPIの出力結果に応じた構造体群を定義する必要がある

デコード結果格納までのコードの見通しをよくする

  • 上記の例では、Goの平易な言語仕様しか用いていないために、見通しの悪い実装となっているのでリファクタリングする
  • Goは構造体をネスト定義することが可能
  • さらにネスト定義する構造体には型名が必要ないので、これも削除する
package main

import (
    "encoding/json"
    "net/http"
)

type Result struct {
    Took     int  `json:"took"`
    TimedOut bool `json:"timed_out"`
    Shards   struct {
        Total      int `json:"total"`
        Successful int `json:"successful"`
        Failed     int `json:"failed"`
    } `json:"_shards"`
    Hits struct {
        Total    int     `json:"total"`
        MaxScore float32 `json:"max_score"`
        Hits     []struct {
            Index  string  `json:"_index"`
            Type   string  `json:"_type"`
            Id     string  `json:"_id"`
            Score  float32 `json:"_score"`
            Source struct {
                User    string `json:"user"`
                Message string `json:"message"`
                Date    string `json:"date"`
                Likes   int    `json:"likes"`
            } `json:"_source"`
        } `json:"hits"`
    } `json:"hits"`
}

func main() {
    resp, err := http.Get("http://127.0.0.1:9200/_search")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    var result Result
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        panic(err)
    }
    println(result.Hits.Total)
}
  • ネストに定義することで、本来のJSONの出力形式に見た目が近くなり、かつコード量も減る

発展的な実装テクニック

  • 出力されるJSONの形式を完全に構造体でカバーする必要がなく、参照が必要なオブジェクトだけを構造体の定義でおさえるようにしても、デコード処理に支障は出ない
  • ヒットしたドキュメントのリストのみを参照した場合は以下のように実装することができる
package main

import (
    "encoding/json"
    "net/http"
)

func main() {
    resp, err := http.Get("http://127.0.0.1:9200/_search")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    var result struct {
        Hits struct {
            Hits     []struct {
                Source struct {
                    Title       string `json:"title"`
                    Description string `json:"description"`
                    ImageUrl    string `json:"image_url"`
                    Url         string `json:"detail_url"`
                } `json:"_source"`
            } `json:"hits"`
        } `json:"hits"`
    }
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        panic(err)
    }
    for _, hit := range result.Hits.Hits {
        println(hit.Source.Title)
        println(hit.Source.Description)
        println(hit.Source.ImageUrl)
        println(hit.Source.Url)
    }
}
  • JSONデータに含まれるオブジェクトには、エラー情報のように「もしかしたら返却されるかもしれない」オブジェクトというものもあるので、こういった場合は対応するフィールドをポインタ型で用意するう

構造体定義の自動生成

  • デコード結果を受け取る構造体の定義は、JSONデータから自動生成するWebアプリが公開されている

json-to-go

interfaceを利用して局所的に参照する

  • 必要なオブジェクトをピンポイントに取り出す
  • 各オブジェクトへのアクセスには型アサートを利用する
    • 注意点としては数値型はすべてfloat64型として変換される
    • JSON上に整数値が入っていても、それをint型として変換しようとするとエラーになる
    • JSONデータ上での型とGo側の型の対応ドキュメント
package main

import (
    "encoding/json"
    "net/http"
)

func main() {
    resp, err := http.Get("http://127.0.0.1:9200/_search")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    var result interface{}
    decoder := json.NewDecoder(resp.Body)
    if err := decoder.Decode(&result); err != nil {
        panic(err)
    }
    n, _ := result.(map[string]interface{})["hits"].(map[string]interface{})["total"].(float64)
    println(int(n))
    // 359
    // (int にキャストしなかった場合:+3.590000e+002)
}
  • JSONデータ内に検索結果として格納されている、ヒット件数の値を取り出しています
  • 整数型はすべてfloat64として格納されているので、取得後にintにキャストする操作を行っている

Jasonライブラリ

  • JasonというGitHub上で個人開発されているパッケージ
  • GoのJSONライブラリの中では比較的なメジャーなもの
  • 入れ子のオブジェクトを辿るためのインタフェイスも提供されていて、Jason を利用しない場合、型アサートを繰り返し記述することになりますが、これを簡略化して実装できる
package main

import (
    "github.com/antonholmquist/jason"
    "net/http"
)

func main() {
    resp, err := http.Get("http://127.0.0.1:9200/_search")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    v, err := jason.NewObjectFromReader(resp.Body)
    if err != nil {
        panic(err)
    }

    n, err := v.GetInt64("hits", "total")
    if err != nil {
        panic(err)
    }
    println(n)
}

参考URL

DecoderとUnmarshalの使い分け

  • データがio.Readerストリームからのものである場合、またはデータストリームから複数の値をデコードする必要がある場合は、json.Decoderを使用します。
  • すでにJSONデータがメモリにある場合は、json.Unmarshalを使用します。

Use json.Decoder if your data is coming from an io.Reader stream, or you need to decode multiple values from a stream of data.

Use json.Unmarshal if you already have the JSON data in memory.

Amazon S3に保存されているバケット毎のオブジェクト容量、オブジェクト数を知る方法は大きく分けて下記の2つの方法がある

  • CloudWatchのメトリクスであるS3ストレージメトリクスのBucketSizeBytes、NumberOfObjectsを参照する
  • AWS CLIでS3のバケット配下のオブジェクトに対して再帰的に容量と数を集計する。
    • CloudWatchのメトリクスは自動的に集計してくれるというメリットの一方で1日1回の集計結果しか表示されないというデメリットがある
    • リアルタイムで容量やオブジェクトを知りたい場合は使用できない

www.magtranetwork.com