20191023のdockerに関する記事は14件です。

dockerでnginxを起動しようと思ったら、80番ポートがすでに使われていて起動できなかった

  • まずは80ポートを何が掴んでいるのか確かめる
$ sudo lsof -i :80 

これで,80ポートを占領しているものが見れる↓
スクリーンショット 2019-10-23 22.51.42.png

どうやら80番ポートがnginxに大量に掴まれている様子

  • killしてみる
$ kill (PID)
# ここでいうと Kill 3040 とか

これで狙って起動している物を消せる

が、

俺のはなぜか無限に出てくる...

  • 効率よく消すにはこれがいい
$ kill $(lsof -t -i:80)

これで$sudo lsof -i :80で出てきた一覧にある15個ほどのポートは消せる。
ただ、僕の場合は消してもまた同じ量80番ポートが掴まれていた
何度消しても何度も出てくる...

色々調べてみた結果

これ↓でnginx全てを落とせるらしい

$ sudo nginx -s stop

ようやく全て消えたので、dockerでnginxを立ち上げてみると起動できた。
一度dockerを落としてもう一度起動してみても問題なく起動できた!
理由がなんだかわからないが、ひとまず動いたので良しとします!笑

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

【メモ】Rails APIモード × MySQL5.7 × Docker で環境構築

はじめに

久しぶりにDockerを作成したら所々躓いたので要点をメモ

手順

  1. ディレクトリ用意&移動
  2. Gemfile, Gemfile.lock, Dockerfile, docker-compose.ymlを作成
  3. 2.のファイルにコード記述
  4. $ docker-compose build
  5. $ docker-compose run app rails new --api . --force --no-deps --database=mysql
  6. config/database.ymlを修正
  7. $ docker-compose build --no-cache
  8. $ docker-compose up
  9. $ docker-compose run app rails db:create

image.png

手順3, 6で用意するファイルについて

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

source "https://rubygems.org"
gem 'rails', '6.0.0'
Dockerfile
FROM ruby:2.6.5
ENV LANG C.UTF-8

# install required libraries
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

RUN gem install bundler -v 2.0.2

# install bundler
RUN gem install bundler

WORKDIR /tmp
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock
RUN bundle install

WORKDIR /app
COPY . /app
docker-compose.yml
version: '3'
services:
  mysql:
    image: mysql:5.7
    command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
    environment:
      MYSQL_DATABASE: app_development
      MYSQL_USER: root
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
  app:
    tty: true
    stdin_open: true
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - mysql
volumes:
  mysql-data:
    driver: local
config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: mysql

development:
  <<: *default
  database: app_development

test:
  <<: *default
  database: app_test

# 一例です。productionについてはデプロイする際に注入する環境変数を適宜用意して下さい。
production:
  <<: *default
  username: <%= ENV['MYSQL_USER'] %>
  password: <%= ENV['MYSQL_ROOT_PASSWORD'] %>
  database: <%= ENV['MYSQL_DATABASE'] %>
  host: <%= ENV['MYSQL_HOST'] %>
  socket: <%= ENV['MYSQL_SOCKET'] %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker超入門

コンピュータに詳しくない自分が急にDockerを使う必要が出てきたときのノートです.
同じような状況に陥った人の参考になれば幸いです.

超入門

dockerおよびnvidia-dockerはinstall済みの場合を想定します.
難しいことを考えずにただ使うでけだと, 以下の3つのステップで大丈夫です.

  1. Docker Imageを用意する.
  2. Docker Imageを使って, Docker Containerを起動する.
  3. 起動したContainer内に入って作業する.

Docker Imageを用意する.

Docker Imageを用意する方法は, 自分が知っている限り以下の2つあります.

  1. DockerfileでImageをbuildする.
  2. 既存のDocker Imageをpullしてくる.

Dockerfileが既に用意されている場合, 以下のコマンドでImageをbuildできます.

$ docker image build -f ./Dockerfile -t <repository name:tag name> .

Dockerfileがない場合は, Dockerhubで自分の作業に必要なDocker Imageをpullできます.
DockerhubにあるImageは公式のものほど動作が保証されているので安心して使えます.
例えば, nvidia/cudaのGPU環境が必要な場合は,

$ docker pull nvidia/cuda

のコマンドだけでImageをpullできます.
逆に自分がbuildしたImageをdockerhubにpushしておけば, 違うマシンでImageをbuildし直す必要がないので非常に便利です.

用意したDocker Imageは,

$ docker image ls

で確認できます.

Containerの起動

作成したDocker Imageを使って, Docker Containerを起動します.

$ docker container run -it -p <host:cont> -v <host dir:cont dir> --name <container name> -d <image name>

GPU環境のcontainerを起動する場合は, dockernvidia-docker に変えてください.
-p はポートフォワード, -v はdirectoryのmountのオプションなのでよしなに設定してください.

起動したcontainerは,

$ docker ps -a

で確認できます. -aを入れると, stopしているcontainerも確認できます.

docker container内に入る

$ docker exec -it <container id or name> bash

これだけです. ここまでで, dockerよくわかってないけど作業できる状態までなります!

使用例

自分はjupyterで作業したかったので, containerを起動する時に-p 8000:8888と設定して,
container内で以下のコマンドでjupyterを立ち上げます.

$ jupyter notebook --ip=0.0.0.0 --allow-root

もしくはjupyterのconfigを設定するのが良いかもしれないです.

最後に

docker, 使うだけなら簡単ですね!!
当然dockerはもっと便利なツールなので, もう少し勉強します. Dockerfileの書き方とか, docker-composeの使い方辺りかな.

Qiita初投稿なので, 内容だけでなくマナー的なところでご指摘あればよろしくお願いします.

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

Dockerfile作成からDocker-composeまでの流れ

前提:Dockerがインストールされている環境

Docker Composeのインストール
Dockerがインストールされている環境で以下を実行
$ curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose

Dockerfile を作成
docker build する為の手順を書いたファイル

FROM python:3.6

RUN mkdir /code
VOLUME /code

ADD main.py /code/
ADD requirements.txt /code/

WORKDIR /code
RUN apt-get update 
RUN pip install --upgrade "pip<10" \
&& pip install flask \
&& pip install -r requirements.txt

CMD ["python", "main.py"]

Docker-compose.yml を作成
複数のコンテナを組み合わせて1つのアプリケーションとして構成を定義するファイル

docker-compose.yml
version: '2'

services:
    flask:
      build:
      ports:
        - "5000:5000"
      volumes:
        - ".:/code"

実行
$ docker-compose up -d で、Dockerfile から独自のイメージをビルドし、
コンテナを起動してくれる。

参考
Python+Flask環境をDockerで構築する
Docker | docker build と Dockerfile でイメージをビルドする基本
Dockerfileを書いてみる
Docker Compose - docker-compose.yml リファレンス
Docker入門(第六回)〜Docker Compose〜

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

Ansible Playbook検証環境構築2 (2019版)

目的

Ansible Playbook検証環境構築 (2019版)では、ローカルに色々導入したので、ローカルにあまり多くを導入しない様に内容を更新した。
ansible controllerもdockerコンテナとし、検証環境もdockerとしてKitchenCIを回す

環境

MAC
macOS 10.14.6

ローカルに以下を導入

環境変数設定

$ echo 'eval "$(chef shell-init bash)"' >> ~/.bash_profile
$ . ~/.bash_profile

ローカルに導入するgem

  • kitchen-ansible (0.50.0)
  • kitchen-docker (2.9.0)
$ gem install kitchen-ansible
$ gem install kitchen-docker

環境構築

