web-technical-blog

web開発に関する技術メモ

digdagServerの構築メモ

Centos7にdigdagサーバーを構築した際のメモ

久しぶりに自分のローカルPCにCentos7でサーバーを構築しようとしたが、 vagrant up しようとするとエラーが発生。。

下記組み合わせだとvagrant upがエラーする

  • virtual box:バージョン 5.2.6 r120293 (Qt5.6.2)
  • vagrant:Vagrant 2.0.4
D:\vagrant\labo>vagrant up
The version of powershell currently installed on this host is less than
the required minimum version. Please upgrade the installed version of
powershell to the minimum required version and run the command again.

  Installed version: 2
  Minimum required version: 3

上記エラーメッセージからPowerShellのバージョンを2系から3系以上に変更したが 次にWMF5.0(Windows Management Framework 5.0)をインストールする必要もあった。 詳細は以下のリンクから確認できる。

WMFのインストールが必要

digdagServerを構築する際にpostgreSQL

  • postgres9.6のインストールは以下のURLを参考にしてインストール
  • CREATE EXTENSIONしないとエラーになるので注意
digdag_db=> CREATE EXTENSION "uuid-ossp";
ERROR:  could not open extension control file "/usr/pgsql-9.6/share/extension/uuid-ossp.control": No such file or directory

## 拡張モジュールのインストールが必要
# yum -y install postgresql96-contrib.x86_64

## postgresユーザーでログインして
psql -U postgres -h 127.0.0.1 -d digdag_db

CREATE EXTENSION "uuid-ossp";

postgre9.6 install

digdag/embulkのインストール

  • java1.8がインストールされていることが前提
$ java -version
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (build 1.8.0_151-b12)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)

# インストールされていなければいれる
$ yum -y install java-1.8.0-openjdk
# Embulkの導入
$ curl --create-dirs -o ~/bin/embulk -L https://dl.embulk.org/embulk-latest.jar
$ chmod +x ~/bin/embulk

# Digdagの導入
$ curl --create-dirs -o ~/bin/digdag -L "https://dl.digdag.io/digdag-latest"
$ chmod +x ~/bin/digdag

# OSX環境の場合は次のように.bash_profileを編集する
$ cat << 'EOF' >> ~/.bash_profile
# User specfic environment and startup programs
export PATH=$PATH:$HOME/bin
EOF
$ source ~/.bash_profile

digdagはまりポイント

https://qiita.com/toyama0919/items/142d290c8dcb2c86851c

# vi /usr/lib/systemd/system/digdag.service
[Unit]
Description=digdag

[Service]
Type=simple
PIDFile=/run/digdag.pid
ExecStart="/usr/local/bin/digdag server -b 0.0.0.0 --config /opt/digdag/server.properties -O /opt/digdag/logs/tasklogs -A /opt/digdag/logs/accesslogs -L /opt/digdag/logs/server.log"
User=root
Group=root
WorkingDirectory=/opt/digdag
Restart=always
RestartSec=5
KillMode=process
TimeoutStopSec=1200
SyslogIdentifier=digdag

[Install]
WantedBy=multi-user.target

ExecStartは別ファイルにしないとエラーになるので注意

シェルは別ファイルにしないとエラーする

[Unit]
Description=digdag

[Service]
Type=simple
PIDFile=/run/digdag.pid
ExecStart=/opt/digdag/start.sh
User=root
Group=root

[Install]
WantedBy=multi-user.target

https://qiita.com/cmwig65/items/3386a061aeb3d2f81b81 https://qiita.com/toyama0919/items/142d290c8dcb2c86851c https://qiita.com/bwtakacy/items/ec3151644512ca65f6b6

Go言語でJSONのデコード処理を検証してみた

GOでJSONをデコードする際の方法

GoでJSONを処理する最も一般的な方法

  • Goの標準ライブラリには、JSONを扱うためのパッケージ(encoding/json)が含まれていて
    エンコード(構造体から文字列)およびデコード(文字列から構造体)の両方をサポートしている
