GitHub CLIとhubを使い比べての感想

agent bank開発部の森です。 最近GitHubのPull Requestをローカルでcheckoutする案内に GitHub CLI Beta という項目が追加されましたね。

f:id:jiska_roxx:20200214124120p:plain
GitHub CLIの項目が追加されている

初めてみたときは hub がリネームでもしたのかな?と思っていたんですが実際はhubとは別のツールでした。 GitHub CLIとhubとを使い比べてみての感想をまとめます。

hubとの違い

GitHubCLIツールといえば hub が数年ほど前からあります。なぜhubがあるのになぜ別のツールを作ったのか? https://github.com/cli/cli#comparison-with-hub に違いが記載されているので引用します。

For many years, hub was the unofficial GitHub CLI tool. gh is a new project for us to explore what an official GitHub CLI tool can look like with a fundamentally different design. While both tools bring GitHub to the terminal, hub behaves as a proxy to git and gh is a standalone tool.

...そもそもhubがunofficialだったなんて知らなかった…公式プロジェクトとするべくhubとは異なる設計でつくられたそうです。とりあえず試しに使ってみましょう。

GitHub CLI

https://github.com/cli/cli#installation-and-upgrading に記載されている手順に沿ってインストールしてください。インストールすると gh コマンドが実行できるようになります。

❯ gh --help
Work seamlessly with GitHub from the command line.

GitHub CLI is in early stages of development, and we'd love to hear your
feedback at <https://forms.gle/umxd3h31c7aMQFKG7>

Usage:
  gh [command]

Available Commands:
  help        Help about any command
  issue       Create and view issues
  pr          Create, view, and checkout pull requests

Flags:
      --help              Show help for command
  -R, --repo OWNER/REPO   Select another repository using the OWNER/REPO format
      --version           Show gh version

Use "gh [command] --help" for more information about a command.

GitHub CLIでできること

今のところできることはissueとprの閲覧、作成です。 hubよりもできることはだいぶ少ないですが、issueやprをCUIで作る時に案内が丁寧だったりブラウザでプレビューできたりするのがhubよりもいいところです。 issue, prのリストはあまりhubと見た目変わらないですね。

f:id:jiska_roxx:20200214124531p:plain
gh issue list
f:id:jiska_roxx:20200214124525p:plain
gh pr list

気に入ったのはissueを作成する時にブラウザでプレビュー画面を表示してくれるところです。まちがって誤字入りissueやpr作ってしまうのが恥ずかしいので一度チェックできるのは嬉しい(チェックが漏れないとは言っていない)。

❯ gh issue create
Creating issue in jiska/example
? Title example
? Body <Received>
? What's next? Preview in browser
Opening github.com/jiska/example/issues/new/ in your browser.

公式ブログのアナウンス https://github.blog/2020-02-12-supercharge-your-command-line-experience-github-cli-is-now-in-beta/ に詳しい例が記載されていますのでそちらも参照ください。

今後期待したいこと

いまのところBetaだからなのか、僕がhubで重宝している機能、たとえば hub browse , hub ci-status , hub compare に相当する機能がGitHub CLIにはありません、今後の拡張を期待したいしcontributeがんばって追加してみようかな…

PHPerKaigi2020に参加して得た知見

こんにちは。bacckcheck事業部の前田です。
この記事は個人ブログの転載です。

phperkaigi.jp

PHPerKaigi2020に参加してきました。 一般参加ではなくスタッフとして参加したのでトークはきいてなく*1、スタッフとしての「PHPerKaigiの裏側!!」みたいなのも書かないので、あまりブログに書く内容がないのです・・・

そんな中で得た知見を少しでも紹介します。

朝のコンポタが最高

朝にモスバーガーで飲むコーンポタージュスープが最高です。 コンポタのためにスタッフ業をしているといっても過言ではないかもしれません。(過言です)
最高なので毎日行きました。

1日目

2日目

3日目

PHPerも踊れる

前夜祭にPHPrePartyというのがあり、なんとびっくり、DJイベントをやったのです。 普段のエンジニアの様子からはまったく想像つかないのですが、みんな踊れるんです・・・!!

ほしさきさんが動画をTwitterにあげていて、とても場の雰囲気が伝わってきます。

みんな場があればこんなにはしゃげるんだなーと再認識しました!

みんなコミュニケーションをとりたい

