20200407のdockerに関する記事は10件です。

さわっておぼえるDocker入門?#2(コンテナの様子をみる)

はじめに

Dockerコンテナを実際に動かして確認しながら、動いているコンテナの様子をざっくり眺めていきます。

前回はコンテナを作ったり壊したりしました -> さわっておぼえるDocker入門?#1

目的

Dockerを触りながら、コンテナの様子を確認したり、コンテナ内のシェルを使用できるようになる

今回は、下記の操作を行います。

  • コンテナのメタデータを確認
  • コンテナのステータスを確認
  • コンテナのシェルにアクセス
  • ubuntuコンテナにパッケージをインストール
  • コンテナのプロセスを確認

環境

  • macOS Mojave 10.14.6
  • Docker engine 19.03.5

まずはDockerのインストール

https://www.docker.com/

コンテナをアレコレしてみる

まずはコンテナを起動させてみる

以下のコマンドは、nginxのコンテナを起動します。

  • -dオプションによって、バックグラウンドでコンテナが起動します。
  • --nameオプションによって、nginxと名付けました。
docker container run -d --name nginx nginx

さらに、MySQLのコンテナも起動します。

MySQLサーバーには、rootパスワードが必要なので-eオプションを使用してランダムなパスワードを設定しています。
-eはenvironmentオプションです。

docker container run -d --name mysql -e MYSQL_RANDOM_ROOT_PASSWORD=true mysql

これで、nginxとmysqlの2つのコンテナが起動しました。

container inspect

指定したコンテナのメタデータを取得します。

以下のコマンドを実行すると、mysqlコンテナのメタデータがJSON形式で表示されます。つまりコンフィグです。

このメタデータを確認することで、どんな設定でこのコンテナが作られているのかを知ることができます。

docker container inspect mysql

container stats

メタデータではなく、現在の様子を知りたい場合は以下のようにstatsコマンドを使用します。

コマンドを実行することで、CPU使用率やRAMの占有率など、リアルタイムのデータが表示されます。

docker container stats mysql

コンテナ内のシェルにアクセスする

立ち上げたコンテナの中で作業するには、シェルにアクセスする必要があるかもしれません。

その場合は-itオプションを使用します。SSHは必要ありません。

bashを指定しbashシェルが使えるようにします。

docker container run -it --name proxy nginx bash
# root@96d294dee3b4:/# 

これで、コンテナ内でファイルをいじったり、パッケージをインストールしたりすることができます。

コンテナの「コマンド」が変化していることを確認する

コンテナの一覧を確認してみると-itオプション付きで立ち上げたコンテナは、
COMMANDの値が変化しています。

docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                 NAMES
96d294dee3b4        nginx               "bash"                   4 minutes ago       Exited (0) 12 seconds ago                         proxy
d43f74bd3fea        mysql               "docker-entrypoint.s…"   20 minutes ago      Up 20 minutes               3306/tcp, 33060/tcp   mysql
40a021da02c9        nginx               "nginx -g 'daemon of…"   22 minutes ago      Up 22 minutes               80/tcp                nginx

シェルを使用するために、デフォルトで使用するプログラムを「bash」に指定したので、COMMANDが書き換わっています。

したがって、シェルからexitした場合には、コンテナは停止状態になります。
コマンドで指定したプログラムが停止した時には、コンテナは停止するからです。

Ubuntuを起動してみる

以下のコマンドでubuntuイメージからコンテナを起動し、シェルにアクセスします。

docker container run -it --name ubuntu ubuntu

ubuntuはaptというパッケージマネージャを使用してパッケージのダウンロードを行うことができます

まずはパッケージリストを更新します。

apt-get update

さらに、curlという Httpリクエストを行うパッケージをインストールしてみます。

apt-get install -y curl

試しにGoogle.comにHttpリクエストを送信してみると、
レスポンスがちゃんと返ってきます。

curl google.com
# <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
# <TITLE>301 Moved</TITLE></HEAD><BODY>
# <H1>301 Moved</H1>
# The document has moved
# <A HREF="http://www.google.com/">here</A>.
# </BODY></HTML>

ubuntuコンテナを停止する

先ほどと同じくexitすることで自動的にコンテナも停止します。

docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                 NAMES
9a881f1a18c6        ubuntu              "/bin/bash"              12 minutes ago      Exited (0) 2 seconds ago                          ubuntu
96d294dee3b4        nginx               "bash"                   23 minutes ago      Exited (0) 19 minutes ago                         proxy
d43f74bd3fea        mysql               "docker-entrypoint.s…"   39 minutes ago      Up 39 minutes               3306/tcp, 33060/tcp   mysql
40a021da02c9        nginx               "nginx -g 'daemon of…"   41 minutes ago      Up 41 minutes               80/tcp                nginx

ubuntuのCOMMANDをみると、デフォルトで起動するプログラムがbashであることを確認できます。
そのため、特に何も設定しなくてもbashが起動しました。
さらにbashからexitすることで、コンテナも停止しました。

コンテナを再起動する

もう一度このコンテナに入りたい場合は、container startを使用します。
container runだと、新しいコンテナが起動してしまうので、先ほどダウンロードしたcurlはありません。

runの時は-itだったところが-aiになっていることに注意です。

docker container start -ai ubuntu

バックグラウンド起動中のコンテナに入る

では、バックグランドで起動しているコンテナ内のシェルにアクセスしたい場合はどうすれば良いでしょうか。

container execコマンドを使用します。
今回は先ほどバックグラウンドで起動したmysqlコンテナを使用します。
bashシェルを指定することを忘れないでください。

docker container exec -it mysql bash

コンテナのプロセスを確認する

この時、mysqlコンテナ内ではどのようなプロセスが動いているのでしょうか。
ps auxコマンドを使用することで、起動中のプロセス一覧を確認できます。

まずはmysqlコンテナがpsコマンドを使用できるよう、必要なパッケージを更新&インストールします。

最初にパッケージの更新。

apt-get update

続いて、パッケージをインストールします。

apy-get install -y procps

これでpsコマンドが利用可能です。

早速、mysqlコンテナのプロセスを確認してみます。

ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
mysql        1  0.9 20.0 2135592 408776 ?      Ssl  13:26   0:32 mysqld
root       197  0.0  0.1   3868  3244 pts/0    Ss   14:15   0:00 bash
root       602  0.0  0.1   7640  2724 pts/0    R+   14:20   0:00 ps aux
  • まず一番上、mysqlのデーモンがバックグランドで起動中です。
  • 次に真ん中、これは今使っているbashのプロセスです。
  • 最後に、たった今打ち込んだps auxのプロセスが表示されています。

今回は、exitしてもコンテナは停止しません。
なぜなら、mysqlのデーモンがバックグラウンドで動いているからです。

まとめ

  • コンテナのメタデータを確認
  • コンテナのステータスを確認
  • コンテナのシェルにアクセス
  • ubuntuコンテナにパッケージをインストール
  • コンテナのプロセスを確認

上記を実際に動かして確認しながら、動いているコンテナの様子をざっくり眺めました。?

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

laradocker構築

初めてdockerlaravelを構築したのでその手順をまとめた

1.Laradock取得

$mkdir ~/my_docker
$cd ~/my_docker
$git clone https://github.com/laradock/laradock.git

2.Laradockの設置ファイル修正

$cd Laradock
$cp env-example .env
$cp mysql/docker-entrypoint-initdb.d/createdb.sql.example mysql/docker-entrypoint-initdb.d/createdb.sql
.env
//修正することで別プロジェクトで競合を防ぐ
-DATA_PATH_HOST=~/.laradock/data
+DATA_PATH_HOST=~/.laradock/my_docker_project/data

//こちらも競合を防ぐ
-COMPOSE_PROJECT_NAME=laradock
+COMPOSE_PROJECT_NAME=my_docker-laradock

