20201016のdockerに関する記事は12件です。

docker runの[ i, t, d ]オプションの意味を調べた

はじめに

Dockerについて初心者の自分が
理解するのに難しかった部分を少しまとめます。
特に、コマンドオプションのところが難しかったので
少しでも自分と同じようなDocker初心者の方の理解の助けになればと思います!(^^)
間違った部分がありましたら是非お教えください!m(_ _)m

Dockerとは

これについては他にも沢山の記事があるので深く言及しませんが、
プロセスをコンテナ化するための仮想化ソフトウェアです。
Windowsの中に、Ubuntuも起動したい、そういった仮想化要望を叶えてくれます。

主に、開発したものをデプロイする!って時に役立つようです。
本番環境はLinux!でも自分のPCはWindows!な時に、
Windows上に、「コンテナ」という箱を作り、その箱の中にLinuxを立ち上げて開発をする感じ。
もう少し具体的な使い方は、
Webサーバー用コンテナ、データベース用コンテナなどを作って開発をして
本番環境にそのままえーいっ!って使い方なのかな?

複数人で開発する時に、
みんなの環境を合わせる意味でも便利なようです!

以下の記事が丁寧で分かりやすかったです。

Dockerとはどういったものなのか、めちゃくちゃ丁寧に説明してみる
【連載】世界一わかりみが深いコンテナ & Docker入門

Dockerの使い方

Dockerのインストール

まずは、Docker自体をインストールする必要があります。

  公式サイト : https://docs.docker.com/get-docker/

こんなコンテナを作りたーい!って頼めば、そんなコンテナを作ってくれて、
コンテナ何個も作ったから管理してー!って頼めば複数のコンテナを管理してくれるやつです。
(管理は頼まずとも基本勝手に良い感じにしてくれます)

Dockerをインストールしたら、
コマンドでDockerを操作することができるようになります。

docker ~~~~

みたいな感じでコマンドを打って、Dockerを操作します。

Linux以外?は、
「Docker Desktop For Windows/Mac」というGUIツールもありますね^^

Docker Image

先ほど述べた、
こんなコンテナ作りたーい とは
こんなImageのコンテナ作りたーい ということで
具体的には
Ubuntuが入ったコンテナを作りたーい というようなことです。

なので、コンテナを作るために、まずは「Image」を手に入れる必要があります。
これを「Docker Image」と言います。

  「 イメージ → コンテナ 」 ≒ 「 クラス → インスタンス 」

みたいな感じで考えれば、理解しやすいかもです。
Image群は基本的にここにあります。

  Docker Hub : https://hub.docker.com/

Docker Imageのリポジトリです。
GitHubのような感じと思えば良し。

ここからImageを取ってきて、(必要があれば自分でカスタマイズして)
コンテナを作っていきます。

docker pull someImage

でImageをローカルに取ってくる。

docker push someImage

でImageをサーバーにpushします。

既存のImageをカスタマイズして、自分でImageを作ることもできます。
「Dockerfile」というファイルにいろいろと設定を書いて

docker build -t imageName .    // ※Dockerfile が存在する階層にいる想定 

でImageを作成します。

コンテナの作成・起動

Docker Imageを元に
実際のプロセスであるコンテナを作ります。

docker create --name containerName someImage

そして、プロセスを動かす。

docker start containerName/containerID

コンテナID(containerID)は
コンテナを作成した時に自動的に付けられます。

コンテナ名(containerName)も指定しなければ自動的に付けられますが、
[ --name ] オプションを使って、自分で管理しやすい名前を付けるのが良いと思います。

上記の、作成・起動という動作を一度に行ってくれるのが

docker run --name containerName someImage

です。

コンテナの停止・削除

動いているコンテナを停止するのが

docker stop containerName 

そして、停止しているコンテナ自体を削除するのが

docker rm containerName 

動作中のコンテナを強制削除するには、

docker rm -f containerName 

として、[ -f ] オプションを使います。


全体的に
「イメージ情報」を持ったクラスから
「実際のプロセス(コンテナ)」であるインスタンスを作って
Dockerが管理しているリストにコンテナを登録する
みたいに考えれば自分は理解しやすかったです。

なので、
あくまでコンテナ自体はそれぞれ独立しています。
同じイメージから2つコンテナを作っても、それらはデータを共有してる訳ではないし、
コンテナを削除したら、そのコンテナで使っていたデータは基本的には消えます。
(データを永続化するには、Volume設定をする必要がある)

Dockerコマンドのオプション

ここまでの内容は
様々な記事で書かれていて理解もしやすかったのですが、
自分が結構悩んだのがよく使うオプションの挙動?というか意味合い?でした。

自分の中でも、これはこう書きましょう的な
ある種のおまじないのようになっていたので
少し踏み込んで調べてみて、ちょっとだけわかった気がしたので書いてみます。

[ -i ][ -t ]オプション

これらのオプションは

docker run -it someImage /bin/bash

のように使います。
( "/bin/bash" の部分は、コンテナ起動時に実行するメインコマンドを指定しています。)

" -it " と書いているのは、" -i -t "と書いても良いです。
ただ、基本一緒に使うので短縮して書くのがスタンダードのようです。
それぞれ
  [ -i ]オプション : interactive ( 対話型 )
  [ -t ]オプション : tty ( 端末デバイス )
の意味です。

[ -i ]オプション

シェルは何かしらの入力を受け取って処理をするコマンドプロセッサ?(何て言うの?)で、
対話型で入力を与えたい時は、標準入力からコマンドを与えて処理をしてもらいます。
(キーボード ≒ 標準入力 なので、キーボードでコマンドを打てばそれがシェルに届く)
なので、
コンテナの中で実行しているシェルに対話型で処理をしてもらうためには
コンテナの中で実行しているシェルと標準入力が繋がらないといけないです。
標準入力と繋がっていなければ、
コマンドを打っても、その入力はコンテナの中のシェルに届かず、
コンテナの中のシェルにコマンドを実行してもらうことができない状態になります。

そうならないようにするためのオプションが [ -i ] オプションで、
「docker run で指定したコマンドの" /bin/bash "を対話型で使いたいから標準入力と繋いでね」
という意味のオプションなんだと思います。

実際に、

1: docker run -it someImage
2: docker run -t someImage

1と2で挙動を比較してみたらイメージしやすかったです。
2で起動した場合は、" ls " や " exit " などを入力しても何も起こりません。
入力したコマンドがコンテナ内のシェルに届いていないのがわかります。

[ -t ]オプション

こちらは自分の中でも完全にしっくりきている訳ではないのですが、
おそらくこういうことなんじゃないかという推測です。。。

" docker run " のコマンドを実行しているターミナルがあります。
で、
[ -t ]オプションを付けてコンテナを起動すると、
そのターミナルの中に、コンテナ用の疑似的なターミナルを用意する
ということだと思います。

これも実際に、

1: docker run -it someImage
2: docker run -i someImage

1と2を比較すれば、どういう状態かはすぐに体験できます。
2の場合、" ls " や " exit " などのコマンドを打てば、
それがコンテナ内のシェルに届いて、コマンドは実行されるのですが、
いつものようにターミナルを開いた時のような状態にはなっておらず、
ユーザー名やカレントディレクトリも出ていないし、タブによる入力補完なども機能しません。

イメージ的には、
キーボードの入力がターミナルを介することなく、生のまま反映される感じ。。
ただ、これによって何か大きな問題があるのかと言われれば特にはわからず、
シェルと対話できるし、ただ単にターミナルの機能?が使えない分、不便なのかなという感じです。