PHPerKaigiに参加して毎年思うことですが、みんなやっぱりコミュニケーションを取りたいんだな!!ということです。

当然に僕もコミュ障なわけですが、コミュニケーション下手ながらも、誰かと話したり、自分の知らない考え方をきいたりするのはとても楽しいのです。

そういうタイプは僕だけなのかな?と思ってたのですが、どうやらみんな同じ気持ちのようだとわかりました。

PHPerKaigiにはコミュニケーションスペースというものがあって、色々な人と話せる仕組みがあったりします。

f:id:chiroruxx:20200212213553j:plain

ここが本当にずっと賑やかで(トークやってるときもずっと!)、みんな楽しそうでした。

また、IRTもやっていて、ここもずっと満員でにぎわっていました。

f:id:chiroruxx:20200212213846j:plain

これは言ったら怒られるのかもしれないのですが、トークは後からでも情報を追えるんですよね。スピーカーのみなさんは優しいのでスライドをWebにあげてくれるし、最近のカンファレンスは後で動画を上げてくれるところも多いですよね。

でもこうやって誰かと話したり意見交換したりすることは、実はイベントに来ないとできないことなんです。

なのでみんな、色んな人といっぱい話すためにカンファレンスに行こう!PHPerKaigiに行けばいっぱい話せるよ!もちろんコミュ障でも!

と言っていきたいと思いました。

余談

今回はトレカが受付時に配られていて、PHPrePartyから本格的に交換していきました。
80人くらいの人と交換できて、わりと満足です。

ちなみに僕のカードはこれ。 f:id:chiroruxx:20200212215034j:plain

対戦相手はカードを1枚捨てる

うーん、自分の性格がでてるなぁ。

*1:私はリーダーだったのできかなかったのですが、他のスタッフは普通にトークをききにいけます

CDKとterraformの使い分け

こんにちは、 kotamat です。

少し前から、CDKを用いた環境整備を行いました。今までterraformで構築してきたので、それとの差分をメモ代わりに書こうと思います。

TL; DR

使い分けとしては下記のようになるかなと思っています

  • CDK
    • ベストプラクティスを簡単に構築したい場合。
    • ビルドプロセスなど、動的にインフラ構成を構築したい場合
  • terraform
    • サービスの基盤となるインフラを構築したい場合
    • 同等の環境を、異なるVPCなどで構築したい場合

CDK

とは

AWS クラウド開発キット (AWS CDK) は、使い慣れたプログラミング言語を使用してクラウドアプリケーションリソースをモデル化およびプロビジョニングするためのオープンソースのソフトウェア開発フレームワークです。

https://aws.amazon.com/jp/cdk/

簡単に言うと、TypeScript、PythonJava、および .NET でCluodFormation(AWS専用のIaCサービス)用のパターンファイルを生成してくれるものとなります。 いつも使い慣れた言語を使うことができるため、サーバーサイドのエンジニアが手軽にインフラの構築を実現できるものとなります。

使用感

今回はTypeScriptを用いたのですが、型推論がしっかり効いていたため、どのリソースにどのようなパラメータが設定可能なのかを、コードをみるだけである程度推測することが可能で、流れるようにコードを書くことができました。

const app = new cdk.App()
new ApiStack(app, 'Api', {
  env: {
    account: 'account',
    region: 'ap-northest-1'
  }
})
app.synth()

class ApiStack extends cdk.Stack {
  constructor(parent: cdk.App, name: string, props: cdk.StackProps) {
    super(parent, name, props)

    const vpc = Vpc.fromVpcAttributes(
      this,
      'Vpc',
      {
        vpcId: 'vpc-8932jkdw',
        availabilityZones: ['ap-northeast-1a', 'ap-northeast-1c'],
        publicSubnetIds: ['subnet-8302sba', 'subnet-32890231'],
        privateSubnetIds: ['subnet-9042389fdjosa', 'subnet-re032109jifds']
      })

    const cluster = Cluster.fromClusterAttributes(this, 'Cluster', {
      clusterName: 'api-cluster-name',
      vpc,
      securityGroups: []
    })
  }
}

このように、<リソース名>.from<設定するアトリビュート>()とすると、現在のリソースから情報を取得し、その取得した結果を別のリソース作成時に使い回すといったことができます。

実行時は下記のようにTypeScriptをJS化した上で通常のJavaScriptを実行するだけなので、環境変数の注入など、動的に指定することが可能です。