-MYSQL_VERSION=latest
+MYSQL_VERSION=5.7

3.Laradockビルド・アクセス

$cd ~/my_docker/laradock
$docker-compose build workspace ngnix mysql redis
$docker-compose up -d ngnix mysql redis

立ち上がればworksqaceサーバへアクセス

$docker-compose exec workspace bash

4.Laravel・yarnインストール

workspaceサーバ内で書きコマンドを実行

$ composer create-project laravel/laravel --prefer-dist laravel //laravelインストール。プロジェクト名laravel
$cd laravel
$yarn install  //yarnインストール

5.Laravel設定ファイル修正

laradocker内の.envファイルと合わせる
こちらはlaravelの.envファイル

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=default
DB_USERNAME=default
DB_PASSWORD=secret

6.ブラウザアクセス

hosts修正

$sudo vi /private/etc/hosts
private/etc/hosts
+127.0.01 my_docker.com

アクセス

ブラウザで http://my_docker.com へアクセスしlaravelが表示されれば完了

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

初心者のKubernetes入門(書籍 Kubernetes 実践入門の写経から学ぶ)NameSpace 編

背景

個人的にインフラの知識以上にこれからのアプリケーションが動く環境を作ってデプロイしたりしてこれからの知識を身に着けたい。そしてより一層、自分の知識のアップデートをしたいと思いました。

その中でこの本に出会い、これから少しずつやったことを残し、未来の自分への手紙としてもあり、見つめ直せればと思いました。

引用や参考と今回の自分の勉強用の書籍の紹介

技術評論社『Kubernetes実践入門』のサンプルコード
Kubernetes実践入門 プロダクションレディなコンテナ&アプリケーションの作り方

実際の学びについて

書籍を読みながら、章ごとに少しずつ進めていきたいと思います。
GitHub のソースコードも使いながら学んで行きたいと思います。

勉強開始

ここから namespace を学んでいきます。

my-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: my-namespace #オブジェクト名 (1)

コマンド郡

namespace を検索

$ kubectl get ns
NAME              STATUS   AGE
default           Active   25d
docker            Active   25d
kube-node-lease   Active   25d
kube-public       Active   25d
kube-system       Active   25d

NameSpace のマニュフェストファイルを適用する

$ kubectl apply -f my-namespace.yaml
namespace/my-namespace created

namespace を検索

$ kubectl get ns
NAME              STATUS   AGE
default           Active   25d
docker            Active   25d
kube-node-lease   Active   25d
kube-public       Active   25d
kube-system       Active   25d
my-namespace      Active   6s

指定した Namespace が作成されることを確認できた

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

Docker outside of Docker (DooD) で GitLab-CI する

このポストは、
やん事ない理由で Git*.com を使えない日々をオンプレ版 GitLab で乗り切る の後続記事です。

ゴール

  1. DOODでCI環境を構築
    公式 -- Use Docker socket binding

  2. 下図の (1)~(6)
    (Start) http://192.168.100.201/kubolab/tms の master のコミットを契機にCI実行し
    (End) http://192.168.100.201:10080 で初期画面を見れること。
    ※とりあえず慣れてる CakePHP3 を使いました。

構成図

ソースコード: https://gitlab.com/kubolab/tms

項目
GitLab グループ名 kubolab
GitLab プロジェクト名 tms

グループ名、プロジェクト名をブログ投稿用に変更しようかと思ったのですが、手間だったのでそのままです、すんません。

tree

?: gitignore 対象

.
├── ?.env
├── .gitlab-ci.yml
├── docker-compose.yml
├── docker
│   ├── build
│   │   └── cake_php
│   │       └── Dockerfile
│   ├── config
│   │   └── nginx
│   │       └── default.conf
│   ├── ?data
│   │   └── htdocs #公開Dir ※srcへのシンボリックリンクを張る
│   │   └── postgres
│   │       └── data
│   │
│   ├── ?postgres-dump
│   │   └── dumpall.sql #初期データ投入スクリプト
│   └── sh
│       └── postgres
│           └── docker-entrypoint-initdb.d
│               └── init.sh
├── src
│    ├── config
│    │   └── ?app.php
│    └── webroot
├── run-app.cmd #おまけ: ローカルPCに開発環境をダブルクリックで構築
└── shared_サーバ配置用
項目
オンプレ GitLab の IP 192.168.100.201
Host OS Windows 10 Pro 1909
VirtualBox 上の Guest OS 前記事参照
Guest OS上で動く GitLab ver 前記事参照
GitLab Runner 12.9.0
Docker for Windows 2.2.0.4
Docker コンテナ名(任意) image 備考
cake_nginx nginx:1.13.5-alpine ports: 10080:80 1
cake_php php:7-fpm
cake_postgres postgres:10.3-alpine

準備 (GitLab が動くサーバ上で)

  1. CI実行時に使用するファイル (バージョン管理しないファイル) を手動で配置しておく。 ※SCP か何かで /shared_サーバ配置用 を GitLab サーバに配置
/shared
   └── kubolab
       └── tms
           ├── app-settings
           │   └── app.php    #DB接続情報等
           └── postgres-dump
               └── dumpall.sql #初期データ
  1. GitLab-Runner (gitlab-runner-dd) コンテナ起動

    ⅰ. Docker をインストール
    https://qiita.com/ymasaoka/items/b6c3ffea060bcd237478 -- Install Docker on CentOS 7

    ⅱ. GitLab-Runner コンテナ起動

    $ docker run -d --name gitlab-runner-dd --restart always \
        -v /srv/gitlab-runner-dd/config:/etc/gitlab-runner \
        -v /var/run/docker.sock:/var/run/docker.sock \
        gitlab/gitlab-runner:latest
    
    # GitLab-Runner コンテナに入って実行
    $ docker exec -it gitlab-runner-dd bash
    
    # docker インストール
    $ curl -sSL https://get.docker.com/ | sh
    $ usermod -aG docker gitlab-runner
    
  2. ジョブを実行する Runner を登録

    ⅰ. 登録

    ★メモしたToken
    参考: https://qiita.com/hykisk/items/ebff2f7cd2e8100a6bbe -- ジョブを実行する Runner を登録

    $ gitlab-runner register -n \
        --url http://172.17.0.1/ \
        --registration-token ★メモしたToken \
        --executor docker \
        --description "tms docker runner" \
        --docker-image "docker:stable" \
        --docker-volumes /var/run/docker.sock:/var/run/docker.sock \
        --docker-volumes /shared-builds/kubolab/tms/docker:/builds/kubolab/tms/docker \
        --docker-volumes /shared/kubolab/tms:/gitlab-runner-dd/app \
        --tag-list app-build \
        --clone-url = "http://192.168.100.201"
    

    memo: GitLabにホスト名を割り当ててる場合は、クローンに失敗するため、以下を追加して名前解決を指定する。
    --docker-extra-hosts "gitlab.private.hykisk.com:172.17.0.1"

    ⅱ. 登録結果を確認したいとき

    /etc/gitlab-runner/config.toml
    [[runners]]
      name = "tms docker runner"
      url = "http://172.17.0.1/"
      token = ★メモしたToken
      executor = "docker"
      clone_url = "="
      [runners.custom_build_dir]
      [runners.cache]
        [runners.cache.s3]
        [runners.cache.gcs]
      [runners.docker]
        tls_verify = false
        image = "docker:stable"
        privileged = false
        disable_entrypoint_overwrite = false
        oom_kill_disable = false
        disable_cache = false
        volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/shared-builds/kubolab/tms/docker:/builds/kubolab/tms/docker", "/shared/kubolab/tms:/gitlab-runner-dd/app", "/cache"]
        shm_size = 0
    

gitlab-ci.yml

stages:
  - package