なので、[ -t ]オプションは
「普段のようにターミナルを使えないと不便だからコンテナ用に疑似ターミナル用意するね」
という意味のオプションなんだと思います。

[ -d ]オプション

docker run -d someImage

のように、runコマンドを実行する時に
[ -d ] オプションを使うことがあるのですが、
これはそのコンテナにアタッチせずにコンテナを起動するという意味です。
(アタッチしない = デタッチの"d")
つまりは、バックグラウンドでコンテナを起動するということです。

このオプションで気になった点は、
バックグラウンドで実行してるのにコンテナがすぐに停止してしまうことでした。
なんとなく自分のイメージだと
バックグラウンドで実行したら裏で動き続けてくれるのかなと思ってたのですが、
単純にそういう訳でもなさそうでした。

例えば

docker run -d someImage /bin/bash

これでコンテナを起動すると、
起動したコンテナは即座に停止します。

これは指定した " /bin/bash "(メインコマンド) が起動直後に終了してしまうからで、
コンテナは、バックグラウンドで実行しようがしなかろうが、
メインコマンドの実行が終わると自動で停止する
という動作になっているからだと思います。

なので、
バックグラウンドで実行させ続けたい時は
勝手に終了することのないコマンドを、コンテナに与える必要があるようです。

先ほどの起動方法を

docker run -d -it someImage /bin/bash    // ※この話に関しては[ -t ]オプションはあってもなくてもいい

とすると、
" bin/bash " を対話型で実行するようになり、
メインコマンドが勝手に終了することがなくなり、
コンテナもバックグラウンドで動き続けるようになります。

[ -d ]オプションで大事な点は、
「あくまでバックグラウンドで実行するだけでコンテナの挙動に差はないよ」
ということだと思いました。

おわりに

完全にDockerの動きを理解できた訳ではありませんが、
よく使うオプションで
「とりあえずこのオプション付けておいたら良いんでしょ」
という状態からは少し抜け出せたかなと思います。

同じようにDocker学び始めた方の理解の参考に
少しでもなれたら幸いです(^^)

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

OpenFOAM v8 Docker Image on Macのカスタマイズ

はじめに

OpenFOAMv8(Foundation版)をMacにインストールした後,イメージをカスタマイズする方法をここにまとめておきます。
インストールなどの詳細はこちらのページも参考になると思います。

Dockerのインストール

Dockerサイトから,Docker Desktopをダウンロード,インストールします。ここでは特筆事項はありません・・・(楽でいいですよね)。

インストールが終了しましたら,Docker Desktopを起動します。こんな感じで右上にクジラマークが現れます。

Screen Shot 2020-10-17 at 23.41.46.png

OpenFOAMv8のインストール

FoundationにあるFoundationに従ってインストールします。openfoamというディレクトリを作成し,その中で,以下のコマンドを打ち込みます。

本家から抜粋
sudo curl --create-dirs -o /usr/local/bin/openfoam8-macos http://dl.openfoam.org/docker/openfoam8-macos
sudo chmod 755 /usr/local/bin/openfoam8-macos

OpenFOAMコンテナ

openfoamのディレクトリ内で以下のコマンド(スクリプト)を実行することにより,OpenFOAMの環境が立ち上がります。

openfoam8-macos

これでblockMeshやsolversが使えるようになっています。

イメージのカスタマイズ

まず,イメージのIDを確認します。

docker images

Screen Shot 2020-10-16 at 22.37.49.png

このImage IDをメモしておきます。その後,rootでImageを立ち上げます。

docker run -it -u=root <Image ID>

スクリプトで立ち上げた場合は,aptなどでアプリをインストールしても,記録されません。が,rootで起動した場合はカスタマイズや各種設定変更はコンテナに記録されます。この状態で自分の所望のアプリをインストールします。

その後,コンテナを停止(ログアウト),Container IDを確認します。

docker ps -a

変更を加えたコンテナをイメージにcommitさせます。

docker commit <container ID> <Repository>

Repositoryですがデフォルト設定を活かし,openfoam/openfoam8-graphical-macosとしておくのが良いです。ここでRepositoryを自分の好きな名に変えることも可能ですが,その際は起動スクリプト/usr/local/bin/openfoam8-macos内のDOCKER_IMAGESを修正する必要があります。

たとえば,Repositoryをopenfoam8としたいのであれば,

example
docker commit <container ID> openfoam8
openfoam8-macos
DOCKER_IMAGE='macos8' 

とします。

個人的には,lv, gmshpython3.8, python3-pip(numpy, pandas, torch, tensorflow, scikit-learn)は入れておくと便利かなと思います。1
以上でカスタマイズは終了です。お疲れさまでした。

最後に

OpenFOAMのDockerイメージのカスタマイズ方法をまとめたサイト,ページが見当たらなかったこともあり,ここにまとめてみました。まだ,Dockerの使い方を理解できていない,使い切れていないこともあり,ベストなやり方であるかは分かりません・・・。より良いやり方があれば,ご指摘いただければ嬉しいです。


  1. torchインストール時にここにあるようなエラーでインストールできないという事象が発生しました。torchをここから直接ダウンロードし,pip3でインストールすることにより解決できます。または,pip --no-cache-dir install torchとしても解決することができます。 

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

【Docker】php artisan migrate エラー(SQLSTATE[HY000] [2002])

環境:Mac
   Laravel 6.x系
   Mariadb

php artisan migrate 実行時に以下のエラーが表示される。

Illuminate\Database\QueryException  : SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: nodename nor servname provided, or not known (SQL: select * from information_schema.tables where table_schema = database and table_name = migrations and table_type = 'BASE TABLE')

上記エラーメッセージの一部をコピペして検索すると「.envファイル」修正の対策がでてきたが、
私の場合は、どれも設定済みであったので、解決には至らなかった。
その後も検索し続け、やっと解決したので備忘録として。
結果として、ごく初歩的なミスでした。。。

以下簡単な流れ。
①コンテナに入る
②コンテナ内で「php artisan migrate」を実行
③違うエラーメッセージが表示される
④エラーメッセージの内容を確認し、訂正する

==================================

①コンテナに入る

$ docker-compose exec app bash

②コンテナ内で「php artisan migrate」を実行

[app] php artisan migrate

③違うエラーメッセージが表示される
おそらく、記載ミス等の指摘がされている内容が表示されるかと思う。

④エラーメッセージの内容を確認し、訂正する
③で表示されたエラーメッセージを読み取り、対象ファイルの修正を行う。

私の例:エラーメッセージ内に「255」の記載あり。
    migrationファイル内のtableのcreateで、以下のように記載していたのが原因
    修正後、問題なく、php artisan migrate できた。。。

#誤り(デフォルトで255のため不要)
$table->string('title',255);

#修正後
$table->string('title');

恥ずかしいミスだが、これが誰かの役に立てば幸い!!!!!!

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

これだけ知っとけ!docker-composeスニペット集

はじめに

コピペで使える docker-compose 記載例集です。

使い方

  1. Mac, Windows 双方とも、空のディレクトリを作成する。
  2. docker-compose.ymlという名前のファイルを新規作成する。
  3. 記載例集に載っている記載例をコピー&ペーストして保存する。
  4. 上記フォルダをカレントディレクトリにして、Macならターミナル、WindowsならPowerShellかコマンドプロンプトを開く。
  5. docker-compose upを実行。

Macでターミナルを開く方法

https://qiita.com/yamagh/items/02608e97be22c85cefaa 参照。

WindowsでPowerShellを開く方法

エクスプローラの「ファイル」メニューからPowerShellを開くことができる。
image.png

記載例集

MySQL

