20200919のdockerに関する記事は15件です。

Docker 初心者が Dockerfile からDocker イメージを作成するまで

はじめに

インターネットにつながっていない環境で numpy や pandas といった Python ライブラリを使って処理をする必要がでてきたので、Docker を使って必要なライブラリやファイルを Docker イメージとして固めて、別のサーバで docker load するまでの手順をまとめます。
この記事のゴール.png
内容に誤りや、修正すべき箇所があればご教示いただけますと幸いです。

環境

  • Mac OS Catalina 10.15.6
  • Homebrew 2.5.1

Docker の準備

インストールと起動

こちらの記事を参考に Homebrew から Docker をインストールし、Docker.app を起動します。
DockerをHomebrewでMac OSに導入する方法 - Qiita

Docker を起動しました。チュートリアルを無視したのでまっさらな状態です。起動後の画面.png
Docker を起動することで、ターミナル上で docker コマンドが打てるようになります。なお、Docker.app(GUI画面)はこのあと使いませんので閉じてしまって大丈夫です。

kaz@Kaz-MBP ~ % docker --version
Docker version 19.03.12, build 48a66213fe

イメージとコンテナ

この後「イメージ」と「コンテナ」の違いを理解している前提で話を進めます。「イメージ」と「コンテナ」については以下のサイトの解説が個人的に分かりやすくて好きです。
dockerのコンテナとイメージの違い|ハックノート

Docker Hub から大元となるイメージを取得

これから Docker イメージの作成に向けて頑張っていくのですが、どうやらゼロから Docker イメージを作るのではなく、別の Docker イメージをベースにオリジナルの Docker イメージを作成するのが主流だそうなので、私もそれに倣います。
今回は Python とそれに関連するライブラリが必要なので、Miniconda3 が乗っかった Docker イメージをベースにしたいと思います。
continuumio/miniconda3 - Docker Hub

では早速 pull します。

kaz@Kaz-MBP ~ % docker pull continuumio/miniconda3
Using default tag: latest
latest: Pulling from continuumio/miniconda3
68ced04f60ab: Pull complete
9c388eb6d33c: Pull complete
96cf53b3a9dd: Pull complete
Digest: sha256:456e3196bf3ffb13fee7c9216db4b18b5e6f4d37090b31df3e0309926e98cfe2
Status: Downloaded newer image for continuumio/miniconda3:latest
docker.io/continuumio/miniconda3:latest

pull が完了すると、以下の通り docker imagesコマンドでその結果が確認できます。continuumio/miniconda3 という名前のリポジトリが登録されていれば OK です。docker image ls でも同じ結果が表示されます。

kaz@Kaz-MBP ~ % docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
continuumio/miniconda3   latest              b4adc22212f1        6 months ago        429MB

試しに Docker イメージからコンテナを実行してみる

ではこの miniconda3 の Docker イメージから、docker run --rm -it continuumio/miniconda3 コマンドでコンテナを実行してみます。

kaz@Kaz-MBP ~ % docker run --rm -it continuumio/miniconda3
(base) root@8993959bca74:/#
  • --rm はコンテナ終了後に自動的にコンテナを削除するオプションです(Docker イメージは削除されません)
  • -it はインタラクティブシェルでコンテナを操作するためのオプションです
  • continuumio/miniconda3 は、スラッシュも含めて Docker イメージの名前です(記号ではない)

実行すると、ターミナル左側のプロンプト表示が変わります。この状態になれば、想定どおり Docker イメージからコンテナが作成できインタラクティブシェルで操作ができます。

(base) root@8993959bca74:/# ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr
(base) root@8993959bca74:/#
(base) root@8993959bca74:/# conda info -e
# conda environments:
#
base                  *  /opt/conda

(base) root@8993959bca74:/#
(base) root@8993959bca74:/# python --version
Python 3.7.6

exit を入力するとコンテナが終了します。run コマンド実行時に --rm オプションをつけたのでこの時実行されたコンテナは消え去ります。

(base) root@8993959bca74:/# exit
exit
kaz@Kaz-MBP ~ %
kaz@Kaz-MBP ~ % python --version
Python 2.7.16

この Docker イメージが使えそうであることが確認できたので、これをベースにオリジナルの Docker イメージを作ってみます。

Dockerfile の作成

オリジナルの Docker イメージを作るのに必要な設計図のようなものが Dockerfile になります。Dockerfile ではベースとなる Docker イメージと、実行したいコマンドなどを定義する必要があります。
今回は numpy, pandas, jupyter notebook をインストールしつつ、/usr/workspace ディレクトリにローカルで作成した Python スクリプトを含む Docker イメージを作るための Dockerfile を作成します。

FROM continuumio/miniconda3
RUN conda install numpy pandas jupyter -y
RUN mkdir /usr/workspace/
COPY helloworld.py /usr/workspace/
  • FROM では元となる Docker イメージを指定します
    • pull していない Docker イメージも指定することができます
  • RUN では実行したいシェルスクリプトを指定します
    • RUN の実行結果はその次の RUN に引き継がれないことに気を付けてください。例えば、cd /usr/workspace/ でカレントディレクトリを移動したとしても、次の RUN ではカレントディレクトリはホームディレクトリである / になります
  • COPY では Dockerfile と同一ディレクトリにある Python スクリプト(helloworld.py)を RUN で作成した /usr/workspace/ ディレクトリ に配置します

ではこの Dockerfile から、Docker イメージを作成します。Dockerfile が保存されているディレクトリまで cd した後、docker build -t test . コマンドを実行します。
- -t は今回作成する Docker イメージに名前を指定します。今回は 'test' という名前にしました
- 最後の . は Dockerfile が保存されているディレクトリを指定します。今回はカレントディレクトリを指定するために . としています。

kaz@Kaz-MBP qiita % docker build -t test .
Sending build context to Docker daemon  3.072kB
Step 1/4 : FROM continuumio/miniconda3
 ---> b4adc22212f1
Step 2/4 : RUN conda install numpy pandas jupyter -y
 ---> Running in fce2bbf772f7
Collecting package metadata (current_repodata.json): ...working... done
Solving environment: ...working... failed with initial frozen solve. Retrying with flexible solve.
Solving environment: ...working... failed with repodata from current_repodata.json, will retry with next repodata source.
Collecting package metadata (repodata.json): ...working... done
Solving environment: ...working... done

## Package Plan ##

  environment location: /opt/conda

  added / updated specs:
    - jupyter
    - numpy
    - pandas


The following packages will be downloaded:

    package                    |            build
    ---------------------------|-----------------

(中略)

Preparing transaction: ...working... done
Verifying transaction: ...working... done
Executing transaction: ...working... done
Removing intermediate container fce2bbf772f7
 ---> 1b4aaad5eec1
Step 3/4 : RUN mkdir /usr/workspace/
 ---> Running in 3c29b1840f3b
Removing intermediate container 3c29b1840f3b
 ---> 6b9931871d40
Step 4/4 : COPY helloworld.py /usr/workspace/
 ---> bf07bc8332af
Successfully built bf07bc8332af
Successfully tagged test:latest
kaz@Kaz-MBP qiita %

Dockerfile には合わせて 4 行記載していたので、Step n/4 として進捗が表示されます。
Docker イメージの作成が完了すると、先と同様に docker images コマンドで結果を確認します。

kaz@Kaz-MBP qiita % docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
test                     latest              bf07bc8332af        40 seconds ago      2.46GB
continuumio/miniconda3   latest              b4adc22212f1        6 months ago        429MB
kaz@Kaz-MBP qiita %

test という名前のイメージが追加されています!ではこちらも同様にコンテナとして実行してみましょう。

kaz@Kaz-MBP qiita % docker run --rm -it test
(base) root@ba61e23ba183:/#
(base) root@ba61e23ba183:/# ipython
Python 3.7.6 (default, Jan  8 2020, 19:59:22)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.18.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import numpy, pandas

In [2]: exit
(base) root@ba61e23ba183:/#
(base) root@ba61e23ba183:/# ls /usr/workspace/
helloworld.py
(base) root@ba61e23ba183:/#
(base) root@ba61e23ba183:/# python /usr/workspace/helloworld.py
Hello, world!
(base) root@ba61e23ba183:/#
(base) root@ba61e23ba183:/#
(base) root@ba61e23ba183:/# exit
exit
kaz@Kaz-MBP qiita %

IPython が実行でき、numpy も pandas もインポートできました。/usr/workspace/helloworld.py も正しく Docker イメージに含まれています。

Docker イメージをファイルとして保存

docker save コマンドで保存できます。ちなみに save は Docker イメージの保存、export は Docker コンテナの保存にそれぞれ用いるらしいです。
Docker save→load export→import どっち? - Qiita

kaz@Kaz-MBP qiita % docker save test > saved_img.tar
kaz@Kaz-MBP qiita %
kaz@Kaz-MBP qiita % ls
Dockerfile  helloworld.py   saved_img.tar
kaz@Kaz-MBP qiita %

docker save により tar ファイルとして書き出されます。この tar ファイルは docker load -i saved_img.tar で読み込めます。

Docker イメージの読み込み

最後に、既存の "test" Docker イメージを削除した状態から、save した saved_img.tar を load して正しくコンテナが動作するかを確認します。

既存 Docker イメージの削除

kaz@Kaz-MBP qiita % docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
test                     latest              bf07bc8332af        24 minutes ago      2.46GB
continuumio/miniconda3   latest              b4adc22212f1        6 months ago        429MB
kaz@Kaz-MBP qiita %
kaz@Kaz-MBP qiita % docker rmi test
Untagged: test:latest
Deleted: sha256:bf07bc8332af060f8711f1610d331c1b5e28555942f1d78b57d8522c04206645
Deleted: sha256:375aa2957526e2da07a6057f47463dfc1568f4eb486df466ecf152c88e1069c4
Deleted: sha256:6b9931871d407a8f3506e64a8e2487a1ef418190308c34cc1d16ea4b97ea310b
Deleted: sha256:33afa4a779be5b75042a62c62563d16940f9d8c71a754317f33d74220f275f4c
Deleted: sha256:1b4aaad5eec1c08bf8b7dd8a3023fdd856c33b835467281d058694e3874d4d9c
Deleted: sha256:77a6450e1f92f5757655aafa4b74ea2c413b7a93b7fbcf1c15741bd3f9c0669e
kaz@Kaz-MBP qiita %
kaz@Kaz-MBP qiita % docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
continuumio/miniconda3   latest              b4adc22212f1        6 months ago        429MB
kaz@Kaz-MBP qiita %

.tar ファイルのロード

kaz@Kaz-MBP qiita % docker load -i saved_img.tar
7ee81e12667e: Loading layer   2.07GB/2.07GB
8f3d70528b79: Loading layer   2.56kB/2.56kB
771c1ba5862d: Loading layer  3.072kB/3.072kB
Loaded image: test:latest
kaz@Kaz-MBP qiita %
kaz@Kaz-MBP qiita %
kaz@Kaz-MBP qiita % docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
test                     latest              bf07bc8332af        28 minutes ago      2.46GB
continuumio/miniconda3   latest              b4adc22212f1        6 months ago        429MB
kaz@Kaz-MBP qiita %

ロードした Docker イメージからコンテナを実行

kaz@Kaz-MBP qiita % docker run --rm -it test
(base) root@d4a072d6c4bd:/#
(base) root@d4a072d6c4bd:/# ipython
Python 3.7.6 (default, Jan  8 2020, 19:59:22)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.18.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import numpy, pandas

In [2]: exit
(base) root@d4a072d6c4bd:/#
(base) root@d4a072d6c4bd:/# python /usr/workspace/helloworld.py
Hello, world!
(base) root@d4a072d6c4bd:/#
(base) root@d4a072d6c4bd:/# exit
exit
kaz@Kaz-MBP qiita %

