20200227のdockerに関する記事は10件です。

DockleやTrivyをCircle CIのDocker Orbと組み合わせてセキュアなコンテナを継続的に作るっ!

概要

Circle CIの「circleci/docker」というOrbを使って、以下を実践してみました!ʕ◔ϖ◔ʔ

  • コンテナのビルド
  • DockleTrivyを使って、ビルドしたコンテナイメージをチェック
    • OSや依存パッケージが脆弱性を含んでいないかをチェック
    • ベストプラクティスに準拠したセキュアなコンテナになっているかをチェック
  • 上記チェック結果がPassなら、docker hubにコンテナイメージを登録

上述のサンプルを紹介しつつ、設定ファイル.circleci/config.ymlを書く際のポイントなどを説明します。この記事を読み終わった時に、あなたにとってセキュアなコンテナのビルドがより身近なものになっていたら嬉しいなぁと思います!

ゆっくり見ていってね!(o´・з・)o

読んでほしい人

  • DockleTrivyといったコンテナチェックツールに興味がある方
  • CIでのコンテナチェックに興味がある方
  • CircleCIのOrbという仕組みに興味がある方
  • CircleCIを含むCI/CDに知見がある方(マサカリ、お願いします!m(_ _)m)

etc..

作ったもの

まずは、実践した内容の雰囲気を掴んでいただくため、実際に作ったサンプルをごらんください。٩(ˊᗜˋ*)و

Dockle on CircleCI

Trivy on CircleCI

準備

ログイン

まずはCircleCIdocker hubにログインできるようにしましょう。

リントツールを使って手元でチェック

ケモケモ「よっしゃ出来た!CircleCIさん、がっつりビルド頼むわ!!」
CircleCI「記法がinvalidやで。Failやで。」
スクリーンショット 2020-02-20 20.45.16.png
ケモケモ「Oh... (´・ω・`)」

せっかく書いた.circleci/config.ymlが、記法の間違いなどビルドできないと辛いですね。そういった問題を未然に防ぐため、コミットする前にcircleciツールでチェックしちゃいましょうヽ(=´▽`=)ノ。

まずはインストール!

以下のコマンドで.circleci/config.ymlが動きそうかチェックします。

circleci config validate

詳しくは「CircleCI のコンフィグのバリデーション」をご覧ください。

Dockleをインストール

CIで継続的に実行するのが本記事の目的ではありますが、便利なツールですし手元でコミット前にチェックするのは良い習慣ですのでインストールしましょう。

公式の Installation で各種プラットフォーム向けのインストール方法が書かれているので、好きな方法でインストールします。

使い方も簡単で、チェックしたいコンテナイメージの名前を指定して実行するだけです。めちゃシンプル!

$ dockle {YOUR_IMAGE_NAME}

チェックしてくれるポイントは Checkpoint Summary にまとまってます。

Trivyをインストール

こちらもやはり手元で実行できるようにインストールしましょう。先のDockle同様にGo言語製のツールですので、非常に多くのプラットフォームで実行可能です。

公式の Installation を見ながらインストールします。
簡単に使えます。シンプルです。

$ trivy {YOUR_IMAGE_NAME}

実行結果のサンプルが Examples に多数掲載されているのでチェキです。:eyes:

Orb選び

この記事のサブテーマとしてCircle CIのOrbを積極的に使います。まずは、目的にあったOrbを CircleCI Orb Registry で探してみましょう。

スクリーンショット 2020-02-20 23.10.33.png

今回はコンテナのビルドとプッシュをしたいのでdockerで検索してみました(上図)。CERTIFIEDマークのcircleci/dockerを使ってみましょう。

Orb: circleci/docker

https://circleci.com/orbs/registry/orb/circleci/docker
スクリーンショット 2020-02-20 23.13.19.png

Orbの説明ページでは、Orbの機能を呼び出すための方法について以下の情報が記載されています。

  • 冒頭のクイックスタートガイドで、.circleci/config.ymlにどう書けば使い始められるか説明してる
  • 左のカラムに並ぶUsage Examplesで、需要が高そうな使い方に対してjobやworkflowの書き方を実例と共に説明してる
  • 左のカラムに並ぶJobsで、job名やパラメータの指定方法を説明してる
  • 左のカラムに並ぶCommandsで、command名やパラメータの指定方法を説明してる

クイックスタートガイド

以下を書くだけでOrbの力を使えるようになります。パネェ₍₍ (ง ˙ω˙)ว ⁾⁾

version: 2.1

orbs:
  docker: circleci/docker@0.5.20

Usage Examples

比較的シンプルな、以下のサンプルが参考になります。

  • standard-build-and-push
    • Orbの定義済みのjobであるpublishを使ったサンプル。
    • コンテナのbuildとpushをまとめて実施してくれる。便利。
  • build-push-digest
    • Orbの定義済みのcommandであるcheckbuildを使ったサンプル。
    • docker.hubへのログインチェックや、build、pushなど、順を追って実施したい時に参考になる。

今回は、上記build-push-digestをベースに作っていきます。

Executor

Orbの実態は、CI/CDにすぐ組み込んで便利に使えるようCircleCIやパートナー企業が作ってくれたカスタムコンテナイメージです。

では、このcircleci/dockerのベースイメージはどんなものなのでしょう?今回のサンプルで使う「Executor - docker」でSourceを確認することでわかります。

スクリーンショット 2020-02-21 0.15.53.png

circleci/python 」を使っているようです。このイメージを作るDockerfileは「 CircleCI-Public / circleci-dockerfiles 」にあります。追加で依存パッケージなどを使いたい場合、コンテナの素性が関係してくる場合があるので心の片隅に置いておきます。

書き始めよう

かなり入念に、CircleCIの設定を書くための準備とOrbの機能を確認しました。そろそろ実際にconfig.ymlを書き始めましょう。

まずはOrbだけで書いてみる

まずはOrbの機能だけでCIの骨子を組み立てましょう。Orbの各種機能を呼び出す方法を整理すると、以下のような感じです。

上述のサンプルなどをベースにザクッと書いてみたのが以下です。

version: 2.1

orbs:
  docker: circleci/docker@0.5.20

jobs:
  # ビルドしたりテストしたりするジョブ。
  build-and-test:
    executor: docker/docker
    steps:
      - setup_remote_docker
      - checkout
      # コンテナレジストリにログイン可能かチェックします。
      - docker/check

      # コンテナをビルドします。
      - docker/build:
          image: ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}

      # ! DockleとTrivyを使ったコンテナのtestを書く予定地です。

workflows:
  # ビルドしたりテストしたり、masterにマージされたらlatestコンテナを更新するワークフロー。
  build-and-deploy:
    jobs:
      # masterを含む全てのブランチで実行するジョブ。
      - build-and-test

      # masterでだけ実行されるジョブ。latestのコンテナイメージをレジストリに登録、更新する。
      - docker/publish:
          image: ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}
          tag: 'latest'
          requires:
            - build-and-test
          filters:
            branches:
              only: master

  # gitのタグがついたらビルドとテストをして、タグ付きのコンテナをレジストリに登録するワークフロー。
  deploy-tags:
    jobs:
      - build-and-test:
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^v.*/
      - docker/publish:
          image: ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}
          tag: ${CIRCLE_TAG}
          # 「build-and-test」のジョブが成功した時だけ、このジョブを実行するよ。
          requires:
            - build-and-test
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^v.*/

なお、commandのdocker/checkは、イメージをpushするコンテナレジストリ(標準でdocker hub)にログイン可能かをチェックしてくれます。ログイン情報は、あらかじめ環境変数としてCircle CIに登録しておきます。ジョブの実行結果のログでは、ちゃんと伏せ字になりますので安心です。詳しくは プロジェクト内で環境変数を設定する を参照してください。

DockleTrivyをCIに組み込む

実はDockleTrivyも、どちらも公式でCircleCIの実行サンプルを公開しています。

これらのサンプルを上述のOrbベースのCI設定に組み込みます。その際、ツールのインストールを行う箇所がそのままでは使えません。circleci/dockerのOrbとは、executorとして使っているコンテナイメージのOSが異なるためです。それらの違いも踏まえて実際に動作することを確認した設定内容が以下です。