ansible controllerもkitchenCIで導入する
(なお、DockerFileでも可能と思える)
その為、以降はcdやviなどの基本的なコマンド以外では、kitchenコマンドのみで完結。

$ mkdir ansible_controller
$ cd ansible_controller
$ vi kitchen.yml
...
$ vi site.yml
...
kitchen.yml
---
driver:
  name: docker
  run_options: >-
    -v /var/run/docker.sock:/var/run/docker.sock
    -v /usr/local/bin/docker:/bin/docker

provisioner:
  name: ansible_playbook
  chef_bootstrap_url: nil
  hosts: ansible-controller
  playbook: site.yml

platforms:
- name: centos-7

suites:
- name: ansible-controller
  • /var/run/docker.sockを共有することで、dockerコンテナからdockerの操作を可能とする。
    • この場合、コンテナで制御を奪われるとベースOSも制御を奪われるので要注意
  • kitchen-ansible は対象にansibleを導入し、ローカルモードで実行する。
    • この導入されたansibleをそのまま利用する。
  • -v を追加し、ローカルのansible repoをansible controllerコンテナと共有させることも可能

  • centos-7を対象としている。ubuntu-16.04 とした場合、幾つか修正が必要となる可能性がある。

site.yml
---
- hosts: all
  gather_facts: false
  tasks:

  - name: install the chef-workstation rpm from a remote repo
    yum:
      name: https://packages.chef.io/files/stable/chef-workstation/0.9.42/el/7/chef-workstation-0.9.42-1.el7.x86_64.rpm

  - name: add 'eval "$(chef shell-init bash)"' to ~/.bash_profile
    lineinfile:
      dest: ~kitchen/.bash_profile
      line: eval "$(chef shell-init bash)"
    become: true
    become_user: kitchen

  - name: gem install
    shell: |
      set -e
      eval "$(chef shell-init bash)"
      gem install kitchen-ansiblepush
      gem install kitchen-docker
    become: true
    become_user: kitchen

ansible controllerのdockerコンテナ起動、作成

$ kitchen test -d never
-----> Starting Kitchen (v1.25.0)
-----> Cleaning up any prior instances of <ansible-controller-centos-7>

...

       PLAY [all] *********************************************************************

       TASK [install the chef-workstation rpm from a remote repo] *********************
       ok: [localhost]

       TASK [add 'eval "$(chef shell-init bash)"' to ~/.bash_profile] *****************
       changed: [localhost]

       TASK [gem install] *************************************************************
       changed: [localhost]

       PLAY RECAP *********************************************************************
       localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

       Downloading files from <ansible-controller-centos-7>
       Finished converging <ansible-controller-centos-7> (5m22.82s).
-----> Setting up <ansible-controller-centos-7>...
       Finished setting up <ansible-controller-centos-7> (0m0.00s).
-----> Verifying <ansible-controller-centos-7>...
       Preparing files for transfer
       Transferring files to <ansible-controller-centos-7>
       Finished verifying <ansible-controller-centos-7> (0m0.00s).
       Finished testing <ansible-controller-centos-7> (5m28.24s).
-----> Kitchen is finished. (5m29.68s)

これでansible controllerコンテナが起動する。
削除は、kitchen destroyで可能。
kitchenコマンドからdocker stopやstartなど細かい操作は出来ないので、必要があればdockerコマンドを使用。

ansible controllerにログインして利用

$ kitchen login
Last login: Wed Oct 23 07:44:58 2019 from 172.17.0.1

[kitchen@7b012ce1d2a9 ~]$ id
uid=1000(kitchen) gid=1000(kitchen) groups=1000(kitchen)

[kitchen@7b012ce1d2a9 ~]$ pwd
/home/kitchen

[kitchen@7b012ce1d2a9 ~]$ ansible --version
ansible 2.8.5
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/kitchen/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Oct 30 2018, 23:45:53) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]

[kitchen@7b012ce1d2a9 ~]$ kitchen --version
Test Kitchen version 2.3.3

[kitchen@7b012ce1d2a9 ~]$ gem list | grep kitchen
kitchen-ansiblepush (0.10.1)
kitchen-azurerm (0.14.9)
kitchen-digitalocean (0.10.4)
kitchen-docker (2.9.0)
kitchen-dokken (2.7.0)
kitchen-ec2 (3.2.0)
kitchen-google (2.0.1)
kitchen-hyperv (0.5.3)
kitchen-inspec (1.2.0)
kitchen-vagrant (1.6.0)
kitchen-vcenter (2.5.2)
test-kitchen (2.3.3)

[kitchen@7b012ce1d2a9 ~]$ mkdir roles                

[kitchen@7b012ce1d2a9 ~]$ cd roles/

[kitchen@7b012ce1d2a9 roles]$ ansible-galaxy init role01 
- role01 was created successfully

[kitchen@7b012ce1d2a9 roles]$ cd role01/

[kitchen@7b012ce1d2a9 role01]$ vi kitchen.yml
...

[kitchen@7b012ce1d2a9 role01]$ vi site.yml
...

[kitchen@7b012ce1d2a9 role01]$ vi tasks/main.yml
...

# 以下はInSpecの設定
[kitchen@7b012ce1d2a9 role01]$ mkdir -p test/integration/default

[kitchen@7b012ce1d2a9 role01]$ vi test/integration/default/default_test.rb
...


KitchenCI設定

kitchen.yml
---
driver:
  name: docker
  use_sudo: true
  use_internal_docker_network: true

provisioner:
  name: ansible_push
  chef_bootstrap_url: nil

platforms:
- name: centos-7

suites:
- name: ansiblepush
  provisioner:
    playbook: ./site.yml

verifier:
  name: inspec
  inspec_tests:
  - test/integration/default

kitchen-dockerとkitchen-ansiblepushを使用。
kitchen-ansiblepushは、ターゲットにansibleを導入せずにcontrollerのansibleを使用する。

プレイブック (site.yml)

site.yml
---
- hosts: all
  #gather_facts: false
  become: true
  tasks:
    - name: include_role 
      include_role:
        name: "../{{ lookup('env', 'PWD') | basename }}"

ここではテスト実行時のカレントディレクトリーをロール名としてincludeしている。

ロールのtasks (tasks/main.yml)

tasks/main.yml
---
- debug:
    msg: hello world!

InSpecテスト

test/integration/default/default_test.rb
# # encoding: utf-8

# This is an example test, replace it with your own test.
describe port(80) do
  it { should_not be_listening }
end

テスト実施 (kitchen test -d never)

[kitchen@7b012ce1d2a9 role01]$ kitchen test -d never 
-----> Starting Kitchen (v2.3.3)
-----> Cleaning up any prior instances of <ansiblepush-centos-7>
-----> Destroying <ansiblepush-centos-7>...
       Finished destroying <ansiblepush-centos-7> (0m0.00s).
-----> Testing <ansiblepush-centos-7>
-----> Creating <ansiblepush-centos-7>...
...

PLAY [all] *********************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************
ok: [centos-7]

TASK [include_role] ************************************************************************************************************

TASK [../role01 : debug] *******************************************************************************************************
ok: [centos-7] => {
    "msg": "hello world!"
}

PLAY RECAP *********************************************************************************************************************
centos-7                   : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

       *************** AnsiblePush end run *******************
       Downloading files from <ansiblepush-centos-7>
       Finished converging <ansiblepush-centos-7> (0m6.98s).
-----> Setting up <ansiblepush-centos-7>...
       Finished setting up <ansiblepush-centos-7> (0m0.00s).
-----> Verifying <ansiblepush-centos-7>...
       Loaded tests from {:path=>".home.kitchen.roles.role01.test.integration.default"} 