package main

import "encoding/json"

type Message struct {
    Name string
    Body string
    Time int64
}

func main() {
    b := []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
    var m Message
    json.Unmarshal(b,&m)
    println(m.Name) // Alice
    println(m.Body) // Hello
    println(m.Time) // 1294706395881547000
}
  • bとして定義されたJSONjson.Unmarshalでデコード
  • ポイントは、デコード結果を受け取るために構造体を定義している点
  • 対象のJSONデータの構造を確認しつつ、実装時に構造体の定義を決める必要がある

複雑な構造のJSON処理で直面する問題

  • JSONデータの構造が複雑な場合、具体的には多重の入れ子を含むケースだと実装がだんだん大変になってくる

  • (例)Elasticsearchの検索APIは処理結果として複雑なJSONを返してくる

{
    "took": 1,
    "timed_out": false,
    "_shards":{
        "total" : 1,
        "successful" : 1,
        "failed" : 0
    },
    "hits":{
        "total" : 1,
        "max_score": 1.3862944,
        "hits" : [
            {
                "_index" : "twitter",
                "_type" : "tweet",
                "_id" : "0",
                "_score": 1.3862944,
                "_source" : {
                    "user" : "kimchy",
                    "message": "trying out Elasticsearch",
                    "date" : "2009-11-15T14:12:12",
                    "likes" : 0
                }
            }
        ]
    }
}

上記JSONデータをデコードする処理を、単純にコーディングすると

package main

import (
    "encoding/json"
    "net/http"
)

type ResultShards struct {
    Total      int `json:"total"`
    Successful int `json:"successful"`
    Failed     int `json:"failed"`
}

type ResultHitSource struct {
    User    string `json:"user"`
    Message string `json:"message"`
    Date    string `json:"date"`
    Likes   int    `json:"likes"`
}

type ResultHit struct {
    Index  string          `json:"_index"`
    Type   string          `json:"_type"`
    Id     string          `json:"_id"`
    Score  float32         `json:"_score"`
    Source ResultHitSource `json:"_source"`
}

type ResultHits struct {
    Total    int         `json:"total"`
    MaxScore float32     `json:"max_score"`
    Hits     []ResultHit `json:"hits"`
}

type Result struct {
    Took     int          `json:"took"`
    TimedOut bool         `json:"timed_out"`
    Shards   ResultShards `json:"_shards"`
    Hits     ResultHits   `json:"hits"`
}

func main() {
    resp, err := http.Get("http://127.0.0.1:9200/_search")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    var result Result
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        panic(err)
    }
    println(result.Hits.Total)
}
  • JSON入れ子構造になっているケースでは、結果を受け取る構造体も入れ子に定義する必要がある
  • 実装にかなり苦労する
  • 別のAPIを呼ぶたびにそのAPIの出力結果に応じた構造体群を定義する必要がある

デコード結果格納までのコードの見通しをよくする

  • 上記の例では、Goの平易な言語仕様しか用いていないために、見通しの悪い実装となっているのでリファクタリングする
  • Goは構造体をネスト定義することが可能
  • さらにネスト定義する構造体には型名が必要ないので、これも削除する
package main

import (
    "encoding/json"
    "net/http"
)

type Result struct {
    Took     int  `json:"took"`
    TimedOut bool `json:"timed_out"`
    Shards   struct {
        Total      int `json:"total"`
        Successful int `json:"successful"`
        Failed     int `json:"failed"`
    } `json:"_shards"`
    Hits struct {
        Total    int     `json:"total"`
        MaxScore float32 `json:"max_score"`
        Hits     []struct {
            Index  string  `json:"_index"`
            Type   string  `json:"_type"`
            Id     string  `json:"_id"`
            Score  float32 `json:"_score"`
            Source struct {
                User    string `json:"user"`
                Message string `json:"message"`
                Date    string `json:"date"`
                Likes   int    `json:"likes"`
            } `json:"_source"`
        } `json:"hits"`
    } `json:"hits"`
}