version: 2.1

orbs:
  docker: circleci/docker@0.5.20

jobs:
  build-and-test:
    executor: docker/docker
    steps:
      - setup_remote_docker
      - checkout
      - docker/check
      - docker/build:
          image: ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}

      # (追加)Dockleをインストールしてスキャン実行!
      - run:
          name: Install dockle
          command: |
             VERSION=$(
               curl --silent "https://api.github.com/repos/goodwithtech/dockle/releases/latest" | \
               grep '"tag_name":' | \
               sed -E 's/.*"v([^"]+)".*/\1/'
             )
             wget https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.tar.gz
             tar zxvf dockle_${VERSION}_Linux-64bit.tar.gz
             sudo mv dockle /usr/local/bin
      - run:
          name: Scan the image with dockle
          command: dockle --exit-code 1 ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}:${CIRCLE_SHA1}

      # (追加)Trivyをインストールしてスキャン実行!
      - run:
          name: Install trivy
          command: |
            VERSION=$(
                curl --silent "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | \
                grep '"tag_name":' | \
                sed -E 's/.*"v([^"]+)".*/\1/'
            )
            wget https://github.com/aquasecurity/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz
            tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz
            sudo mv trivy /usr/local/bin
      - run:
          name: Scan the image with trivy
          command: trivy --exit-code 0 --no-progress ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}:${CIRCLE_SHA1}

workflows:
  build-and-deploy:
    jobs:
      - build-and-test
(後略)

実際に動かしてみるとこんな感じになります。

スクリーンショット 2020-02-27 21.36.15.png

良いですね、バッチリチェックできています。:tada:
DockleTrivy両方を使ったconfig.ymlの完成形は こちら を御覧ください。

まとめ

CIを使ったコンテナの作成とレジストリへの登録は、Kubernetesクラスタへのデプロイを想定したGitOpsなどで必須となる重要なポイントだと感じています。これらの技術をProduction環境へ適用することを考えるならば、使用するコンテナ自体の脆弱性チェックはもはや避けては通れない課題です。

今回は、極力余計なものは省いて「いかに楽して脆弱性チェックの機構をCIに取り込むか」という点を意識して実践したつもりです。本記事はCircle CIを使った例でしたが、DockleTrivyの公式ドキュメントにもあるように Travis CI など多数のCI環境で同様の「コンテナ脆弱性チェック」の仕組みを実現することが可能です。

この記事が、コンテナのセキュリティを意識し始めた私のような方にとって、少しでも役立つものになっていることを願っていますʕ◔ϖ◔ʔ

おまけ

Circle CI関連の資料ばかりなのですが、有益だなぁと思ったリンクを載っけときます(ΦωΦ)

  • セキュリティ機能

    • Circle CIのセキュリティに関する取り組みについて書いてあります。セキュリティを懸念する上司やチームメンバーに説明する際の資料として良さそう。
  • 定義済み環境変数

    • Circle CIのジョブで使える定義済みの環境変数。リポジトリ名やタグ名など、比較的よく使いそうな情報が多数揃ってる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンテナ上のpython27/pyodbcでホストのSQL Serverにアクセスする

概要

先日の以下の記事でmysqlにアクセスする方法をご紹介しましたが、
コンテナ上のpython3/mysql-connector-pythonでホストのmysqlにアクセスする
SQL serverバージョンをご紹介します。

前提

ホストSQL Server

ODBC Driver 17 for SQL Server
SQLServer認証でポート1443で動作している

手順

ODBCが動くDockerコンテナの作り方の記事内にある以下のリポジトリをcloneします。
https://github.com/Microsoft/mssql-docker/tree/master/oss-drivers/pyodbc

> git clone https://github.com/microsoft/mssql-docker.git
> cd mssql-docker/oss-drivers/pyodbc

以下のツリーになっていると思います。

pyodbc
├── Dockerfile
├── README.md
├── docker-compose.yml *docker-composeで立ち上げるために追加
├── entrypoint.sh
└── sample.py *編集します。もしくは別ファイルを作ってもよいです。touch入ってませんが...
Dockerfile
# mssql-python-pyodbc
# Python runtime with pyodbc to connect to SQL Server
FROM ubuntu:16.04

# apt-get and system utilities
RUN apt-get update && apt-get install -y \
    curl apt-utils apt-transport-https debconf-utils gcc build-essential g++-5\
    && rm -rf /var/lib/apt/lists/*

# adding custom MS repository
RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list

# install SQL Server drivers
RUN apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql unixodbc-dev

# install SQL Server tools
RUN apt-get update && ACCEPT_EULA=Y apt-get install -y mssql-tools
RUN echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
RUN /bin/bash -c "source ~/.bashrc"

# python libraries
RUN apt-get update && apt-get install -y \
    python-pip python-dev python-setuptools \
    --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

# install necessary locales
RUN apt-get update && apt-get install -y locales \
    && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \
    && locale-gen
RUN pip install --upgrade pip

# install SQL Server Python SQL Server connector module - pyodbc
RUN pip install pyodbc

# install additional utilities
RUN apt-get update && apt-get install gettext nano vim -y

# add sample code
RUN mkdir /sample
ADD . /sample
WORKDIR /sample

CMD /bin/bash ./entrypoint.sh
docker-compose.yaml
version: '3'
services:
  pyodbc:
    restart: always
    build: .
    container_name: 'pyodbc'
    working_dir: '/sample'
    tty: true
    volumes:
      - .:/sample
    extra_hosts:
      - "(ホストのCOMPUTERNAME):(ホストのIPアドレス)"
    ports:
      - 1443:1443
sample.py
import pyodbc


server = '(ホストのIPアドレス),(ホストのSQLServer動作ポート)'
username = 'sa'
password = 'password'

cnxn = pyodbc.connect('DRIVER={ODBC Driver 17 for SQL Server};SERVER='+server+';database=(データベース名);UID='+username+';PWD='+password)
cursor = cnxn.cursor()

print ('Using the following SQL Server version:')
tsql = "SELECT @@version;"
with cursor.execute(tsql):
    rows = cursor.fetchall()
    for row in rows:
        print(str(row))

> docker-compose up -d --build
...
> docker container ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
2c51574bfd36        pyodbc_pyodbc       "/bin/sh -c '/bin/ba…"   51 minutes ago      Up 51 minutes       0.0.0.0:1443->1443/tcp   pyodbc
> docker exec -it 2c51574bfd36 /bin/bash
$ python --version
Python 2.7.12
$ python sample.py
Using the following SQL Server version:
(u'Microsoft SQL Server 2017 (RTM) - 14.0.1000.169 (X64) \n\tAug 22 2017 17:04:49 \n\tCopyright (C) 2017 Microsoft Corporation\n\tExpress Edition (64-bit) on Windows 10 Pro 10.0 <X64> (Build 18362: ) (Hypervisor)\n', )

結果

無事SQL ServerにアクセスしDB参照できました。

後日談(2/28追記内容)

ヒアドキュメントを文字列代入した時にエラー発生

変更内容

sample.py
tsql = '''\
    SELECT *
FROM
    ~
INNER JOIN
    ~
ON
'''

エラー内容

$ python sample.py 
  File "sample.py", line 17
SyntaxError: Non-ASCII character '\xe7' in file sample.py on line 18, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

  File "sample.py", line 54, in <module>
    with cursor.execute(tsql):
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7 in position 48: ordinal not in range(128)

試行した手順

以下の記事で解決しましませんでした。
http://shu223.hatenablog.com/entry/20111201/1328334689

以下の記事で解決しませんでした。
https://qiita.com/chatrate/items/eb4b05cd1a6652529fd9

root@2c51574bfd36:/sample# python -c "import site; print (site.getsitepackages())"
['/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages']

解決した手順

以下を参考にしたら解決しました。
https://kaworu.jpn.org/python/Python%E3%81%AEUnicodeDecodeError%E3%81%AE%E5%AF%BE%E5%87%A6%E6%96%B9%E6%B3%95
with cursor.execute(tsql.decode('utf-8')):

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンテナ上のpython/pyodbcでホストのSQL Serverにアクセスする

概要

先日の以下の記事でmysqlにアクセスする方法をご紹介しましたが、
dockerコンテナ内からホストのmysqlにアクセスする
SQL serverバージョンをご紹介します。

前提

ホストSQL Server

ODBC Driver 17 for SQL Server
SQLServer認証でポート1443で動作している

手順

ODBCが動くDockerコンテナの作り方の記事内にある以下のリポジトリをcloneします。
https://github.com/Microsoft/mssql-docker/tree/master/oss-drivers/pyodbc

> git clone https://github.com/microsoft/mssql-docker.git
> cd mssql-docker/oss-drivers/pyodbc

以下のツリーになっていると思います。

pyodbc
├── Dockerfile
├── README.md
├── docker-compose.yml *docker-composeで立ち上げるために追加
├── entrypoint.sh
└── sample.py *編集します。もしくは別ファイルを作ってもよいです。touch入ってませんが...
Dockerfile
# mssql-python-pyodbc
# Python runtime with pyodbc to connect to SQL Server
FROM ubuntu:16.04

# apt-get and system utilities
RUN apt-get update && apt-get install -y \
    curl apt-utils apt-transport-https debconf-utils gcc build-essential g++-5\
    && rm -rf /var/lib/apt/lists/*

# adding custom MS repository
RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list

# install SQL Server drivers
RUN apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql unixodbc-dev

# install SQL Server tools
RUN apt-get update && ACCEPT_EULA=Y apt-get install -y mssql-tools
RUN echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
RUN /bin/bash -c "source ~/.bashrc"

# python libraries
RUN apt-get update && apt-get install -y \
    python-pip python-dev python-setuptools \
    --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

# install necessary locales
RUN apt-get update && apt-get install -y locales \
    && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \
    && locale-gen
RUN pip install --upgrade pip

# install SQL Server Python SQL Server connector module - pyodbc
RUN pip install pyodbc

# install additional utilities
RUN apt-get update && apt-get install gettext nano vim -y

# add sample code
RUN mkdir /sample
ADD . /sample
WORKDIR /sample

CMD /bin/bash ./entrypoint.sh
docker-compose.yaml
version: '3'
services:
  pyodbc:
    restart: always
    build: .
    container_name: 'pyodbc'
    working_dir: '/sample'
    tty: true
    volumes:
      - .:/sample
    extra_hosts:
      - "(ホストのCOMPUTERNAME):(ホストのIPアドレス)"
    ports:
      - 1443:1443
sample.py
import pyodbc


server = '(ホストのIPアドレス),(ホストのSQLServer動作ポート)'
username = 'sa'
password = 'password'

cnxn = pyodbc.connect('DRIVER={ODBC Driver 17 for SQL Server};SERVER='+server+';database=(データベース名);UID='+username+';PWD='+password)
cursor = cnxn.cursor()

print ('Using the following SQL Server version:')
tsql = "SELECT @@version;"
with cursor.execute(tsql):
    rows = cursor.fetchall()
    for row in rows:
        print(str(row))

> docker-compose up -d --build
...
> docker container ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
2c51574bfd36        pyodbc_pyodbc       "/bin/sh -c '/bin/ba…"   51 minutes ago      Up 51 minutes       0.0.0.0:1443->1443/tcp   pyodbc
> docker exec -it 2c51574bfd36 /bin/bash
$ python --version
Python 2.7.12
$ python sample.py
Using the following SQL Server version:
(u'Microsoft SQL Server 2017 (RTM) - 14.0.1000.169 (X64) \n\tAug 22 2017 17:04:49 \n\tCopyright (C) 2017 Microsoft Corporation\n\tExpress Edition (64-bit) on Windows 10 Pro 10.0 <X64> (Build 18362: ) (Hypervisor)\n', )

結果

無事SQL ServerにアクセスしDB参照できました。

後日談(2/28追記内容)

ヒアドキュメントを文字列代入した時にエラー発生

変更内容

sample.py
tsql = '''\
    SELECT *
FROM
    ~
INNER JOIN
    ~
ON
'''

エラー内容

$ python sample.py 
  File "sample.py", line 17
SyntaxError: Non-ASCII character '\xe7' in file sample.py on line 18, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

  File "sample.py", line 54, in <module>
    with cursor.execute(tsql):
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7 in position 48: ordinal not in range(128)

試行した手順

以下の記事で解決しましませんでした。
http://shu223.hatenablog.com/entry/20111201/1328334689

以下の記事で解決しませんでした。
https://qiita.com/chatrate/items/eb4b05cd1a6652529fd9

root@2c51574bfd36:/sample# python -c "import site; print (site.getsitepackages())"
['/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages']

解決した手順

以下を参考にしたら解決しました。
https://kaworu.jpn.org/python/Python%E3%81%AEUnicodeDecodeError%E3%81%AE%E5%AF%BE%E5%87%A6%E6%96%B9%E6%B3%95
with cursor.execute(tsql.decode('utf-8')):

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker を用いた AOSP Android10 のビルド時間の測定結果

はじめに

AOSP Android10 全体のビルド時間を測定したので、結果を共有します。
ビルド環境には Docker (Ubuntu 18.04) を用いました。
ビルド時間短縮のポイントは、これまで AOSP のワークスペースに組み込まれてきた
ccacheAndroid10 からはサポートされなくなった事 を受けて、

Our binary was rather old, and for a variety of reasons we haven't kept
it updated.

ビルド環境に ccache をインストールし、有効化する必要がありました。

but if you still want to use ccache, continue setting USE_CCACHE
and also set CCACHE_EXEC to the path of your ccache executable.

実行結果

クリーンビルド ビルド時間(分)
ccache なし 245m
ccache あり 81m
インクリメンタルビルド ビルド時間(分)
ccache なし 212m
ccache あり 33m

実行環境

  • Azure D8 v3
    • vCPU = 8 / Memory = 32GiB / Disk = 200GiB
  • Docker 19.03.6
    • ベースイメージは ubuntu:18.04
    • ccache 3.4.1

環境構築

ホストについて

ホスト OS は Ubuntu 18.04.4 LTS とします。また、Android のビルドには必要なパッケージが多いため、ホストに直接インストールする代わりに Docker を活用します。Ubuntu に Docker をインストールする手順やインストール後の便利な設定は、こちらをご確認ください。

Dockerfile

AOSP がサポートしている(と言っても少々古い) build/tools/docker を参考にしながら、
ベースイメージ ubuntu:18.04 で Android10 がビルドできるように少し修正を加えました。

Dockerfile
FROM ubuntu:18.04
ARG userid
ARG groupid
ARG username

RUN apt-get update && apt-get install -y \
    git-core \
    gnupg \
    flex \
    bison \
    gperf \
    build-essential \
    zip \
    curl \
    zlib1g-dev \
    gcc-multilib \
    g++-multilib \
    libc6-dev-i386 \
    lib32ncurses5-dev \
    x11proto-core-dev \
    libx11-dev \
    lib32z-dev \
    ccache \
    libgl1-mesa-dev \
    libxml2-utils \
    xsltproc \
    unzip \
    python \
    python3-minimal \
    rsync \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

RUN curl -o /usr/local/bin/repo https://storage.googleapis.com/git-repo-downloads/repo \
 && chmod a+x /usr/local/bin/repo

RUN groupadd -g $groupid $username \
 && useradd -m -u $userid -g $groupid $username \
 && echo $username >/root/username \
 && echo "export USER="$username >>/home/$username/.gitconfig
COPY gitconfig /home/$username/.gitconfig
RUN chown $userid:$groupid /home/$username/.gitconfig
ENV HOME=/home/$username

ENTRYPOINT chroot --userspec=$(cat /root/username):$(cat /root/username) / /bin/bash -i

元 Dockerfile との差分をハイライトすると、次のようになります。

build/tools/docker/Dockerfileとの差分
-FROM ubuntu:14.04
+FROM ubuntu:18.04

RUN apt-get update && apt-get install -y \
-    openjdk-7-jdk
+    python3-minimal \
+    rsync \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*


- RUN curl -o jdk8.tgz https://android.googlesource.com/platform/prebuilts/jdk/jdk8/+archive/master.tar.gz \
- && tar -zxf jdk8.tgz linux-x86 \
- && mv linux-x86 /usr/lib/jvm/java-8-openjdk-amd64 \
- && rm -rf jdk8.tgz

RUN curl -o /usr/local/bin/repo https://storage.googleapis.com/git-repo-download
- && echo "e147f0392686c40cfd7d5e6f332c6ee74c4eab4d24e2694b3b0a0c037bf51dc5  /usr/local/bin/repo" | sha256sum --strict -c - \

修正箇所について補足します。

  • openjdk-7-jdk / jdk8.tgz 削除
    • Software requirements >> JDK によると、ビルド済 OpenJDK は既にワークスペースに組み込まれているため、古いバージョンの Android のために必要な JDK は削除しました
  • python3-minimal 追加
    • python3.x がないと AssertionError: Could not find python binary: python3 とエラーになるため追加しました
  • rsync 追加
    • rsync がないと FAILED: out/target/product/generic/ramdisk-debug.img とエラーになるため追加しました(ベースイメージ ubuntu:18.04 に含まれていませんでした)
  • repo のチェックサムの確認の省略
    • 元コードでは repo の正真性をハッシュ値で確認していましたが e147f039 は古いため削除しました
    • その代わりに現時点の repo 2.4.1 のハッシュ値 d2e17c4b としても良かったのですが git-repo を読む限り、ここ最近頻繁に更新されていることを受け、今回はチェックそのものをやめておきます

ただし、十分にメンテナンスされていないように見える AOSP の Dockerfile を起点として作業をしているため、他にも既に不要なパッケージが含まれているかもしれません。今後 AOSP から新しい世代向けの Dockerfile が公開されたら、そちらに移行した方が無難です。

Docker イメ―ジのビルド

build/tools/docker/README.md を参考に Docker イメージをビルドします。

# Copy your host gitconfig, or create a stripped down version
$ cp ~/.gitconfig gitconfig
$ docker build --build-arg userid=$(id -u) --build-arg groupid=$(id -g) --build-arg username=$(id -un) -t android-build-bionic .

ビルドされたイメージを確認します。

$ docker image list
REPOSITORY             TAG                 IMAGE ID            CREATED              SIZE
android-build-bionic   latest              ff2cb9986cd7        About a minute ago   612MB

ソースコード取得

今回は Android10 Release revision 14 を題材としてビルド時間の測定することにします。

# /mnt/work/aosp をワークスペースとします
$ sudo chown -R $USER:$USER /mnt
$ mkdir -p /mnt/work/aosp
$ cd /mnt/work/aosp
$ repo init -u https://android.googlesource.com/platform/manifest -b android-10.0.0_r14 
$ time repo sync -j8

repo sync has finished successfully.

real    51m0.159s
user    133m47.445s
sys     13m13.264s

構築されたワークスぺースを確認します。

$ repo list | wc -l 
745

$ du -sh . 
90G . 

このように、745 もの Git リポジトリからワークスペースが構成され、ビルド前にも関わらず 90GB もディスクを使用していることが確認できました。相変わらず巨大なプロジェクトです。

ビルド

さて、いよいよビルドします。
build/tools/docker/README.md を参考に、少し手直ししながら Docker コンテナを起動します。

docker-run.sh
# Docker 用のキャッシュディレクトリを用意します
$ mkdir -p /mnt/.cache /mnt/.ccache

$ docker run -it --rm \
    -v /mnt/work/aosp:/src \
    -v /mnt/.cache:/mnt/.cache \
    -v /mnt/.ccache:/mnt/.ccache \
    -e XDG_CACHE_HOME=/mnt/.cache \ # Docker 用キャッシュディレクトリを参照します
    -e CCACHE_DIR=/mnt/.ccache \ # Docker 用キャッシュディレクトリを参照します
    -e CCACHE_COMPRESS=1 \ # キャッシュサイズの圧縮のため
    -e CCACHE_EXEC=/usr/bin/ccache \ # 後述
    -e USE_CCACHE=1 \ # 後述
    android-build-bionic

こちら のコミットログによると、ccache を有効化するためには
-e USE_CCACHE=1-e CCACHE_EXEC=/usr/bin/ccache の設定が必要でした。

So if you still want to use ccache, continue setting USE_CCACHE, but also set
the CCACHE_EXEC environment variable to the path to your ccache executable.

これでビルド用 Docker コンテナが起動できたはずです。
続いて ccache の有無やビルド方式の違いについて比較していきます。
なお、以降のコマンドはすべて Docker コンテナ上で実行します。

クリーンビルド

中間生成物やビルド成果物を削除して、イチからビルドする方式となります。
リリースビルドの場合は、こちらになると思います。
コンフィギュレーションやビルドターゲットについては以下の公式手順を参考にしています。

クリーンビルド(ccache なし)

Docker
$ rm -rf out

$ source build/envsetup.sh
$ lunch aosp_arm-userdebug
$ unset USE_CCACHE # 実験的に ccache を無効化するため
$ time m

#### build completed successfully (04:05:56 (hh:mm:ss)) ####
real    245m55.990s
user    1790m50.629s
sys     97m4.830s

クリーンビルド(ccache あり)

キャッシュが保存されていない環境では、以下のビルド手順を 2 回 実行して計測する必要があります。( 1 回目のビルドはキャッシュを蓄えるため )

Docker
$ rm -rf out
$ ccache -z # キャッシュ統計情報のクリア

$ source build/envsetup.sh
$ lunch aosp_arm-userdebug
$ time m

#### build completed successfully (01:21:53 (hh:mm:ss)) ####
real    81m52.620s
user    557m28.541s
sys     39m36.687s

ccache が効果的に作用したことで ビルド時間が約 1/3 に短縮されました。:tada:
この状態で、キャッシュのヒット数やヒット率などの統計情報を見てみます。

ccache_統計情報の表示
$ ccache -s

cache directory                     /mnt/.ccache
primary config                      /mnt/.ccache/ccache.conf
secondary config      (readonly)    /etc/ccache.conf
stats zero time                     Thu Feb 27 00:55:12 2020
cache hit (direct)                 33652
cache hit (preprocessed)             325
cache miss                             0
cache hit rate                    100.00 %
called for link                       77
called for preprocessing              18
unsupported code directive             2
cleanups performed                     0
files in cache                    101488
cache size                           3.9 GB
max cache size                       5.0 GB

期待通り cache hit rate 100.00 % となっていることが確認できました。

cache hit (direct) 33652

このヒット数が妥当な数値であるかについては、ビルド対象となった C/C++ ファイルの数を調べるなどして、検証する必要があると感じています。(今回は調べ切れていません)

インクリメンタルビルド

インクリメンタルビルド(差分ビルド)は、依存関係が正しく書かれたビルドシステムを前提として、変更されたファイルとその依存ファイルがビルドされる方式です。したがって、通常クリーンビルドと比べて処理数が少なくなるため、その分高速になります。開発者がローカル環境でビルドするときは、多くの場合がこちらになると思います。

何も更新されていない場合

Docker
$ ccache -z

$ source build/envsetup.sh
$ lunch aosp_arm-userdebug
$ time m

#### build completed successfully (5 seconds) ####
real    0m5.080s
user    0m11.956s
sys     0m3.644s

Android のビルドシステムは依存関係の記述が正しくメンテナンスされているため、差分がまったくない状態でインクリメンタルビルドをすると、あっという間に完了します。なお、このケースではコンパイルもされないため cache hit rate 0.00 % のままとなっていました。

インクリメンタルビルド(ccache なし)

さて、今度はソースコードの更新をシミュレーションするため AOSP の一部のファイルのタイムスタンプを更新します。それによって変更されたファイルとその依存ファイルの再ビルドが実行されます。

Docker
$ ccache -z
$ touch external/protobuf/src/google/protobuf/*

$ source build/envsetup.sh
$ lunch aosp_arm-userdebug
$ unset USE_CCACHE # 実験的に ccache を無効化するため
$ time m

#### build completed successfully (03:32:32 (hh:mm:ss)) ####
real    212m31.772s
user    1586m36.813s
sys     69m56.492s

前回の クリーンビルド(ccache なし)と比べて、わずかに短縮されていたものの、かなり時間がかかってしまいました。

インクリメンタルビルド(ccache あり)

Docker
$ ccache -z
$ touch external/protobuf/src/google/protobuf/*

$ source build/envsetup.sh
$ lunch aosp_arm-userdebug
$ time m

#### build completed successfully (33:53 (mm:ss)) ####
real    33m53.000s
user    237m48.218s
sys     10m31.486s

インクリメンタルビルドのおいてもやはり ccache は効果的で、無効時と比べて ビルド時間が約 1/6 ~ 1/7 に短縮されました。ただし、どのファイルが更新されたかで依存関係やビルド対象となるファイル数は大きく変わるため、これはあくまで一例となります。この状態で、キャッシュのヒット数やヒット率などの統計情報を見てみます。

キャッシュ統計情報の表示
$ ccache -s

cache directory                     /mnt/.ccache
primary config                      /mnt/.ccache/ccache.conf
secondary config      (readonly)    /etc/ccache.conf
stats zero time                     Thu Feb 27 04:28:46 2020
cache hit (direct)                  1737
cache hit (preprocessed)              35
cache miss                             0
cache hit rate                    100.00 %
called for link                        8
cleanups performed                     0
files in cache                    101503
cache size                           3.9 GB
max cache size                       5.0 GB

今回も期待通り cache hit rate 100.00 % となっていることが確認出来ました。

cache hit (direct) 1737

クリーンビルド(ccache あり)の時と比べて、再コンパイルの必要なファイルが少ないため、このようにヒット数自体が下がることは妥当だと思います。

おわりに

今回は Docker を用いた AOSP Android10 のビルド方法とビルド時間の測定結果についてまとめました。今後、更にビルド時間を短縮化するために、

  • 更に高パフォーマンスなホストを試してみる
  • より新しい https://ccache.dev (例えば 3.7+) を試してみる

などのチャレンジをしてみても面白いと思います。

補足資料

ベンチマークが Android 4.x なので少し古い資料になりますが、Core 数や ccache と Android のビルド時間の関係について調査しているので、こちらも参考になると思います。
Accelerating Android Builds

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分なりのGoアプリケーションDockerfileのベストプラクティス

最終的に作成されるDocker Imageが軽量になること
Docker Buildにかかる時間を短縮する

を目標としている

フォルダ構成

$ tree .
.
├── Dockerfile
├── Makefile
├── pkg_a
│   ├── pkg_a.go
│   └── pkg_a_test.go
├── pkg_b
│   ├── pkg_b.go
│   └── pkg_b_test.go
├── go.mod
├── go.sum
├── main.go

標準的なGoアプリケーションの構成
ライブラリ管理はGo Modulesを使います。

Dockerfile

Dockerfileは以下のようになっています。
工夫点はコメントで番号つけてます。詳細は後述します

# ①
FROM golang:1.13 as builder

# ②
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
WORKDIR /go/<アプリのリポジトリ名>

# ③
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# ④
RUN make

# ⑤
FROM alpine
RUN apk add --no-cache ca-certificates

# ⑥
COPY --from=builder /go/<アプリのリポジトリ名>/app /app
CMD ["/app"]

説明

① multi-stage buildsを使いDocker Imageの軽量化を行う

Dockerにはmulti-stage buildsという機能があり、これを使うと1つのDockerfile内で複数のFROMを指定し、ビルドのベースとなるImageを分けることができます。

これによりアプリケーションのビルド時のみ必要となるパッケージなどを、最終的なImageから省き、Imageサイズを削減するというのが簡単に行えます。

Goの場合、Goランタイムはビルド時のみ必要でバイナリにビルドして実行する場合は不要なので、最初のbuild用のImageベースはgolang, 最終的なImageのベースはalpineとしています。

Imageのサイズとしてはgolang:1.13が5.5GB、alpine:latestが2.68MBなので、そのままgolangを最終的なイメージベースとするより5GB以上のサイズ削減になります。

② Goのビルド設定

ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64

でビルドの設定を行なっています

特に重要なのがCGO_ENABLED=0で静的バイナリの生成を指定してます。
これ指定しないと、alpineで実行するときにエラーになります。

③ 依存ライブラリのダウンロードはなるべくキャッシュをきかせる

なるべくDocker build時にキャッシュをきかせて、ビルド速度を上げるために、リポジトリ内の全てのファイルをコピーする前に、Go Modules系のファイルだけコピーして依存ライブラリのダウンロードを行なっています。

Dockerのビルドは1行を1レイヤーとして考え、そのレイヤーで変更がなければキャッシュを利用し、変更があれば、それ以降のレイヤー全てがキャッシュ使われなくなります。

そのため実際は以下でも動くのですが、アプリのソースコードを修正しただけでも毎回依存ライブラリのダウンロードが発生してしまいます。

- COPY go.mod go.sum ./
- RUN go mod download
- COPY . .
- RUN make
+ COPY . .
+ RUN make

そのため、事前にgo.mod, go.sumだけをコピーしています。
この場合、依存ライブラリを新たに追加しない限りは、毎回キャッシュが使われてDocker buildにかかる時間を大幅に短縮できます。

④ Makefile使う

RUN go build -o appとかでもいいんですが、Makefileを用意しておくと、Dockerfileがスッキリして見栄えがよくなるかと思います。

ちなみにMakefileは以下のようになっています。

all: test build

setup:
    go get
test:
    go test ./...
build:
    go build -o app

⑤ 最終的なImageのベースはalpineを利用する

multi-stage buildについては前述の通りです。
mulit-stage buildで最終的なImageのベースとしては、サイズを意識するならalpine一択かなと思ってます。
(運用時にコンテナ内にexecで入ってデバッグ作業とかしたいなら別のImageとかの方が良いかもですが)

⑥ 別のステージで作成したアプリのバイナリファイルをコピーする

FROM golang:1.13 as builder
COPY --from=builder /go/<アプリのリポジトリ名>/app /app

FROM golang:1.13 as builderで最初のgolang:1.13を使ったbuildの間にbuilderというaliasをつけてます。

これを使ってCOPY --from=builder /go/<アプリのリポジトリ名>/app /appとすることで、golang:1.13でビルドしたアプリのバイナリファイルをalpineの中に持ってくることができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アプリケーション作成で詰まった箇所

執筆中

環境

Ruby 2.6.5
Rails 5.2.4
Docker 19.03.5
bundler 2.1.4

bundler問題

別途記事

フラッシュメッセージ

表示される

user_sessions_controller.rb
redirect_back_or_to root_url, success: 'ログインしました'

表示されない

user_sessions_controller.rb
redirect_to root_path, success: 'ログアウトしました'

解決法

application_controller.rb
# 追加
add_flash_types :danger, :success, :warning

原因

調査中
そもそもadd_flash_typesの指定がないとBootstrapに対応したsuccess info warning danger の4つのキーが使用できないはずなのにredirect_back_ro_toだと表示されるのは4つのキーがデフォルトで備わっているからなのか?

文字コードがlatin1による文字化け

データベース内で文字化けするだけであって、取り出して使用する場合は文字化けしないのでとりあえず放置。

jQueryのclickイベントでdocument使用時のみ発火しない

試したこと

turbolinksが原因か?

Gemfile.lockからturbolinks削除
application.jsから読み込み削除
application.html.slimから読み込み削除
参考資料

docker-compose bundle update
docker-compose build
・コンテナの再起動
→変わらず

原因

調査中
remote: true & application.jsの//= require rails-ujs

remote: trueで送信するとrails-ujsのjQueryがなんかしてるっぽくclickイベントが使用できない。

解決法

remote: trueをやめるかclickイベントをやめる。
今回はclickイベントを使用せず実装する。

ransackで関連付けしたモデルからも検索をかけたい

postsテーブルのtitle、body、tagsテーブルのtagの3つのカラムから検索をかけたい。

原因

join 関連付けの検索ワードが出てこず若干詰まったこと

解決法

訂正前

= f.search_field :title_or_body_or_cont, class: "form-control mr-sm-2", placeholder: '検索ワード'

訂正後

= f.search_field :title_or_body_or_tags_tag_cont_any, class: "form-control mr-sm-2", placeholder: '検索ワード'

関連付けしてあることが前提!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでMySQLを立ち上げる時に文字コードを指定する

docker run --restart=always -d -e MYSQL_ROOT_PASSWORD=mysql \
    -p 3306:3306 --name testdb mysql:8.0 --character-set-server=utf8mb4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker PHP(7.3) Apacheを使ってxdebugを動かす際にハマった

概要

理由

  • php.iniに記述したxdebugの設定が反映されない。

対応

  • php.iniではなくdocker-php-ext-xdebug.iniにxdebugの設定を記述する

以下記述例

docker-php-ext-xdebug.ini
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so
xdebug.remote_enable=1
xdebug.remote_autostart=1
xdebug.remote_host=docker.for.mac.host.internal
xdebug.remote_handler=dbgp
xdebug.remote_port=9020
xdebug.remote_log=/var/log/xdebug.log
xdebug.remote_connect_back=0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP開発用のVisual Studioコード

私は個人的にPHPStormを開発用のメインIDEとして使用していますが、作業中の最高のIDEについて議論し始めました.VSCodeを使用する人もいれば、PHPStormを使用する人もいます。議論のため、VSCodeが完全なPHP開発IDEとして動作するようにプラグインの検索を開始しました。

While I personally use PHPStorm as my main IDE for development, we started debating the best IDE at work, some use VSCode, others use PHPStorm, because of the discussion, I started searching for plugins to make VSCode behave as a full PHP development IDE.

私の検索では、このブログ投稿に出会いました
In my search I came across this blog post

vscode php development

このブログを読んで、私はインストールしました:
Reading this blog, I installed:

残りについては、 settings.jsonの指示に従っただけです。
For the rest, I just followed the instructions for the settings.json

私が行った唯一の追加手順は、常にdockerを使用し、マシンにローカルにPHPがインストールされていないため、「組み込みのphp検証」を無効にすることです。
The only extra step I took, is disabling the built-in php validation because I always use docker, and don´t have PHP installed locally on my machine.

VscodeおよびDocker用にXdebugを構成する

私は通常、開発環境にdockerを使用するため、xdebugの設定には時間がかかりました。さまざまなブログやstackoverflowの質問にアクセスした後、これが私の「launch.json」設定です
Since I usually make use of docker for my development enviroment, configuring xdebug took me a while, after visiting various blogs and stackoverflow questions, this is my launch.json config

{
        "name": "Listen for XDebug",
        "type": "php",
        "request": "launch",
        "port": 9000,
        "log": true,
        "externalConsole": false,
        "pathMappings": {
            "/application": "${workspaceFolder}"
        },
        "ignore": [
            "**/vendor/**/*.php"
        ]
    }