cdk synth --app 'node index.js' > ../template.yml

いいところ

上記で紹介したとおり、環境変数などを用いて動的にパラメータを注入できるわけですが、それが生きるポイントとしては、 何かをビルドしたときにインフラを用意する というユースケースが考えられます。

CDKを使っているということは必然的にAWSを用いているということになるわけですが、例えばあるリポジトリでCodeBuildで環境を整備しようとしたときにbuildspec.ymlを配置すると思うのですが、そちらに下記のように指定することによって、dockerイメージを作成したあとにそのイメージを使って環境を作るといったことが可能になります。

phases:
  install:
    commands:
      - npm install -g npm@6.12.0 aws-cdk@1.14.0

  build:
    commands:
    # ... docker build
      - npm ci
      - npm run build
      - cdk synth --app 'node index.js' > template.yml
artifacts:
  files:
    - template.yml

こちらのartifactsを使ってCloudFormationを立ち上げるといった具合です。 この際、例えばCodeBuildに対して特定の条件を分岐させるような環境変数を提供し、それをコード化することによって必要な環境をよしなに作ることができます。

const vpc = Vpc.fromVpcAttributes(
  this,
  'Vpc',
  {
    vpcId: 'vpc-8932jkdw',
    availabilityZones: ['ap-northeast-1a', 'ap-northeast-1c'],
    publicSubnetIds: ['subnet-8302sba', 'subnet-32890231'],
    privateSubnetIds: ['subnet-9042389fdjosa', 'subnet-re032109jifds']
  })
if(process.env.SOME_CONDITION){
  Cluster.fromClusterAttributes() //...ECSを立ち上げる
} else {
  Repository.fromRepositoryName() //... ECRからなにか取る
}

相性のいい環境

上記のようによしなに分岐ができるため、例えば特定の環境のみを再現したい場合の プレビュー環境 や、単純に日頃使い慣れている言語で、サクッと環境を作りたい サーバーサイドのエンジニア などがこの昨日を使うといいかもしれません。

相性が良くない環境

CDKは、 @aws-cdk/aws-ecs-patternsApplicationLoadBalancedFargateService など、便利な関数を呼び出す形で組み立てていきます。 型である程度中を推論できるものの、ハードコーディングされているオプションなどがあり、細かいチューニングには向かないかなと思いました。

Terraform

Use Infrastructure as Code to provision and manage any cloud, infrastructure, or service

https://www.terraform.io/

Terraformのサイトから引用したのですが、文字通り、AWS以外のクラウドインフラにおいて使えるIaCのツールとなります。

使用感

下記のように、独自のDSLを用いて記述していきます。 HashiCorpプラグインなどを使うことによって、設定できるパラメータが何があるかと言うのをある程度推論してくれます。

resource "aws_alb" "default" {
  name = local.alb_name

  security_groups = [
    aws_security_group.alb.id,
  ]

  subnets = data.aws_subnet_ids.main.ids
}

resource "aws_alb_listener" "http" {
  default_action {
    target_group_arn = aws_alb_target_group.default.arn
    type             = "forward"
  }

  protocol          = "HTTP"
  load_balancer_arn = aws_alb.default.arn
  port              = 80
}

実行は基本的にサイト上にあるバイナリファイルをダウンロードしてきて、それを実行していく形になります。

terraform plan # 仮実行
terraform apply # 本実行

実行後はstateというストレージに環境全体の情報が格納され、次回以降の実行時はこのstateと実際のインフラ環境、コードの状況を比較し、差分のみを実行するといったことを行ってくれます。

いいところ

一つ一つしっかり記述していくため、細かいチューニングができるのが特徴です。 また、バイナリの方でフォーマットのコマンドも用意されているため、基本だれが書いても同じようなコードになります。

また、importという機能があり、Webコンソールなどですでに構築されている環境の状態をstateに持ってくる機能があるため、試しにWebコンソールで環境を作ったあと、importでコード化するということができます。

相性のいい環境

基本的に運用し続ける環境で効果を発揮すると思われます。理由としては、

  • 差分適応による効率的な環境設定ができる点
  • import機能による、コード化の簡易性
  • インフラ構築に特化した、宣言型の実装

というところで、メンテナンスに適しているためです。 また、workspaceを用いることによって、本番環境と同等の環境を手軽に別の環境で作成することができるため、本番適応前の最終確認などが手軽に行えるといった点も評価できるポイントです

