継続的ブログ

主にweb系の技術について書いています

docker build 時に必要な RAILS_MASTER_KEY を Buildkit の Secret Mount で渡す

docker build 時に assets:precompile してコンテナに assets を含めてしまう場合などは RAILS_MASTER_KEY をどうにかして渡す必要があるかと思います。

ARG で渡すことも可能ですが、 docker history で見ることができてしまうので公式でも秘匿情報を扱う際は推奨していません。

docs.docker.com

そこで今回は Buildkit の Secret Mount を使って安全に渡したいと思います。

$ echo $RAILS_MASTER_KEY > /tmp/master_key.txt
$ DOCKER_BUILDKIT=1 docker build --secret id=master_key,src=/tmp/master_key.txt  .

Dockerfile

# syntax = docker/dockerfile:1.0-experimental

...

RUN --mount=type=secret,id=master_key,dst=config/master.key,required \
      bundle exec rails assets:precompile

dive をつかって最終イメージをみてみると config/master.key には 0 byte のファイルがあるだけです。

f:id:akiza:20201127195128p:plain

動かす際は ENV['RAILS_MASTER_KEY'] を渡してやれば config/master.key より優先されます。

rails/encrypted_file.rb at master · rails/rails · GitHub

参考

生産的な毎日を過ごすためのツール

Habitify

Habitify - The Minimal, Data-Driven Habit Tracker

ルーチン管理に使っています。
使い方は下記ブログ記事が最高なのでその通りにやれば間違いないです。

時間は有限なのでHabitifyを使って能力を高めることで一日を実質48時間にし圧倒的なパフォーマンス向上を実現する | 栗林健太郎

対応プラットフォームも多く(Android, iOS & watchOS, Web, Macアプリ)、自分は Android のウィジェットと Mac アプリで常に確認できるようにして使ってます。

f:id:akiza:20200828184900p:plainf:id:akiza:20200828184919p:plain

『実践版GRIT やり抜く力を手に入れる』という本にこのようなことが書いてあります。

なかでも最も注目すべきは、オバマは大統領としての決断が必要となること以外は何ひとつ自分では決めないということだ。(中略) 彼がこのやり方を採用していたのは、意思決定のためのエネルギーを節約するためだ。難しい決断には意志力の消耗が伴い、意志力が使い尽くされると「決断疲れ」と呼ばれる状態に陥るということが、最新の研究で明らかになったことが背景にある。「たわいのないことに気を取られて一日を無駄にしないためには、ルーチン化する必要がある」とルイスは説明する。(中略)。彼は、本当に極めて重要な問題以外のことについては、それをすべきかどうか悩む時間さえも惜しんでいたのだ。そのため、そういう仕事はすべて誰かに委任していた。そして意識しないでも行動できるように何もかもをルーチン化することで、精神的エネルギーの浪費を回避していた。そうすることで、本当に重要な問題に対して大統領としての決断が求められたときだけ、彼はクリアな頭脳と質の高い集中力をそこに注ぎ込んでいたのだ。

何も考えず上から順にルーチンをこなしていけば意志力も消費せずにすみますし、そこに習慣化したいものを追加すれば本当に習慣化できるのでオススメです。

Toggl

Toggl: Simple & Beautiful Tools that Help Teams Work Better

勉強(本)の時間管理に使ってます。
毎日どれくらい勉強時間とれたか、一週間でどのジャンルを多くやってるかなどを見てます。

毎日の勉強時間は、今のところ平日は最低1時間、休日は最低2時間は勉強時間取ろうと思ってるのでそれを目標にやってます。
あくまで目標なので完璧にやろうとは思ってませんが、計測するとその目標時間まで少し足りないってときも一踏ん張りできて良いです。
平日はあまりまとまった時間は取れなかったりするので、始業前や昼休み、お風呂などの時間を使って細かくやってます。

週のレポートは、エンジニアなので技術的な勉強の時間を意識的に多めに取ろうと思ってますが、あまり意識しないでやると英語ばっかりやってしまったりするので、そういうときに気付けて良いです。
(↓ 英語の比率多いですが...)

f:id:akiza:20200828224454p:plainf:id:akiza:20200828224049p:plain

Todoist

Todoist: ToDoリストで仕事と生活を管理

主に毎日ではないけど定期的に発生する家事の管理に使ってます。
スケジュールを柔軟に入れられるので 毎週土曜 とか 隔週土曜 とか 第2土曜 とか入れて使ってて、その日になったら今日の予定に入ってくる感じです。
なので基本的に今日の予定しか見ないし、もしも出先とかでパッと思いついた TODO とかも今日の予定に入れて、あとですぐ消化するようにしてます。

f:id:akiza:20200828225410p:plain