.tar ファイルのロード後も同じようにライブラリ・スクリプトが利用できました!これでやりたいことは達成です。

まとめ

  • Docker で動くコンテナを用意するためには、Docker イメージを作成する必要があります
    • 今回は Dockerfile を作成・ビルドして Docker イメージを作成しました
  • Dockerfile には元となる Docker イメージの名前と実行したい処理を定義します
  • Docker イメージを run することでコンテナが実行されます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IBM Cloud CLIを使ってCode Engineでアプリケーションを起動する

IBM Cloud Code Engine ベータ版がリリースされたとのことで、IBM Cloud CLIを使ってアプリケーションをサーバーレス(サーバーを意識させない)で動かします。Code Engineは、Kubernetes、Knative、Istio、Tekton などの上に構築されています。
Code Engineに作成するアプリケーションは、Node-REDです。DockerHubで公開されているNode-REDのイメージを使用します。

2020年9月現在、IBM Cloud Code Engineはベータ版のため無料で使えています。

作業環境

途中で失敗しても良いように、VirtualBox上に仮想マシンとしてUbuntu Desktop 20.04を導入し下記の作業に使用しています。Ubuntuを使った理由は、Linuxの中で最も使っている人が多いだけで他意はありません。仮想マシンを自分のパソコン上に用意しておくことで何度もやり直しができます。今回はやり直しはほぼありませんが。

IBM Cloud CLI

IBM Cloud Code Engineを使用するにあたり、ここではWebブラウザ上ではなくすべての処理を文字で行うコマンドラインインタフェース(CLI)を使用するため、「IBM Cloud CLI」をインストールします。Webブラウザ上のユーザインタフェースは時々画面のデザインが変わることがありますが、コマンドラインインタフェース(CLI)では、基本的に変わることがない特徴があります。画面変わったら操作が出来ない人は、CLIが向いています。

IBM Cloud CLIのインストール

次のコマンドを実行します。

$ curl -sL https://ibm.biz/idt-installer | bash

画面の指示に従って、y や n (yesとno)を入力し処理を進めます。このインストーラーコマンドを実行することで、IBM Cloud Code Engineで使用するkubectlやDockerなどをまとめてインストールしてくれます。(IBM Cloud CLIのオンラインマニュアル)

IBM Cloudにログイン

次のコマンドを実行し、IBM Cloud CLIを用いて、IBM Cloudにログインします。

$ ibmcloud login

IBM Cloudでログイン時に使っているメールアドレスを入力します。

$ Email> ここにメールアドレスを入力

IBM Cloudでログイン時に使っているパスワードを入力します。

Password> 

パスワードは入力しても見えないようになっています。
リージョンを選択しますと表示されます。リージョンとはいわばデータセンターのことで、普段使っているリージョンを選びます。jp-tokやus-southなどを選ぶことができます。筆者は、us-south を選びました。

リージョンを選択します (または Enter キーを押してスキップします):
1. au-syd
2. ......

使用統計をIBMに送信しますか?と表示されますので、n を入力してEnterキーを押します。

使用統計を IBM に送信しますか? [y/n]> n

リソース・グループを指定

リソース・グループ自体の解説については公式のオンラインマニュアルへ。
リソース・グループを指定していない場合は、IBM Cloud Code Engineで、アプリケーションやジョブをまとめる器となる「プロジェクト」を作成することができません。

次のコマンドを実行し、既存のリソース・グループを表示します。

$ ibmcloud resource groups

結果の例

名前      ID                  デフォルト・グループ   状態
default   xxxxxxxxxxxxxxxxx   true                ACTIVE

ここでは名前が「default」というリソース・グループをターゲットに指定します。

$ ibmcloud target -g default

IBM Cloud Code Engine

Webブラウザ上で操作したい場合は、末尾の参考情報をご覧ください。ここでは、IBM Cloud CLIを用いてアプリケーションの作成を実施します。

Code Engine CLI プラグインのインストール

IBM Cloud CLIで、IBM Cloud Code Engineを使用するために、Code Engine CLI プラグインをインストールします。

$ ibmcloud plugin install code-engine

実行後は画面の指示に従って操作します。
プラグインが無事にインストールできたかどうか、次のコマンドを実行して確認します。

$ ibmcloud plugin show code-engine

結果の例

プラグイン名                              code-engine/ce
プラグイン・バージョン                    0.4.2217
プラグイン SDK バージョン                 0.3.0
必要な最小限の IBM Cloud CLI バージョン   1.0.0

コマンド:
 code-engine,ce                    Manage Code Engine components.
 以下省略....

プロジェクト作成

IBM Cloud Code Engineでは、アプリケーションやジョブなどを動かすための器を「プロジェクト」と言い、必ず用意しなければいけません。
次のコマンドを実行します。作成するプロジェクト名は、1stproject としました。プロジェクト名は英数字が使えますので、好みの名称で良いでしょう。

$ ibmcloud ce project create --name 1stproject

作成済みの既存のプロジェクトは、次のコマンドを実行することで確認できます。

$ ibmcloud ce project list

プロジェクトの指定

アプリケーションを作成するにあたり、プロジェクトを指定するため、次のコマンドを実行します。ここでは、1stprojectを指定しています。実際は、1stprojectのところがプロジェクト名を指定する箇所になりますので、各自が作成したプロジェクト名に置き換えてコマンドを実行してください。

$ ibmcloud ce project select -n 1stproject

結果の例

Selecting project '1stproject'...
OK

アプリケーションの作成

DockerHubで公開されているNode-REDのDockerイメージを呼び出して、IBM Cloud Code Engineにアプリケーションとして作成します。
次のコマンドを実行します。

$ ibmcloud ce application create --name nodered --image nodered/node-red --cpu 1 --memory 512Mi --min-scale 1

結果の例

roject '1stproject' and all its contents will be automatically deleted 7 days from now.
Creating application 'nodered'...
Configuration 'nodered' is waiting for a Revision to become ready.
Ingress has not yet been reconciled.
Waiting for load balancer to be ready
Run 'ibmcloud ce application get -n nodered' to check the application status.
OK

https://nodered.xxxxxxxx-xxxx.us-south.codeengine.appdomain.cloud

上記のURLにアクセスすることで、作成したアプリケーションにアクセスすることができます。noderedは、ibmcloud ce application create で指定したアプリケーション名で、xxxxxxxx-xxxxはアプリケーションが所属するプロジェクトのIDの一部になりますので、各自で異なります。
なお、2020年9月現在、IBM Cloud Code Engineはベータ版のため、7日間で自動削除する旨が表示されています。

実行した、ibmcloud ce application create コマンドの内容

様々なオプションをセットしていますので、ここで使用したコマンドオプションがどのような意味を持つか説明します。

コマンドオプション 意味 入力例 必須 備考
--name アプリケーション名の指定 nodered 
--image コンテナ(Docker)イメージの指定 nodered/node-red  今回はDockerHubで公開されているイメージを使用。
--cpu 割り当てるCPUの数を指定 1  デフォルトの値は、0.1。
--memory 割り当てるメモリの量を指定 512Mi  デフォルトの値は、1024Mi。つまり1GB。
--min-scale 最小のインスタンス数を指定 1  デフォルトの値は、0。デフォルト値の場合、使っていないとインスタンスが自動削除されてしまい、アプリケーション作成時に戻ってしまうので注意。

コマンドオプションについては、必ず公式のオンラインマニュアルを確認してください。

アプリケーションのステータス確認

どのように構成されたかどうか、アプリケーションの状況を確認するには、次のコマンドを実行します。

$ ibmcloud ce application get -n nodered

上記のnoderedは、ibmcloud ce application create で指定したアプリケーション名になります。アプリケーション名が異なる場合は、各自が使用したアプリケーション名に置き換えてコマンドを実行してください。

結果の例

Getting application 'nodered'...
OK

Name:          nodered
ID:            xxxxxxxx-xxxx-xxxxxxxx-xxxx
Project Name:  1stproject
Project ID:    xxxxxxxx-xxxx-xxxxxxxx-xxxx
Age:           78s
Created:       2020-09-19 15:19:36 +0900 JST
URL:           https://nodered.xxxxxxxx-xxxx.us-south.codeengine.appdomain.cloud
Console URL:   https://cloud.ibm.com/codeengine/project/us-south/xxxxxxxx-xxxx-xxxxxxxx-xxxx/application/nodered/configuration

Image:  nodered/node-red
Resource Allocation:
  CPU:     1
  Memory:  512Mi
  以下省略

実行結果として表示されたもののうち、「URL」にアクセスするとアプリケーションが表示され、「Console URL」にアクセスすると、Webブラウザ上で作成したアプリケーションの設定を確認することができます。

「Console URL」にアクセスした例
image.png

アプリケーションの削除

作成したアプリケーションを削除するには、次のコマンドを実行します。noderedは、ibmcloud ce application create で指定したアプリケーション名になります。アプリケーション名が異なる場合は、各自が使用したアプリケーション名に置き換えてコマンドを実行してください。

$ ibmcloud ce application delete --name nodered

結果の例

Project '1stproject' and all its contents will be automatically deleted 7 days from now.
Are you sure you want to delete application nodered? [y/N]> y
Deleting application 'nodered'...
OK

アプリケーションを削除して良いか確認を求めてきますので、y を入力しEnterキーで実行します。
なお、2020年9月現在、IBM Cloud Code Engineはベータ版のため、7日間で自動削除する旨が表示されています。

Webブラウザでアクセス

アプリケーションの作成、またはアプリケーションのステータス確認で表示されたURLにアクセスし、アプリケーションが動いているか確認します。ここではNode-REDを指定していましたので、Node-REDが表示されます。
image.png

アプリケーションのログを見る

起動したアプリケーションのログを見るには、ibmcloud ce application logs コマンドを使用します。そのために、アプリケーションのインインスタンス名を取得し、取得したインスタンス名に対して、ibmcloud ce application logs コマンドを実行します。

アプリケーションのインスタンス名の取得

次のコマンドを実行します。アプリケーションのステータス確認で使用したものと同じです。

$ ibmcloud ce application get -n nodered

上記のnoderedは、ibmcloud ce application create で指定したアプリケーション名になります。アプリケーション名が異なる場合は、各自が使用したアプリケーション名に置き換えてコマンドを実行してください。

結果の例

Instances:
  Name                                         Running  Status   Restarts  Age
  nodered-xxxx-1-deployment-xxxxxxxx-xxxxxxx  2/2      Running  0         14h

実行結果の一番したに表示される、Instances:のNameに表示されているアプリケーションのインスタンス名が重要です。

アプリケーションのログを取得

次のコマンドを実行します。取得したアプリケーションのインスタンス名を使用します。

$ ibmcloud ce application logs --instance nodered-xxxx-1-deployment-xxxxxxxx-xxxxxxx

上記のnodered-xxxx-1-deployment-xxxxxxxx-xxxxxxxは、取得したアプリケーションのインスタンス名になります。各自で置き換えてください。

結果の例

Project '1stproject' and all its contents will be automatically deleted 7 days from now.
Logging application instance 'nodered-xxxx-1-deployment-xxxxxxxx-xxxxxxx'...
OK


> node-red-docker@1.1.3 start /usr/src/node-red
> node $NODE_OPTIONS node_modules/node-red/red.js $FLOWS "--userDir" "/data"

19 Sep 11:53:35 - [info]

Welcome to Node-RED
===================

19 Sep 11:53:35 - [info] Node-RED version: v1.1.3
19 Sep 11:53:35 - [info] Node.js  version: v10.22.0
19 Sep 11:53:35 - [info] Linux 4.15.0-112-generic x64 LE
19 Sep 11:53:35 - [info] Loading palette nodes
19 Sep 11:53:36 - [info] Settings file  : /data/settings.js
19 Sep 11:53:36 - [info] Context store  : 'default' [module=memory]
以下省略