相性の悪い環境

すべてのパラメータを宣言的に書くという仕様上、あまり動的に変わるようなインフラ環境には向かないと思われます。 動的に変更したい場合はCDKを検討しましょう

まとめ

CDKとTerraformを比較してきました。それぞれ一長一短あると思いますが、よしなに使い分けていけるといいかなと思っております。

元記事

kotamat.com

lernaコマンドの標準出力が微妙な時はstreamオプションをつける

TL;DR タイトル

lernaはモノレポ管理下にあるpackage.jsonのコマンドを同時に実行することができる。

lerna run --scope s-* lint

とするとpackage.jsonのnameがs-で始まるすべてのワークスペースnpm run lintが実行される。(s-はオレオレパッケージのプレフィックスです)

f:id:apple19940820:20200120131838p:plain

モノレポではワークスペースでlintの設定を統一したり、ビルドのコマンドを統一したりするのでlernaのコマンド実行は便利。

しかし問題もなくはない。

標準出力がしょぼい

一部コマンドではルートのpackage.jsonにlernaコマンドを書いて実行すると、Warningになっていても標準出力に出ずsuccess!とだけ表示される。鬱なのでなんとかしたい。

lernaからの実行では一見パーフェクトに見えるが f:id:apple19940820:20200120125453p:plain

ワークスペースに潜ってyarn generateするとバンドルサイズ周りでwebpackが警告出していたという罠 f:id:apple19940820:20200120125604p:plain

--stream オプションをつけて子の標準出力をlernaに送る

runコマンドはオプションで--streamをつけると子プロセスの出力をストリーミングすることができる。各ワークスペースのコマンド実行ログがそのまま流れるため、lernaからの実行時でも重要な標準出力を見逃すことがない。

出力がごちゃ混ぜになってやばいみたいなこともない。

f:id:apple19940820:20200120130206p:plain

他にも並列実行のオプションや実行時のパフォーマンスを測定するオプションもある。需要に応じて使ってみると良いかもしれない。

元記事

ushirock.hateblo.jp

AWS Cloudfront をL7スイッチで使うならTerraform0.12 Dynamicが便利

こちらのブログは個人ブログと同じ内容です

kotamat.com

こんにちは、 kotamat です。

Terraform 0.12がでてしばらく立ちますが、先日構築したCloudfrontの環境において、Terraform0.12のdynamicを使わないと実現できない事案が発生したため、この際にと思い、一気に0.12にバージョンアップしました。

マイグレーション方法とかは、terraform側が提供しているものを使えば80%くらいはやってくれるので(一部手動で修正が必要ですが、WARNING出してくれるのでポチポチやっていくだけです。)今回は主題のdynamicについて紹介します。

Dynamicとは

terraform 0.12で追加されたシンタックスのうちの一つです。 通常、トップレベルのリソースに関しては、一つ一つのリソースに名前をつけて記述したり、 count とかを使って繰り返しを記述していきますが、security groupcloudfront のようなネストされたリソースを記述する際には一つ一つ重複して書く必要がありました。

特にcloudfrontは機能が多く、befaviorに各パスごとのoriginを書くことによってL7スイッチになることができるため、設定が繰り返し記述になりがちであり、かつネストが多い非常に複雑な構造になっているため、dynamicの恩恵を受けることができます。

記述方法

今回は

slides.com

で紹介した形を例に考えてみます。

各種スイッチの向き先の対照表は下記になっています。