でも最近は、完全リモートになって外に出ることも少なくなったのでアナログでバレットジャーナルやってみてます。

DayOne

Your Journal for Life | Day One

template 作って毎日の作業ログ(日報)書いたりしてます。

f:id:akiza:20200828230107p:plain

あとは、IFTTT 使って DayOne にその日のツイート・いいねや Pocket に保存したものを保存するようにしてます。
ライフログ的な感じで日毎にろいろまとまって良いし、Day One にだいたい集まっいくので「なんかこんなことあったな」的なことがあった際に Day One 検索するだけで済むのも良いです。


Fargate 1.4.0 に対応する(VPCエンドポイントの設定)

先日 Fargate プラットフォームバージョン 1.4.0 がリリースされました。

aws.amazon.com

タスク Elastic Network Interface (ENI) が追加のトラフィックフローの実行を開始

変更点の中にこのようなものがあり、VPCエンドポイントを使用している場合は追加のエンドポイントの設定が必要になります。

必要な設定がされていない場合、下記のようなエラーが出ます。

ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed: unable to retrieve ecr registry auth: service call has been retried 1 time(s): RequestError: send request failed caused by: Post https://api.ecr....

説明文にも書いてあるとおり、 ecr.api エンドポイントの追加は必要です。

実際の例としては、以前は Fargate で ECR にプライベートリンクを使用していた場合、ecr.dkr エンドポイントを設定するだけで済みました。プラットフォームバージョン 1.4.0 では、api.ecr エンドポイントも設定する必要があります。

resource "aws_vpc_endpoint" "ecr_api" {
  service_name        = "com.amazonaws.ap-northeast-1.ecr.api"
  vpc_endpoint_type   = "Interface"
  vpc_id              = aws_vpc.this.id
  subnet_ids          = aws_subnet.private.*.id
  security_group_ids  = [aws_security_group.ecs_private_link.id]
  private_dns_enabled = true
}

そのほかにも Secrets ManagerSystems Manager を使用している場合は、追加で設定が必要です。

  • 例: SSM
resource "aws_vpc_endpoint" "ssm" {
  service_name        = "com.amazonaws.ap-northeast-1.ssm"
  vpc_endpoint_type   = "Interface"
  vpc_id              = aws_vpc.this.id
  subnet_ids          = aws_subnet.private.*.id
  security_group_ids  = [aws_security_group.ecs_private_link.id]
  private_dns_enabled = true
}

Fargate どんどん良くなっていって素晴らしい!

TerraformでSourceをECRにしたCodePipelineを作成する

SourceにECRをしたものを設定する機会があったので書いておきます。
SourceにGitHubを指定しているサンプルはよく見かけますが、ECRを指定しているものはあまりなかったので参考になれば。

CodePipeline

resource "aws_codepipeline" "example" {
  name     = "example"
  role_arn = aws_iam_role.codepipeline.arn

  artifact_store {
    location = aws_s3_bucket.artifact.bucket
    type     = "S3"
  }

  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "ECR"
      version          = "1"
      output_artifacts = ["source"]

      configuration = {
        RepositoryName = "example"
        ImageTag       = "stage"
      }
    }
  }

  stage {
    name = "Build"

    action {
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      version          = "1"
      input_artifacts  = ["source"]
      output_artifacts = ["build"]

      configuration = {
        ProjectName = aws_codebuild_project.example.id
      }
    }
  }

  stage {
    name = "Deploy"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "ECS"
      input_artifacts = ["build"]
      version         = "1"

      configuration = {
        ClusterName = aws_ecs_cluster.example.arn
        ServiceName = aws_ecs_service.ecample.name
        FileName    = "imagedefinitions.json"
      }
    }
  }
}

Sourceの部分は provider = "ECR" を設定して、configurationにトリガーしたいECRのリポジトリ名、イメージタグ名を設定します。(ImageTagはオプショナル)
ここでは example リポジトリの stage タグのプッシュをトリガーにする設定をしています。

buildspec.yml

version: 0.2

phases:
  install:
    runtime-versions:
      python: 3.7
  build:
    commands:
      - echo Build started on `date`
      - REPOSITORY_URI=$(cat imageDetail.json | python -c "import sys, json; print(json.load(sys.stdin)['ImageURI'].split('@')[0])")
      - IMAGE_TAG=$(cat imageDetail.json | python -c "import sys, json; print(json.load(sys.stdin)['ImageTags'][0])")
      - echo $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Writing image definitions file...
      - printf '[{"name":"web","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json

artifacts:
    files: imagedefinitions.json

output として imageDetail.json が生成されるので、そこから imagedefinitions.json を作成します。
ECS Blue/Greenデプロイを選択する場合は、imageDetail.json そのままで大丈夫かと思いますが、今回は標準デプロイを選択したので imagedefinitions.json を作成しています。