なお、なお、2020年9月現在、IBM Cloud Code Engineはベータ版のため、7日間で自動削除する旨が表示されています。
IBM Cloud CLIを使うことで、ログを取得することができます。Webブラウザ上では2020年9月現在はログの取得ができません。

参考記事

Webブラウザ上で操作する場合は、サーバレスプラットフォームのCode Engine(IBM Cloud)を試してみた がオススメです

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

実務経験約6ヶ月のペーペーが実務で使ってない技術だらけでWebアプリを0から開発してみた話

0.はじめに

実務経験だいたい6ヶ月の僕がスキルアップのために現時点では実務で使っていない技術を使ってWebアプリを0から開発してみた話。

未経験からエンジニア転職を目指す方々のWebアプリ(いわゆるポートフォリオ)作成にも結構参考になることはあると思いますのでぜひ。

作成したのは面接情報共有アプリです。

1.使用技術

  • フロント:HTML/MDBootstrap4/Vue.js(2.6.11)
  • バックエンド:Laravel(6.8)
  • DB:開発環境はDockerのMySQLコンテナ、本番環境はRDS(MySQL)
  • 開発環境:Docker(LEMP環境)
  • インフラ:AWS(EC2/S3/Route53/ELB/RDS)
  • CI/CD:CircleCI
  • ソース管理:Git/GitHub

赤太字が2020/9/19時点で実務で使ってない技術

2.簡単に自己紹介

  • 関西のWeb系企業でPHPを使ったWebアプリ開発を行う新米パパエンジニア
  • 2019年10月からプログラミング学習を開始→2020年4月からエンジニアのキャリアスタート
  • Twitterで情報発信
  • 好きな食べ物はホットケーキ

Twitterはこちら↓
https://twitter.com/shimotaroo

3.開発背景

面接情報共有アプリを開発した理由は大きく分けて2つ。

  • Laravel、Docker、AWS、CircleCIの勉強のため(アウトプットしながらが効率的)
  • エンジニアへの転職活動中の方の面接の内容(特に質問)がTwitterで伸びやすいく、需要のある情報だと思ったため
  • 1つのサービスの開発、運用を経験してみたかったら(特に運用)
    ↑開発前はリリース→機能追加→リリースをしてサービスをグロースできたらいいなと思っていました、他にやりたいこと・やるべきことがたくさんあり、今はそこまで考えてない

4.(ひとまず)完成したWebアプリをご紹介

トップページ↓
スクリーンショット 2020-09-13 23.59.34.png

URL↓
https://mensetsu-app.work/

GitHub↓
https://github.com/shimotaroo/laravel-app

README.mdはこちらの記事を見ながら書きました。
【GitHub】シンプルなREADME.mdの書き方 -コピペで使えるテンプレート付き-

トップページに画像だったりとアプリの説明をいれるケースもありますが、Twitter、note、Instagramをイメージしてこんな感じのトップページのUIアプリの説明とか画像が入ってない)にしました。

自分がテスト投稿したデータが本番環境に残ってますが悪しからずww

5.完成までにかかった日数

スクリーンショット 2020-09-14 0.06.34.png

毎日このアプリ開発に時間を費やしていたわけではないので明確にわかりませんが草は上の画像の感じです。
(育児と本業、その他諸々の傍らコツコツやってました)

まあフルコミットすれば1ヶ月くらいでできたのかな、って感じです。(多分)

6.実装した機能

  • CRUD機能
  • ユーザー登録
  • ログイン/ログアウト
  • Googleアカウントでユーザー登録/ログイン
  • Twitterアカウントでユーザー登録/ログイン
  • プロフィール編集
  • パスワード変更(現在のパスワードをチェックする仕様)
  • プロフィール画像のデフォルト設定/変更
  • いいね(お気に入り)
  • ページネーション
  • 並び替え+ページネーション保持
  • 絞り込み検索+ページネーション保持

特別難しい機能はないかと思います。レベルの高い初学者の方ならポートフォリオとして盛り込んでいたりするかもです。
(と言っても僕はいくつかは手こずりました)

7.その他にやったこと

  • ER図書いてDB設計
  • 開発環境DockerでLEMP環境構築(もちろんLaradockじゃないです)
  • PHPPUnitで単体テスト
  • AWSにデプロイ
  • Circle CIで自動テスト/自動デプロイ

1つ前に書いた機能実装よりこっちの方が未知の分野だったので大変でした。そして頑張った。
(上手くいかず何度PCをぶん投げようと思ったか...)

8.DB設計

DB設計というかER図作成はDraw.ioというツールを使いました。
VSCode用のプラグインがありエディター上ででER図を作成できるスグレもの。

作成したER図はこちら↓
スクリーンショット 2020-09-14 0.49.36.png

作成したテーブルはこちら↓

テーブル名 説明
users 登録ユーザーの管理
articles 投稿された面接情報の管理
prefectures articlesに紐づける都道府県
company_types articlesに紐づける企業の事業形態
phases articlesに紐づける選考フェーズ
likes いいねの管理

ポイントとしてはarticlesテーブルにprefecturesやcompany_types、phaseの情報をベタでINSERTせずにそれぞれのテーブルで値を保持しておき、articlesテーブルではそのidを参照するように外部キー制約を設定しました。(正規化っていうらしい)

詳しいことは上のER図をみていただければわかるかなと思います。

僕はこれまでER図を書いたことがなかったので1から勉強しました。
初めはリレーションあたりが「?」でしたが、めっちゃググって理解できました。ググった中でも特にわかりやすかった記事を下にピックアップします↓

9.ソース管理

ソース管理は皆さんお馴染みのGit/GitHubです。

個人開発なのでGitHub Flowでソース管理しました。(masterとfeatureブランチ)

GitHub Flowがよくわからない人はこちらから↓
GitHub Flow 図解

※Gitに関して、僕の記事にもまとめていますので特に初学者の方はこちらも併せて読んでいただくとお役に立てると思います↓

Gitでやりたいこと、ここで見つかる

※僕のQiitaの記事は約800LGTMなので結構オススメです(笑)

10.環境構築

環境構築はDockerでLEMP環境を構築しました。

LEMP環境は以下の通りです。

  • L:Linux(OS)
  • E:Nginx(Webサーバー)
  • M:MySQL(DBサーバー)
  • P:PHP(アプリケーション)

Dockerを使ったLaravel+Vue.jsの実行環境の構築方法については以下にまとめております。今回作成したアプリもこの記事通りに環境構築しました。

絶対失敗しないのでDockerでLaravelの実行環境を構築をしたい方はぜひ併せて読んでいただければと思います。

11.インフラ

インフラはAWSを使いました。
僕は独学期間にAWSはノータッチ&実務でもまだAWSを使ったことがないので、この開発を通して初めて触りました。最初は意味不明ながらもUdemyの動画教材(後ほどご紹介します)を見ながら必死でインフラ構築しました。

構築したインフラはこちらです。

image.png

上図の通りですが簡単に。

  • Webサーバー:EC2
  • DB:RDS
  • DNS:Route53
  • 画像用ストレージ:S3
  • ロードバランサー:ALB

せっかくローカルの環境をDockerで構築したのでECSを使ってデプロイできればよかったのですが、いかんせんAWSを初めて触ったレベルだったので諦めました...(一応チャレンジはしました)
なお、"EC2内でDockerコンテナを立ち上げる"方法も検討しましたが、こちらも全然上手くいかず諦めました...
良い勉強になりましたが、今後は↑このあたりも勉強&挑戦していきたい。

あと、先ほど掲載したインフラの図を見て、「あれ...」って思われた方がいらっしゃると思うのでここで簡単に反省点をあげます(笑)

  • ロードバランサー使っているのにアクセスを振り分けるEC2が1つしかない
  • てかそもそもEC2もRDSも1つしかなくて全然冗長化できてない
  • 画像を扱うのにCloudFront使ってない

わかっています。わかってるんです...
お、お金の面を考慮してリーズナブルにしました...(お許しを...)

12.使った教材

僕がこのWebアプリを作成するために活用した教材をご紹介します。

Laravel、Vue.js:【Techpit】Laravel(+Vue.js)でSNS風Webサービスを作ろう!

PHPUnit、Circle CI、AWS:【 Techpit】Laravel × CircleCI × AWSで学ぶCI/CD

AWS:【Udemy】AWS:ゼロから実践するAmazon Web Services。手を動かしながらインフラの基礎を習得

Docker:【Udemy】米国AI開発者がゼロから教えるDocker講座

ここで紹介している教材は本当にわかりやすかったです。もちろん上記プラスで公式ドキュメントQiitaを見ながらしました。

当たり前と思いますがLaravelならこちらは必ず見た方が良いですね。
ReadDouble

13.大変だったこと

大変だったこと、苦労したことはやはり初めて触る技術・実務で使っていないですね。
具体的にはこちら↓

  • Laravel
  • PHPUnit
  • Circle CI
  • AWS

Laravelで大変だったこと

Laravelに関しては現職に入社する前に基礎は学習していたのでそこまで躓かなかったのですが、

  • 並び替え・絞り込み検索時のページネーション保持
  • プロフィール画像のデフォルト設定/変更

は結構手こずりました。

画像の取り扱いに関しては

  • 開発環境ではローカルに保存
  • 本番環境ではS3に保存

にするように変更したので、初めからS3に保存していてもよかったですね。

PHPUnitで大変だったこと

  • テストコードの書き方全般
  • DBを使ったユニットテスト(単体テスト)

結合テストだったり統合テストは今後勉強していきたいです。

Circle CIで大変だったこと

  • config.yml(設定ファイル)の書き方全般

だいぶ苦労しました。4日くらいひたすら自動テストの段階で格闘してましたw
めっちゃググって実際に書いて大まかな書き方は理解できたのでこれは今後別の記事にできたら良いなと。

※ちなみに、自動テストと自動デプロイが無事に通った瞬間は最高の気分で昇天するかと思いました。

AWSで大変だったこと

  • AWSの概念の理解
  • ALB(ELB)を使ってクライアントとの通信をHTTPS化

HTTPS化に関しては結局Laravel側にも設定が必要ということに気づいて解決したんですが、ここも結構時間を使いました。

14.実装したかったけどできなかったこと・まだしていないこと

思いつくだけでもこちらです↓

  • 要件定義
  • 基本設計
  • 詳細設計
  • ユーザー登録時のバリデーション強化
  • セキュリティ面の確認
  • コメント機能
  • DM機能
  • フォロー機能
  • Vue.jsの活用
  • ファビコン設定
  • Twitterへのシェア機能
  • Webサーバー、DBサーバーの冗長化
  • ECS(Elastic Container Service)でデプロイ
  • Cloud Frontでの画像のキャッシュ配信
  • Docker環境の最適化
  • 結合テスト/統合テスト

見てもらうとわかると思いますが、できてないこと・してないことはたっぷりです(笑)
少しずつできたらいいなと思います。

15.開発してみた感想

プライベートでWebアプリを開発したのは転職活動前ぶりでしたが、当時は

  • フレームワークなしでネイティブPHPのみ
  • ローカルはMAMP
  • Xserverに手動デプロイ
  • テストコード0

というなかなか笑えない感じだったので、今回色々な技術を使って開発した感想としては

  • アウトプットしながらのインプットがまじで最強
  • FW超便利
  • Docker超便利
  • AWSはむずい
  • CI/CDパイプラインは構築はむずいけどできたら超便利
  • 公式ドキュメントをちゃんと読むようになった
  • ググればある程度の問題は解決できた

とかですかね。

赤字で強調しましたが、アウトプットありきのインプットはまじで効率がオバケです。
どういうことかと言うと、勉強(インプット)してから「どんな機能を実装しようかな」(アウトプット)じゃなくて、まず実装する機能をまず決めて(アウトプット)それに必要な知識を勉強(インプット)するということです。

アウトプット駆動開発とでも言いいますか。