docker-compose.yml
version: '3'

services:
  # MySQL
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: mysql
      MYSQL_DATABASE: appdb
    volumes:
      - ./db/data:/var/lib/mysql
    ports:
    - 3306:3306

バージョン指定は8.0にしていますが、5.5、5.7等でもOKです。
接続ID/Passwordは、root/mysqlになります。また、初期作成されるデータベース名がappdbになります。
変更したい場合は、environmentの各項目を修正してください。
localhost:3306にアクセスすれば接続できます。
docker-compose.ymlがあるフォルダにdbフォルダが自動的に作成され、この中にMySQLのデータが入ります。
このフォルダの中身を消去するとDBを初期化できます(downしている時に消去してください)

jupyterLab

docker-compose.yml
version: '3'
services:
  lab:
      image: jupyter/datascience-notebook
      environment:
          - JUPYTER_ENABLE_LAB=yes
      ports:
          - "8888:8888"
      volumes:
          - ./work:/home/jovyan/work

notebookよりもlabのほうが使い勝手が良いと思います。また、ホームにworkディレクトリをマウントさせています。
workの中にノートを作成していけば良いでしょう。

gcc

たまにめっちゃCのソースをコンパイルしたいときがある。

docker-compose.yml
version: "3"

services:
  gcc:
    image: gcc
    tty: true
    volumes:
      - ./src:/src

tty: trueを書いておかないとすぐexitしてコンテナが終了してしまいます。
コンテナは起動だけしておいて、あとからゆっくりCLIでアタッチしましょう。

終わりに

記載例集は順次増やしていく予定です。

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

高校生がAIでWebサイトの改善点を提案するサービスを作った話

まえがき

アクセシビリティーの観点からWebサイトを診断し、AIプラットフォームを利用して得た情報をもとにベストプラクティスを提案してくれるオープンソースのWebサービス「Visible」を開発しました。

Visible ─ アクセシビリティー診断&修正提案

GoogleのLighthouseなど、Webサイトの診断を行ってくれるサービスは以前からありましたが、診断だけではなく改善点の提案も行う新しいサービスになっています。また、アクセシビリティーに関する理解を深めてもらえるように工夫をした設計にしていたり、コマンドライン版ではスタンドアロンで実行可能なようになっています。

2020年度の「独創的アイデアと卓越した技術を持つ小中高生クリエータ支援プログラム」未踏ジュニアに採択されていて、技術・資金面で援助を頂いており、11月1日にYouTube Liveで最終成果報告会が行われる予定です!

機能の紹介

WebサイトのURLを入力することでページを診断し、改善点を自動で提案してくれます。

診断結果のスクリーンショット

以下に提案する修正の一例を紹介します。

alt属性

<img>要素はスクリーンリーダーやSEOのクローラーに画像の内容を説明するためにalt属性が提供されることが推奨されており、Google Cloud PlatformのVision APIを利用してキャプションを生成することで改善のヒントを提案します。

image.png

lang属性

Webページの言語が明示的に指定されていないと、言語情報を必要とするユーザーエージェントで問題があるためページの内容からTranslate APIから言語情報を検出し提案します。

image.png

色コントラスト比

もちろん機械学習を使わない改善提案も可能です。色コントラスト比が低いと色覚特性を持ったユーザーが使いづらいため、コントラストを上げる修正を提案します。

コントラスト

アクセシビリティーに関するベストプラクティスはW3CによってWCAGという名前で標準化されており、他にも標準に基づいたルールがいくつかあります。

提案の仕組み

診断プログラムの実行にはChromeをヘッドレスで実行できるPuppeteerを使っていて、チェックポイント(Rule)のインターフェイスを実装したプログラムを実行し、各ruleから返された値をもとにファイルの情報と紐付けて最終的に差分として表示できるようになっています。

ワークフロー。下で詳解します

全体はコア、プラグイン、アプリケーションの3つのコンポーネントから構成されていて、コアで公開されている最低限の実装とインターフェイスを実装する形でプラグインで実際の処理を書いています。プラグインの形式はESLintを参考にしています。

動作フローを図で示しています。下で詳しく説明します。

改善点の生成アルゴリズムやヘッドレスブラウザーもruleと同様にプラグインとして拡張可能なため、Google Cloud Platformに限らず別の方法を使うことも可能です。

使った技術

プロジェクトはTypeScriptで開発されています。Tech stacksは以下のとおりです。

コア部分

  • Puppeteer - Chromeを使ったヘッドレスブラウザーです。診断プログラムの実行に使っています
  • domhandler - HTMLのASTです
  • PostCSS - プリプロセッサーとして知られていますが、stylelintを参考にASTとして使っています

Webバックエンド

  • Clean Architecture - 4層にレイヤー分けした有名なバックエンドの設計方法です。
  • TypeORM - TypeScript向けのORMです。
  • Bull - Redisを使ったジョブキューのフレームワークです
  • Apollo - Node.jsのGraphQL実装です

Webフロントエンド

  • Next.js - SSR/SSG/LambdaをやってくれるReact向けのBFFです
  • Apollo - Node.jsのGraphQL実装です
  • Tailwind CSS - Utility-firstのCSSフレームワークです
  • i18next - JS向けの国際化ライブラリです。

その他

  • GitHub Actions - CI/CDに使っています
  • Docker - Web版のデプロイに使っています
  • Yargs - CLI版に使っているフレームワークです
  • Lerna - JSのモノリポジトリのためのツールです

開発時のエピソード

Webアクセシビリティーという技術には前から興味があった一方で、僕自身は支援技術の利用を迫られたことがなく、正しいマークアップを心がけるモチベーションは専ら検索エンジンへの最適化くらいだったため、知識はあってもアクセシビリティーを欠いたWebサイトを作ってしまうことが多くあり、ESLintみたいに修正の方法も教えてくれるソフトウェアがあればいいと考えていました。

同時に、未踏ジュニアに応募できる年齢制限が17歳までで、当時僕はすでに17歳だったため最後にチャレンジしてみたいと考えていて、丁度いい機会にそれを作って応募してみようとプロトタイプを制作し始めました。

プロトタイプ

僕自身は中学生のときからプログラムを書いていてアルバイトだったりもしてるのでコードはかなり書いていましたが、それでも応募まで数ヶ月しかない状態でプロトタイプを仕上げるのは結構厳しく(正直採択後よりも応募段階のほうが忙しかったかもしれない)明確にゴールを設定する必要がありました。

応募自体にはプロトタイプは必須ではなくプロダクトの概要を書いた書類を送れば良かったものの、採択されるには僕自身が持っている技術を示して最後まで作り上げることができることを証明する必要がありました。逆に、それを示せればどこが面白いのかは伝わると判断し診断できる項目は最低限に絞り、「修正の提案」はバッサリ捨てて、「URLを入力したら診断結果が出てくる」ところまでをやることにしました。あとは脳内のふわふわした概念をノートに書いてドメインモデルに落とし込み、一番慣れている技術スタックでコードを書き始めました。

応募時のプロトタイプのスクリーンショット
応募時のプロトタイプのスクリーンショット

大体2ヶ月くらいで動くプロトタイプが出来上がり、無事書類選考も通過しました。その後は面接を受けることになっていて、オンラインでメンター陣にプロダクトに関する質問を受けました。正直何を言ったかよく覚えていませんが、確かプロダクト自体が将来的にどういう目標があるかみたいなことを訊かれて、漠然とした回答しかできなかったのは覚えています...。

採択後