Profile: tests from {:path=>"/home/kitchen/roles/role01/test/integration/default"} (tests from {:path=>".home.kitchen.roles.role01.test.integration.default"})
Version: (not specified)
Target:  ssh://kitchen@172.17.0.3:22

  Port 80
     ✔  should not be listening

Test Summary: 1 successful, 0 failures, 0 skipped
       Finished verifying <ansiblepush-centos-7> (0m1.13s).
       Finished testing <ansiblepush-centos-7> (0m9.58s).
-----> Kitchen is finished. (0m13.83s)

ターゲットとなる別のdockerコンテナを立ち上げ、playbook適用、およびInSpecテスト実施

kitchen list, login, destroy

[kitchen@7b012ce1d2a9 role01]$ kitchen list 
Instance              Driver  Provisioner  Verifier  Transport  Last Action  Last Error
ansiblepush-centos-7  Docker  AnsiblePush  Inspec    Ssh        Verified     <None>

[kitchen@7b012ce1d2a9 role01]$ kitchen login
Last login: Wed Oct 23 08:23:19 2019 from 172.17.0.2
[kitchen@b72f869eae28 ~]$ id
uid=1000(kitchen) gid=1000(kitchen) groups=1000(kitchen)
[kitchen@b72f869eae28 ~]$ ls
[kitchen@b72f869eae28 ~]$ logout
Connection to 172.17.0.3 closed.

[kitchen@7b012ce1d2a9 role01]$ kitchen destroy
-----> Starting Kitchen (v2.3.3)
-----> Destroying <ansiblepush-centos-7>...
       PID                 USER                TIME                COMMAND
       72332               root                0:00                /usr/sbin/sshd -D -o UseDNS=no -o UsePAM=no -o PasswordAuthentication=yes -o UsePrivilegeSeparation=no -o PidFile=/tmp/sshd.pid
       b72f869eae282ebcdff9c1a20a03b85e94bd1aa709e8e750202f5265ef7c81d4
       b72f869eae282ebcdff9c1a20a03b85e94bd1aa709e8e750202f5265ef7c81d4
       Finished destroying <ansiblepush-centos-7> (0m0.84s).
-----> Kitchen is finished. (0m5.17s)

[kitchen@7b012ce1d2a9 role01]$ kitchen list
Instance              Driver  Provisioner  Verifier  Transport  Last Action    Last Error
ansiblepush-centos-7  Docker  AnsiblePush  Inspec    Ssh        <Not Created>  <None>

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

LaradockでLaravel環境を構築する流れとMySQL接続について

LaradockでMySQLに接続したい!!

Laradockでlaravelの開発環境を構築する方法については、公式ドキュメントや多くのQiitaの記事で説明されており、基本的にはそれに従って行けば開発環境を立ち上げることができると思います。しかし、自分が公式ドキュメントや記事通りに作業していく中でハマった点が2つあり、その解決法を探すのに時間がかかったので、ハマった点とその対処法について記録を残しておくものです

ハマった点

1 MySQLのバージョンについて

MySQLの認証方法がLaravelにサポートされてないのでエラーが出る。
MySQL 8.0.4からデフォルトの認証方式が変わり、セキュリティが強化されました。しかし、Laravel側でサポートされていないためphp artisan migrateを打っても「The server requested authentication method unknown to the client」というエラーが出ます

2 .envの編集について

Laradock,Laravelの.envの編集すれば良いか、MySQLのユーザー登録をどのようにすれば良いかがわからない

1 Laradockのインストール、env編集

今回はこのようなディレクトリ構造で作業を行なっていきます

  • (root)
    • sampleapp
      • Laradock(Laradock側)
      • Laravel
        • wikiLearns(Laravel側)

まずは公式ドキュメント通り

$ git clone https://github.com/laradock/laradock.git
$ cd laradock
$ cp env-example .env

ここで.envファイルを編集します

laradock.env
### MYSQL ################################################

MYSQL_VERSION=5.7 //latest
MYSQL_DATABASE=default
MYSQL_USER=default
MYSQL_PASSWORD=secret
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root
MYSQL_ENTRYPOINT_INITDB=./mysql/docker-entrypoint-initdb.d

MySQLのバージョンをlatestから5.7にしました
なのでハマった点1の答えは「認証方法変更前のMySQL5.7を使おう」です。現在インストールしているMySQLも8.0.4 -> 5.7に変更しました

それともう一つ

laradock.env
### NGINX ################################################

NGINX_HOST_HTTP_PORT=8888 // 80
NGINX_HOST_HTTPS_PORT=443
NGINX_HOST_LOG_PATH=./logs/nginx/
NGINX_SITES_PATH=./nginx/sites/
NGINX_PHP_UPSTREAM_CONTAINER=php-fpm
NGINX_PHP_UPSTREAM_PORT=9000
NGINX_SSL_PATH=./nginx/ssl/

NGINXのポート番号を80 -> 8888に変更しました
Apacheのポート番号とダブらないようにするためです

2 コンテナの起動

コンテナを起動します

docker-compose up -d nginx mysql phpmyadmin

ここで8.0 -> 5.7にバージョンを変えた場合、MySQLのコンテナだけ起動されないという不具合が発生します。その時はDATA_PATH_HOSTで設定したフォルダを綺麗にして、イメージを作成し直す必要があります

対処法
① パス確認
  $ cat .env | grep DATA_PATH_HOST
  自分の場合は DATA_PATH_HOST=~/.laradock/data が表示されました
② パス、イメージ、コンテナを消去
  rm -rf ~/.laradock/data/mysql
  docker rmi laradock_mysql -f
  docker rmi mysql -f
③ mysqlをビルドし直し
  docker-compose build --no-cache mysql
④ docker ps でちゃんと動いているか確認しましょう

3 Laravelプロジェクトの作成

workspaceに入って

$ docker exec -it laradock_workspace_1 bash

Laravelプロジェクトの作成
生成後、sampleapp/Laravel/wikiLearns 内にディレクトリwikiLearnsが出来るので、これをsample/Laravelに置き直してください(何かここ方法があるはず)

# composer create-project laravel/laravel wikiLearns(任意のアプリ名)

workspaceから出ます

# exit

設定の変更をするために一時停止

$ docker-compose stop

APP_CODE_PATH_HOST=../をAPP_CODE_PATH_HOST=../laravel/wikiLearns(自分の作ったディレクトリに対するLaradock起点での任意のパス)に書き換えてください

laradock.env
APP_CODE_PATH_HOST=../laravel/wikiLearns // ../

コンテナ再起動

$ docker-compose up -d nginx mysql

ブラウザでhttp://localhost:8888/ につなぐと例の画面が出てくるはずです。(出てこない場合は多分APP_CODE_PATH_HOSTのパス間違い)

4 MySQLへの接続

ハマった点2の説明です

LaradockのMySQLに接続したい時、

Laravel.env
DB_CONNECTION=mysql
DB_HOST=mysql // 127.0.0.1
DB_PORT=3306
DB_DATABASE=wikiLearns
DB_USERNAME=homestead
DB_PASSWORD=secret

LaravelのDB_HOSTを127.0.0.1 -> mysqlに変更すれば接続できるはず...なんですが、PDOException::("SQLSTATE[HY000] [2002] Connection refused")が出てきます。このエラーはMySQLに認証されていないユーザーで接続しようとした時に発生します。MySQLのコンテナに入って新たにユーザー生成しましょう

1 ユーザー生成

「laradockのworkspaceコンテナからmysqlコンテナに接続」します。そうするとworkspaceからMySQLに接続してマイグレーション等ができるようになります
まずdocker inspectでworkspaceのIPアドレスを確認しましょう