超オススメです。

(あとは実務未経験でポートフォリオにDocker、Circle CI、AWSを盛り込んでいる方がいますがマジですげえなと思いましたね)

16.さいごに

かなり長くなってしまいましたが、最後までご覧いただき本当にありがとうございますm(__)m

今後はもっと上流工程(要件定義、基本設計、詳細設計)にチャレンジしたいなと思っています。

初学者の方々も僕と同じくエンジニア1年生の方もアウトプット駆動開発でゴリゴリスキルアップしていきましょう(^^)

(あ...最後に...LGTMをポチッと押していただけると...昇天しま...あ、嬉しいです)

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

実務経験約6ヶ月の僕が実務で使ってない技術だらけでWebアプリを0から開発してみた話

0.はじめに

実務経験だいたい6ヶ月の僕がスキルアップのために現時点では実務で使っていない技術を使ってWebアプリを0から開発してみた話。

未経験からエンジニア転職を目指す方々のWebアプリ(いわゆるポートフォリオ)作成にも結構参考になることはあると思いますのでぜひ。

作成したのは面接情報共有アプリです。

1.使用技術

  • フロント:HTML/MDBootstrap4/Vue.js(2.6.11)
  • バックエンド:Laravel(6.8)
  • DB:開発環境はDockerのMySQLコンテナ、本番環境はRDS(MySQL)
  • 開発環境:Docker(LEMP環境)
  • インフラ:AWS(EC2/S3/Route53/ELB/RDS)
  • CI/CD:CircleCI
  • ソース管理:Git/GitHub

赤太字が2020/9/19時点で実務で使ってない技術

2.簡単に自己紹介

  • 関西のWeb系企業でPHPを使ったWebアプリ開発を行う新米パパエンジニア
  • 2019年10月からプログラミング学習を開始→2020年4月からエンジニアのキャリアスタート
  • Twitterで情報発信
  • 好きな食べ物はホットケーキ

Twitterはこちら↓
https://twitter.com/shimotaroo

3.開発背景

面接情報共有アプリを開発した理由は大きく分けて2つ。

  • Laravel、Docker、AWS、CircleCIの勉強のため(アウトプットしながらが効率的)
  • エンジニアへの転職活動中の方の面接の内容(特に質問)がTwitterで伸びやすいく、需要のある情報だと思ったため
  • 1つのサービスの開発、運用を経験してみたかったら(特に運用)
    ↑開発前はリリース→機能追加→リリースをしてサービスをグロースできたらいいなと思っていました、他にやりたいこと・やるべきことがたくさんあり、今はそこまで考えてない

4.(ひとまず)完成したWebアプリをご紹介

トップページ↓
スクリーンショット 2020-09-13 23.59.34.png

URL↓
https://mensetsu-app.work/

GitHub↓
https://github.com/shimotaroo/laravel-app

README.mdはこちらの記事を見ながら書きました。
【GitHub】シンプルなREADME.mdの書き方 -コピペで使えるテンプレート付き-

トップページに画像だったりとアプリの説明をいれるケースもありますが、Twitter、note、Instagramをイメージしてこんな感じのトップページのUIアプリの説明とか画像が入ってない)にしました。

自分がテスト投稿したデータが本番環境に残ってますが悪しからずww

5.完成までにかかった日数

スクリーンショット 2020-09-14 0.06.34.png

毎日このアプリ開発に時間を費やしていたわけではないので明確にわかりませんが草は上の画像の感じです。
(育児と本業、その他諸々の傍らコツコツやってました)

まあフルコミットすれば1ヶ月くらいでできたのかな、って感じです。(多分)

6.実装した機能

  • CRUD機能
  • ユーザー登録
  • ログイン/ログアウト
  • Googleアカウントでユーザー登録/ログイン
  • Twitterアカウントでユーザー登録/ログイン
  • プロフィール編集
  • パスワード変更(現在のパスワードをチェックする仕様)
  • プロフィール画像のデフォルト設定/変更
  • いいね(お気に入り)
  • ページネーション
  • 並び替え+ページネーション保持
  • 絞り込み検索+ページネーション保持

特別難しい機能はないかと思います。レベルの高い初学者の方ならポートフォリオとして盛り込んでいたりするかもです。
(と言っても僕はいくつかは手こずりました)

7.その他にやったこと

  • ER図書いてDB設計
  • 開発環境DockerでLEMP環境構築(もちろんLaradockじゃないです)
  • PHPUnitで単体テスト
  • AWSにデプロイ
  • Circle CIで自動テスト/自動デプロイ

1つ前に書いた機能実装よりこっちの方が未知の分野だったので大変でした。そして頑張った。
(上手くいかず何度PCをぶん投げようと思ったか...)

8.DB設計

DB設計というかER図作成はDraw.ioというツールを使いました。
VSCode用のプラグインがありエディター上ででER図を作成できるスグレもの。

作成したER図はこちら↓
スクリーンショット 2020-09-14 0.49.36.png

作成したテーブルはこちら↓

テーブル名 説明
users 登録ユーザーの管理
articles 投稿された面接情報の管理
prefectures articlesに紐づける都道府県
company_types articlesに紐づける企業の事業形態
phases articlesに紐づける選考フェーズ
likes いいねの管理

ポイントとしてはarticlesテーブルにprefecturesやcompany_types、phaseの情報をベタでINSERTせずにそれぞれのテーブルで値を保持しておき、articlesテーブルではそのidを参照するように外部キー制約を設定しました。(正規化っていうらしい)

詳しいことは上のER図をみていただければわかるかなと思います。

僕はこれまでER図を書いたことがなかったので1から勉強しました。
初めはリレーションあたりが「?」でしたが、めっちゃググって理解できました。ググった中でも特にわかりやすかった記事を下にピックアップします↓

9.ソース管理

ソース管理は皆さんお馴染みのGit/GitHubです。

個人開発なのでGitHub Flowでソース管理しました。(masterとfeatureブランチ)

GitHub Flowがよくわからない人はこちらから↓
GitHub Flow 図解

※Gitに関して、僕の記事にもまとめていますので特に初学者の方はこちらも併せて読んでいただくとお役に立てると思います↓

Gitでやりたいこと、ここで見つかる

※僕のQiitaの記事は約800LGTMなので結構オススメです(笑)

10.環境構築

環境構築はDockerでLEMP環境を構築しました。

LEMP環境は以下の通りです。

  • L:Linux(OS)
  • E:Nginx(Webサーバー)
  • M:MySQL(DBサーバー)
  • P:PHP(アプリケーション)

Dockerを使ったLaravel+Vue.jsの実行環境の構築方法については以下にまとめております。今回作成したアプリもこの記事通りに環境構築しました。

絶対失敗しないのでDockerでLaravelの実行環境を構築をしたい方はぜひ併せて読んでいただければと思います。

11.インフラ

インフラはAWSを使いました。
僕は独学期間にAWSはノータッチ&実務でもまだAWSを使ったことがないので、この開発を通して初めて触りました。最初は意味不明ながらもUdemyの動画教材(後ほどご紹介します)を見ながら必死でインフラ構築しました。

構築したインフラはこちらです。

image.png

上図の通りですが簡単に。

  • Webサーバー:EC2
  • DB:RDS
  • DNS:Route53
  • 画像用ストレージ:S3
  • ロードバランサー:ALB

せっかくローカルの環境をDockerで構築したのでECSを使ってデプロイできればよかったのですが、いかんせんAWSを初めて触ったレベルだったので諦めました...(一応チャレンジはしました)
なお、"EC2内でDockerコンテナを立ち上げる"方法も検討しましたが、こちらも全然上手くいかず諦めました...
良い勉強になりましたが、今後は↑このあたりも勉強&挑戦していきたい。

あと、先ほど掲載したインフラの図を見て、「あれ...」って思われた方がいらっしゃると思うのでここで簡単に反省点をあげます(笑)

  • ロードバランサー使っているのにアクセスを振り分けるEC2が1つしかない
  • てかそもそもEC2もRDSも1つしかなくて全然冗長化できてない
  • 画像を扱うのにCloudFront使ってない

わかっています。わかってるんです...
お、お金の面を考慮してリーズナブルにしました...(お許しを...)

12.使った教材

僕がこのWebアプリを作成するために活用した教材をご紹介します。

Laravel、Vue.js:【Techpit】Laravel(+Vue.js)でSNS風Webサービスを作ろう!

PHPUnit、Circle CI、AWS:【 Techpit】Laravel × CircleCI × AWSで学ぶCI/CD

AWS:【Udemy】AWS:ゼロから実践するAmazon Web Services。手を動かしながらインフラの基礎を習得

Docker:【Udemy】米国AI開発者がゼロから教えるDocker講座

ここで紹介している教材は本当にわかりやすかったです。もちろん上記プラスで公式ドキュメントQiitaを見ながらしました。

当たり前と思いますがLaravelならこちらは必ず見た方が良いですね。
ReadDouble

13.大変だったこと

大変だったこと、苦労したことはやはり初めて触る技術・実務で使っていないですね。
具体的にはこちら↓

  • Laravel
  • PHPUnit
  • Circle CI
  • AWS

Laravelで大変だったこと

Laravelに関しては現職に入社する前に基礎は学習していたのでそこまで躓かなかったのですが、

  • 並び替え・絞り込み検索時のページネーション保持
  • プロフィール画像のデフォルト設定/変更

は結構手こずりました。

画像の取り扱いに関しては

  • 開発環境ではローカルに保存
  • 本番環境ではS3に保存

にするように変更したので、初めからS3に保存していてもよかったですね。

PHPUnitで大変だったこと

  • テストコードの書き方全般
  • DBを使ったユニットテスト(単体テスト)

結合テストだったり統合テストは今後勉強していきたいです。

Circle CIで大変だったこと

  • config.yml(設定ファイル)の書き方全般

だいぶ苦労しました。4日くらいひたすら自動テストの段階で格闘してましたw
めっちゃググって実際に書いて大まかな書き方は理解できたのでこれは今後別の記事にできたら良いなと。

※ちなみに、自動テストと自動デプロイが無事に通った瞬間は最高の気分で昇天するかと思いました。

AWSで大変だったこと

  • AWSの概念の理解
  • ALB(ELB)を使ってクライアントとの通信をHTTPS化

HTTPS化に関しては結局Laravel側にも設定が必要ということに気づいて解決したんですが、ここも結構時間を使いました。

14.実装したかったけどできなかったこと・まだしていないこと

思いつくだけでもこちらです↓

  • 要件定義
  • 基本設計
  • 詳細設計
  • ユーザー登録時のバリデーション強化
  • セキュリティ面の確認
  • コメント機能
  • DM機能
  • フォロー機能
  • Vue.jsの活用
  • ファビコン設定
  • Twitterへのシェア機能
  • Webサーバー、DBサーバーの冗長化
  • ECS(Elastic Container Service)でデプロイ
  • Cloud Frontでの画像のキャッシュ配信
  • Docker環境の最適化
  • 結合テスト/統合テスト

見てもらうとわかると思いますが、できてないこと・してないことはたっぷりです(笑)
少しずつできたらいいなと思います。

15.開発してみた感想

プライベートでWebアプリを開発したのは転職活動前ぶりでしたが、当時は

  • フレームワークなしでネイティブPHPのみ
  • ローカルはMAMP
  • Xserverに手動デプロイ
  • テストコード0

というなかなか笑えない感じだったので、今回色々な技術を使って開発した感想としては

  • アウトプットしながらのインプットがまじで最強
  • FW超便利
  • Docker超便利
  • AWSはむずい
  • CI/CDパイプラインは構築はむずいけどできたら超便利
  • 公式ドキュメントをちゃんと読むようになった
  • ググればある程度の問題は解決できた

とかですかね。