採択後は、既にプロトタイプがあったためアジャイルで開発しました。未踏ジュニアでは定期的にメンタリングを受けられることになっていて、僕のプロジェクトでは週に一度メンターに進捗を報告しフィードバックを頂いていたので、だいたい各週でマイルストーンを設けてそれまでに小分けにした機能の開発を進めました。

ユーザーに使ってもらうまでのタスクの優先順位は完全に僕の勘で「これができるようになったぞ」って言ったときのインパクトが強い順でやっていましたが、今考えるとあんまりいい方法ではなかったかなと思います。ただ、それがあったおかげでビジネスロジックには拘ってもフレームワークに関する細かいところを弄りすぎることはなくてスピードは上がっていた気がします。

インパクト重視な機能の例
インパクト重視な機能の例

初ユーザーテスト

未踏ジュニアでは期間中、採択直後と中間時点と最後の3回登壇する機会があり(今年はオンラインでしたが)、一回目のプレゼンの機会がやってきたので既にあるプロトタイプ+αの段階のものを発表しました。

その際に、その時点のものをデプロイしたURLを共有し聴いていた方たちに実際に使っていただいたのですが、dockerの共有メモリの設定をミスっていたり非同期にするべきところを同期でレスポンスしたいたりして発表直後にサーバーがダウンしてしまい、期待していたほどのフィードバックは得られませんでした (トホホ〜)

A11yが専門の方々へのインタビュー

期間の四分の一を過ぎたあたりでメンター陣のご協力もあり某社のアクセシビリティーチームの方にフィードバックしていただく機会を頂けました。

アクセシビリティーの現場でどんなプロセスが行われているのかや、どんなツールを使っているか、チーム開発特有の問題を伺うことができ、このインタビューでストーリーラインを具体化してユーザー層を絞ることができました。タスク優先度もここらへんから明確になってきたと思います。

その後の改善

未踏ジュニアでの2回目の発表(もちろん負荷対策はしました...)やTwitterのフォロワーなどを活用して、実際に使ってもらいアンケートに答えてもらうというフィードバックループを小分けに回しました。

フィードバックは「How to create a good survey」と検索して上に出てきた良いとされる質問をパクってフィードバック用のフォームをGoogle Formsで作成したものと、Google Analyticsのタグを埋め込んだもので多面的に得られるようにしています。

特にユーザーがどの情報を欲しがっているかは重視していて、例えば未実装の機能にもURLを割り振ってそのURLにどれくらいトラフィックが発生したかで機能の優先度を付けることで開発に反映しました。

Google formのスクリーンショット

詰まったところ

すげーニッチな内容かもしれませんが、開発にあたって詰まったところのメモを書いておきます。

CSSのDOMから取れる情報とASTのマッピングができない

HTMLやCSSなどのソースコードはブラウザでパースされたあとにDOMに変換されてJavaScriptから利用可能になりますが、getComputedStlyeなどのメソッドから取得できるCSSの情報からは、どのファイルや宣言が適用されているのかわかりません。

そこで、Google Chromeの開発者ツールのAPIである Chrome Devtools Protocol を使うことにしました。CDPはCSS.styleSheetAdded というイベントから読み込まれたスタイルシートの情報を取得できるため、問題検出時にNodeのIDと該当のCSSプロパティから対応するCSSファイルを探し出し、PostCSSのASTに変換して扱うことができました。

Clean ArchitectureでORMをどこに置くか問題

書籍では「Interface adapter層はframework層が必要としているデータ形式に変換するレイヤー」と説明されており、そのためSQLクエリを発行する部分は interface adapter層、それを具体的なRDBMSで実行するのはframework層という扱いになっているのですが、ORMではこの2つのプロセスの境界が曖昧で、ググっても色んな人が全然違うことを言ってるっぽかったです。

TypeORMは(もちろん限度はありますが)どのRDBMSを使うかが抽象化されていて、最終的にormconfigで決定するようになっているので、詳細について言及していないと割り切ってinterface adapter層でDALを実装することにしました。

余談ですが、ドメインモデルをAPIの形に変換するpresenterもGraphQLの定義を直接扱うのではなくpresenter側で定義した型を使うことで依存の方向を守っています。

モノレポ(Yarn Workspace)でDockerをやるのが辛すぎる

マイクロサービスとかだとモジュール同士が依存することはあんまり無いのかと思いますが、フロントエンドとバックエンドで共有するパッケージがあるみたいなケースでYarn workspaceとDockerを使いたくなってしまうと、パッケージごとにlockfileを作れなかったりnode_modules以下がシンボリックリンクになっているから単純にコピーしても動かなかったりして詰みます。

今の所ごちゃごちゃなワークアラウンドを書いてなんとか動いています。Yarn v2 (berry) では yarn workspace focus という機能が提供されて、ほしいパッケージの依存だけをインストールして独立して動かせるようになっているっぽいので早く移行したいなと思っているのですが、Plug'n'Play周りが理解しきれていないためまだ手を付けられていません。

styled-componentsが辛い

初めてちゃんとデザインシステム的なUIコンポーネントを作ったんですが、一つのコンポーネントが複数のバリアントを持ってるみたいなとき(下記)にstyled-componentsでやると可読性が最悪で最終的にTailwindに逃げました。

const Button = styled.button`
  font-size: 12px;

  ${
    (props) => props.variant === 'primary'
      ? css`
        color: ${props.theme.primaryFgColor};
        background-color: ${props.theme.primaryBgColor};
      `
      : css`
        color: ${props.theme.secondaryFgColor};
        background-color: ${props.theme.secondaryBgColor}
      `;
  }
`;

詳細: https://qiita.com/rigarashi/private/5c97be5ed8fb15ea2d96

Utilify-firstをCSS-in-JSに輸入したstyled-systemとかxstyledというやつもあるっぽいんですが、テーマの型が静的に付かなかったので見送りました。

Puppeteerで並列処理

puppeteer-clusterというライブラリがいい感じっぽかったのですが、unmaintained気味なのと、pageのインスタンスをコールバックで受け取って簡単な処理をすることしかできず、例えばObservableに変換するみたいな今回のユースケースだと厳しかったのでやめました。

代わりに、いわゆるObject Poolingでbusyなインスタンスとそうでないインスタンスを管理して、暇そうなインスタンスに処理を投げるようにしました。こういうのは多分ワーカープロセスをforkしていくのがいいのかと思いますが、Puppeteerを起動した時点ですでにChromiumのプロセスと分かれるのであんまり意味がなかったのと、GraphQLのsubscriptionのソースになっているのがただのシングルトンで、redisとかを挟んでなくて同じプロセスで呼ばないとブラウザに伝えられないためです。

あとがき

蔑ろにされがちなアクセシビリティーで人々の気を引くにはどうすればいいか考えた結果、若干地雷っぽいタイトルになってしまったことをお赦しください。

また、web版等公開していますのでぜひお試しいただきフィードバックをお訊かせいただけると嬉しいです。

関連リンク

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

【Docker】Todoリスト作成時に詰まったところ

環境:Mac
   Laravel 6.x系
   Mariadb

【Docker】dbコンテナに入り、データを見ると日本語が???で表示される

https://qiita.com/n_oshiumi/items/cfe91c60730f602b38eb
こちらの記事を参考にしましたが、私の場合は以下のエラーが発生し、解決できなかった。

E: Unable to locate package language-pack-ja-base
E: Unable to locate package language-pack-ja

そこで上記エラーを検索しまくり、やっと以下の記事にたどり着いた。
https://www.atmarkit.co.jp/ait/articles/1806/28/news043.html

結論としては、先に [apt update] をすることで、[language-pack-ja-base]らがインストール可能になる。