$ docker-compose up -d nginx mysql
$ docker ps (これでworkspaceのコンテナIDを見られる)
$ docker inspect be68295513a3(workspaceのIPアドレス)

下の方にIPアドレスが書いてあります

"Gateway": "172.20.0.1",
"IPAddress": "172.20.0.3", <- これ
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:14:00:03",
"DriverOpts": null

MySQLコンテナに入ります

$ docker-compose exec mysql bash
# mysql -u root -p
# (パスワードはLaradockの.envのMYSQL_ROOT_PASSWORDを見る
    デフォルトはroot)

データベースを作成

> create database wikiLearns(任意の名前);

ユーザー作成

> create user 'root(任意の名前)'@'172.20.0.3(workspaceのIPアドレス)'identified by 'secret(任意のパスワード)';
> grant all privileges on . to 'root'@'172.20.0.3';

2 .env編集

MySQLを出てLaradock、Laravelの.env編集

laradock.env
MYSQL_VERSION=5.7
MYSQL_DATABASE=wikiLearns(作ったデータベース名)
MYSQL_USER=root(作ったユーザー名)
MYSQL_PASSWORD=secret(設定したパスワード)
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root
MYSQL_ENTRYPOINT_INITDB=./mysql/docker-entrypoint-initdb.d
Laravel.env
DB_CONNECTION=mysql
DB_HOST=mysql // 127.0.0.1 → mysql
DB_PORT=3306
DB_DATABASE=dockapp(Laradockと同じに)
DB_USERNAME=root(Laradockと同じに)
DB_PASSWORD=secret(Laradockと同じに)

dockerを停止して再起動すればマイグレーションができます

$ docker-compose up -d nginx mysql
$ docker exec -it laradock_workspace_1 /bin/bash
$ php artisan migrate

これでdockerコンテナを立ち上げてlaravelを利用できるようになりました

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

DockerでReact + Swagger 環境を作ろう

現在業務でReactを使用しているのですが、事前に勉強する際にDocker環境を作成していたので、それを共有します。
ちなみにSwagger環境に関しては、自分で作成してうまくいかなかったので、上司が作成した環境を参考にさせてもらいました。感謝です:pray:

前提

  • Docker導入済み
  • docker-composeコマンドが使用できる

事前準備

今回は4つのコンテナを使用。

  • node...Reactのコードを実行するコンテナ
  • swagger-editor...Swagger Editorのコンテナ
  • swagger-api...Swagger定義ファイルから生成されたコードを実行するコンテナ
  • swagger-nginx...swagger-apiの内容を配信するアプリケーションサーバのコンテナ(nginxをさらに高性能に使えるようにしたopenrestyを使用)

これらのDockerfileや必要な設定ファイルを用意していきます。
ちなみに作成後のディレクトリは以下のようになります。

例
React(プロジェクトフォルダ)
├─ docker
|  ├─ nginx
|  |  ├─ swagger.conf
|  ├─ node
|  |  ├─ Dockerfile
|  ├─ swagger-api
|     ├─ Dockerfile
|     ├─ swagger.json
├─ docker-compose.yml

nginx/swagger.conf

nginx(openresty)の設定を記述します。
ここでCORSに関する設定をしておかないと、React側からaxiosでリクエストする際にエラーになってしまいます。
(自分が最初環境を作ろうとしたときに、ここでつまづいていました。今も設定に関しては疎いです...。)

server {
  listen 80;
  server_name localhost;

  location /docs/ {
    proxy_pass http://swagger-api:8000/docs/;
  }

  location / {
    if ($request_method = 'OPTIONS') {
      add_header Access-Control-Allow-Origin $http_origin;
      add_header Access-Control-Allow-Headers 'X-Requested-With, Authorization, Origin, Accept, Content-Type';
      add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE';
      add_header Access-Control-Max-Age 86400;
      add_header Access-Control-Allow-Credentials true;
      add_header Access-Control-Expose-Headers Set-Cookie;
      add_header Content-Type 'text/plain charset=UTF-8';
      add_header Content-Length 0;
      return 204;
    }
    proxy_pass http://swagger-api:8000;
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Access-Control-Allow-Credentials true always;
    add_header Access-Control-Expose-Headers Content-Disposition;
  }
}

node/Dockerfile

Facebook公式のReactプロジェクトを作成するCLIツールであるcreate-react-appを使用するので、インストールしておきます。

FROM "node:12-alpine"

WORKDIR /usr/src/app/

RUN yarn global add create-react-app

swagger-api/Dockerfile

Swagger定義ファイルをもとにコードを生成するSwagger Codegenをインストールして、実際にnode.jsのコードを生成しています。

FROM openjdk:8-jdk-alpine

ARG CLI_VERSION=2.4.5

RUN wget http://central.maven.org/maven2/io/swagger/swagger-codegen-cli/${CLI_VERSION}/swagger-codegen-cli-${CLI_VERSION}.jar -O /swagger-codegen-cli.jar