stackoverflow.com

これで設定完了!ECRにプッシュすればCodePipelineが起動する!

と思ったのですが、Terraform等から作成する場合はCloudWatch Events等は自前で用意しなきゃいけないんですね...(マネージメントコンソールから作成すれば勝手に作ってくれる)

docs.aws.amazon.com

CloudWatch Events & CloudTrail

data "aws_iam_policy_document" "cloudwatch_events" {
  statement {
    effect    = "Allow"
    resources = ["*"]

    actions = [
      "codepipeline:StartPipelineExecution"
    ]
  }
}

data "aws_iam_policy_document" "cloudwatch_events_assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["events.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "cloudwatch_events" {
  name               = "codepipeline-cloudwatch-events"
  assume_role_policy = data.aws_iam_policy_document.cloudwatch_events_assume_role.json
}

resource "aws_iam_policy" "cloudwatch_events" {
  name   = "codepipeline-cloudwatch-events"
  policy = data.aws_iam_policy_document.cloudwatch_events.json
}

resource "aws_iam_role_policy_attachment" "cloudwatch_events" {
  role       = aws_iam_role.cloudwatch_events.name
  policy_arn = aws_iam_policy.cloudwatch_events.arn
}

resource "aws_cloudwatch_event_rule" "ecr" {
  name        = "codepipeline-ecr-event-rule"
  description = "Amazon CloudWatch Events rule to automatically start your pipeline when a change occurs in the Amazon ECR image tag."

  event_pattern = <<-JSON
  {
    "source": [
      "aws.ecr"
    ],
    "detail-type": [
      "AWS API Call via CloudTrail"
    ],
    "detail": {
      "eventSource": [
        "ecr.amazonaws.com"
      ],
      "eventName": [
        "PutImage"
      ],
      "requestParameters": {
        "repositoryName": [
          "example"
        ],
        "imageTag": [
          "stage"
        ]
      }
    }
  }
  JSON

  depends_on = ["aws_codepipeline.example"]
}

resource "aws_cloudwatch_event_target" "ecr" {
  rule      = aws_cloudwatch_event_rule.ecr.name
  target_id = aws_cloudwatch_event_rule.ecr.name
  arn       = aws_codepipeline.example.arn
  role_arn  = aws_iam_role.cloudwatch_events.arn
}
data "aws_caller_identity" "current" {}

resource "aws_cloudtrail" "example" {
  name           = "example"
  s3_bucket_name = aws_s3_bucket.example.id

  event_selector {
    read_write_type           = "All"
    include_management_events = true

    data_resource {
      type   = "AWS::S3::Object"
      values = ["arn:aws:s3:::"]
    }
  }
}

resource "aws_s3_bucket" "example" {
  bucket = "cloudtrail"
  acl    = "private"

  policy = <<-POLICY
  {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Sid": "AWSCloudTrailAclCheck",
        "Effect": "Allow",
        "Principal": {
          "Service": "cloudtrail.amazonaws.com"
        },
        "Action": "s3:GetBucketAcl",
        "Resource": "arn:aws:s3:::cloudtrail"
      },
      {
        "Sid": "AWSCloudTrailWrite",
        "Effect": "Allow",
        "Principal": {
          "Service": "cloudtrail.amazonaws.com"
        },
        "Action": "s3:PutObject",
        "Resource": "arn:aws:s3:::cloudtrail/AWSLogs/${data.aws_caller_identity.current.account_id}/*",
        "Condition": {
          "StringEquals": {
            "s3:x-amz-acl": "bucket-owner-full-control"
          }
        }
      }
    ]
  }
  POLICY
}

CloudWatch Events の設定だけではうまくいかなくて、↓を参考に CloudTrail も設定したらうまくいきました!

https://forums.aws.amazon.com/thread.jspa?threadID=306306&tstart=0


今後もしかしたら CloudWatch Events の作成までしてくれる可能性もあるかも?

github.com


CodePipelineのことは書いてないですが、この本かなり良かったです!

Terraform - Up & Running: Writing Infrastructure As Code

Terraform - Up & Running: Writing Infrastructure As Code

Second Edition は v0.12 に対応していて、まだ 発売前 ですが O'Reilly の Safari Books Online では読めます!

www.oreilly.com

自分も Safari Books Online で読んだのですが、 Free Trial の 10日で読める分量だと思うのでもし興味があれば是非!

モデルの多言語化について(Rails)

モデルの多言語化については、RailsガイドでGemが紹介されているのでそれらのGemについて軽くまとめてみました。

railsguides.jp

traco