赤字で強調しましたが、アウトプットありきのインプットはまじで効率がオバケです。
どういうことかと言うと、勉強(インプット)してから「どんな機能を実装しようかな」(アウトプット)じゃなくて、まず実装する機能をまず決めて(アウトプット)それに必要な知識を勉強(インプット)するということです。

アウトプット駆動開発とでも言いいますか。

超オススメです。

(あとは実務未経験でポートフォリオにDocker、Circle CI、AWSを盛り込んでいる方がいますがマジですげえなと思いましたね)

16.さいごに

かなり長くなってしまいましたが、最後までご覧いただき本当にありがとうございますm(__)m

今後はもっと上流工程(要件定義、基本設計、詳細設計)にチャレンジしたいなと思っています。

初学者の方々も僕と同じくエンジニア1年生の方もアウトプット駆動開発でゴリゴリスキルアップしていきましょう(^^)

(あ...最後に...LGTMをポチッと押していただけると...昇天しま...あ、嬉しいです)

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

Go(Echo)×MySQL×Docker×Clean ArchitectureでAPIサーバー構築してみた

はじめに

タイトルにもある通り、Go×MySQL×DockerでAPIサーバーを作ってみました。
はじめは、

・Goで簡単なREST APIを作ってみる
・Dockerで開発環境を立てる

を目的として作っていたのですが、ディレクトリ構成をどうしようか悩んでいろいろ調べているとGo×クリーンアーキテクチャでAPIサーバーを作っている記事をたくさん発見したので、クリーンアーキテクチャやってみよう、ということでやってみました。

ということでこの記事は、

・GoでAPIを書いてみたい
・Dockerで開発環境をたてたい
・その際にクリーンアーキテクチャを採用してみたい

ぐらいの温度感の方におすすめです。

逆に、がっつりクリーンアーキテクチャを勉強したいという方にはあまり参考にならないかもしれません。。