variables:
  GIT_STRATEGY: fetch
package:
  stage: package
  variables:
    # HOST OS
    SHARED_BUILDS_DOCKER_DIR: /shared-builds/$CI_PROJECT_PATH/docker
    SHARED_DIR: /shared/$CI_PROJECT_PATH
  image: docker:stable
  script:
    # アプリケーションコードを配置
    - cp -pr $CI_PROJECT_DIR/src/. $CI_PROJECT_DIR/docker/data/htdocs
    # アプリケーション設定ファイルを配置
    - cp -pf /gitlab-runner-dd/app/app-settings/app.php $CI_PROJECT_DIR/docker/data/htdocs/config
    # このコンテナ (gitlab-runner) に docker-compose をインストール
    - apk update && apk add docker-compose
    # 2回目以降、CI実行されたときマウント有効にするため down する
    - docker-compose down # volumeを有効にするため
    # Dockerfile に手を加えたら build しなおす
    #- docker-compose -f docker/docker-compose.yml build --no-cache
    - docker-compose up -d
    # マウント後に実行する必要があるため Dockerfile 内ではなくコンテナに入って行う
    - docker exec cake_php sh -c "cd /var/www/app && composer install"
  tags:
    - app-build

docker-compose.yml

version: '3'
services:
  web:
    container_name: cake_nginx
    image: nginx:1.13.5-alpine
    ports:
      - 10080:80
    volumes:
      # ローカルで使う環境変数はでは .env に、CI環境で使う環境変数は gitlab-ci の設定(GUI) に書く
      - $SHARED_BUILDS_DOCKER_DIR/config/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - $SHARED_BUILDS_DOCKER_DIR/data/htdocs:/var/www/app
    networks:
      vpcbr:
        ipv4_address: 172.26.0.2
  php:
    container_name: cake_php
    build: docker/build/cake_php
    volumes:
      - $SHARED_BUILDS_DOCKER_DIR/data/htdocs:/var/www/app
    networks:
      vpcbr:
        ipv4_address: 172.26.0.3
  db:
    container_name: cake_postgres
    image: postgres:10.3-alpine
    ports:
      - 5433:5432
    volumes:
      - $SHARED_BUILDS_DOCKER_DIR/data/postgres:/var/lib/postgresql
      # DB作成と初期データ投入スクリプトを配置
      - $SHARED_BUILDS_DOCKER_DIR/sh/postgres/docker-entrypoint-initdb.d/init.sh:/docker-entrypoint-initdb.d/init.sh
      - $SHARED_DIR/postgres-dump/dumpall.sql:/tmp/dumpall.sql:ro
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DUMP_FILE_DIR: /tmp
    networks:
      vpcbr:
        ipv4_address: 172.26.0.4
networks:
  vpcbr:
    ipam:
      driver: default
      config:
      - subnet: 172.26.0.0/24

memo: 公開してはいけない情報は環境変数を使用

    environment:
      POSTGRES_USER: $TMS_POSTGRES_USER
      POSTGRES_PASSWORD: $TMS_POSTGRES_PASS

http://192.168.100.201/kubolab/tms/-/settings/ci_cd

他ファイル (Dockerfile等)

ソースコード: https://gitlab.com/kubolab/tms 参照

確認

PUSHしてCIジョブが created -> running -> passed になればOK

image.png


おまけ

ローカルPC (Docker for Windows インストール済) の開発環境をダブルクリックで構築

/run-app.cmd
@echo off
rem cd /d %~dp0

rem アプリケーションコードを配置
rmdir docker\data\htdocs
mklink /d .\docker\data\htdocs ..\..\src

rem アプリケーション設定ファイルを配置
copy /Y .project-items\app.php src\config

rem DB作成と初期データ投入スクリプトを配置
copy /Y .project-items\dumpall.sql docker\postgres-dump

rem docker-compose 環境設定ファイルを配置
copy /Y .project-items\.env .env

rem Dockerfile に手を加えたら build しなおす
docker-compose down --rmi all
docker-compose build --no-cache

rem 各コンテナ起動
docker-compose up -d

rem マウント後に実行する必要があるため Dockerfile 内ではなくコンテナに入って行う
docker exec cake_php sh -c "cd /var/www/app && composer install"

pause

後記

CI環境構築時に困ったこと

  • 問題: コンテナ内で実行するシェルが Permission denied で実行できない。
  • 解法: 実行権限を付与する

  • $ git update-index --add --chmod=+x {filepath}
    

  • 問題: 試行錯誤中に (CI環境・ローカルPC問わず) Docker の挙動がおかしくなった場合

  • 解法: 全消しする

CI環境の場合

docker exec -it gitlab-runner-dd bash
rm -f /etc/gitlab-runner/config.toml
exit

docker ps -aq | xargs docker rm -f
docker images -aq | xargs docker rmi
docker volume rm $(docker volume ls -qf dangling=true)
docker network rm docker_vpcbr

ローカルPCの場合 (powershell)

docker rm -f $(docker ps -aq)
docker rmi -f $(docker images -aq)
docker volume rm $(docker volume ls -qf dangling=true)
docker network rm docker_vpcbr

# Docker for windows 再起動

  1. ports: 10080:80
    Dockerホストにあたる GitLab が80番を使っているため10080番を使う。
    アクセスの流れ:
    1.ブラウザでGitLabサーバに 10080 でアクセス
    2.VirtualBox のポートフォワーディングルール10080 -> 10080 にマッピング
    3.nginxの設定で 10080 -> 80 にマッピング
     

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

Windows10 Home Edition でDockerが使用可能に

はじめに

2020/3月末 Windows 10 HomeでWSL2が使用できることになり、Docker desktopに対応することが出来るようになりました。また、WSL2によるDockerの使用は、Hyper-Vを用いた方法よりも15倍速い速度となるため、より軽い処理で実現できます。ただし、OSを開発者verで入れないといけないこと、Dockerも現状edgeという正式版での対応はまだであることから、多少のリスクを伴うため、ユーザーが安心して導入するのはまだしばらく時間がかかりそうです。(2020/4/7時点)
今回は開発版でDockerを入れてみた場合の操作を記載します。

この記事を読んでもらいたい方

Windows ユーザーでDockerをこれから使いたいエンジニア
もう既にDockerをVMWare等で使用しているエンジニア
Windows 10 pro もしくはEnterprise しか対応してなかったので、Dockerを使えなかったと思っているエンジニア

注意事項

上記でもアナウンスしましたが、これからWindowsに開発版のOSを入れていきます。
製品版に近いものの、万一OSに問題があった場合、再度OSを入れなおします。その際のフォーマット化に伴い内部ストレージのデータが消えることが予想されます。
必要なデータは外付けHDD or クラウド上に保存するようにして、十分ご理解の上実行してください。

作業時間

約半日

概要

1.WindowsOSのversion確認
2.Windows Insider Program(slowRing)にする
3.WSL と Virtual Machine Platform の有効化
4.Linux ディストリビューションの取得および開発者
5.WSL2のデフォルト化とUbuntu<=>WSL2間の接続
6.VScode<=>Ubuntu間の接続
7.Docker Desktopのinstall
8.Dockerの接続を確認

手順

1.WindowsOSのversion確認

WindowsOSが10 かつ  ver18917以上であることをコマンドプロンプトから、verコマンドで確認しましょう。

Windows_ver.jpg
versionが低い場合は更新→再起動してください。

2.Windows Insider Program(slowRing)にする

Microsoftアカウントの登録→ログインから、Insider Programに登録してください。
slowRing とfastRingの違いについて
登録されると以下のガイダンスがでますので、手順に従ってインストールします。
registrated_insider_preview.jpg