func main() {
    resp, err := http.Get("http://127.0.0.1:9200/_search")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    var result Result
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        panic(err)
    }
    println(result.Hits.Total)
}
  • ネストに定義することで、本来のJSONの出力形式に見た目が近くなり、かつコード量も減る

発展的な実装テクニック

  • 出力されるJSONの形式を完全に構造体でカバーする必要がなく、参照が必要なオブジェクトだけを構造体の定義でおさえるようにしても、デコード処理に支障は出ない
  • ヒットしたドキュメントのリストのみを参照した場合は以下のように実装することができる
package main

import (
    "encoding/json"
    "net/http"
)

func main() {
    resp, err := http.Get("http://127.0.0.1:9200/_search")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    var result struct {
        Hits struct {
            Hits     []struct {
                Source struct {
                    Title       string `json:"title"`
                    Description string `json:"description"`
                    ImageUrl    string `json:"image_url"`
                    Url         string `json:"detail_url"`
                } `json:"_source"`
            } `json:"hits"`
        } `json:"hits"`
    }
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        panic(err)
    }
    for _, hit := range result.Hits.Hits {
        println(hit.Source.Title)
        println(hit.Source.Description)
        println(hit.Source.ImageUrl)
        println(hit.Source.Url)
    }
}
  • JSONデータに含まれるオブジェクトには、エラー情報のように「もしかしたら返却されるかもしれない」オブジェクトというものもあるので、こういった場合は対応するフィールドをポインタ型で用意するう

構造体定義の自動生成

  • デコード結果を受け取る構造体の定義は、JSONデータから自動生成するWebアプリが公開されている

json-to-go

interfaceを利用して局所的に参照する

  • 必要なオブジェクトをピンポイントに取り出す
  • 各オブジェクトへのアクセスには型アサートを利用する
    • 注意点としては数値型はすべてfloat64型として変換される
    • JSON上に整数値が入っていても、それをint型として変換しようとするとエラーになる
    • JSONデータ上での型とGo側の型の対応ドキュメント
package main

import (
    "encoding/json"
    "net/http"
)

func main() {
    resp, err := http.Get("http://127.0.0.1:9200/_search")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    var result interface{}
    decoder := json.NewDecoder(resp.Body)
    if err := decoder.Decode(&result); err != nil {
        panic(err)
    }
    n, _ := result.(map[string]interface{})["hits"].(map[string]interface{})["total"].(float64)
    println(int(n))
    // 359
    // (int にキャストしなかった場合:+3.590000e+002)
}
  • JSONデータ内に検索結果として格納されている、ヒット件数の値を取り出しています
  • 整数型はすべてfloat64として格納されているので、取得後にintにキャストする操作を行っている

Jasonライブラリ

  • JasonというGitHub上で個人開発されているパッケージ
  • GoのJSONライブラリの中では比較的なメジャーなもの
  • 入れ子のオブジェクトを辿るためのインタフェイスも提供されていて、Jason を利用しない場合、型アサートを繰り返し記述することになりますが、これを簡略化して実装できる
package main

import (
    "github.com/antonholmquist/jason"
    "net/http"
)

func main() {
    resp, err := http.Get("http://127.0.0.1:9200/_search")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    v, err := jason.NewObjectFromReader(resp.Body)
    if err != nil {
        panic(err)
    }

    n, err := v.GetInt64("hits", "total")
    if err != nil {
        panic(err)
    }
    println(n)
}

参考URL

DecoderとUnmarshalの使い分け

  • データがio.Readerストリームからのものである場合、またはデータストリームから複数の値をデコードする必要がある場合は、json.Decoderを使用します。
  • すでにJSONデータがメモリにある場合は、json.Unmarshalを使用します。

Use json.Decoder if your data is coming from an io.Reader stream, or you need to decode multiple values from a stream of data.

Use json.Unmarshal if you already have the JSON data in memory.