github.com

  • locale毎にカラムを用意するシンプルな方式(例:title_svtitle_en
  • localeが増える毎にマイグレーション(カラム追加)が必要になるので複数言語対応する可能性があるなら厳しいが、英語しか対応しないなど固定で決まっているならこれでも良いかも
  • 使用されてなくても全てのレコードに全てのlocale分のカラムが存在する(NULLで)のでスペースの使用効率は悪い

globalize

github.com

  • 一番メジャー
  • 多言語化したいカラムが存在するテーブル毎に _translations という別テーブルを作成する方式
  • locale毎にレコードができる(localeカラムを持っている)
id post_id locale title name
1 1 ja タイトル 名前
2 1 en title name
  • 必要なlocaleの分だけレコードが作られるのでスペースの使用効率が良い
  • 毎回joinする必要あるのでクエリが少し複雑になる

mobility

github.com

  • globalize のコアコミッターでRubyKaigiでも発表されていたshioyamaさんがもっといいソリューションをと作ったもの
  • 基本何でもできる
    • カラム方式
      • tracoのような
    • 別テーブル方式
      • globalizeのような
    • 共有別テーブル方式(default)
      • これがmobility独自の方式で売りっぽい
      • globalizeはそれぞれテーブルを持っていたが、これは一つのテーブルに全てのモデルのデータが入る(ポリモーフィック関連)
    • シリアライズ方式
    • Hstore/Jsonb方式(PostgreSQLオンリー)
  • mobilityを作った背景などが書かれているブログ(多言語化に対する解決法がまとまっていてかなり参考になる

dejimata.com

習慣化するのに役に立ったサービス(英語学習・瞑想)

やると絶対いいとわかってるけど、なかなか続かなくて三日坊主で終わってしまうことありませんか?

自分は

  • 英語学習
  • 瞑想

この2つを毎日続けられずに悩んでいました...

英語の重要性は言わずもがなで、瞑想は脳に良いと言われているのでパフォーマンス・学習効率上げるためにもやりたいなと思っていました。

いろいろ試してみたのですが、最近自分の中でこれだというものが見つかったので紹介させていただきます!

英語学習

iKnow

iknow.jp

最初無料トライアルで試してみて、良さそうだったので課金しました。
忙しかったりで一日数分だけの日もありますが、今のところ約200日連続で続けられています。

脳の記憶メカニズムに沿って、繰り返し出題してくれるのでそれに従ってやるだけというのが手軽でいいです。
ネイティブスピーカーによる例文の音読もあるので、あとに続いて音読する感じでやっています。

これを続けたおかげで英語学習に対するモチベーションもあがって、他に足りないところ(文法)とか勉強するようになったのもよかったなと。

f:id:akiza:20190309121558j:plain

瞑想

MEISOON

yoga-lava.com

毎朝起きたら10分やってます。
昔は5分でも辛かったのですが、最近は全然苦ではなくなってきました。
こちらは基本無料で、課金して他の瞑想のコースを買うことができますが、無料のものだけで十分だと思います。

期待していた脳への効果はまだわかりませんが、気持ちがスッキリするのでやってよかったです。

f:id:akiza:20190309121623j:plain

番外編(筋トレ)

筋肉体操

www.youtube.com

もともとジムは毎週通えていたのですが、これは家で十分に追い込めるしとても楽しくできるので最高です!
5分という短さも、先生の追い込み時の煽りも最高で、運動嫌いの妻も毎日筋トレ続けられるようになったので感謝です。


こうしてみると自分はレコーディング系のサービスが合ってるんだなということがわかりました。
人それぞれ合う合わないはあると思いますが、参考になれば。

docker環境でvimからrspec実行

qiita.com

少し前にjokerさんの ↑ の記事を参考にして、docker環境でvimからrspec実行できるようにして、最高に快適になりました。
ありがとうございます。

記事ではdirenv使って環境変数でコンテナ名を指定してフィルターしているみたいですが、自分はリポジトリ増えたり、docker環境とそうでない環境が混在してきたらでいいかなと思ったのでとりあえず雑に設定...

nnoremap <Leader>tn :TestNearest<CR>
nnoremap <Leader>tf :TestFile<CR>

let test#strategy = 'neoterm'
let test#ruby#rspec#executable = 'rspec'
function! DockerTransformer(cmd) abort
  let container_id = system("docker ps | grep app | grep rails | awk '{print $1}'")
  return 'docker exec -t ' . container_id . ' bundle exec ' . a:cmd
endfunction
let g:test#custom_transformations = {'docker': function('DockerTransformer')}
let g:test#transformation = 'docker'
let container_id = system("docker ps | grep app | grep rails | awk '{print $1}'")

これでrails起動しているコンテナIDを取得するようにしました。(grepの条件はターゲットにしているコンテナを一意に特定できる条件)

ご参考まで。

github.com

github.com