20201215のdockerに関する記事は21件です。

docker バインドマウントをreadonlyにすれば早くなるか?

macでdocker開発時にファイルio?周りで遅くなる問題があるが、バインドマウントをreadonlyにすれば早くなるのか調べる。
なんとなくreadonlyにした方が早くなる気がする

コンテナの/appにLaravleのコードをマウントする。

FROM centos:centos8

RUN dnf module install -y php:7.4

WORKDIR /app
CMD [ "php", "artisan", "serve", "--port=80", "--host=0.0.0.0" ]

まずは普通にroオプション付けずにマウントする。

$ docker run -it --rm -v ~/Documents/test/docker/volume_test/ro_test:/app -p 8080:80 ro_test

負荷テストにはabテストを使用。
10人で10リクエスト = 合計100リクエストで試験。
1秒あたり2.78リクエスト捌ける
全部処理するのに36秒

$ ab -n 100 -c 10 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        17473 bytes

Concurrency Level:      10
Time taken for tests:   35.945 seconds
Complete requests:      100
Failed requests:        0
Total transferred:      1861900 bytes
HTML transferred:       1747300 bytes
Requests per second:    2.78 [#/sec] (mean)
Time per request:       3594.549 [ms] (mean)
Time per request:       359.455 [ms] (mean, across all concurrent requests)
Transfer rate:          50.58 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:   380 3392 655.6   3578    3659
Waiting:      379 3391 655.7   3578    3659
Total:        380 3392 655.6   3578    3659

Percentage of the requests served within a certain time (ms)
  50%   3578
  66%   3596
  75%   3609
  80%   3616
  90%   3630
  95%   3642
  98%   3653
  99%   3659
 100%   3659 (longest request)

readonlyありの方でマウントする

$ docker run -it --rm -v ~/Documents/test/docker/volume_test/ro_test:/app:ro -p 8080:80 ro_test

1秒あたり2.27リクエスト捌ける
全部処理するのに43秒
あれ、readonlyの方が処理が遅くなっている。

ab -n 100 -c 10 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        519560 bytes

Concurrency Level:      10
Time taken for tests:   43.979 seconds
Complete requests:      100
Failed requests:        0
Non-2xx responses:      100
Total transferred:      51981200 bytes
HTML transferred:       51956000 bytes
Requests per second:    2.27 [#/sec] (mean)
Time per request:       4397.921 [ms] (mean)
Time per request:       439.792 [ms] (mean, across all concurrent requests)
Transfer rate:          1154.25 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       0
Processing:   455 4155 808.2   4374    4487
Waiting:      447 4145 808.2   4362    4478
Total:        455 4155 808.2   4374    4488

Percentage of the requests served within a certain time (ms)
  50%   4374
  66%   4400
  75%   4436
  80%   4444
  90%   4457
  95%   4471
  98%   4483
  99%   4488
 100%   4488 (longest request)

readonlyの時はstorage以下に書き込もうとしてエラーになって余計に時間がかかっている・・・?
試しにstorage以下だけroを外してみる。

マウント時に後から-vを追加することで上書きできるらしい。

$ docker run -it --rm -v $(pwd)/ro_test:/app:ro -v $(pwd)/ro_test/storage:/app/storage -p 8080:80 ro_test

abテストすると、
1秒あたり2.73リクエスト捌ける
全部処理するのに36秒
と、ほぼroなしの状態と同じになった。

ab -n 100 -c 10 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        17473 bytes

Concurrency Level:      10
Time taken for tests:   36.620 seconds
Complete requests:      100
Failed requests:        0
Total transferred:      1861900 bytes
HTML transferred:       1747300 bytes
Requests per second:    2.73 [#/sec] (mean)
Time per request:       3661.974 [ms] (mean)
Time per request:       366.197 [ms] (mean, across all concurrent requests)
Transfer rate:          49.65 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:   370 3461 688.7   3613    4053
Waiting:      369 3461 688.8   3613    4053
Total:        370 3461 688.7   3613    4054

Percentage of the requests served within a certain time (ms)
  50%   3613
  66%   3629
  75%   3640
  80%   3644
  90%   4031
  95%   4044
  98%   4050
  99%   4054
 100%   4054 (longest request)

このことからro付けても早くなるわけではなさそう。

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

DockerとPythonとTwitterAPIで社内業務を自動化してみた+おまけ

社内の案件でツイッターと連動したキャンペーンを担当している方が、
手動でツイートを収集しなければいけないという悩みを抱えており、
何とかしてあげたいという思いから、DockerとPythonを使って
ツイートの収集ツールを作成してみました。

作る際に直面した問題点

  • 無料のTwitterDeveloperアカウントを所有していたが、取得できるツイートに制限が多く、
    かといって自動化のためだけにに高額なプレミアム契約はできない
  • そこで以下のサードパーティライブラリ"GetOldTweet"を使って
    API無しで任意の数と時期のツイートを取得できるプログラムを作成
    https://github.com/Jefferson-Henrique/GetOldTweets-python
    => ある日からプログラムがエラーを吐くようになり、調べたところIssueが挙げられていた
    バグが解消される見込みがしばらくはなさそう

  • 困っていたところ、Issueにインドのエンジニアがコメントしていた
    以下のリポジトリを発見
    https://github.com/itsayushisaxena/Get_Old_Tweets-Python
    どうやらソースコードを見ると、TwitterStandardAPIのアカウントは必要だが、
    tweepyとsnscrapeを組み合わせて、これまでと同じように欲しい範囲と数の
    ツイートが取得できそう

ということで作ったもの

  • DockerでPython3の動作する環境構築
  • PythonでTwitterAPIを使ったスクレイピング
  • 担当の方にdockerコマンドを意識させずにターミナルから使えるように
    自動化するシェルスクリプト

ソースコードはこちら
https://github.com/hikkymouse1007/GetTweets_pub

今回は、案件担当の方のPCで動かせてかつ、難しい操作を排除できる仕組みを
考えて作りました。

そこでDockerとシェルスクリプトによってコンテナ起動・ツイート取得・CSV作成
までを実行してくれる一連の流れを作ってみました。

ディレクトリ構造は以下のようになっております。

.
├── Dockerfile
├── Makefile
├── README.md
├── command
│   └── twitter //シェルスクリプト
├── docker-compose.yml
└── src
    ├── csv_files //ここにCSVを出力
    └── got_v2.py //Pythonのソースコード

Dockerfile, docker-compose

python3の動くコンテナのレシピは以下の記事を参考にさせていただきました。
https://qiita.com/reflet/items/4b3f91661a54ec70a7dc
tweepyが3.9に対応していないため、今回はpython3.8のバージョンを指定しました。

pythonの動作環境と必要なライブラリをインストールします。

# Dockerfile
FROM python:3.8
USER root

RUN apt-get update
RUN apt-get -y install locales && \
    localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
RUN apt-get -y install sudo
RUN sudo apt-get update && apt-get install -y cowsay fortunes
ENV PATH $PATH:/usr/games
RUN echo $PATH

ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm

RUN apt-get install -y vim less
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN pip install requests requests_oauthlib
RUN pip install pandas
RUN pip install IPython
RUN pip install twitter
RUN pip install tweepy
RUN pip install snscrape
# docker-compose.yml
version: '3'
services:
  python3:
    restart: always
    build: .
    container_name: 'python3'
    working_dir: '/root/'
    tty: true
    volumes:
      - ./src:/root/src

got.py

TwtterAPIにアクセスし、ツイートを取得するためのソースコードです。
基本のソースコードはこちらのリポジトリから拝借しました。
https://github.com/itsayushisaxena/Get_Old_Tweets-Python

twitterStandardAPIのアカウント発行時に貰う以下の情報を入力してください。

定数名 入力するキーの種類
TWITTER_CLIENT_KEY APIキー
TWITTER_CLIENT_SECRET APIシークレットキー
TWITTER_CLIENT_ID_ACCESS_TOKEN アクセストークン
TWITTER_CLIENT_ID_ACCESS_TOKEN_SECRET シークレットトークン

簡単な流れとしては、後述するシェルスクリプトからdockerコンテナ内に
環境変数を渡し、pythonで環境変数からハッシュタグなどの情報を読み取り、
tweepyとsnscrapeを使って、tweetを取得。
取得したツイートをCSVファイルに出力といった処理を実行します。

import tweepy
import csv
import os
import snscrape.modules.twitter as sntwitter
import sys
sys.dont_write_bytecode = True

#ENV_VALUES
tag = os.environ["TAG"]
since_date = os.environ["FROM"]
until_date =  os.environ["UNTIL"]
tweet_count = os.environ["NUM"]

#Provide your own credentials here.
TWITTER_CLIENT_KEY = '####################'
TWITTER_CLIENT_SECRET = '########################'
TWITTER_CLIENT_ID_ACCESS_TOKEN = '####################################'
TWITTER_CLIENT_ID_ACCESS_TOKEN_SECRET = '################################'

auth = tweepy.OAuthHandler(TWITTER_CLIENT_KEY, TWITTER_CLIENT_SECRET)
auth.set_access_token(TWITTER_CLIENT_ID_ACCESS_TOKEN, TWITTER_CLIENT_ID_ACCESS_TOKEN_SECRET)
api = tweepy.API(auth,wait_on_rate_limit=True)

#pip install snscrape
csvFile = open('/root/src/csv_files/%s_from_%s_to_%s_%s_tweets.csv' %(tag, since_date, until_date, tweet_count), 'a')
csvWriter = csv.writer(csvFile)
maxTweets = int(tweet_count)  # the number of tweets you require
print('%s since:%s until:%s' % (tag, since_date, until_date))
for i,tweet in enumerate(sntwitter.TwitterSearchScraper('%s' % tag +'since:%s until:%s' % (since_date, until_date)).get_items()) :
        if i > maxTweets :
            break
        csvWriter.writerow([tweet.date, tweet.username, tweet.content]) #If you need more information, just provide the attributes

シェルスクリプト

ここではtwiiterという名前のコマンドのパスを通すことで、検索したい条件に一致する
ツイートを取得するための処理を全て実行させます。
ここで標準入力されたパラメータはDockerコンテナ内に環境変数として渡されます。

command/twitter

#!/bin/sh
echo "次のデータを入力してenterを押してください"
read -p "ハッシュタグ(eg. #テスト): " str1
read -p "データ取得開始日(eg. 2020-08-10): " str2
read -p "データ取得終了日(eg. 2020-08-20): " str3
read -p "取得ツイート件数(eg. 100): " str4
TAG=$str1 FROM=$str2 UNTIL=$str3 NUM=$str4
echo "入力されたデータ"
echo $TAG $FROM $UNTIL $NUM

ANIMALS=("cheese" \
         "cock" \
         "dragon-and-cow" \
        "ghostbusters" \
        "pony" \
        "stegosaurus" \
        "turtle" \
        "turkey" \
        "gnu"\
        )
ANIMAL=${ANIMALS[$(($RANDOM % ${#ANIMALS[*]}))]}

docker-compose -f ~/path/to/docker-compose.yml \
    run \
    --rm \
    -e TAG=$TAG \
    -e FROM=$FROM \
    -e UNTIL=$UNTIL \
    -e NUM=$NUM \
    -e ANIMAL=$ANIMAL \
    python3 \
    /bin/bash -c "python /root/src/got_v2.py && cowsay -f $ANIMAL “ツイートを収集したよ” "

FILENAME="${TAG}_from_${FROM}_to_${UNTIL}_${NUM}_tweets.csv"
echo $FILENAME
mkdir -p ~/Desktop/twitter_csv_files
cp src/csv_files/$FILENAME ~/Desktop/twitter_csv_files
open ~/Desktop/twitter_csv_files/$FILENAME

Makefile

make pathでディレクトリの作成・twitterコマンドの作成を一発で実行してくれます。
makeコマンドは本リポジトリのルートディレクトリで実行します。
今回はユーザディレクトリの直下にcommadというディレクトリを作成し、
そこにtwitterコマンド用のスクリプトファイルを設置します。
make rm-pathでパスを削除可能。

docker-path:
    @echo $(PWD)
path:
    @mkdir ~/command
    @cp ./command/twitter ~/command/twitter
    @ln -si ~/command/twitter /usr/local/bin
    @chmod 777 ~/command/twitter
rm-path:
    @rm -rf ~/command
    @rm /usr/local/bin/twitter 

実際に動かしてみた

今回作成したプログラムを動かしてみた動画を以下に記載します。

output1

おまけ

先程の動画に謎の動物が入っていますが、こちらはcowsayというプログラムを
今回作成したDockerイメージにインストールしています。
作業者が単調な作業に飽きないように可愛い動物がランダムにCSVファイルの作成完了を
教えてくれるようにしました。
シェルスクリプトに書いた動物名をdockerの起動時にランダムに環境変数として渡し、
スクリプトの終盤にcowsayコマンドを実行させています。

スクリーンショット 2020-12-15 22 20 56
スクリーンショット 2020-12-15 22 21 04

ここに書いた動物以外にも色々あるので、興味のある方は調べて追加してみてください。

参考記事

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

Lambda on Terraform Container

はじめに

この記事はterraform Advent Calendar 2020の15日目です。

もともとTerraformのテストについてつらつら書こうとしましたが、re:Invent2020でLambdaのコンテナサポートされました
今回re:Invent2020で発表された中で、比較的反響がでかかった発表じゃないでしょうか
この波に乗らねば、ということで、Terraformをコンテナ化してLambdaで実行できるようにしてみました
※決してLambdaをTerraformで構築する記事ではありません

構成

今回の構成は下記です
lambda_terraform.png

開発ではCloud9を利用して、内部でコンテナを使って確認を行い、本番ではECRにイメージをアップしてLambdaから利用するという形になります
今回は時間がなかったのでAPI Gatewayは未使用です

ユースケース

ユースケースとしては下記の2つほどあるかと思ってます

  • 共通実行基盤
  • 監視

共通実行基盤としては、よくTerraformの実行基盤で悩む人がいると思うのですが、こちらを今回作成するLambdaを共通基盤とするのも一つの案かと思います
今回はただコードを持ってきて叩いてますが、これをCodeCommitやGithubで管理してるコードを引っ張ってくるようにすれば、最新コードをとってデプロイできるかと思います

監視としては、よくコードとリソースが乖離している問題があると思いますが、今回のLambdaを利用すれば、乖離したら検知できるようにできます。今回の作成したAPIを叩けば追加・変更・削除のリソース数が取れるので、PrometheusでもDatadogでも利用すれば0じゃない時にアラートを出せるかと思います

リポジトリ

対象のリポジトリは下記です
https://github.com/Yusuke-Shimizu/terraform_on_lambda
記事読むのだるいって人はcloneして使ってください
Dockerfileと.envrcの値は各自修正してご利用ください(アクセスキーとシークレットキー書いたままプッシュしちゃダメだよ!)

コンテナイメージ

コンテナをLambdaに乗っける場合、下記のどちらかで構築する必要があります

ランタイムAPIは必要なAPIを作る必要があるので、柔軟性はありますが手間が多いです
ベースイメージの方が、FROMに入れて追加していくだけで簡単なため、今回はこちらを利用します

ベースイメージの選定

今回はPythonを利用してTerraformを実行していきます
Terraform自体がGoなので、Goでやろうとしたのですが、いかんせんGoの経験が少ないのと、Lambda on Goの情報も少ないので断念しました。。
Amazon ECR Public Galleryを参考に下記のようにベースイメージを決めました

FROM public.ecr.aws/lambda/python:3.8
...

ちなみに、このFROMの public.ecr.aws もre:Invent2020で発表されたECR Publicです
簡単にいうと、AWS版のDocker Hubです

Terraformのインストール

上記のPythonイメージはPythonの実行しか出来ないため、Terraformを実行できるようにする必要があります
そのため、下記のようにzipをインストールしてterraformの実行をできるようにします

ARG terraform_version="0.14.2"
ADD https://releases.hashicorp.com/terraform/${terraform_version}/terraform_${terraform_version}_linux_amd64.zip terraform_${terraform_version}_linux_amd64.zip
RUN yum -y install unzip wget
RUN unzip ./terraform_${terraform_version}_linux_amd64.zip -d /usr/local/bin/
RUN rm -f ./terraform_${terraform_version}_linux_amd64.zip
ENV TF_DATA_DIR /tmp

実行ディレクトリ

Lambdaは実行するとき、/var/taskディレクトリで実行するのですが、基本的にこのディレクトリでファイルを作成したり修正することはできません
参考:https://stackoverflow.com/questions/45608923/aws-lambda-errormessage-errno-30-read-only-file-system-drive-python-quic

そのため、最後の ENV TF_DATA_DIR /tmp でTFのデータ出力先を変更してます
参考:https://www.terraform.io/docs/commands/environment-variables.html

Dockerfileの残り

あとはAWSのキーや必要なファイルをコピーしてhandlerを実行するだけでDockerfileは完了です
AWSキーは本来はLambdaのロールを利用すれば良いのですが、ローカル検証も合わせて実行したいので、とりあえずで入れてます
本来はここもsts化するなり外部から環境変数を注入するタイプにしたほうが良いと思います

ENV AWS_ACCESS_KEY_ID 【アクセスキー】
ENV AWS_SECRET_ACCESS_KEY 【シークレットキー】

# copy files
COPY app.py ${LAMBDA_TASK_ROOT}
COPY main.tf ${LAMBDA_TASK_ROOT}

CMD [ "app.handler" ]

Python

Pythonコードとしては、handle関数をapp.pyに作成すれば良いです
やってることとしては、tfファイルを/tmpにコピーして、terraformを初期化してplanを実行してます
/tmpに移動する理由としては、 $ terraform init を実行したタイミングで、カレントディレクトリにファイルを作成するのですが、上記で説明したように/tmp以外でファイル作成が出来ないので、移動してから実行としています

def handler(event, context): 
    cmd('./', "cp ./main.tf /tmp/")
    cmd('/tmp/', "terraform init --upgrade")
    result = cmd('/tmp/', "terraform plan")
    last_result = extraction(result)
    return last_result

planの結果は下記でaddやchangeの数を正規表現でとってきて、dictで返すようにしてます

def extraction(plan):
    print(plan)
    change_state = {'add': 0, 'change': 0, 'destroy': 0}
    if "No changes" in plan:
        return change_state
    elif "Plan" in plan:
        line_extraction = re.findall("Plan.*", plan)
        result = "".join(line_extraction)

        change_state['add'] = int(re.findall('(\d)\sto\sadd', result)[0])
        change_state['change'] = int(re.findall('(\d)\sto\schange', result)[0])
        change_state['destroy'] = int(re.findall('(\d)\sto\sdestroy', result)[0])
        return change_state
    elif "Error" in plan:
        line_extraction = re.findall("Error.*", plan)
        result = "".join(line_extraction)
        return result
    else:
        result = "予期せぬエラーです。ログを確認して下さい。"
        return result

Terraform

Terraformのコードは簡単にS3バケットの作成にしました

provider "aws" {
  region  = "ap-northeast-1"
}

resource "aws_s3_bucket" "default" {
  bucket = "created-by-lambda"
  acl    = "private"
}

ローカル(Cloud9)での実行

下記のように実行して、コンテナを起動します

$ export REPOSITORY_NAME=【リポジトリ名】
$ docker build -t ${REPOSITORY_NAME} .
$ docker run --rm -p 9000:8080 ${REPOSITORY_NAME}

そして、ローカルの9000番ポートを叩くと、下記のようにS3が作成されるため、addに1が入った状態でかえってきました

$ curl -sd '{}' http://localhost:9000/2015-03-31/functions/function/invocations | jq .
{
  "add": 1,
  "change": 0,
  "destroy": 0
}

ECRへアップ

上記で設定した REPOSITORY_NAME の名前でECRのリポジトリを作成します
その後、下記でイメージをプッシュすると、ECRへイメージがlatestで上がっています

$ export AWS_ACCOUNT_ID=$(aws sts get-caller-identity | jq -r .Account)
$ export REGISTRY_URL=${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${REGISTRY_URL}
$ docker tag ${REPOSITORY_NAME} ${REGISTRY_URL}/${REPOSITORY_NAME}
$ docker push ${REGISTRY_URL}/${REPOSITORY_NAME}

Lambdaで実行

Lambdaの作成

まず実行基盤のLambdaを作成します
といっても、コンソールからポチポチやっていけば良いです
自動化するならsamかCDKが良いかと思います(個人的にはCDK推し)

コンソールからだと、関数の作成からコンテナイメージを選択し、関数名適当に決めてコンテナイメージも「画像を参照」ボタンから対象のリポジトリのlatestを選択すればOKです

一点注意しないといけないのが、今回のTerraformの実行は時間がかかってメモリも多少食うので、下記のように設定してください
タイムアウト:1分
メモリ:4GB

実行

さて、準備が出来たので、ようやくLambdaからコンテナのTerraformを叩いてみましょう
こちらもコンソールから軽くテスト出来るので、こちらで試してみましょう

テストイベントは何でも良いので、デフォのものを利用して実行してみました
すると、下記のようにjsonでTerraformのplan結果が帰ってきました
スクリーンショット 2020-12-15 18.50.57.png
これを利用すれば、外部からこのAPIを叩いて、現状のリソースとの差分がないかを監視することが出来ます

さいごに

Lambdaがコンテナで実行できるようになったので、Terraformを入れて実行してみました
今後は、API Gatewayと連携してAPI化して、Terraformの監視をしたり、planだけじゃなくapplyを実行するアプリも作っていきたいですね

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

docker volume調べた

dockerのvolumeについて調べた。

・dockerコンテナの内部データは削除時に消える。
・データ永続化のためにホストやdocker volumeというdockerが管理する領域をマウントする。
・マウントに指定したファイルやディレクトリはコンテナ削除した後も残る(データがコンテナ外部に保存されている感じ)
・マウントには二種類ある。hostのディレクトリを同期するタイプとdockerが管理するvolume空間をマウントするやつ。
・前者をバインドマウント, 後者をvolumeという。
・前者は開発中のコードをコンテナ内に同期して開発するのに使ったりする(エディタで編集しながらコンテナ内にコードを配置できる)。後者はホスト側で管理しなくても良い時に使う(mysqlのデータとかnginxとphpを繋ぐソケットファイルとか)

使い方 基本

docker run実行時に-v オプションでマウントできる
書式は-v ホストpath:コンテナpath。
ホストのpathに相対pathは使えないので絶対pathで書くこと。

↓a.txtを同期させる
$ docker run -it --rm -v ~/Documents/test/docker/volume_test/a.txt:/a.txt centos:centos8
[root@defef339acca /]# ls -la 
-rw-r--r--   1 root root    4 Dec 15 12:02 a.txt
略

ホスト側でa.txtを更新するとコンテナ側にも反映される。
また、コンテナ側から変更してもホスト側に反映される。

$ echo "aaa" > a.txt
↓コンテナ側
[root@defef339acca /]# cat a.txt 
aaa

ホスト側でa.txtを削除するとコンテナ側でバグる。ls -la だと存在するが、ファイルへのアクセスはできなくなる。

[root@defef339acca /]# ls -la 
-?????????   ? ?    ?       ?            ? a.txt
[root@defef339acca /]# cat a.txt 
cat: a.txt: No such file or directory

コンテナ側でa.txtを削除しようとするとエラーになる。ファイル編集は可能。

[root@57c0c3b18570 /]# rm a.txt 
rm: remove regular file 'a.txt'? yes
rm: cannot remove 'a.txt': Device or resource busy

a.txtがホスト側にない状態でマウントしようとすると、a.txtという名前でディレクトリが作成され、それがマウントされる。

[root@47850246319a /]# ls -la 
drwxr-xr-x   2 root root   64 Dec 15 12:12 a.txt

ファイルだけでなく、ディレクトリもマウントできる。

$ mkdir hoge
$ touch hoge/a.txt
$ docker run -it --rm -v ~/Documents/test/docker/volume_test/hoge:/hoge centos:centos8
[root@3c0109bbdf3c /]# ls -la
drwxr-xr-x 3 root root 96 Dec 15 12:24 hoge

使い方 複数volumeマウント

-vオプションは複数使用できる。

$ docker run -it --rm -v ~/Documents/test/docker/volume_test/a.txt:/a.txt -v ~/Documents/test/docker/volume_test/b.txt:/b.txt centos:centos8
[root@19354f136991 /]# ls -la 
-rw-r--r--   1 root root    0 Dec 15 12:14 a.txt
-rw-r--r--   1 root root    0 Dec 15 12:14 b.txt

使い方 dockerボリュームをマウントする

-vオプションの値を変えることでdockerボリュームをマウントできる
ホストのディレクトリをマウントする際は絶対pathを使用したが、dockerボリュームをマウントする際はvolume名を指定する
-v volume名:コンテナpath

$ docker run -it --rm -v hoge:/hoge centos:centos8 
[root@31ac4d5be7fa /]# ls -la 
drwxr-xr-x   2 root root 4096 Dec 15 12:40 hoge
[root@31ac4d5be7fa /]# touch hoge/a.txt
[root@31ac4d5be7fa /]# exit

--rmをつけてコンテナ起動しているのでexit時にコンテナは破棄される。
↓再度コンテナを作成する
$ docker run -it --rm -v hoge:/hoge centos:centos8 

↓hogeはdockerボリュームになっているので永続化されていることが分かる。
[root@4c03049e11c9 /]# ls hoge
a.txt

docker volumeをコマンドで確認する

$ docker volume ls 
DRIVER    VOLUME NAME
local     hoge

dockerボリュームをマウントする際はvolume名を指定しなくても良い。
volume名を指定しないvolumeを匿名ボリュームという。
その場合は、コンテナpathだけ指定する。
-v コンテナpath

$ docker run -it --rm -v /fuga centos:centos8 
[root@6806bc7b9bcf /]# ls -la 
drwxr-xr-x   2 root root 4096 Dec 15 12:44 fuga

ただし、volume名を指定しないとvolume名はハッシュ値となり何のvolumeなのか分かりづらくなるのでおすすめしない。

$ docker volume ls 
DRIVER    VOLUME NAME
local     16fba3a332888fcd049a7dd569d6d27f360cc07ef58000bfee93f25e85416df9

また、volume名をつけない + --rm付きで起動した場合、コンテナ内でディレクトリは作成されたがvolumeは作成されず永続化されなかった。--rmなしでやるとvolumeが作成された。
(あれ、こんな挙動だったっけ?)

docker volumeコマンド

docker volumeコマンドには以下がある。

$ docker volume 

Usage:  docker volume COMMAND

Manage volumes

Commands:
  create      Create a volume
  inspect     Display detailed information on one or more volumes
  ls          List volumes
  prune       Remove all unused local volumes
  rm          Remove one or more volumes

Run 'docker volume COMMAND --help' for more information on a command.

ls でvolume一覧を表示する。
prune で未使用のローカルvolume削除
rm で特定のvolume削除

docker volume inspectについて

inspect = 検査。
volumeの詳細情報を教えてくれるらしい。
Mountpointを見るとvolumeがどこに格納されているか分かる。このpathはmac上のpathではなくdocker engineが動いているvm上のpath。

$ docker volume ls 
DRIVER    VOLUME NAME
local     16fba3a332888fcd049a7dd569d6d27f360cc07ef58000bfee93f25e85416df9
local     bbb
local     hoge

$ docker volume inspect hoge
[
    {
        "CreatedAt": "2020-12-15T12:40:52Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/hoge/_data",
        "Name": "hoge",
        "Options": null,
        "Scope": "local"
    }
]

参考

公式
https://matsuand.github.io/docs.docker.jp.onthefly/storage/volumes/

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

dokcer-compose run で Please run `yarn install –check-files` to update.エラー

はじめに

Docker環境を構築してる際にエラーが出たので、その解決方法をメモととして記事に残します。

Docker内の環境

ruby 2.6.3-alpine3.10
rails 6.0.1

事象

dokcer-compose run を実行すると下記エラーコードが出た。

エラーコード

Please run `yarn install –check-files` to update.

対処方法

config/environmet/development.rb に下記のyarnの設定に関するコードを追加する

config.webpacker.check_yarn_integrity = false

以上で解決できると思います。

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

既存のRails6プロジェクト(API)をDockerで開発(少しイメージの軽量化)

本記事は東京学芸大学 櫨山研究室 Advent Calendar 2020の15日目の記事になります.

はじめに

本記事はRails6が出たのでその環境をDocker化し,また少しイメージを軽くする方法です(RDBはMysql8.0を想定).
起動を確認するところまでを記事にしています.
railsのプロジェクトはローカルで作成したもの使用しています(私はローカルで以下のように作成しています).

console
$ rails new api --api -d mysql

フロントとバックエンドを分けて開発を行いたかったので,Rails6プロジェクトをapiモードで作成しています.
また本記事ではフロントエンド側の記述はしていません.
普段はRailsを使用していないので,メモとして残します?‍♂️

開発環境

  • MacOs:
  • Docker:
  • docker-compose:
  • ruby:2.6.3(rbenv で管理: 参考記事)
  • Rails 6
  • Mysql8.0

1. 準備

今回は既存のRails6プロジェクト(apiモード)をDockerしていきます.

1.1 ディレクトリ構成

今回はdocker-compose.ymlやDockerfileが階層的になっているため,makeで実行していきます.

1.2 ファイルの準備

Makefile

Makefile
.PHONY: build
build:
    docker-compose -f docker/docker-compose.yml build

.PHONY: start
start:
    docker-compose -f docker/docker-compose.yml up --remove-orphans

.PHONY: start.background
start.background:
    docker-compose -f docker/docker-compose.yml up -d --remove-orphans

.PHONY: stop
stop:
    docker-compose -f docker/docker-compose.yml down
  • buildでパッケージの取得
  • startでアプリケーションを実行
  • start.backgroundでバックグラウンドでアプリケーションを実行
  • stopでアプリケーションを停止

docker-compose.yml

docker/docker-compose.yml
version: "3"
services:
  api:
    container_name: api
    build:
      context: ../.
      dockerfile: ./docker/api/Dockerfile
    tty: true
    working_dir: /api
    volumes:
      - ../api:/api
    env_file: # dockerディレクトリ直下にapi.envファイルを作成し環境変数を設定する.
      - api.env
    ports:
      - "8000:3000" # フロントエンド側が3000番開放になるので変更
    depends_on:
      - db
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    networks:
      - sample-rails-docker-network

  db:
    container_name: db
    build:
      context: ../.
      dockerfile: ./docker/mysql/Dockerfile
    volumes:
      - ./mysql/db:/docker-entrypoint-initdb.d
    env_file: # dockerディレクトリ直下にmysql.envファイルを作成し環境変数を設定する.
      - mysql.env
    networks:
      - sample-rails-docker-network

networks: # 名前解決できるようにネットワークの設定
  sample-rails-docker-network:
    driver: bridge

Dockerfile:Mysql関連

docker/mysql/my.cnf
FROM mysql:8.0 # mysq;8.0を使用する

EXPOSE 3306
ADD docker/mysql/my.cnf /etc/mysql/conf.d/my.cnf

CMD ["mysqld"]

3306番ポートを開放し,設定ファイルを読み込みます.

docker/mysql/Dockerfile
[mysqld]
character-set-server=utf8
default_authentication_plugin=mysql_native_password

[mysql]
default-character-set=utf8

[client]
default-character-set=utf8

mysqlのvolumeはdocker/mysql/dbに設定しているので,必要な場合はそちらに配置してください.

Dockerfile:Rails 6関連

docker/api/Dockerfile
FROM ruby:2.6.3-alpine as builder # ruby:2.6.3-alpineを使用する,bundle installするためにbuilderとして使用 
RUN apk update && \
  apk upgrade && \
  apk --no-cache add mysql-client mysql-dev tzdata build-base && \
  gem install bundler

WORKDIR /tmp
COPY api/Gemfile Gemfile
COPY api/Gemfile.lock Gemfile.lock
RUN bundle install

FROM ruby:2.6.3-alpine
ENV LANG ja_JP.UTF-8 
RUN apk --update add mysql-client mysql-dev tzdata nodejs && \
  gem install bundler

ENV APP_HOME /api
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY api $APP_HOME

今回使用するimageはruby:2.6.3-alpineですが,それぞれの環境で変更しても大丈夫です.
マルチステージングにし,bundle installにだけ必要なもの(build-base)を省くようにする.(apk delで消しても問題ない.)
今回はmysqlが動く最低限の環境になっています.

Rails関連

Gemfile

api/Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.6.3'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3', '>= 6.0.3.2'

# database
gem 'mysql2'

# Use Puma as the app server
gem 'puma', '~> 4.1'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  gem 'listen', '~> 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

Gemfileは,mysql2rack-corsを設定しています.
rack-corsはCORSの設定です.

こちらもそれぞれの開発環境に合わせて変更してください(変更時にはDockerfileでapk add で加えるパッケージもも変更する必要があるので気をつけてください).

cors.rb

api/config/initializers/cors.rb
# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.

# Read more: https://github.com/cyu/rack-cors

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

CORSの設定は全てを許可しているので,それぞれで変更しましょう.

ルーティング(routes.rb)

api/config/routes.rb
Rails.application.routes.draw do
  namespace 'api' do
    namespace 'v1' do
      resources :hoges
    end
  end
end

APIなのでルーティングをVersion管理しやすくしておきます.

コントローラー(hoges_controller.rb)

api/app/controllers/api/v1/hoges_controller.rb
module Api
  module V1
    class HogesController < ApplicationController
      def index
        render json: { status: 'SUCCESS', message: 'Hello Rails!'}
      end
    end
  end
end

2. ビルドとアプリケーションの実行

2.1 rails new

今回は既存のプロジェクトを使用したので,省略します.(参考記事)

2.2 rake db:create

今回はDBの接続確認はしないので省略します.
参考までにapi直下で,以下のコマンドで環境を作成できます.

console
$ rails g model post title:string text:text
$ rails g controller posts
$ rails db:create
$ rake db:migrate

2-3. 依存関係の解決&imageの取得

console
$ make build

apk add [パッケージ]bundle installをしますので,少し時間がかかります.

2-4. アプリケーションの実行

console
$ make start

Railsとmysqlが起動します.

起動の確認

起動確認

スクリーンショット 2020-12-15 21.09.33.png

いつもの画面が出てくれば問題なく起動できています.

api化の確認

console
% curl http://localhost:8000/api/v1/hoges
{"status":"SUCCESS","message":"Hello Rails!"}%

問題なくできています!!

2.5 他のイメージと比較

サイズはプロジェクト事態が存在しての大きさになります.

イメージ サイズ
ruby:2.6.3 1.1GB
ruby:2.6.3-alpine 387MB
ruby:2.6.3-alpine(ビルドベース削除) 264MB

bundle install時に必要なbuild-baseを削除するだけでかなり削減されています!

おわりに

Rails6が出たので,そのAPIプロジェクトのDocker化,微量ながら軽量化をしました.
特に元イメージとalpineの差はとても大きいので,イメージサイズを減らすことができてよかったです.
これで好きなフロントエンドを使用してRailsのAPIを使用します.

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

Rails6プロジェクト(API)をDockerで開発(少しイメージの軽量化)

本記事は東京学芸大学 櫨山研究室 Advent Calendar 2020の15日目の記事になります.

はじめに

本記事はRails6が出たのでその環境をDocker化し,また少しイメージを軽くする方法です(RDBはMysql8.0を想定).
起動を確認するところまでを記事にしています.
railsのプロジェクトはローカルで作成したもの使用しています(私はローカルで以下のように作成しています).

console
$ rails new api --api -d mysql

フロントとバックエンドを分けて開発を行いたかったので,Rails6プロジェクトをapiモードで作成しています.
また本記事ではフロントエンド側の記述はしていません.
普段はRailsを使用していないので,メモとして残します?‍♂️

開発環境

  • MacOs:
  • Docker:
  • docker-compose:
  • ruby:2.6.3(rbenv で管理: 参考記事)
  • Rails 6
  • Mysql8.0

1. 準備

今回は既存のRails6プロジェクト(apiモード)をDockerしていきます.

1.1 ディレクトリ構成

今回はdocker-compose.ymlやDockerfileが階層的になっているため,makeで実行していきます.

1.2 ファイルの準備

Makefile

Makefile
.PHONY: build
build:
    docker-compose -f docker/docker-compose.yml build

.PHONY: start
start:
    docker-compose -f docker/docker-compose.yml up --remove-orphans

.PHONY: start.background
start.background:
    docker-compose -f docker/docker-compose.yml up -d --remove-orphans

.PHONY: stop
stop:
    docker-compose -f docker/docker-compose.yml down
  • buildでパッケージの取得
  • startでアプリケーションを実行
  • start.backgroundでバックグラウンドでアプリケーションを実行
  • stopでアプリケーションを停止

docker-compose.yml

docker/docker-compose.yml
version: "3"
services:
  api:
    container_name: api
    build:
      context: ../.
      dockerfile: ./docker/api/Dockerfile
    tty: true
    working_dir: /api
    volumes:
      - ../api:/api
    env_file: # dockerディレクトリ直下にapi.envファイルを作成し環境変数を設定する.
      - api.env
    ports:
      - "8000:3000" # フロントエンド側が3000番開放になるので変更
    depends_on:
      - db
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    networks:
      - sample-rails-docker-network

  db:
    container_name: db
    build:
      context: ../.
      dockerfile: ./docker/mysql/Dockerfile
    volumes:
      - ./mysql/db:/docker-entrypoint-initdb.d
    env_file: # dockerディレクトリ直下にmysql.envファイルを作成し環境変数を設定する.
      - mysql.env
    networks:
      - sample-rails-docker-network

networks: # 名前解決できるようにネットワークの設定
  sample-rails-docker-network:
    driver: bridge

Dockerfile:Mysql関連

docker/mysql/my.cnf
FROM mysql:8.0 # mysq;8.0を使用する

EXPOSE 3306
ADD docker/mysql/my.cnf /etc/mysql/conf.d/my.cnf

CMD ["mysqld"]

3306番ポートを開放し,設定ファイルを読み込みます.

docker/mysql/Dockerfile
[mysqld]
character-set-server=utf8
default_authentication_plugin=mysql_native_password

[mysql]
default-character-set=utf8

[client]
default-character-set=utf8

mysqlのvolumeはdocker/mysql/dbに設定しているので,必要な場合はそちらに配置してください.

Dockerfile:Rails 6関連

docker/api/Dockerfile
FROM ruby:2.6.3-alpine as builder # ruby:2.6.3-alpineを使用する,bundle installするためにbuilderとして使用 
RUN apk update && \
  apk upgrade && \
  apk --no-cache add mysql-client mysql-dev tzdata build-base && \
  gem install bundler

WORKDIR /tmp
COPY api/Gemfile Gemfile
COPY api/Gemfile.lock Gemfile.lock
RUN bundle install

FROM ruby:2.6.3-alpine
ENV LANG ja_JP.UTF-8 
RUN apk --update add mysql-client mysql-dev tzdata nodejs && \
  gem install bundler

ENV APP_HOME /api
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY api $APP_HOME

今回使用するimageはruby:2.6.3-alpineですが,それぞれの環境で変更しても大丈夫です.
マルチステージングにし,bundle installにだけ必要なもの(build-base)を省くようにする.(apk delで消しても問題ない.)
今回はmysqlが動く最低限の環境になっています.

Rails関連

Gemfile

api/Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.6.3'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3', '>= 6.0.3.2'

# database
gem 'mysql2'

# Use Puma as the app server
gem 'puma', '~> 4.1'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  gem 'listen', '~> 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

Gemfileは,mysql2rack-corsを設定しています.
rack-corsはCORSの設定です.

こちらもそれぞれの開発環境に合わせて変更してください(変更時にはDockerfileでapk add で加えるパッケージもも変更する必要があるので気をつけてください).

cors.rb

api/config/initializers/cors.rb
# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.

# Read more: https://github.com/cyu/rack-cors

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

CORSの設定は全てを許可しているので,それぞれで変更しましょう.

ルーティング(routes.rb)

api/config/routes.rb
Rails.application.routes.draw do
  namespace 'api' do
    namespace 'v1' do
      resources :hoges
    end
  end
end

APIなのでルーティングをVersion管理しやすくしておきます.

コントローラー(hoges_controller.rb)

api/app/controllers/api/v1/hoges_controller.rb
module Api
  module V1
    class HogesController < ApplicationController
      def index
        render json: { status: 'SUCCESS', message: 'Hello Rails!'}
      end
    end
  end
end

2. ビルドとアプリケーションの実行

2.1 rails new

今回は既存のプロジェクトを使用したので,省略します.(参考記事)

2.2 rake db:create

今回はDBの接続確認はしないので省略します.
参考までにapi直下で,以下のコマンドで環境を作成できます.

console
$ rails g model post title:string text:text
$ rails g controller posts
$ rails db:create
$ rake db:migrate

2-3. 依存関係の解決&imageの取得

console
$ make build

apk add [パッケージ]bundle installをしますので,少し時間がかかります.

2-4. アプリケーションの実行

console
$ make start

Railsとmysqlが起動します.

起動の確認

起動確認

スクリーンショット 2020-12-15 21.09.33.png

いつもの画面が出てくれば問題なく起動できています.

api化の確認

console
% curl http://localhost:8000/api/v1/hoges
{"status":"SUCCESS","message":"Hello Rails!"}%

問題なくできています!!

2.5 他のイメージと比較

サイズはプロジェクト事態が存在しての大きさになります.

イメージ サイズ
ruby:2.6.3 1.1GB
ruby:2.6.3-alpine 387MB
ruby:2.6.3-alpine(ビルドベース削除) 264MB

bundle install時に必要なbuild-baseを削除するだけでかなり削減されています!

おわりに

Rails6が出たので,そのAPIプロジェクトのDocker化,微量ながら軽量化をしました.
特に元イメージとalpineの差はとても大きいので,イメージサイズを減らすことができてよかったです.
これで好きなフロントエンドを使用してRailsのAPIを使用します.

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

【入門】はじめての Docker Desktop for Mac のインストールと CentOS の仮想環境構築のセットアップ


【入門】はじめての Docker Desktop for Mac のインストールと CentOS の仮想環境構築のセットアップ

最近では、Linuxの環境を利用したいときに 無償の Docker Desktop (for Mac and Windows ) で Linuxコンテナを使うというのがお手軽になりつつあります。
今回、MacBookに Doker Desktop for Mac をインストールし、CentOSの仮想環境を構築する手順について、はじめがからていねいにまとめて、ご紹介します。

また、途中で Dockerコマンドなどについても詳しく説明していくので、 Docker初心者で CentOSの仮想環境を構築したいという方は、是非参考にしていただければ幸いです。

  • 前提条件
  • 事前準備
  • Docker Desktop for Mac のインストール
  • CentOS コンテナ (仮想環境) の構築
  • CentOS コンテナ (仮想環境)の整備
  • 作業済み CentOS コンテナ (仮想環境)の保存

なお、Windows 10のノートPCに Doker Desktop for Windows をインストールし、CentOSの仮想環境を構築する手順については「【入門】はじめての Docker Desktop for Windows のインストールと CentOS の仮想環境構築のセットアップ」の方を参照していただければと思います。

前提条件

システム要件の詳細はこちらを参照してください。

なお、今回インストールしたデバイスの仕様とWindowsの仕様は次の通りです。
image.png
- MacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports)
- プロセッサ 2.9 GHz デュアルコアIntel Core i5
- メモリ 16 GB 2133 MHz LPDDR3
- エディション Windows 10 Pro
- macOS Big Sur バージョン 11.0.1

Docker Desktop for Mac のインストール

インストール

インストーラーをダウンロードする

インストールは次のサイトで Docker for Mac インストーラーをダウンロードしてインストールしていくことができます。

https://www.docker.com/products/docker-desktop

image.png
[Download for Mac] のダウンロードボタンをクリックします。

image.png
これで、[Docker.dmg] ファイルがダウンロードできます。ファイルは1つだけです。

ここまでで MacBookマシンへのダウンロードまでが完了です。

MacBookマシンへのインストールする

次はインストールを行います。ダウンロードした[Docker.dmg] ファイルをダブルクリックしてインストーラを実行します。
image.png
左側の「Docker」のアイコンを右側の「Appllications」フォルダにドラッグ&ドロップします。
image.png
image.png
アプリケーション一覧を確認し、Docker.appが入っていたらインストール完了です。

Dockerの起動

アプリケーションから「Docker.app」をクリックして起動します。

初回起動時にセキュリティ警告が出たら「開く」をクリックします。
image.png
ネットワークコンポーネントへのアクセスを問われるウィンドウが出たら「OK」を押して許可のためMacBook本体のパスワードを入力します。
image.png
初回起動時にはダッシュボード上にチュートリアルが表示されます。とりあえず、「Skip tutorial」をクリックしてください。
スクリーンショット 2020-12-09 21.49.39.png
次のような画面が通常のDocker Dashboardです。
image.png
なお、ダッシュボードはDocker起動中であればナビゲーションからも起動できます。
image.png

動作確認

Docker のバージョンの確認をしてみる

まずは、インストールされた Docker のバージョンを確認してみましょう。
ターミナル を起動して、docker version コマンドでバージョン情報が表示されます。

$ docker version
Client: Docker Engine - Community
 Cloud integration: 1.0.4
 Version:           20.10.0
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        7287ab3
 Built:             Tue Dec  8 18:55:43 2020
 OS/Arch:           darwin/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.0
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       eeddea2
  Built:            Tue Dec  8 18:58:04 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Hello Worldを動かしてみる

では、早速、プログラマにはおなじみの hello world をやってみましょう。
docker run コマンドは、イメージからコンテナを起動するコマンドです。

$ docker run hello-world

上記のコマンドの場合、hello-world というイメージからコンテナを作成して起動するという意味になります。ただし、ローカルに hello-world イメージがないため、Docker デーモンが hello-world イメージを Docker Hub(Docker社が運営する、インターネット上でイメージを公開・共有したりする Docker Registry サービス)からダウンロードし、イメージからコンテナを起動します。一般的には、イメージはファイルシステムとアプリケーションやミドルウェア、実行時に必要とするパラメータから構成されます。

このコンテナは次のような標準出力を出して終了します。

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete 
Digest: sha256:1a523af650137b8accdaed439c17d684df61ee4d74feac151b5b337bd29e7eec
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
$

:warning: 注意 :warning:

次のようなエラーが発生した場合、Proxyなどのネットワーク関連の設定の問題の場合があります。

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
docker: Error response from daemon: Get https://registry-1.docker.io/v2/: dial tcp: lookup registry-1.docker.io on 192.168.65.1:53: no such host.
See 'docker run --help'.

Docker Desktopメニューで[setting]を選択し[setting]ダイアログの[Resources]タブの[PROXIES]でProxyの設定をしてみてください。

スクリーンショット 2020-12-14 23.02.55.png

CentOS コンテナ (仮想環境) の構築

先ほどの hello-world の例で、コマンドラインでイメージの取得とコンテナの作成・起動・実行が簡単にできることがおわかりいただけたと思います。
では、本題の CentOS のイメージを取得し、CentOS のコンテナ (仮想環境) を作成・起動させてみましょう。

CentOS の イメージの取得

イメージを取得する

CentOS のコンテナのイメージも Docker Hub の CentOS のリポジトリの中で、CentOS のバージョンごとにタグ付けされて管理されています。
それでは CentOS7 の Docker イメージを取得しましょう。 Docker イメージの取得は docker pull コマンドを利用します。なお、すでに Docker ID を取得しログインしているので、この DokerHub サービスを利用することができます

$ docker pull centos:centos7
centos7: Pulling from library/centos
2d473b07cdd5: Pull complete 
Digest: sha256:0f4ec88e21daf75124b8a9e5ca03c37a5e937e0e108a255d890492430789b60e
Status: Downloaded newer image for centos:centos7
docker.io/library/centos:centos7
$

これで CentOS イメージのダウンロードが終わりました。環境に依存しますが数十秒で終了します。なお、pull コマンドの「centos:centos7」の意味は、centosリポジトリの、centos7タグが付いたイメージという意味です。これを理解すればいろいろなリポジトリからいろいろなイメージをダウンロードできるようになります。

取得した イメージを確認する

取得した Docker イメージは docker images コマンドで一覧表示できます。

$ docker images
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
centos        centos7   8652b9f0cb4c   4 weeks ago     204MB
hello-world   latest    bf756fb1ae65   11 months ago   13.3kB
$ 

centos リポジトリの centos7 タグが付いたイメージが IMAGE ID 8652b9f0cb4c で、そして Hello Worldを動かしてみる時に使用した hello-worldリポジトリのlatestタグが付いたイメージが IMAGE ID bf756fb1ae65 で保管されていることを示しています。

CentOS コンテナ の作成・起動

コンテナ を作成・起動する

CentOS7 のイメージが取得できたら、さっそく CentOS コンテナを作成して起動してみましょう。

Docker イメージからコンテナを作成して起動するには docker run コマンドを利用します。次のように docker run コマンドを実行して、コンテナを作成・起動してみます。ここでは新しく作成するコンテナに "centos7f" という名前を付けています。CentOS7 のイメージから CentOS7 コンテナを作成し、同時にコンテナも起動し、そのタイミングで自動的にログインし、そのまま bash シェルに接続します。

$ docker run -it --name="centos7f" centos:centos7 /bin/bash
#

このように、いきなり Centos コンテナ起動し、ログインした状態になります。このまま exit コマンドを実行すれば、このコンテナは停止します。

# exit
exit
$

起動または存在しているコンテナの確認をする

docker ps コマンドを実行してみてください。

docker ps コマンドは、コンテナリストを表示・取得するコマンドです。現在起動または存在しているコンテナをリストにしてくれます。オプションなしの場合は、現在起動中のコンテナを表示してくれます。

$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
$

今回は、停止された状態なので、現在起動中のコンテナはありません。

下記のように -a、--allオプションをつけた場合は、停止中のコンテナを含めたすべてのコンテナを表示してくれます。

$ docker ps -a
CONTAINER ID   IMAGE            COMMAND       CREATED          STATUS                      PORTS     NAMES
2e57a6f562a8   centos:centos7   "/bin/bash"   3 minutes ago    Exited (0) 2 minutes ago              centos7f
1c5f75cb06e6   hello-world      "/hello"      27 minutes ago   Exited (0) 27 minutes ago             inspiring_chatelet
$

イメージの centos:centos7 からコンテナが"centos7f"という名前で作成・起動され、bin/shコマンドを実行し、現在は、停止した状態であることを示しています。

Centos コンテナ (仮想環境) の再開・接続

停止中のコンテナを再開する

停止したコンテナを docker start コマンドで再開できます。

"centos7f" を再開させます。

$ docker start centos7f
centos7f
$

docker ps コマンドでコンテナ"centos7f"が確かに再開していることを確認しましょう。

docker ps
CONTAINER ID   IMAGE            COMMAND       CREATED         STATUS          PORTS     NAMES
2e57a6f562a8   centos:centos7   "/bin/bash"   6 minutes ago   Up 36 seconds             centos7f

起動中のコンテナへ接続する

起動させたコンテナに接続して操作する場合は、docker attach コマンドまたは docker execコマンド を用います。

docker attach コマンドの場合は、次のようになります。

$ docker attach centos7f
#

ただし docker attach コマンドでコンテナに接続後、exit コマンドで接続から抜けると、コンテナが停止してしまいます。 コンテナを稼動させたままにしたい場合は、Ctrl + P、Ctrl + Q を続けて押して抜ける必要があります。

ここでは、Ctrl + P、Ctrl + Q を続けて押して、コンテナを稼動させたまま抜けてみます。次のようなメッセージが出力されます。

# read escape sequence
$

ここで、docker ps コマンドでコンテナの状況を確認してみましょう。
CentOS コンテナ "centos7f" は稼働中です。

$ docker ps -a
CONTAINER ID   IMAGE            COMMAND       CREATED          STATUS                      PORTS     NAMES
2e57a6f562a8   centos:centos7   "/bin/bash"   9 minutes ago    Up 4 minutes                          centos7f
1c5f75cb06e6   hello-world      "/hello"      34 minutes ago   Exited (0) 34 minutes ago             inspiring_chatelet

この状態であれば、再度、CentOS コンテナ (centos7f) に接続することができます。

今度は、docker attach コマンドではなく docker exec コマンドも用いてコンテナに接続して操作してみましょう。ただし、動作に微妙な違いがあります。

$ docker exec -it centos7f /bin/bash
#

docker attach コマンド と docker exec コマンドの動作の違いは次の通りです。

attach コマンド exec コマンド
稼働中のコンテナで起動している PID=1 のプロセスの標準入出力 (STDIN/STDOUT) に接続 (attach) する。 コンテナ内でシェルが動作していなければ接続することができない。
attachコマンドで抜けるとコンテナが停止してしまう exitコマンドで抜けるてもコンテナは停止しない

Centos コンテナ (仮想環境) の作成・起動 (バックグランド)

コンテナを作成しバックグラウンドで起動する

別の ターミナル を起動します。
次のように docker run コマンドを -d で実行して、コンテナを作成しコマンドプロンプトの裏でバックグラウンド起動してみましょう。

ここでは新しく作成するコンテナに "centos7b" という名前をつけています。
centos7 イメージからコンテナを作成してバックグラウンドで起動します。

Last login: Mon Dec 14 19:43:55 on ttys000

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
$ docker run -it -d --name="centos7b" centos:centos7 /bin/bash
9246cc7051bf88970fbeb84fc5aed0e508143f7737c5684514fe7d076eb99ff7
$

CentOS コンテナ (仮想環境) の停止・削除

コンテナの削除をするには、まずは動ているコンテナを停止させてから削除することになります。

では、docker ps コマンドで動作しているコンテナを確認します。

$ docker ps
CONTAINER ID   IMAGE            COMMAND       CREATED              STATUS              PORTS     NAMES
9246cc7051bf   centos:centos7   "/bin/bash"   About a minute ago   Up About a minute             centos7b
2e57a6f562a8   centos:centos7   "/bin/bash"   17 minutes ago       Up 12 minutes                 centos7f
$ 

Ctrl + P、Ctrl + Q を続けて押して、稼動させたまま抜けたコンテナ "centos7f" と、先ほどバックグラウンドで起動したコンテナ "centos7b" を確認することができるはずです。

起動しているコンテナの停止する

では、docker stop コマンドで、動作しているコンテナを停止させます。

$ docker stop centos7b centos7f
centos7b
centos7f
$

docker ps コマンドで、現在のコンテナの稼働状況を確認します。

先に述べた通り、-a、--allオプションをつけた場合は、停止中のコンテナを含めたすべてのコンテナを表示してくれます。

$ docker ps -a
CONTAINER ID   IMAGE            COMMAND       CREATED          STATUS                        PORTS     NAMES
9246cc7051bf   centos:centos7   "/bin/bash"   5 minutes ago    Exited (137) 56 seconds ago             centos7b
2e57a6f562a8   centos:centos7   "/bin/bash"   22 minutes ago   Exited (137) 56 seconds ago             centos7f
1c5f75cb06e6   hello-world      "/hello"      46 minutes ago   Exited (0) 46 minutes ago               inspiring_chatelet

現在、存在しているコンテナは、"centos7b", "centos7f", "inspiring_chatelet" の3つであることが確認できるでしょう。"inspiring_chatelet" というコンテナの名前はイメージ hello-world からコンテナが作成・起動された時に一意的にたまたま決まったものです。なので、違う名前が表示されているかもしれません。

では、docker rm コマンドで、3つのコンテナを削除してみましょう。

コンテナ(仮想環境)を削除する

$ docker rm centos7b centos7f laughing_lehmann
centos7b
centos7f
inspiring_chatelet
$

削除が完了しますと、削除対象のコンテナ名が出力されます。

$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

これで、コンテナがすべて削除されました。

イメージを削除する

Docker イメージの削除は、docker rmi コマンドを用います。

その前にdocker images コマンドで、取得したDockerイメージを一覧表示できます。

$ docker images
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
centos        centos7   8652b9f0cb4c   4 weeks ago     204MB
hello-world   latest    bf756fb1ae65   11 months ago   13.3kB
$ 

docker rmi コマンドを用いて、取得したDockerイメージを削除してみます。

$ docker rmi centos:centos7 hello-world:latest
Untagged: centos:centos7
Untagged: centos@sha256:0f4ec88e21daf75124b8a9e5ca03c37a5e937e0e108a255d890492430789b60e
Deleted: sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf
Deleted: sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02
Untagged: hello-world:latest
Untagged: hello-world@sha256:1a523af650137b8accdaed439c17d684df61ee4d74feac151b5b337bd29e7eec
Deleted: sha256:bf756fb1ae65adf866bd8c456593cd24beb6a0a061dedf42b26a993176745f6b
Deleted: sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63

これで、docker上からすべてのイメージが削除されました。

CentOS コンテナ (仮想環境)の整備

centos:centos7 のイメージは、最小構成 (?)でインストールされた CentOS7 環境であるようなので、実際に使用するには、構築した CentOS コンテナに個別に必要なもののインストールや環境の整備の作業をしなければなりません。
参考までにとりあえず、unzipコマンドを使えるようにする手順を紹介します。

まずは、Centos7 のコンテナやイメージなど削除してしまったので、復習をかねて、再度 Docker for Windows 上の CentOS コンテナ (仮想環境) を構築をします。

CentOS コンテナ (仮想環境) の構築 【復習】

CentOS の Docker イメージを取得

$ docker pull centos:centos7
centos7: Pulling from library/centos
2d473b07cdd5: Pull complete 
Digest: sha256:0f4ec88e21daf75124b8a9e5ca03c37a5e937e0e108a255d890492430789b60e
Status: Downloaded newer image for centos:centos7
docker.io/library/centos:centos7
$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
centos       centos7   8652b9f0cb4c   4 weeks ago   204MB
$ 

CentOS コンテナの作成・起動

$ docker run -it --name="my_centos" centos:centos7 /bin/bash
# 

unzip コマンドのインストール

unzip コマンドを入力したら、次のような"コマンドが見つかりません"という旨のメッセージが出力されました。

# unzip
bash: unzip: command not found
# 

まずは yum update コマンドで CentOS のパッケージを最新の状態にしておきます。

# yum update
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
 * base: ftp.riken.jp
 * extras: ftp.riken.jp
 * updates: ftp.riken.jp
base                                                                                                                           | 3.6 kB  00:00:00     
extras                                                                                                                         | 2.9 kB  00:00:00     
updates                                                                                                                        | 2.9 kB  00:00:00     
(1/4): base/7/x86_64/group_gz                                                                                                  | 153 kB  00:00:00     
(2/4): extras/7/x86_64/primary_db                                                                                              | 222 kB  00:00:00     
(3/4): updates/7/x86_64/primary_db                                                                                             | 3.7 MB  00:00:00     
(4/4): base/7/x86_64/primary_db                                                                                                | 6.1 MB  00:00:01     
Resolving Dependencies
--> Running transaction check
---> Package bind-license.noarch 32:9.11.4-26.P2.el7 will be updated
---> Package bind-license.noarch 32:9.11.4-26.P2.el7_9.2 will be an update
---> Package centos-release.x86_64 0:7-9.2009.0.el7.centos will be updated
---> Package centos-release.x86_64 0:7-9.2009.1.el7.centos will be an update
---> Package coreutils.x86_64 0:8.22-24.el7 will be updated
---> Package coreutils.x86_64 0:8.22-24.el7_9.2 will be an update
---> Package curl.x86_64 0:7.29.0-59.el7 will be updated
---> Package curl.x86_64 0:7.29.0-59.el7_9.1 will be an update
---> Package glib2.x86_64 0:2.56.1-7.el7 will be updated
---> Package glib2.x86_64 0:2.56.1-8.el7 will be an update
---> Package kpartx.x86_64 0:0.4.9-133.el7 will be updated
---> Package kpartx.x86_64 0:0.4.9-134.el7_9 will be an update
---> Package libcurl.x86_64 0:7.29.0-59.el7 will be updated
---> Package libcurl.x86_64 0:7.29.0-59.el7_9.1 will be an update
---> Package python.x86_64 0:2.7.5-89.el7 will be updated
---> Package python.x86_64 0:2.7.5-90.el7 will be an update
---> Package python-libs.x86_64 0:2.7.5-89.el7 will be updated
---> Package python-libs.x86_64 0:2.7.5-90.el7 will be an update
---> Package systemd.x86_64 0:219-78.el7 will be updated
---> Package systemd.x86_64 0:219-78.el7_9.2 will be an update
---> Package systemd-libs.x86_64 0:219-78.el7 will be updated
---> Package systemd-libs.x86_64 0:219-78.el7_9.2 will be an update
--> Finished Dependency Resolution

Dependencies Resolved

======================================================================================================================================================
 Package                              Arch                         Version                                        Repository                     Size
======================================================================================================================================================
Updating:
 bind-license                         noarch                       32:9.11.4-26.P2.el7_9.2                        updates                        90 k
 centos-release                       x86_64                       7-9.2009.1.el7.centos                          updates                        27 k
 coreutils                            x86_64                       8.22-24.el7_9.2                                updates                       3.3 M
 curl                                 x86_64                       7.29.0-59.el7_9.1                              updates                       271 k
 glib2                                x86_64                       2.56.1-8.el7                                   updates                       2.5 M
 kpartx                               x86_64                       0.4.9-134.el7_9                                updates                        81 k
 libcurl                              x86_64                       7.29.0-59.el7_9.1                              updates                       223 k
 python                               x86_64                       2.7.5-90.el7                                   updates                        96 k
 python-libs                          x86_64                       2.7.5-90.el7                                   updates                       5.6 M
 systemd                              x86_64                       219-78.el7_9.2                                 updates                       5.1 M
 systemd-libs                         x86_64                       219-78.el7_9.2                                 updates                       418 k

Transaction Summary
======================================================================================================================================================
Upgrade  11 Packages

Total download size: 18 M
Is this ok [y/d/N]: y
Downloading packages:
Delta RPMs disabled because /usr/bin/applydeltarpm not installed.
warning: /var/cache/yum/x86_64/7/updates/packages/bind-license-9.11.4-26.P2.el7_9.2.noarch.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
Public key for bind-license-9.11.4-26.P2.el7_9.2.noarch.rpm is not installed
(1/11): bind-license-9.11.4-26.P2.el7_9.2.noarch.rpm                                                                           |  90 kB  00:00:00     
(2/11): centos-release-7-9.2009.1.el7.centos.x86_64.rpm                                                                        |  27 kB  00:00:00     
(3/11): curl-7.29.0-59.el7_9.1.x86_64.rpm                                                                                      | 271 kB  00:00:00     
(4/11): glib2-2.56.1-8.el7.x86_64.rpm                                                                                          | 2.5 MB  00:00:00     
(5/11): kpartx-0.4.9-134.el7_9.x86_64.rpm                                                                                      |  81 kB  00:00:00     
(6/11): coreutils-8.22-24.el7_9.2.x86_64.rpm                                                                                   | 3.3 MB  00:00:00     
(7/11): python-2.7.5-90.el7.x86_64.rpm                                                                                         |  96 kB  00:00:00     
(8/11): libcurl-7.29.0-59.el7_9.1.x86_64.rpm                                                                                   | 223 kB  00:00:00     
(9/11): systemd-libs-219-78.el7_9.2.x86_64.rpm                                                                                 | 418 kB  00:00:00     
(10/11): systemd-219-78.el7_9.2.x86_64.rpm                                                                                     | 5.1 MB  00:00:00     
(11/11): python-libs-2.7.5-90.el7.x86_64.rpm                                                                                   | 5.6 MB  00:00:00     
------------------------------------------------------------------------------------------------------------------------------------------------------
Total                                                                                                                  12 MB/s |  18 MB  00:00:01     
Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Importing GPG key 0xF4A80EB5:
 Userid     : "CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>"
 Fingerprint: 6341 ab27 53d7 8a78 a7c2 7bb1 24c6 a8a7 f4a8 0eb5
 Package    : centos-release-7-9.2009.0.el7.centos.x86_64 (@CentOS)
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Is this ok [y/N]: y
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Updating   : libcurl-7.29.0-59.el7_9.1.x86_64                                                                                                  1/22 
  Updating   : coreutils-8.22-24.el7_9.2.x86_64                                                                                                  2/22 
  Updating   : python-libs-2.7.5-90.el7.x86_64                                                                                                   3/22 
  Updating   : systemd-libs-219-78.el7_9.2.x86_64                                                                                                4/22 
  Updating   : centos-release-7-9.2009.1.el7.centos.x86_64                                                                                       5/22 
  Updating   : systemd-219-78.el7_9.2.x86_64                                                                                                     6/22 
Failed to get D-Bus connection: Operation not permitted
  Updating   : python-2.7.5-90.el7.x86_64                                                                                                        7/22 
  Updating   : curl-7.29.0-59.el7_9.1.x86_64                                                                                                     8/22 
  Updating   : glib2-2.56.1-8.el7.x86_64                                                                                                         9/22 
  Updating   : 32:bind-license-9.11.4-26.P2.el7_9.2.noarch                                                                                      10/22 
  Updating   : kpartx-0.4.9-134.el7_9.x86_64                                                                                                    11/22 
  Cleanup    : systemd-219-78.el7.x86_64                                                                                                        12/22 
  Cleanup    : curl-7.29.0-59.el7.x86_64                                                                                                        13/22 
  Cleanup    : python-2.7.5-89.el7.x86_64                                                                                                       14/22 
  Cleanup    : centos-release-7-9.2009.0.el7.centos.x86_64                                                                                      15/22 
  Cleanup    : 32:bind-license-9.11.4-26.P2.el7.noarch                                                                                          16/22 
  Cleanup    : python-libs-2.7.5-89.el7.x86_64                                                                                                  17/22 
  Cleanup    : coreutils-8.22-24.el7.x86_64                                                                                                     18/22 
  Cleanup    : libcurl-7.29.0-59.el7.x86_64                                                                                                     19/22 
  Cleanup    : systemd-libs-219-78.el7.x86_64                                                                                                   20/22 
  Cleanup    : glib2-2.56.1-7.el7.x86_64                                                                                                        21/22 
  Cleanup    : kpartx-0.4.9-133.el7.x86_64                                                                                                      22/22 
  Verifying  : python-2.7.5-90.el7.x86_64                                                                                                        1/22 
  Verifying  : kpartx-0.4.9-134.el7_9.x86_64                                                                                                     2/22 
  Verifying  : centos-release-7-9.2009.1.el7.centos.x86_64                                                                                       3/22 
  Verifying  : coreutils-8.22-24.el7_9.2.x86_64                                                                                                  4/22 
  Verifying  : libcurl-7.29.0-59.el7_9.1.x86_64                                                                                                  5/22 
  Verifying  : curl-7.29.0-59.el7_9.1.x86_64                                                                                                     6/22 
  Verifying  : python-libs-2.7.5-90.el7.x86_64                                                                                                   7/22 
  Verifying  : systemd-libs-219-78.el7_9.2.x86_64                                                                                                8/22 
  Verifying  : 32:bind-license-9.11.4-26.P2.el7_9.2.noarch                                                                                       9/22 
  Verifying  : systemd-219-78.el7_9.2.x86_64                                                                                                    10/22 
  Verifying  : glib2-2.56.1-8.el7.x86_64                                                                                                        11/22 
  Verifying  : systemd-libs-219-78.el7.x86_64                                                                                                   12/22 
  Verifying  : glib2-2.56.1-7.el7.x86_64                                                                                                        13/22 
  Verifying  : kpartx-0.4.9-133.el7.x86_64                                                                                                      14/22 
  Verifying  : 32:bind-license-9.11.4-26.P2.el7.noarch                                                                                          15/22 
  Verifying  : centos-release-7-9.2009.0.el7.centos.x86_64                                                                                      16/22 
  Verifying  : systemd-219-78.el7.x86_64                                                                                                        17/22 
  Verifying  : python-libs-2.7.5-89.el7.x86_64                                                                                                  18/22 
  Verifying  : coreutils-8.22-24.el7.x86_64                                                                                                     19/22 
  Verifying  : python-2.7.5-89.el7.x86_64                                                                                                       20/22 
  Verifying  : libcurl-7.29.0-59.el7.x86_64                                                                                                     21/22 
  Verifying  : curl-7.29.0-59.el7.x86_64                                                                                                        22/22 

Updated:
  bind-license.noarch 32:9.11.4-26.P2.el7_9.2         centos-release.x86_64 0:7-9.2009.1.el7.centos         coreutils.x86_64 0:8.22-24.el7_9.2        
  curl.x86_64 0:7.29.0-59.el7_9.1                     glib2.x86_64 0:2.56.1-8.el7                           kpartx.x86_64 0:0.4.9-134.el7_9           
  libcurl.x86_64 0:7.29.0-59.el7_9.1                  python.x86_64 0:2.7.5-90.el7                          python-libs.x86_64 0:2.7.5-90.el7         
  systemd.x86_64 0:219-78.el7_9.2                     systemd-libs.x86_64 0:219-78.el7_9.2                 

Complete!
# 

エラーが生じた場合、こちらを参考してください。

yum install コマンドで、unzip をインストールします。

# yum install unzip
Loaded plugins: fastestmirror, ovl
Loading mirror speeds from cached hostfile
 * base: ftp.riken.jp
 * extras: ftp.riken.jp
 * updates: ftp.riken.jp
Resolving Dependencies
--> Running transaction check
---> Package unzip.x86_64 0:6.0-21.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

======================================================================================================================================================
 Package                           Arch                               Version                                  Repository                        Size
======================================================================================================================================================
Installing:
 unzip                             x86_64                             6.0-21.el7                               base                             171 k

Transaction Summary
======================================================================================================================================================
Install  1 Package

Total download size: 171 k
Installed size: 365 k
Is this ok [y/d/N]: y
Downloading packages:
unzip-6.0-21.el7.x86_64.rpm                                                                                                    | 171 kB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : unzip-6.0-21.el7.x86_64                                                                                                            1/1 
  Verifying  : unzip-6.0-21.el7.x86_64                                                                                                            1/1 

Installed:
  unzip.x86_64 0:6.0-21.el7                                                                                                                           

Complete!
# 

これで unzip コマンドのインストールができました。

# unzip
UnZip 6.00 of 20 April 2009, by Info-ZIP.  Maintained by C. Spieler.  Send
bug reports using http://www.info-zip.org/zip-bug.html; see README for details.

Usage: unzip [-Z] [-opts[modifiers]] file[.zip] [list] [-x xlist] [-d exdir]
  Default action is to extract files in list, except those in xlist, to exdir;
  file[.zip] may be a wildcard.  -Z => ZipInfo mode ("unzip -Z" for usage).

  -p  extract files to pipe, no messages     -l  list files (short format)
  -f  freshen existing files, create none    -t  test compressed archive data
  -u  update files, create if necessary      -z  display archive comment only
  -v  list verbosely/show version info       -T  timestamp archive to latest
  -x  exclude files that follow (in xlist)   -d  extract files into exdir
modifiers:
  -n  never overwrite existing files         -q  quiet mode (-qq => quieter)
  -o  overwrite files WITHOUT prompting      -a  auto-convert any text files
  -j  junk paths (do not make directories)   -aa treat ALL files as text
  -U  use escapes for all non-ASCII Unicode  -UU ignore any Unicode fields
  -C  match filenames case-insensitively     -L  make (some) names lowercase
  -X  restore UID/GID info                   -V  retain VMS version numbers
  -K  keep setuid/setgid/tacky permissions   -M  pipe through "more" pager
  -O CHARSET  specify a character encoding for DOS, Windows and OS/2 archives
  -I CHARSET  specify a character encoding for UNIX and other archives

See "unzip -hh" or unzip.txt for more help.  Examples:
  unzip data1 -x joe   => extract all files except joe from zipfile data1.zip
  unzip -p foo | more  => send contents of foo.zip via pipe into program more
  unzip -fo foo ReadMe => quietly replace existing ReadMe if archive file newer
# 

unzip コマンドをインストールした CentOS のコンテナの"my_centos" を停止して抜けます。

# exit
exit
$ 

作業済み CentOS コンテナ (仮想環境)の保存

今回、CentOS コンテナ (仮想環境)に対して unzip コマンドをインストールをしました。

このように設定や更新などのカスタマイズ作業をおこなったコンテナの状態をイメージとして保存しておくことができます。
では、今回のカスタマイズ作業をおこなった CentOS コンテナ からイメージを作って保存をしてみます。

保存するコンテナを停止する

まず、 オプションなしで、現在起動中のコンテナを確認します。

$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
$

今回は、unzip コマンドのインストールしたCentOS のコンテナの"my_centos" は、停止状態のはずなので、現在起動中のコンテナはありません。

コンテナが動いている状態でコンテナに接続されていない場合は docker stop コマンドで停止させることができます。

次に -a オプションをつけて、停止中のコンテナを含めたすべてのコンテナを表示します。

$ docker ps -a
CONTAINER ID   IMAGE            COMMAND       CREATED          STATUS                     PORTS     NAMES
6d6d204664d1   centos:centos7   "/bin/bash"   23 minutes ago   Exited (0) 3 minutes ago             my_centos
$

unzip コマンドのインストールなどの作業が行われ、現在停止した状態である CentOS コンテナの"my_centos"が表示されます。

停止したコンテナをイメージに保存する

docker images コマンドで、作成されているイメージを確認します。

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
centos       centos7   8652b9f0cb4c   4 weeks ago   204MB
$ 

カスタマイズ作業をおこなった CentOS コンテナの"my_centos"を docker commit コマンドで、Docker イメージ "my_centos" として保存します。

$ docker commit my_centos my_centos
sha256:528149497a38f8a5c7e3c3dacf3a876d03c0a5abbc9e110a14de621a4b3db0a9
$

なお、一時停止せず、コンテナが動いたままの状態でDockerイメージを作成する場合は「--pause=false」オプションを使用します。

docker imagesコマンドにて、Dockerイメージが作成されているか確認します

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
my_centos    latest    528149497a38   2 minutes ago   357MB
centos       centos7   8652b9f0cb4c   4 weeks ago     204MB
$ 

新たに Dokcer イメージ "my_centos" が追加されたことがわかります。

作成されたイメージの内容を確認する

先ほど作業した内容がちゃんと反映されている イメージなのか、確認してみましょう。

docker rm コマンドで、コンテナ "my_centos" を削除します。

$ docker ps -a
CONTAINER ID   IMAGE            COMMAND       CREATED          STATUS                      PORTS     NAMES
6d6d204664d1   centos:centos7   "/bin/bash"   34 minutes ago   Exited (0) 15 minutes ago             my_centos
$ docker rm my_centos
my_centos
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
$ 

これで、すべてのコンテナがなくなりました。

では、docker run コマンドで、イメージ "my_centos" からコンテナ "my_centos" を作成・起動してみます。

$ docker run -it --name="my_centos" my_centos /bin/bash
# 

unzip コマンドを実行してみます。

# unzip
UnZip 6.00 of 20 April 2009, by Info-ZIP.  Maintained by C. Spieler.  Send
bug reports using http://www.info-zip.org/zip-bug.html; see README for details.

Usage: unzip [-Z] [-opts[modifiers]] file[.zip] [list] [-x xlist] [-d exdir]
  Default action is to extract files in list, except those in xlist, to exdir;
  file[.zip] may be a wildcard.  -Z => ZipInfo mode ("unzip -Z" for usage).

  -p  extract files to pipe, no messages     -l  list files (short format)
  -f  freshen existing files, create none    -t  test compressed archive data
  -u  update files, create if necessary      -z  display archive comment only
  -v  list verbosely/show version info       -T  timestamp archive to latest
  -x  exclude files that follow (in xlist)   -d  extract files into exdir
modifiers:
  -n  never overwrite existing files         -q  quiet mode (-qq => quieter)
  -o  overwrite files WITHOUT prompting      -a  auto-convert any text files
  -j  junk paths (do not make directories)   -aa treat ALL files as text
  -U  use escapes for all non-ASCII Unicode  -UU ignore any Unicode fields
  -C  match filenames case-insensitively     -L  make (some) names lowercase
  -X  restore UID/GID info                   -V  retain VMS version numbers
  -K  keep setuid/setgid/tacky permissions   -M  pipe through "more" pager
  -O CHARSET  specify a character encoding for DOS, Windows and OS/2 archives
  -I CHARSET  specify a character encoding for UNIX and other archives

See "unzip -hh" or unzip.txt for more help.  Examples:
  unzip data1 -x joe   => extract all files except joe from zipfile data1.zip
  unzip -p foo | more  => send contents of foo.zip via pipe into program more
  unzip -fo foo ReadMe => quietly replace existing ReadMe if archive file newer
# 

さいごに

今回、MacBookに Doker Desktop for Mac をインストールし、CentOS の仮想環境を構築・設定・保存などの一連の手順を詳細に記述したつもりです。
もし、記述について誤りがあったり、気になることがあれば、編集リクエストやコメントでフィードバックしていただけると助かります。

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

ECS + CodePipline でコンテナ管理

アプリケーションを作る際にコンテナを使う人も多くなってきたのではないでしょうか。
今回は、CodeCommitにDockerfileをアップロードしたらBuildして自動でECSのタスクが更新される仕組みをコード化したいと思います。

インフラをコードで管理するためにCloudFormationを使用します。awslabsが公開しているコードを参照し、Cloudformationで作成します。いずれTerraFormでも作りたいと思います。

awslabs
https://github.com/awslabs/ecs-refarch-continuous-deployment

今回作成するアーキテクチャは以下のようになります。

qiita-cfn.png

EC2かFargateかはスタックを作る際に選択可能な形となります。なお、スタック作成時はAWS公式のdocker image(php-sample)を使用しますので、他のimageを使用したい場合にはDockerfileをCodeCommitにアップロードしてください。

(なお、PiplineにGithubを使用したいという方はawslabsのコードをご参照ください。)

ディレクトリ構造は下記となります。templatesフォルダにある5つのyamlはS3にアップロードしてご使用ください。

├── ecs-refarch-continuous-deployment.yaml
├── tempaltes
    ├── vpc.yaml
    ├── load-balancer.yaml
    ├── ecs-cluster.yaml
    ├── service.yaml
    └── deployment-pipline.yaml

まずはVPCからです。

---
AWSTemplateFormatVersion: 2010-09-09


Parameters:
  Name:
    Type: String

  VpcCIDR:
    Type: String

  Subnet1CIDR:
    Type: String

  Subnet2CIDR:
    Type: String


Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      Tags:
        - Key: Name
          Value: !Ref Name

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref Name

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  Subnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      MapPublicIpOnLaunch: true
      CidrBlock: !Ref Subnet1CIDR
      Tags:
        - Key: Name
          Value: !Sub ${Name} (Public)

  Subnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      MapPublicIpOnLaunch: true
      CidrBlock: !Ref Subnet2CIDR
      Tags:
        - Key: Name
          Value: !Sub ${Name} (Public)

  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Ref Name

  DefaultRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  Subnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref Subnet1

  Subnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref Subnet2


Outputs:
  Subnets:
    Value: !Join [ ",", [ !Ref Subnet1, !Ref Subnet2 ] ]
  VpcId:
    Value: !Ref VPC

次にLoadBalancer(ALB)です。NLBのTemplateもありますので、見たいという方はコメントしてください!

Parameters:
  LaunchType:
    Type: String
    Default: Fargate
    AllowedValues:
      - Fargate
      - EC2

  Subnets:
    Type: List<AWS::EC2::Subnet::Id>

  VpcId:
    Type: String


Conditions:
  EC2: !Equals [ !Ref LaunchType, "EC2" ]


Resources:
  SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: !Sub ${AWS::StackName}-alb
      SecurityGroupIngress:
        - CidrIp: "0.0.0.0/0"
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80
      VpcId: !Ref VpcId

  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Subnets: !Ref Subnets
      SecurityGroups:
        - !Ref SecurityGroup

  LoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref LoadBalancer
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DependsOn: LoadBalancer
    Properties:
      VpcId: !Ref VpcId
      Port: 80
      Protocol: HTTP
      Matcher:
        HttpCode: 200-299
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      TargetType: !If [ EC2, "instance", "ip" ]
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 30

  ListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      ListenerArn: !Ref LoadBalancerListener
      Priority: 1
      Conditions:
        - Field: path-pattern
          Values:
            - /
      Actions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward


Outputs:
  TargetGroup:
    Value: !Ref TargetGroup

  ServiceUrl:
    Description: URL of the load balancer for the sample service.
    Value: !Sub http://${LoadBalancer.DNSName}

  SecurityGroup:
    Value: !Ref SecurityGroup

次にCluseterです。今回はAMIはマッピングを利用しました。SSMを使用して最新のAMIを取得してくることもできますが、リージョン別に自動選択したかったので採用しませんでした。

Parameters:
  LaunchType:
    Type: String
    Default: Fargate
    AllowedValues:
      - Fargate
      - EC2

  InstanceType:
    Type: String
    Default: t2.micro

  ClusterSize:
    Type: Number
    Default: 2

  Subnets:
    Type: List<AWS::EC2::Subnet::Id>

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id

  VpcId:
    Type: AWS::EC2::VPC::Id

  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"


Conditions:
  EC2: !Equals [ !Ref LaunchType, "EC2" ]

Mappings:
  AWSRegionToAMI:
    ap-south-1:
      AMI: ami-00491f6f
    eu-west-3:
      AMI: ami-9aef59e7
    eu-west-2:
      AMI: ami-67cbd003
    eu-west-1:
      AMI: ami-1d46df64
    ap-northeast-2:
      AMI: ami-c212b2ac
    ap-northeast-1:
      AMI: ami-872c4ae1
    sa-east-1:
      AMI: ami-af521fc3
    ca-central-1:
      AMI: ami-435bde27
    ap-southeast-1:
      AMI: ami-910d72ed
    ap-southeast-2:
      AMI: ami-58bb443a
    eu-central-1:
      AMI: ami-509a053f
    us-east-1:
      AMI: ami-28456852
    us-east-2:
      AMI: ami-ce1c36ab
    us-west-1:
      AMI: ami-74262414
    us-west-2:
      AMI: ami-decc7fa6

Resources:
  EC2Role:
    Type: AWS::IAM::Role
    Condition: EC2
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Condition: EC2
    Properties:
      Path: /
      Roles:
        - !Ref EC2Role

  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Ref AWS::StackName

  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Condition: EC2
    Properties:
      VPCZoneIdentifier: !Ref Subnets
      LaunchConfigurationName: !Ref LaunchConfiguration
      MinSize: !Ref ClusterSize
      MaxSize: !Ref ClusterSize
      DesiredCapacity: !Ref ClusterSize
      Tags: 
        - Key: Name
          Value: !Sub ${AWS::StackName} - ECS Host
          PropagateAtLaunch: true
    CreationPolicy:
      ResourceSignal:
        Timeout: PT15M
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinInstancesInService: 1
        MaxBatchSize: 1
        PauseTime: PT15M
        WaitOnResourceSignals: true

  LaunchConfiguration:
    Type: AWS::AutoScaling::LaunchConfiguration
    Condition: EC2
    Metadata:
      AWS::CloudFormation::Init:
        config:
          commands:
            01_add_instance_to_cluster:
                command: !Sub echo ECS_CLUSTER=${Cluster} > /etc/ecs/ecs.config
          files:
            "/etc/cfn/cfn-hup.conf":
              mode: 000400
              owner: root
              group: root
              content: !Sub |
                [main]
                stack=${AWS::StackId}
                region=${AWS::Region}
            "/etc/cfn/hooks.d/cfn-auto-reloader.conf":
              content: !Sub |
                [cfn-auto-reloader-hook]
                triggers=post.update
                path=Resources.ContainerInstances.Metadata.AWS::CloudFormation::Init
                action=/opt/aws/bin/cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource LaunchConfiguration
          services:
            sysvinit:
              cfn-hup:
                enabled: true
                ensureRunning: true
                files:
                  - /etc/cfn/cfn-hup.conf
                  - /etc/cfn/hooks.d/cfn-auto-reloader.conf
    Properties:
      ImageId: !FindInMap [ AWSRegionToAMI, !Ref "AWS::Region", AMI ]
      InstanceType: !Ref InstanceType
      IamInstanceProfile: !Ref InstanceProfile
      SecurityGroups:
        - !Ref SecurityGroup
      KeyName: !Ref KeyName
      UserData:
        "Fn::Base64": !Sub |
          #!/bin/bash
          yum install -y aws-cfn-bootstrap
          /opt/aws/bin/cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource LaunchConfiguration
          /opt/aws/bin/cfn-signal -e $? --region ${AWS::Region} --stack ${AWS::StackName} --resource AutoScalingGroup

Outputs:
  ClusterName:
      Value: !Ref Cluster

次にServiceです。

Parameters:
  Cluster:
    Type: String

  DesiredCount:
    Type: Number
    Default: 1

  LaunchType:
    Type: String
    Default: Fargate
    AllowedValues:
      - Fargate
      - EC2

  TargetGroup:
    Type: String

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id

  Subnets:
    Type: List<AWS::EC2::Subnet::Id>


Conditions:
  Fargate: !Equals [ !Ref LaunchType, "Fargate" ]

  EC2: !Equals [ !Ref LaunchType, "EC2" ]


Resources:
  TaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /ecs/${AWS::StackName}
      RetentionInDays: 14

  FargateService:
    Type: AWS::ECS::Service
    Condition: Fargate
    Properties:
      Cluster: !Ref Cluster
      DesiredCount: !Ref DesiredCount
      TaskDefinition: !Ref TaskDefinition
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref SecurityGroup
          Subnets: !Ref Subnets
      LoadBalancers:
        - ContainerName: simple-app
          ContainerPort: 80
          TargetGroupArn: !Ref TargetGroup

  EC2Service:
    Type: AWS::ECS::Service
    Condition: EC2
    Properties:
      Cluster: !Ref Cluster
      DesiredCount: !Ref DesiredCount
      TaskDefinition: !Ref TaskDefinition
      LaunchType: EC2
      LoadBalancers:
        - ContainerName: simple-app
          ContainerPort: 80
          TargetGroupArn: !Ref TargetGroup

  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Sub ${AWS::StackName}-simple-app
      RequiresCompatibilities:
        - !If [ Fargate, "FARGATE", "EC2" ]
      Memory: 512
      Cpu: 256
      NetworkMode: !If [ Fargate, "awsvpc", "bridge" ]
      ExecutionRoleArn: !Ref TaskExecutionRole
      ContainerDefinitions:
        - Name: simple-app
          Image: amazon/amazon-ecs-sample
          Essential: true
          Memory: 256
          MountPoints:
            - SourceVolume: my-vol
              ContainerPath: /var/www/my-vol
          PortMappings:
            - HostPort: 80
              ContainerPort: 80
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-region: !Ref AWS::Region
              awslogs-group: !Ref LogGroup
              awslogs-stream-prefix: !Ref AWS::StackName
        - Name: busybox
          Image: busybox
          EntryPoint:
            - sh
            - -c
          Essential: true
          Memory: 256
          VolumesFrom:
            - SourceContainer: simple-app
          Command:
            - /bin/sh -c "while true; do /bin/date > /var/www/my-vol/date; sleep 1; done"
      Volumes:
        - Name: my-vol


Outputs:
  Service:
    Value: !If [ Fargate, !Ref FargateService, !Ref EC2Service ]

次にパイプラインの構築です。

Parameters:
  CodeCommitRepositoryName:
    Type: String

  Cluster:
    Type: String

  Service:
    Type: String


Resources:
  Repository:
    Type: AWS::ECR::Repository
    DeletionPolicy: Retain

  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource: "*"
                Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - ecr:GetAuthorizationToken
                  - secretsmanager:GetSecretValue
              - Resource: !Sub arn:aws:s3:::${ArtifactBucket}/*
                Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:GetObjectVersion
              - Resource: !Sub arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${Repository}
                Effect: Allow
                Action:
                  - ecr:GetDownloadUrlForLayer
                  - ecr:BatchGetImage
                  - ecr:BatchCheckLayerAvailability
                  - ecr:PutImage
                  - ecr:InitiateLayerUpload
                  - ecr:UploadLayerPart
                  - ecr:CompleteLayerUpload

  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource:
                  - !Sub arn:aws:s3:::${ArtifactBucket}/*
                Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketVersioning
              - Resource: "*"
                Effect: Allow
                Action:
                  - ecs:DescribeServices
                  - ecs:DescribeTaskDefinition
                  - ecs:DescribeTasks
                  - ecs:ListTasks
                  - ecs:RegisterTaskDefinition
                  - ecs:UpdateService
                  - codebuild:StartBuild
                  - codebuild:BatchGetBuilds
                  - iam:PassRole

  ArtifactBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain

  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.2
          phases:
            pre_build:
              commands:
                - $(aws ecr get-login --no-include-email)
                - TAG="$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)"
                - IMAGE_URI="${REPOSITORY_URI}:${TAG}"
            build:
              commands:
                - docker build --tag "$IMAGE_URI" .
            post_build:
              commands:
                - docker push "$IMAGE_URI"
                - printf '[{"name":"simple-app","imageUri":"%s"}]' "$IMAGE_URI" > images.json
          artifacts:
            files: images.json
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/docker:17.09.0
        Type: LINUX_CONTAINER
        EnvironmentVariables:
          - Name: AWS_DEFAULT_REGION
            Value: !Ref AWS::Region
          - Name: REPOSITORY_URI
            Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repository}
      Name: !Ref AWS::StackName
      ServiceRole: !Ref CodeBuildServiceRole

  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: App
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: 1
                Provider: CodeCommit
              Configuration:
                RepositoryName: !Ref CodeCommitRepositoryName
                BranchName: master
              RunOrder: 1
              OutputArtifacts:
                - Name: App
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: 1
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref CodeBuildProject
              InputArtifacts:
                - Name: App
              OutputArtifacts:
                - Name: BuildOutput
              RunOrder: 1
        - Name: Deploy
          Actions:
            - Name: Deploy
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: 1
                Provider: ECS
              Configuration:
                ClusterName: !Ref Cluster
                ServiceName: !Ref Service
                FileName: images.json
              InputArtifacts:
                - Name: BuildOutput
              RunOrder: 1


Outputs:
  PipelineUrl:
    Value: !Sub https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/${Pipeline}

以上5つのファイルをS3にアップロードしてください。
最後にまとめのテンプレートです。

Parameters:
  LaunchType:
    Type: String
    Default: Fargate
    AllowedValues:
      - Fargate
      - EC2
    Description: >
      The launch type for your service. Selecting EC2 will create an Auto
      Scaling group of t2.micro instances for your cluster. See
      https://docs.aws.amazon.com/AmazonECS/latest/developerguide/launch_types.html
      to learn more about launch types.

  TemplateBucket:
    Type: String
    Description: >
      The S3 bucket from which to fetch the templates used by this stack.

  CodeCommitRepositoryName:
    Type: String

  ClusterSize:
    Type: Number
    Default: 2

  DesiredCount:
    Type: Number
    Default: 1

  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterLabels:
      CodeCommitRepositoryName:
        default: "CodeCommitRepositoryName"
      LaunchType:
        default: "Launch Type"
    ParameterGroups:
      - Label:
          default: Cluster Configuration
        Parameters:
          - LaunchType
      - Label:
          default: CodeCommit Configuration
        Parameters:
          - CodeCommitRepositoryName
      - Label:
          default: Stack Configuration
        Parameters:
          - TemplateBucket
      - Label:
          default: TaskDesiredCount
        Parameters:
          - DesiredCount

Resources:
  Cluster:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/ecs-cluster.yaml"
      Parameters:
        LaunchType: !Ref LaunchType
        SecurityGroup: !GetAtt LoadBalancer.Outputs.SecurityGroup
        Subnets: !GetAtt VPC.Outputs.Subnets
        VpcId: !GetAtt VPC.Outputs.VpcId
        ClusterSize: !Ref ClusterSize
        KeyName: !Ref KeyName

  DeploymentPipeline:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/deployment-pipeline.yaml"
      Parameters:
        Cluster: !GetAtt Cluster.Outputs.ClusterName
        Service: !GetAtt Service.Outputs.Service
        CodeCommitRepositoryName: !Ref CodeCommitRepositoryName

  LoadBalancer:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/load-balancer.yaml"
      Parameters:
        LaunchType: !Ref LaunchType
        Subnets: !GetAtt VPC.Outputs.Subnets
        VpcId: !GetAtt VPC.Outputs.VpcId

  VPC:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/vpc.yaml"
      Parameters:
        Name: !Ref AWS::StackName
        VpcCIDR: 10.215.0.0/16
        Subnet1CIDR: 10.215.10.0/24
        Subnet2CIDR: 10.215.20.0/24

  Service:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/service.yaml"
      Parameters:
        Cluster: !GetAtt Cluster.Outputs.ClusterName
        LaunchType: !Ref LaunchType
        TargetGroup: !GetAtt LoadBalancer.Outputs.TargetGroup
        SecurityGroup: !GetAtt LoadBalancer.Outputs.SecurityGroup
        Subnets: !GetAtt VPC.Outputs.Subnets
        DesiredCount: !Ref DesiredCount

Outputs:
  ServiceUrl:
    Description: The sample service that is being continuously deployed.
    Value: !GetAtt LoadBalancer.Outputs.ServiceUrl

  PipelineUrl:
    Description: The continuous deployment pipeline in the AWS Management Console.
    Value: !GetAtt DeploymentPipeline.Outputs.PipelineUrl

以上となります。最後までご覧いただきありがとうございます。

参照記事
https://qiita.com/chisso/items/3c4dd1af0382d4978288
https://github.com/awslabs/ecs-refarch-continuous-deployment

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

PHP8.0 + Laravel6.x(LTS)環境をDocker Composeで手早く作る

本記事はAll About Group(株式会社オールアバウト) Advent Calendar 2020 15日目になります。

概要三行

  • LAMP(PHP8.0) + Laravelの簡易開発環境をDocker Composeで作成
  • 基本コピペで誰でも作業できるように記述。
  • {}で囲んでいる箇所は任意に書き換えてください。

概要

PHP8.0が正式リリースされましたね。https://www.php.net/releases/8.0/en.php
実行速度の改善はありがたい限りです。
気になる楽しみ!ということで、今回はPHP8で遊べるように簡易のLAMP+laravel開発環境を構築いたしました。

環境構築_概要

使用技術 / 環境

  • Docker Compose
  • centos7
  • PHP8.0
  • laravel6.x(LTS)
  • MySQL(5.7)
  • phpMyAdmin

構成について

先に、今回作成する開発環境の構成を説明しておきます

{開発環境_命名は自由} /
├── docker-compose.yaml
├── web_php8/
|  ├── Dockerfile
|  └── setup/
|     ├──.bashrc
|     └── httpd/
|        └── app.conf
└── repository/
   └── {laravelアプリ(命名は自由)}
名称 種別 概要
{開発環境_命名は自由}/ ? ディレクトリ 今回の開発環境ファイルを束ねるディレクトリ
docker-compose.yaml ? ファイル docker composeの実行ファイル
web_php8/ ? ディレクトリ WEB系コンテナの関連ファイル置き場です
web_php8/Dockerfile ? ファイル 今回作成するWEB系コンテナのDocker Fileです
web_php8/setup/ ? ディレクトリ WEB系コンテナ内での設定ファイル郡です
web_php8/setup/.bashrc ? ファイル WEB系コンテナ内に適用するbashrcです
web_php8/setup/httpd/ ? ディレクトリ WEB系コンテナ内に適用するApacheの設定ファイル郡です
web_php8/setup/httpd/app.conf ? ファイル WEB系コンテナで摘要するconfファイルです
repository/ ? ディレクトリ WEBサーバーで稼働させるWEBアプリ置き場です
repository/{laravelアプリ(命名は自由)} ? ディレクトリ 今回WEB系コンテナ内で稼働させるLaravelアプリです

本件で立ち上がるコンテナは、WEB、DB、PHP-MY-ADMINの3つです。
最低限の環境なので、自由に変更/追加するといいと思います。

repository ディレクトリに保管したアプリをWEBコンテナ内にマウントしており、
アプリ開発する際には、localのrepository/{laravelアプリ(命名は自由)}を編集すれば、コンテナ内に反映されます。

URL

今回の構築にて作成されるURLを記載します

名称 URL
アプリURL http://localhost/
phpMyAdmin http://localhost:89/

前提

  • 筆者はMac端末で作業しております。
  • {}で囲んでいる箇所は任意に書き換えてください。
  • docker-composeは既に入っているものとします。

環境構築_手順

docker-compose.yamlの作成

複数のdockerコンテナを束ねるDocker Composeのyamlファイルを作成します。
※ {}で囲んでいる箇所は任意に書き換えてください

mkdir {開発環境_命名は自由}; cd {開発環境_命名は自由}
vim docker-compose.yaml

下記をdocker-compose.yamlに記述してください

docker-compose.yaml
version: '3'

services:
  web_php8:
    build:
      context: ./web_php8
    volumes:
      - ./repository/{laravelアプリ(命名は自由)}:/var/www/{laravelアプリ(命名は自由)}
      - ./web_php8/setup/.bashrc:/root/.bashrc
    ports:
      - "80:80"
    working_dir: /var/www/{laravelアプリ(命名は自由)}/
    environment:
      TZ: "Asia/Tokyo"

  db:
    image: mysql:5.7
    ports:
      - "3306:3306"
    environment:
      MYSQL_DATABASE: test_db
      MYSQL_USER: test_user
      MYSQL_PASSWORD: test_pass
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
      TZ: "Asia/Tokyo"

  php-my-admin:
    image: phpmyadmin/phpmyadmin
    links:
      - db
    ports:
      - "89:80"
    environment:
      MYSQL_USERNAME: test_user
      MYSQL_ROOT_PASSWORD: test_pass
      PMA_HOST: db
      PMA_PORT: 3306

立ち上げる各コンテナとその設定を定義しています。
WEBコンテナはPHP8.0の導入など、色々やりたいので、個別でDockerFileを作成します。

WEB系コンテナの作成①Docker File編

WEB系のDockerコンテナを立ち上げるためのDocker Fileを作成します。

mkdir web_php8; cd web_php8
vim Dockerfile

下記をDockerfileに記述してください

FROM centos:centos7

# timezone
RUN rm -f /etc/localtime \
    && cp -p /usr/share/zoneinfo/Japan /etc/localtime

# php8.0
    RUN yum -y update \
    && yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \
    && yum -y install https://rpms.remirepo.net/enterprise/remi-release-7.rpm \
    && yum -y install yum-utils \
    && yum-config-manager --disable 'remi-php*' && yum-config-manager --enable remi-php80 \
    && yum remove php* \
    && yum -y install php php-{cli,fpm,mysqlnd,zip,devel,gd,mbstring,curl,xml,pear,bcmath,json} \
    && php --version

# apache
RUN yum install -y httpd httpd-devel \
    && rm -rf /var/cache/yum/* \
    && yum clean all \
    && mkdir -p /app/ENV /AccessLog.local
COPY ./setup/httpd/app.conf /etc/httpd/conf.d/app.conf

# composer
RUN cd /tmp && curl -sS https://getcomposer.org/installer | php \
    && mv ./composer.phar /usr/bin/composer

CMD ["httpd", "-D", "FOREGROUND"]

WEB系コンテナに必要なツール類や設定はここで定義しています。

WEB系コンテナの作成②関連ファイル編

WEB系コンテナ内にて摘要する各ファイルを作成しておきます

まずは、コンテナ内に適用するbashrcを作成します

mkdir setup; cd setup
vim .bashrc

中身は自由ですので、皆さんのお好きな設定を記述ください。
(マウントしちゃってもいいと思います)

次にApache confファイルを作成しておきます

mkdir httpd; cd httpd
vim app.conf

app.conf に下記を記述してください

app.conf
<VirtualHost *:80>
  ServerName localhost
  DocumentRoot /var/www/{laravelアプリ(命名は自由)}/public
  ErrorLog  "/dev/stdout"
  CustomLog "/dev/stdout" combined

  <Directory /var/www/{laravelアプリ(命名は自由)}/public>
    AllowOverride All
    Options Includes FollowSymLinks
    Order allow,deny
    Allow from all
  </Directory>
</VirtualHost>

Laravelアプリケーション_①アプリのclone

実際に稼働するLaravelアプリを作っていきましょう。

ディレクトリを移動して、laravelをgit cloneします。
ここではWEBアプリはrepositoryディレクトリ配下で管理するようにしています。

cd ../../../
mkdir repository; cd repository
git clone -b 6.x https://github.com/laravel/laravel.git {適当なアプリ名}

環境の立ち上げ

下記にてdocker-composeを実行。

cd で移動しまくってて、わかりにくく申し訳ないですが
docker-compose.yamlと同階層で実行すればOKです。

cd ../
docker-compose up

各環境が立ち上がり、各コンテナのログが流れ始めたことを確認します。

Laravelアプリケーション_②初期設定

laravelをcloneした後に実行するお作法です。
権限とcomposer、そしてenv周り

cd repository/{laravelアプリ(命名は自由)}
chmod -R 777 storage
chmod -R 777 bootstrap/cache
docker-compose exec web_php8 /bin/bash -c “composer install”
cp -p .env.example .env
php artisan key:generate

この時点で http://localhost:/ にアクセスし、Laravelのテストページが表示されていたら、構築成功です。

Laravelアプリケーション_③DB周り初期設定

.envに本環境のDB設定を記述しましょう

sed  -e 's/DB_HOST=127.0.0.1/DB_HOST=db/' .env
sed  -e 's/DB_DATABASE=laravel/DB_DATABASE=test_db/' .env
sed  -e 's/DB_USERNAME=root/DB_USERNAME=test_user/' .env
sed  -e 's/DB_PASSWORD=/DB_PASSWORD=test_pass/' .env

接続テスト兼、DBマイグレーション
下記にてmigrateを実行をし、デフォルトのテーブル郡が作成できれば成功です。

docker-compose exec web_php8 /bin/bash -c “cd /app/operation-cms && php artisan migrate

方法は何でもいいですが一例として。
下記にてphpMyAdminからDB内を確認できます。
http://localhost:89/

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

Lambda関数でEmbulkコマンドを実行してみる

はじめに

この記事は BeeX Advent Calendar 2020 の12/16の記事です。

==

前の記事でLambdaのコンテナイメージでの実行を試してみました。

今回はそれに加えて、最近よく使っているEmbulkコマンドを使えないか試してみました。

環境

PC:Windows 10
Docker:Docker version 19.03.13, build 4484c46d9d

前提

  • Dockerの初期設定は完了済み
  • Python3はインストール済み(Lambdaランタイムと同じかそれ以上)
  • AWSアカウントは作成済み
  • Amazon ECRリポジトリは作成済み

実際の操作

lambda functionの作成

@shiro01さんの記事を参考にLambda Functionを作成します。
今回はEmbulkの実行の確認なのでシンプルにヘルプコマンドを実行してみます。

lambda_function.py
import subprocess

def lambda_handler(event, context):
    cmd = ['/usr/bin/embulk','help']
    out = subprocess.run(cmd,shell=True , stdout=subprocess.PIPE)
    print(out.stdout.decode())

1つ注意点として、subprocess.runの引数に"shell=Treu"を追加しています。
これを追加しないとOSErrorが発生します。

参考

Dockerfileの作成

続いてDockerfileを作成します。
Embulkは事前にインストールして実行ファイルを作成しておきます。
DockerfileのCOPYでEmbulkとlambda_functionをコンテナ上にコピーします。

FROM amazon/aws-lambda-python:3.7

COPY lambda_function.py ./
COPY embulk /usr/bin

RUN chmod +x /usr/bin/embulk

CMD [ "lambda_function.lambda_handler" ]

イメージをBuildする

以下のコマンドでイメージをビルドします。

$ cd [DockerFile格納先DIR]
$ docker build -t lambda_embulk .

イメージが作成されます。

$ docker images
REPOSITORY    TAG       IMAGE ID       CREATED              SIZE
lambda_embulk latest    472005da7cf7   About a minute ago   980MB

ローカルテスト

以下のコマンドでローカルでLambdaを実行することが可能です。

$ docker run -p 9000:8080 lambda_embulk:latest
time="2020-12-15T06:11:57.95" level=info msg="exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)"

別のウィンドウを開いて、以下のコマンドを実行します。
curlコマンドのレスポンスとしてはnullが返ってきます。

$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d''{}'
null

コンテナを実行しているウィンドウを確認すると実行された結果がコンソールに表示されています。
"embulk help"コマンドが実行されているので、ローカルテストは問題なさそうです。

$ docker run -p 9000:8080 lambda_embulk:latest
time="2020-12-15T06:13:53.604" level=info msg="exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)"
START RequestId: 0ed577b3-6a07-447a-bccb-c03d255c0201 Version: $LATEST
time="2020-12-15T06:13:57.601" level=info msg="extensionsDisabledByLayer(/opt/disable-extensions-jwigqn8j) -> stat /opt/disable-extensions-jwigqn8j: no such file or directory"
time="2020-12-15T06:13:57.601" level=warning msg="Cannot list external agents" error="open /opt/extensions: no such file or directory"
Embulk v0.9.23
Usage: embulk [-vm-options] <command> [--options]
Commands:
   mkbundle   <directory>                             # create a new plugin bundle environment.
   bundle     [directory]                             # update a plugin bundle environment.
   run        <config.yml>                            # run a bulk load transaction.
   cleanup    <config.yml>                            # cleanup resume state.
   preview    <config.yml>                            # dry-run the bulk load without output and show preview.
   guess      <partial-config.yml> -o <output.yml>    # guess missing parameters to create a complete configuration file.
   gem        <install | list | help>                 # install a plugin or show installed plugins.
   new        <category> <name>                       # generates new plugin template
   migrate    <path>                                  # modify plugin code to use the latest Embulk plugin API
   example    [path]                                  # creates an example config file and csv file to try embulk.
   selfupdate [version]                               # upgrades embulk to the latest released version or to the specified version.

VM options:
   -E...                            Run an external script to configure environment variables in JVM
                                    (Operations not just setting envs are not recommended nor guaranteed.
                                     Expect side effects by running your external script at your own risk.)
   -J-O                             Disable JVM optimizations to speed up startup time (enabled by default if command is 'run')
   -J+O                             Enable JVM optimizations to speed up throughput
   -J...                            Set JVM options (use -J-help to see available options)
   -R--dev                          Set JRuby to be in development mode

Use `<command> --help` to see description of the commands.

END RequestId: 0ed577b3-6a07-447a-bccb-c03d255c0201
REPORT RequestId: 0ed577b3-6a07-447a-bccb-c03d255c0201  Init Duration: 1.03 ms  Duration: 591.61 ms     Billed Duration: 600 mMemory Size: 3008 MB     Max Memory Used: 3008 MB

ECRリポジトリへのPush

AWSコンソールからリポジトリを作成します。
image.png

以下のコマンドで作成したECRリポジトリにイメージをPushします。
ここら辺は前回の記事をほぼ同じです。
Embulkが少々サイズがあるので、前回よりPushに時間がかかります。

$ docker tag lambda_embulk:latest XXXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-embulk
$ docker images
REPOSITORY                                                                     TAG       IMAGE ID       CREATED          SIZE
941996685139.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-embulk                latest    472005da7cf7   11 minutes ago   980MB
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin XXXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded
$ docker push XXXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-embulk:latest
The push refers to repository [XXXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-embulk]
5f70bf18a086: Pushed
65f9fe7cdd01: Pushed
1965e83122e7: Pushed
701bdcbf3b47: Pushed
6e660533f001: Pushed
069cd8bd11dd: Pushed
6e191121f7ea: Pushed
d6fa53d6caa6: Pushed
1fb474cee41c: Pushed
b1754cf6954d: Pushed
464c816a7003: Pushed
latest: digest: sha256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX size: 2624

無事にアップロードできました。
image.png

Lambda作成・実行

AWSのコンソールからLambda関数を作成します。
image.png

Lambda関数が作成されました。
image.png

実行結果確認

timeoutでエラーが出ました。
image.png

Lambdaの基本設定でタイムアウト値が3秒になっていたので、とりあえず3分に変更します。
image.png

少し時間はかかりましたが、正常終了しました。
出力内容を見る限りEmbulkのhelpコマンドも実行できているみたいです。
image.png

さいごに

これが良いかどうかは別として、無事にLambdaでEmbulkを実行することができました。
多分実運用するならLambdaのスペックとかdiffファイルどこで保持するとか色々検討すべきことはありますが、とりあえずs3に置いてそこから小規模の処理を動かすとかならいけるんじゃないですかね?知らんけど。

ただEmbulkの処理ってLambdaの実行時間に収まることあまりないから、その辺りは難しいところかもしれません。

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

docker環境でlaravel/Duskをインストール(laravel6)

煮詰まっちゃったんで記事に残しておきます

前提条件

simotarooさんの絶対に失敗しないDockerでLaravel+Vueの実行環境(LEMP環境)を構築する方法、もしくはdockerにてlaravel,PHPの環境構築済みの方

環境

mac

docker (LEMP環境)

構築方法

任意のdockerfileに以下を追加しbuildし直す
※僕はsimotarooさんの記事を参考にして構築したためphpのdockerfileに記述しました

dockerfile
RUN apt-get -y install libzip-dev
RUN docker-php-ext-install zip


RUN apt-get -y install libnss3
RUN apt-get -y install libasound2-data  libasound2 xdg-utils
RUN apt install sudo
RUN apt-get install -y wget
RUN apt-get install -y gnupg2

RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list
RUN sudo apt update
RUN sudo apt-get install -y google-chrome-stable
RUN apt-get install -y fonts-ipafont 

.envのAPP_URLを書き換え

.env
APP_URL = http://localhost を

APP_URL=http://127.0.0.1:8000 に書き換え

tests/DuskTestCase.phpを書き換え

tests/DuskTestCase.php
    protected function driver()
    {
        $options = (new ChromeOptions)->addArguments([
            '--no-sandbox',
            '--disable-gpu',
            '--headless',
            '--window-size=1920,1080',
            '--lang=ja_JP',
        ]);

インストール後プロジェクト内で

composer require --dev laravel/dusk:
artisan dusk:install
php artisan serve

そんでテスト実行

php artisan dusk
PHPUnit 8.5.8 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 11.2 seconds, Memory: 24.00 MB

OK (1 test, 1 assertion)

成功したら無事完成です

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

【Docker】MySQLとの接続

概要

  • ホストとコンテナの標準出力を結びつける

標準出力の結びつけ

  • psオプションでCONTAINER ID または NAMESを確認する
% docker ps
CONTAINER ID   IMAGE               COMMAND                  CREATED      STATUS         PORTS                    NAMES
  • itオプションで標準出力を結びつける
    • -i:ホストの標準入出力をコンテナに結びつける
    • -t:コンテナの標準入出力をコンテナに結びつける
% docker exec -it [CONTAINER ID または NAMES] bash
  • mysqlにログイン(passはdatabase.ymlで確認)
    • -u:ユーザーの指定
    • -p:パスワード使用
% mysql -u root -p

以上

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

Nuxt.js + Rails APIをDocker上で立ち上げCRUD操作してみる

今回初めて Nuxt.js を触りました。
Todoアプリを作ろうかなと思ったのですが, せっかくならAPIを叩こうじゃないかということでサーバーサイドも用意してみました。

サーバーサイドはRuby on Rails(API), クライアントサイドはNuxt.ts(Nuxt.js + TypeScript), DBはpostgresという構成で実装していきます。

環境構築に関しては, サーバーサイド/クライアンドサイド共にDocker上で動かしており, ディレクトリ構成はモノシリックにまとめました。

↓ソースコードはこちら
Kazuhiro-Mimaki/nuxt-rails-crud - GitHub

動作環境

macOS Catalina : version 10.15.4
Docker for macはインストール済みとする。

ディレクトリ構成

ディレクトリ構成
.
├── client-side
├── server-side
└── docker-compose.yml

1. サーバーサイド(Ruby on Rails)

Dockerfile作成

server-side/ 配下にdockerfileを作成。

Dockerfile
FROM ruby:2.7.0

RUN apt-get update -qq && \
  apt-get install -y \
  build-essential \
  libpq-dev \
  nodejs \
  postgresql-client

WORKDIR /app

COPY Gemfil Gemfile.lock /app/
RUN bundle install

Gemfile, Gemfile.lock作成

同じく server-side/ 配下にGemfileとGemfile.lockを作成。

Gemfile内に以下を記述。

Gemfile
source 'https://rubygems.org'
gem 'rails', '6.0.3'

Gemfile.lockは空のままで大丈夫。

docker-compose.yml作成

railsとpostgresの設定をdocker-compose.ymlに書いていきます。

docker-compose.yml
version: '3.8'

volumes:
  db_data:

  services:
    db:
      image: postgres
      volumes:
        - db_data/var/lib/postgresql/data
      environment:
        POSTGRES_PASSWORD: password

    server-side:
      build: ./server-side/
      command: bundle exec rails server -b 0.0.0.0
      image: server-side
      ports:
        - 3000:3000
      volumes:
        - ./server-side:/server-app
      tty: true
      stdin_open: true
      depends_on:
        - db
      links:
        - db

APIモードで rails new

以下のコマンドを叩けば, server-side/ 配下にrails関連のファイル群が作成されます。

$ docker-compose run server-side rails new . --api --force --database=postgresql --skip-bundle

database.yml の内容を修正

このままだとserver-sideのコンテナからDBのコンテナにアクセスできないので database.yml の内容を修正します。

以下のようになっていると思うので

database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

以下のように編集。

database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  user: postgres
  password: password
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

server-side ホストを受け入れるように修正

この設定をすることで, Nuxtからserver-sideにアクセスできます。

server-side/config/environments/development.rb
config.hosts << "server-side"

DBを作成

以下のコマンドを叩いてdbを作成。

$ docker-compose run server-side rails db:create

動作させてみる

以下のコマンドを打って, localhost:3000 にアクセス。
railsのデフォ画面が表示されればOK!

$ docker-compose up -d

サーバーサイドのAPIを実装

以下のコマンドを叩き, コンテナの中に入った上で作業を進めていきます。

$ docker exec -it server-side bash

ルーティングを設定。

routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  namespace :api do
    namespace :v1 do
      resources :todos do
        collection do
          get :complete
        end
      end
    end
  end
end

Todoモデル, todosコントローラーを作成。

$ rails g model Todo title:string isDone:boolean
$ rails db:migrate
$ rails g controller api::v1::todos

controllerの中身は以下のように書きました。

api/app/controllers/api/v1/posts_controller.rb
class Api::V1::TodosController < ApplicationController
  before_action :set_todo, only: [:update, :destroy]

  def index
    todos = Todo.where(isDone: false)
    render json: { status: 'SUCCESS', message: 'Loaded todos', data: todos }
  end

  def complete
    todos = Todo.where(isDone: true)
    render json: { status: 'SUCCESS', message: 'Loaded todos', data: todos }
  end

  def create
    todo = Todo.new(todo_params)
    if todo.save
      render json: { status: 'SUCCESS', data: todo }
    else
      render json: { status: 'ERROR', data: todo.errors }
    end
  end

  def destroy
    @todo.destroy
    render json: { status: 'SUCCESS', message: 'Deleted the todo', data: @todo }
  end

  def update
    if @todo.update(todo_params)
      render json: { status: 'SUCCESS', message: 'Updated the todo', data: @todo }
    else
      render json: { status: 'ERROR', message: 'Not updated', data: @todo.errors }
    end
  end

  private

    def set_todo
      @todo = Todo.find(params[:id])
    end

    def todo_params
      params.require(:todo).permit(:title, :isDone)
    end
end

動作確認

この記事を参考に, Postmanを利用してCRUD操作ができるかどうか確認します。
curlコマンドでも確認できますが, たぶんPostmanの方が楽。

2. クライアントサイド(Nuxt.js)

環境構築

基本的には 公式のInstallation に沿って進めるだけ。
nodeはインストール済みとします。(今回の環境では12/15現時点でのLTS ver. 14.15.1を使用しています。)

プロジェクトの作成

まずは create-nuxt-app で雛形作りましょう。

$ npx create-nuxt-app client-side

色々質問されると思うのですが, 今回は以下のように設定しました。(その他はデフォルト)

terminal
? Project name: client-side
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: None
? Nuxt.js modules: Axios
? Linting tools: None
? Testing framework: None
? Rendering mode: Single Page App
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: None
? Version control system: None

この辺りの設定は各自の好みで設定してください。
全てのオプションは ここから 確認できます。

Dockerfile作成

client-side/ 配下にDockerfileを作成。

Dockerfile
FROM node:14.15.1

WORKDIR /client-app

COPY package.json yarn.lock ./

RUN yarn install

CMD ["yarn", "dev"]

docker-compose.ymlに client-side の設定を追加

server-side の設定を記述したdocker-compose.yml に client-side の設定を追加します。

docker-compose.yml
version: '3.8'

volumes:
  db_data:

services:
  db:
    image: postgres
    volumes:
      - db_data/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password

  server-side:
    build: ./server-side/
    image: server-side
    ports:
      - 3000:3000
    volumes:
      - ./server-side:/server-app
    command: bundle exec rails server -b 0.0.0.0
    tty: true
    stdin_open: true
    depends_on:
      - db
    links:
      - db

  # ここから下を追加
  client-side:
    build: ./client-side/
    image: client-side
    ports:
      - 8000:8000
    volumes:
      - ./client-side:/client-app
      - /client-app/node_modules
    command: sh -c "yarn && yarn dev"

portの設定

このままだとエラーが出るので, portとhostを以下のように設定します。

nuxt.config.js
export default {
  // Disable server-side rendering (https://go.nuxtjs.dev/ssr-mode)
  ssr: false,

  // ここを追記
  server: {
    port: 8000,
    host: '0.0.0.0',
  },

// 以下省略
}

動作させてみる

以下のコマンドを打って, localhost:8000 にアクセスするとNuxt.jsのデフォ画面が表示されます。

$ docker-compose up -d

これで環境構築は完了!

3. サーバーサイドとクライアントサイドの連携

いよいよクライアント側からサーバーサイドのAPIを叩きにいきます。
感動の瞬間。。。

CORS (オリジン間リソース共有) 問題を解消

CORSについては こちらの記事 が参考になると思います。
公式GitHubのREADMEに解決方法がありました。
READMEの記述を参考に @nuxtjs/proxy をインストールし, app/nuxt.config.js を以下のように編集します。
サーバーサイドのポート番号をは3000で指定していたので, ここは server-side:3000で。(コンテナ間の通信はコンテナ名で解決するため, localhostではなくserver-sideにしている。)

$ yarn add @nuxtjs/proxy
app/nuxt.config.js
modules: [
  '@nuxtjs/axios',
  '@nuxtjs/proxy'
],
// 以下を追加
proxy: {
  '/api': {
    target: 'http://server-side:3000',
    pathRewrite: {
      '^/api': '/api/v1/',
    },
  },
},

Composition APIとaxiosを設定

この辺り使いたいので設定しましたが, なくてもCRUD操作はできます。

shell
$ yarn add @nuxtjs/composition-api
client-side/nuxt.config.js
modules: [
  '@nuxtjs/proxy',
  //追加
  '@nuxtjs/axios',
  '@nuxtjs/composition-api',
],
client-side/tsconfig.json
"types": [
  "@types/node",
  "@nuxt/types",
  #追加
  "@nuxtjs/axios"
]

型定義

client-side に新たに models/todo.ts ディレクトリを作り, 以下を記述。

todo.ts
export interface ITodo {
  id: number;
  title: string;
  isDone: boolean;
}

viewを記述

本当はコンポーネントに分割して書くべきですが, 今回は1ファイルにまとめた方が見やすいかなと思ったのでまとめます。

client-side/pages/index.vue に以下の内容を記述。

client-side/pages/index.vue
<script lang="ts">
import {
  defineComponent,
  reactive,
  ref,
  onMounted,
} from "@nuxtjs/composition-api";
import { ITodo } from "../models/todo";
import $axios from "@nuxtjs/axios";

export default defineComponent({
  setup(_, { root }) {
    onMounted(() => {
      getTodo();
    });

    const todoItem = reactive({
      title: "",
      isDone: false,
    });

    const todoList = ref<ITodo[]>([]);
    const completeTodoList = ref<ITodo[]>([]);

    // todoをpost
    const addTodo = async () => {
      try {
        await root.$axios.post("/api/todos/", {
          title: todoItem.title,
          isDone: todoItem.isDone,
        });
        getTodo();
        todoItem.title = "";
      } catch (e) {
        console.log(e);
      }
    };

    // todoをget
    const getTodo = async () => {
      try {
        const response = await root.$axios.get("/api/todos");
        todoList.value = { ...response.data.data };
        getCompleteTodo();
      } catch (e) {
        console.log(e);
      }
    };

    // todoをupdate
    const updateTodo = async (i: number, todo: ITodo) => {
      try {
        const newTodo = todoList.value[i].title;
        await root.$axios.patch(`/api/todos/${todo.id}`, { title: newTodo });
      } catch (e) {
        console.log(e);
      }
    };

    // todoをdelete
    const deleteTodo = async (id: number) => {
      try {
        await root.$axios.delete(`/api/todos/${id}`);
        getTodo();
      } catch (e) {
        console.log(e);
      }
    };

    // todoをdone
    const completeTodo = async (todo: ITodo) => {
      try {
        todo.isDone = !todo.isDone;
        await root.$axios.patch(`/api/todos/${todo.id}`, {
          isDone: todo.isDone,
        });
        getTodo();
      } catch (e) {
        console.log(e);
      }
    };

    // complete_todoをget
    const getCompleteTodo = async () => {
      try {
        const response = await root.$axios.get("/api/todos/complete");
        completeTodoList.value = { ...response.data.data };
      } catch (e) {
        console.log(e);
      }
    };

    return {
      todoItem,
      todoList,
      completeTodoList,
      addTodo,
      deleteTodo,
      updateTodo,
      completeTodo,
    };
  },
});
</script>

<template>
  <div class="container">
    <section class="todo-new">
      <h1>Add todos</h1>
      <input v-model="todoItem.title" type="text" placeholder="todoを記入" />
      <button @click="addTodo()">Todoを追加</button>
    </section>

    <section class="todo-index">
      <h1>Incomplete todos</h1>
      <ul>
        <li v-for="(todo, i) in todoList" :key="i">
          <input
            class="item"
            type="checkbox"
            :checked="todo.isDone"
            @change="completeTodo(todo)"
          />
          <input
            class="item"
            type="text"
            v-model="todo.title"
            @change="updateTodo(i, todo)"
          />
          <button @click="deleteTodo(todo.id)">削除する</button>
        </li>
      </ul>
    </section>

    <section class="todo-complete">
      <h1>Complete todos</h1>
      <ul>
        <li v-for="(todo, i) in completeTodoList" :key="i">
          <input
            class="item"
            type="checkbox"
            :checked="todo.isDone"
            @change="completeTodo(todo)"
          />
          {{ todo.title }}
          <button @click="deleteTodo(todo.id)">削除する</button>
        </li>
      </ul>
    </section>
  </div>
</template>

<style>
.container {
  margin: 80px auto;
  min-height: 100vh;
  text-align: center;
}

section {
  margin-bottom: 30px;
}

.item {
  font-size: 1rem;
  margin: 0 10x;
}

li {
  list-style: none;
  margin-bottom: 0.5em;
}
</style>

実際に動作させてみる

docker-compose upさせて, localhost:8000 にアクセスすると以下のような画面になると思います。

スクリーンショット 2020-12-09 10.36.16.png

実際にtodoを追加/編集/削除してみてください。

まとめ

Dockerfileを1から書いたのも初めてだったので良い勉強になりました。
Nuxt.jsに関しては知らないことしかないので勉強していきます。
「ここのコードもっとこうした方がいいよ!」というのがあればぜひアドバイスお願いします。

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

Dockerを使用してマルチノードでOpen Distro for Elasticsearchのローカル開発環境を構築

今回の記事にするOpen Distro for Elasticsearch(以下odfe)は、Amazon Elasticsearch Service(以下Amazon ES)のアップストリームです。

将来的にAmazon ESで運用する予定のため開発環境をodfeで構築しています。
マルチノードでodfeの開発環境を構築するときに詰まった箇所があるので、今回はそのことについてまとめていきたいと思います。

環境構築

環境構築は基本的にはodfeの公式ドキュメントに準じて行っています。
https://opendistro.github.io/for-elasticsearch-docs/docs/install/docker/

詰まった箇所①クラスター内に3つのマスター適格ノードを含める

ですが、公式ドキュメントではクラスターを2ノードで構成しており、ドキュメントに沿って2ノードで作成することは耐障害性の面で非推奨です。
Elastic社から以下のように説明されています。

通常は、クラスター内に3つのマスター適格ノードを含めることが推奨されます。ノードの1つに障害が発生しても、その他の2つで安全にクォーラムを形成し、続行できるからです。クラスター内のマスター適格ノードが2つ以下の場合は、そのいずれの障害にも耐えられません。それとは逆に、クラスター内にマスター適格ノードが4つ以上ある場合は、マスターの選出およびクラスターステータスの更新に時間がかかる可能性があります。(新世代Elasticsearchクラスターコーディネーション

なので、今回はクラスターを3つのマスター適格ノードで構成するようdocker-composeファイルに設定を書いています。
参考記事:Important discovery and cluster formation settings

補足

また、バージョン6系まではマスター適格ノードを偶数台にしないよう設定する必要があったのですが、バージョン7系から新たなクラスターコーディネーションの仕組みが導入されて、偶数台構成であってもElasticsearchが自動で調整しスプリットブレインの発生を回避してくれるようになっています。

詳細はElastic Stack実践ガイド(Elasticsearch/Kibana編)筆者のQiitaがとても分かりやすいので、ご覧ください。「ElasticsearchのMasterノードって偶数台で構成してもいいんですか?」(2020年版)

dockerファイル

今回作成したDockerファイルとdocker-composeファイルは以下です。

Dockerfile.es
FROM amazon/opendistro-for-elasticsearch:1.11.0

RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch analysis-kuromoji
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch analysis-icu
docker-compose.yml
version: "3"
services:
  elasticsearch-node1:
    build:
      context: .
      dockerfile: dockerfiles/Dockerfile.es
    container_name: elasticsearch-node1
    environment:
      - cluster.name=elasticsearch-cluster
      - node.name=elasticsearch-node1
      - discovery.seed_hosts=elasticsearch-node1,elasticsearch-node2,elasticsearch-node3
      - cluster.initial_master_nodes=elasticsearch-node1,elasticsearch-node2,elasticsearch-node3
      - bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536 # maximum number of open files for the Elasticsearch user, set to at least 65536 on modern systems
        hard: 65536
    volumes:
      - elasticsearch-data1:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
      - 9600:9600 # required for Performance Analyzer
    networks:
      - elasticsearch-net
  elasticsearch-node2:
    build:
      context: .
      dockerfile: dockerfiles/Dockerfile.es
    container_name: elasticsearch-node2
    environment:
      - cluster.name=elasticsearch-cluster
      - node.name=elasticsearch-node2
      - discovery.seed_hosts=elasticsearch-node1,elasticsearch-node2,elasticsearch-node3
      - cluster.initial_master_nodes=elasticsearch-node1,elasticsearch-node2,elasticsearch-node3
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - elasticsearch-data2:/usr/share/elasticsearch/data
    networks:
      - elasticsearch-net
  elasticsearch-node3:
    build:
      context: .
      dockerfile: dockerfiles/Dockerfile.es
    container_name: elasticsearch-node3
    environment:
      - cluster.name=elasticsearch-cluster
      - node.name=elasticsearch-node3
      - discovery.seed_hosts=elasticsearch-node1,elasticsearch-node2,elasticsearch-node3
      - cluster.initial_master_nodes=elasticsearch-node1,elasticsearch-node2,elasticsearch-node3
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - elasticsearch-data3:/usr/share/elasticsearch/data
    networks:
      - elasticsearch-net
  kibana:
    image: amazon/opendistro-for-elasticsearch-kibana:1.11.0
    container_name: elasticsearch-kibana
    ports:
      - 5601:5601
    expose:
      - "5601"
    environment:
      ELASTICSEARCH_URL: https://elasticsearch-node1:9200
      ELASTICSEARCH_HOSTS: https://elasticsearch-node1:9200
    networks:
      - elasticsearch-net

volumes:
  elasticsearch-data1:
  elasticsearch-data2:
  elasticsearch-data3:

networks:
  elasticsearch-net:

設定値についてもodfeの公式ドキュメントで説明されているので参照されると良いと思います。

Amazon ESでは、デフォルトですべてのドメインにICU AnalysisとJapanese (kuromoji) Analysisのプラグインが含まれているため、odfeの開発環境にも該当のプラグインを入れています。

詰まった箇所②マルチノードで立ち上げると一部のコンテナが立ち上がらない

docker-composeでコンテナを起動する時に、マルチノードで立ち上げようとすると、一部のコンテナがexit with 137で終了してしまいました。

exit with 137で終了し、一部のコンテナが立ち上がらない場合、Docker Engineのメモリ不足の可能性が考えられます。

Issueにも上がっているので、自分と同様に一部のコンテナが立ち上がらなかった方は、Docker Desktopの設定画面からDocker Engineのメモリを増やす対応を取ってみてください。
自分の環境ではこの対応でexit with 137が出なくなりすべてのコンテナが立ち上がりました。

おわりに

マルチノードでodfeの開発環境を構築するときに詰まった箇所を紹介しました。
私自身Elasticsearchの利用ははじめてなので、設定に不備がある場合はコメント、編集リクエストをいただけますと幸いです。

明日のアドベントカレンダーは@mt-kageが担当します!お楽しみに〜!

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

ECR Public をDockerHubの代わりに使ってみた(DockerHubのDownload Rate Limit対策)

あらまし

CodeBuildでDockerのビルド中にDockerHubにひっかかりました。AWSの新サービスECR Publicを使って簡単に回避できました。

事象

CodeBuildでubuntu をbaseイメージとしたDockerコンテナをビルドしようとしたところ、以下のエラーが発生しました。

error pulling image configuration: toomanyrequests: Too Many Requests. Please see https://docs.docker.com/docker-hub/download-rate-limit/

調査

DockerHubが最近ダウンロード数に制限をかける事になった。ダウンロード数はグローバルIP/アカウント(ログインすれば)ごとに制限がかかるとの事。
クラスメソッドさんの記事が詳しい。
“Too Many Requests.” でビルドが失敗する…。AWS CodeBuild で IP ガチャを回避するために Docker Hub ログインしよう!という話

対応

(DockerHubの認証めんどくさいな、、、、と思ってたところ)ちょうど、AWSがDockerHubの代替となるサービス ECR Publicを提供し始めたので、こちらを利用する事にしました。

[速報]AWS、Docker Hubの代替を狙う「Amazon Elastic Container Registry Public」提供開始。AWS re:Invent 2020
AWSがDocker Hubの代替サービスを発表予告。パブリックにコンテナイメージを公開可能で50GBまで無料、AWSからなら何度でもプルし放題に

DockerHubに認証を入れるパターンと比較して、

  • 簡単
  • 無料枠の幅が大きい
  • AWSサービスで使うならRateLimit気にしなくて良い

というメリットがあると判断しました。

ECR Publicにアクセスすると、主要な公式イメージは登録されてる模様です。(全て確認したわけではないです)

image.png

ImageURIがあるので(tagの構成はDockerHubと同じ)、DockerFileのfromを書き換えるだけです。

FROM public.ecr.aws/ubuntu/ubuntu:bionic

どうやらDockerHubと全く同じものがhostingされているらしく(同じhashでしたし、Dockerのcacheも効きました)、単純に書き換えるだけですんなり動きました。

nginxのDocker用リポジトリを見たら、自動でECRPublicにpushするようなスクリプトがありました。

まとめ

少し使ってみただけですが、公式イメージの取得元として、DockerHubの代替としてECR Publicは使えそうです。
特にAWS環境を使っている場合は、特に制限も無いので特に有用だと思われます。

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

【Docker】コンテナ作成までの操作#2

本投稿の目的

・Dockerの操作についての議事録です。


学習に使った教材

Udemyの "米国AI開発者がゼロから教えるDocker講座" を教材として使用しました。


○container作成 (2.image作成する場合)

○build contextの作成

mkdir <build context名>

・build context とは,docker image を作成する際のimage元となるフォルダ

○build contextへ移動

cd <build context名>

・カレントディレクトリが build context の状態にする

○Dockerfileの作成,編集

touch Dockerfile

・build context 内にDockerfileというテキストファイルを作成 (これは naming convention *命名規則)
・この中に書いたコマンド実行されてimageが作成される (編集方法は別途記事を書くので割愛)

○imageの作成

docker build .

・ "." はカレントディレクトリを意味する (カレントディレクトリうちのDockerfileを使てimage作成が実行される)
・名前とTAGを指定してないので "none" というimageが作成される
・dangling image とよばれる

【Dockerfileを明示的に指定する場合】

docker build -f docker -f <Dockerfileのpath/名前>

・-f はDockerfileを明示的に指定するためのオプション
・ の部分には適切なpathとファイル名を記述する

【名前とTAGを指定して作成する場合】

docker build -t <image名>:<TAG> .

・-t は名前を付けてimageを作成するためのオプション
・ の部分は,通常はlatestと書く

○containerの作成

docker run <image名>

・作成したいcontainerの素となるimage名を指定してrunを実行
・#1で説明したとおり,これではデフォルトコマンドを実行後にexitしてcontainerから抜けてしまう

【run後のデフォルトコマンドを上書きして実行】

docker run <image名> <上書きコマンド>

・デフォルトコマンドの代わりに上書きコマンドを実行
・これでは上書きコマンドを実行後にexitしてcontainerから抜けてしまう
・デフォルトコマンドはrun実行後に docker ps -a のCommandから確認可能
・上書きするとここに指定したCommandが表示される

【run後にcontainerに残るための記述】

docker run -it <image名> bash

・これでcontainer起動後にexitせずにcontainerの中にとどまる
・そのため,ターミナルの実行前の記述が以下のように変化する

root@xxxxxxxxxxx:/#

・これはContainer上のOSにbashを通してアクセスする状態になったことを意味する
・デフォルトではコンテナ起動後はrootユーザーになる
・Continaerからexitで抜ける処理は実行されていない
・container から抜けるときは以下を実行するとHostのターミナル状態に戻る

exit

○-itとは?

docker run -it <image名> bash

・run後にcontainerに残るための記述に使用することが多いコマンド
・-iと-tを重ねて-it
・それぞれには以下の意味がある

○-i
・インプット可能
・HOSTからコンテナへのSTDIN(入力ch)を繋ぐコマンド
・繋がないとHostにしか入力が伝わらないため,コンテナへ指示が届かない

【chとは】
・ "STDIN" "STDOUT" "STDERR" の3つがある
・STDIN : キーボードの入力をプログラムへ繋ぐためのch
・STDOUT,STDERR : プログラム結果をモニタへ出力するためのch

○-t
・ターミナル表示が綺麗になる (pretyにする)
・コマンド保管も使用可能になる


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

Dockerを使ってLaravelのローカル開発環境を作る(Apache版)

Laravel Advent Calendar 2020 - Qiita の 15日目 の記事です。
昨日は @noel_kuma さんのLinter / FormatterからLaravelへの贈り物の記事でした!
明日は @kyoya0819 さんの記事です!

概要

最強のLaravel開発環境をDockerを使って構築する【新編集版】

この記事の反響が多く、Apache版も作って欲しいという要望もあってので記事を書いてみました。

対象読者

  • Laravelを愛する心の持っている方
  • PHP, Linux, Git, Dockerの知識をある程度持っている方

リポジトリ

https://github.com/ucan-lab/docker-laravel-apache

使い方

A. Laravelプロジェクトの新規作成

$ git clone git@github.com:ucan-lab/docker-laravel-apache.git
$ cd docker-laravel-apache
$ make create-project

http://localhost

以上の3ステップでLaravelの新規プロジェクトの環境構築は完了です。

A'. Laravelプロジェクトをバージョン指定して新規作成

Makefile のLaravelインストール部分を書き換えます。

    docker-compose exec app composer create-project --prefer-dist "laravel/laravel=6.*" .

B. 既存のLaravelプロジェクトの環境を構築する

$ git clone git@github.com:ucan-lab/docker-laravel-apache.git

# Laravelプロジェクトを docker-laravel-apache/backend へクローンする
$ git clone git@github.com:laravel/laravel.git docker-laravel-apache/backend

$ cd docker-laravel-apache
$ make init

http://localhost

これで既存のLaravelプロジェクトの環境構築は完了です。

コンテナ構成

├── web
└── db

web, db の2つのコンテナ構成で進めます。

nginxとの違い

Nginxの場合は、php-fpm(アプリケーションサーバー)とコンテナを分けていました。
Apacheの場合は、mod_phpというモジュールがデフォルトでインストールされており、それがPHPを実行してくれるアプリケーションサーバーを兼ねるウェブサーバーです。

ディレクトリ構成

.
├── backend # Laravelプロジェクトのルートディレクトリ
├── infra
│     └── docker
│          ├── apache
│          │   └── httpd.conf
│          ├── mysql
│          │   ├── Dockerfile
│          │   └── my.cnf
│          └── php
│              ├── Dockerfile
│              └── php.ini
├── .env.example
├── Makefile
└── docker-compose.yml

nginx版との差分

※nginx版との違いはnginxのディレクトリがapacheディレクトリに変わったくらいですね?

  • infra/docker/apache/httpd.conf
  • infra/docker/php/Dockerfile
  • Makefile
  • docker-compose.yml

変更点に関しては、上記の4つのファイルに注目していただければ良いかなと思います。
プルリクエストで詳細な差分を確認できます。

PHP のベースコンテナ

イメージタグに php:<version>-apache が用意されています。

解説など

docker-compose.yml

version: "3.8"
volumes:
  db-store:
services:
  web:
    build:
      context: .
      dockerfile: ./infra/docker/php/Dockerfile
    ports:
      - ${WEB_PORT:-80}:80
    volumes:
      - ./backend:/work/backend
    environment:
      - DB_CONNECTION=mysql
      - DB_HOST=db
      - DB_PORT=3306
      - DB_DATABASE=${DB_NAME:-laravel_local}
      - DB_USERNAME=${DB_USER:-phper}
      - DB_PASSWORD=${DB_PASS:-secret}

  db:
    build:
      context: .
      dockerfile: ./infra/docker/mysql/Dockerfile
    ports:
      - ${DB_PORT:-3306}:3306
    volumes:
      - db-store:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=${DB_NAME:-laravel_local}
      - MYSQL_USER=${DB_USER:-phper}
      - MYSQL_PASSWORD=${DB_PASS:-secret}
      - MYSQL_ROOT_PASSWORD=${DB_PASS:-secret}

appとwebコンテナをまとめて、webコンテナのみになりました。
どっちにするか悩みましたけど、webコンテナにしました。

解説することないので、次へ行きます。

infra/docker/php/Dockerfile

infra/docker/php/Dockerfile
FROM node:14-buster as node
FROM php:7.4-apache-buster
LABEL maintainer="ucan-lab <yes@u-can.pro>"
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

# timezone environment
ENV TZ=UTC \
  # locale
  LANG=en_US.UTF-8 \
  LANGUAGE=en_US:en \
  LC_ALL=en_US.UTF-8 \
  # composer environment
  COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer

# composer command
COPY --from=composer:2.0 /usr/bin/composer /usr/bin/composer
# node command
COPY --from=node /usr/local/bin /usr/local/bin
# npm command
COPY --from=node /usr/local/lib /usr/local/lib
# yarn command
COPY --from=node /opt /opt

RUN apt-get update && \
  apt-get -y install git libicu-dev libonig-dev libzip-dev unzip locales && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  locale-gen en_US.UTF-8 && \
  localedef -f UTF-8 -i en_US en_US.UTF-8 && \
  a2enmod rewrite && \
  docker-php-ext-install intl pdo_mysql zip bcmath && \
  composer config -g process-timeout 3600 && \
  composer config -g repos.packagist composer https://packagist.org

COPY ./infra/docker/php/php.ini /usr/local/etc/php/php.ini
COPY ./infra/docker/apache/httpd.conf /etc/apache2/sites-available/000-default.conf

WORKDIR /work/backend

php, composer, nodeの3つのコンテナをマルチステージビルドして使ってます。
(phpのイメージ内にapacheも入ってます。)

nodeいらないよって場合はDockerfileから削除しちゃってください。
英語圏の方にも使ってもらえるように言語&タイムゾーンのデフォルトは英語にしてます。
実際に使うときは日本に変更してご利用ください。

a2enmod rewrite はApacheのmod_rewriteモジュールを有効化してます。
これがないとLaravelのルーティングがうまく機能しなくなります。

infra/docker/apache/httpd.conf

infra/docker/apache/httpd.conf
<VirtualHost *:80>
    ServerName example.com
    ServerAdmin webmaster@localhost
    DocumentRoot /work/backend/public

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    <Directory /work/backend/public>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

最低限設定必要な項目だけ設定してます。

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

DockerとAlfredでMySQLとRedisのお手軽環境構築

はじめに

2020 年度 XTech グループアドベントカレンダーの 15 日目の記事です。
エキサイト株式会社、新卒の西牧です。

今回は、Docker と Alfred で MySQL と Redis をお手軽に環境構築する方法を紹介したいと思います。
必要なもの Docker で立ち上げて、Alfred でどこからでも呼び出せるようにするという流れです。
コードはこちら

環境

macOS: 10.15.1
docker-compose: 1.27.4
Alfred: 4.0.9

docker-composeですべて立ち上げる

docker-compose で以下のものを立ち上げます。

  • MySQL
  • Redis
  • adminer
  • redisinsight

MySQL の GUI クライアントには adminer を、Redis の GUI クライアントには redis-insight を使います。
理由としては、デスクトップアプリより web アプリがよかった(個人的にはできるだけ web で完結したい)のと、軽量なことぐらいです。
ちなみに以前は、Sequel Pro と Medis を使っていましたが、どちらもあまり使いやすくなく、adminer と redisinsight の方が個人的にはいいと思います。

$ docker-compose up -d
version: '3'

services:
  redis:
    image: redis:latest
    restart: always
    ports:
      - 6378:6379
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - ./redis/data:/data
      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf

  redisinsight:
    image: redislabs/redisinsight
    restart: always
    ports:
      - 8011:8001
    volumes:
      - ./redisinsight:/db
    depends_on: 
      - redis

  mysql:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_USER: docker
      MYSQL_PASSWORD: docker
      MYSQL_ROOT_PASSWORD: local_root_password
      MYSQL_DATABASE: db
    ports:
      - 3307:3306
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/conf.d:/etc/mysql/conf.d

  adminer:
    image: adminer:latest
    restart: always
    environment:
      # choose your favorite design from https://www.adminer.org/
      ADMINER_DESIGN: lucas-sandery
    ports:
      - 8090:8080
    depends_on:
      - mysql

動作確認

立ち上げられると、GUI クライアントを開いて、MySQL と Redis に接続してみます。

adminer を開くとこんな感じです。デザインは環境変数で変えられます。
接続情報のサーバには、docker-compose.yml で書いたサービス名 mysql を入れます。
image.png

image.png

redisinsight はこんな感じです。
接続情報は以下の通りです。

  • Name: 任意の名前
  • Host: docker-compose.yml で書いたサービス名 redis
  • Port: 6379

image.png

image.png

GUI クライアントをどこからでも呼び出せるようにする

Alfred を使います。
Alfredとはなんぞや?という方は、Alfredを使いこなせてない君に!【Alfredの使い方完全版】
いうたら、spotlight 検索の上位互換みたいなやつです。
Alfred の Web Search に 先ほどの http://localhost:8090/ とかを登録しておくと、ポート番号を覚えておく必要もないし、どっからでも呼び出せるので便利です。
Web Search というのは、URL にキーワードを紐付けて登録しておくと、Alfred でそのキーワードを実行することで、登録した URL をブラウザで開けるというものです。
画像のようにして http://localhost:8090/ を登録してみます。

image.png

登録後、実行すると、登録した URL が自動で開きます。
image.png

おわりに

最近は Docker で入れられるツールも増えてきているので、うまく活用すると、オンボーディングコストさげられそうだなと、これ書いてて思いました。
みなさんもためしてみてください〜

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

「Rails APIモードとReact Hooksを使ってToDoリストを作る」をdocker上に配置する

目的

これもやったことの備忘録が目的になります。
Know Howより、How to methodな内容にします

背景

バックエンドとフロントエンドを分けたアプリケーションをdockerにあと乗せする記事がなかったため。
せっかくやったのだから記事にしちゃおうって考えです。

アウトライン

  1. docker-compose.ymlを用意する
  2. Dockerfile(Backend用)を用意する
  3. entrypoint.shを用意する
  4. Dockerfile(Frontend用)を用意する
  5. rails側の調整
  6. axiosmaterial-uiのインストール

やってみよう!

docker-compose.yml

既存のrailsアプリ等に「後からdocker」を用意する場合でも、
アプリを作り始める時でも、やることはほとんど変わりません。

docker-compose.ymlはメインのディレクトリ上に配置します。

docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.6
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    volumes:
      - mysql-data:/var/lib/mysql 
    ports:
      - "4306:3306" #別件で3306は使っていたので4306を指定しました

  app:
    build: 
      context: .
      dockerfile: Dockerfile_back
    command: /bin/sh -c "rm -f /myapp/tmp/pids/server.pid && bundle exec rails s -p 3001 -b '0.0.0.0'"
    image: rails:dev
    volumes:
      - .:/myapp    #myappというところは任意で設定してください
      - ./app/vendor/bundle:/myapp/vendor/bundle 
    environment:
      TZ: Asia/Tokyo
      RAILS_ENV: development
    ports:
      - "3001:3001"
    depends_on: 
      - db

  front:
    build: 
      context: todo_front
      dockerfile: Dockerfile_front
    volumes:
      - ./todo_front:/todo_front
    command: /bin/sh -c "cd todo_front && yarn && yarn start"
    ports:
      - "3000:3000"

volumes:
  mysql-data:
  bundle: 

Dockerfile(バックエンド用)を用意する

entrypoint.shを用意する

Rubyのバージョンを既存のrailsアプリのRubyバージョンを合わせました。
それとmyappというのは任意になります。合わせてもOKです!
SQLがmysql-clientと入力するとエラーになります。
いつしか、maliadb-clientに統一されたようです。

Dockerfile_backentrypoint.shdocker-compose.ymlと同じディレクトリ階層に配置しています。

Dockerfile_back
FROM ruby:2.6.3

RUN apt-get update && \
    apt-get install -y mariadb-client nodejs vim

RUN mkdir /myapp

WORKDIR /myapp

ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock

RUN gem install bundler
RUN bundle install

ADD . /myapp

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3001

CMD ["rails", "server", "-b", "0.0.0.0"]
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

Dockerfile(frontend用)

todo_frontディレクトリ上に配置します。

Dockerfile_front
FROM node:14

Rails側の調整

database.yml'を調整してあげます。
socket通信になっているので、
host: db`に変えてあげます。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
- socket: /tmp/mysql.sock
+ host: db

axiosmaterial-uiのインストール

コンテナをバックグラウンドで立ち上げます。
そして、axiosやmaterial-uiをインストールします。
ちなみにexecはすでにコンテナが立ち上がっている状態で使えます。
コンテナが立ち上がっていない時はrunを使います。
終わったら、dbを作って、マイグレートして、植えて終わりになります。

console
$ docker-compose up -d #コンテナを立ち上げる
$ docker-compose exec front npm install axios
$ docker-compose exec front npm install @material-ui/core
$ docker-compose exec app bin/rails db:create
$ docker-compose exec app bin/rails db:migrate
$ docker-compose exec app bin/rails db:seed

localhost:3000にアクセスすると、前回と同じ画面が立ち上がるはずです。
終わり

終わりに

今回は、docker for Macの調子が悪く、何度もattachが発生しました。
再起動すると次のプロセスに行けました。
なんだろう、もう少しdockerの勉強した方がいいかなと思いました。

Zennに投稿した記事

https://zenn.dev/nicoryo/articles/2020121402tech

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

Goで手早くJSON APIを構築してみた

※この記事は自身のブログからの転載です。
Goで手早くJSON APIを構築してみた | outputable

Goで手早くJSON APIを構築してみたのでメモっておく。

環境構築

手短にいきたいのでDockerで。

軽量なalpineを選択。

コンテナ側の使用PORTも忘れず開放しておく。

ビルダーイメージ側のプラットフォームも実行側イメージに合わせておかないとGoサーバが起動しないので注意。

  • Dockerfile
FROM golang:1.15.3-alpine3.12 as builder

ENV GO111MODULE=on

ENV GOPATH=

WORKDIR /usr/src/app

COPY . ./

RUN go mod init gochi1 && go get && go build .

FROM alpine:3.12 as gochi1

COPY --from=builder /usr/src/app .

EXPOSE 8080

CMD ["./gochi1"]
  • .dockerignore
    イメージ化の際避けたいファイルなどを指定しておく。
.gitignore
go.mod
go.sum

 

ここからgo moduleに準じたプロジェクトを作る。

$ mkdir gochi1
$ cd gochi1
$ touch main.go

プログラムの構築

サーバーを作る。

ライブラリで「chi」というのがあったので今回はそれを利用してみる。

  • main.go
package main

import (
    "gochi1/resources"
    "net/http"
    "github.com/go-chi/chi"
    "github.com/go-chi/chi/middleware"
    "os"
    "fmt"
)

func main() {
    r := chi.NewRouter()

    r.Use(middleware.RequestID)
    r.Use(middleware.RealIP)
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.BasicAuth("secret-room", map[string]string{"user1": "value1"}))

    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("ベタ打ち"))
    })

    r.Mount("/users", resources.UsersResource{}.Routes())

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
  }

    http.ListenAndServe(fmt.Sprintf(":%s", port), r)
}
  • resources/todos.go

ロジックの分離。

DBの代わりにグローバル変数で状態変化を再現している。

package resources

import (
    "net/http"
    "github.com/go-chi/chi"
    "encoding/json"
    "io/ioutil"
    "reflect"
)

type Response2 struct {
  Name   string      `json:"name"`
    Todos []string `json:"todos"`
}

var state Response2

type UsersResource struct {}

func (ur UsersResource) Routes() chi.Router {
    r := chi.NewRouter()
    r.Get("/", ur.List)
    r.Post("/", ur.Create)
    r.Put("/", ur.Delete)
    r.Route("/{id}", func(r chi.Router) {
        r.Get("/", ur.Get)
        r.Put("/", ur.Update)
        r.Delete("/", ur.Delete)
    })
    return r
}

func (ur UsersResource) List(w http.ResponseWriter, r *http.Request) {
    res := &Response2{
        Name:   "John",
        Todos: []string{"compile", "clean", "console"}}

    w.Header().Set("Content-Type", "application/json; charset=UTF-8") // <- Added
    w.WriteHeader(http.StatusOK)

    if err := json.NewEncoder(w).Encode(res); err != nil {
        panic(err)
    }
    return
}

func (ur UsersResource) Create(w http.ResponseWriter, r *http.Request) {
    defer r.Body.Close()

    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        panic(err)
    }

    var response2 Response2

    error := json.Unmarshal(body, &response2)
    if error != nil {
        panic(err)
    }

    for i, v := range response2.Todos {
        todo := v

        if !reflect.DeepEqual(state, Response2{}) {
            todo = state.Todos[i]
        }

        if len(todo) > 9 && len(todo) <= 15 {
          response2.Todos[i] = todo + "(long)"
        } else if len(todo) > 15 {
          response2.Todos[i] = v
        } else {
          response2.Todos[i] = todo + "(short)"
        }
    }

    res := &Response2{
        Name:  response2.Name + "(updated)",
        Todos: response2.Todos}

    state = *res

    w.Header().Set("Content-Type", "application/json; charset=UTF-8") // <- Added
    w.WriteHeader(http.StatusOK)

    if err := json.NewEncoder(w).Encode(res); err != nil {
        panic(err)
    }
    return
}

func (ur UsersResource) Get(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("ベタ打ち"))
}

func (ur UsersResource) Update(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("ベタ打ち"))
}

func (ur UsersResource) Delete(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("ベタ打ち"))
}

サーバ起動、アクセスしてみる

サーバ起動

$ go run main.go

以下のコマンド or ブラウザアクセスでレスポンスをみてみる。

$ curl -X GET localhost:8080/users -u user1:value1

BASIC認証がかけられており、ユーザー名、パスワードが求められる。

認証が失敗すれば、レスポンスヘッダ

WWW-Authenticate: `Basic realm=secret-room`

のように返ってくる。

$ curl -X POST localhost:8080/users -d '{"name": "test", "todos": ["compile","test","package"]}' -u user1:value1

また、POSTでデータを送ってみると、送るたびにレスポンスが変化しているのを確認した。

dockerでやってみる

カレントディレクトリの構成は、前述のファイル群を含んだ以下の構成で行う。

$ tree -a
.
├── .dockerignore
├── go.mod
├── Dockerfile
├── go.sum
├── main.go
└── resources
    └── users.go
$ docker build -t tester1/gochi1:1.0 .
$ docker run -it --rm -d -p 8080:8080 --name tester1-gochi1 tester1/gochi1:1.0

前項で起動していたGoサーバを一旦停止し、dockerコンテナ作成・起動後、前述のURLにアクセスすると非コンテナ時と同様に動作した。

後片付けは

$ docker ps -a //で該当のコンテナIDを探して
$ docker stop [コンテナID]
$ docker images //で該当イメージIDを探して
$ docker rmi [イメージID] 

で完了した。

最後に

とりあえずGoのライブラリを使って即席でJSON APIを立ててみたが、比較的簡単に実装できてお手軽感があっていいな、と思った。

動きも早いのでこれからもっと触っていくための走り書きをここにして終わりにしたいと思う。

おしまい。

参考:
go-chi公式
JSONの扱い方

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