Docker with GoのMulti Stage Buildで"no such file or directory"と出てハマった話

対象のDockerfile。特に特殊なことは行っていない素朴なDockerfile。

FROM golang:1.14.4 as builder

WORKDIR /app
COPY . .
RUN go mod download
RUN go build path/to/main.go

FROM alpine
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/path/to/main /app/main
EXPOSE 5000
ENTRYPOINT [ "/app/main" ]

build log

$ docker build .
Sending build context to Docker daemon  24.16MB
Step 1/10 : FROM golang:1.14.4 as builder
 ---> 00d970a31ef2
Step 2/10 : WORKDIR /app
 ---> Using cache
 ---> 290d933fcd92
Step 3/10 : COPY . .
 ---> 5c338d20e119
Step 4/10 : RUN go mod download
 ---> Running in fa40233a9ad4
Removing intermediate container fa40233a9ad4
 ---> 32ededdd590c
Step 5/10 : RUN go build cmd/app/main.go
 ---> Running in 715374ae5931
Removing intermediate container 715374ae5931
 ---> 0df4e6ac142b
Step 6/10 : FROM alpine
 ---> a24bb4013296
Step 7/10 : RUN apk add --no-cache ca-certificates
 ---> Using cache
 ---> 63a365fab492
Step 8/10 : COPY --from=builder /app/main /app/main
 ---> 262cef1fcd0f
Step 9/10 : EXPOSE 5000
 ---> Running in d2cc58f47b7f
Removing intermediate container d2cc58f47b7f
 ---> 49e6a57ba3bd
Step 10/10 : ENTRYPOINT [ "/app/main" ]
 ---> Running in 887cc0dbb3a8
Removing intermediate container 887cc0dbb3a8
 ---> d5c6b638a8a7
Successfully built d5c6b638a8a7

buildしたimageを実行すると exec user process caused "no such file or directory" と出て実行が出来ない。

$ docker run -it d5c6b638a8a7
standard_init_linux.go:211: exec user process caused "no such file or directory"

Docker公式のDocumentを眺めると CGO_ENABLED=0 という変数を指定している。

docs.docker.com

CGO_ENABLEについて公式のDocumentに書いてある。

golang.org