Amazon S3に保存されているバケット毎のオブジェクト容量、オブジェクト数を知る方法は大きく分けて下記の2つの方法がある

  • CloudWatchのメトリクスであるS3ストレージメトリクスのBucketSizeBytes、NumberOfObjectsを参照する
  • AWS CLIでS3のバケット配下のオブジェクトに対して再帰的に容量と数を集計する。
    • CloudWatchのメトリクスは自動的に集計してくれるというメリットの一方で1日1回の集計結果しか表示されないというデメリットがある
    • リアルタイムで容量やオブジェクトを知りたい場合は使用できない

www.magtranetwork.com

AWS SAMを利用してGolangなLambdaをデプロイする

AWS CLIでデプロイ(Windows環境)

$ GOOS=linux go build -o main
$ zip deployment.zip main
$ aws lambda create-function \
--region us-west-2 \
--function-name HelloFunction \
--zip-file fileb://./deployment.zip \
--runtime go1.x \
--tracing-config Mode=Active \
--role arn:aws:iam::account_id:role/role_name \
--handler main

aws lambda create-functionのオプション

https://docs.aws.amazon.com/cli/latest/reference/lambda/create-function.html

depoly方法

https://github.com/aws/aws-lambda-go https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/deploying-lambda-apps.html


AWS SAMを利用してLambdaをデプロイする(Mac環境)

  • template.ymlのroleは指定する

テンプレート

$ cat template.yml

AWSTemplateFormatVersion: "2010-09-09"
Transform: 'AWS::Serverless-2016-10-31'
Resources:
  App:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: lambda-go-sample # ファイル名
      Runtime: go1.x
      CodeUri: build # ビルドファイルの設置ディレクトリを設定
      Role: arn:aws:iam::account_id:role/role_name # Roleを設定する
      Timeout: 1

デプロイ

$ GOARCH=amd64 GOOS=linux go build -o build/lambda-go-sample

$ aws cloudformation package \
    --template-file template.yml \
    --s3-bucket <bucket-name> \
    --s3-prefix lambda-go-sample \
    --output-template-file .template.yml

$ aws cloudformation deploy \
    --template-file .template.yml \
    --stack-name lambda-go-sample \
    --capabilities CAPABILITY_IAM

実行結果

$ aws cloudformation describe-stack-resources --stack-name lambda-go-sample

実行

$ aws lambda invoke --function-name lambda-go-sample-App-xxxx --payload '"Lambda"' out.txt

https://qiita.com/ikeisuke/items/3c0c422888ae8ae09831

Docker compose

  • npmコマンドが使用できる必要あり
  • docker-composeコマンドが使用できる必要あり

docker-compose.ymlファイルとは

docker-compose.ymlファイルは以下のようにyaml形式でDockerコンテナに関する起動オプション(buildオプションも含まれることもある)を記述したファイル

docker-compose.ymlサンプル

web:
  build: .
  ports:
    - "5000:5000"
  volumes:
    - .:/code
  links:
    - redis
  redis:
    image: redis

# yaml の記載方法については下記を参照
# https://docs.docker.com/compose/compose-file/

Docker Compose概要

Docker composeとは複数のコンテナから成るサービスを構築・実行する手順を自動的にし、管理を用意にする機能 Docker composeでは、composeファイルを用意してコマンドを1回実行することで、そのファイルから設定を読み込んで全てのコンテナサービスを起動することができる

Docker Composeを使うまでの主なステップ

  • それぞれのコンテナのDockerfieを作成する(既にあるイメージを使う場合は不要)
  • docker-compose.ymlを作成し、それぞれ独立したコンテナの起動定義を行う(場合によっては構築定義も含まれる)
  • docker-compose upコマンドを実行してdocker-compose.ymlで定義したコンテナを開始する

動作確認を行う環境について

(Working dir)
+- docker-compose.yml
+- app-server/
    +- Dockerfile
    +- src/
        +- app.js

Docker composeを使用した簡単なサンプル