fuxideブログ投稿へのクレジット。これは設定の参照に使用します。
Credits to fuxide blog post, that I use for reference for the config.

何らかの理由で変数の監視は機能しませんが、これらはデバッガの一般的なセクションに表示されます
For some reason the variable watches don´t work, but these are visible in the general section of the debugger

CLIを使用するのが好きなので、gitプラグインをインストールしたくありませんでしたが、職場の友人からの推奨に従って、gitkraken を試してみます。
I did not want to install any git plugin, because I like using the CLI, but per recommendation from my friends at work, I´ll give gitkraken a try.

世の中の読者にとって、どのIDEが開発にお気に入りですか?
For the readers out there, what IDE is your favorite for development?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker Toolboxを使ってDockerでLaravel開発環境を作ってみる

はじめに

今までVagrant+VirtualBox、XAMPP、Laravel Homesteadと開発環境を構築してきましたが、現在本格的にエンジニア志望の身の上であるということと、友人がDockerで環境構築をしたという話を聞いたので私もこれからの開発環境をDockerに絞ってやっていこうと思い今回挑戦してみましたので備忘録として書きます。

一応Githubにも慣れる目的でpushしてありますが、基本的には後述の参考記事の手順通りにやったことをWindows10 Homeでやるために私がしたことを追記して書いたようになりますので、作ったDocker環境は同じです。