RUN apk --update add bash nodejs npm && rm -rf /var/cache/apk/* 

ADD swagger.json swagger.json

RUN java -jar /swagger-codegen-cli.jar generate -l nodejs-server -i swagger.json -o src && \
    cd src && npm install

CMD ["java", "-jar", "/swagger-codegen-cli.jar"]

swagger-api/swagger.json

Swagger定義ファイルです。
ここでは簡単に1つだけAPIを定義しています。

{
  "swagger": "2.0",
  "info": {
    "description": "テスト用API",
    "version": "1.0.0",
    "title": "Swagger test API設計書",
    "contact": {},
    "license": {
      "name": ""
    }
  },
  "host": "localhost:8000",
  "basePath": "/",
  "tags": [
    {
      "name": "User",
      "description": "User Controller"
    }
  ],
  "paths": {
    "/user": {
      "get": {
        "tags": [
          "User"
        ],
        "summary": "ユーザ情報取得",
        "description": "ユーザ情報を取得する。",
        "operationId": "doGetUsingGET",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/UserGetOut"
            }
          },
          "400": {
            "description": "バリデーションエラー",
            "schema": {
              "$ref": "#/definitions/ValidationErrorsOut"
            }
          },
          "500": {
            "description": "想定外のエラー",
            "schema": {
              "$ref": "#/definitions/ErrorInfo"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "UserGetOut": {
      "type": "object",
      "required": [
        "userId",
        "name",
        "kana"
      ],
      "properties": {
        "userId": {
          "type": "string",
          "example": "abc001",
          "description": "ユーザID"
        },
        "name": {
          "type": "string",
          "example": "山田 太郎",
          "description": "名前"
        },
        "kana": {
          "type": "string",
          "example": "ヤマダ タロウ",
          "description": "カナ"
        }
      }
    },
    "ValidationError": {
      "type": "object",
      "required": [
        "itemName"
        "code",
        "content"
      ],
      "properties": {
        "itemName": {
          "type": "string",
          "example": "userId",
          "description": "項目名"
        },
        "code": {
          "type": "string",
          "example": "NotBlank",
          "description": "code"
        },
        "content": {
          "type": "string",
          "example": "必須項目が設定されていません。",
          "description": "content"
        }
      }
    },
    "ValidationErrorsOut": {
      "type": "object",
      "required": [
        "validationInfo"
      ],
      "properties": {
        "validationInfo": {
          "type": "array",
          "description": "バリデーション情報",
          "items": {
            "$ref": "#/definitions/ValidationError"
          }
        }
      }
    },
    "ErrorInfo": {
      "type": "object",
      "required": [
        "code",
        "content"
      ],
      "properties": {
        "code": {
          "type": "string",
          "example": "exception.errors.unexpeced.exception",
          "description": "エラーコード"
        },
        "content": {
          "type": "string",
          "example": "システムエラーが発生しました。誠に恐れ入りますが、初めからやり直して下さい。",
          "description": "メッセージ"
        }
      }
    }
  }
}

docker-compose.yml

今回使用する4つのコンテナ定義を書いていきます。
なお、nodeのenvironmentのCHOKIDAR_USEPOLLING=trueはホットリロードを有効化にするものです。

version: "3"
services:
  node:
    build: ./docker/node
    environment:
      - NODE_ENV=development
      - CHOKIDAR_USEPOLLING=true
    volumes:
      - ./app/:/usr/src/app
    tty: true
    stdin_open: true
    ports:
      - "3000:3000"

  swagger-editor:
    image: swaggerapi/swagger-editor
    ports:
      - "8081:8080"

  swagger-api:
    build: ./docker/swagger-api
    working_dir: /src
    command: node index.js

  swagger-nginx:
    image: openresty/openresty:alpine
    ports:
      - 8000:80
    depends_on:
      - swagger-api
    volumes:
      - ./docker/nginx/swagger.conf:/etc/nginx/conf.d/default.conf:ro

コンテナの作成と起動

コンテナのビルド

$ docker-compose build

コンテナをバックグラウンドで起動

$ docker-compose up -d

Reactプロジェクト作成と起動

1.コンテナの中に入る

$ docker-compose exec node sh

2.プロジェクトのひな型作成

$ create-react-app .

これでプロジェクトのひな型が作成されます。
この時点でのディレクトリ構成は以下のようになります。

例
React(プロジェクトフォルダ)
├─ app
|  ├─ node_modules
|  |  ├─ (各種パッケージ)
|  ├─ public
|  |  ├─ favicon.ico
|  |  ├─ index.html
|  |  ├─ logo192.png
|  |  ├─ logo512.png
|  |  ├─ manifest.json
|  |  ├─ rebots.txt
|  ├─ src
|  |  ├─ App.css
|  |  ├─ App.js
|  |  ├─ App.test.js
|  |  ├─ index.css
|  |  ├─ index.js
|  |  ├─ logo.svg
|  |  ├─ serviceWorker.js
|  ├─ .gitignore
|  ├─ package.json
|  ├─ README.md
|  ├─ yarn.lock
├─ docker
|  ├─ nginx
|    ├─ swagger.conf
|  ├─ node
|    ├─ Dockerfile
|  ├─ swagger-api
|    ├─ Dockerfile
|    ├─ swagger.json
├─ docker-compose.yml

3.サーバの起動

$ yarn start

4.ブラウザでアクセス
localhost:3000にアクセスして、以下のような画面になればOKです。
ちなみにホットリロードを有効化にしているため、コードを変更して保存すると即座に反映されます。
react-app.png

なお、毎回コンテナの中に入ってサーバを起動するのが面倒な場合は、nodeのDockerfileの末尾に以下を追記してビルドしなおせば、次回からはコンテナ起動時にサーバも一緒に起動してくれます。

CMD ["yarn", "start"]

Swagger

Swagger Editor

localhost:8081にアクセス。
左側がエディタ、右側が定義をもとにしたドキュメントになっています。

デフォルトではサンプルのAPI定義が表示されます。
swagger-editor.png

Swagger定義ファイルを読み込む場合は、File→Import Fileからインポートできます。
なお、エディタ上から定義をもとにしたコードを生成することもできます。

※注意点
エディタ上の変更は自動で保存されますが、インポートしたファイル自体を保存までは行ってくれません。
エディタで変更した定義ファイルをアプリケーションサーバに反映させたい場合は、File→Convert and save as JSONでファイルをダウンロードし、React(プロジェクトフォルダ)/docker/swagger-api配下に設置して、コンテナをビルドしなおす必要があります。

Swagger UI

Swagger Codegenを使用してコードを生成すると、あわせてドキュメントも作成してくれています。
localhost:8000/docsにアクセスすることで確認できます。
swagger-ui.png

ちなみに最初はSwagger UIのコンテナをまた別で立ち上げていたのですが、参照するSwagger定義ファイルを編集しているうちに、ファイルに間違いがない状態でもなぜかエラーが出てしまい、いまいち使えなくなってしまいました。(Swagger Editor上で何もエラーが出ていない状態で出力したファイルでもエラーになりました)

Swaggerのサーバ

localhost:8000でアクセスできます。
今回はSwagger定義ファイルで定義しているAPIにリクエストしてみます。

ブラウザからアクセス

GETのAPIなので、直接ブラウザからアクセスしてもレスポンスが返ってきます。
localhost:8000/userにアクセスすると以下のようになります。
swagger-api-browser.png

axiosでアクセス

まずはコンテナの中に入り、yarnでaxiosをインストール。

$ yarn add axios

src/App.jsを以下のように修正。
(あまりいい書き方ではないと思いますが、とりあえず動作確認用のコードです:bow:

import React from 'react';
import logo from './logo.svg';
import './App.css';
import axios from 'axios'; // 追記

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
        {/* 以下を追記 */}
        <button
          onClick={
            () => axios.get('http://localhost:8000/user')
                       .then((result) => {
                         console.log(result)
                       })
                       .catch((error) => {
                         console.log(error)
                       })
          }
        >
          APIリクエスト
        </button>
        {/* ここまで追記 */}
      </header>
    </div>
  );
}

export default App;

localhost:3000にアクセスし、APIリクエストのボタンを押すとaxiosのリクエストが実行されます。
コンソールに出力しているので、Developer Toolsで確認すると以下のようにデータが取得できているのが確認できるかと思います。
swagger-api-axios.png

その他必要なもの

使用しているエディタにReactに関する拡張を導入するとより開発しやすくなります。

VSCode拡張

Full React/React Native/React Router/Redux/GraphQL/ES7/Testing/PropTypes snippets

名称にある通り、各種のスニペットが使用でき、慣れてくるとコーディングが捗るかと思います。
(自分はあまり使いこなせてなかったりします...)

Chrome拡張

React Developer Tools

Developer ToolsにComponentsとProfilerタブが追加されます。
コンポーネントの階層や、propsやstateの中身を確認したりすることができデバッグがしやすくなります。

  1. Chromeウェブストアからインストール

  2. ChromeのReact Developer Toolsアイコンを右クリックして、「拡張機能を管理」へ

  3. 「ファイルの URL へのアクセスを許可する」をONにする


静的解析ツールであるESLintも導入した方がいいとは思うのですが、VSCode上で動かすのがうまくいかなかったので今回は書いていません。
(いずれちゃんと設定できたら書くかも...)

参考

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

Vue.js 編 - 初心者こそ、Docker を使おう! 

簡単なTodo アプリを作りましょう

  • オリジナルはdotinstall さんの vue.js のtodoアプリをアレンジしたものですので詳しくはそちらをみてください。

今回は、Vue.js を使って、Todoに必要な処理の練習をします。

Todo タスクを追加してみましょう。

lang指定でts,stylus を指定していますが、typescript, stylus css 用ですが、実際つかっているのは、javascript, css です。 stylusにすると、混在できますのでstylus を使っています。 typescript の混在できますので、型つけたければつければいいかと思います。

Vue.js は、input周りは、簡単に処理できるのがいいですね。

src/MyApp.vue

<template lang="pug">
  div.container
    Todo