↓[スタート] -[設定] - [更新とセキュリティ]に入って、Insider Programへ
[スタート] -[設定] - [更新とセキュリティ] -Windows Insider Program.jpg
↓[開始]を押してアカウントをリンクさせます
account_Link.jpg
↓Insiderはスローを選択しましょう
account_Link3.jpg

3.WSL と Virtual Machine Platform の有効化

管理者権限でPowerShellを立ち上げ、下記コマンドを実行

#WSLの有効化
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
#Virtual Machine Platformの有効化
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

一度再起動します。(先ほど実行したInsider Program slow.verも有効化します)

その後、WSL2カーネルの更新を行います。
こちら参考にダウンロード→update→再起動するだけ
※ARM64=x64です
※自分のPCがx64かどうかの確認は、「設定」→「システム」→「バージョン情報」で確認できます。
今後Microsoftはこのような手動でのUpdateはやめ、定常的なWindowsのUpdateにこの操作を内部に組み込むとのことです。

4.Linux ディストリビューションの取得および初期設定

Windows MicrosoftstoreからUbuntuをinstall
私はUbuntu-18.04をinstallしました。
Ubuntuの初期設定はこちらが参考になりました。
image.png

5.WSL2のデフォルト化とUbuntu<=>WSL2間の接続

#Ubuntu<=>WSL2間の接続
PS C:>wsl --set-version Ubuntu-18.04 2
#WSL2のデフォルト化
PS C:>wsl --set-default-version 2

↓こんな感じになります。
WSL2_upgrade_success.jpg

接続を確認しましょう。

#Ubuntuに接続
PS C:>wsl
#Logoutの確認
PS C:>exit

Ubuntu初期設定後、WSL2環境のUbuntuを操作している状態でファイルを共有します(つまり、LocalのファイルをLinuxコマンドで動作させれるように同期します)
Ubuntuを開き、↓を実行します。
ちなみに実行後、フォルダが開くので、ショートカットを作成しておくと、Localからファイルを触れるようになりますよ。

explorer.exe .

6.VScode<=>Ubuntu間の接続

VScodeの画面左下「><」こんなのをクリック。「Remote-WSL: New Window」を選択してやると、デフォルトに設定しているUbuntuに接続されます。画像の右のようになればOKです。
VS-code.jpg

7.Docker Desktopのinstall

やっとDockerに入ってきました。あと少しです。
Docker Desktop for Windowsをinstallしましょう。こちらのEdgeをinstallします。(stableは試しましたが、まだ対応できてませんでした。今後に期待!)
image.png

※Docker Desktop のtutorialの注意点とインテグレーション
最初の接続先はPowerShellになってるかと思います。
その場合はwslでUbuntuに切り替えて、Dockerのコマンドを流し込んでいきましょう。
↓tutorialが終わったところから始めます。
defaultの接続先(Resources)をUbuntuにしときましょう。(インテグレーション)
image.png

8.Dockerの接続を確認

tutorialが終われば、PCの立ち上げと同時に自動で立ち上がるようになります。

Ubuntuを起動し、↓下記コマンドを実行。

#nginxを立ち上げます
docker run -p 80:80 nginx
#終了を確認(接続状況を確認)
docker ps
#もし、終了していないdockerがあればこれで接続を切ります
docker kill [CONTAINER ID]

localhostに接続を確認します。→Ctrl + C で終了
↓これがでたらOKです。
welcome_to_enginx.jpg

お疲れ様でした。

参考にしたサイト

「WSL 2」は「Windows 10 Home」でも利用可能 ~MicrosoftがFAQを掲載(ニュース記事)
https://forest.watch.impress.co.jp/docs/news/1184353.html
「WSL2とHyper-Vの速度比較について」
https://www.docker.com/blog/new-docker-desktop-wsl2-backend/
「Installation Instructions for WSL 2」
https://docs.microsoft.com/en-us/windows/wsl/wsl2-install
「WSL2入れてみた」
https://qiita.com/TsuyoshiUshio@github/items/947301bd9317610572fc
「windows10のversion.update」
https://support.microsoft.com/ja-jp/help/4027667/windows-10-update
「WindowsカーネルのUpdate方法」
https://japan.zdnet.com/article/35150847/
「Microsoftのカーネル管理方法の意向」
https://news.mynavi.jp/article/20200316-997109/
「Windows Insider ProgramのFast slowの違い」
https://ascii.jp/elem/000/001/099/1099995/
「Windows Insider preview」
https://insider.windows.com/ja-jp/how-to-pc/
「Windows Subsystem for Linuxディストリビューションのインストール」
https://qiita.com/Aruneko/items/c79810b0b015bebf30bb#
「UbuntuとLocalディレクトリとの同期(/home/user/でexplorer.exe .)」
https://note.com/akazawa_studio/n/nda7b97038e5c
「Dockerのinstall(edge版をinstallすること。homeは現在2020/4で開発verしか対応できていない)」
https://hub.docker.com/editions/community/docker-ce-desktop-window
「Docker tutorial」
https://qiita.com/wMETAw/items/8d1b0c053a39841765bf

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

Elastic SearchをDocker Composeで展開したらError code 78でコンテナが停止される問題

はじめに

Elastic Searchは公式にDockerイメージを提供していてドキュメントも公開されているため、Dockerでシングル構成のElastic Searchを動かしたり、Docker Composeで複数ノードからなるElastic Search環境を導入できます。

そしてdocker-composeで構築するわけですが、起動後数分するとExit 78で停止されてしまうわけです。

$ sudo docker-compose up -d
$ sudo docker-compose ps
Name              Command                State    Ports
-------------------------------------------------------
es01   /usr/local/bin/docker-entr ...   Exit 78        
es02   /usr/local/bin/docker-entr ...   Exit 78        
es03   /usr/local/bin/docker-entr ...   Exit 78        

調査

「Elastic Search Exit 78」などで検索するとvm.max_map_countの設定を増やせばThat's All!...みたいな情報ばかり出てきます(→例えばこれ)。

念のため試してみるのですが、相変わらずExit 78を回避できませんでした。

問題の原因

問題の原因はDockerとDocker Composeのバージョンの組み合わせの問題だったようです。今回Ubuntu Server 18.04でElastic Searchを動かそうとしたわけですが、DockerはUbuntu 18.04提供のバージョン、Docker Composeはサイトの手順を見ながらDocker公式のStableバージョンを入れたのがよくなかったようです。

バイナリインストールしたDocker Composeを削除して次のように導入した環境では正常にデプロイできました。

$ sudo apt update
$ sudo apt install docker.io docker-compose -y

$ docker -v
Docker version 19.03.6, build 369ce74a3c
$ docker-compose -v
docker-compose version 1.17.1, build unknown

$ sudo docker-compose up -d
$ sudo docker-compose ps
$ sudo docker-compose ps
Name              Command               State                Ports              
--------------------------------------------------------------------------------
es01   /usr/local/bin/docker-entr ...   Up      0.0.0.0:9200->9200/tcp, 9300/tcp
es02   /usr/local/bin/docker-entr ...   Up      9200/tcp, 9300/tcp              
es03   /usr/local/bin/docker-entr ...   Up      9200/tcp, 9300/tcp  

Ubuntu 18.04のUbuntu Archive Packageのdokcer-composeはPython2が必要みたいですね。新しいバージョンはPython 3を要求するようです。単に必要なPythonモジュールが足りなかったのが原因だったのかもしれませんが、Elastic Searchの1ノードは問題なくて3ノード構成は失敗するのは本当に謎。Ubuntu Archive版のdocker-composeでは問題ありません。

おまけ

公式の手順では3つのノードのElastic SearchをデプロイするDocker Composeの例ですが、このようなYAMLを作れば1台のコンテナーにElastic SearchをDocker Composeで展開できます。

version: '3'