https://github.com/shitikamiyako/Docker-Laravel-Demo

最初に行っておきたいこと

Dockerを使うならMacかWindowsならせめてWindows10 Proを使うことをおすすめします、Homeだとまーややこしいというより、Dockerを使うメリットの半分くらいなくなっている気がしなくもないです。
Linuxに明るい人だとLinuxでやる人もいるそうな。

構築する環境

windows10 home(Ryzen 5 3600)
docker v18.06.1-ce(途中でv19.03.1にアップデートします)
VirtualBox 6.0.16

Dockerとは?

私が語るよりも詳しいことは下記の記事をご覧になってください。

さくらナレッジ様より Docker入門(第一回)~Dockerとは何か、何が良いのか~

Dockerでプログラマが最低限知るべきことが、最速でわかるチュートリアル

さくらナレッジさんの記事より引用させていただくと、Dockerとは簡潔に言うと以下のような仕組みです。

Dockerは、Linuxのコンテナ技術を使ったもので、よく仮想マシンと比較されます。VirtualBoxなどの仮想マシンでは、ホストマシン上でハイパーバイザを利用しゲストOSを動かし、その上でミドルウェアなどを動かします。それに対し、コンテナはホストマシンのカーネルを利用し、プロセスやユーザなどを隔離することで、あたかも別のマシンが動いているかのように動かすことができます。そのため、軽量で高速に起動、停止などが可能です。

