- 投稿日:2020-09-19T22:56:49+09:00
Docker 初心者が Dockerfile からDocker イメージを作成するまで
はじめに
インターネットにつながっていない環境で numpy や pandas といった Python ライブラリを使って処理をする必要がでてきたので、Docker を使って必要なライブラリやファイルを Docker イメージとして固めて、別のサーバで docker load するまでの手順をまとめます。
内容に誤りや、修正すべき箇所があればご教示いただけますと幸いです。環境
- Mac OS Catalina 10.15.6
- Homebrew 2.5.1
Docker の準備
インストールと起動
こちらの記事を参考に Homebrew から Docker をインストールし、Docker.app を起動します。
DockerをHomebrewでMac OSに導入する方法 - QiitaDocker を起動しました。チュートリアルを無視したのでまっさらな状態です。
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:latestpull が完了すると、以下の通り
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 どっち? - Qiitakaz@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 することでコンテナが実行されます
- 投稿日:2020-09-19T21:23:18+09:00
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 loginIBM 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 defaultIBM 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ブラウザ上で作成したアプリケーションの設定を確認することができます。
アプリケーションの削除
作成したアプリケーションを削除するには、次のコマンドを実行します。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が表示されます。
アプリケーションのログを見る
起動したアプリケーションのログを見るには、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)を試してみた がオススメです
- 投稿日:2020-09-19T18:28:15+09:00
実務経験約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/shimotaroo3.開発背景
面接情報共有アプリを開発した理由は大きく分けて2つ。
- Laravel、Docker、AWS、CircleCIの勉強のため(アウトプットしながらが効率的)
- エンジニアへの転職活動中の方の面接の内容(特に質問)がTwitterで伸びやすいく、需要のある情報だと思ったため
1つのサービスの開発、運用を経験してみたかったら(特に運用)
↑開発前はリリース→機能追加→リリースをしてサービスをグロースできたらいいなと思っていました、他にやりたいこと・やるべきことがたくさんあり、今はそこまで考えてない4.(ひとまず)完成したWebアプリをご紹介
URL↓
https://mensetsu-app.work/GitHub↓
https://github.com/shimotaroo/laravel-app
README.md
はこちらの記事を見ながら書きました。
【GitHub】シンプルなREADME.mdの書き方 -コピペで使えるテンプレート付き-トップページに画像だったりとアプリの説明をいれるケースもありますが、Twitter、note、Instagramをイメージしてこんな感じのトップページのUIアプリの説明とか画像が入ってない)にしました。
自分がテスト投稿したデータが本番環境に残ってますが悪しからずww
5.完成までにかかった日数
毎日このアプリ開発に時間を費やしていたわけではないので明確にわかりませんが草は上の画像の感じです。
(育児と本業、その他諸々の傍らコツコツやってました)まあフルコミットすれば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図を作成できるスグレもの。作成したテーブルはこちら↓
テーブル名 説明 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に関して、僕の記事にもまとめていますので特に初学者の方はこちらも併せて読んでいただくとお役に立てると思います↓
※僕のQiitaの記事は約800LGTMなので結構オススメです(笑)
10.環境構築
環境構築はDockerでLEMP環境を構築しました。
LEMP環境は以下の通りです。
- L:Linux(OS)
- E:Nginx(Webサーバー)
- M:MySQL(DBサーバー)
- P:PHP(アプリケーション)
Dockerを使ったLaravel+Vue.jsの実行環境の構築方法については以下にまとめております。今回作成したアプリもこの記事通りに環境構築しました。
- 絶対に失敗しないDockerでLaravel+Vueの実行環境(LEMP環境)を構築する方法〜前編〜
- 絶対に失敗しないDockerでLaravel6.8+Vueの実行環境(LEMP環境)を構築する方法〜後編〜
絶対失敗しないのでDockerでLaravelの実行環境を構築をしたい方はぜひ併せて読んでいただければと思います。
11.インフラ
インフラはAWSを使いました。
僕は独学期間にAWSはノータッチ&実務でもまだAWSを使ったことがないので、この開発を通して初めて触りました。最初は意味不明ながらもUdemyの動画教材(後ほどご紹介します)を見ながら必死でインフラ構築しました。構築したインフラはこちらです。
上図の通りですが簡単に。
- 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ならこちらは必ず見た方が良いですね。
ReadDouble13.大変だったこと
大変だったこと、苦労したことはやはり初めて触る技術・実務で使っていないですね。
具体的にはこちら↓
- 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をポチッと押していただけると...昇天しま...あ、嬉しいです)
- 投稿日:2020-09-19T18:28:15+09:00
実務経験約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/shimotaroo3.開発背景
面接情報共有アプリを開発した理由は大きく分けて2つ。
- Laravel、Docker、AWS、CircleCIの勉強のため(アウトプットしながらが効率的)
- エンジニアへの転職活動中の方の面接の内容(特に質問)がTwitterで伸びやすいく、需要のある情報だと思ったため
1つのサービスの開発、運用を経験してみたかったら(特に運用)
↑開発前はリリース→機能追加→リリースをしてサービスをグロースできたらいいなと思っていました、他にやりたいこと・やるべきことがたくさんあり、今はそこまで考えてない4.(ひとまず)完成したWebアプリをご紹介
URL↓
https://mensetsu-app.work/GitHub↓
https://github.com/shimotaroo/laravel-app
README.md
はこちらの記事を見ながら書きました。
【GitHub】シンプルなREADME.mdの書き方 -コピペで使えるテンプレート付き-トップページに画像だったりとアプリの説明をいれるケースもありますが、Twitter、note、Instagramをイメージしてこんな感じのトップページのUIアプリの説明とか画像が入ってない)にしました。
自分がテスト投稿したデータが本番環境に残ってますが悪しからずww
5.完成までにかかった日数
毎日このアプリ開発に時間を費やしていたわけではないので明確にわかりませんが草は上の画像の感じです。
(育児と本業、その他諸々の傍らコツコツやってました)まあフルコミットすれば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図を作成できるスグレもの。作成したテーブルはこちら↓
テーブル名 説明 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に関して、僕の記事にもまとめていますので特に初学者の方はこちらも併せて読んでいただくとお役に立てると思います↓
※僕のQiitaの記事は約800LGTMなので結構オススメです(笑)
10.環境構築
環境構築はDockerでLEMP環境を構築しました。
LEMP環境は以下の通りです。
- L:Linux(OS)
- E:Nginx(Webサーバー)
- M:MySQL(DBサーバー)
- P:PHP(アプリケーション)
Dockerを使ったLaravel+Vue.jsの実行環境の構築方法については以下にまとめております。今回作成したアプリもこの記事通りに環境構築しました。
- 絶対に失敗しないDockerでLaravel+Vueの実行環境(LEMP環境)を構築する方法〜前編〜
- 絶対に失敗しないDockerでLaravel6.8+Vueの実行環境(LEMP環境)を構築する方法〜後編〜
絶対失敗しないのでDockerでLaravelの実行環境を構築をしたい方はぜひ併せて読んでいただければと思います。
11.インフラ
インフラはAWSを使いました。
僕は独学期間にAWSはノータッチ&実務でもまだAWSを使ったことがないので、この開発を通して初めて触りました。最初は意味不明ながらもUdemyの動画教材(後ほどご紹介します)を見ながら必死でインフラ構築しました。構築したインフラはこちらです。
上図の通りですが簡単に。
- 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ならこちらは必ず見た方が良いですね。
ReadDouble13.大変だったこと
大変だったこと、苦労したことはやはり初めて触る技術・実務で使っていないですね。
具体的にはこちら↓
- 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をポチッと押していただけると...昇天しま...あ、嬉しいです)
- 投稿日:2020-09-19T16:31:37+09:00
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/DockerfileFROM 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/DockerfileFROM 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.sqlCREATE 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-compose.yml
こちらも Compose ファイル・公式リファレンス が参考になります。
docker-compose.ymlversion: '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が先に起動する - dbmysqlの永続化をしているので、コンテナを立ち上げ直した場合も、以前のデータが残ったままになります。
また、apiにdepend_onを指定することで、先にdbが起動し、その次にapiが起動するようになります。
しかし、depend_onは起動する順番を指定するだけで、出来上がるまでは待ってくれないので、別で対策が必要です。
なお、対策については実装編で紹介しています。参考
クリーンアーキテクチャ
クリーンアーキテクチャは、The Clean Architecture で提唱されているアーキテクチャです。
僕はそもそも、アーキテクチャ?何それ?状態だったのですが、以下の記事を読んで
上澄み1mmぐらいは理解できた気がします。他にもたくさん記事があるのでぜひ調べてみてください!参考 (クリーンアーキテクチャ以外の記事もあります)
- クリーンアーキテクチャの書籍を読んだのでAPIサーバを実装してみた
- 今すぐ「レイヤードアーキテクチャ+DDD」を理解しよう。(golang)
- Clean ArchitectureでAPI Serverを構築してみる
- 【Golang + レイヤードアーキテクチャー】DDD を意識して Web API を実装してみる
実装編
ディレクトリ構成
ディレクトリ構成は以下の通りです。
├── 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.sumdomain → Entities層
infrastructure → Frameworks & Drivers層
interfaces → Interface層
usecase → Use cases層に対応しています。
Domain ( Entities層 )
最も内側にある層で、どこにも依存しない層となります。
Userモデルを定義していきます。なお、
json:"-"
については、- と書くと出力されなくなります。domain/user.gopackage 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 []UserInterfaces/database, Infrastructure (Interface層, Frameworks & Driver層)
次に、データベース周辺を実装していきます。
データベースの接続は外部との接続となるので、一番外側のInfrastructure層に定義します。infrastructure/sqlhandler.gopackage 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.gopackage 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.gopackage 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.gopackage 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.gopackage 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.gopackage 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.gopackage controllers type Context interface { Param(string) string Bind(interface{}) error JSON(int, interface{}) error }interfaces/controllers/error.gopackage 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.gopackage 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.gopackage 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にします。
GetUsers
全ユーザーを取得してみます。
/usersにGETリクエストを送ります。
Getuser
任意のユーザーを取得してみます。
/users/:idにGETリクエストを送ります。
CreateUser
まずはUserを作成してみます。
/usersにPOSTリクエストを送ります。
UpdateUser
任意のユーザーを更新してみます。
/users/:idにPUTリクエストを送ります。
DeleteUser
任意のユーザーを削除してみます。
/users/:idにDELETEリクエストを送ります。
しっかり機能してます!
さいごに
Dockerで開発環境を立ててREST APIを作るだけだったはずが、クリーンアーキテクチャまで勉強することになり大変でしたが、アーキテクチャを知るとても良い機会になったと思います。
今回ぐらいの小さいAPIだと逆に複雑になった感はありますが、大規模だったら良さが発揮されるのかなといった感想を持ちました。
(実際のところ、あまり理解できていないので)また設計についても勉強していきたいと思います!
- 投稿日:2020-09-19T15:31:12+09:00
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-updatedockerインストール
前は公式に従って、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
手順
- リポジトリをクローンする。
git clone https://github.com/microsoft/vscode-remote-try-python.git
- F1 キーかctrl + shift + p キーを押す。
Remote-Containers: Open Folder in Container...
コマンドを選択。- クローンしたフォルダを選択してしばらくするとコンテナが作成される。端末を開くとコンテナ内に。
python -m flask run --port 9000 --no-debugger --no-reload
を実行。- google chromeで
localhost:9000
を指定しhtml確認。とりあえず実行完了。チュートリアルを実行する際は続きはgithubのREADME.mdをご参考ください。。
おわりに
インストール、全然つまづきませんでした。
最近のアウトプット文化のおかげで沢山記事はありますが、目的の記事は見つけにくかったり埋もれてたりします。
この記事でよりわかりやすい優良記事を紹介できたかなと思います。docker開発環境はいい感じですね。vscodeってなんで無料なんでしょうか。。
ただ頻繁に使うものはローカルで環境欲しいなーとおもったり←
venv切り替えたりとかの手間がなかったりするのもメリットでしょうか。
初回立ち上げは少し時間かかりますが、次回以降は使いまわしてくれます。
docker-composeやgithub連携も試してみないとですね。。
imagesにalpineさんいたけどpythonのコンテナはdebianでした。alpineなにしてるん?ご参考になれば幸いです。
ありがとうございました。
- 投稿日:2020-09-19T13:17:16+09:00
Go(Gin)+MySQLなDocker環境でpanic: dial tcp 127.0.0.1:3306: connect: connection refusedに苛まれた
症状
タイトルの通り。
解決策
こちらに見事なまでにおんなじ症状の方がいて、言われるがままにMySQLの接続情報を修正したところ無事直りました。以下Before/Afterです。
Before
docker-compose.ymlversion: "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: passworddb/db.gopackage 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.ymlversion: "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: passworddb/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
参考
- 投稿日:2020-09-19T12:25:47+09:00
実務経験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を立ち上げるターミナルを、他のモニターに投げる
- 投稿日:2020-09-19T12:12:56+09:00
《未経験→webエンジニア》実務5日目
昨日投稿できなかったので、今日投稿します。
この記事の目的
自分がやったこと、知らなかったこと、やるべきことを明確にし
1日あたりの成長速度を速める。【今日やったこと】
APIテスト
イシューに上がっている軽微な内容の修正【知らなかったこと】
・APIのレスポンスは、コントローラーに種別で作られている
・メモ帳のデフォルト設定で””が逆さになっており、エラーが起きる
・現在いるブランチ名はVScodeの左下に表示されている!知らなかったテストbreakman→セキュリティ系のテストを行ってくれる
今回の案件では、breakman,rspec,rubocopの3段階でテストする
expected: "期待されている文言" got: "実際の文言"ここが違わないかをチェックする
API文言の修正だけでなく、ステータスコードもセットで直すようにする
HTTPのステータスコードhttps://qiita.com/terufumi1122/items/997e24dde87f807e3944
- 投稿日:2020-09-19T11:39:09+09:00
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-profilesAmazon 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-repository3.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 Succeeded4.前のステップの値 repositoryUri を使用して、Amazon ECR にイメージをプッシュします。
docker push aws_account_id.dkr.ecr.region.amazonaws.com/hello-repository5.プッシュしたイメージの確認
list-images
を使って、プッシュしたイメージを確認aws ecr list-images --repository-name hello-repository --region region --profile prf-name
- 投稿日:2020-09-19T10:54:20+09:00
[解決]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 versiondaemonのエラーが出てなければ解決
参考
https://docs.docker.com/engine/install/linux-postinstall/
https://stackoverflow.com/questions/21871479/docker-cant-connect-to-docker-daemon
- 投稿日:2020-09-19T09:47:21+09:00
Windows10 HomeにDokerをベースとした開発環境を構築する 2020 ver. その2 VSCodeでDocker開発環境を快適にできるはず
はじめに
前回はWindows10Homeにまともに動きそうなDockerを構築しました。今回は統合開発環境として十分に使えそうなVSCodeの設定を実施していきます。
快適な開発環境を定義してみる
(その1 で書くべきだと反省していますが)
そもそもWindowsベースで快適じゃない開発がなぜ発生するか考えてみると、WindowsがLinuxやUnix(Mac)と同等に動かないことが一番の原因だと思いました。
改善ポイントと改善方法を考えてみました。
- ruby、python, MySQLなどの開発に必要なモジュールがまともに動かない(きがする)→ WSL2ベースのDockerならまともにうごく(はず)
- 開発に必要なモジュールのバージョン管理がもうカオス(こればMacでも一緒) → Docker適切な環境をコンテナとして用意。ただしその環境にすぐ切り替え可能なこと
- コマンドプロンプトや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-overviewVSCode & 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
そうするとVSCodeの左下に緑色の新しいステータスバーが追加されました。クリックしてみるとRemote Containersのコマンドが出てきます。
今のところ開発環境はありませんのでPythonのSample開発環境を導入してみます。
Remote-Containers: Try a Sample... を選択し、その中のPythonをクリックします
コンテナがインストールされ始めました。
これでサンプルのPython開発環境の準備ができました。ものの数分でできてしまいました。快適です。
Docker Desktopの画面でも新しいコンテナが認識されています。
VSCodeにもどってF5キーを押しすと、サンプルのFlaskを使ったWebアプリケーションが立ち上がります。Running on の後に続くURLにマウスフォーカスし、Follow Linkをクリックします。
Webブラウザが立ち上がり以下のような画面がみえたら成功です。
最後に開発ができること≒コードの編集をVSCode上で実施してみます。
index.htmlを開いて日本語の「もちろんできるよ!」を追記し保存します。
意図通りのコードが反映されました。あとはgitですがWindowsにはまだ導入していませんが、コンテナには導入されているのでVSCodeから実行できます。しかしコンテナ側でのgitの設定は必要になります。今後もコンテナ側のgitの設定は都度実施するのは快適ではありません。VSCode RemoteContainersではローカル側のgitの情報をシェアすることが可能となっています。ローカルにgitをインストールしておきましょう。
Git for Windows のインストール
インストーラーを公式サイトからダウンロードします。
https://git-scm.com/download/win基本的にウィザードに従っていけば問題ありませんが、いくつか変更を考えた方がよい部部分があります。
デフォルトエディターの選択でVSCodeにしておく
改行コードの変換は「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する必要があります。
あとは通常のgitコマンドもしくはVSCode上のgitUIでもお好きなほうで作業OKです。
今後はリモートレポジトリの設定なども必要ですがローカルでコンテナを使った開発自体は以上でできると思います。
- 投稿日:2020-09-19T08:05:52+09:00
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.ymlversion: '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.devweb: 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
を見にいくと以下が表示された。やったこと
①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.ymlports: - - "3000:3000" + - "5000:5000"しかし、解決ができない。。。
これでやっと解決した
https://docs.docker.jp/compose/reference/run.html
ポート番号までコマンドで改めて指定してあげて、サーバーの立ち上げに成功docker-compose run -p 5000:5000 --rm web bin/serverただ、なぜポート番号の指定を明示的にしないと繋がらないのかが分からないよ。。。
これまでにこんなコマンド使ったことなかったし。
- 投稿日:2020-09-19T08:05:52+09:00
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.ymlversion: '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.devweb: 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
を見にいくと以下が表示された。やったこと
①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.ymlports: - - "3000:3000" + - "5000:5000"しかし、解決ができない。。。
これでやっと解決した
https://docs.docker.jp/compose/reference/run.html
ポート番号までコマンドで改めて指定してあげて、サーバーの立ち上げに成功docker-compose run -p 5000:5000 --rm web bin/serverただ、なぜポート番号の指定を明示的にしないと繋がらないのかが分からないよ。。。
これまでにこんなコマンド使ったことなかったし。
- 投稿日:2020-09-19T00:28:21+09:00
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 buildDockerfile
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 問題をどう対処するかいつも悩んでます。
以上。