volumes:
  es-data:
    driver: local

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
    volumes:
      - es-data:/usr/share/elasticsearch/data
    environment:
      - discovery.type=single-node
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    ports:
      - "9200:9200"

えっ一台ならdocker runでええやろですって?
そうかもしれません。

おまけ2

データの永続化をもう少し安全にするには、次のように設定すると良いです。

$ sudo mkdir -p /home/data
$ sudo chmod 777 /home/data

$ vi docker-compose.yml

version: '3'


services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
    volumes:
      - /home/data:/usr/share/elasticsearch/data
    environment:
      - discovery.type=single-node
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    ports:
      - "9200:9200"

Dockerのボリュームについては以下を参考にしました。

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

Docker初心者がdocker-compose.ymlについて説明する

はじめに

docker-compose.ymlは一見意味がわからないですが、一つ一つ調べると理解できたので説明していきます。

docker-compose.yml

docker-compose.yml
version: '3'
services:
  web:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - db
    volumes:
      - .:/API_sample
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
  db:
    image: mysql:5.7
    volumes:
      - mysql_data:/var/lib/mysql/
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
volumes:
  mysql_data:

version

docker-composeのバージョンを指定します。
現時点では、version3が最新版のようです。

リファレンスはこちら

services

アプリケーションを動かすための要素です。
ネストされたwebdbは各要素で、自分で好きな名前をつけることができます。

build

docker-compose buildのときにビルドするためのDockerfileのパスを指定しています。
ここでは、Dockerfileもdocker-compose.ymlもプロジェクト直下にあるのでこのような書き方をしています。

ports

各serviceのポート番号を指定しています。
ホストOS(local)のポート番号:dockerコンテナ内のポート番号という書き方ができます。
ここでは、railsのポート番号が3000なので、3000:3000という書き方をしています。

1台のPCでDockerを使った複数のプロジェクトを扱うときに、ポート番号が重複するのを防ぐことができます。
たとえば3001:3001という書き方をすると、localhost:3001でサーバーに接続できます。

depends_on

コンテナの依存関係を設定します。
今回はdbに依存関係があることを明記しています。

こうすることで、docker-compose upのときにDBを先に起動してくれます。

volumes

dockerの中でデータを永続化する設定です。
この設定をすることで、コンテナ内のデータは消えずに残ってくれます。

上記の例だと、webでは相対パスのすべてのソース(API_sample)をコンテナのAPI_sampleにマウントしています。
そして、dbではdbコンテナ内の/var/lib/mysqlをmysql_dataという名前でマウントしています。
このmysql_dataは最後の2行でvolumesとして設定されています。

volumeは下記コマンドで一覧を見ることができます。

$ docker volume ls

command

docker-compose upで実行されるコマンドです。
upと同時に、bundle exec rails s -p 3000 -b '0.0.0.0'が実行されてサーバーに接続できるようになります。

image

コンテナ作成に必要なイメージを指定しています。
ここでは、dbのコンテナ作成に、mysql5.7を使用すると設定しています。

environment

環境変数を設定します。
database.ymlに書かれているパスワードを環境変数として設定し、mysqlに接続できるようにします。

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

HTTPリクエストと(Postgre)SQLを自動化するスクリプトを書いてみた

ローカル環境において、手動でHTTPリクエストや(Postgre)SQLを実行するのが辛くなってきたため、それらを自動化するBashスクリプトを書いてみました。
今回はこのスクリプトを解説いたします。

GitHubリポジトリはこちらです

想定している環境

  • ローカル環境
  • DBサーバ(PostgreSQL)がDocker上に存在

必要なライブラリのチェック

スクリプトではdocker、psql、jq、curlを使用しているため、これらがインストールされているかをチェックします。
各ライブラリの名前を、for文のinに列挙しています。
which コマンドでエラーが出力された場合、usage()を呼び出し、標準出力は捨てています。

function usage() {
  echo "please install docker, psql, jq, and curl"
  exit 1
}

# check requirements
for libName in docker psql jq curl; do
  which $libName > $DEVNULL || usage
done

(テスト用)既存のPostgreSQLコンテナの削除

スクリプトのテスト用に作成したDBコンテナがあれば削除します(実際には既存のDBコンテナが立っているはずなので、この処理はコメントアウトされるでしょう)。
docker ps -aで、指定の名前のコンテナが存在するかを0or1で取得します。空白が入るためsedで除去しています。
コンテナが存在した場合、停止と削除を実行します。

# remove existing container
exist=$(docker ps -a | grep $NAME | wc -l | sed 's/ //g')
if [[ $exist = 1 ]]; then
  docker stop $NAME > $DEVNULL && docker rm $NAME > $DEVNULL
  echo "existing container $NAME removed"
fi

(テスト用)PostgreSQLコンテナの起動、疎通が確認できるまで待機

スクリプトのテスト用に、PostgreSQLのコンテナを起動します(実際には既にDBコンテナが立っているはずなので、この処理はコメントアウトされるでしょう)。
起動直後にpsqlを実行してしまうと接続できずにエラーとなるため、疎通が確認できるまで待つようにしています。
ここではpsql -c \lを実行し、エラーが出力された場合はwhileをループするようにしています。

function healthcheck() {
  PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -c '\l' > $DEVNULL 2>&1 || return 1
  return 0
}

# startup postgres
docker run -it -d -p $PORT:$PORT --name $NAME -e POSTGRES_PASSWORD=$PASSWORD postgres:latest > $DEVNULL

# wait for container running up
while true; do
  sleep 0.1 && healthcheck || continue
  break
done

(テスト用)データベース、テーブルの作成、レコードのinsertとselect

テスト用PostgreSQLコンテナ内にDB、Tableを作成し、レコードをinsert, selectします(実際には既存のDBコンテナが立っているはずなので、この処理はコメントアウトされるでしょう)。

# (temporary) preparation postgres
PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -c "create database $DB"
PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -d $DB << EOF
  create table fruit (id serial PRIMARY KEY, name text, price integer);
  insert into fruit (name, price) values ('apple', 100), ('orange', 200), ('lemon', 300);
EOF

# select all records before updates
for i in {1..3} ; do
PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -d $DB << EOF
select name, price from fruit where id = ${i};
EOF
done

HTTPリクエストで取得したResponseを元にSQLを実行

curlでデータを取得します。ここではBitBankのPublic APIを利用しています。
取得したdataはヒアドキュメント内で参照され、psqlが実行されます。

# fetch data by HTTP Request
# following response should be returned
# {
#   "success": 0,
#   "data": {
#     "code": 10000
#   }
# }
data=$(curl -s $URL | jq '.data | .code')
echo $data

# update record with the data
PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -d $DB << EOF
  update fruit set price = ${data} where name = 'apple';
  update fruit set price = ${data} where name = 'orange';
EOF

レコードが更新されたことを確認

取得したdataによってレコードが更新されていることを確認します。

# select all records after updates
for i in {1..3} ; do
PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -d $DB << EOF
select name, price from fruit where id = ${i};
EOF
done

スクリプト全体

上記をまとめると、以下となります。
実際には下記のコードをアレンジしてご使用頂くかと思います。

#!/bin/bash

readonly NAME='test_postgres'
readonly PASSWORD='password'
readonly HOST='0.0.0.0'
readonly PORT='5432'
readonly USER='postgres'
readonly DB='temp'

readonly DEVNULL='/dev/null'

readonly URL='https://public.bitbank.cc'

function healthcheck() {
  PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -c '\l' > $DEVNULL 2>&1 || return 1
  return 0
}

function usage() {
  echo "please install docker, psql, jq, and curl"
  exit 1
}

# check requirements
for libName in docker psql jq curl; do
  which $libName > $DEVNULL || usage
done