The cgo tool is enabled by default for native builds on systems where it is expected to work. It is disabled by default when cross-compiling. > You can control this by setting the CGO_ENABLED environment variable when running the go tool: set it to 1 to enable the use of cgo, and to > 0 to disable it. The go tool will set the build constraint "cgo" if cgo is enabled. The special import "C" implies the "cgo" build constraint, as > > though the file also said "// +build cgo". Therefore, if cgo is disabled, files that import "C" will not be built by the go tool. (For more about >build constraints see https://golang.org/pkg/go/build/#hdr-Build_Constraints).

今回のMulti Stage Buildのようなビルド環境と実行環境が違うクロスコンパイル時には CGO_ENABLE=0 にする必要があるとのこと。go envで見てみると案の定 CGO_ENABLE=1 となっている。

Step 6/11 : RUN go env
 ---> Running in f2d4bf833350
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/app/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build835780014=/tmp/go-build -gno-record-gcc-switches"

Dockerfileのbuild箇所に CGO_ENABLE=0 を指定することで正常にビルドが出来ました。

FROM golang:1.14.4 as builder

WORKDIR /app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 go build path/to/main.go
RUN go env

FROM alpine
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/path/to/main /app/main
EXPOSE 5000
ENTRYPOINT [ "/app/main" ]
$ docker build .
Sending build context to Docker daemon  24.16MB
Step 1/11 : FROM golang:1.14.4 as builder
 ---> 00d970a31ef2
Step 2/11 : WORKDIR /app
 ---> Using cache
 ---> 290d933fcd92
Step 3/11 : COPY . .
 ---> 1e985aa7595b
Step 4/11 : RUN go mod download
 ---> Running in 4f47920a2f07
Removing intermediate container 4f47920a2f07
 ---> b3af89cc0fae
Step 5/11 : RUN CGO_ENABLED=0 go build cmd/app/main.go
 ---> Running in 5418122649bb
Removing intermediate container 5418122649bb
 ---> a22ffab8bc7c
Step 6/11 : RUN go env
 ---> Running in f2d4bf833350
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/app/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build835780014=/tmp/go-build -gno-record-gcc-switches"
Removing intermediate container f2d4bf833350
 ---> 658a897f518b
Step 7/11 : FROM alpine
 ---> a24bb4013296
Step 8/11 : RUN apk add --no-cache ca-certificates
 ---> Using cache
 ---> 63a365fab492
Step 9/11 : COPY --from=builder /app/main /app/main
 ---> 4aea7dfd10cd
Step 10/11 : EXPOSE 5000
 ---> Running in 17d50b507678
Removing intermediate container 17d50b507678
 ---> e35b39616e48
Step 11/11 : ENTRYPOINT [ "/app/main" ]
 ---> Running in 2a03dd0d5636
Removing intermediate container 2a03dd0d5636
 ---> 7f1b74a479da
Successfully built 7f1b74a479da
$ docker run -it 7f1b74a479da

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.1.16
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on 127.0.0.1:5000

ref

qiita.com docs.docker.com golang.org github.com

ポートフォリオサイトをGatsby.jsからNext.jsに移行した。

PR

github.com

動機

Next.jsが今後伸びていくと踏んで今のうちに技術投資をしておきたいと思い移行をしました。
Next.jsにはstatic HTML exportという機能があり、Gatsby.jsと同じ様に静的サイトを作れるのでキャッチアップにちょうど良かったという事情もありました。

nextjs.org

Nuxt.jsに投資するかも悩みましたが、

  1. Vue.jsよりReact.jsに慣れている
  2. TypeScript Full Support

という点もあり、Next.jsにしました。

移行で困った点

PRを見てもらえればわかると思うのですが、1枚だけのページという事もあって困るような事に遭遇することすらありませんでした。
styled-componentを使っていると大変という話を聞きましたが、使っていなかったこともあり、特につまずくようなところは無かったです。逆につまずいて知見を得たかったので残念

所管

Reactで静的なサイトを作る場合はGatsby.js以外にもNext.jsがあるよというお話でした。Next.jsの知見を利用出来る&貯めることが出来るので、今後静的なサイトを作る場合はNext.jsでやろうと思います。

Next.jsでfaviconを設定する

PR

github.com

Projectの構成はこのような形

$ tree src/pages public
src/pages
├── _app.tsx
├── _document.tsx
└── index.tsx
public
└── static
    └── favicon
        ├── android-chrome-192x192.png
        ├── android-chrome-512x512.png
        ├── apple-touch-icon.png
        ├── browserconfig.xml
        ├── favicon-16x16.png
        ├── favicon-32x32.png
        ├── favicon.ico
        ├── mstile-150x150.png
        └── site.webmanifest

1. publicディレクトリし、画像を置く

project直下にpublicというディレクトリを作成してfaviconの画像ファイルを置いてください。 以前はstaticというディレクトリ名でしたが、非推奨になりました。

github.com

2. pages/_document.tsxを作成し、link tagを追加する

nextjs.org

import Document, { Head, Main, NextScript } from "next/document";

export default class MyDocument extends Document {
  render() {
    return (
      <html>
        <Head>
          <link
            rel="apple-touch-icon"
            sizes="180x180"
            href="static/favicon/apple-touch-icon.png"
          />
          <link
            rel="icon"
            type="image/png"
            sizes="32x32"
            href="static/favicon/favicon-32x32.png"
          />
          <link
            rel="icon"
            type="image/png"
            sizes="16x16"
            href="static/favicon/favicon-16x16.png"
          />
          <link rel="manifest" href="static/favion/site.webmanifest" />
          <meta name="msapplication-TileColor" content="#da532c" />
          <meta name="theme-color" content="#ffffff" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

3. build and exportして確認する

$ npm run build && npm run export

Next.jsでSVG画像をReact Componentとして描画する

@svgr/webpackを利用する。

react-svgr.com

npm install @svgr/webpack --save-dev

# or use yarn

yarn add @svgr/webpack --dev

next.config.js

module.exports = {
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/,
      issuer: {
        test: /\.(js|ts)x?$/,
      },
      use: ["@svgr/webpack"],
    });

    return config;
  },
};

github.com

react-svg-loaderを利用する

github.com

npm install react-svg-loader --save-dev

# or 

yarn add react-svg-loader --dev

next.config.js

const path = require("path");

module.exports = {
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/,
      include: [path.resolve(path.resolve(), "src/images")],
      use: ["react-svg-loader"],
    });

    return config;
  },
};