イメージとコンテナについて十全に理解するにはLinux及びDockerについてもう少し深く勉強する必要があるので、あくまで今の私の感覚で話をすると、イメージはコンテナを作る材料のようなものだと思うとわかりやすいかもしれません。
例えば、今回はLaravelをDockerに導入するために

Nginx(サーバー) 、PHP(言語)、 MySQL(データベース)

の3つを用意しないといけないのですけど、Dockerの場合これを用意するにはまず各々のイメージをDocker Hubのレポジトリから引っ張ってこないといけない(pullするという)ので、まずそれを行います。
そしてそれを使用するために用意してきたイメージから各コンテナを作ることになります。
こうすることで各コンテナがサーバー・PHP実行環境・データベースの役割を持つのであとはPHP実行環境にLaravelをインストールしてしまえばLaravel環境の完成ということになります。
よく言われるDockerの利点の1つがここで、開発環境によって使うイメージを組み合わせることで容易に異なる環境を構築することができます。
例えば上記の例においてもこれとは別にデータベースをMySQLではなくMariaDBにしたLaravel環境を用意したいということであれば、MariaDBイメージを用意すればいいということになりますね。

Dockerインストール

DockerはMacを使ってると話が早いみたいなのですが、そうでなければ早くも詰まるポイントその1です。