なお、
フレームワークにEcho (https://echo.labstack.com/)
DBへのアクセスにはGorm (https://github.com/go-gorm/gorm)
を使用しています。

リポジトリはこちら↓

準備編

開発環境

Dockerで開発環境を構築しました。

Dockerfile

基本的なDockerfileの書き方は Dockerfile 公式リファレンス が参考になります。

では、さらっとですが順番に見ていきます。

まずはGoです。

docker/api/Dockerfile
FROM golang:1.14

# go moduleを使用
ENV GO111MODULE=on 

# アプリケーションを実行するディレクトリを指定
WORKDIR /go/src/github.com/Le0tk0k/go-rest-api

# 上記のディレクトリにgo.modとgo.sumをコピー
COPY go.mod go.sum ./
# 上記のファイルに変更がなければキャッシュ利用できる
RUN go mod download

COPY . .
RUN go build .

RUN go get github.com/pilu/fresh

EXPOSE 8080

# freshコマンドでサーバーを起動
CMD ["fresh"]

github.com/pilu/fresh でホットリロードできるようにしました。
他は特に変わったところはありません。

参考
Using go mod download to speed up Golang Docker builds
Go v1.11 + Docker + fresh でホットリロード開発環境を作って愉快なGo言語生活

次にMySQLです。

docker/mysql/Dockerfile
FROM mysql

EXPOSE 3306

# MySQL設定ファイルをイメージ内にコピー
COPY ./docker/mysql/my.cnf /etc/mysql/conf.d/my.cnf

CMD ["mysqld"]

MySQLの設定ファイルは以下のようになりました。
MySQL8.0以降では接続時の認証方式が変更になっているため、2行目の記述が必要になるらしい。
あと、文字化けを防ぐために文字コードを変更しています。

docker/mysql/my.cnf
[mysqld]
default_authentication_plugin=mysql_native_password
character-set-server=utf8mb4

[client]
default-character-set=utf8mb4

/docker-entrypoint-initdb.d/ というディレクトリ内に初期化用のSQLやスクリプトを置いておくと、最初にimageを起動したときにデータの初期化を自動的に行えます。

ということで、テーブルを作成します。

docker/mysql/db/init.sql
CREATE DATABASE IF NOT EXISTS go_rest_api;
USE go_rest_api;

CREATE TABLE IF NOT EXISTS users (
  id          INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
  name        VARCHAR(256) NOT NULL,
  age         INT NOT NULL,
  created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  updated_at  TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

参考
Docker + MySQLで開発環境用DBの作成

docker-compose.yml

こちらも Compose ファイル・公式リファレンス が参考になります。

docker-compose.yml
version: '3'
services: 
  db:
    build: 
      # Dockerfileのあるディレクトリのパスを指定
      context: .
      dockerfile: ./docker/mysql/Dockerfile
    ports: 
      # 公開ポートを指定
      - "3306:3306"
    volumes: 
      # 起動時にデータの初期化を行う
      - ./docker/mysql/db:/docker-entrypoint-initdb.d
      # mysqlの永続化
      - ./docker/mysql/db/mysql_data:/var/lib/mysql
    environment: 
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: go_rest_api
      MYSQL_USER: user
      MYSQL_PASSWORD: password

  api:
    build:
      context: .
      dockerfile: ./docker/api/Dockerfile
    volumes: 
      - ./:/go/src/github.com/Le0tk0k/go-rest-api
    ports: 
      - "8080:8080"
    depends_on:
      # dbが先に起動する
      - db

mysqlの永続化をしているので、コンテナを立ち上げ直した場合も、以前のデータが残ったままになります。

また、apiにdepend_onを指定することで、先にdbが起動し、その次にapiが起動するようになります。
しかし、depend_onは起動する順番を指定するだけで、出来上がるまでは待ってくれないので、別で対策が必要です。
なお、対策については実装編で紹介しています。

参考

クリーンアーキテクチャ

クリーンアーキテクチャは、The Clean Architecture で提唱されているアーキテクチャです。

僕はそもそも、アーキテクチャ?何それ?状態だったのですが、以下の記事を読んで上澄み1mmぐらいは理解できた気がします。他にもたくさん記事があるのでぜひ調べてみてください!

参考 (クリーンアーキテクチャ以外の記事もあります)

実装編

ディレクトリ構成

ディレクトリ構成は以下の通りです。

├── docker
│   ├── api
│   │   └── Dockerfile
│   └── mysql
│       ├── Dockerfile
│       ├── db
│       │   ├── init.sql
│       │   └── mysql_data
│       └── my.cnf
├── docker-compose.yml
├── domain
│   └── user.go
├── infrastructure
│   ├── router.go
│   └── sqlhandler.go
├── interfaces
│   ├── controllers
│   │   ├── context.go
│   │   ├── error.go
│   │   └── user_controller.go
│   └── database
│       ├── sql_handler.go
│       └── user_repository.go
|── usecase
│   ├── user_interactor.go
│   └── user_repository.go
├── server.go
├── go.mod
├── go.sum

domain    →  Entities層
infrastructure → Frameworks & Drivers層
interfaces   → Interface層
usecase   → Use cases層

に対応しています。

Domain ( Entities層 )

最も内側にある層で、どこにも依存しない層となります。
Userモデルを定義していきます。

なお、 json:"-" については、- と書くと出力されなくなります。

domain/user.go
package domain

import "time"

type User struct {
    ID        int       `gorm:"primary_key" json:"id"`
    Name      string    `json:"name"`
    Age       int       `json:"age"`
    CreatedAt time.Time `json:"-"`
    UpdatedAt time.Time `json:"-"`
}

type Users []User

Interfaces/database, Infrastructure (Interface層, Frameworks & Driver層)

次に、データベース周辺を実装していきます。
データベースの接続は外部との接続となるので、一番外側のInfrastructure層に定義します。

infrastructure/sqlhandler.go
package infrastructure

import (
    "fmt"
    "time"

    "github.com/Le0tk0k/go-rest-api/interfaces/database"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

type SqlHandler struct {
    Conn *gorm.DB
}

func NewMySqlDb() database.SqlHandler {

    connectionString := fmt.Sprintf(
        "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true&loc=Local",
        "user",
        "password",
        "db",
        "3306",
        "go_rest_api",
    )

    conn, err := open(connectionString, 30)
    if err != nil {
        panic(err)
    }

    //接続できているか確認
    err = conn.DB().Ping()
    if err != nil {
        panic(err)
    }

    // ログの詳細を出力
    conn.LogMode(true)
    // DBのエンジンを設定
    conn.Set("gorm:table_options", "ENGINE=InnoDB")

    sqlHandler := new(SqlHandler)
    sqlHandler.Conn = conn

    return sqlHandler
}

// MySQLの立ち上がりを確認してからapiコンテナが立ち上がるようにする
func open(path string, count uint) (*gorm.DB, error) {
    db, err := gorm.Open("mysql", path)
    if err != nil {
        if count == 0 {
            return nil, fmt.Errorf("Retry count over")
        }
        time.Sleep(time.Second)
        count--
        return open(path, count)
    }
    return db, nil
}

func (handler *SqlHandler) Find(out interface{}, where ...interface{}) *gorm.DB {
    return handler.Conn.Find(out, where...)
}

func (handler *SqlHandler) Create(value interface{}) *gorm.DB {
    return handler.Conn.Create(value)
}

func (handler *SqlHandler) Save(value interface{}) *gorm.DB {
    return handler.Conn.Save(value)
}

func (handler *SqlHandler) Delete(value interface{}) *gorm.DB {
    return handler.Conn.Delete(value)
}

opan()では、dbコンテナが立ち上がるまで待ってからapiコンテナが立ち上がるように設定しています。
今回は、Goのコードでこの機能を実装しましたが、シェルスクリプトで実装する方がいいのでしょうか?ベストプラクティスがわかりません、、
参考
- docker-compose upでMySQLが起動するまで待つ方法(2種類紹介)

なお、infrastructure層はdb接続のみにして、実際の処理はinterfaces/database層に実装します。

interfaces/database/user_repository.go
package database

import (
    "github.com/Le0tk0k/go-rest-api/domain"
)

type UserRepository struct {
    SqlHandler
}

func (userRepository *UserRepository) FindByID(id int) (user domain.User, err error) {
    if err = userRepository.Find(&user, id).Error; err != nil {
        return
    }
    return
}

func (userRepository *UserRepository) Store(u domain.User) (user domain.User, err error) {
    if err = userRepository.Create(&u).Error; err != nil {
        return
    }
    user = u
    return
}

func (userRepository *UserRepository) Update(u domain.User) (user domain.User, err error) {
    if err = userRepository.Save(&u).Error; err != nil {
        return
    }
    user = u
    return
}

func (userRepository *UserRepository) DeleteByID(user domain.User) (err error) {
    if err = userRepository.Delete(&user).Error; err != nil {
        return
    }
    return
}

func (userRepository *UserRepository) FindAll() (users domain.Users, err error) {
    if err = userRepository.Find(&users).Error; err != nil {
        return
    }
    return
}

UserRepositoryにSqlHandlerを埋め込んでいます。
domain層をインポートしていますが、これは最も内側にある層なので、依存関係は守れています。

しかし、SqlHandlerは1番外側のInfrastructure層に定義したので、依存関係が内から外に向いており、ルールを守れていないのでは?という疑問が生まれますが、実はここで呼んでいるSqlHandlerはInfrastructure層に定義した構造体ではなく、同階層であるinterface/database層に定義したインターフェースなのです。
これをDIP(依存関係逆転の原則)といいます。

どういうことかといいますと、interfaces/database/user_repository.goでは、Infrastructure層で定義されている処理を呼んでしまうと依存関係が内→外になってしまい依存関係を守れていないことになります。それを避けるために同階層にDBとのやりとりをinterfaceで定義しておき、それを呼び出しましょうということです。
これで、実際にはInfrastructure層で処理を行っているが、interfaces/database/user_repository.goではSqlHandlerインターフェースを呼び出しているので依存関係は守れていることになります。

そしてGoでは、型TがインターフェースIで定義されているメソッドを全て持つとき、インターフェースIを実装していることになるので、Infrastructure.SqlHandler構造体にinterfaces/databases/sql_handerのSqlHandlerインターフェースで定義されているメソッドを定義しています。

interfaces/database/sql_handler.go
package database

import "github.com/jinzhu/gorm"

type SqlHandler interface {
    Find(interface{}, ...interface{}) *gorm.DB
    Create(interface{}) *gorm.DB
    Save(interface{}) *gorm.DB
    Delete(interface{}) *gorm.DB
}

Usecase ( Use Case層 )

Usecase層では、interfaces/database層から情報を受け取り、interfaces/controller層へと情報を渡す役割を持ちます。

usecase/user_interactor.go
package usecase

import "github.com/Le0tk0k/go-rest-api/domain"

type UserInteractor struct {
    UserRepository UserRepository
}

func (interactor *UserInteractor) UserById(id int) (user domain.User, err error) {
    user, err = interactor.UserRepository.FindByID(id)
    return
}

func (interactor *UserInteractor) Users() (users domain.Users, err error) {
    users, err = interactor.UserRepository.FindAll()
    return
}

func (interactor *UserInteractor) Add(u domain.User) (user domain.User, err error) {
    user, err = interactor.UserRepository.Store(u)
    return
}

func (interactor *UserInteractor) Update(u domain.User) (user domain.User, err error) {
    user, err = interactor.UserRepository.Update(u)
    return
}

func (interactor *UserInteractor) DeleteById(user domain.User) (err error) {
    err = interactor.UserRepository.DeleteByID(user)
    return
}

UserInteractorはUserRepositoryを呼び出していますが、これもInterfase層から呼び出してしまうと内→外への依存となり依存関係を守れなくなってしまうので、同階層にuser_repository.goを作成し、インターフェースを実装してDIP(依存関係逆転の原則)を利用して依存関係を守ります。

usecase/user_repository.go
package usecase

import "github.com/Le0tk0k/go-rest-api/domain"

type UserRepository interface {
    FindByID(id int) (domain.User, error)
    Store(domain.User) (domain.User, error)
    Update(domain.User) (domain.User, error)
    DeleteByID(domain.User) error
    FindAll() (domain.Users, error)
}

Interfaces/controllers, Infrastructure (Interface層, Frameworks & Driver層)

そして、コントローラーとルーターを実装していきます。

interfaces/controllers/user_controller.go
package controllers

import (
    "strconv"

    "github.com/Le0tk0k/go-rest-api/domain"
    "github.com/Le0tk0k/go-rest-api/interfaces/database"
    "github.com/Le0tk0k/go-rest-api/usecase"
)

type UserController struct {
    Interactor usecase.UserInteractor
}

func NewUserController(sqlHandler database.SqlHandler) *UserController {
    return &UserController{
        Interactor: usecase.UserInteractor{
            UserRepository: &database.UserRepository{
                SqlHandler: sqlHandler,
            },
        },
    }
}

func (controller *UserController) CreateUser(c Context) (err error) {
    u := domain.User{}
    c.Bind(&u)
    user, err := controller.Interactor.Add(u)

    if err != nil {
        c.JSON(500, NewError(err))
        return
    }
    c.JSON(201, user)
    return
}

func (controller *UserController) GetUsers(c Context) (err error) {
    users, err := controller.Interactor.Users()

    if err != nil {
        c.JSON(500, NewError(err))
        return
    }
    c.JSON(200, users)
    return
}

func (controller *UserController) GetUser(c Context) (err error) {
    id, _ := strconv.Atoi(c.Param("id"))
    user, err := controller.Interactor.UserById(id)

    if err != nil {
        c.JSON(500, NewError(err))
        return
    }
    c.JSON(200, user)
    return
}

func (controller *UserController) UpdateUser(c Context) (err error) {
    id, _ := strconv.Atoi(c.Param("id"))
    u := domain.User{ID: id}
    c.Bind(&u)

    user, err := controller.Interactor.Update(u)

    if err != nil {
        c.JSON(500, NewError(err))
        return
    }
    c.JSON(201, user)
    return
}

func (controller *UserController) DeleteUser(c Context) (err error) {
    id, _ := strconv.Atoi(c.Param("id"))
    user := domain.User{ID: id}

    err = controller.Interactor.DeleteById(user)
    if err != nil {
        c.JSON(500, NewError(err))
        return
    }
    c.JSON(200, user)
    return
}

echoはecho.Contextを使用するので、今回利用するメソッドのインターフェイスを定義します。

interfaces/controllers/context.go
package controllers

type Context interface {
    Param(string) string
    Bind(interface{}) error
    JSON(int, interface{}) error
}
interfaces/controllers/error.go
package controllers

type Error struct {
    Message string
}

func NewError(err error) *Error {
    return &Error{
        Message: err.Error(),
    }
}

ルーティングはechoを使用している (外部パッケージを使用している)ので、Infrastructure層に実装します。
なお、uesrController.関数(c)で引数にecho.Context型を渡せているのは、echo.Contextがinterfaces/controllers/context.goのcontextインターフェースを満たしているからです。
(つまり、echo.Context型はcontextインターフェースのメソッドをすべて持っている。)

infrastructure/router.go
package infrastructure

import (
    "github.com/Le0tk0k/go-rest-api/interfaces/controllers"
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
)

func Init() {
    e := echo.New()

    userController := controllers.NewUserController(NewMySqlDb())

    // アクセスログのようなリクエスト単位のログを出力する
    e.Use(middleware.Logger())
    // アプリケーションのどこかで予期せずにpanicを起こしてしまっても、サーバは落とさずにエラーレスポンスを返せるようにリカバリーする
    e.Use(middleware.Recover())

    e.GET("/users", func(c echo.Context) error { return userController.GetUsers(c) })
    e.GET("/users/:id", func(c echo.Context) error { return userController.GetUser(c) })
    e.POST("/users", func(c echo.Context) error { return userController.CreateUser(c) })
    e.PUT("/users/:id", func(c echo.Context) error { return userController.UpdateUser(c) })
    e.DELETE("/users/:id", func(c echo.Context) error { return userController.DeleteUser(c) })

    e.Logger.Fatal(e.Start(":8080"))
}

最後に、server.goから呼び出します。

server.go
package main

import "github.com/Le0tk0k/go-rest-api/infrastructure"

func main() {
    infrastructure.Init()
}

実行編

では、完成したAPIを叩いてみましょう。
今回はPostmanを使用してみたいと思います。 (https://www.postman.com/downloads/)

まずHeadersのContent-Typeをapplication/jsonにします。
スクリーンショット 2020-09-19 16.09.22.png

GetUsers

全ユーザーを取得してみます。
/usersにGETリクエストを送ります。
スクリーンショット 2020-09-19 16.14.07.png

Getuser

任意のユーザーを取得してみます。
/users/:idにGETリクエストを送ります。
スクリーンショット 2020-09-19 16.16.59.png

CreateUser

まずはUserを作成してみます。
/usersにPOSTリクエストを送ります。
スクリーンショット 2020-09-19 16.11.47.png

UpdateUser

任意のユーザーを更新してみます。
/users/:idにPUTリクエストを送ります。
スクリーンショット 2020-09-19 16.18.02.png

DeleteUser

任意のユーザーを削除してみます。
/users/:idにDELETEリクエストを送ります。
スクリーンショット 2020-09-19 16.19.14.png

GETで確認してみると、しっかり削除されています。
スクリーンショット 2020-09-19 16.19.24.png

しっかり機能してます!

さいごに

Dockerで開発環境を立ててREST APIを作るだけだったはずが、クリーンアーキテクチャまで勉強することになり大変でしたが、アーキテクチャを知るとても良い機会になったと思います。
今回ぐらいの小さいAPIだと逆に複雑になった感はありますが、大規模だったら良さが発揮されるのかなといった感想を持ちました。
実際のところ、あまり理解できていないので)また設計についても勉強していきたいと思います!

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

windows10上のvirtual boxにUbuntu 20.04をインストールし、dockerを使った開発環境を構築する

環境構築って苦戦しますよね。
苦戦したときには、検索して対処方法を調べると思います。
様々な記事にお世話になっているので、自分も環境構築のメモを残して、
参考になる情報を残しておこうと思います。

今までUbuntu18.04を利用していましたが、
Ubuntu20.04も半年前くらいにリリースされてますので、移行しようと思います。
今回はローカルをできる限り汚さない、vscodeのremote-containerを使ったdocker開発環境を試してみます。
使いにくかったら変えればいいのです。dockerを手になじませたい。

構築内容、ゴール

vscode上からdockerコンテナにアクセスし、webサーバを立て、アクセスできれば完了です。
windows 10上で行います。virtual boxは事前に準備ください。

以下のインストール手順は紹介していきます。

  • Ubuntu20.04
  • docker
  • chrome
  • vscode

手順

以下の順で説明します。

  • Ubuntu20.04 インストール
  • docker インストール
  • chrome インストール
  • vscode インストール

Ubuntu 20.04 インストール

この記事をみましょう!!(初手丸投げ) → Windows10上のVirtualBoxにUbuntu20.04をインストール
図付きのインストール手順が紹介されています。
仕事レベルのクオリティなんですが。。 (手順書でつかいたい)LGTMが足りないのでは??

上記記事以外で行ったことを紹介します。

コピー&ペースト

virtual boxの「デバイス」→「クリップボードの共有」「ドラック&ドロップ」を双方向にします。なにかと捗ります。
「デバイス」から共有フォルダの設定も可能です。

ディレクトリ名英語化

端末で「ダウンロード」とか「デスクトップ」とか日本語まざってるとストレスですよね。
打ちたくないので変更します。
以下のコマンドを実行すると、変更しますか?とポップアップ出てくるので快諾しましょう。

LANG=C xdg-user-dirs-gtk-update

dockerインストール

前は公式に従って、docker-ceを入れてました。 公式URL:https://docs.docker.com/engine/install/debian/

今回はdocker.ioというubuntuさんがメンテしてるパッケージを使ってみました。
コマンドが少ないのでとても嬉しい。

sudo apt install docker.io
sudo gpasswd ${USER} docker # dockerコマンドをsudoつけずに実行できる
su ${USER} # group情報を更新。自分はrebootで再起動しちゃいました

chrome インストール

公式HPからダウンロードして、インストール。
ファイルを落とさないでインストールできそうな画面が出ますが、
ubuntu softwareに怒られてできません。

公式URL
https://www.google.com/intl/ja_jp/chrome/

参考 URL
【図解で理解】Ubuntu Desktop 20.04 LTSへのGoogle Chromeインストール手順

vscode インストール

公式HPからダウンロードして、インストール。
こちらもファイルを落とさないでインストールできそうな画面が出ますが、
ubuntu softwareに怒られてできません。
VScodeってクラウド版あるんですね。

公式URL
https://azure.microsoft.com/ja-jp/products/visual-studio-code/

vscodeプラグイン

プラグインボタンから検索していれました。
とりあえずすぐ使うものだけ。。

  • remote container
  • python
  • docker
  • (japanese language pack for visual studio code)

git関連とhtml関連のプラグインは今後入れると思います。。

開発環境

気になっていたremote-containerを使ったコンテナ開発を試みてみます。
2019年6月くらいに追加された機能ですね。一年以上前。。
といっても公式tutorialを実践するだけです。
自分はpythonのものを使用しましたが、いろいろな言語のチュートリアルがあります。

公式URL https://github.com/microsoft/vscode-remote-try-python#things-to-try

手順

  1. リポジトリをクローンする。 git clone https://github.com/microsoft/vscode-remote-try-python.git
  2. F1 キーかctrl + shift + p キーを押す。Remote-Containers: Open Folder in Container... コマンドを選択。
  3. クローンしたフォルダを選択してしばらくするとコンテナが作成される。端末を開くとコンテナ内に。
  4. python -m flask run --port 9000 --no-debugger --no-reloadを実行。
  5. google chromeでlocalhost:9000を指定しhtml確認。

とりあえず実行完了。チュートリアルを実行する際は続きはgithubのREADME.mdをご参考ください。。

おわりに

インストール、全然つまづきませんでした。
最近のアウトプット文化のおかげで沢山記事はありますが、目的の記事は見つけにくかったり埋もれてたりします。
この記事でよりわかりやすい優良記事を紹介できたかなと思います。

docker開発環境はいい感じですね。vscodeってなんで無料なんでしょうか。。
ただ頻繁に使うものはローカルで環境欲しいなーとおもったり←
venv切り替えたりとかの手間がなかったりするのもメリットでしょうか。
初回立ち上げは少し時間かかりますが、次回以降は使いまわしてくれます。
docker-composeやgithub連携も試してみないとですね。。
imagesにalpineさんいたけどpythonのコンテナはdebianでした。alpineなにしてるん?

ご参考になれば幸いです。
ありがとうございました。

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

Go(Gin)+MySQLなDocker環境でpanic: dial tcp 127.0.0.1:3306: connect: connection refusedに苛まれた

症状

タイトルの通り。

解決策

こちらに見事なまでにおんなじ症状の方がいて、言われるがままにMySQLの接続情報を修正したところ無事直りました。以下Before/Afterです。

Before

docker-compose.yml
version: "3"
services:
  app:
    build: .
    depends_on:
      - db
    volumes:
      - ./:/go/src/app
    ports:
      - 3000:3000
    environment:
      MYSQL_DATABASE: go_app_dev
      MYSQL_USER: docker
      MYSQL_PASSWORD: password

  db:
    image: mysql:5.7
    container_name: dockerMySQL
    volumes:
      - ./storage/mysql_data:/var/lib/mysql
    ports:
      - 3306:3306
    environment:
      MYSQL_DATABASE: go_app_dev
      MYSQL_USER: docker
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
db/db.go
package db

import (
    "fmt"
    "os"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

var (
    db  *gorm.DB
    err error
)

func Init() {
    user = os.Getenv("MySQL_USER")
    pass = os.Getenv("MYSQL_PASSWORD")
    dbname := os.Getenv("MYSQL_DATABASE")
    connection := fmt.Sprintf("%s:%s@/%s?charset=utf8&parseTime=True&loc=Local", user, pass, dbname)

    db, err := gorm.Open("mysql", connection)

    if err != nil {
        panic(err)
    }
}

After

docker-compose.yml
version: "3"
services:
  app:
    build: .
    depends_on:
      - db
    volumes:
      - ./:/go/src/app
    ports:
      - 3000:3000
    environment:
      MYSQL_DATABASE: go_app_dev
      MYSQL_HOST: dockerMySQL  # 追加!!
      MYSQL_USER: docker
      MYSQL_PASSWORD: password

  db:
    image: mysql:5.7
    container_name: dockerMySQL  # 追加!!
    volumes:
      - ./storage/mysql_data:/var/lib/mysql
    ports:
      - 3306:3306
    environment:
      MYSQL_DATABASE: go_app_dev
      MYSQL_USER: docker
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
db/db.go
// ...略

func Init() {
    user = os.Getenv("MySQL_USER")
    pass = os.Getenv("MYSQL_PASSWORD")
    host = os.Getenv("MYSQL_HOST")  // ココ!!
    dbname := os.Getenv("MYSQL_DATABASE")
    connection := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", user, pass, host, dbname)  // 修正!!

    db, err := gorm.Open("mysql", connection)

    if err != nil {
        panic(err)
    }
}

決め手はgorm.Openの第二引数で接続情報を指定する際に、MySQLを動かしているDockerコンテナのコンテナ名で接続するという点でした。そもそもBeforeのコードでMySQLに接続できなかったのはMySQLがlocalhost,あるいはTCPの3306番ポートでListenしていなかったからです。そしてdocker-composeで起動しているコンテナ達はdocker networkを介して通信しているため、ネットワーク名の代わりにコンテナ名で接続することが可能であり、Afterに載せたような接続情報でappコンテナからdbコンテナに接続できるということらしいです。このような理解で合っているかは少々自信がありませんがw

参考

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

実務経験1週間のまとめ

【何をやったか】

1. psotmanを利用したAPIテスト

→今回のプロジェクトでは、フロント開発とバックエンド開発が分かれており、
 バックエンド側だけで修正し、テストを実施するために必要

2. 軽微な修正(API返答レスポンスの修正など)

→イシューに上げられた、レスポンスがおかしいところや
 HTTPステータスがおかしい点の修正

【実務で使うため、学習すべき内容】

下記は、それぞれ別の記事でまとめていこうと思う。

SELECT文

Docker

コンソール
[mac] $ docker-compose ps
# コンテナ一覧を表示

[mac] $ docker run --rm
# docker-compose.yml に定義したサービスを起動
# --rmは不要なイメージを残さずに起動するオプション(テスト走らせる時など) 

[mac] $ docker-compose down
# コンテナを落とす

[mac] $ docker-compose up -d --build

-d 「デタッチド」モードでコンテナを起動します。
デフォルトは「アタッチド」モードで全てのコンテナログを画面上に表示
「デタッチド」モードではバックグラウンドで動作
--build コンテナの開始前にイメージを構築します
特に変更がない場合はキャッシュが使用されます。

Git

細かい便利技

デュアルディスプレイ
テストやDockerを立ち上げるターミナルを、他のモニターに投げる

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

《未経験→webエンジニア》実務5日目

昨日投稿できなかったので、今日投稿します。

この記事の目的

自分がやったこと、知らなかったこと、やるべきことを明確にし
1日あたりの成長速度を速める。

【今日やったこと】

APIテスト
イシューに上がっている軽微な内容の修正

【知らなかったこと】

・APIのレスポンスは、コントローラーに種別で作られている
・メモ帳のデフォルト設定で””が逆さになっており、エラーが起きる
・現在いるブランチ名はVScodeの左下に表示されている!

知らなかったテストbreakman→セキュリティ系のテストを行ってくれる
今回の案件では、breakman,rspec,rubocopの3段階でテストする
expected: "期待されている文言" got: "実際の文言"

ここが違わないかをチェックする
API文言の修正だけでなく、ステータスコードもセットで直すようにする
HTTPのステータスコードhttps://qiita.com/terufumi1122/items/997e24dde87f807e3944

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

AWS CLIを使ってECRに自前Dockerイメージを登録する

やりたいこと

ローカルで作成したDockerイメージをAWS CLIを使ってECRにプッシュする

前提

・AWS CLI バージョン2
・Dockerイメージ作成済

登録手順

ここに記載したのはイメージを Amazon Elastic Container Registry にプッシュするを自分なりにまとめなおしたものです。コマンド通らないってことがあったので、そこらへんも書いてます。

前準備

  • aws configure
    aws cliを利用するときにconfigureを実施しておく必要があります。すでに設定済の場合はスキップしてOKです。
    これはAWS CLIをセットアップする方法で、下記4つの情報の入力を求められます。

    • アクセスキーID
    • シークレットアクセスキー
    • AWSリージョン
    • 出力形式

configureするときは下記のように--profileを指定しておくことをお勧めします。
ここでは、prf-nameという名前にしました。

aws configure --profile prf-name

これを設定しておくとawsコマンドを使用するときに下記のような感じで--profileで作成した設定情報を指定してやれば、自分がどこにアクセスしたいのか簡単にコマンドに教えることができるわけです。

aws s3 ls --profile prf-name

--profileの指定がないと下記のように認証情報を特定することができないのでまずはaws configureしてね、と注意されます

Unable to locate credentials. You can configure credentials by running "aws configure"

作成したprofileのリストを確認したい場合は下記を実行します。設定した名前を忘れてしまったときに便利です。

aws configure list-profiles

Amazon ECRリポジトリを作成する

aws ecr create-repository --repository-name your-repository-name --region region --profile prf-name

your-repository-name: 好きなリポジトリ名
region: リージョン(例:ap-northeast-1)

公式サイトには記載がないですが、--profileを記載していないと、さきほどの認証エラーメッセージが表示されてしまいます。

イメージにタグをつけ、Amazon ECRにプッシュする

1. Amazon ECRリポジトリを作成する

aws ecr create-repository --repository-name hello-repository --region region

hello-repository: リポジトリ名
region: リージョン(例:ap-northeast-1)
出力:

{
    "repository": {
        "registryId": "aws_account_id",
        "repositoryName": "hello-repository",
        "repositoryArn": "arn:aws:ecr:region:aws_account_id:repository/hello-repository",
        "createdAt": 1505337806.0,
        "repositoryUri": "aws_account_id.dkr.ecr.region.amazonaws.com/hello-repository"
    }
}

2.前のステップの repositoryUri の値で hello-world イメージにタグを付けます。

docker tag hello-world aws_account_id.dkr.ecr.region.amazonaws.com/hello-repository

3.aws ecr get-login-password コマンドを実行します

aws ecr get-login-password --profile prf-name | docker login --username AWS --password-stdin aws_account_id.dkr.ecr.region.amazonaws.com

出力:

Login Succeeded

4.前のステップの値 repositoryUri を使用して、Amazon ECR にイメージをプッシュします。

docker push aws_account_id.dkr.ecr.region.amazonaws.com/hello-repository

5.プッシュしたイメージの確認
list-imagesを使って、プッシュしたイメージを確認

aws ecr list-images --repository-name hello-repository --region region --profile prf-name
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[解決]Dockerエラー'ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?'が出た時の対処法

これを解決します

[vagrant@localhost ~]$ sudo docker version
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

方法

$ sudo groupadd docker
$ sudo usermod -aG docker $USER

再度ログイン

$ newgrp docker 
$ sudo docker version

daemonのエラーが出てなければ解決

参考
https://docs.docker.com/engine/install/linux-postinstall/
https://stackoverflow.com/questions/21871479/docker-cant-connect-to-docker-daemon

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

Windows10 HomeにDokerをベースとした開発環境を構築する 2020 ver. その2 VSCodeでDocker開発環境を快適にできるはず

はじめに

前回はWindows10Homeにまともに動きそうなDockerを構築しました。今回は統合開発環境として十分に使えそうなVSCodeの設定を実施していきます。

快適な開発環境を定義してみる

その1 で書くべきだと反省していますが)
そもそもWindowsベースで快適じゃない開発がなぜ発生するか考えてみると、WindowsがLinuxやUnix(Mac)と同等に動かないことが一番の原因だと思いました。
改善ポイントと改善方法を考えてみました。

  1. ruby、python, MySQLなどの開発に必要なモジュールがまともに動かない(きがする)→ WSL2ベースのDockerならまともにうごく(はず)
  2. 開発に必要なモジュールのバージョン管理がもうカオス(こればMacでも一緒) → Docker適切な環境をコンテナとして用意。ただしその環境にすぐ切り替え可能なこと
  3. コマンドプロンプトやPowerShellなどのターミナルの操作が慣れない。bashがいい → Windowsで動くbash導入

1はDocker Desktopを導入しましたのであとは触ってみるだけですが一応解決とします。残りは2と3です。

開発に必要なモジュールのバージョン管理

コンテナ環境を用意するのはDockerに任せるとしまして、問題はその開発環境にすぐに切り替えられること。もっと言いますとコンテナの環境を開発環境として
まともに動かせるのか調査しました。VSCode の Remote Developmentがよさそうなので構築してみました。
「Visual Studio Code Remote Development allows you to use a container」
https://code.visualstudio.com/docs/remote/remote-overview

VSCode & Remote Containers

VSCodeをインストールします。
https://azure.microsoft.com/ja-jp/products/visual-studio-code/

拡張機能のRemote Containersをインストールします
https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers
image.png

そうするとVSCodeの左下に緑色の新しいステータスバーが追加されました。クリックしてみるとRemote Containersのコマンドが出てきます。
image.png

今のところ開発環境はありませんのでPythonのSample開発環境を導入してみます。
Remote-Containers: Try a Sample... を選択し、その中のPythonをクリックします
image.png
コンテナがインストールされ始めました。
image.png

これでサンプルのPython開発環境の準備ができました。ものの数分でできてしまいました。快適です。
image.png

Docker Desktopの画面でも新しいコンテナが認識されています。
image.png

VSCodeにもどってF5キーを押しすと、サンプルのFlaskを使ったWebアプリケーションが立ち上がります。Running on の後に続くURLにマウスフォーカスし、Follow Linkをクリックします。
image.png

Webブラウザが立ち上がり以下のような画面がみえたら成功です。
image.png

最後に開発ができること≒コードの編集をVSCode上で実施してみます。
index.htmlを開いて日本語の「もちろんできるよ!」を追記し保存します。
image.png

Webブラウザをリロードすると・・・
image.png

意図通りのコードが反映されました。あとはgitですがWindowsにはまだ導入していませんが、コンテナには導入されているのでVSCodeから実行できます。しかしコンテナ側でのgitの設定は必要になります。今後もコンテナ側のgitの設定は都度実施するのは快適ではありません。VSCode RemoteContainersではローカル側のgitの情報をシェアすることが可能となっています。ローカルにgitをインストールしておきましょう。

Git for Windows のインストール

インストーラーを公式サイトからダウンロードします。
https://git-scm.com/download/win

基本的にウィザードに従っていけば問題ありませんが、いくつか変更を考えた方がよい部部分があります。

デフォルトエディターの選択でVSCodeにしておく

(Vimでもよいと思います))
image.png

改行コードの変換は「Checkout as-is, commit Unix-style line ending」にしておく

開発環境がWindows、実行環境がLinuxの場合はこれにしておくことを推奨します。
(Checkout Windows-styleですとチェックアウトの時に改行コードをCRLFに変更します。
以前のnotepadなどWindowsの改行コードのみ対応しているエディターを利用する場合はこちらを選びますが、そのような人は今はほとんどいないと思っています)

インストールが終わったらGit Bashを立ち上げて以下のコマンドでuser.nameとuser.emailを設定します

> git config --global user.name "TakaK"
> git config --global user.email "sample@sample.sample"

gitの設定はとりあえずこれでOKです。

既存のコンテナへgitの設定を反映させる

コンテナのビルド時にgitの設定が自動でコピーされ反映されますので、既存のコンテナに反映させるにはReBuildする必要があります。

image.png

あとは通常のgitコマンドもしくはVSCode上のgitUIでもお好きなほうで作業OKです。

今後はリモートレポジトリの設定なども必要ですがローカルでコンテナを使った開発自体は以上でできると思います。

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

Docker rails + Vue.js localhostで接続が拒否された

この記事について

Dockerの理解が乏しいが故に解決に時間がかかったことを忘れないために投稿する記事となっております。

Vue.jsの導入にあたって参考にした記事

https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253

この記事で関連するファイル

  • Dockerfile
FROM ruby:2.6

# install package to docker container
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev \
    && apt-get install apt-transport-https \
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update && apt-get install -y yarn \
    && curl -sL https://deb.nodesource.com/setup_10.x | bash - \
    && apt-get install -y nodejs mariadb-client \
    && mkdir /vue_app

WORKDIR /vue_app
COPY Gemfile /vue_app/Gemfile
COPY Gemfile.lock /vue_app/Gemfile.lock

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
  • docker-compose.yml
compose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3320:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    command: bundle exec puma -C config/puma.rb
    environment:
      RAILS_ENV: development
    volumes:
      - .:/vue_app
      - bundle:/usr/local/bundle
      - /app/vendor
      - /app/log
      - /app/.git
    ports:
      - "3000:3000"
    depends_on:
      - db
    tty: true
    stdin_open: true
volumes:
  mysql-data:
    driver: local
  bundle:
    driver: local
  • Procfile.dev
Procfile.dev
web: bundle exec rails s
webpacker: ./bin/webpack-dev-server
  • bin/server
bin/server
#!/bin/bash -i
bundle install
bundle exec foreman start -f Procfile.dev

起こった問題

https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253#-foreman%E3%81%AB%E3%82%88%E3%82%8B%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E8%A8%AD%E5%AE%9A
参考URLと同じようにforemanを導入して2つのサーバーが同時に立ち上げられるように設定を行い、docker-copmpose upを実行してブラウザでlocalhost:5000を見にいくと以下が表示された。スクリーンショット 2020-09-11 13.46.23.png

やったこと

①Procfile.devに -b 0.0.0.0を追記して docker-compose run --rm web bin/serverを実行

Procfile.dev
+ web: bundle exec rails s -b 0.0.0.0
- web: bundle exec rails s

②foremanのポート番号指定

foremanのポート番号はデフォルトで5000番が設定されているようですが、改めて明示的に5000番を指定。

bin/server
#!/bin/bash -i
bundle install
+ bundle exec foreman start -p 5000 -f Procfile.dev

③そもそもdocker-compose.ymlのwebのポート番号を変更するのを忘れていたので、ポート番号を変更して docker-compose run --rm web bin/serverを実行

docker-compose.yml
ports:
-    - "3000:3000"
+    - "5000:5000"

しかし、解決ができない。。。

これでやっと解決した

https://docs.docker.jp/compose/reference/run.html
ポート番号までコマンドで改めて指定してあげて、サーバーの立ち上げに成功

docker-compose run -p 5000:5000 --rm web bin/server

ただ、なぜポート番号の指定を明示的にしないと繋がらないのかが分からないよ。。。
これまでにこんなコマンド使ったことなかったし。

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

Docker + rails + Vue.js localhostで接続が拒否された

この記事について

Dockerの理解が乏しいが故に解決に時間がかかったことを忘れないために投稿する記事となっております。
foremanによるサーバの設定をし、Ruby on RailsにVue.jsを導入しようとしたが、localhost:5000に接続ができず、どん詰まりしてしまった。

Vue.jsの導入にあたって参考にした記事

https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253

この記事で関連するファイル

  • Dockerfile
FROM ruby:2.6

# install package to docker container
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev \
    && apt-get install apt-transport-https \
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update && apt-get install -y yarn \
    && curl -sL https://deb.nodesource.com/setup_10.x | bash - \
    && apt-get install -y nodejs mariadb-client \
    && mkdir /vue_app

WORKDIR /vue_app
COPY Gemfile /vue_app/Gemfile
COPY Gemfile.lock /vue_app/Gemfile.lock

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
  • docker-compose.yml
compose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3320:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    command: bundle exec puma -C config/puma.rb
    environment:
      RAILS_ENV: development
    volumes:
      - .:/vue_app
      - bundle:/usr/local/bundle
      - /app/vendor
      - /app/log
      - /app/.git
    ports:
      - "3000:3000"
    depends_on:
      - db
    tty: true
    stdin_open: true
volumes:
  mysql-data:
    driver: local
  bundle:
    driver: local
  • Procfile.dev
Procfile.dev
web: bundle exec rails s
webpacker: ./bin/webpack-dev-server
  • bin/server
bin/server
#!/bin/bash -i
bundle install
bundle exec foreman start -f Procfile.dev

起こった問題

https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253#-foreman%E3%81%AB%E3%82%88%E3%82%8B%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E8%A8%AD%E5%AE%9A
参考URLと同じようにforemanを導入して2つのサーバーが同時に立ち上げられるように設定を行い、docker-copmpose upを実行してブラウザでlocalhost:5000を見にいくと以下が表示された。スクリーンショット 2020-09-11 13.46.23.png

やったこと

①Procfile.devに -b 0.0.0.0を追記して docker-compose run --rm web bin/serverを実行

Procfile.dev
+ web: bundle exec rails s -b 0.0.0.0
- web: bundle exec rails s

②foremanのポート番号指定

foremanのポート番号はデフォルトで5000番が設定されているようですが、改めて明示的に5000番を指定。

bin/server
#!/bin/bash -i
bundle install
+ bundle exec foreman start -p 5000 -f Procfile.dev

③そもそもdocker-compose.ymlのwebのポート番号を変更するのを忘れていたので、ポート番号を変更して docker-compose run --rm web bin/serverを実行

docker-compose.yml
ports:
-    - "3000:3000"
+    - "5000:5000"

しかし、解決ができない。。。

これでやっと解決した

https://docs.docker.jp/compose/reference/run.html
ポート番号までコマンドで改めて指定してあげて、サーバーの立ち上げに成功

docker-compose run -p 5000:5000 --rm web bin/server

ただ、なぜポート番号の指定を明示的にしないと繋がらないのかが分からないよ。。。
これまでにこんなコマンド使ったことなかったし。

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

Python with poetry での Dockerfile の効率的な書き方の模索

Pythonの機械学習用Docker imageのサイズ削減方法の紹介 という大変有益な M3 blog を twitter で見かけたので、便乗して自分が使っている Dockerfile との違いを簡単に書いてみようと思う。こちらは dashboard 用 Dockerfile であり機械学習用ではないので参考扱い程度に見てもらえればと。なお以下の内容が優れているとは思えないので better idea ある方にツッコミもらえれば幸いです。そしていつもながらインターネットの向こうの誰か1人に役に立ってもらえればも。なお、streamlit は大変便利。

結論

同じ部分

  • poetry で version 管理
  • multi-stage build を使う
  • docker build は dev, prod で cache が効く

違う部分

  • builder にて apt install を使わないなので builder も slim image を使う
  • poetry install は使わず poetry export を使う(実行イメージにはpoetry入れない)。そして requirements.txt と requirements-dev.txt を作成。
  • docker-compose.yml で dev, prod 切り替え
  • vscode を使い remote-container で開けば、開発環境側に python が不要。

code

build

少し前は .env 内で STAGE= と環境変数を定義していたが、書き換えがめんどくさいのでやめた。

# dev build
STAGE=dev docker-compose build
# prod build
docker-compose up build

Dockerfile

FROM python:3.8.5-slim as builder

WORKDIR /work/src
RUN pip install --upgrade pip && pip install poetry
COPY pyproject.toml poetry.lock ./
RUN poetry export --without-hashes -f requirements.txt > requirements.txt
RUN poetry export --without-hashes --dev -f requirements.txt > requirements-dev.txt
RUN pip install -r requirements.txt

### for dev. 
FROM python:3.8.5-slim as dev
ENV PYTHONUNBUFFERED=1
WORKDIR /work/src
COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages
# need to copy if python package is installed in /usr/local/bin
COPY --from=builder /usr/local/bin/streamlit /usr/local/bin/streamlit
COPY src/ ./
# for dev packages
COPY --from=builder /work/src/requirements-dev.txt requirements-dev.txt
RUN pip install -r requirements-dev.txt
COPY tests/ ./
EXPOSE 8501  
CMD ["streamlit", "run", "hello.py"]

### for prod
FROM python:3.8.5-slim as prod
ENV PYTHONUNBUFFERED=1
WORKDIR /work/src
COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages
# need to copy if python package is installed in /usr/local/bin
COPY --from=builder /usr/local/bin/streamlit /usr/local/bin/streamlit
COPY src/ ./
EXPOSE 8501  
CMD ["streamlit", "run", "hello.py"]

docker-compose.yml

version: '3'

services:
  app:
    build: 
      context: .
      target: ${STAGE:-prod}
    image: "internal-dashboard_app_${STAGE:-prod}"
    container_name: "internal-dashboard_${STAGE:-prod}"
    volumes:
      - ./src:/work/src
    ports:
      - "8501:8501"
    restart: always
    command: ["streamlit", "run", "hello.py"]

感想

tests の path 問題をどう対処するかいつも悩んでます。
以上。

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