</template>
<script lang="ts">
import Vue from 'vue';
import  Hello  from './Hello.vue';
import Todo from './Todo.vue';
export default Vue.extend({
  components: {
    Todo,
  }
})
</script>
<style lang="stylus">
body {
  font-size: 16px;
  font-family: Verdana, Geneva, Tahoma, sans-serif;
}
.container {
  width: 300px;
  margin: auto;
}
h1 {
  font-size: 16px;
  border-bottom: 1px solid #ddd;
  padding: 16px 0;
}
li {
  line-height: 1.5;
}
input[type="text"] {
  padding: 2px;
}

.done
  color gray
  text-decoration line-through


</style>

src/Todo.vue

<template lang="pug">
  div
    ul
      li(v-for="todo in todos")
        | {{ todo }}
    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [
        "task 0",
        "task 1",
        "task 2",
      ]
    })
  },
  methods: {
    addItem() {
      this.todos.push(this.newItem);
      this.newItem = '';
    }
  }
})
</script>
  • コンテナ(uname linux でしたね)
uname
cd src
npx parcel --hmr-port 1235 --hmr-hostname localhost index.pug

delete / checkbox

削除もチェックボックスも実装が楽ですね。
id 追加しましたが、使っていません(笑) index指定で削除していますが、まずければ id に変えようと思いましたが、問題なさそうなので、使っていません。

src/Todo.vue

<template lang="pug">
  div
    ul( v-if="todos.length" )
      li(v-for="todo, index in todos")
        input(type="checkbox" v-model="todo.isDone")
        span( :class="{done: todo.isDone }" )  {{ todo.title }} : {{ todo.isDone }}
        button( @click="deleteItem(index)" ) Delete
    ul( v-else )
      | Nothing todo
    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [
        { id: 0, title: 'Task 0', isDone: false },
        { id: 1, title: 'Task 1', isDone: false },
        { id: 2, title: 'Task 2', isDone: true },
      ]
    })
  },
  methods: {
    addItem() {
      const newItem = {
        id: new Date().getTime().toString(36),
        title: this.newItem,
        isDone: false,
      }
      this.todos.push(newItem);
      this.newItem = '';
      console.log(newItem);

    },
    deleteItem(index) {
      this.todos.splice(index, 1)
    }
  }
})
</script>

複数削除

完了していないタスクは、computed で常に集計できるので、それを新しい配列にすれば、結果的に完了タスクを削除したことになりますね。

Todo.vue

<template lang="pug">
  div
    h1
      | todo
      span ( {{ remaining.length }} : {{ todos.length }} )
      button( @click="doneItemsPurge" ) Purge
    ul( v-if="todos.length" )
      li(v-for="todo, index in todos")
        input(type="checkbox" v-model="todo.isDone")
        span( :class="{done: todo.isDone }" )  {{ todo.title }} : {{ todo.isDone }}
        button( @click="deleteItem(index)" ) Delete
    ul( v-else )
      | Nothing todo
    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [
        { id: 0, title: 'Task 0', isDone: false },
        { id: 1, title: 'Task 1', isDone: false },
        { id: 2, title: 'Task 2', isDone: true },
      ]
    })
  },
  methods: {
    doneItemsPurge() {
      this.todos = this.remaining
    },
    addItem() {
      const newItem = {
        id: new Date().getTime().toString(36),
        title: this.newItem,
        isDone: false,
      }
      this.todos.push(newItem);
      this.newItem = '';
      console.log(newItem);
    },
    deleteItem(index) {
      this.todos.splice(index, 1)
    }
  },
  computed: {
    remaining() {
      return this.todos.filter( todo => {
        return !todo.isDone
      })
    }
  }

})
</script>

データの永続化

初心者むけでは大体 localStorage を使う例が多いので、 ここでは、Firebase の DB FireStore を使ってみようかと思います。

FireStore db は、googole のアカウントがあれば、利用できますので、firebase console で検索してみてください。 ここでは、プロジェクトの作成やfireStoreの有効にする方法は説明しませんので、利用出来る前提で始めていきます。
fireStore の 読み書きルール はすべて true の テストモードで行っています。

  • ファイル構成です。
├── Dockerfile
├── docker-compose.yml
├── firebase.js
└── src
    ├── Hello.vue
    ├── MyApp.vue
    ├── Todo.vue
    ├── dist
    ├── index.js
    ├── index.pug
    ├── package.json
    └── yarn.lock

ルードディレクトリに firebase.js を作っていますので、そこにfirebase の configをコピペしてください。

firebase.js

export const config = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: "",
  measurementId: ""
};

MyApp.vue はほとんど変わっていませんが一応記載します。

src/MyApp.vue

<template lang="pug">
  div.container
    //- Hello
    Todo
</template>
<script lang="ts">

import Vue from 'vue';
import  Hello  from './Hello.vue';
import Todo from './Todo.vue';
export default Vue.extend({
  components: {
    Todo,
    Hello,
  }
})
</script>
<style lang="stylus">
body {
  font-size: 16px;
  font-family: Verdana, Geneva, Tahoma, sans-serif;
}
.container {
  width: 700px;
  margin: auto;
}
h1 {
  font-size: 16px;
  border-bottom: 1px solid #ddd;
  padding: 16px 0;
}
li {
  line-height: 1.5;
}
input[type="text"] {
  padding: 2px;
}

.done
  color gray
  text-decoration line-through


</style>

Todo.vue は、fireStore対応で大幅に変わっています。
リファクタリングもしていないので、見苦しい感じが残っていますが、実装の苦労の跡が見えていいのではないでしょうか(笑)
なるべく、fireStoreの機能を使って実装しようとしているので、データのやり取りは、fireStoreを毎回介して行っています。
マウントとアンマウントの時にfireStore にデータを渡したほうが動作が軽く楽かもしれませんが
fireStoreの練習には、こちらのほうがいいかと思います.

ラグが気になりますが、追加、削除、完了、未完了、一括削除ができているのOK でしょう。(笑)

async / await に慣れていない人へ

DB を使うと処理が非同期になるため、async / await を使っていますが、asyce/await に慣れていない人に一言いうと、処理に時間がかかりそうだと思うものの前に,すべてawait を 付けていけばいいです。
今回で言えば、データの読み書きで firestore に対してのと命令を書いた場合にあたります。
付けていないと、ほしいデータが入る前に次の処理が走ってしまって、空処理になるのを防いでいます。
ですから、時間がかかると思ったら、await を処理の前につけてください。
await を使うとき、親要素に async という呪文が必要なので、これを関数式に付けます。

awaitをつけたけれど、親要素がない場合は、そのあたりの処理一体を、関数にまとめてしまえばいいです。
そうすれば、非同期処理はできているはずです。

src/Todo.vue

<template lang="pug">
  div
    h1
      | todo
      span ( {{ remaining.length }} : {{ todos.length }} )
      button( @click="doneItemsPurge" ) Purge

    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

    ul( v-if="todos.length" )
      li(v-for="todo, index in todos")
        //- input(type="checkbox" v-model="todo.isDone")
        input(type="checkbox" @click="checkboxChange(index)" :checked="todo.isDone")
        span( :class="{done: todo.isDone }" )  {{ todo.title }} : {{ todo.isDone }} : {{ todo.date }}
        button( @click="deleteItem(index)" ) Delete
    ul( v-else )
      | Nothing todo

</template>
<script lang="ts">

import * as firebase from 'firebase/app';
import 'firebase/firestore';
import { config } from '../firebase';

firebase.initializeApp(config);
const db = firebase.firestore()
const collection = db.collection('todos')