root # apt update
root # apt-get install language-pack-ja-base language-pack-ja
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Docker】dbコンテナに入り、データを見ると日本語が???で表示される

環境:Mac
   Laravel 6.x系
   Mariadb

https://qiita.com/n_oshiumi/items/cfe91c60730f602b38eb
こちらの記事を参考にしましたが、私の場合は以下のエラーが発生し、解決できなかった。

E: Unable to locate package language-pack-ja-base
E: Unable to locate package language-pack-ja

そこで上記エラーを検索しまくり、やっと以下の記事にたどり着いた。
https://www.atmarkit.co.jp/ait/articles/1806/28/news043.html

結論としては、先に [apt update] をすることで、[language-pack-ja-base]らがインストール可能になる。

root # apt update
root # apt-get install language-pack-ja-base language-pack-ja
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCodeのRemote ContainersでGoの開発環境を構築

はじめに

VSCodeの拡張機能である、Remote Containersを使って、Goの開発環境を構築したら快適だったので、紹介します。
尚、VSCodeおよび、Docker Desktopのインストール方法については割愛します。

Remote Containers

Remote Containersとは、VSCodeのリモート開発機能のうち、Dockerに特化した拡張機能です。
他にもSSH経由でリモート接続する「Remote SSH」、WSL(Windows Subsystem for Linux)を使用してリモート接続する「Remote WSL」があるようです。

ローカルのVSCodeからVSCodeServerを介して、Dockerコンテナにリモート接続することで、ローカル環境とは切り離された環境で開発をすることが可能です。
つまり、Docker上で、全てが完結するため、コード補完などの恩恵を受けつつ、ローカル環境を汚さなくて良い素晴らしい機能です。
公式サイト
※2020年10月時点ではプレビュー版となります。

インストール

VSCodeのExtentionsからRemote Containersを検索してインストールします。
image.png

初期設定

Ctrl + Shift + Pでメニューを出して、
Remote-Containers:Add Development Cotainers Configuration Files...を選択します。
image.png

私の環境ではすでにdocker-compose.yml及びDockerFileで環境を構築しているため、そちらの設定を元に初期設定を行うことができます。
また、From a predefined container configuration definition...を選択すると、あらかじめ用意されている、設定を使用することも可能です。
今回は、既存のdocker-composeファイルを使用して初期設定を行います。
image.png

image.png

.devcontainer

初期設定をすると.devcontainerフォルダが自動的に作成され、配下に
devcontainer.json
docker-compose.yml
が自動生成されます。
devcontainer.jsonが設定ファイルになります。

ここで、現在のディレクトリ構造は以下のようになりました。

- .devcontainer
 ├ devcontainer.json
  └ docker-compose.yml
- sample
  └ ソースコード
- docker-compose.yml
- docker-compose.dev.yml
- Dockerfile 
- Dockerfile.dev

自動生成されたファイルは以下。

devcontainer.json
// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
{
    "name": "Existing Docker Compose (Extend)",

    // Update the 'dockerComposeFile' list if you have more compose files or use different names.
    // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
    "dockerComposeFile": [
        "..\\docker-compose.dev.yml",
        "docker-compose.yml"
    ],

    // The 'service' property is the name of the service for the container that VS Code should
    // use. Update this value and .devcontainer/docker-compose.yml to the real service name.
    "service": "sample",

    // The optional 'workspaceFolder' property is the path VS Code should open by default when
    // connected. This is typically a file mount in .devcontainer/docker-compose.yml
    "workspaceFolder": "/workspace",

    // Set *default* container specific settings.json values on container create.
    "settings": {
        "terminal.integrated.shell.linux": null
    },

    // Add the IDs of extensions you want installed when the container is created.
    "extensions": []

    // Use 'forwardPorts' to make a list of ports inside the container available locally.
    // "forwardPorts": [],

    // Uncomment the next line if you want start specific services in your Docker Compose config.
    // "runServices": [],

    // Uncomment the next line if you want to keep your containers running after VS Code shuts down.
    // "shutdownAction": "none",

    // Uncomment the next line to run commands after the container is created - for example installing curl.
    // "postCreateCommand": "apt-get update && apt-get install -y curl",

    // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
    // "remoteUser": "vscode"
}
docker-compose.yml
version: '3'
services:
  # Update this to the name of the service you want to work with in your docker-compose.yml file
  sample:
    # If you want add a non-root user to your Dockerfile, you can use the "remoteUser"
    # property in devcontainer.json to cause VS Code its sub-processes (terminals, tasks, 
    # debugging) to execute as the user. Uncomment the next line if you want the entire 
    # container to run as this user instead. Note that, on Linux, you may need to 
    # ensure the UID and GID of the container user you create matches your local user. 
    # See https://aka.ms/vscode-remote/containers/non-root for details.
    #
    # user: vscode

    # Uncomment if you want to override the service's Dockerfile to one in the .devcontainer 
    # folder. Note that the path of the Dockerfile and context is relative to the *primary* 
    # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile"
    # array). The sample below assumes your primary file is in the root of your project.
    #
    # build:
    #   context: .
    #   dockerfile: .devcontainer/Dockerfile

    volumes:
      # Update this to wherever you want VS Code to mount the folder of your project
      - .:/workspace:cached

      # Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker-compose for details.
      # - /var/run/docker.sock:/var/run/docker.sock 

    # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
    # cap_add:
    #   - SYS_PTRACE
    # security_opt:
    #   - seccomp:unconfined

    # Overrides default command so things don't shut down after the process ends.
    command: /bin/sh -c "while sleep 1000; do :; done"

※一旦ファイルは生成されたものの、私は、docker-compose.ymlの2重管理になってしまうのが嫌だったので、.devcontainer配下のdocker-compose.ymlに全てを移しました。

Go向け設定

Goを開発するための設定を加えていきます。

extensions

ローカルにインストールしたVSCodeの拡張機能は、リモートコンテナ内部では使用できないので、コンテナ起動時に、自動的にインストールする拡張機能を設定します。
Goの拡張機能を入れます。この文字列は、拡張機能一覧で右クリックCopy Extension Idから取得できます。

devcontainer.json
    "extensions": [
        "golang.go"
    ]

settings

goplsを使用するので、githubを参考に、設定を追加します。
Gomod設定(GO111MODULE)に関しては、1.13以降不要となりましたが、設定しないとimport文にエラーが出てしまうので、設定します。

devcontainer.json
"settings": {
        "terminal.integrated.shell.linux": "/bin/bash",
        "go.useLanguageServer": true,
        "[go]": {
            "editor.formatOnSave": true,
            "editor.codeActionsOnSave": {
                "source.organizeImports": true,
            },
            // Optional: Disable snippets, as they conflict with completion ranking.
            "editor.snippetSuggestions": "none",
        },
        "[go.mod]": {
            "editor.formatOnSave": true,
            "editor.codeActionsOnSave": {
                "source.organizeImports": true,
            },
        },
        "gopls": {
            // Add parameter placeholders when completing a function.
            "usePlaceholders": true,
            // If true, enable additional analyses with staticcheck.
            // Warning: This will significantly increase memory usage.
            "staticcheck": false,
        },
        "go.toolsEnvVars":{
            "GO111MODULE":"on"
        }
    }

workspaceFolder

リモートコンテナ起動時のワークスペースを設定します。
docker-compose.yml側で、ボリュームを/go/src/配下にマウントするので、ワークスペースもそれに合わせて変更します。

devcontainer.json
"workspaceFolder": "/go/src/sample",

以上で設定は終了です。

起動

実際にコンテナを起動します。
Ctrl+Shift+Pから、Remote-Containers:Reopen in Containerを選択