ディレクト オリジンの向き先
/api/* API
/nova/* nova
/nova-assets/* nova
/nova-api/* nova
/vendor/nova/* nova
default S3(Nuxt.js)

今までであれば、/apiのブロックを一つ、novaのブロックを4つ、deafultのブロックを一つ書く必要がありましたが、dynamicを用いることによって、defaultとordered一つのブロックですべての表現をすることができます。

resource "aws_cloudfront_distribution" "main" {
// api
  ordered_cache_behavior {
    allowed_methods = [
      "GET",
      "HEAD",
      "OPTIONS",
      "POST",
      "PUT",
      "PATCH",
      "DELETE",
    ]

    cached_methods = [
      "GET",
      "HEAD",
    ]

    default_ttl = 0

    forwarded_values {
      cookies {
        forward = "all"
      }

      headers = ["*"]

      query_string = true
    }

    max_ttl                = 0
    min_ttl                = 0
    target_origin_id       = local.api_origin_id
    viewer_protocol_policy = "redirect-to-https"
    path_pattern           = "/api/*"
  }
// nova
  ordered_cache_behavior {
    allowed_methods = [
      "GET",
      "HEAD",
    ]

    cached_methods = [
      "GET",
      "HEAD",
    ]

    default_ttl = 0

    forwarded_values {
      cookies {
        forward = "all"
      }

      headers = ["*"]

      query_string = true
    }

    max_ttl                = 0
    min_ttl                = 0
    target_origin_id       = local.nova_origin_id
    viewer_protocol_policy = "redirect-to-https"
    path_pattern           = "/nova/*"
  }
// 以下 nova-assets/*とかも記述していく

locals {
  path_patterns = [
    "/api/*",
    "/nova/*",
    "/nova-assets/*",
  ]
}
resource "aws_cloudfront_distribution" "main" {
  dynamic "ordered_cache_behavior" {
    // 何を繰り返すのかをfor_eachで指定する
    for_each = locals.path_patterns
    content {
      allowed_methods = [
        "GET",
        "HEAD",
        "OPTIONS",
        "POST",
        "PUT",
        "PATCH",
        "DELETE",
      ]

      cached_methods = [
        "GET",
        "HEAD",
      ]

      default_ttl = 0

      forwarded_values {
        cookies {
          forward = "all"
        }

        headers = ["*"]

        query_string = true
      }

      max_ttl                = 0
      min_ttl                = 0
      target_origin_id       = local.api_origin_id
      viewer_protocol_policy = "redirect-to-https"
      path_pattern           = ordered_cache_behavior.value // dynamicで指定したリソース名 + .valueでpathの中身を取得
    }
  }
}

他の使いみち

上記はbehaviorの設定をまとめるために使ってみましたが、例えばステージング環境は特定のパスだけBasic認証いれたいという要件が来たときには、今までだとlambda_function_associationだけ違う別リソースを記述する必要がありましたが、mapと組み合わせる事によって、下記のように同一ソースで記述することができます。

locals {
  lambda_associations = {
    "stg": {
      // stg特有のもの(Basic認証とか)
      {
        event_type = "viewer-request"
        lambda_arn = module.viewer_request.qualified_arn
      },
      // prodと共通のもの
      {
        event_type = "origin-request"
        lambda_arn = module.origin_request.qualified_arn
      },
    },
    "prod": {
      {
        event_type = "origin-request"
        lambda_arn = module.origin_request.qualified_arn
      }
    }
  }
}

resource "aws_cloudfront_distribution" "main" {
  default_cache_behavior {
    dynamic "lambda_function_association" {
      for_each = local.lambda_associations[terraform.workspace] // workspaceの値を取得し、動的にassociationを切り替える
      content {
        event_type = lambda_function_association.value.event_type
        lambda_arn = lambda_function_association.value.lambda_arn
      }
    }
  }
}

まとめ

terraform 0.12 dyanmicの使い方を、Cluodfrontを用いて紹介させてもらいました。 特に後者の、lambda edgeとの組み合わせは非常に使い勝手がいいので、よかったら試してもらえればと思います。

macOS Catalinaでもphpenv使いたかったのでdefault_configure_optionsいじったりPHP_BUILD_CONFIGURE_OPTS指定したりした

techblog.roxx.co.jp の続きです。

前回はphpenv, php-buildを諦めてしまいましたがいろいろ設定を調査して再挑戦です。

github.com

default_configure_options を調整する

php-buildには default_configure_options というファイルがあり、こちらに configure で利用するオプションを記載しています。 $PHPENV_ROOT/plugins/php-build/share/php-build/default_configure_options をお好みのエディターで編集し、前回エラーが出ていたzlib, bzip2, iconv のオプションを調整します。

default_configure_optionsはこのようになりました。 gist.github.com

それぞれビルド用にHomebrewでインストールしておきましょう。

brew install bzip2 libiconv zlib

これで再度php7.2.22のインストールに挑戦してみましたが libedit を再インストールしろというエラーが起きてしまいました。くやしい。

$ phpenv install 7.2.22
[Info]: Loaded extension plugin
[Info]: Loaded apc Plugin.
[Info]: Loaded composer Plugin.
[Info]: Loaded github Plugin.
[Info]: Loaded uprofiler Plugin.
[Info]: Loaded xdebug Plugin.
[Info]: Loaded xhprof Plugin.
[Info]: Loaded zendopcache Plugin.
[Info]: php.ini-production gets used as php.ini
[Info]: Building 7.2.22 into /Users/jiska/.anyenv/envs/phpenv/versions/7.2.22
[Downloading]: https://secure.php.net/distributions/php-7.2.22.tar.bz2
(中略)

-----------------
|  BUILD ERROR  |
-----------------

Here are the last 10 lines from the log:

-----------------------------------------
configure: error: Please reinstall libedit - I cannot find readline.h
-----------------------------------------

とりあえずlibeditをインストールしてdefault_configure_options に --libedit を指定して再実行しましたが状況が変わりませんでした。

brew install libedit
$ phpenv install 7.2.22
(中略)

-----------------
|  BUILD ERROR  |
-----------------

Here are the last 10 lines from the log:

-----------------------------------------
configure: error: Please reinstall libedit - I cannot find readline.h
-----------------------------------------

くやしい。もうちょっと原因調べないとダメです。

PHP_BUILD_CONFIGURE_OPTS を指定する

php-build のコードを読んでいて気づいたのですが、macOSの場合は --libedit を上書きしてしまっているようです。

https://github.com/php-build/php-build/blob/a73138438907cc2cfefc8279102ccba234c85369/bin/php-build#L621-L627:該当箇所へのリンク

    if is_osx; then
        configure_option -D "--with-gettext"
        configure_option -D "--with-readline"
        configure_option "--with-libedit"

        configure_option -R "--with-png-dir" "/usr/X11"
    fi

そして、envに PHP_BUILD_CONFIGURE_OPTS を指定すればconfigureにオプションとして反映してくれるようです。

https://github.com/php-build/php-build/blob/a73138438907cc2cfefc8279102ccba234c85369/bin/php-build#L135:PHP_BUILD_CONFIGURE_OPTSが存在しなければ空文字で初期化している

[ -z "$PHP_BUILD_CONFIGURE_OPTS" ] && PHP_BUILD_CONFIGURE_OPTS=""

https://github.com/php-build/php-build/blob/a73138438907cc2cfefc8279102ccba234c85369/bin/php-build#L652:指定したoptionsは最終的にマージされる

    CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS $PHP_BUILD_CONFIGURE_OPTS $CONFIGURE_OPTS"

envにlibeditの設定を指定して再度実行してみたところ、ようやくbuildすることができました!ビルドを試したのは7.2.22, 7.2.23, 7.3.12 です。

# 7.2.22
$ PHP_BUILD_CONFIGURE_OPTS="--with-libedit=/usr/local/opt/libedit" phpenv install 7.2.22
(中略)
Generating phar.php
Generating phar.phar
PEAR package PHP_Archive not installed: generated phar will require PHP's phar extension be enabled.
directorytreeiterator.inc
clicommand.inc
directorygraphiterator.inc
invertedregexiterator.inc
pharcommand.inc
phar.inc

Build complete.
Don't forget to run 'make test'.
(中略)

[xdebug]: Installing xdebug configuration in /Users/jiska/.anyenv/envs/phpenv/versions/7.2.22/etc/conf.d/xdebug.ini
[xdebug]: Cleaning up.
[Info]: Enabling Opcache...
[Info]: Done
[Info]: The Log File is not empty, but the Build did not fail. Maybe just warnings got logged. You can review the log in /tmp/php-build.7.2.22.20191127024529.log or rebuild with '--verbose' option
[Success]: Built 7.2.22 successfully.

# 7.3.12 も大丈夫だった
PHP_BUILD_CONFIGURE_OPTS="--with-libedit=/usr/local/opt/libedit" phpenv install 7.3.12

まとめ

  • default_configure_optionsを調整しよう
  • ビルドに必要なものをインストールしておこう
    • brew install bzip2 libedit libiconv zlib
  • PHP_BUILD_CONFIGURE_OPTSに --with-libedit を追加してから phpenv install しよう
    • PHP_BUILD_CONFIGURE_OPTS="--with-libedit=/usr/local/opt/libedit" phpenv install 7.3.12

最後に

株式会社ROXXでは一緒に agent bankback check を作っていくメンバーを随時募集しています。 この記事を読んでROXXに興味を持ってくれた方はぜひご応募ください。   www.wantedly.com

MySQLのDockerイメージをイチから作成する

backcheck事業部の前田です。
この記事は個人ブログと同じ内容になります。

toyo.hatenablog.jp

わけあって、公式のものとは別にMySQLのDockerイメージを作成しました。
この記事はその備忘録です。

公式のイメージはデータ保存の部分がVOLUMEマウントされているので、イメージの中にデータを含められないからです。

公式イメージではデータを保存できない

Docker環境でMySQLを利用したい場合はMySQLの公式イメージを使用するのが一番ラクで便利です。
ですが、公式イメージではデータを保存しておくことができません。 MySQLではデータを /var/lib/mysql というディレクトリ内に保存するのですが、このディレクトリがVOLUMEマウントの対象になっているためです。

「データをdockerイメージに含めるのはおかしい」という話はもちろんあるのですが、それでも含めたいときがあるじゃないですか。。。というお話です。

試行錯誤してみた

自分で色々とやってみて、たどり着いたのが以下でした。

Dockerfile

FROM ubuntu:18.04

ENV TZ Asia/Tokyo
ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        mysql-server \
        tzdata \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# timezone
WORKDIR /opt
RUN mysql_tzinfo_to_sql /usr/share/zoneinfo > timezone.sql

# mysql
RUN mkdir -p /var/run/mysqld \
    && chown mysql:mysql /var/run/mysqld \
    && usermod -d /var/lib/mysql mysql \
    && mv /etc/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/00_mysql.cnf \
    && mv /etc/mysql/conf.d/mysqldump.cnf /etc/mysql/conf.d/01_mysqldump.cnf \
    && mv /etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/mysql.conf.d/00_mysqld.cnf \
    && mv /etc/mysql/mysql.conf.d/mysqld_safe_syslog.cnf /etc/mysql/mysql.conf.d/01_mysqld_safe_syslog.cnf \
    && sed -i -e "s~log_error~# log_error~g" /etc/mysql/mysql.conf.d/00_mysqld.cnf \
    && sed -i -e "s~bind_address~# bind_address~g" /etc/mysql/mysql.conf.d/00_mysqld.cnf

COPY original_mysql.cnf /etc/mysql/conf.d/02_original_mysql.cnf
COPY original_client.cnf /etc/mysql/conf.d/03_original_client.cnf
COPY original_mysqld.cnf /etc/mysql/mysql.conf.d/02_original_mysqld.cnf

COPY entrypoint.sh entrypoint.sh
RUN chmod +x entrypoint.sh

EXPOSE 3306

ENTRYPOINT ["./entrypoint.sh"]

entrypoint.sh

#!/bin/sh

set -e

find /var/lib/mysql -type f -exec touch {} \;

if [ ! -f timezone_applied ];then
  mysqld &
  sleep 3
  mysql -uroot -D mysql < timezone.sql
  mysqladmin shutdown -uroot
  touch timezone_applied
fi

mysqld

ひとつずつ解説していきます。

Dockerfile

FROM ubuntu:18.04

コンテナと言えばAlpine Linuxですが、Alpineのパッケージ管理ツール(apk)では、デフォルトではMySQLをインストールできません。
MySQL互換のMariaDBならインストールすることができますが、MariaDBは認証まわりがMySQLとだいぶ違うため、今回は見送りました。
Alpine以外なら何でも大丈夫だと思います。Ubuntuは私の趣味です。

ENV TZ Asia/Tokyo

TZ というタイムゾーンを指定することで、MySQLタイムゾーンの指定をすることができます。1

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        mysql-server \
        tzdata \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

MySQLとtzdataをインストールしています。
tzdataはタイムゾーンの情報が入ったパッケージです。
MySQLではデフォルトではタイムゾーンの情報が入っていないため、このtzdataをベースにタイゾーン情報を入れていきます。

また、tzdataをインストールする際に対話シェルが開始されます。
Dockerイメージをビルドする際に対話シェルが開始されると処理が止まってしまうので、 DEBIAN_FRONTEND を指定して対話シェルが開始されないようにしています。

apt使用後はaptに関するデータは不要になるので、最後に使用されていないパッケージや更新されたリポジトリリストを削除しています。

WORKDIR /opt

ここからはファイルを生成したりしていくので、ルートディレクトリから移動をしています。
どこでもいいので、とりあえず /opt に設定しています。

RUN mysql_tzinfo_to_sql /usr/share/zoneinfo > timezone.sql

先ほどインストールしたtzdataの情報をSQLに変換しています。
このSQLはentrypointで使用します。

RUN mkdir -p /var/run/mysqld \
    && chown mysql:mysql /var/run/mysqld \

MySQLのデーモンがpidファイルを作成するディレクトリを作成しています。
また、Dockerイメージをビルドする際は基本的にrootユーザなので、ディレクトリのオーナーもmysqlに変更しています。

    && usermod -d /var/lib/mysql mysql \

mysqlユーザのホームディレクトリが設定されていない場合、 No directory, logging in with HOME=/ というエラーが発生するので、ここでmysqlユーザのホームディレクトリを設定しています。

    && mv /etc/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/00_mysql.cnf \
    && mv /etc/mysql/conf.d/mysqldump.cnf /etc/mysql/conf.d/01_mysqldump.cnf \
    && mv /etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/mysql.conf.d/00_mysqld.cnf \
    && mv /etc/mysql/mysql.conf.d/mysqld_safe_syslog.cnf /etc/mysql/mysql.conf.d/01_mysqld_safe_syslog.cnf \

Debian系のLinuxでは、設定をひとつのmy.cnfに書かずに、それぞれの設定をファイルに分割して書きます。 その際にファイル名の順番で読み込まれていきます。
読み込まれる設定の順番を管理するために、デフォルトの設定ファイルに連番のプレフィックスを割り振っています。

ちなみに、 /etc/mysql/mysql.conf.d/ がデーモン寄りの設定が入っており、 /etc/mysql/conf.d/ にクライアント寄りの設定が入っているようです。

    && sed -i -e "s~log_error~# log_error~g" /etc/mysql/mysql.conf.d/00_mysqld.cnf \
    && sed -i -e "s~bind_address~# bind_address~g" /etc/mysql/mysql.conf.d/00_mysqld.cnf

デフォルトの設定のいくつかを無効化(コメントアウト)しています。
Dockerではログファイルにログを吐かれても仕方がないので、 log_error を無効にしています。
また、Dockerコンテナを使う以上、外部ネットワークからの接続を期待するので、 bind_address を無効にしています。

COPY original_mysql.cnf /etc/mysql/conf.d/02_original_mysql.cnf
COPY original_client.cnf /etc/mysql/conf.d/03_original_client.cnf
COPY original_mysqld.cnf /etc/mysql/mysql.conf.d/02_original_mysqld.cnf

自分の作成した設定ファイルをコピーしています。

COPY entrypoint.sh entrypoint.sh
RUN chmod +x entrypoint.sh

自分で作成したエントリーポイントを使用したいため、コピーしてきています。
また、実行可能にするために実行権限を付与しています。

EXPOSE 3306
ENTRYPOINT ["./entrypoint.sh"]

MySQLなので3306番ポートを開き、エントリーポイントを設定しています。

entrypoint.sh

#!/bin/sh
set -e

set -e を指定すると、エラー発生時にスクリプトを止めてくれます。

find /var/lib/mysql -type f -exec touch {} \;

Dockerのストレージエンジン(overlay2)とMySQLの相性が悪いらしく、そのままではCan’t open and lock privilege tables: Table storage engine for ‘user’ doesn’t have this optionというエラーが発生します。
このコメントを参考にさせていただきました。

if [ ! -f timezone_applied ];then

このあとの処理で、タイムゾーンの設定が完了した際に timezone_applied というファイルを作成しています。
つまり、タイムゾーン設定が完了していない場合にタイムゾーンの設定を行っています。

  mysqld &
  sleep 3

mysqldを起動し、起動が完了するまで待機しています。
3秒なのは手抜きです。本当は起動完了しているかどうかを何かしらの方法でチェックする必要があります。

  mysql -uroot -D mysql < timezone.sql

MySQLタイムゾーンのデータをインポートしています。
timezone.sql はDockerfile内で作成しました。

  mysqladmin shutdown -uroot
  touch timezone_applied
fi

mysqldを終了し、 timezone_applied ファイルを作成しています。
これで2回目以降はタイムゾーンの設定がスキップされます。

mysqld

MySQLを実行します。実質のentrypointです。

まとめ

自分でMySQLのイメージを作成してみました。
公式のMySQLのDockerfileがめちゃくちゃ勉強になります。
最初は全く読めないので、こちらの記事がとても参考になりました。