import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [],
      itemsId: [],
      storeItem: {},
      state: [],
    })
  },
  watch: {
   state: { // 更新チェック されたら、実行
     handler: async function(){
        const queryItems = await collection.orderBy("created_at", "desc").get()
        const qureyItemsData = queryItems.docs.map( item => item.data())
        const qureyItemsId = queryItems.docs.map( item => item.id )

        this.todos = qureyItemsData;
        this.itemsId = qureyItemsId;
    }, deep: true
   },
   todos: {
     handler: async function(){
    }, deep: true
   }
  },
  methods: {
    async checkboxChange(index) {
      let isDoneState = Boolean;
      const item = await collection.doc(this.itemsId[index])
      const isDone = await item.get()
        .then(function(doc) {
          isDoneState = doc.data().isDone
        })
        .catch( err => {
          console.log(err);
        })
      const changeItem = await item.update({
        isDone: !isDoneState,
      })
      this.state = ["checkboxUpdate"]

    },
    async deleteItem(index) {
      const item = await collection.doc(this.itemsId[index]).delete()
      this.state = ["delete"]
    },
    addItem() {
      this.storeItem = {
        id: new Date().getTime(),
        date: new Date().toLocaleString("ja"),
        title: this.newItem,
        isDone: false,
        created_at: firebase.firestore.FieldValue.serverTimestamp(),
      }
      const item = collection.add(this.storeItem);
      this.newItem = '';
      this.state = ["addItem"] // 状態更新通知用
    },
    async doneItemsPurge() {
      // this.todos = this.remaining

      const itemsQuery = await collection.where("isDone", "==" , true)
        .get()
        .then(function(querySnapshot) {
          querySnapshot.forEach(function(doc) {
            const item = collection.doc(doc.id).delete();
            console.log(item);
          });
        })
        .catch(function(error) {
          console.log("Error getting documents: ", error);
        })

      this.state = ["purge"] // 状態更新通知用
    },
  },
  computed: {
    remaining() {
      return this.todos.filter( todo => {
        return !todo.isDone
      })
    }
  },
  async mounted() {
    const query =  await collection.get()
    const items = query.docs.map( item => item.data())
    const itemsId = query.docs.map( item => item.id)
    this.todos = items;
    this.itemsId = itemsId;
    this.state =  items;
  }

})
</script>

これで、Vue.js での練習は、終了です。
お疲れ様でした。

次回は, この環境で、React を使ってみたいと思います

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

AWSのDNS障害でDebianパッケージがインストールできなかった話

やろうとしたこと

Dockerファイルをビルドしようとしていました。

FROM php:7.1.11-fpm

WORKDIR /var/local

RUN apt-get update && apt-get install -y \
    apt-transport-https \
    lsb-release \
    ca-certificates \
    wget
...

エラー発生

Step 3/13 : RUN apt-get update && apt-get install -y     apt-transport-https     lsb-release     ca-certificates     wget
 ---> Running in 50a658d96e1f
Get:1 http://security.debian.org jessie/updates InRelease [44.9 kB]
Err http://deb.debian.org jessie InRelease
Err http://deb.debian.org jessie-updates InRelease
Err http://deb.debian.org jessie Release.gpg
  Could not resolve 'cdn-fastly.deb.debian.org'
Err http://deb.debian.org jessie-updates Release.gpg
  Could not resolve 'cdn-fastly.deb.debian.org'
Get:2 http://security.debian.org jessie/updates/main amd64 Packages [893 kB]
Fetched 938 kB in 4s (192 kB/s)
Reading package lists...
W: Failed to fetch http://deb.debian.org/debian/dists/jessie/InRelease
W: Failed to fetch http://deb.debian.org/debian/dists/jessie-updates/InRelease
W: Failed to fetch http://deb.debian.org/debian/dists/jessie/Release.gpg  Could not resolve 'cdn-fastly.deb.debian.org'
W: Failed to fetch http://deb.debian.org/debian/dists/jessie-updates/Release.gpg  Could not resolve 'cdn-fastly.deb.debian.org'
W: Some index files failed to download. They have been ignored, or old ones used instead.
Reading package lists...
Building dependency tree...
Reading state information...
Package lsb-release is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
E: Package 'lsb-release' has no installation candidate
The command '/bin/sh -c apt-get update && apt-get install -y     apt-transport-https     lsb-release     ca-certificates     wget' returned a non-zero code: 100
make: *** [build] Error 100

原因調査

deb.debian.orgのサーバーが死んだのかな?と思いきや、エラーメッセージに下記を発見。

Could not resolve 'deb.debian.org'

ネームサーバー変わったのかなと思っていたら、社内のエンジニアからSlackが飛んできました。
AWSでDNS周りの障害が起きていたみたいです。

https://www.theregister.co.uk/2019/10/22/aws_dns_ddos/

その障害によって、deb.debian.orgの名前解決ができず、パッケージのダウンロードができなくなっていたんですね。

障害復旧後

下記のステータスを、AWSで確認しました。

[RESOLVED] Intermittent DNS Resolution Errors
Between 10:30 AM and 6:30 PM PDT, we experienced intermittent errors with resolution of some AWS DNS names.
Beginning at 5:16 PM, a very small number of specific DNS names experienced a higher error rate. These issues have been resolved.

https://status.aws.amazon.com

その後再ビルドすると、無事にdebianパッケージもインストールされ、ビルドできました!

考察

おそらくdeb.debian.orgはRoute53あたりを使っているんでしょうか。
今後debianパッケージ取得の際に名前解決関係のエラーが出たら、AWSの障害を疑ってみることも一つの手ですね。

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

GCPのGCRとAWSのECR併用時に no basic auth credentials エラー

環境

  • MacOS
  • docker desktop community 2.1.0.1(37199) stable
  • aws-cli/1.16.166 Python/2.7.15 Darwin/18.7.0 botocore/1.12.156

事象

以下のコマンドでECRにログインする。

$ $(aws ecr get-login --no-include-email --region ap-northeast-1) 

そして、docker buildしようとすると以下のようなエラーメッセージが出た。

no basic auth credentials

原因

結論から言うと、その前にGCPのGCRにログインしていたことが原因だった。

~/.docker/config.jsonの中身を見ると、以下のようにECRとGCRの設定が混在していた。

{
    "auths": {
        "xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com": {},
        "asia.gcr.io": {},
        "eu.gcr.io": {},
        "gcr.io": {},
        "https://asia.gcr.io": {},
        "https://eu.gcr.io": {},
        "https://gcr.io": {},
        "https://marketplace.gcr.io": {},
        "https://staging-k8s.gcr.io": {},
        "https://us.gcr.io": {},
        "marketplace.gcr.io": {},
        "staging-k8s.gcr.io": {},
        "us.gcr.io": {}
    },
    "HttpHeaders": {
        "User-Agent": "Docker-Client/19.03.1 (darwin)"
    },
    "credsStore": "desktop",
    "credHelpers": {
        "asia.gcr.io": "gcloud",
        "eu.gcr.io": "gcloud",
        "gcr.io": "gcloud",
        "marketplace.gcr.io": "gcloud",
        "staging-k8s.gcr.io": "gcloud",
        "us.gcr.io": "gcloud"
    }
}

そこで ~/.docker/config.json を削除した後、再度 $(aws ecr get-login --no-include-email --region ap-northeast-1)によりログインするとうまく行った。

うまくいったときの ~/.docker/config.json は以下のようであった

{
    "auths": {
        "xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com": {}
    },
    "HttpHeaders": {
        "User-Agent": "Docker-Client/19.03.1 (darwin)"
    },
    "credsStore": "osxkeychain"
}

もっとスマートな回避方法はあると思いますが、分かり次第追記します。
一旦内容共有まで。

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

dockerを使って構築した、vue.jsのアプリがデプロイした際に動かなくなった話

dockerのマルチステージビルドを使って構築した、vue.jsのアプリがデプロイした際にローカル環境では発生しないエラーが実際のサーバー上でだけ発生する事象が発生しました。
通常通りのやり方だと気づけなかったポイントなので、共有しようと思います。