image.png

初回起動時では、ツール類が/go/binにインストールされていないのでインストールします。

goplsを使用するので、以下の通知からインストールするか、
go get -v golang.org/x/tools/goplsを実行します
image.png

その他ツールに関しては、Go:Install/Update Toolsからgocodegocode-gomod以外をインストールします。
image.png

image.png

/go/bin配下に全てインストールされていることが確認できました。

image.png

ここまでで、コンテナ上で開発することができるようになります。
当然ながら、コンテナ単位での管理となるため、コンテナの削除を行うと、インストールしたものについては、消えるので、再設定が必要となります。

デバッグ

コンテナ内でのデバッグも可能です。
マウントした、ソースコードと同じディレクトリに.vscodeフォルダを作成して、launch.jsonを作成します。

launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Remote",
            "type": "go",
            "request": "launch",
            "host": "localhost",
            "program": "${workspaceRoot}",
            "args": []
        }
    ]
}

まとめ

最終的に作成したファイルを記載しておきます。何かのご参考になれば幸いです。

devcontianer.json
{
    "name": "Existing Docker Compose (Extend)",
    "dockerComposeFile": [
        "docker-compose.yml"
    ],
    "service": "sample",
    "workspaceFolder": "/go/src/sample",
    "settings": {
        "terminal.integrated.shell.linux": "/bin/bash",
        "go.useLanguageServer": true,
        "[go]": {
            "editor.formatOnSave": true,
            "editor.codeActionsOnSave": {
                "source.organizeImports": true,
            },
            "editor.snippetSuggestions": "none",
        },
        "[go.mod]": {
            "editor.formatOnSave": true,
            "editor.codeActionsOnSave": {
                "source.organizeImports": true,
            },
        },
        "gopls": {
            "usePlaceholders": true,
            "staticcheck": false,
        },
        "go.toolsEnvVars":{
            "GO111MODULE":"on"
        }
    },
    "extensions": [
        "golang.go"
    ]
}
docker-compose.yml
version: '3'
services:
  # Update this to the name of the service you want to work with in your docker-compose.yml file
  sample:
    image: golang:1.14
    volumes:
      - ./../:/go/src/:cached
    tty: true
    environment:
      - MYSQL_CONNECTION_STRING=${CONNECTION_STRING}
    networks:
      - default
      - db-network
networks:
  db-network:
    external: true

参考

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

docker-composeで Nuxt TypeScript + Vuetify 環境を構築

はじめに

普段Laravelを触ることが多く、イチからフロントエンドの環境をDockerで構築したことがなかったので挑戦してみました。
docker-composeでNuxt TypeScript+Vuetifyなフロントエンド環境を構築します。
最終的に、モノリポでバックエンドとしてlaravelも同じリポジトリに載せる予定です。
Dockerとdocker-composeがインストールされているものとします。

実行環境

  • macOS Catalina ver 10.15.6
  • Docker ver 2.4.0
  • docker-compose ver 1.27.4

ディレクトリ構成

docker-compose.yml
nuxt/
docker/
└ app/
  └ Dockerfile

docker-compose

docker-compose.yml
version: "3"
services:
  app:
    build:
      context: ./
      dockerfile: ./docker/app/Dockerfile
    ports: 
      - "3000:3000"
    command: yarn run dev
    volumes:
      - ./nuxt:/nuxt

Dockerfile

FROM node:14.4.0-alpine

ENV HOME=/nuxt \
    LANG=C.UTF-8 \
    TZ=Asia/Tokyo \
    HOST=0.0.0.0

# Vuetifyのインストールに必要
RUN apk update && \
    apk upgrade && \
    apk add --no-cache make gcc g++ python


WORKDIR /nuxt

EXPOSE 3000

ビルド

$ docker-compose build

Nuxt、Vuetifyのインストール

$ docker-compose run --rm app yarn create nuxt-app nuxt
...
...
create-nuxt-app v3.4.0
✨  Generating Nuxt.js project in nuxt
? Project name: `nuxt`
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: Vuetify.js
? Nuxt.js modules: Axios (<= スペースキーを押さないと選択されないので注意)
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Testing framework: Jest
? Rendering mode: Single Page App
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: None
? Version control system: None

各種質問が出てくるので、上記のようにUI frameworkの欄でVuetify.jsを選ぶ。
スペースを押さないと選択されない項目があるので注意。
インストールが終わるとnuxtディレクトリが以下のようになる

nuxt
 ┣ .config/
 ┗ nuxt/
   ┣ asset/
   ┣ ...
   ┣ ...
   ┣ package.json
   ┗ yarn.loc