# remove existing container
exist=$(docker ps -a | grep $NAME | wc -l | sed 's/ //g')
if [[ $exist = 1 ]]; then
  docker stop $NAME > $DEVNULL && docker rm $NAME > $DEVNULL
  echo "existing container $NAME removed"
fi

# startup postgres
docker run -it -d -p $PORT:$PORT --name $NAME -e POSTGRES_PASSWORD=$PASSWORD postgres:latest > $DEVNULL

# wait for container running up
while true; do
  sleep 0.1 && healthcheck || continue
  break
done

# (temporary) preparation postgres
PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -c "create database $DB"
PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -d $DB << EOF
  create table fruit (id serial PRIMARY KEY, name text, price integer);
  insert into fruit (name, price) values ('apple', 100), ('orange', 200), ('lemon', 300);
EOF

# select all records before updates
for i in {1..3} ; do
PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -d $DB << EOF
select name, price from fruit where id = ${i};
EOF
done

# fetch data by HTTP Request
# following response should be returned
# {
#   "success": 0,
#   "data": {
#     "code": 10000
#   }
# }
data=$(curl -s $URL | jq '.data | .code')
echo $data

# update record with the data
PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -d $DB << EOF
  update fruit set price = ${data} where name = 'apple';
  update fruit set price = ${data} where name = 'orange';
EOF

# select all records after updates
for i in {1..3} ; do
PGPASSWORD=$PASSWORD psql -h $HOST -U $USER -d $DB << EOF
select name, price from fruit where id = ${i};
EOF
done

さいごに

久々にシェルスクリプトを書いたのですが、楽しいですね。
こんな書き方があるよ、といったコメント等あれば是非お願いいたします。

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

コーディング未経験のPO/PdMのためのRails on Dockerハンズオン、Rails on Dockerハンズオン vol.14 - TDDでPost機能をコーディング part3 -

はじめに

こんにちは!
またまたPost機能の開発の続きです。今回でラスト!

前回のソースコード

前回のソースコードはこちらに格納してます。今回のハンズオンからやりたい場合はこちらからダウンロードしてください。

前回の残り

  1. 未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
  2. 未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
  3. サインイン済のユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
  4. サインイン済のユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
  5. サインイン済のユーザーは、プロフィールページで自身のポストを投稿日時降順で閲覧できること
  6. サインイン済のユーザーが、プロフィールページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと

残り6シナリオ。ユーザー詳細ページにそのユーザーのポストを表示する機能ですね。

では早速最後のコーディングをしていきましょう!!

未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること

ユーザー詳細ページでそのユーザーのポストが投稿日時降順で表示されていることと、他のユーザーのポストが表示されていないことを検証します。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること" do
+     # テスト用のユーザーを作成する
+     user1 = create_user(1)
+     user2 = create_user(2)
+     # ポストを用意する
+     posts1 = []
+     posts1.unshift Post.create(content: "First Post!!", user: user1)
+     posts1.unshift Post.create(content: "Second Post!!", user: user1)
+     posts2 = []
+     posts2.unshift Post.create(content: "初めてのポスト", user: user2)
+     posts2.unshift Post.create(content: "2回目のポスト", user: user2)
+
+     # user1のユーザー詳細ページにアクセスする
+     visit user_path(user1)
+
+     # user1のポストが投稿日時降順で表示されていることを検証する
+     posts1.each_with_index do |post, i|
+       expect(find("#posts_list").all(".post-item")[i]).to have_text post.user.name
+       expect(find("#posts_list").all(".post-item")[i]).to have_text post.content
+     end
+     # user2のポストは表示されないことを検証する
+     posts2.each do |post|
+       expect(page).not_to have_text post.user.name
+       expect(page).not_to have_text post.content
+     end
+
+     # user2のユーザー詳細ページにアクセスする
+     visit user_path(user2)
+
+     # user2のポストが投稿日時降順で表示されていることを検証する
+     posts2.each_with_index do |post, i|
+       expect(find("#posts_list").all(".post-item")[i]).to have_text post.user.name
+       expect(find("#posts_list").all(".post-item")[i]).to have_text post.content
+     end
+     # user1のポストは表示されないことを検証する
+     posts1.each do |post|
+       expect(page).not_to have_text post.user.name
+       expect(page).not_to have_text post.content
+     end
+   end        
  end

少し長いですが、今までの延長で理解できるコードになっているはずです!(コメントアウトも参考にしてね。)
さて、このテストを回してみましょう。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい 未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
     Failure/Error: expect(find("#posts_list").all(".post-item")[i].find(".post-user-name")).to have_text user.name

     Capybara::ElementNotFound:
       Unable to find css "#posts_list"

Finished in 27.77 seconds (files took 5.67 seconds to load)
16 examples, 1 failure

この時点では#posts_listがないと怒られます。posts_list、つまりユーザー詳細ページでポストを表示する機能をコーディングしていないので、テストが失敗しています。

さて、今回のposts_listですが、ポストページで同じようにポストの一覧を表示するViewを作りました。開発を効率的に進めるために、是非その時の機能を利用したいですね。
Railsでは部分テンプレート(Partial Template)という機能があります。複数のテンプレートから呼び出されるような一部分のViewを別ファイルに切り出して、各テンプレートからそれを呼び出すようなイメージです。
百聞は一見にしかずですので、まずは試してみましょう。
まずは、ポストページのposts_list配下の要素を部分テンプレート化してみます。

# touch app/views/posts/_posts_list.html.erb

部分テンプレートは頭に_をつけるのが習わしです。

app/views/posts/_posts_list.html.erb
<% posts.each do |post| %>
  <div class="card post-item my-1">
    <div class="card-body">
      <h5 class="card-title"><%= link_to post.user.name, post.user, class: "post-user-name" %></h5>
      <p class="card-text"><%= safe_join(post.content.split("\n"), tag(:br)) %></p>
    </div>
  </div>
<% end %>

部分テンプレートはこんな感じで書きます。posts/index.html.erbに書いていた内容と変わらないです。唯一変わるポイントは最初のeachする配列の変数が@postsからpostsになっていることです。
部分テンプレートは別のテンプレートファイルから呼び出されますが、その時にインスタンス変数でなくても変数を渡すことができます。これも実際にみてみた方が早いと思いますので、まずはposts/index.html.erbからこの部分テンプレートを読み込んで今と変わらない状態になることを確認してみましょう。

app/views/posts/index.html.erb
  ...
  <div id="posts_list" class="my-5">
-   <% @posts.each do |post| %>
-     <div class="card post-item my-1">
-       <div class="card-body">
-         <h5 class="card-title"><%= link_to post.user.name, post.user, class: "post-user-name" %></h5>
-         <p class="card-text"><%= safe_join(post.content.split("\n"), tag(:br)) %></p>
-       </div>
-     </div>
-   <% end %>
+   <%= render partial: "posts_list", locals: { posts: @posts } %>
  </div>
  ...

呼び出し方はrender partial:に対して適用したいテンプレートのファイル名(頭の_は除く)を指定するだけです。さらにオプションでlocals:の後に{ 変数名: 値 }をつけることで部分テンプレートに変数を受け渡すことができます。今回の例では@postsを部分テンプレート内のpostsに代入させていることになります。
変数は複数受け渡すことができ、その場合は{ 変数名1: 値1, 変数名2: 値2 }のように,で区切るだけです。

ここで一度デグレが起きていないかテストを実行しておきましょう。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい 未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
     Failure/Error: expect(find("#posts_list").all(".post-item")[i].find(".post-user-name")).to have_text user.name

     Capybara::ElementNotFound:
       Unable to find css "#posts_list"

Finished in 37.59 seconds (files took 6.58 seconds to load)
16 examples, 1 failure

今取り掛かっているシナリオのテスト失敗だけなので、ポストページに関するテスト失敗は起きていませんね。うまく部分テンプレートが機能しているようです。