マルチステージビルドとは?

マルチステージビルドはdockerのimageを作成する際、docker build時にだけ立ち上がるコンテナでそこでできた生成物を実際のイメージに配置するみたいなことができます。
どこかの環境で(jenkins等)node.jsをbuildしてその生成物を配置するみたいな必要がなくなりdockerだけで完結できるようになるのでVue.js公式も推奨しているデプロイ方法になります。

ハマったポイント

ローカルでnpm install したときに生成される node_modules と サーバーのコンテナ上で生成される node_modules に差分があり、ローカル環境でうまく実行できていたものがサーバーのコンテナ上ではうまく実行されずエラーになってしまった。

原因

ローカル環境で Vue.jsの開発をするときは、npm run dev 等で ローカルのサーバーを立てて開発することが多いと思います。
そのため、 node_modulesdist ファイルは`同じプロジェクト内にローカル環境だけは共存している形になると思います。
ローカルで以下のようなdockerfileをもとにvue.js のプロジェクトをbuildしたのですが、

Dockerfile
COPY ./vue ./
RUN npm install
RUN npm run build

このときの COPY の挙動ではローカルに含まれるnode_modulesdist ファイルも一緒にコンテナ内に配置されてしまいます。
そのため、RUN npm install が実行されても、新規でnode_modulesが生成されることがなく baseimageのversionとローカルのversionの差分等があるとうまく生成されないというような事象が発生してしまっていました。

対処方法

.dockerignoreと呼ばれるdockerから無視させるファイルリストを追加する方法があるので、このファイルに以下のように無視ファイルの対象として追加する。

./vue/dist/
./vue/node_modules/

※ディレクトリ階層は使っているものに合わせてください。

追加することで、docker build 時は毎回新規で npm install が走るようになるので、サーバーだろうがローカル環境だろうが同じ状態にすることができ、問題を解決することができました。

さいごに

今回のミスは、たまたまアプリを起動するときに出てくるエラーで動作の一部ができなくなるという感じのエラーではなく気づくことができましたが、ほんとに一部でしか使っていない動作でのエラーとかだとリリースしてからエラーが発生する等の問題になりかねないので注意が必要ですね。

あと、今回差分が発生してしまった一番の大きな原因は dockerの baseimageのversionが node:6.4 とかだったのに対して、ローカル環境が 10くらいになっていたことだったので、プロジェクトの生成からdockerのコンテナ内でやったほうが幸せになれると思いました。 Rubyとかは記事とかそこそこあるんですけどね…

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

Dockerfileではまったことや最近したことまとめ

docker buildをしていてうまく動かないことや新たにもった知見などがあったのでまとめる。

ARG

外部から値を注入できる

下記Dockerdileから一部抜粋

ARG copy_path

# 使うとき
COPY ${copy_path} /path/to/use

buildするとき

docker build ./ --build-arg copy_path=./script

ADD

COPYとの違いとして、解凍、展開をしてくれると記載されていることが多いが、zipファイルは解凍されないので注意。
解凍、展開はtar、gzの場合に行われる。

docker build時の注意点

context

一番ハマったやつ

sample
 |- docker
 |  |- web
 |      |- Dockerfile
 |
 |- config
     |- web
        |- nginx.conf

上記のようなディレクトリ構造でプロジェクトルートにてdokcer buildを行う際、

docker build -f docker/web/Dockerfile docker/web とすると nginx.conf を参照(ADDやCOPY)することはできなくなる。

これは docker/web と指定している部分(context)で参照スコープを絞っているため、 docker/web よりも上位階層から枝分かれしたディレクトリを見れなくなってしまうからということらしい。

言われてみるとなるほど!となるんだけど別リポジトリのDockerfileやMakefileをコピってきてちょちょで使おうとするとハマりやすそうなところだったのでまとめてみました。

以上

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

Windows HomeでDockerを使うためにAmazon EC2を利用した話

はじめに

本記事は,「量子アニーリングの基礎」を読む,という勉強会(読書会)での不都合解消のために書いたものです。

不都合

@kaizen_nagoya さんが用意してくれている検証用のDockerコンテナ があるのですが,手持ちのノートPCがWindows HomeであったためDockerがインストールできません。(Homeでも頑張れば使えるようにできるようですが面倒くさい)
なので,Amazon EC2にLinuxインスタンスを立ててDocker使えるようにすればいいじゃない,と思いつき,動かすまでの作業を記録することにしました。

Amazon EC2

初めて使う場合料金が心配かと思いますが,勉強会用に利用するだけなら無料枠で十分です。
ここにアカウント作成からインスタンスの起動までに必要なことが一通り書いてあります。

Linuxインスタンスとの接続はここに手順が記されています。

ちなみに,私の場合インスタンスの種類はubuntuを選択しました。(単に自分が使い慣れてるディストリビューションだったため)
接続手段としてPuTTYを選びました。
ファイル転送ソフトはWinSCPを選びました。
ubuntuへのDockerインストールはこの記事「Ubuntuにdockerをインストールする」を参考にしました。

主な作業内容

・アカウントを作成する
・インスタンスを起動する
 ・プライベートキーの作成と保存
 ・インスタンスIDの把握
 ・パブリックDNSを把握する
 ・デフォルトユーザー名の把握
・接続端末(TTY)を用意する
 ・PuTTYの準備
  ・PuTTYの場合は,プライベートキーの変換
  ・PuTTYの接続設定
・ファイル転送できるようにする
・インスタンスへの接続
Dockerのインストール
Dockerコンテナの取得

参考文献

Qiita記事「docker(28) Openjij チュートリアルをdockerで」
Qiita記事「Ubuntuにdockerをインストールする」

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

Windows10 HomeでDockerを使うためにAmazon EC2を利用した話

はじめに

本記事は,「量子アニーリングの基礎」を読む,という勉強会(読書会)での不都合解消のために書いたものです。

不都合

@kaizen_nagoya さんが用意してくれている検証用のDockerコンテナ があるのですが,手持ちのノートPCがWindows10 HomeであったためDockerがインストールできません。 HomeでもDocker Toolboxを使えば利用できるようになりますが,Windows10以前のバージョンを利用している方もいましたので,Amazon EC2にLinuxインスタンスを立ててDocker使えるようにすればいいじゃない,と思いつき,動かすまでの作業を記録することにしました。

Amazon EC2

初めて使う場合料金が心配かと思いますが,勉強会用に利用するだけなら無料枠で十分です。
ここにアカウント作成からインスタンスの起動までに必要なことが一通り書いてあります。

Linuxインスタンスとの接続はここに手順が記されています。

ちなみに,私の場合インスタンスの種類はubuntuを選択しました。(単に自分が使い慣れてるディストリビューションだったため)
接続手段としてPuTTYを選びました。
ファイル転送ソフトはWinSCPを選びました。
ubuntuへのDockerインストールはこの記事「Ubuntuにdockerをインストールする」を参考にしました。

主な作業内容

・アカウントを作成する
・インスタンスを起動する
 ・プライベートキーの作成と保存
 ・インスタンスIDの把握
 ・パブリックDNSを把握する
 ・デフォルトユーザー名の把握
・接続端末(TTY)を用意する
 ・PuTTYの準備
  ・PuTTYの場合は,プライベートキーの変換
  ・PuTTYの接続設定
・ファイル転送できるようにする
・インスタンスへの接続
Dockerのインストール
Dockerコンテナの取得

参考文献

Qiita記事「docker(28) Openjij チュートリアルをdockerで」
Qiita記事「Ubuntuにdockerをインストールする」

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