ので、nuxt/nuxt/* をnuxt/に移動して以下のようにする

nuxt
 ┣ .config/
 ┣ asset/
 ┣ ...
 ┣ ...
 ┣ package.json
 ┗ yarn.loc

起動

$ docker-compose up -d

http://localhost:3000/
にアクセスして下の画像のように表示されればとりあえず完了!
スクリーンショット 2020-10-13 10.57.19.png

tsconfig.jsonの設定などあるかもしれませんが、次はLaravelのSanctumとSPA認証の記事で書く予定…

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

DockerイメージをビルドしてECRにpushするshell script

いちいちビルドしてタグ付けしてecrにログインしてpushするのが面倒だったので、1コマンドでやってくれるスクリプト書きました。

普段そんなに書かないのでShell書くときのConventionみたいなの知らなくて、変な書き方してる部分もあるかも。
気が向いたらご指摘いただけると嬉しいです。

書いたやつ

#!/bin/sh

# Returns boolean indicates whether designated tagged-image exists.
  # arg1: repository name
  # arg2: tag name
function image_exists() {
  image=$(docker image ls -a | grep $1 | grep $2)

  if [ "$image" ]; then
    return 0;
  else
    return 1;
  fi
}

# Builds new image.
  # arg1: tag in the form like ${repository}:${tag}
  # arg2: Path to Dockerfile
function build_image() {
  docker build -t $1 -f $2 .
}

# Work on master branch
git checkout master

# Get latest master revision
revision=$(git rev-parse --short HEAD)

echo "Current master revision is ${revision}\n"

# Set constants
readonly repository=your-repository-name
readonly ecr_repository=************.dkr.ecr.<region>.amazonaws.com
readonly revised_repository=$repository:$revision
readonly ecr_revised_repository=$ecr_repository/$revised_repository
readonly path_to_dockerfile=<relative-path-to-dockerfile>

echo "local-repository: ${repository}
ecr-repository: ${ecr_repository}
local-revised-repository: ${revised_repository}
ecr-revised-repository: ${ecr_revised_repository}\n"

# Build current source if revision not exists
if image_exists $repository $revision; then
  # Do nothing.
  echo "local-revised-repository already exists. Skip build.\n"
  true;
else
  echo "Start building local-revised-repository."
  build_image $revised_repository $path_to_dockerfile;
fi

# Generate ecr-repository-tagged image if not exists
if image_exists $ecr_repository $revision; then
  # Do nothing.
  echo "ecr-revised-repository already exists. Skip tagging.\n"
  true;
else
  docker tag $revised_repository $ecr_revised_repository;
fi

# 必要に応じてこのへんでAWSの認証情報を設定する

# Login to ECR
aws ecr get-login-password --region <region> \
  | docker login --username AWS --password-stdin $ecr_repository

# Push new image to ECR
docker push $ecr_revised_repository

echo "Done.\n"

実装要件

  • latestっていうタグ付けをすると、あとから見た時にいつのビルドなんだか分からなくなるので、リリース時点のgitリビジョンを利用する
    • 注意点: リリースブランチのリビジョンを取るべきなので、必要に応じてmaster部分を変更してください
  • 指定されたリビジョンがタグ付けされたイメージがまだ存在していなければビルドする
    • 存在していればビルドをスキップする
  • 指定されたリビジョンがタグ付けされたECR向けのイメージがまだ存在していなければタグ付けする
    • 存在していればタグ付けをスキップする
  • ECRにログインする
    • 注意点: ECRログインする前に、そのECRが存在するAWSアカウントを操作できる認証情報を取得しておくこと。
    • profileがあるならそれを読み込む。
    • 自分の場合、MFAで一時的な認証情報を取得する必要があるため、その部分を書いてある
  • ビルドされたイメージをECRにpushする

おまけ: MFA認証

これはこれでめんどくさかったので、誰かの役に立てば。

# 標準入力からmfaコード入力を受け付ける
read -p "Input mfa code: " mfaCode

result=$(aws sts assume-role \
  --role-arn arn:aws:iam::************:role/<role-name> \
  --role-session-name <session-name> \
  --serial-number <mfa-serial> \
  --token-code $mfaCode --profile <profile-name>)

export AWS_ACCESS_KEY_ID=$(echo $result | jq ".Credentials.AccessKeyId" -r)
export AWS_SECRET_ACCESS_KEY=$(echo $result | jq ".Credentials.SecretAccessKey" -r)
export AWS_SESSION_TOKEN=$(echo $result | jq ".Credentials.SessionToken" -r)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-compose upでコンテナが立ち上がらずエラーが出たときの話

前日まで使えていたのに、ある日いきなりdockerが立ち上がらなくなった。(PCアップデートしたせい??)

最初にでたのはこんな感じのエラー

 Could not find ancestry-3.1.0 in any of the sources
 Run `bundle install` to install missing gems.

上記のgemはgemfile.lockに載っているし、エラーが出るのはおかしいと思いつつもdocker-compose run web bundle installしてみたけど案の定同じエラーで動かない。

検索してみるとこんな記事があった。
https://teratail.com/questions/291124
試しにdocker-compose build --no-cacheをやってみると次は以下のエラーが

db uses an image, skipping
Building web
Traceback (most recent call last):
  File "site-packages\docker\utils\build.py", line 96, in create_archive
OSError: [Errno 22] Invalid argument: '\\ディレクトリ名\\¥アプリ名\\node_modules\\.bin\\acorn'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "docker-compose", line 3, in <module>
  File "compose\cli\main.py", line 67, in main
  File "compose\cli\main.py", line 126, in perform_command
  File "compose\cli\main.py", line 302, in build
  File "compose\project.py", line 468, in build
  File "compose\project.py", line 450, in build_service
  File "compose\service.py", line 1125, in build
  File "site-packages\docker\api\build.py", line 160, in build
  File "site-packages\docker\utils\build.py", line 31, in tar
  File "site-packages\docker\utils\build.py", line 100, in create_archive
OSError: Can not read file in context: \\?\ディレクトリ名\アプリ名\node_modules\.bin\acorn
[24060] Failed to execute script docker-compose

上記のエラーで調べてみると、この記事にたどり着いた。
https://crieit.net/posts/docker-compose-build-Can-not-read-file
記載されている通り、.dockerignoreを作成しnode_modulesと記載すればdocker-compose buildが動くように!

エラーで検索した際にdockerのバージョンの問題という記事も見つけたのですが、自分のバージョンと違うようだったので謎が深まりました...無事解決してよかったです。
https://github.com/docker/compose/issues/5888

今回エラーが起きたバージョン

docker-compose version 1.27.4, build 40524192
Docker version Version:  19.03.13
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Db2コンテナをGoで操作する

こんにちは。
今回はDockerで立てたDb2コンテナをGoで操作する方法について紹介します。
Db2コンテナの立て方や、セットアップ時にデータを挿入する方法については、以下の記事で紹介しておりますので、ぜひ参考にしてください。

Db2のDBコンテナを立ててちょっとしたデータを挿入してみる
Db2/DBコンテナに初期テストデータを挿入した状態でセットアップ

今回は、Db2コンテナをデータを挿入した状態で立ち上げ、Goでの実装を中心に紹介します。

コード類はこちらに載せています。

概要

Db2コンテナにデータは挿入できたけど、実際にそのデータを取得してきて操作したり、データを更新したりするにはどうするんだという方向けの内容となっています。

今回はGo言語でDb2からデータを取ってくる方法をご紹介しようと思います。

開発環境

  • Win10 Enterprise
  • docker (v19.03.13)
  • ibmcom/db2 (v11.5.4.0)
  • Go (v1.12.9)
  • Git Bash (git version 2.27.0.windows.1)

前提

  • ibmcomのDb2コンテナの立ち上げ方がある程度分かる
  • Go言語がある程度わかる

Getting Started

開発環境はWindowsですが、MacでもLinuxでもできます。

今回は、Db2との疎通確認に重きを置いておりますので、API化などは行っていません。
単純にDb2からデータを取ってきて、コンソールに出力するだけのプログラムを書いていきます。(いつかGoでREST APIの実装も紹介します。)

1. フォルダ構成の説明

まずはフォルダ構成を説明します。

project
project
├─go
|  ├─model
|  |     ├─user.go
|  |     ├─tweet.go
|  |     └─reply.go
|  └─main.go
└─db  
   ├─data
   |    ├─users_insert.csv
   |    ├─tweets_insert.csv
   |    └─replys_insert.csv
   ├─sql
   |   ├─users_create.sql
   |   ├─tweets_create.sql
   |   └─replys_create.sql
   ├─createschema.sh
   ├─Dockerfile
   └─env.list
  • /go
    • /model
    • user.go ユーザーのDTOとDAO
    • tweet.go ツイートのDTOとDAO
    • reply.go
      リプライのDTOとDAO
    • main.go
      メインの関数
  • /db: データベースをセットアップするフォルダ
    • /data
      初期にデータベースに登録するテストデータのフォルダ
    • /sql
      テーブル作成するSQL文のフォルダ
    • createschema.sh
      データベースセットアップ時に呼ばれるテーブル作成用スクリプト
    • Dockerfile
      コンテナ定義
    • env.list
      Db2コンテナ用の構成情報

本当は、ドメイン駆動設計とかで、ユーザードメインとか、インフラストラクチャとか作ってカッコいい設計をしたいんですが、それはまたの機会ということで。

2. コンテナの立ち上げ

まずはDockerfileを用いて、コンテナイメージをビルドします。
実行するコマンドは以下です。

$ cd db
$ docker build -t test-db:v1.0 .

これで、コンテナイメージが出来上がるので、早速runしていきます。

$ docker run --name go-db --restart=always --detach --privileged=true -p 50000:50000 --env-file env.list test-db:v1.0

詳しい説明はこちらで紹介しています。

ここで大事なのはポートを50000:50000でポートフォワーディングしていることです。
クライアントに公開している50000ポートはDBと接続する時に指定する必要があるので、覚えておきます。

3. インポートするパッケージ

利用するパッケージ
* github.com/ibmdb/go_ibm_db
* github.com/pkg/errors

3.1. go_ibm_db

基本的にGoでDb2を利用する際は、github.com/ibmdb/go_ibm_dbというパッケージを利用します。

以下のコマンドを叩きます。

$ go get github.com/ibmdb/go_ibm_db

またデータベースを操作するにあたって、SQLを操作するためのドライバが必要になります。
色々操作があるので順にやります。

まず、落としてきたgithub.com/ibmdb/go_ibm_dbを見に行きます。
おそらくGOPATH配下に落とされていると思うので、こちらの階層を下ると、installerというフォルダにぶち当たります。
このフォルダ内setup.goがclidriverのダウンロードスクリプトになっています。

$ cd PathToInstaller/installer
$ go run setup.go

これでclidriverがinstaller配下にダウンロードできます。(パーミッションエラーが起きた方は、installerフォルダの権限を変えてみてください。)
結構時間がかかる気がします。

無事落とせてこれた方はPathToInstaller/installer/clidriver/binのパスを通す必要があるので、通しましょう。
これでgo_ibm_dbのセットアップは完了です。

もし余計なパッケージを環境に落としたくないという方は、go modでもできます。
しかしその場合も、sqlcli.hは必要になりますので、インストールしてきたinstallerをプロジェクトにコピーしてきて、、シェルスクリプトなどで、clidriver/binのパスを通し、moduleを指定してビルドすることで実行ファイルを生成できます。

3.2. errors

また、エラーの実装もするので、errorsパッケージも落としましょう。

$ go get github.com/pkg/errors

4. Goの実装

基本的に実装は本当に3で紹介した通りです。
main.goのmain関数を見ながら紹介します。

まずこのコード

main.go
  config := "HOSTNAME=localhost;DATABASE=USERDB;PORT=50000;UID=db2inst1;PWD=password"
    conn, err := sql.Open("go_ibm_db", config)
    if err != nil {
        fmt.Printf("DBとの接続に失敗しました。%+v", err)
    }
    defer conn.Close()

configにDB接続情報を格納します。HOSTNAMEとPORT以外はenv.listに乗せてある情報を使います。
その下のsql.OpenでDBとのコネクションを張ります。
一つ目の引数はドライバ名を指定します。今回はgo_ibm_dbです。
二つ目の引数はDB接続情報を指定します。エラーを取りうるので、エラー処理もかかせず行います。
コネクションは必ず終了する必要があるので、Goのプラクティスであるdeferを使ってコネクションを閉じましょう。

これでDb2コンテナとのコネクションが取得できました。
これを利用してデータを操作していきます。

まずはユーザーを全件取得して、情報をユーザー構造体に格納し、インスタンスの配列を作っています。

main.go
users, err := model.GetAllUser(conn)
if err != nil {
  fmt.Printf("取得に失敗 %+v", err)
}

ではユーザーDAOとDTOを定義しているuser.goを見ていきます。

user.go
// User is users entity
type User struct {
    id        string
    name      string
    mail      string
    password  string
    createdAt time.Time
    updatedAt time.Time
}

func (u *User) String() string {
    return fmt.Sprintf(
        "ユーザー名:%s",
        u.name,
    )
}

// GetID returns user's id
func (u *User) GetID() string {
    return u.id
}

ユーザー構造体はテーブル定義のカラムをフィールドに定義しています。
GetIDメソッドはユーザーのIDを取得するメソッドです。これは他のテーブルのクエリにIDを渡すためにユーザー構造体のフィールドがプライベートに指定されているため、書いています。
まぁここら辺は他の言語でも似たようなことやると思います。

その下、ユーザー全件取得メソッドですが、

user.go
// GetAllUser returns all user instances
func GetAllUser(conn *sql.DB) ([]User, error) {
    selectAllUserQuery := `SELECT * FROM users`

    selectAllUserPstmt, err := conn.Prepare(selectAllUserQuery)
    if err != nil {
        return []User{}, errors.Wrapf(err, "ステートメントの作成に失敗しました")
    }

    var users []User

    rows, err := selectAllUserPstmt.Query()
    if err != nil {
        return []User{}, errors.Wrap(err, "クエリ実行に失敗")
    }
    for rows.Next() {
        var user User
        if err := rows.Scan(
            &user.id,
            &user.name,
            &user.mail,
            &user.password,
            &user.createdAt,
            &user.updatedAt,
        ); err != nil {
            return []User{}, errors.Wrap(err, "結果読み込み失敗")
        }
        users = append(users, user)
    }
    return users, nil
}

ここは色んな書き方があるんですが、Prepare()メソッドでステートメントを用意してから、queryを実行する方法で書きます。

これを実行すると、取れてきたレコードがrowsに格納されます。
rowsはNextメソッドを持っていて、for文でそれぞれのレコードを回すことができます。
さらにrows.Scan()にユーザーインスタンスの情報を渡してあげると、そこにレコードの情報を格納してくれます。

これで、ユーザー情報をユーザーインスタンスに格納することができました。
ユーザーの配列を返します。

それではmainに戻ります。

次からはユーザーインスタンスからIDを取ってきて、TweetのWHERE句に渡して挙げて、ユーザーに紐づくレコードを取ってきています。
取ってきたtweetレコードからさらにIDを取ってきて、それに紐づくReplyを取得し出力、それをユーザーレコード分行うといった処理をしています。

main.go
// 件数少ないので3重for文で。
    for _, user := range users {
        fmt.Println(user.String())
        tweets, err := model.GetAllTweets(conn, user.GetID())
        if err != nil {
            fmt.Printf("取得に失敗 %+v", err)
        }
        for _, tweet := range tweets {
            fmt.Println(tweet.String())
            replys, err := model.GetAllReplys(conn, tweet.GetID())
            if err != nil {
                fmt.Printf("取得に失敗", err)
            }
            for _, reply := range replys {
                fmt.Println(reply.String())
            }
        }
    }

WHERE句にIDを渡すためにはSQL文をSELECT * FROM Tweets WHERE user_id = ?のように与えたいパラメータの箇所を?とします。
パラメータ分第2引数を与えることで、WHERE句をカスタムできます。

書き方は、
rows, err := selectAllTweetPstmt.Query(userID)
このような形です。

5. 実行結果

Windowsで実行すると、コンテナから値を受け取ってくる段階で、日本語箇所は文字化けして表示されてしまいます。
Db2で用いているコンテナがLinuxコンテナなので、文字コードがUTF-8のまま文字列が送られてくることに起因していると思われます。

実行結果は以下のようになります。

ユーザー名:hoge
ツイート本文:�����̓e�X�g�ł��B, 作成日:2020-10-09 12:00:00 +0900 JST
リプライユーザー名:fugaaaa, リプライ本文:�e�X�g�m�F���܂����B, 作成日:2020-10-11 12:00:00 +0900 JST
-----------------------
ユーザー名:fuga
ツイート本文:�����̓e�X�g�ł��B, 作成日:2020-10-10 12:00:00 +0900 JST
リプライユーザー名:hogeeee, リプライ本文:�e�X�g�m�F���܂����B, 作成日:2020-10-11 12:00:00 +0900 JST
-----------------------

まぁめっちゃ文字化けしてますね。
悲しいです。
このままだとあれなんで、Macで実行した結果も載せときます。

ユーザー名:hoge
ツイート本文:これはテストです。, 作成日:2020-10-09 12:00:00 +0900 JST
リプライユーザー名:fugaaaa, リプライ本文:テスト確認しました。, 作成日:2020-10-11 12:00:00 +0900 JST
-----------------------
ユーザー名:fuga
ツイート本文:これはテストです。, 作成日:2020-10-10 12:00:00 +0900 JST
リプライユーザー名:hogeeee, リプライ本文:テスト確認しました。, 作成日:2020-10-11 12:00:00 +0900 JST
-----------------------

こんな感じで、Db2から取得できています。

6. まとめ

文字コードの弊害がありながらも、GoでDb2コンテナに接続する手法を紹介しました。

これでAPI開発とか楽に行えますね。

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