通常、Macの場合であればDocker for Macと呼ばれるインストーラーからインストールできるのですがWindowsの場合だとかなりややこしいです。

まず、使用しているWindowsがWindows10 Proの64bit版であるかどうかで分岐します。
そうであるなら、Docker for Windowsというインストーラーが使えるのでこれを使いましょう。
違う場合、つまり今回のパターンだと残念ながらそのままだとDockerは使えずDocker Toolboxというものを使い、VirtualBox下でDockerを使うことになります。
仮想マシンを使わないのがDockerの利点の1つのはずなので虚無の気持ちを覚えますが、受け入れましょう。

まずGithubから最新版をダウンロードします。
DockerToolbox

しかし、ここでも詰まるポイントがあってCPUがAMD製つまりRyzenを使用されてる場合はなんとバージョンによってインストールの際弾かれてしまう場合があります。
私はRyzenを使っているので見事に弾かれました。
その場合は、バージョンを下げたもので試してみてください、私は冒頭のバージョンで成功した後、最新版にアップグレードすることで事なきことを得ました。
さて、ToolboxをインストールするとDocker QuickStart Terminalというものがあるはずなのでそれを起動してください。
インストールの際にVirtual BoxやGitなどWindows10 HomeでDockerを使うために必要なものを同時にインストールするかどうか聞かれますが、各々で必要なければチェックを外しましょう。