Docker composeを使用した複数コンテナでひとつのサービスを作成するサンプルを実施してみる redisを使用してアクセス数をカウントする簡単なアプリケーションを作成してみる

application側の作成

アプリケーション側はnode.jsを使用する nodeの公式レポジトリを使用するための、Dockerfileは以下のように簡単なもの

app-server/Dockerfile

FROM node:5
RUN npm -g install redis
ENV NODE_APTH /usr/local/lib/mode_modules

ENTRYPOINT ["node", "app.js"]

npmはnode.jsのpackageを管理するためのツール ENTRYPOINT ["node", "app.js"]は nodeコマンドの引数にapp.jsを渡している

Redis側の作成

Redis側のコンテナの作成を行うが、公式のRedisイメージを使用するため、Dockerfileは不要

Docker composeファイルの作成

アプリ側のnodeのアプリケーションとredis側のアプリケーションをbuild,runするための定義をdocker-coposeファイルに記載する

docker-compose.yml

nodeapp:
  build: "./app-server"
  container_name: "nodeapp"
  working_dir: "/usr/src/app"
  ports:
   - "10080:10080"
  volumes:
   - "$PWD/app-server/src:/usr/src/app"
  links:
   - "noderedis"
noderedis:
  image: "redis:3"
  container_name: "noderedis"

nodeのアプリケーションが乗るコンテナは、Dockerfileからイメージをbuildしてそこからnodeappという名前のコンテナを起動する redisが乗るコンテナについては、DockerfileからイメージをせずDocker hubからpullしてきた公式イメージを利用し、そこからnoderedisという名前のコンテナを起動する

docker-compose.ymlファイルのあるディレクトリ移動し、docker-compose upコマンドを実行する

$ docker-compose up

動作確認

$ curl http://localhost:10080
You accessed here 1 times.
$ curl http://localhost:10080
You accessed here 2 times.

docker-composeコマンドで作成されるイメージ名

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
wk_nodeapp          latest              c36a03206379        15 minutes ago      649MB
redis               3                   256639e384de        8 weeks ago         99.7MB
node                5                   12b4a63115bc        17 months ago       648MB

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                      NAMES
9fa382e87f70        wk_nodeapp          "node app.js"            15 minutes ago      Up 15 minutes       0.0.0.0:10080->10080/tcp   nodeapp
dba91dafd516        redis:3             "docker-entrypoint.s…"   15 minutes ago      Up 15 minutes       6379/tcp                   noderedis

docker-compose.yml

nodeapp:                            # <- サービス名
  build: "./app-server"             # <- Dockerfile のあるファイルの場所(Dockerfile のある場所。git リポジトリのURL も指定可能)
  container_name: "nodeapp"         # <- コンテナ名。指定しなかった場合はDocker compose で勝手に決められる
  working_dir: "/usr/src/app"       # <- コンテナ内のワーキングディレクトリ。docker run コマンドの-w/--workdir に相当
  ports:                            # <- Expose するポート。docker run コマンドの-p/--publish に相当
   - "10080:10080"
  volumes:                          # <- Bind mount するディレクトリ。volume。docker run コマンドの-v/--volume に相当
   - "$PWD/app-server/src:/usr/src/app"
  links:                            # <- 他のコンテナと接続するときのコンテナ名。docker run コマンドの--link に相当
   - "noderedis"
noderedis:
  image: "redis:3"                  # <- イメージIDとtag
  container_name: "noderedis"

参考URL https://qiita.com/TsutomuNakamura/items/7e90e5efb36601c5bc8a

Docker Hubのオフィシャルイメージを使ったLAMP環境(Apache+PHP+MySQL)

PHP+Apacheのイメージをつかってみる

$ docker rund -d php:5.6-apache

PHP+ApacheのイメージをDocker Hubから取得し、コンテナを起動

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
php                 5.6-apache          61a89dae852c        3 weeks ago         378MB

取得したイメージを一覧を表示する STATUSの欄がUpになっていれば起動

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
0877ed1b4e88        php:5.6-apache      "docker-php-entrypoi…"   5 minutes ago       Up 5 minutes        80/tcp              agitated_wright

