IDEA Note

  • redashの導入、運用で得た知見、改善まとめ

    redashの導入、運用で得た知見、改善まとめ

    はじめに

    redashは去年の年末頃Version 4が出て、夏にVersion 5、そしてVersion 6が近日リリースというスピードで、どんどんメジャーバージョンアップを繰り返し、機能が拡充されています。

    レアジョブではDocker版 redash Version 1.03で初めてredashを導入し、何度もVersion UPを繰り返し現在は最新版(5.02)を使っています。

    そこで得た知見についてはメモを書いていましたが、( https://tsukada.sumito.jp/category/tech/redash/ ) あくまでもメモベースなので、これを機にちゃんとブログとしてまとめたいとおもいます。

    弊社での利用者

    アルバイトから役員まで、様々なロールを持ってる人が使ってます。

    Botも動いており、一部APIを使い、Chatに通知する仕組みも取り入れています。

    SQLを書ける人を増やしたい

    最初は非エンジニアの方は書けない人が大半でした。

    社内勉強会を定期的に開き、誰でもSQLを書ける土台を作りました。

     

    社内勉強会の成功は「自分ゴト化」がキモ。非エンジニアにSQLを教えた話

    https://appeal.rarejob.co.jp/2016/11/11/2174/

    それでもすぐにSQLが書けるようにはならないので、足りない部分はchatで専用の窓口を開き、SQLやredashについて気軽に質問できる土台を作りました。

     

    redashの使い方についてはkakakakakkuさんのhandson資料を使わせてもらい、社内でredash使える人を増やしました。

    https://github.com/kakakakakku/redash-hands-on

    APIを使いたい

    残念ながらAPIについてのドキュメントはあまりありません。

    ソースを見るのが一番早かったりします。

    https://github.com/getredash/redash/blob/master/redash/handlers/api.py

    運用編

    redashのコンテナの役割を知りたい

    以前Qiitaにまとめた記事にredash_serverコンテナがどのような振る舞いをしているのかまとめた。

    https://qiita.com/S-T/items/bee0ae9c8c0acbd940cc

     

    redashのバックアップを取りたい

    基本的にredashのデータはredashのPostgresに入ります。redisはキャッシュ情報が入るのでこちらはバックアップの対象外で問題ありません。(自分はクエリが詰まったらredisの中身を全部空にしたりします)

    我々はDockerで動かしているので、host側から以下のcronを仕込んでます。

    0 6 * * * /bin/docker exec redash_postgres_1 pg_dump -U postgres postgres | /bin/gzip > /tmp/redash_backup.gz

    このバックアップで取得できたファイルを、定期的に別サーバやS3に格納しています。

    redashのクエリのバックアップを取りたい

    redashに登録したクエリをバックアップし、世代管理したい事が多々あります。

    @ariarijp さんが作成した redashman (https://github.com/ariarijp/redashman) というツールが非常に便利です。

    どこでも動くように、という事を念頭に開発されたようでgo言語で作られています。

    定期的に実行し、gitで保存するようにしています。

    redashのメジャーVersion UPをしたい

    オフィシャルの手順はこちらです。

    https://redash.io/help/open-source/admin-guide/how-to-upgrade

    作業前にPostgresのバックアップを取得することを強くお勧めします。

     

    私がVersion UPした際の手順は以下の通りです。

    メジャーバージョンアップを行う場合、DBのスキーマ変更を伴う場合が多いです。

    https://tsukada.sumito.jp/2018/08/08/docker-redash-v5-update/

    redashのマイナーバージョンアップをしたい

    docker-composeでdockerのイメージを切り替えるだけで動く事が多い。

    作業前にPostgresのバックアップを取得することを強くお勧めします。

    https://hub.docker.com/r/redash/redash/tags/

    誰にどのデータを見せるかの制御したい

    様々なロールを持ってる人がredashを使っています。

    単純にKPIを追う人、センシティブな数値を扱う人と様々です。

    残念ながら現段階ではredashではオフィシャルに「見せる・見せない」を制御する機能はありません。

    データソースをもう一つつくり、そちらで制御するようにしました。

     

    https://qiita.com/S-T/items/c0e4bf3929771a30b720

    クエリが詰まる原因を知りたい

    クエリを実行したのに、結果が返ってこない。ということはよくあります。

    いわゆる「クエリが詰まる」という状況です。

    なぜ、どのような理由で詰まるのか。これについては @ariarijp さんのスライドが非常にわかりやすくまとまってます。

    詰まる箇所、つまり、全部。というわけです。

    クエリが詰まった。対処したい

    根本対応ではないのですが、前途したように、redisの中身をdeleteしてます。

    https://tsukada.sumito.jp/2018/08/25/redash-v5%E3%81%A7in-progress%E7%8A%B6%E6%85%8B%E3%81%AE%E3%82%BE%E3%83%B3%E3%83%93%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%82%92kill%E3%81%99%E3%82%8B/

    このような状況に陥った場合は、host側のswapも相当使用している可能性があるのでOSの再起動も合わせて行なってます。

    CLIで操作したい

    「redashのユーザーを作成してください」というのはredashを運用していてとても多いオペレーションです。

    GUIでもよいのですが、エンジニアにとってはCLIでやりたいこともあります。

     

    • ユーザー確認
      docker exec -it redash_server_1 ./manage.py users list
    • ユーザー作成
      docker exec -it redash_server_1 ./manage.py users create  tsukada@sumito.jp tsukada
    • ユーザーパスワード変更
      docker exec -it redash_server_1 ./manage.py users password tsukada@sumito.jp password
      
    • ユーザー削除
    docker exec -it redash_server_1 ./manage.py users delete tsukada@sumito.jp

    ただし、ユーザーに行動履歴があると、削除できません。その場合は履歴情報を削除してからCLIを実施する必要がありました。

    この点はVersion 5から改善され、GUIでユーザーをdisable化できるようになりました。

    そのため、現在弊社ではユーザーを削除するというオペレーションは行なっておりません。

    誰がどのクエリを実行したのか知りたい

    実は全てのクエリはredashのpostgresに保存されています。

    redashにログインし、 /admin に接続すると、管理者用のページが表示されます。

    但し、このページはadminのみアクセスが可能なページです。

    複数のデータソースを結合したい

    データソースAとデータソースBを結合することができます。joinのようなものです。

    Gunosyさんのブログに詳しい使い方が書いてあります。

    簡単にいうと、以下のようなクエリを書き、datasourceにQueryResultsを選択すると結合できます。

    select *
    from query_59 a
    join query_96 b
    where a.member_id = b.member_id;

    Dynamo DBをSQLで操作したい

    DynamoDBはDQLというSQLっぽいんだけど、SQLじゃないオリジナルの言語を使います。これが細かいところに手が届かなかったり、これが馴染みの無い人には辛い。

    QueryResultを経由させてSQLで操作できるようにします。

    https://tsukada.sumito.jp/2018/10/05/redash-query-result-dql/

     

    BigQueryを操作したい

    BigQueryはScan対象のカラムによって課金される。redashは実際に何行読み込み、容量は何kbyteだったのか表示してくれます。親切。

    redashのUserをgitで管理したい

    前途したuser listをCLIで表示させ、gitと連携しユーザの棚卸しを行なっている。

    ただし、Version 5でUserのenable/disable機能が組み込まれたが、CLIで確認することができなかった。一部昨日追記したところ、無事取り込んで戴けました。

    https://github.com/getredash/redash/pull/2951/files

    メリットは以下の通りです。

     

    redashにコントリビュートしたい

    CLIは穴場です。必要なものがまだ実装されておらず、且つ比較的簡単に実装できたりします。

    とはいえ、どのようにはじめたらいいかわからない、という方はぜひredashのdeveloper meeetupにお越しください。

    もくもくを経て、一人一人発表、フィードバックを得れる会です。その為参加人数も絞ってます。プルリク送ってコントリビュートしたい方、是非。

    会場は前回に引き続き、弊社レアジョブです。

    https://redash-meetup.connpass.com/event/110549/

  • jsonにコメントを書く

    jsonにコメントを書く

    はじめに

    Jsonは正式にコメント欄というのがない。しかしコメント重要。なんとかする

    解決策

    “` hjson “` を導入する。

    hjsonは正式にはjsonではないが、コメントついたjson。ちゃんとしたjsonにするには、変換コマンドを通すことにより、正式なjsonへ変換が可能。

    導入

    前提条件としてnodeがインストールされていることが必須。

    npm install hjson -g

    使い方

    hjsonファイルを用意する

    [
    {
      /*
        email  (tsukada@sumito.jp)
      */
      "name": "mailAddress",
      "type": "STRING"
    },
    {
      /*
        realname (TSUKADA SUMITO)
      */
      "name": "realName",
      "type": "STRING"
    },
    {
      /*
        nickname (Smith)
      */
      "name": "nickname",
      "type": "STRING"
    },
    {
      /*
        day (2013/08/03 15:11:00)
      */
      "name": "recordDay",
      "type": "TIMESTAMP"
    },
    {
      /*
        some plans
      */
      "name": "Plan",
      "mode": "REPEATED",
      "type": "RECORD",
      "fields": [
        {
          "name": "optionID",
          "type": "INTEGER"
        },
        {
          "name": "optionName",
          "type": "STRING"
        }
      ]
    }
    ]

    hjsonコマンドでコメントが外れる

    [
      {
        "name": "mailAddress",
        "type": "STRING"
      },
      {
        "name": "realName",
        "type": "STRING"
      },
      {
        "name": "nickname",
        "type": "STRING"
      },
      {
        "name": "recordDay",
        "type": "TIMESTAMP"
      },
      {
        "name": "Plan",
        "mode": "REPEATED",
        "type": "RECORD",
        "fields": [
          {
            "name": "optionID",
            "type": "INTEGER"
          },
          {
            "name": "optionName",
            "type": "STRING"
          }
        ]
      }
    ]
    

     

    リダイレクトをしてコメントを省いたjsonフォーマットに変換することが可能

    hjson -j sample.hjson > sample.json

    参考情報

    https://co.bsnws.net/article/131

     

     

  • AWS S3でフォルダを作成する方法

    AWS S3でフォルダを作成する方法

    はじめに

    AWSにおける作業の大半はawsコマンドで操作できます。

    しかし、画面上にあるはずの「フォルダの作成」については、オフィシャルドキュメントにコマンドが見つかりませんでした。 https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/using-s3-commands.html

    S3におけるフォルダという概念

    実はS3においてフォルダという概念は存在しませんが、論理的な階層を暗示するためにキー名のプレフィックスや区切り記号を使用することができます。Amazon S3のデータモデルはフラットな構造を持ち、バケット内にオブジェクトが格納されます。バケット内にはサブバケットやサブフォルダの階層は存在しません。

    https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingMetadata.html

    Amazon S3 のデータモデルはフラットな構造です。バケットを作成し、そのバケットにオブジェクトが格納されます。サブバケットやサブフォルダの階層はありません。ただし、Amazon S3 コンソールで使用されているようなキー名のプレフィックスや区切り記号を使用して論理的な階層を暗示できます。

    S3におけるデータモデルはフラットな構造を持ちます。バケットを作成し、そのバケットにオブジェクトが格納されます。サブバケットやサブフォルダの階層は存在せず、1つのバケットに多数のファイルが格納される形式となります。

    S3におけるフォルダの作成方法

    awsコマンドを使用して、S3上にフォルダを作成する方法を説明します。まずは以下のコマンドを使用して、フォルダを作成するためのキー名を指定します。

    aws s3 cp sample.txt s3://test/sample-folder/

    このコマンドでは、testバケット内にsample-folderという名前のフォルダを作成し、その中にsample.txtをコピーすることができます。複数の環境が.awsディレクトリに登録されている場合は、–profileオプションを使用して読み込むプロファイルを指定することができます。

    参考情報: Amazon S3における「フォルダ」という幻想をぶち壊し、その実体を明らかにする https://dev.classmethod.jp/cloud/aws/amazon-s3-folders/

    また、フォルダ内の全てのファイルを再帰的にコピーする場合は、–recursiveオプションを使用します。

    以上が AWS S3でフォルダを作成する方法になります。

  • cronで簡易監視スクリプトを作る

    cronで簡易監視スクリプトを作る

    はじめに

    zabbixやcloudWatchのような”ちゃんとした監視”ではなく、本当にシンプルな値だけを監視して、敷地を超えた場合は通知のみしたいことがある。今回はdisk使用量のみを監視するスクリプトを作った。

    設定

    disk使用率は60%を超えた場合、通知する簡易シェル

    $ df -h
    ファイルシス   サイズ  使用  残り 使用% マウント位置
    devtmpfs         484M   56K  484M    1% /dev
    tmpfs            494M     0  494M    0% /dev/shm
    /dev/xvda1       493G  302G  191G   62% /

    通知の部分は以下の記事を流用する。

    https://tsukada.sumito.jp/2018/11/27/chatwork/

     

    sshでserverにログインし、dfコマンドでxvda1をgrep、必要なところをawkで取得し、sedで%を削除。

    その値をtestコマンドで比較し、true(閾値超え)であればchatworkへ通知する処理がこちら。

    10 12 * * 1-5 test $(/usr/bin/ssh server df | /usr/bin/grep xvda1 | /usr/bin/awk '{print $5}' | /usr/bin/sed -e 's/\%//g') -gt 60 && sh /usr/local/src/server-disk.sh

    単純にプロセスを監視して、プロセスが落ちてたら起動する処理はこちら

    * * * * * ps ax |grep -v grep | grep -q sample.sh || sh /bin/sample.sh

     

     

  • コマンドやシェルスクリプトからchatworkを投げる

    コマンドやシェルスクリプトからchatworkを投げる

    はじめに

    プログラムからchatを送信したい事がある。chatworkはAPIを公開している ( http://developer.chatwork.com/ja/endpoint_rooms.html ) ので比較的簡単に実装できる。

    ワンライナーで実施

    コマンド一発でなんとかなる

    _roomid

    _body

    _token

    は適宜設定した後、以下のcurlコマンドを実施

    curl -X POST -H "X-ChatWorkToken: ${_token} -d "body=${_body}" "https://api.chatwork.com/v2/rooms/${_roomid}/messages"

     

    シェルで実施

    簡易的な監視などを

    #!/bin/bash
    
    cat << _EOT_ > /tmp/msg.txt
    [info]
    [title] DISK is over 85%!
    [/title]
    Too dangerous, Please fix it.
    [/info]
    _EOT_
    
    _roomid=1234567890
    _body=`cat /tmp/msg.txt`
    _token=abcdefghijkl12345678
    
    curl -X POST -H "X-ChatWorkToken: ${_token} -d "body=${_body}" "https://api.chatwork.com/v2/rooms/${_roomid}/messages"
    exit 0

    _roomid、_tokenは環境にあわせ適宜入力する。

     

     

  • docker buildコマンドおさらい 

    docker buildコマンドおさらい 

    はじめに

    docker buildを中心とするdockerコマンドの基本的なところをまとめた。基礎に立ち返って再入門する。

    Dockerコマンドについて

    よく使うのはこのあたり

    コマンド意味
    runDockerイメージからDockerコンテナを作成
    execDockerコンテナでコマンド実行
    pushDockerイメージをDockerレジストリへ送信
    buildDockerfileに基づき、imageを作成
    rm停止中のコンテナを削除
    rmiDockerホスト上のイメージを削除
    commitDockerコンテナからイメージを作成する
    kill起動中のコンテナを強制停止
    loadtarアーカイブからDockerイメージを読み込む
    tag既存のDockerイメージから新しDockerイメージを作成
    historyDockerイメージの精製履歴を表示
    psDockerコンテナ一覧を表示

    Dockerfileとは

    DockerイメージはDockerfileというファイルを生成、buildコマンドで読み込ませることによりイメージにすることが可能。

    基本構文はこちら

    docker build -f {Dockerfileへのパス} -t {イメージ名}:{タグ名} {Dockerfileのあるディレクトリ}

    デフォルトでコマンドを実行するディレクトリにあるDockerfileが読み込まれる。

    AnsibleやChefなどを用いて環境構築することもできるが、Dockerfileでも構築手順が明確になる。

    コマンド意味
    FROMベースになるDockerイメージを指定
    ENV環境変数を設定
    ARGbuild時の引数を定義するコマンド
    デフォルト値の設定も可能
    buildコマンドから –build-argsオプションを使う
    LABELDockerイメージにメタデータを付与
    versionやdescriptionを記載
    RUNshellの実行を行う
    SHELLデフォルトのシェルを設定
    WORKDIR基点となるいディレクトリを設定
    これを設定すると、Dockerfile内の多くのコマンドが相対パスで利用可能
    ADDファイルやディレクトリのコピー
    (重要!圧縮ファイルは自動的に展開される)
    COPYファイルやディレクトリを指定したURLからコピー。(重要!圧縮ファイルは展開されない)
    EXPOSEコンテナがlistenするポートを明記
    ENTRYPOINTコンテナ起動時に実行するコマンド
    CMDENTRYPOINTと同じくコンテナ起動時に実行するコマンドを指定

    動いているコンテナを全て止める

    停止前の状態

    MacBook:Dockerfile darkenergy$ docker ps
    CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                         NAMES
    10fea02bca93        redash/nginx:latest     "nginx -g 'daemon of…"   5 weeks ago         Up About an hour    0.0.0.0:80->80/tcp, 443/tcp   redash_nginx_1
    25581536faf9        redash/redash:latest    "/app/bin/docker-ent…"   5 weeks ago         Up About an hour    0.0.0.0:5000->5000/tcp        redash_server_1
    b71d8b8f7dd4        postgres:9.5.6-alpine   "docker-entrypoint.s…"   5 weeks ago         Up About an hour    5432/tcp                      redash_postgres_1
    e60a2354c981        redis:3.0-alpine        "docker-entrypoint.s…"   5 weeks ago         Up About an hour    6379/tcp                      redash_redis_1
    24229a9e8831        redash/redash:latest    "/app/bin/docker-ent…"   5 weeks ago         Up About an hour    5000/tcp                      redash_worker_1
    MacBook:Dockerfile darkenergy$ 
    
    

    docker ps -qでコンテナIDのみ表示させることが可能。

    それとdocker stopコマンドを組み合わせることにより、動いているコンテナを停止させる事ができる。

    MacBook:Dockerfile darkenergy$ docker stop `docker ps -q`
    10fea02bca93
    25581536faf9
    b71d8b8f7dd4
    e60a2354c981
    24229a9e8831
    MacBook:Dockerfile darkenergy$ docker ps -q 
    MacBook:Dockerfile darkenergy$ 

    止まっているコンテナを削除する

    MacBook:Dockerfile darkenergy$ docker ps -a
    CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS                          PORTS               NAMES
    10fea02bca93        redash/nginx:latest     "nginx -g 'daemon of…"   5 weeks ago         Exited (0) About a minute ago                       redash_nginx_1
    25581536faf9        redash/redash:latest    "/app/bin/docker-ent…"   5 weeks ago         Exited (0) About a minute ago                       redash_server_1
    b71d8b8f7dd4        postgres:9.5.6-alpine   "docker-entrypoint.s…"   5 weeks ago         Exited (0) About a minute ago                       redash_postgres_1
    MacBook:Dockerfile darkenergy$ 

    このコンテナを削除

    docker ps -qaで停止しているコンテナID一覧が表示できる。これとdocker rmコマンドを組み合わせる。

    MacBook:Dockerfile darkenergy$ docker rm `docker ps -qa`
    10fea02bca93
    25581536faf9
    b71d8b8f7dd4
    MacBook:Dockerfile darkenergy$ docker ps -a
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
    MacBook:Dockerfile darkenergy$ 
    

    docker build時のキャッシュの利用を止める

    Dockerをbuildしている時、以下のようにキャッシュを使われてしまう事がある。その利用を停止させる

     ---> Using cache

    docker buildするとデフォルトで “` –no-cache=false “` が設定されている。これを “` –no-cache=true “` にすることにより、キャッシュを使わずbuildする事が可能になる。

    Dockerfileは以下とする

    FROM ubuntu
    
    ENV myName John Doe
    ENV myDog Rex The Dog
    ENV myCat fluffy
    
    RUN echo "$myName, $myDog, $myCat"

    キャッシュを利用した場合

    MacBook% docker build -f env/Dockerfile_first -t build:env_first .
    Sending build context to Docker daemon   72.7kB
    Step 1/5 : FROM ubuntu
     ---> 113a43faa138
    Step 2/5 : ENV myName John Doe
     ---> Using cache
     ---> 94a9595b4ad2
    Step 3/5 : ENV myDog Rex The Dog
     ---> Using cache
     ---> 8e541e0cadf3
    Step 4/5 : ENV myCat fluffy
     ---> Using cache
     ---> d7f742cdd48d
    Step 5/5 : RUN echo "$myName, $myDog, $myCat"
     ---> Using cache
     ---> 08403e0b3e95
    Successfully built 08403e0b3e95
    Successfully tagged build:env_first
    MacBook% 

    キャッシュを利用しなかった場合

    MacBook% docker build -f env/Dockerfile_first -t build:env_first . --no-cache=true
    Sending build context to Docker daemon   72.7kB
    Step 1/5 : FROM ubuntu
     ---> 113a43faa138
    Step 2/5 : ENV myName John Doe
     ---> Running in f14667b92f6d
    Removing intermediate container f14667b92f6d
     ---> 7b9af0d9b741
    Step 3/5 : ENV myDog Rex The Dog
     ---> Running in 042dc782bef6
    Removing intermediate container 042dc782bef6
     ---> 51d13644f407
    Step 4/5 : ENV myCat fluffy
     ---> Running in 482cfc9e924e
    Removing intermediate container 482cfc9e924e
     ---> b0dd7f89783c
    Step 5/5 : RUN echo "$myName, $myDog, $myCat"
     ---> Running in 099ffe8325d8
    John Doe, Rex The Dog, fluffy
    Removing intermediate container 099ffe8325d8
     ---> 327d3ea27902
    Successfully built 327d3ea27902
    Successfully tagged build:env_first
    MacBook% 
    

    docker inspectを利用する

    docker inspectはイメージの詳細を調べるコマンド

    FROM ubuntu:16.04
    LABEL maintainer="tsukada@sumito.jp" 
    MacBook% docker inspect build:label                                       
    [
        {
            "Id": "sha256:19832872f5b87f8adf2f212d9371a32f396537394ecedd86ad7794487e4cbd4d",
            "RepoTags": [
                "build:label"
            ],
            "RepoDigests": [],
            "Parent": "sha256:7aa3602ab41ea3384904197455e66f6435cb0261bd62a06db1d8e76cb8960c42",
            "Comment": "",
            "Created": "2018-11-24T08:40:08.172027394Z",
            "Container": "22d4da4c92440ad7a9b946088a0ce970919c801bef124a1587c9f3d5374b714a",
            "ContainerConfig": {
                "Hostname": "22d4da4c9244",
                "Domainname": "",
                "User": "",
                "AttachStdin": false,
                "AttachStdout": false,
                "AttachStderr": false,
                "Tty": false,
                "OpenStdin": false,
                "StdinOnce": false,
                "Env": [
                    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
                ],
                "Cmd": [
                    "/bin/sh",
                    "-c",
                    "#(nop) ",
                    "LABEL maintainer=Image maintainer team <hogehoge@example.com>"
                ],
                "ArgsEscaped": true,
                "Image": "sha256:7aa3602ab41ea3384904197455e66f6435cb0261bd62a06db1d8e76cb8960c42",
                "Volumes": null,
                "WorkingDir": "",
                "Entrypoint": null,
                "OnBuild": null,
                "Labels": {
                    "maintainer": "tsukada@sumito.jp"
                }
            },
            "DockerVersion": "18.09.0",
            "Author": "",
            "Config": {
                "Hostname": "",
                "Domainname": "",
                "User": "",
                "AttachStdin": false,
                "AttachStdout": false,
                "AttachStderr": false,
                "Tty": false,
                "OpenStdin": false,
                "StdinOnce": false,
                "Env": [
                    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
                ],
                "Cmd": [
                    "/bin/bash"
                ],
                "ArgsEscaped": true,
                "Image": "sha256:7aa3602ab41ea3384904197455e66f6435cb0261bd62a06db1d8e76cb8960c42",
                "Volumes": null,
                "WorkingDir": "",
                "Entrypoint": null,
                "OnBuild": null,
                "Labels": {
                    "maintainer": "tsukada@sumito.jp"
                }
            },
            "Architecture": "amd64",
            "Os": "linux",
            "Size": 114808794,
            "VirtualSize": 114808794,
            "GraphDriver": {
                "Data": {
                    "LowerDir": "/var/lib/docker/overlay2/80942b3ef1ac6bd6683a0a610ebe509f493b3ab8e9981f55dad8277363dc5481/diff:/var/lib/docker/overlay2/44018ca585286af57480e331d2fa04d89de442742fe0432d9ee1ee0290872bf6/diff:/var/lib/docker/overlay2/c5e674d237e84ae63fcc71cf3e8fea790ea6072635406d095c16a9f22fa72706/diff:/var/lib/docker/overlay2/38077e61ba31390b978a4eabe12f01e31cc638590344241909c137356cae60c8/diff",
                    "MergedDir": "/var/lib/docker/overlay2/020aa677865cf852f816916ac2e7df525a26d9984f4e78e8c2d9e7e478a2c22b/merged",
                    "UpperDir": "/var/lib/docker/overlay2/020aa677865cf852f816916ac2e7df525a26d9984f4e78e8c2d9e7e478a2c22b/diff",
                    "WorkDir": "/var/lib/docker/overlay2/020aa677865cf852f816916ac2e7df525a26d9984f4e78e8c2d9e7e478a2c22b/work"
                },
                "Name": "overlay2"
            },
            "RootFS": {
                "Type": "layers",
                "Layers": [
                    "sha256:0a42ee6ceccb1b90de2a3badec7c74cc452ce61e7ef20a80bb7f20ea53f2825e",
                    "sha256:c2af38e6b250a39e0e7b9665687385c75fdf7bab5ea856a2eec4fd6b74adda95",
                    "sha256:5e95929b27983df137a6cff59695739f69f6571e70fa68eb6a7abe3b74e402d2",
                    "sha256:2166dba7c95bfbc84b38b7a6c7d96d323d16239aeff2f2292c61821002df2dfb",
                    "sha256:bcff331e13e35a0beb71d43b4f6b859327f18587f084a1036a603e64a990e44d"
                ]
            },
            "Metadata": {
                "LastTagTime": "2018-11-24T08:40:08.241971894Z"
            }
        }
    ]
    MacBook% 

    docker run

    shellの実行を行うためのコマンド

    RUN <command> shell from

    shell fromと呼ばれる使われ方

    SHELLの値にもとづいたコマンドによって実行される。

    “` /bin/sh -c “` で実行される

    RUN [“executable”, “param1″,”param2”] (exec from)

    exec fromと呼ばれる使われ方

    SHELLの値とは無関係に実行可能コマンドを直接実行

    shell fromとexec fromの違い

    shell fromはSHELLの設定により実行される
    “` /bin/sh -c echo $HOGE “`

    exec from はexecutableに指定されたコマンドが実行

    コマンドシェル経由での実行ではなく、直接実行する

    “`  echo $HOGE    “`

    参考方法: https://docs.docker.com/engine/reference/builder/#run

    /bin/sh: 1: Syntax error: “(” unexpected

    shellの指定がされていない可能性が高い

    SHELL ["/bin/bash", "-c"]

    RUNコマンドの前に上記SHELLを追加することにより解消できる

    SHELL

    SHELLコマンドはRUNやCMD,ENTRYPOINTなどでshell fromが行われた際のコマンドを指定する事が可能

    “` SHELL [“executable”, “parameters”] “`

    WORKDIR

    linuxで言うところの、mkdirとcdを同時に実行しているようなコマンド

    FROM ubuntu:16.04
    WORKDIR /sumito.tsukada
    WORKDIR b
    WORKDIR c
    RUN pwd

    と言うdockerfileがあったとすると、

    /sumito.tsukada/b/c

    と表示される

    ADDとCOPYの違い

    COPYはURLを指定する事ができない

    圧縮ファイル(tarファイル)を自動で解凍しない

    ENTRYPOINT

    exec from

    exec fromは推奨された使い方。

    Dockerfile

    FROM ubuntu:16.04
    ENTRYPOINT ["top", "-H"]

    build

    docker build -f entrypoint/exec/Dockerfile -t build:entrypoint_exec ./entrypoint/exec/.
    Sending build context to Docker daemon  2.048kB
    Step 1/2 : FROM ubuntu:16.04
     ---> a51debf7e1eb
    Step 2/2 : ENTRYPOINT ["top", "-H"]
     ---> Running in f48801d93416
    Removing intermediate container f48801d93416
     ---> 8c57c2838f55
    Successfully built 8c57c2838f55
    Successfully tagged build:entrypoint_exec

    PIDが1で起動される

    docker run --rm -it build:entrypoint_exec 
    
    top - 12:27:52 up  4:39,  0 users,  load average: 0.00, 0.03, 0.00
    Threads:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.2 us,  0.5 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem :  2047036 total,   502868 free,   260180 used,  1283988 buff/cache
    KiB Swap:  1048572 total,  1048104 free,      468 used.  1599568 avail Mem 
    
      PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                   
        1 root      20   0   36660   3036   2620 R  0.0  0.1   0:00.07 top                                                                                       
    
    

    shell from

    こちらが推奨された使い方ではない

    Dockerfile

    FROM ubuntu:16.04
    
    ENTRYPOINT top -H

    build

    build -f entrypoint/shell/Dockerfile -t build:entrypoint_shell ./entrypoint/shell 
    Sending build context to Docker daemon  2.048kB
    Step 1/2 : FROM ubuntu:16.04
     ---> a51debf7e1eb
    Step 2/2 : ENTRYPOINT top -H
     ---> Running in 30505870ab0c
    Removing intermediate container 30505870ab0c
     ---> e5825519f101
    Successfully built e5825519f101
    Successfully tagged build:entrypoint_shell

    docker run

    docker run --rm -it build:entrypoint_shell
    
    top - 12:31:56 up  4:43,  0 users,  load average: 0.00, 0.02, 0.00
    Threads:   2 total,   1 running,   1 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem :  2047036 total,   502612 free,   260024 used,  1284400 buff/cache
    KiB Swap:  1048572 total,  1048104 free,      468 used.  1599720 avail Mem 
    
      PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                   
        1 root      20   0    4500    696    624 S  0.0  0.0   0:00.04 sh                                                                                        
        6 root      20   0   36660   3140   2724 R  0.0  0.2   0:00.00 top   

    1はshが動き、shell fromによって起動されたコマンドは別プロセスになってしまう。これにより、ホスト側が実施したコマンドを直接受け取る事ができない。

    docker stopの動作

    docker stopを実施すると、PID 1にSIGTERMを送信する。

    しかしデフォルトで10秒応答が返ってこない場合はSIGKILLを実施。強制終了させると言う仕様がある。

    shell fromとexecの違い

    RUNはあくまでもDockerイメージをbuildするために実際にコンテナでコマンドを実施。commitすることにイメージに変更を加える。

    参考情報

    オライリー本より読みやすく、Dockerについて体系的に学ぶことができるのでおすすめ。

  • nginxでIPもしくはuseragentでアクセス制限をする

    nginxでIPもしくはuseragentでアクセス制限をする

    はじめに

    ここ数年、nginxを使う企業も増えて来た。今回はnginxのアクセス制御についてまとめる

    特定のIPからのアクセスを停止する

    例えばDoS攻撃。Firewallで止めるのが一般的だが、Nginxで止めたい時もある。

    その場合、拒否したいIPを先に書いて、allow allで締める。

            location / {
                # block below IPs
                deny 123.11.12.3;
                deny 10.1.2.3;
                allow all;
            }

    特定のIPから”のみ”アクセスを停止する

    allowを先に書いて、deny allで締める。

            location / {
                # block below IPs
                allow 123.11.12.3;
                allow 10.1.2.3;
                deny all;
            }

    nginxでIPもしくはuseragentでアクセス制限をする

    Nigixで複雑な制御をしたいときがある。 例えばnginxでIPもしくはuseragentでアクセス制限などだ。 set関数を使うと、if文のように管理しやすい

    サンプルコード

    nginxの設定ファイルで以下のように設定 123.12.1.1[2-9]のIPであれば許可 もしくはUseragentがFireFoxの場合はベーシック認証をかける設定

    server {
    
      set $allow_ip 0;
      if ($http_x_forwarded_for ~ ^123\.12\.1\.1[2-9]$){
        set $allow_ip 1;
      }
    
      set $fx 0;
      if ($http_user_agent ~ FireFox){
        set $fx 1;
      }
      set $conditions "${allow_ip}${fx}";
    
    
    
      location / {
        if ($conditions = 00){
          return 403;
        }
        if ($conditions = 01){
          set $auth_basic "basic auth";
        }
    
        auth_basic  $auth_basic;
        auth_basic_user_file "/etc/nginx/himitsu.htpasswd";
    }

     

  • PythonでCloud Strageへファイルをupload

    PythonでCloud Strageへファイルをupload

    はじめに

    ローカルのファイルをCloud Strageへファイルをアップロードしたい。今回はpythonを利用することにする

    手順

    インストールから利用開始まで

    実は公式ドキュメント(https://cloud.google.com/python/)が簡潔にまとまってるw

    pip install google-cloud-storage

    して

        import os
    
        import google.cloud.storage
    
        # Create a storage client.
        storage_client = google.cloud.storage.Client()
    
        # TODO (Developer): Replace this with your Cloud Storage bucket name.
        bucket_name = 'Name of a bucket, for example my-bucket'
        bucket = storage_client.get_bucket(bucket_name)
    
        # TODO (Developer): Replace this with the name of the local file to upload.
        source_file_name = 'Local file to upload, for example ./file.txt'
        blob = bucket.blob(os.path.basename(source_file_name))
    
        # Upload the local file to Cloud Storage.
        blob.upload_from_filename(source_file_name)
    
        print('File {} uploaded to {}.'.format(
            source_file_name,
            bucket))
                    

    を作り、該当箇所を適宜変更するだけでファイルのアップロードが可能。

     

  • BigQueryにbqコマンドでテーブル作成・スキーマ変更する

    BigQueryにbqコマンドでテーブル作成・スキーマ変更する

    はじめに

    BigQueryにbqコマンドでテーブル作成・データ投入・スキーマ変更する。 ネストされたデータも扱いたいので、今回はjsonファイルを読み込むようにする CSVは現段階でネストされたデータのimportをサポートされていない

    CSV ファイルはネストされたデータや繰り返しデータに対応していません。

    https://cloud.google.com/bigquery/docs/loading-data-cloud-storage-csv ちょうどいいサンプルデータ( https://gist.github.com/isdyy/5072792 )を置いてくださる方がいたので、それを拝借。

    準備

    https://gist.github.com/isdyy/5072792 にあるように2つのデータを用意する。1つはスキーマ。もう1つはデータ

    • スキーマ
      [
        {
          "name": "kind",
          "type": "string"
        },
        {
          "name": "fullName",
          "type": "string"
        },
        {
          "name": "age",
          "type": "integer"
        },
        {
          "name": "gender",
          "type": "string"
        },
        {
          "name": "citiesLived",
          "type": "record",
          "mode": "repeated",
          "fields": [
            {
              "name": "place",
              "type": "string"
            },
            {
              "name": "numberOfYears",
              "type": "integer"
            }
          ]
        },
        {
          "name": "note",
          "type": "string"
        }
      ]

       

    • データ
    {"gender": "Male", "fullName": "John Doe", "age": 22, "citiesLived": [{"numberOfYears": 5, "place": "Seattle"}, {"numberOfYears": 6, "place": "Stockholm"}], "kind": "person"}
    {"gender": "Female", "fullName": "Jane Austen", "age": 24, "citiesLived": [{"numberOfYears": 2, "place": "Los Angeles"}, {"numberOfYears": 2, "place": "Tokyo"}], "kind": "person"}
    {"note": "with newline (\\n)", "kind": "person", "gender": "Male", "age": 30, "fullName": "newline", "citiesLived": [{"place": "Shinjuku-ku,\nTokyo"}]}
    {"note": "with tab (\\t)", "age": 30, "fullName": "tab", "citiesLived": [{"place": "Shinjuku-ku,\tTokyo"}], "kind": "person"}
    {"note": "japanese / unicode escape sequence", "gender": "Female", "citiesLived": [{"place": "\u6a2a\u6d5c\u5e02"}], "fullName": "\u6a2a\u6d5c \u82b1\u5b50 (1)", "kind": "person"}
    {"note": "japanese / raw utf-8", "gender": "Female", "citiesLived": [{"place": "横浜市"}], "fullName": "横浜 花子 (2)", "kind": "person"}
    {"note": "double-quoted string", "fullName": "\"quoted\"", "kind": "person"}

    テーブルのみ作成

    # bq mk --table logs.sample nested01.fields.json
    Table '****:logs.sample' successfully created.

    ブラウザ上で以下の通り作成されている また、bq showコマンドでスキーマを確認できる

    # bq show logs.sample 
    Table ****:logs.sample
    
       Last modified                 Schema                 Total Rows   Total Bytes   Expiration   Time Partitioning   Labels  
     ----------------- ----------------------------------- ------------ ------------- ------------ ------------------- -------- 
      19 Nov 21:09:54   |- kind: string                     0            0                                                      
                        |- fullName: string                                                                                     
                        |- age: integer                                                                                         
                        |- gender: string                                                                                       
                        +- citiesLived: record (repeated)                                                                       
                        |  |- place: string                                                                                     
                        |  |- numberOfYears: integer                                                                            
                        |- note: string  

    データの投入

    # bq load --source_format=NEWLINE_DELIMITED_JSON logs.sample nested01.data.json  
    Upload complete.
    Waiting on bqjob_r4b711f3cb6f6854b_000001672be52e12_1 ... (0s) Current status: DONE  

    無事投入された。適切にネストもされている。

    テーブル作成+データ投入

    スキーマ作成と同時に、データの投入も可能

    # bq load --source_format=NEWLINE_DELIMITED_JSON logs.sample nested01.data.json nested01.fields.json
    Upload complete.
    Waiting on bqjob_r49704f78c779ebd5_000001672bd96baa_1 ... (0s) Current status: DONE  

    もちろん結果は同じ

    データのみをつかって、テーブルを自動作成

    “` –autodetect “` オプションを使う。

    # bq load --source_format=NEWLINE_DELIMITED_JSON --autodetect  logs.sample nested01.data.json
    Upload complete.
    Waiting on bqjob_r49704f78c779ebd115_00001672bd96baa_1 ... (0s) Current status: DONE  

    スキーマを変更する

    nested01.fields.jsonを以下の通り変更

    [
      {
        "name": "kind",
        "type": "string"
      },
      {
        "name": "fullName",
        "type": "string"
      },
      {
        "name": "age",
        "type": "integer"
      },
      {
        "name": "gender",
        "type": "string"
      },
      {
        "name": "country",
        "type": "string"
      },
      {
        "name": "company",
        "type": "string"
      },
      {
        "name": "citiesLived",
        "type": "record",
        "mode": "repeated",
        "fields": [
          {
            "name": "place",
            "type": "string"
          },
          {
            "name": "numberOfYears",
            "type": "integer"
          }
        ]
      },
      {
        "name": "note",
        "type": "string"
      }
    ]
    # bq update logs.sample nested01.fields.json
    Table '****:logs.sample' successfully updated.

    スキーマが追加。 今まで投入されていたデータに追加されたカラムはnullが入る。

    新しいスキーマに、旧データを投入

    (スキーマは変更したが、投入するデータは以前と同じ)

    # bq load --source_format=NEWLINE_DELIMITED_JSON logs.sample nested01.data.json 
    Upload complete.
    Waiting on bqjob_r73583e1d3c68485e_000001672bece222_1 ... (0s) Current status: DONE   

    エラーにはならず、単純に新しいスキーマにはnullが入る

    新スキーマに沿ったデータを投入

    {"gender": "Male", "fullName": "John Doe", "age": 22, "citiesLived": [{"numberOfYears": 5, "place": "Seattle"}, {"numberOfYears": 6, "place": "Stockholm"}], "kind": "person", "country": "Japan", "company": "A company"}
    {"gender": "Female", "fullName": "Jane Austen", "age": 24, "citiesLived": [{"numberOfYears": 2, "place": "Los Angeles"}, {"numberOfYears": 2, "place": "Tokyo"}], "kind": "person", "country": "USA", "company": "B company"}
    {"note": "with newline (\\n)", "kind": "person", "gender": "Male", "age": 30, "fullName": "newline", "citiesLived": [{"place": "Shinjuku-ku,\nTokyo"}], "country": "Japan", "company": "A company"}
    {"note": "with tab (\\t)", "age": 30, "fullName": "tab", "citiesLived": [{"place": "Shinjuku-ku,\tTokyo"}], "kind": "person", "country": "USA", "company": "B company"}
    {"note": "japanese / unicode escape sequence", "gender": "Female", "citiesLived": [{"place": "\u6a2a\u6d5c\u5e02"}], "fullName": "\u6a2a\u6d5c \u82b1\u5b50 (1)", "kind": "person", "country": "Japan", "company": "A company"}
    {"note": "double-quoted string", "fullName": "\"quoted\"", "kind": "person"}
    # bq load --source_format=NEWLINE_DELIMITED_JSON logs.sample nested01.data.json 
    Upload complete.
    Waiting on bqjob_r34be71bb151950ee_000001672bf33c1d_1 ... (0s) Current status: DONE 

    投入された

    テーブル作成

    jsonファイルを作成する

    [
          {
            "name": "register_day", 
            "type": "STRING"
          }, 
          {
            "name": "rtime", 
            "type": "STRING"
          }, 
          {
            "name": "tid", 
            "type": "INTEGER"
          }, 
          {
            "name": "sid", 
            "type": "INTEGER"
          }, 
          {
            "name": "flag", 
            "type": "INTEGER"
          }, 
          {
            "name": "id", 
            "type": "INTEGER"
          }, 
          {
            "name": "lesson_date", 
            "type": "TIMESTAMP"
          }
    ]

    jsonファイルを用意して、bqコマンドで作成する

    bq mk --table --expiration 3600 --description "This is my table" --label organization:development logs.mytable testTable.json
    

    Table ‘***:logs.mytable’ successfully created. と表示されたら成功。 ブラウザで確認すると、問題なく作成されている。

    スキーマ変更

     

    [
          {
            "name": "register_day", 
            "type": "STRING"
          }, 
          {
            "name": "rtime", 
            "type": "STRING"
          }, 
          {
            "name": "tid", 
            "type": "INTEGER"
          }, 
          {
            "name": "sid", 
            "type": "INTEGER"
          }, 
          {
            "name": "flag", 
            "type": "INTEGER"
          }, 
          {
            "name": "id", 
            "type": "INTEGER"
          }, 
          {
            "name": "lesson_date", 
            "type": "TIMESTAMP"
          },
          {
            "name": "lesson_date2", 
            "type": "TIMESTAMP"
          }
    ]

    lesson_date2を追加し、bq update コマンドを実施

    bq update logs.mytable testTable.json

    Table ‘****:logs.mytable’ successfully updated.

    となれば成功 無事カラムが追加された

    テーブル定義をdumpする

    bq --format=prettyjson show --schema tsukadaproject:logs.logsPartition > bbbb
    [
      {
        "mode": "NULLABLE", 
        "name": "register_day", 
        "type": "STRING"
      }, 
      {
        "mode": "NULLABLE", 
        "name": "rtime", 
        "type": "STRING"
      }, 
      {
        "mode": "NULLABLE", 
        "name": "lesson_date", 
        "type": "TIMESTAMP"
      }
    ]

    当然このformatを読み込ませることができる

    bq mk --table --expiration 3600 --description "This is my table" --label organization:development logs.bbbb bbbb   

    パーティションテーブルを作成する

    JSONはスキーマ情報のみ。パーティションテーブルの指定はbqコマンドのオプションで行う。

    [
      {
        "mode": "NULLABLE", 
        "name": "register_day", 
        "type": "STRING"
      }, 
      {
        "mode": "NULLABLE", 
        "name": "rtime", 
        "type": "STRING"
      }, 
      {
        "mode": "NULLABLE", 
        "name": "lesson_date", 
        "type": "TIMESTAMP"
      }
    ]

    パーテョションテーブル作成

    bq mk --table --expiration 3600 --description "This is my table" --time_partitioning_field=lesson_date --time_partitioning_type=DAY --label organization:development logs.cccc cccc

    Table ‘****:logs.cccc’ successfully created.

     

    参考情報

    ストーリー仕立てで分かりやすくまとまっているのでおすすめ。

  • embulkでBigQueryの分割テーブル(partitioned table)へデータ投入

    embulkでBigQueryの分割テーブル(partitioned table)へデータ投入

    はじめに

    BigQueryでクエリスキャンを行うと、いくらwhereで絞ったとしても、テーブル内の指定したカラムは全行読み、それに応じて課金される。BigQueryに分割テーブル(partitioned table)を作成することにより、時間や日付を絞り膨大なスキャンならびに課金を限定的にすることが可能だ。今回はembulkからデータのinsertを行うことにする。

    やりたいこと

    MySQL -> (embulk) -> BigQuery

    embulkの設定

    in:
      よしなに
    
    out:
      type: bigquery
      auth_method: json_key
      json_keyfile: xxxx.json
      path_prefix: /tmp/
      file_ext: .csv.gz
      source_format: CSV
      project: xxxxx
      dataset: logs
      time_partitioning:
        type: DAY
      auto_create_dataset: true
      auto_create_table: true
      delete_from_local_when_job_end: true
      table: sample
      formatter: {type: csv, charset: UTF-8, delimiter: ',', header_line: false}
      encoders:
      - {type: gzip}

    パラメータの詳細はこちら

    https://github.com/embulk/embulk-output-bigquery

    実施

    docker run -t -v ${PWD}:/work tsukada/embulk run bq.yml 

    結果(BigQuery)

    select 
    _PARTITIONTIME AS pt,
    lesson_date
    rtime
    from [logs.sample]
    
    WHERE 
      _PARTITIONTIME >= "2018-11-13 00:00:00" AND _PARTITIONTIME < "2018-11-16 00:00:00"
      

    _PARTITIONTIMEは見えないカラムのようなもので、その日付はinsertで更新される。

    WHERE _PARTITIONTIME でスキャン対象カラムを絞ることが可能。