無事にこのようにクジラが出てきたらDockerのインストール完了です。
以後Dockerを起動する際もDocker QuickStart Terminalから起動できます。
2020-02-25_02h50_00.png

あと、これも詰まるポイントの1つなのですが仮想環境を使うためには
Hyper-vの機能を有効にしないといけません。
これはCPUがIntel製かAMD製か、さらにWindowsがProかHomeかで、もっというとマザーボードがどこのものかで有効にする手段が違うのですが、このあたりはお使いの環境を調べてHyper-v 有効というキーワードでググっていただければ。
私の場合は、BIOSの設定から有効にしないといけませんでした。

開発環境の構築をしていく

docker-composeでLaravelの開発環境を整える方法とその解説
(以下参考記事1と呼称させて頂きます。)

Laravelの開発環境をDockerを使って構築する

詳しいことは上記の記事を参照して頂いた方がわかりやすく話も早いのですが、今回の場合はWindows10 Homeでの導入になりますので、私からも順を追って話をさせていただきます。

1.Virtual Boxのポートフォワーディングの確認

まずは、Windows10 Homeの場合必ず確認しなればならないのがここです。
これ、今冷静に考えればWindows10 HomeでのDockerの使用はVirtual Boxをかませてその中の仮想マシン(Linux)で動かすわけですから、当然仮想マシン側のポートもちゃんと設定してあげないとlocalhostすらままならないんですけど、なぜか私はそれに気づかず8時間くらい溶かしてました。
こんな人は他にはいないと思いますけど、後々あれ? とならないようにまずこちらから設定しましょう。

Virtual Boxを起動して、Dockerで使用している仮想マシンを選択して設定のところをクリックします。
どれを使用しているんだ? となる方はDockerを起動してdocker-machine lsとコマンドプロンプトで打つと、起動している仮想マシンの詳細が出るのでそこのNAMEの部分がDockerで使用されている仮想マシンの名前になりますので確認してみてください。
導入直後のままならおそらくdefaultという名前のはずです。

さて、設定をクリックしたあとはネットワークの項目を選択、高度と書かれているところのプルダウンメニューを表示させると下記のようにポートフォワーディングの項目が出るのでそこをクリックしましょう。
2020-02-26_02h38_29.png

すると下記のような画面が出てくるので、右側のプラスマークのアイコンからポートの設定ができます。

2020-02-26_02h39_03.png

名前・ホストIP(ローカル環境なので、127.0.0.1)、ホストポート、ゲストポートを設定します。
このあと、Dockerの方でもホスト⇒ゲストとポートの設定のをするのですが、ややこしいことにWindows10 HomeでのDocker利用は

Windows⇒Virtual Box⇒Docker

つまり

大本のホスト⇒Dockerから見たホスト(Windowsから見るとゲスト)⇒ゲスト

といったような構造になっているため、Docker側でVirtual Box⇒Dockerでのポートの設定をしても、Windows⇒Virtual Box側でポートを通してやらないと暖簾に腕押しということになってしまうのです。
ネットワークの知識に少し明るいとこういうところは当然なのかもしれないですが、私みたいなまだまだビギナーという人には落とし穴になりがちだと思うので最初に確認して頂きたいです。

2.docker-compose.ymlの記述

参考記事1から引用させていただくと

docker-compose.ymlには、yaml形式で、複数のコンテナで構成されるサービスを構築・実行する手順等を記述します。
それぞれのコンテナについて、イメージ作成時にベースにするDockerイメージやDockerfileの指定・ホスト側からDockerコンテナ側へのポートフォワーディングの指定・Dockerコンテナに共有する(ホスト側の)ファイル群の指定などを定義することができます。

ということになります。
つまり、このコンテナ(役割)を使った環境を作りますよという設計図のようなものですね。
なお、今回私は以下のようなディレクトリ構造で構築しましたので以後の説明はこれに沿っているものと考えてください。

・docker-compose-laravel(ルートフォルダ)
├── demo-docker-app(Laravel)
├── docker
│ └── web
│ └── default.conf
│  └── php
│ └── Dockerfile
├── docker-compose.yml
└── index.html
└── index.php

ディレクトリの構築が終わったらdocker-compose.ymlに以下のように記述します。

version: '3'
services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html

上記はNginxを実行するコンテナを定義しています。
versionはdocker-compose.ymlのファイルフォーマットのバージョン指定。

serviceはコンテナの名前の定義で、インテンドが1つ下がった部分が実行するコンテナの定義となります。
なので、docker-compose.yml記述でのインテンド上げ下げは正確に行わければいけないので注意しましょう、インテンドが間違っていると必ずエラーが出てうまくいきません。

imageはDockerイメージの指定になります。
このコンテナではNginxを使いたいのでバージョンを含めて上記のように指定します。

portsは前述の通り、ポートフォワーディングの設定になります。
1で設定したものと同じになるように設定しましょう。
ちなみにホストのポート:ゲストのポートという記述になりますが、ゲスト側のポートの下2桁の00は省略します。

volumesはホスト・コンテナ間でのファイル共有の指定で、ホスト側のパス:コンテナ側のパスの形式で記述します。
Homestead使ったことがある人ならわかると思うのですが、要はホスト側で編集した内容等がゲスト側にも反映されるように同期をとるようなものです。
いちいちゲスト側の環境でコード書いたりしなくてもいいようにすると思っていただければ。

./docker/web/default.conf:/etc/nginx/conf.d/default.conf

と書いてありますがこれはホスト側のパス:ゲスト側のパスという記述になります。

.:/var/www/html

つまり上記の場合はホスト側のルートフォルダの内容はゲスト側の/var/www/html部分に反省されていますよということになります。

また、volumesを設定することファイルやディレクトリは、永続化(コンテナを削除してもホスト側にファイルやディレクトリが残る)させることができるようです。

3.default.confの記述

このファイルはNginxの設定ファイルとなります。
以下のように基準してください。

server {
    listen 80;
    root  /var/www/html;
    index index.html;
    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;
}

listenはリクエストを受け付けるIPアドレスやポート番号の設定です。
上記の場合はすべてのIPアドレスの80番ポートでリクエストを受け付けるということになります。
root, indexでは、それぞれ、ドキュメントルートのディレクトリ・インデックスとして使われるファイル名を定義します。
indexの部分は先程の通りでindexは適当な確認用のindex.html作っておいてそれを設定してください。
access_log, error_logはそれぞれのログ出力先の設定となります。

4.Nginxの動作確認

ここまでの工程でローカルでWebサーバーを立ち上げる準備ができたので立ち上げてみます。
ディレクトリをルートフォルダに移して以下のコマンドを実行します。

docker-compose up -d

このコマンドはdocker-compose.ymlの記述どおりにコンテナを作って、起動してくださいという意味になります。
-dのオプションはバックグラウンドでコンテナを実行するためのコマンドになります。
これがない場合は、upで実行された時にコンテナはすぐに終了してしまいます。

無事に作成・開始が成功すると以下のように表示されます。

Creating network "docker-compose-laravel_default" with the default driver
Creating docker-compose-laravel_web_1 ... done

ブラウザでhttp://localhost:8000 にアクセスすると、index.htmlが表示されます。
8000の部分は設定したポート番号に合わせてください。

5.PHP実行環境の準備

NginxでWebサーバーを立ち上げたので今度はそこでPHPを動作できるようにします。
そのためにはPHP-FPMというのものを使うのですが、このあたりの詳しいことは参考記事1で紹介されている、以下の記事をご覧になってください。私も今読んでいます。
nginx と PHP-FPM の仕組みをちゃんと理解しながら PHP の実行環境を構築する

さて、コンテナを追加するのでdocker-compose.ymlを編集していきます。
以下の通りに記述してみてください。

version: '3'
services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    depends_on:
      - app
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
  app:
    image: php:7.2-fpm
    volumes:
      - .:/var/www/html