ブラウザで確認する

上の方法では、起動しているコンテナのポートが内部で閉じているのでブラウザで確認することはできない。コンテナのポートを公開する

$ docker run -p 80:80 -d php:5.6-apache

-pオプションでホストのポートとコンテナのポートを結びつける。コロンの左側がホスト側、右側がコンテナ側のポート番号。

コンテナを削除する

$ docker rm -f コンテナID

これで削除できる。コンテナIDは以下で確認できる

$ docker ps -a

コンテナを一括で全て削除することも可能

$ docker rm -f $(docker ps -a -q)

コンテナを作成する

1.コンテナに入ってファイルを作成する

$ docker run -p 80:80 --name php -d php:5.6-apache

分かりやすように、--nameオプションでコンテナにphpという名前をつけている

$ docker exec -ti php bash

phpコンテナ内に入ることができる。-tiをつけないとターミナルを開くことはできない

ドキュメントルートの/var/www/htmlにいるはずなので、

# echo '<?php phpinfo();' > index.php

ブラウザでサーバーのURLを開きphpinfoの画面が確認できる

2.コンテナのディレクトリとホストのディレクトリを結びつける

コンテナは永続かされないので、コンテナを削除するとコンテナ内で作成したファイルも消えてしまう。ホスト側にファイルを置き、そのディレクトリをコンテナ側から参照する手法を試す

ホスト側のファイルを置くディレクトリは/Users/121799/docker/wwwどこにおいても問題ない 以前のコンテナを削除してから、以下を実行する

$ docker run -p 80:80 -v /Users/121799/docker/www:/var/www/html --name php -d php:5.6-apache

-vオプションでホストのディレクトリとコンテナのディレクトリを結びつける。コロンの左側がホスト側、右側がコンテナ側のディレクトリ。

MySQLのイメージを使ってみる

起動してコマンドラインから操作してみる

$ docker run --name mysql -e MYSQL_ROOT_PASSWORD=pass -d mysql:5.7

MySQL5.7のイメージをDocker Hubから取得し、起動する -eオプションは環境変数を設定するもので、rootのパスワードをpsssに設定している

コンテナに入る MySQLの操作ができるようになる

$ docker exec -ti mysql bash
$ mysql -ppass

PHPMySQLの連携

DockerfileによるPHPイメージのカスタマイズ

オフィシャルのPHPコンテナは最小限のオプションでコンパイルされている。そのままではMySQLと連動するのが難しい。 Dockerfileを使うと、既にあるイメージをカスタマイズして新しいイメージを作ることができる

Dockerfileの場所はどこでも良い。今回は/docker/phpに作成する ホスト側で/docker/php/Dockerfileを以下の内容で作成する

FROM php:5.6-apache
RUN apt-get update && \
  docker-php-ext-install pdo_mysql mysqli mbstring

FROMは元となるイメージを指定する RUMコマンドを指定する。docker-php-ext-installはphp:5.6-apacheイメージに含まれるユーティリティで、オプションを追加してPHPをリコンパイルするもの

docker-php-ext-install 引数を元にエクステンションをインストールしてくれる

Dockerfileが用意できたら、新しいイメージをビルドする

docker build -t php:custom /docker/php

/docker/phpにあるDockerfileを元にphp:customというイメージを作成する。docker imagesで確認する

MySQLイメージの設定を変更する

先ほど、作成したコンテナは削除しておく

Docker HubのMySQLイメージはcharacter setがlatin1になっている部分があるので設定ファイルを追加する /docker/mysql/custom.confを以下の内容で作成

[mysqld]
character-set-server=utf8
$ docker run --name mysql -v /docker/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=pass -d mysql:5.7

-vオプションでホスト側の/docker/mysqlディレクトリとコンテナ側の/etc/mysql/conf.dディレクトリを結びつけている

PHPコンテナをMySQLコンテナとリンクして起動する