さて、ユーザー詳細ページでもこの部分テンプレートを利用しましょう。
ユーザー詳細ページではそのユーザーのポストだけを表示したいので、部分テンプレートに渡すposts変数にはそのユーザーのポストのArrayを渡してあげればいいことになります。

app/views/users/show.html.erb
  <div class="container my-5">
    <% flash.each do |msg_type, msg| %>
      <div class="alert alert-<%= msg_type %>"><%= msg %></div>
    <% end %>
    <%= @user.name %>
    <br>
    <%= @user.email %>
+
+   <div id="posts_list" class="my-5">
+     <%= render partial: "posts/posts_list", locals: { posts: @user.posts.order(created_at: :desc) } %>
+   </div>
  </div>

先ほどと少し違うのは、_posts_list.html.erbがこのファイルとは別のディレクトリ(posts/)にあるので、そのディレクトリも含めて部分テンプレートファイル名を指定しています。(posts/posts_list
また、posts変数には@user.posts.order(created_at: :desc)でそのユーザーのポストを作成日時降順で取得したArrayを部分テンプレートに渡しています。

ではテストを回してみましょう。

# rspec spec/system/07_posts_spec.rb

Finished in 50.91 seconds (files took 7.01 seconds to load)
16 examples, 0 failures

Greenになりました!

未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと

ポストページの場合はユーザー名クリックでユーザー詳細ページへ遷移させていましたが、ユーザー詳細ページ上では

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと" do
+     # テスト用のユーザーを作成する
+     user = create_user(1)
+     # テスト用のポストを作成する
+     posts = []
+     posts.unshift Post.create(content: "First Post!!", user: user)
+     posts.unshift Post.create(content: "Second Post!!", user: user) 
+
+     # userのユーザー詳細ページにアクセスする
+     visit user_path(user)
+
+     # ポストのユーザー名がリンクになっていないことを検証する
+     posts.each_with_index do |post, i|
+       expect(find("#posts_list").all(".post-item")[i]).not_to have_selector("a.post-user-name")
+     end
+   end
  end

ポストページと同じ部分テンプレートを使っているので、現在はポストのカードの中のユーザーの名前が表示されている要素はpost-user-nameをclass属性に付与されているaタグになっています。
これがリンクを作っているところなので、この要素がない状態であれば、ユーザー名をクリックしても何も起こらないことを検証できます。

# rspec spec/system/07_posts_spec.rb
Failures:

  1) ユーザーとして、ポストを投稿したい 未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
     Failure/Error: expect(find("#posts_list")).not_to have_selector("a.post-user-name")
       expected not to find visible css "a.post-user-name" within #<Capybara::Node::Element tag="div" path="/HTML/BODY[1]/DIV[1]/DIV[1]">, found 2 matches: "John Smith", "John Smith"

Finished in 41.18 seconds (files took 5.05 seconds to load)
17 examples, 1 failure

現在はリンクがついてしまっているのでこれをなんとかします。

link_toを使ってリンクを作っていますが、link_to_unless_currentを使ってみます。使い方はlink_toと変わりないのですが、リンク先が今のパスの場合は単なるテキストを表示してくれるメソッドです。

app/views/posts/_posts_list.erb
- <h5 class="card-title"><%= link_to post.user.name, post.user, class: "post-user-name" %></h5>
+ <h5 class="card-title"><%= link_to_unless_current post.user.name, post.user, class: "post-user-name" %></h5>

これでテストを実行してみましょう。

# rspec spec/system/07_posts_spec.rb

Finished in 34.46 seconds (files took 4.86 seconds to load)
17 examples, 0 failures

無事テストがパスしました。ポストページの方もデグレは起きていないかも全てのテストをパスしていることから確認できますね。

サインイン済のユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること

上の2つのテストのサインイン済版ですね。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "サインイン済のユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること" do
+     # テスト用のユーザーを作成する
+     user1 = create_user(1)
+     user2 = create_user(2)
+     # ポストを用意する
+     posts1 = []
+     posts1.unshift Post.create(content: "First Post!!", user: user1)
+     posts1.unshift Post.create(content: "Second Post!!", user: user1)
+     posts2 = []
+     posts2.unshift Post.create(content: "初めてのポスト", user: user2)
+     posts2.unshift Post.create(content: "2回目のポスト", user: user2)
+     # user1でサインインする
+     sign_in(user1)
+
+     # user2のユーザー詳細ページにアクセスする
+     visit user_path(user2)
+
+     # user2のポストが投稿日時降順で表示されていることを検証する
+     posts2.each_with_index do |post, i|
+       expect(find("#posts_list").all(".post-item")[i]).to have_text post.user.name
+       expect(find("#posts_list").all(".post-item")[i]).to have_text post.content
+     end
+     # user1のポストは表示されないことを検証する
+     posts1.each do |post|
+       expect(page).not_to have_text post.user.name
+       expect(page).not_to have_text post.content
+     end
+   end
  end

未サインインの時と検証内容は同じですね。

# rspec spec/system/07_posts_spec.rb

Finished in 36.67 seconds (files took 7.58 seconds to load)
18 examples, 0 failures

すでに実装済みですのでテストはGreenです。

サインイン済のユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと

これも未サインインで同じテストをしているのでそれをパクります。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "サインイン済のユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと" do
+     # テスト用のユーザーを作成する
+     user1 = create_user(1)
+     user2 = create_user(2)
+     # テスト用のポストを作成する
+     posts = []
+     posts.unshift Post.create(content: "First Post!!", user: user2)
+     posts.unshift Post.create(content: "Second Post!!", user: user2) 
+     # user1でサインインする
+     sign_in(user1)
+
+     # user2のユーザー詳細ページにアクセスする
+     visit user_path(user2)
+
+     # ポストのユーザー名がリンクになっていないことを検証する
+     posts.each_with_index do |post, i|
+       expect(find("#posts_list").all(".post-item")[i]).not_to have_selector("a.post-user-name")
+     end
+   end
  end
# rspec spec/system/07_posts_spec.rb

Finished in 41.16 seconds (files took 5.61 seconds to load)
19 examples, 0 failures

これも実装済みなのでテストがパスしていますね。

サインイン済のユーザーは、プロフィールページで自身のポストを投稿日時降順で閲覧できること

これも以前のテストとほぼ同じ。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "サインイン済のユーザーは、プロフィールページで自身のポストを投稿日時降順で閲覧できること" do
+     # テスト用のユーザーを作成する
+     user1 = create_user(1)
+     user2 = create_user(2)
+     # ポストを用意する
+     posts1 = []
+     posts1.unshift Post.create(content: "First Post!!", user: user1)
+     posts1.unshift Post.create(content: "Second Post!!", user: user1)
+     posts2 = []
+     posts2.unshift Post.create(content: "初めてのポスト", user: user2)
+     posts2.unshift Post.create(content: "2回目のポスト", user: user2)
+     # user1でサインインする
+     sign_in(user1)
+
+     # user1のプロフィールページにアクセスする
+     visit user_path(user1)
+
+     # user1のポストが投稿日時降順で表示されていることを検証する
+     posts1.each_with_index do |post, i|
+       expect(find("#posts_list").all(".post-item")[i]).to have_text post.user.name
+       expect(find("#posts_list").all(".post-item")[i]).to have_text post.content
+     end
+     # user2のポストは表示されないことを検証する
+     posts2.each do |post|
+       expect(page).not_to have_text post.user.name
+       expect(page).not_to have_text post.content
+     end
+   end
  end

ほい。ではテストを回しましょう。

# rspec spec/system/07_posts_spec.rb

Finished in 41.83 seconds (files took 6.77 seconds to load)
20 examples, 0 failures

これもパス。

サインイン済のユーザーが、プロフィールページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと

これも同じようなテストをすでにしていますね。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "サインイン済のユーザーが、プロフィールページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと" do
+     # テスト用のユーザーを作成する
+     user = create_user(1)
+     # テスト用のポストを作成する
+     posts = []
+     posts.unshift Post.create(content: "First Post!!", user: user)
+     posts.unshift Post.create(content: "Second Post!!", user: user) 
+     # user1でサインインする
+     sign_in(user)
+
+     # user1のプロフィールページにアクセスする
+     visit user_path(user)
+
+     # ポストのユーザー名がリンクになっていないことを検証する
+     posts.each_with_index do |post, i|
+       expect(find("#posts_list").all(".post-item")[i]).not_to have_selector("a.post-user-name")
+     end
+   end
  end

これもパスするはず。

# rspec spec/system/07_posts_spec.rb

Finished in 43.64 seconds (files took 6.54 seconds to load)
21 examples, 0 failures

パスしましたね。

ここまででポスト機能で定義したテストシナリオは全てパスできるアプリケーションを作ることができました!
最後に、今までのテストシナリオも含めてデグレの確認をしておきましょう!

# rspec

Finished in 1 minute 56.88 seconds (files took 6.09 seconds to load)
91 examples, 0 failures

2分ほど時間がかかりましたが、全てのテストをクリアできていました!!

まとめ

今日はここまでです!前回、前々回と3回に渡ってポスト機能をTDDでコーディングしてきましたがいかがだったでしょうか?
モデルの関連付け(has_many, belongs_to)や部分テンプレート(Partial Template)など新しく使ったものもありましたが、基本的な部分はハードルなくコーディングできるようになったのではないでしょうか?
実際にサービスをリリースするとなると、例えばアイコン登録とか、フォロー機能とか、いいね機能とか、、、作りたい機能がどんどんでてくるとは思いますが、すでに自分で調べながらコーディングをしていくことに対するハードルはなくなったんじゃないでしょうか?
ということでこのハンズオンのコーディング部分はこれで以上にしたいと思います。

最後はデプロイです!次とその次、2回に分けてアプリケーションをHerokuEKSにデプロイしてみようと思います。ここまでできれば、自分の好きなサービスを作って世に公開することができます。

ではまた次週!

後片付け

# exit
$ docker-compose down

本日のソースコード

Other Hands-on Links

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

Dockerのメモリ使用量を確認したい

はじめに

Dockerを使用する中で、この子たちがどのくらいメモリを食っているのかを確認したくなったので、調べました。

確認方法

アクティビティモニタで確認

スクリーンショット 2020-04-02 1.21.16.png

docker stats

実行中のDocker Containerのメモリ使用量を docker stats コマンドを使って調べることができるみたいです。

https://docs.docker.com/engine/reference/commandline/stats/

$ docker stats
CONTAINER ID        NAME                          CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
0d173431ff71        concourse-worker_1   14.37%              32.16MiB / 1.943GiB   1.62%               53.4MB / 86.6MB     0B / 0B             22
91e1a99d4d30        concourse-web_1      24.25%              32.19MiB / 1.943GiB   1.62%               356MB / 318MB       0B / 0B             13
189cc19df954        concourse-db_1       0.82%               49.21MiB / 1.943GiB   2.47%               249MB / 230MB       0B / 0B             24
47590fe69e09        sonarqube-db_1       0.00%               6.246MiB / 1.943GiB   0.31%               6.57kB / 1.22kB     0B / 0B             7
内容
CONTAINER ID ContainerのID
NAME Containerの名前
CPU % Containerが使用しているCPU使用率
MEM USAGE / LIMIT 使用しているメモリ / Dockerに許可されたメモリ(Docker > Stettings > Resources)
MEM % Containerが使用しているメモリ使用率
NET I/O Containerが送受信したデータ量
BLOCK I/O Containerがブロックデバイスに読み書きしたデータ量
PIDS プロセスID

オプション

$ docker stats --help

Usage:  docker stats [OPTIONS] [CONTAINER...]

Display a live stream of container(s) resource usage statistics

Options:
  -a, --all             Show all containers (default shows just running)
      --format string   Pretty-print images using a Go template
      --no-stream       Disable streaming stats and only pull the first result
      --no-trunc        Do not truncate output

起動中の全Container表示

$ docker stats -a
CONTAINER ID        NAME                          CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
ea19cee314d2        studyyml_concourse-worker_1   0.16%               49.73MiB / 1.943GiB   2.50%               302kB / 487kB       0B / 0B             20
11150df2f0af        studyyml_concourse-web_1      0.25%               27.84MiB / 1.943GiB   1.40%               1.85MB / 1.8MB      0B / 0B             10
17a9843e204a        studyyml_sonarqube_1          0.00%               0B / 0B               0.00%               0B / 0B             0B / 0B             0
876602d0178e        studyyml_concourse-db_1       0.01%               45.26MiB / 1.943GiB   2.27%               1.46MB / 1.3MB      0B / 0B             22
06b430623fc0        studyyml_sonarqube-db_1       0.00%               6.891MiB / 1.943GiB   0.35%               4.23kB / 2.44kB     0B / 0B             7

リアルタイムで更新されていくのですが、 control + C で中止することができます。(最初これ知らなくて焦りました・・・)

フォーマットして表示

https://docs.docker.com/engine/reference/commandline/stats/#formatting

.Container , .Name , .ID , .CPUPerc , .MemUsage , .NetIO , .BlockIO , .MemPerc , .PIDs が選択可能。

$ docker stats --format "{{.Name}} : {{.MemUsage}}"
studyyml_concourse-worker_1 : 50.04MiB / 1.943GiB
studyyml_concourse-web_1 : 28.21MiB / 1.943GiB
studyyml_concourse-db_1 : 46.7MiB / 1.943GiB
studyyml_sonarqube-db_1 : 6.883MiB / 1.943GiB

一度だけ表示

$ docker stats --no-stream
CONTAINER ID        NAME                          CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
ea19cee314d2        studyyml_concourse-worker_1   0.14%               50.05MiB / 1.943GiB   2.52%               851kB / 1.38MB      0B / 0B             21
11150df2f0af        studyyml_concourse-web_1      0.27%               28.23MiB / 1.943GiB   1.42%               4.7MB / 4.39MB      0B / 0B             10
876602d0178e        studyyml_concourse-db_1       0.14%               46.62MiB / 1.943GiB   2.34%               3.5MB / 3.26MB      0B / 0B             23
06b430623fc0        studyyml_sonarqube-db_1       0.00%               6.902MiB / 1.943GiB   0.35%               4.44kB / 2.44kB     0B / 0B             7

切り捨て表示しない

ContainerIDが全表示になりました。

$ docker stats --no-trunc
CONTAINER ID                                                       NAME                          CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
ea19cee314d2aa230ae71215fbae442b08386bcc4c3d963f1e02550c1b51f909   studyyml_concourse-worker_1   3.35%               49.99MiB / 1.943GiB   2.51%               923kB / 1.49MB      0B / 0B             21
11150df2f0af1198e3fe689697621a3cb5270d81a572de6a6d67a588b2b17173   studyyml_concourse-web_1      0.00%               28.28MiB / 1.943GiB   1.42%               5.04MB / 4.71MB     0B / 0B             10
876602d0178eb1ae7b7333c2203837fa2f292dc9f89a2832f6622111b2be054c   studyyml_concourse-db_1       0.09%               46.86MiB / 1.943GiB   2.35%               3.75MB / 3.48MB     0B / 0B             23
06b430623fc0a59df6bc2ca9cf9c7add45465c59ff3ad08e28661fe871a1687f   studyyml_sonarqube-db_1       0.02%               6.883MiB / 1.943GiB   0.35%               4.44kB / 2.44kB     0B / 0B             7
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む