servicesの項目に、appを追加します。
そして、webの項目にdepends_onを追加してそこにappを指定しておきます。
これはサービスの依存関係を表すもので、今回はNginxがPHPに依存をしているということになります。
この定義によって、Dockerはコンテナの起動時にサービスの依存関係に基づいてコンテナを起動するようになるので、PHPコンテナが起動した後にNginxコンテナが起動するということになります。
あとの項目はNginxのときと同じですね、イメージを指定してファイル共有の定義をします。

次に、PHPが動くようにNginxの設定を変更します。
以下のように記述してください。

server {
    listen 80;
    root /var/www/html;
    index index.php index.html index.htm;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

indexの項目を見るとindex.phpとindex.htmが追加されています。
先程はindex.htmlをインデックスページとして設定しましたが、このよう複数設定すると先に記述があるファイルから優先してインデックスページに設定されます。
今回はphpを実行したいのでindex.phpを先頭に記述することになります。

locationの項目はURIごとにどのファイルを配信するのか設定する項目です。
最初のlocationではURIのパスにファイルがあるかどうかを確認し、なかった場合はディレクトリがあるかを確認し、いずれもなかった場合はindex.phpを返すという意味になります。
次のlocationではNginxがPHP-FPMにリクエストを渡すための設定をしています。
詳しいことは先述の記事を確認してください。

ここまでが終わったら動作確認です。
index.htmlと同じように適当な確認用のindex.phpを作成して、配置してください。
そのあとは先程と同じようにdocker-compose up -dを行います。
もし、先程実行したまま作業を続けている場合は、docker-compose downというコンテナを停止・削除するコマンドを実行してから行ってください。
実行するとhttp://localhost:8000 でindex.phpが表示されます。
phpinfo();のコマンドを仕込んでおくとPHPが実行されているかよりわかりやすくなると思います。

6.MySQLコンテナを用意する

最後にDBとしてMySQLを準備をします。
docker-compose.ymlを編集してMySQLのコンテナを定義します。

version: '3'
services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    depends_on:
      - app
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
  app:
    image: php:7.2-fpm
    volumes:
      - .:/var/www/html
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: sample
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
volumes:
  mysql-data:

ここまで来るとservicesはweb、app、mysqlで構成されたサービスだということがわかりやすくなりますね。

environmentはMySQLコンテナでの環境変数の設定です。
Laravelでの.envファイルにあるMySQLの項目と同じですね。
またここでのvolumesはmysql-dataをサービス内で共通化して、他のコンテナからも参照できるようにするための設定のようです。

ここまで終わったら動作確認をしましょう。
upしたままならdownをしてから、もう一度docker-compose up -dを行います。
そうしたら以下のコマンドでMySQLのコンテナに入ることができます。

docker-compose exec mysql bash

コンテナに入るとあとは通常のMySQLの操作と同じなので

mysql -h localhost -u -p

でログインしてください、ユーザー名とパスワードは先程設定したものを入力します。
MySQLに入ったらshow databases;でデータベースを確認し、docker-compose.ymlで定義したデータベースが作成できているか確認してください。

7.Laravelのインストール

では、いよいよLaravelのインストールになります。
Composerがどうのとかそういうのはここでは割愛します。
一応私も手前味噌ですがLaravel導入に関して備忘録を残してありますので、よろしければそちらをご覧になってください。

まず、Dockerfileというものを作成します。
これはビルドするイメージの構成の定義です。
docker-compose.ymlで定義したのでは? と思った方、私も思いました。
どうやらこちらは必要なパッケージその他を指定した上でイメージをどう構成するかということを定義するもののようです。
先程だけだとPHPしか入りませんが、今度はComposerなどを入れたいのでそのためにDockerfileで指示書を書くというイメージでしょうか。

FROM php:7.2-fpm
# install composer
RUN cd /usr/bin && curl -s http://getcomposer.org/installer | php && ln -s /usr/bin/composer.phar /usr/bin/composer
RUN apt-get update \
&& apt-get install -y \
git \
zip \
unzip \
vim
RUN apt-get update \
    && apt-get install -y libpq-dev \
    && docker-php-ext-install pdo_mysql pdo_pgsql
WORKDIR /var/www/html

これが今回のDockerfileです。
FROMでイメージ(厳密にはDocker Hubレジストリで公開されているベース)を指定し、
curlコマンドでcomposerをインストール、さらにapt-getコマンドでgit、zip、unzip、vimをインストール、最後にdocker-php-ext-installコマンドでPDOをインストールするという意味になります。
RUNでこれらをDockerイメージのビルド時にコンテナで実行するように定義しています。
WORKDIRはRUNなどの命令が実行される際のディレクトリを指定します。

Dockerfileを作ったら、docker-compose.ymlのPHPの部分、つまりappの部分をDockerfileに基づくように修正します。

version: '3'
services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    depends_on:
      - app
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
  app:
    build: ./docker/php
    volumes:
      - .:/var/www/html
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: sample
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
volumes:
  mysql-data:

imageの部分をbuildにしてDockerfileがあるディレクトリを指定します。
こうすることでDockerfileに基づいてイメージを作成し、appコンテナ(PHPコンテナ)が出来上がるということになります。

あとはLaravelを入れるだけです。
毎度のようにdocker-compose up -dを行い、今度はPHPコンテナに入りたいのでdocker-compose exec app bashを行って以下のコマンドを実行してLaravelをインストールします。

composer create-project --prefer-dist laravel/laravel プロジェクト名

無事にLaravelのインストールが完了したら.envファイルのMySQLの項目をdocker-compose.ymlで定義したものに合わせて設定してください。
DB_HOSTの項目はmysqlと指定します。

最後にNginxの設定をLaravelに合わせたものに修正します。
以下の通りです。

server {
    listen 80;
    root /var/www/html/my-laravel-app/public;
    index index.php index.html index.htm;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

rootの部分をLaravelのpubilcフォルダに合わせることでここがルートディレクトリになります。

ここまで終わったら動作確認です。
まず、docker-compose downを実行して、docker-compose up -ddocker-compose exec app bashでPHPコンテナに入ってマイグレーションを行います。
あとは以下のようにLaravelプロジェクトのフォルダにディレクトリを移してmigrateするだけですね。

cd プロジェクト名
php artisan migrate

マイグレーションが正常に行われたことを確認したら、再度docker-compose down を実行して、docker-compose up -d`を行い、http://localhost:8000 を確認してください。
Laravelのウェルカムページが表示できたら無事終了です、お疲れさまでした。

おまけ

Docker Toolboxのアップグレードについてはこちらを参照してください。

参考:Windows10 Homeで導入したdockerを最新にアップデートする方法

手順としてはアップグレードしたいバージョンのDocker ToolBoxをダウンロードしてきて、導入の時と同じことをするだけです。
Select Componentsは導入と同じように入れる必要のないもの(この場合はアップグレードする必要のないもの)はチェックを外し、次のSelect Additional Tasksもすべてチェックを外します。

インストールが終わったらdocker -vでバージョンを確認して、バージョンが上がったことを確認したらdocker-machine upgrade defaultで仮想マシン側のDockerもバージョンを上げておきます。(厳密にはdocker-machineは仮想マシン側でのDockerのホストを生成して管理しているものみたいです)
これでアップグレードは完了です。

あとがき

最初はGit経由でやろうとしていましたが、ポートフォワーディングの罠にハマりうまく行かないと思い込んでしまったので今回Dockerfileとdocker-compose.ymlで1からDocker環境を構築してみました。
災い転じて福となすとばかりに、とてもわかりやすい記事がありましたのでそれを参照しながら、うまく行かない原因がポートフォワーディングの罠だったことなど色々発見しながらDockerにおける環境構築の基本を学習できたのでとても有意義でした。
でも、フォロワーさんに「Linuxを扱えるようにならないとね?(威圧)」というありがたくも耳が痛すぎるリプライを頂いて、それに納得・承知をした上でやっぱり言わせてください。

Docker環境を整備したい! という方はできればMacも導入してください。

Linux、いずれは向き合わないといけないなぁ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む