Aton-Kish / 複数言語環境を1つのDockerイメージにまとめる
Created 2020-11-05 15:09:23 Modified 2021-02-10

1317 Words

複数言語環境を 1 つの Docker イメージにまとめる

一般的にはサービスごとに Docker コンテナを起ち上げていくことが推奨されていますが、 使いたい言語環境全部入りのコンテナがあると便利な場合もあるので作ってみる

参考

まとめかた

言語のバージョンを管理しやすくするためにも、マルチステージビルドを利用して各言語の公式コンテナからバイナリなど必要なものだけを抽出する

では、抽出すべきデータはどうやって見つける…?

基本的には言語公式コンテナを実行してwhichやらls -lなどで辿っていく (または言語公式の Dockerfile を眺めてもいい?)

Node.js (Alpine) の例を挙げてみると…

  1. コンテナに入る

    docker run --rm -it node:15.0.1-alpine ash
    
  2. nodeyarnなどのコマンドの所在を探る

    which node
    
    # output
    /usr/local/bin/node
    
    • /usr/local/bin下にあることがわかる
  3. /usr/local/bin下を探る

    ls -l /usr/local/bin
    
    # output
    total 75184
    -rwxrwxr-x    1 root     root           116 Oct 23 17:20 docker-entrypoint.sh
    -rwxr-xr-x    1 root     root      76977616 Oct 21 21:17 node
    lrwxrwxrwx    1 root     root            19 Oct 23 17:20 nodejs -> /usr/local/bin/node
    lrwxrwxrwx    1 root     root            38 Oct 23 17:20 npm -> ../lib/node_modules/npm/bin/npm-cli.js
    lrwxrwxrwx    1 root     root            38 Oct 23 17:20 npx -> ../lib/node_modules/npm/bin/npx-cli.js
    lrwxrwxrwx    1 root     root            26 Oct 23 17:20 yarn -> /opt/yarn-v1.22.5/bin/yarn
    lrwxrwxrwx    1 root     root            29 Oct 23 17:20 yarnpkg -> /opt/yarn-v1.22.5/bin/yarnpkg
    
    CommandLinkLink Type
    node/usr/local/bin/nodeハードリンク
    nodejs/usr/local/bin/nodeシンボリックリンク
    npm/usr/local/lib/node_modules/npm/bin/npm-cli.jsシンボリックリンク
    npx/usr/local/lib/node_modules/npm/bin/npx-cli.jsシンボリックリンク
    yarn/opt/yarn-v1.22.5/bin/yarnシンボリックリンク
    yarnpkg/opt/yarn-v1.22.5/bin/yarnpkgシンボリックリンク
  • 抽出すべきデータは…
    • /usr/local/bin
    • /usr/local/lib/node_modules/npm
    • /opt/yarn-v1.22.5

言語別抽出方法

試してみた言語の抽出を書き出してみる(間違いがあるかもしれません)

Alpine 想定で書きますが、Debian でも同じ抽出の仕方で動作しました

Node.js

FROM node:15.0.1-alpine as node
FROM alpine

COPY --from=node /usr/local/bin /usr/local/bin
COPY --from=node /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/npm
COPY --from=node /opt/yarn* /opt/yarn
RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn && \
    ln -fs /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg
  • Yarn のディレクトリ名にバージョン番号が含まれてしまっている(/opt/yarn-vx.x.xみたいな感じ)ので、 COPYするときにopt/yarnに名前を変えている
  • opt/yarnのリネームの影響で Yarn のリンクが効かなくなっているのでシンボリックリンクを上書きしている

Python

FROM python:3.9.0-alpine as python
FROM alpine

# pip deps
RUN apk add --no-cache expat

COPY --from=python /usr/local/bin /usr/local/bin
COPY --from=python /usr/local/lib /usr/local/lib
COPY --from=python /usr/local/include /usr/local/include
  • pip がlibexpat.so.1に依存しているのでexpatをインストールしておく
    • pip インストールするパッケージによってはもっと依存があるので必要があれば追加インストールする

Go

FROM golang:1.15.3-alpine as golang
FROM alpine

COPY --from=golang /usr/local/go /usr/local/go
ENV PATH $PATH:/usr/local/go/bin/
  • /usr/local/go/bin/を環境変数を追加する必要あり

Rust

FROM rust:1.47.0-alpine as rust
FROM alpine

# Rust deps
RUN apk add --no-cache libc-dev gcc

COPY --from=rust /usr/local/cargo /usr/local/cargo
COPY --from=rust /usr/local/rustup /usr/local/rustup
ENV PATH $PATH:/usr/local/cargo/bin/
ENV RUSTUP_HOME /usr/local/rustup

ENV USER root
  • /usr/local/cargo/bin/を環境変数を追加する必要あり

  • Cargo 動作のために少なくとも環境変数RUSTUP_HOMEを設定する必要あり

  • Rust は環境変数USERがセットされていないと動作しないので設定すること

    • 一般ユーザをセットするには…

      # ...
      RUN adduser --disabled-password username
      ENV USER username
      

V

FROM vlang:0.1.29-alpine as vlang
FROM alpine

COPY --from=vlang /opt/vlang /opt/vlang
RUN ln -s /opt/vlang/v /usr/local/bin/v
  • Docker Hub に V 言語のイメージはないので公式 Github レポジトリの手順で事前に手動で生成する

    git clone https://github.com/vlang/v
    cd v
    docker build -t vlang:x.x.x-alpine --file=Dockerfile.alpine .
    
    • バージョンタグつけておくとよい
  • V 言語はroot権限でしか使いこなせないようになっているので、一般ユーザで使いたい場合はchown/opt/vlangの所有者を変えておくとよい

    # ...
    RUN chown -R user:user /opt/vlang && \
        ln -s /opt/vlang/v /usr/local/bin/v
    

All in One

ということで Node.js, Python, Go, Rust, V 全部入りのコンテナは…

FROM node:15.0.1-alpine as node
FROM python:3.9.0-alpine as python
FROM golang:1.15.3-alpine as golang
FROM rust:1.47.0-alpine as rust
FROM vlang:0.1.29-alpine as vlang
FROM alpine:3.12.0

RUN adduser --disabled-password user
ENV USER user

# clang, c++, and deps
RUN apk add --no-cache libc-dev gcc g++ make cmake tzdata

# nodejs
COPY --from=node /usr/local/bin /usr/local/bin
COPY --from=node /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/npm
COPY --from=node /opt/yarn* /opt/yarn
RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn && \
    ln -fs /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg

# python
COPY --from=python /usr/local/bin /usr/local/bin
COPY --from=python /usr/local/lib /usr/local/lib
COPY --from=python /usr/local/include /usr/local/include
RUN pip install pipenv

# golang
COPY --from=golang /usr/local/go /usr/local/go
ENV PATH $PATH:/usr/local/go/bin/

# rust
COPY --from=rust /usr/local/cargo /usr/local/cargo
COPY --from=rust /usr/local/rustup /usr/local/rustup
ENV PATH $PATH:/usr/local/cargo/bin/
ENV RUSTUP_HOME /usr/local/rustup

# vlang
COPY --from=vlang /opt/vlang /opt/vlang
RUN chown -R user:user /opt/vlang && \
    ln -s /opt/vlang/v /usr/local/bin/v

USER user
WORKDIR /home/user