個人的には@svgr/webpackのほうがスマートに見えるのでそっちを利用する。お好みで。

TerraformでAWS LambdaLayer with Python を利用する

ほぼclassmethodさんの記事通りですが

ソースコード

github.com

$ tree
.
├── README.md
├── build-layer.sh
├── iam.tf
├── lambda
│   ├── function.zip
│   └── lib.zip
├── lambda.tf
├── provider.tf
├── requirements.txt
├── src
│   └── index.py
├── terraform.tfstate
├── terraform.tfstate.backup
└── variables.tf

DeployするLambda functionのソースコード

import os
import sys
import requests
from bs4 import BeautifulSoup


def lambda_handler(event: dict, context):
    response = requests.get("http://example.com/")
    bs = BeautifulSoup(response.text)
    return bs.title.name

lambda functionでrequestsとBeautifulSoupを使いたい。そのためにlambda layerを利用する。

Lambda Layer

見るのはlambda.tfとbuild-layer.sh

data "archive_file" "function" {
  type        = "zip"
  source_file = "./src/index.py"
  output_path = "lambda/function.zip"
}

data "archive_file" "layer" {
  type        = "zip"
  source_dir  = "./lib"
  output_path = "lambda/lib.zip"
}

resource "aws_lambda_layer_version" "layer" {
  filename         = "${data.archive_file.layer.output_path}"
  layer_name       = "lib"
  source_code_hash = "${data.archive_file.layer.output_base64sha256}"

  compatible_runtimes = ["python3.6"]
}

resource "aws_lambda_function" "example_lambda" {
  function_name = "example_lambda"

  runtime          = "python3.6"
  handler          = "index.lambda_handler"
  filename         = "${data.archive_file.function.output_path}"
  role             = "${aws_iam_role.iam_role.arn}"
  source_code_hash = "${data.archive_file.function.output_base64sha256}"
  layers           = ["${aws_lambda_layer_version.layer.arn}"]
}

肝になるのはlambda functionとlayerのsourceの参照先の違い。

lambda functionのソースコードproject/src/index.pyを利用するが、 lambda layerは lib/ を利用する。pipライブラリのinstallについてはnull_resourceを利用してterraformで完結しようとしたが、うまく出来なかったので、shellファイルで実行する。何か良い方法あれば教えてほしい

#!/usr/bin/env bash

if [ -d lib/ ]; then
  rm -rf lib/
fi

echo "bundle pypi packages"
pip install -r requirements.txt -t lib/python

find lib -type f | grep -E "(__pycache__|\.pyc|\.pyo$)" | xargs rm

pip installで lib/python を指定しているのはlambda functionからlambda layerを参照するpathを通すため。lib/packagesなどではpathが通らないので注意。 __pycache__と.pyc、pyoを削除しているが、これはpip installをしても__pycache__配下のファイルの影響で差分が発生してしまうから。

$ sh build-layer.sh
$ terraform plan
$ terraform apply

参考