$ docker run -p 80:80 -v /docker/www:/var/www/html --link mysql:mysql -name php -d php:custom

--linkオプションでコンテナmysqlを連携する。 コロンの左側にコンテナ名を指定する

連携が取れているか確認する

$ docker exec -ti php bash
$ cat /etc/hosts172.17.0.2  
mysql d5ca7e687d9b

--linkオプションで指定した名前でホスト名にアクセスできる コロンの右側で指定した名前

phpMyAdminを導入して確認

連携の確認用にphpMyAdminをホスト側に導入

cd /docker/www
wget https://files.phpmyadmin.net/phpMyAdmin/4.4.13.1/phpMyAdmin-4.4.13.1-all-languages.tar.gz
tar zxf phpMyAdmin-4.4.13.1-all-languages.tar.gz
rm phpMyAdmin-4.4.13.1-all-languages.tar.gz
mv phpMyAdmin-4.4.13.1-all-languages myadmin
cd myadmin/
cp config.sample.inc.php config.inc.php

config.inc.php

$cfg['Servers'][$i]['host'] = 'mysql'

hostをlocalhostから--linkオプションにより設定されたホスト名mysqlに変更する

DockerでPHP7.0 + Apacheの環境を構築する

PHP7.0 + Apacheを起動する

docker run -d -p 80:80 --name php70-apache php:7.0-apacheでイメージからコンテナを立ち上げる(ローカルにimageがない場合はDocker Hubから取得してくれる) -pオプションでポートを80番でフォワード

$ docker run -d -p 80:80 --name php70-apache php:7.0-apache
Unable to find image 'php:7.0-apache' locally

7.0-apache: Pulling from library/php
f49cf87b52c1: Pull complete
185616061386: Pull complete
5fc132db2e0d: Pull complete
00c1c323341a: Pull complete
ff3701349211: Pull complete
faab1d6ad70f: Pull complete
eae8d88d75e0: Pull complete
7fbd4c732645: Pull complete
b2e59bdd1208: Pull complete
d5519147d4e5: Pull complete
512315391b1a: Pull complete
b32d82403bad: Pull complete
b5eb11551d42: Pull complete
02fee8125e6e: Pull complete
Digest: sha256:6616fa9ab36d721c2e3e0e128295f474f1e6f5f7bd04207ce0dc50581ecd4073
Status: Downloaded newer image for php:7.0-apache
f4821286b2156584088f0c29c6e09c6cc4b582c310b6449ca94892bb944e219f

Apacheが起動していることを確認

$ curl http://localhost/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /
on this server.<br />
</p>
<hr>
<address>Apache/2.4.10 (Debian) Server at localhost Port 80</address>
</body></html>

php70-apacheコンテナへログインする Forbiddenページしか表示されないので、実際にPHPを書いて表示させてみる

$ docker container exec -ti php70-apache bash
$ echo '<?php phpinfo();' > index.php

ホスト⇄コンテナ間でディレクトリを同期する

コンテナを削除した際に同時にそのコンテナ内にあるファイルも消えてしまう

なので、ホスト側とコンテナのディレクトリを同期させて、そこにファイルを書き込む形式をとる

# コンテナを停止
$ docker container stop php70-apache
# コンテナを削除
$ docker container rm php70-apache

新しく新しくディレクトリが同期されるコンテナを作成する ホスト側の同期対象となるディレクト/Users/121799/docker/php70-apache/www コンテナ側はドキュメントルートである /var/www/htmlとする

$ docker run -d -p 80:80 -v /Users/121799/docker/php70-apache/www:/var/www/html --name php70-apache php:7.0-apache
e78a1ac509425b8cf56ee2c0e988a5d5ec39eb539ea8ffefcced4a7ab6bcea76

/Users/121799/docker/php70-apache/wwwにいる場合に

$echo '<?php phpinfo();' > index.php

これで再度、localhostにアクセスしてみる ホスト側で作成されたファイルがコンテナへ正しく同期されていることが確認できる

参考URL

qiita.com