20200529のdockerに関する記事は20件です。

localに特定のdocker imageが存在しないときや、requirements.txtが変更されているときにbuildするshell script

コード

#!/bin/bash
if test -z "$(docker images -q my-image:my-version)" -o -n "$(git diff --exit-code requirements.txt)"; then
  docker build -t my-image:my-version -f docker/Dockerfile .
fi
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

localに特定のdocker imageが存在しないときにbuildするshell script

コード

#!/bin/bash
if test -z "$(docker images -q my-image:my-version)"; then
  docker build -t my-image:my-version -f docker/Dockerfile .
fi

蛇足

versionをプロジェクト内のpython, shell script両方から参照したい。できれば環境変数を使わずに。

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

tiberiusを使ってRustでMSSQLに接続する

はじめに

RustからMSSQLを操作したいと思い、いい感じのcrateがないか探していました。
色々調べるとDB操作関連に関してはDieselというORMがよく使われているようでした。
https://github.com/diesel-rs/diesel

「情報もある程度ありそうだしこれを使ってみよう!」
と思っていたら‥

Supported databases:

1. PostgreSQL
2. MySQL
3. SQLite

だめやん‥

というわけで他の方法を探すことにしました。

tiberius

tiberiusというcrateがありました。
https://crates.io/crates/tiberius

スター数があまり多くないのは気になりますが、検証ということでとりあえずこちらを使ってみることにします。

注意

「rust tiberius」でググるとgithubのページが二つ出てくると思います。

前者が最新のリポジトリのようなので気をつけてください。

確認環境

今回はそれぞれ以下のバージョンを使用しました。

  • rustc
    • 1.45.0-nightly(dockerの「rustlang/rust:nightly」を使用)
  • tiberius
    • 0.4.0-alpha.6
      • Crates.ioに公開されているバージョンを使いたかったのですが、いい感じのexampleが見つけられず現時点の最新版(2020/05/29 時点)を使っています。申し訳ありません‥

実装

ディレクトリ構造

今回作成したプロジェクトは以下の構造になっています。

├── Dockerfile
├── app
│   ├── Cargo.lock
│   ├── Cargo.toml
│   └── target ※このディレクトリ配下は省略
│   └── src
│       └── main.rs
└── docker-compose.yaml

それでは一つづつみていきましょう!

Dockerfile

今回はnightlyのバージョンを使います。

FROM rustlang/rust:nightly

docker-compose.yaml

docker-compose.yamlは以下の通りです。
接続するMSSQLは公式で配布されているdocker imageを使います。

また、あらかじめテスト用のデータを作成するためにアプリケーションとmssqlの他にmssqlのcliツールを使用するコンテナも作成します。

version: '3.7'
services:
  app:
    build: .
    volumes:
      - ./app:/app
    working_dir: /app
    tty: true
    depends_on: 
      - db
  db:
    image: microsoft/mssql-server-linux:2017-GA
    environment:
      ACCEPT_EULA: Y
      SA_PASSWORD: "P@ssw0rd!"
    volumes:
      - rust-mssql-data:/var/opt/mssql/
    ports:
      - "1433:1433"
  mssql_cil_client:
    image: node:alpine
    tty: true
volumes:
  rust-mssql-data:
    driver: local

Cargo.toml

「Cargo.toml」はdependenciesだけ抜粋しています。
サンプルを動かすにはtiberiusの他にも幾つか必要なcrateがあるので全て追加してください。

[dependencies]
tiberius = { git = "https://github.com/prisma/tiberius.git" }
tokio = { version = "0.2", features = ["macros"] }
anyhow = "1.0.31"
futures = "0.3.5"

tiberiusについては「動作確認環境」にも書きましたが最新版を使いたかったのでgithubから直接ダウンロードするようにしています。

サンプル用確認用のテーブル

サンプルを実装する前にテスト用のデータベースを作成してサンプルデータを登録しておきます。

コンテナの起動

docker-compose up -d

MSSQLが起動するので、データベースやテーブルを作成します。

cli用のコンテナにログイン

docker-compose exec mssql_cil_client /bin/sh

sql-cliのinstall

npm install -g sql-cli

mssqlにログイン

mssql -s db -u sa -p P@ssw0rd!

データベースの作成

create database test;
use test;

テーブルの作成

create table users \
( \
    id int not null, \
    name nvarchar(128), \
    constraint pk_users primary key (id) \
);

テストデータの登録

insert into users(id,name) values(1,'Taro');
insert into users(id,name) values(2,'Jiro');
insert into users(id,name) values(3,'Hanako');

テストデータの確認

想定通りにデータが登録されているか確認しましょう!

select * from users;

以下ように表示されればOKです!

id  name
--  ------
1   Taro
2   Jiro
3   Hanako

3 row(s) returned

Executed in 1 ms

問題なければ、mssqlおよびコンテナからログアウトしましょう

.quit
exit

main.rs

それでは先ほど作成したテストデータをRustから取得してみます!

以下にテーブルを作成するサンプルが格納されています。
今回はこれをベースにしました。
https://github.com/prisma/tiberius/blob/master/examples/new.rs

use tiberius::{AuthMethod, Client};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut builder = Client::builder();
    builder.host("db");
    builder.port(1433);
    builder.database("test");
    builder.authentication(AuthMethod::sql_server("SA", "P@ssw0rd!"));
    builder.trust_cert();

    let mut conn = builder.build().await?;
    let results = conn
        .simple_query("select id, name from Users;")
        .await?
        .into_results()
        .await?;

    for val in results.iter() {
        // 取得した件数分ループする
        for inner in val.iter() {
            // id列の情報を取得
            if let Some(id) = inner.get::<i32, _>("id") {
                print!("id = {} ", id);
            }
            // name列の情報を取得
            if let Some(name) = inner.get::<&str, _>("name") {
                println!("name = {}", name);
            }
        }
    }
    Ok(())
}

とりあえず使ってみるだけであれば、特に難しい点はなさそうです。
resultsが2次元配列になっている理由が今ひとつわからず気になりました。

細かいことは一旦気にせず上記コードを実行してみましょう!

docker-compose exec app cargo run

結果

id = 1 name = Taro
id = 2 name = Jiro
id = 3 name = Hanako

いい感じですね!

今回作ったサンプルは以下で公開しています。
https://github.com/shushutochako/tiberius_sample

さいごに

Rustでmssqlを操作するサンプルがあまり多くない印象で少し手こずってしまいました。
これから色々と実装例が増えてくることを切に願います(´·ω·`)

他にもRustからMSSQLを操作する場合はODBCのWrapperライブラリを使う方法などもあるようです。
ODBCクレートを使用する場合はこちらの記事が非常に参考になりました。
https://setsuna-no-matataki.hateblo.jp/entry/2020/04/30/182305


ZEROBILLBANKでは一緒に働く仲間を募集中です。
ZEROBILLBANK JAPAN Inc.

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

Dockerを使ってHTTPS環境を構築

Dockerを使ってHTTPS環境を構築

Dockerを使ってHTTPS環境を構築出来ないかと調べていたらHTTPS-PORTALという便利なものがあったので、試しにローカルでHTTPS接続できる環境を構築してみました。

HTTPS-PORTALとは?

HTTPS-PORTAL is a fully automated HTTPS server powered by Nginx, Let's Encrypt and Docker. By using it, you can run any existing web application over HTTPS, with only one extra line of configuration.

The SSL certificates are obtained, and renewed from Let's Encrypt automatically.

要するに自動でHTTPSサーバを作ってくれるDockerコンテナです。

前提

VirtualBox(仮想OS)に以下の環境を構築。

  • OS: CentOS7
  • Docker: v19.03.10
  • Docker-compose: v1.25.25

やること

ゲストOSのdocker上に起動したMetabaseにホストOSのブラウザからHTTPSで接続する
※今回はサンプルとしてMetabase(データ分析用のWebアプリ)を使用する

手順

  1. dockerをインストールする ※関連リンクを参照
  2. docker-composeをインストールする ※関連リンクを参照
  3. docker-compose.ymlを作成する
  4. docker-composeでコンテナを起動する
  5. VirtualBoxのネットワーク設定を行う
  6. ブラウザからHTTPSで接続する

docker-compose.ymlを作成する

CentOS7上の適当なディレクトリにdocker-compose.ymlを作成してください。
※今回は /var/lib/docker 直下にファイル作成しました

docker-compose.yml
https-portal:
  image: steveltn/https-portal:1
  ports:
    - '80:80'
    - '443:443'
  links:
    - metabase
  restart: always
  environment:
    STAGE: local
    DOMAINS: 'localhost -> http://metabase:3000'
metabase:
  image: metabase/metabase
  volumes:
    - ~/metabase-data:/metabase-data
  environment:
    - MB_DB_FILE=/metabase-data/metabase.db

https-portalを経由してmetabaseに接続するようなイメージです。
設定内容は以下の情報を元にしています。

docker-composeでコンテナを起動する

dokcer-compose.ymlのあるディレクトリにて以下のコマンドを実行してください。

# docker-composeでコンテナを起動
docker-compose up -d

以下のコマンドでhttps-portalとmetabaseのコンテナが起動していることを確認できます。

# dockerの起動しているコンテナを確認
docker ps

image.png

コンテナを停止したい場合は以下のコマンドです。

# docker-composeでコンテナを停止
docker-compose stop

VirtualBoxのネットワーク設定を行う

ホストOSのブラウザからゲストOSのWebアプリに接続する為にVirtualBoxの設定をします。
ネットワーク設定にて、NATでホストOSの80, 443ポートをゲストOSの80, 443にフォワード設定します。

image.png
image.png

ブラウザからHTTPSで接続する

ブラウザにて https://localhost を表示する
image.png
※オレオレ証明書なので上記の警告画面がでます。

以下のMetabaseの初期画面が表示されていれば成功です。
image.png

あとがき

今回はお試しということでローカル用に環境構築してみました。
(未検証ですが)設定を切り替えればLet's Encryptから取得した証明書を使うようなので社内ツールなどをHTTPS化したい場合に手軽に出来て良さそうです。

関連リンク

以下、環境構築の際に参考にしたサイトです

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

Dockerについて

前提

本日学んだことを書いていきます。

本題

コンテナとは

内部にものを貯める容器・運搬可能

容器にはアプリケーションが入っている。
自宅のPCやラップトップ、データーセンターで稼働しているサーバー
AWSやAZUREやGCPの仮想サーバーへ持ち運び、アプリケーションを実行することができる。

従来の技術モデル

アプリケーションが実行される環境が変わってきている。
一つのハードウェアに一つのOSがあり複数のアプリケーションをインストールして使用する。
このような形が従来型のアプリケーションの実行環境。
少々リソースに無駄があり、コストが高くつくのが難点。

仮想化技術

ハードウェア、OSについては同様。
ハイパーバイザーというシステムをインストール(VMWereやXen、Hyper-V)。
ハイパーバイザーにWindowsやLinuxなどのOSをインストールし、仮想的に一台のサーバーを実行できる(Virtual Server)
ハイパーバイザーは複数のバーチャルサーバを実行することができ、EC2などの仮想サーバーもバーチャルサーバとして実行されている。

メリット

OS丸ごとのバックアップの取得が簡単。

コンテナ

ハードウェアやOSは同様。
コンテナエンジン、コンテナデーモンをインストール。(Dockerが一番有名)
コンテナ同士は独立しており、互いに鑑賞することはない。
それぞれの実行環境がコンテナという一つの容器にまとまっている形をイメージ。

なぜ軽量なのか

コンテナのOSと異なっていてもLinuxのカーネルは互換性があるため互換性の範囲内であればアプリケーションは動作できる。
→コンテナはLinuxイメージが動作している

Linuxイメージ = filesystem / libraries
OS = filesystem / libraries + kernel
それぞれのコンテナ(RedhatやSuze、Ubuntu)はホストOSのカーネルを共有しており、カーネルまで含めて仮想化しているバーチャルサーバと比べて動作が軽量。

コンテナのライフサークル

・Dockerデーモンをホストマシンにインストール
→コンテナイメージを実行したり、ネットワーク周りを管理したり、コンテナ全般の管理を行うもの

・Dockerclientをインストール
→デーモンに対して指示を出す役割のもので、デーモンに対してビルドやコマンドやランコマンドを実行したりする。

・Registry
Dockerイメージを保管する領域。
DockerHubが有名。
AWSではECR、Elastic Container RegistryというプライベートなDockerイメージのアップロード場所をサービスとして提供している。

DockerclientがRunコンマンドをデーモンに対して実行。

デーモンは場合によってRegistryからコンテナイメージをダウンロードし、コンテナとして実行。
※buildコマンドでどのようなコンテナイメージを作成するかによる

コンテナの人生

DockerHubなどのコミュニティにある様々なコンテナ

自分のパソコン(Dockerclient)にpull

行いたい作業に合わせて編集しDockerHubなどのコミュニティ(Registry)にpush

Dockerデーモン(ホスト)に対してRunコマンドを実行

DockerデーモンはRegistryよりイメージをダウンロード

コンテナの実行

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

Qiitaのトレンド情報を保存しておくDocker環境を作ろう!

Qiitaのトレンド情報を保存しておく環境をDockerで作成しました。
基本的にコンテナを立ち上げていれば、毎日勝手にスクレイピング処理が走り、JSON化したトレンド情報を保存してくれます。この記事は以下のような方におすすめです。

  • Qiitaのトレンドを分析しておきたいな
  • Pythonの勉強を少しやってみたいな
  • Dockerちょっと触ってみたい

※保存しておくQiitaのJSONフォーマットについて

  • author(トレンド入りした著者の一覧
  • list(トレンド記事の一覧
  • tag(トレンドの記事に付けられたタグ一覧

実際に保存しておくJSONの中身は以下のようになっています。

author: Qiitaにトレンド入りした著者を取得する

著者のユーザーネームを一覧化。

[
    "uhyo",
    "suin",
    "Yz_4230",
    "atskimura",
    "pineappledreams",
    "Amanokawa",
    "k_shibusawa",
    "minakawa-daiki",
    "morry_48",
    "c60evaporator",
    "takuya_tsurumi",
    "TomoEndo",
    "yhatt",
    "CEML",
    "moritalous",
    "svfreerider",
    "daisukeoda",
    "karaage0703",
    "tommy19970714",
    "tyru",
    "galileo15640215",
    "keitah",
    "mocapapa",
    "akeome",
    "ssssssssok1",
    "yuno_miyako",
    "katzueno",
    "cometscome_phys",
    "mpyw",
    "akane_kato"
]

list: Qiitaにトレンド入りした記事の一覧を取得する

以下の情報を出力します。

  • 記事のUUID(記事のID)
  • 記事のタイトル
  • 記事のURL
  • 記事の著者名
  • LGTM数
  • 記事に付けたれたタグ, タグURL
[
    {
        "article_id":"e66cbca2f582e81d5b16",
        "article_title":"Let's Encryptを使用しているウェブページをブロックするプロキシサーバー",
        "article_url":"https://qiita.com/uhyo/items/e66cbca2f582e81d5b16",
        "author_name":"uhyo",
        "likes":66,
        "tag_list":[
            {
                "tag_link":"/tags/javascript",
                "tag_name":"JavaScript"
            },
            {
                "tag_link":"/tags/node.js",
                "tag_name":"Node.js"
            },
            {
                "tag_link":"/tags/proxy",
                "tag_name":"proxy"
            },
            {
                "tag_link":"/tags/https",
                "tag_name":"HTTPS"
            },
            {
                "tag_link":"/tags/letsencrypt",
                "tag_name":"letsencrypt"
            }
        ]
    },
    {
        "article_id":"83ebaf96caa2c13c8b2f",
        "article_title":"macOSのスクリーンセーバーをHTML・CSS・JSで作る (Swiftスキル不要)",
        "article_url":"https://qiita.com/suin/items/83ebaf96caa2c13c8b2f",
        "author_name":"suin",
        "likes":60,
        "tag_list":[
            {
                "tag_link":"/tags/html",
                "tag_name":"HTML"
            },
            {
                "tag_link":"/tags/css",
                "tag_name":"CSS"
            },
            {
                "tag_link":"/tags/javascript",
                "tag_name":"JavaScript"
            },
            {
                "tag_link":"/tags/macos",
                "tag_name":"macos"
            }
        ]
    }
]

Qiitaのトレンドは1日に2回、毎日5時/17時に更新更新されていますが、そこまで記事の入れ替わりはないので1日1回だけの実行にしておこうと思います。

tag: Qiitaにトレンド入りした記事に付けられたタグを取得する

[
    {
        "tag_link":"/tags/python",
        "tag_name":"Python"
    },
    {
        "tag_link":"/tags/r",
        "tag_name":"R"
    },
    {
        "tag_link":"/tags/%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92",
        "tag_name":"機械学習"
    }
]

上記の記事の一覧でもタグは取得していますが、一個の記事に紐付けられたタグなので、異なる記事で同じタグが付いていた場合重複してしまいます。そのため、重複したタグを省いてトレンド入りしたタグだけ一覧で保存しておく処理にしました。

DockerでPythonが実行できる環境を作成しよう

簡易的なDocker環境を作成していきます。
ディレクトリ構成は以下のような感じ。

├── batch
│   └── py
│       └── article.py
├── docker
│   └── python
│       ├── Dockerfile
│       ├── etc
│       │    └── cron.d
│       │        └── qiita
│       └── requirements.txt
├── docker-compose.yml
└── mnt
    └── json
        ├── author
        ├── list
        └── tag
  • batch directory
    pythonファイルを置いています。
    このファイルがスクレイピングを行う実ファイルです。

  • dockerdirectory
    コンテナの内部で必要なものだったり、実際のcron設定はここで置いています

  • mntdirectory
    host上のディレクトリをマウントしていて、ここにスクレイピングの結果JSONファイルが生成されます

Qiitaのトレンドをスクレイピングで取得しよう (batch directory

batchディレクトリの中にある実ファイルarticle.pyの中身です。
過去にこんな記事を書いていたので、詳しいやり方とかはそっちで解説しています。
≫ Qiitaのトレンド(ランキング)を取得してSlackに送信する
この記事ではあくまでプログラムだけにします。

上記の記事のプログラムとの相違点は以下の2点です。
記事の一覧だけ欲しいねん!って人は上記の記事だけで事足りると思います。

  1. トレンド入りした記事のタグと著者を取得
  2. 取得した内容をJSON化して保存しておく
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
from bs4 import BeautifulSoup
import json
import datetime
import os

def get_article_tags(detail_url):
    tag_list = []

    res = requests.get(detail_url, headers=headers)

    # htmlをBeautifulSoupで扱う
    soup = BeautifulSoup(res.text, "html.parser")

    tags = soup.find_all(class_="it-Tags_item")
    for tag in tags:
        tag_name = tag.get_text()
        tag_link = tag.get('href')

        tag_list.append({
            'tag_name' : tag_name,
            'tag_link': tag_link
        })

    return tag_list

def write_json(json_list, path):
    with open(path, 'w') as f:
        f.write(json.dumps(json_list, ensure_ascii=False, indent=4, sort_keys=True, separators=(',', ':')))

def mkdir(path):
    os.makedirs(path, exist_ok=True)

def get_unique_list(seq):
    seen = []
    return [x for x in seq if x not in seen and not seen.append(x)]

def get_unique_tag(tag_lists):
    tags = []
    for v in tag_lists:
        for i in v:
            tags.append(i)
    return tags        

try:
    # Root URL
    url = "https://qiita.com/"
    headers = {
        "User-Agent" : "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"
    }
    today_date = datetime.datetime.now().date()

    items = []
    item_json = []
    result = []

    res = requests.get(url, headers=headers)

    # htmlをBeautifulSoupで扱う
    soup = BeautifulSoup(res.text, "html.parser")

    try:
        main_items = soup.find(class_="p-home_main") 

        for main_items in soup.find_all():
            if "data-hyperapp-props" in main_items.attrs:
                item_json.append(main_items["data-hyperapp-props"])
        items = json.loads(item_json[1])
    except:
        raise Exception("Not Found Json Dom Info")

    if 'edges' not in items['trend']:
        raise Exception("The expected list does not exist")

    try:
        item_detail_list = []
        tags_list = []
        author_list = []

        for edges in items['trend']['edges']:
            uuid = edges['node']['uuid']
            title = edges['node']['title']
            likes = edges['node']['likesCount']
            article_url =  url + edges['node']['author']['urlName'] + '/items/' + uuid
            author_name = edges['node']['author']['urlName']
            create_at = datetime.datetime.now().date()
            tag_list = get_article_tags(article_url)

            item = {
                'article_title' : title,
                'article_url' : article_url,
                'article_id' : edges['node']['uuid'],
                'likes' : likes,
                'uuid' : uuid,
                'author_name' : author_name,
                'tag_list' : tag_list,
            }

            item_detail_list.append(item)
            tags_list.append(tag_list)
            author_list.append(author_name)

        mkdir('/mnt/json/list/')
        mkdir('/mnt/json/tag/')
        mkdir('/mnt/json/author/')

        # タグをuniqu化
        tags_list = get_unique_tag(tags_list)

        # jsonファイルを書き出し
        write_json(item_detail_list, f"/mnt/json/list/{today_date}.json")
        write_json(tags_list, f"/mnt/json/tag/{today_date}.json")
        write_json(author_list, f"/mnt/json/author/{today_date}.json")
    except:
        raise Exception("Can't Create Json")

except Exception as e:
    # jsonファイル作成失敗
    mkdir('/mnt/log/')
    with open(f'/mnt/log/{today_date}', 'w') as f:
        f.write(e)

次に上記のファイルを実行する環境を作成します。

Pythonを動かすためのDocker部分を作成(docker directory

docker-compose.ymlの作成

ここは大したことしてない。
volumesでPC上のディレクトリとマウント。

version: "3"

qiita_batch:
  container_name: "qiita_batch"
  build: 
    context: ./docker/python
  tty: true
  volumes:
    - ./batch:/usr/src/app
    - ./mnt:/mnt

Dockerfileの作成

Dockerfile汚いのは許して...軽く説明だけ↓

  • コンテナ内のタイムゾーンの設定(cron設定のため
  • cronを反映
  • requirement.txtで必要なモジュールをインストール

cronをちゃんと指定した日本時間で実行したいのであれば、タイムゾーンの設定は必須ですね。
なんかごちゃごちゃやって、ようやく日本時間に変えられたんですが、もっとうまいやり方あるはず...。

cronの設定はetc/cron.d/qiitaにまとめておいて、後々crontabに書き込むような処理にしてます。管理が楽になるのでこっちの方がいいかなと。間違ってもcrontab -rというコマンドは呼び出してはいけない...!!

FROM python:3

ARG project_dir=/usr/src/app
WORKDIR $project_dir

ADD requirements.txt $project_dir/py/
ADD /etc/cron.d/qiita /etc/cron.d/

ENV TZ=Asia/Tokyo

RUN apt-get update && \
    apt-get install -y cron less vim tzdata && \
    rm -rf /var/lib/apt/lists/* && \
    echo "${TZ}" > /etc/timezone && \
    rm /etc/localtime && \
    ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    dpkg-reconfigure -f noninteractive tzdata && \
    chmod 0744 /etc/cron.d/* && \
    touch /var/log/cron.log && \
    crontab /etc/cron.d/qiita && \
    pip install --upgrade pip && \
    pip install -r $project_dir/py/requirements.txt

CMD ["cron", "-f"]

Pythonの実行に必要なパッケージをまとめたrequirement.txtを作成

requirement.txtは自分のMacbookProで使用していたものを出力しただけなので、かなり適当に色々なものが入っています。いらないものは適宜削ってくださいまし。beautifulsoup4requestsjsonだけあれば事足ります。なんか足らん!って人は動かしながら足らないやつpip install!!

appdirs==1.4.3
beautifulsoup4==4.8.1
bs4==0.0.1
certifi==2019.9.11
chardet==3.0.4
Click==7.0
filelock==3.0.12
get==2019.4.13
gunicorn==20.0.4
idna==2.8
importlib-metadata==1.5.0
importlib-resources==1.0.2
itsdangerous==1.1.0
Jinja2==2.11.1
MarkupSafe==1.1.1
post==2019.4.13
public==2019.4.13
query-string==2019.4.13
request==2019.4.13
requests==2.22.0
six==1.14.0
soupsieve==1.9.5
urllib3==1.25.7
virtualenv==20.0.1
Werkzeug==1.0.0
zipp==2.2.0

cron設定

/etc/cron.d/qiitaの中身です

PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

LANG=ja_JP.UTF-8

# Create Qiita JSON (every day AM:10:00)
0 10 * * * python /usr/src/app/py/article.py >> /var/log/cron.log 2>&1

こんな感じ!
あとはdocker-compose up -dでやれば立ち上がるので、放置しておけば勝手にQiitaにスクレイピングしに行ってjsonファイルを作成してくれます。簡易的なDocker環境で出来るのでおすすめ!

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

DISABLE_DATABASE_ENVIRONMENT_CHECK=1 Rails 本番環境でDBデータを消す方法

現在、ポートフォリオを作成している途中のゆーた(@onoblog)です。

ポートフォリオフォリオにこんな感じで、複数のタグを選択して検索する機能を実装しました。

スクリーンショット 2020-05-28 8.50.05.png

本番環境で、段階的に、デプロイしているポートフォリオに、タグを追加したいとなり、seedファイルを編集し、データベースを一回消して追加してみたときのエラーをまとめます。

環境

  • rails 5.2.3
  • carrierwave
  • fog-aws
  • jquery-rails (4.3.5)
  • vue/cli 4.1.2
  • yarn 1.21.1
  • webpacker (5.0.1)
  • Docker 19.03.5
  • docker-compose 1.25.2
  • nginx 1.15.8

対処法

ローカル環境と同じように、DBを削除する方法。

#ターミナルにて

# Dockerの方

$ docker-compose -f staging.yml run app rails db:drop DISABLE_DATABASE_ENVIRONMENT_CHECK=1

# 通常コマンド

$ rails db:drop DISABLE_DATABASE_ENVIRONMENT_CHECK=1
$ RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rake db:drop

原因

Rails5から本番環境では、通常消すことがないように、防止処理が働いているようです。

参考

https://y-yagi.tumblr.com/post/142064773890/rails-5%E3%81%AB%E5%85%A5%E3%81%A3%E3%81%9Fdb%E7%A0%B4%E5%A3%8A%E7%B3%BBtask%E3%81%AE%E9%98%B2%E6%AD%A2%E5%87%A6%E7%90%86%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

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

超基礎からの 速習 Docker (4)

Whale, Docker

本稿は、Christffer Noring さん (@chris_noring) の Learn Docker, from the beginning, part IV を翻訳し、分かりやすいように少しだけ追記、サンプルコードの実行上の補足等を行ったものです。シリーズ翻訳の意図については 超基礎からの 速習 Docker (1) の冒頭に掲載しています。併せてご参照くださいませ。


Twitter をフォローして、トピックへのあなたの問い合わせやご質問、提案を頂けるとハッピーです。

超基礎からの 速習 Docker シリーズ一覧

この文章はシリーズの一つです:

  • 超基礎からの 速習 Docker (1)
    • なぜ Docker なのか、コンテナーやイメージ、Dockerfile の基本コンセプトの解説、もちろん、それらを管理するのに必要なコマンド群もカバーしています。
  • 超基礎からの 速習 Docker (2)
    • Volume を用いたデータの永続化や、開発環境の Volume 化を通じて、開発をより手軽なものにします。
  • 超基礎からの 速習 Docker (3)
    • データベースをコンテナ化し、レガシーなやり方及び新しいやり方である Network を用いて他のコンテナから連絡可能にします。
  • 超基礎からの 速習 Docker (4)
    • いまここ。
  • 超基礎からの 速習 Docker (5)
    • Docker Compose を用いた複数サービスの管理方法を解説します(その2)

このパートでは、複数の Docker コンテナーの扱いについて述べます。やがてはたくさんのコンテナーを持ってしまって、すべてを管理できないと感じるようになるはずです。docker run を入力し続けるのは一定のポイントまでで、複数のコンテナを起動し始めると、すぐに頭と指が痛くなります。

TLDR; Docker Compose は巨大なトピックです。そんなわけで、二つのパートに分けました。このパートでは、なぜ Docker Compose で、いつそれは輝くのか説明します。続編では、Docker Compose について、環境変数、Volume、データベースと言った、より高度なトピックについてカバーします。

このパートでカバーしているのは:

  • なぜ Docker Compose? Docker Compose の理解には重要です。ハイレベルには最低でも、二つの主要なアーキテクチャ、Monolith と Microservice があり、Docker Compose は Microservice をきちんとサポートします。
  • 機能 Docker Compose がどんな機能をサポートし、なぜ、そんなに Microservice にフィットするのか、理解してもらいます。
  • Docker が十分じゃないとき Docker コマンドを使うことがつまらなくて苦痛になる個所と、もっともっと魅力的に見え始める時がいつかについて説明します。
  • アクション 最後に、スクラッチから docker-compose.yaml をビルドして、Docker Compose を使ったコンテナーの管理といくつかコア コマンドについて学びましょう。

リソース

Docker を使う事、コンテナー化は、一枚岩をマイクロサービスに分解していくことです。このシリーズのいたるところで僕らは Docker やそのコマンド体系をマスターするために学ぶことになります。そうすれば、きっと君は自作のコンテナーをプロダクション環境で使いたくなるでしょう。その環境は大抵クラウド上にあります。十分な Docker 経験を積んだと思ったなら、次のリンクで Docker をクラウドでどのように活用できるか、ご確認してみると良いと思います。

  • Azure 無料アカウントのサインアップ プライベート レジストリのようなクラウドのコンテナーを使うには、無料 Azure アカウントが必要でしょう。
  • クラウドのコンテナー クラウドのコンテナーについて他に知っておくべきことについて網羅する概要ページです。
  • 自作コンテナーをクラウドにデプロイ 今の Docker スキルをレバレッジしてクラウド上でサービスを動かすことがいかに簡単かを示すチュートリアル。
  • コンテナー レジストリの作成 自作 Docker イメージを Docker Hub に入れられますが、クラウドのコンテナー レジストリも可能です。自作イメージをどこかにストアして、一瞬でレジストリから実際のサービスをできるようにすることは凄くないですか?

なぜ Docker Compose?

Docker Compose は多くのサービスを個別に管理する必要がある時に使われます。これから話すことは Microservice アーキテクチャと言われるものです。

Microservice アーキテクチャ

このアーキテクチャの特徴を定義していこう。

  • 疎結合 (訳注:疎結合の原文は Loosely coupled、緩くカップルになってる感じ)、これは他のサービスの機能に依存せず、必要なデータがそのままそこにあるということです。それらは他のサービスに影響しあえますが、HTTP コールのような外部 API 呼び出しで実現しています。
  • 個別にデプロイ可能 他のサービスに直接影響することなく、スタート、ストップ、リビルドできることを意味します。
  • 高いメンテナンスとテストが可能 それぞれサービスが小さいため、理解が容易で、お互いに依存しないため、テストがよりシンプルです。
  • ビジネス ケーパビリティと共にある 異なるテーマを見つけて試せることを意味します。ブッキングだったり、プロダクト管理だったり、会計だったり・・

なぜ僕らはこのアーキテクチャにしたいのか?という問いから始めましょう。上の特徴リストからも明らかであるように、高いフレキシビリティや、依存度がない点などがあります。それは良いことに聞こえますが、全てのアプリが持っているべき新しいアーキテクチャなのでしょうか。

そういうことは常にモノに依ります。モノリシックなアーキテクチャに対してMicroserviceが輝くかどうかは基準があります:

  • 異なる技術スタック/新しい技術 多くの開発チームがあって、それぞれが自身の技術スタックあmたはすべてのアプリを変更するわけではなく、新しい技術を試したい。各チームは自分で選んだ技術を、Microservice アーキテクチャの一部としてサービスをビルドしましょう。
  • 再利用 「会計」と言った、一つのケーパビリティをビルドしたい場合、一つの個別のサービスに分解すれば、他のアプリケーションへの再利用が容易になります。Microservice アーキテクチャでは、異なるサービスを組み合わせたり、そこから多くのアプリを作り出すことが良いです。
  • 最小の失敗インパクト モノシリック アーキテクチャで失敗があると、アプリ全体が落ちてしまうかも知れません。Microservice なら、失敗からできるだけ守ることができます。

なぜ Microservice がモノリシック アーキテクチャを超えるのかについてはたくさんの議論があります。ご興味があれば、こちら のリンク先をお勧めします。

Docker Compose の事例

Microservice アーキテクチャの記述は、ビジネス ケーパビリティと共にあるサービス群が必要であることを教えてくれます。更に言うなら、個別にデプロイ可能で、異なる技術スタックや更に多くを使うことが可能である必要があるということです。私の意見としては、これは Docker がとても汎用的にフィットしているように聞こえます。Docker を超えて Docker Compose の事例を作る理由は、そのサイズにあります。もし、僕らが二つ以上のコンテナーを持っていたなら、僕らがタイプしなくちゃならないコマンドの数は突然線形に増えていく。次のセクションでは、たくさんのサービスが増えた時、どんな Docker Compose 機能がスケールできるのか、解説しましょう。

Docker Compose 機能概要

Docker Compose は簡単にいくつかのイメージを一度に簡単にビルドしたり、いくつかのコンテナーその他のものを一度に起動できるという意味で、非常に優れたスケーリングが可能になりました。機能の一覧は以下の通りです:

  • Manages アプリのライフサイクル全体を管理
  • Start、Stop、リビルド
  • View サービス実行のステータスを参照
  • Stream 実行サービスの log 出力を確認
  • Run 一度のコマンドでサービスを実行

見ての通り、多くのサービスで構成されている microservice アーキテクチャを管理する必要があるとき、必要のある可能性のあるすべてを提供しています。

素の Docker では十分じゃないとき

Docker がどのように操作し、どんなコマンドが必要だったか、サービスが一つ二つ追加するときについて見てみましょう。

  • Dockerfile の定義 OS イメージ、インストールするライブラリ、環境変数、開くべき Port、最後にサービス起動の方法
  • イメージのビルド または Docker Hub からイメージを引っ張ってくる
  • コンテナーの生成と実行

Docker Compose でも Dockerfile が一部必要ですが、Docker Compose はイメージをビルドし、コンテナーを管理します。プレーン Docker の場合のコマンドをお見せしましょう:

docker build -t some-image-name .

続いて、

docker run -d -p 8000:3000 --name some-container-name some-image-name

書くのに大変な量と言うわけではないですが、これを行う必要のある 3つの異なるサービスを持っている場合を想像してみてください。突然6つのコマンドまで増えます。さらに、終了用にコマンドが2つ増えます。これはほんとスケールしない。

does't realy scale

docker-compose.yaml を書く

ここは本当に Docker Compose が輝いているところ。ビルドしたいサービス毎に二つのコマンドをタイプする代わりに、プロジェクトの全てのサービスを一つのファイル docker-compose.yaml で定義できます。docker-compose.yaml ファイルの中では以下のトピックを設定できます。

  • Build ビルド内容と 標準的な名前とは異なるべきである Dockerfile の名前を特定できます。
  • Environment 多くの必要な環境変数を定義し、値を設定できます。
  • Image スクラッチからイメージをビルドする代わりに、Docker Hub から引っ張ってきて自身のソリューションに使える、Ready-made イメージを定義します。
  • Networks Network を作成し、もし必要であれば、各サービスがどのネットワークに属しているべきかを特定できます。
  • Ports コンテナー内部のどのポートが、どの外部ポートにあたるか、ポート フォワーディングを定義できます。
  • Volumes もちろん、Volume も定義できます。

Docker Compose の実行

OK、ここまでで、Docker Compose がコマンドラインでできることならなんでも行うことができ、何を実行するのか知るには cocker-compose.yaml ファイルに依存していることをご理解頂けたかと思います。

docker-compose.yaml をはじめよう

実際にファイルを作ってみて、解説を加えましょう。最初に典型的なプロジェクト ファイルの構造について簡単にレビューしましょう。以下は二つのサービスを構成するプロジェクトで、それぞれディレクトリを持っています。それぞれのディレクトリは、サービスをどうビルドするかを指定した Dockerfile があります。

訳注
本章のハンズオンで実際に触るのは、docker-compose.yaml だけで、各イメージやコンテナーを作るソースコードと設定ファイルが提示されていません。パート 5 では、GitHub からそれらサービス用コードをダウンロードしてのハンズオンとなっていますので、本章は「まあ、そんなものか」と読み進んで頂くのが良いかなと思います。

こんな感じになっています:

docker-compose.yaml
/product-service
  app.js
  package.json
  Dockerfile
/inventory-service
  app.js
  package.json
  Dockerfile

上記で注目すべきは、プロジェクトのルートに docker-compose.yaml ファイルを作成するというやり方です。そのようにする理由は、ビルドするすべてのサービスについて、どのようにビルドするか、どのようにスタートするかが一つのファイル docker-compose.yaml に定義されているからです。OK、docker-compose.yaml を開いて、最初の1行を入力しましょうj:

docker-compose.yaml
version: '3'

ここで何を指定するかは実際重要です。現在、Docker は 3つの異なるメジャー バージョンをサポートしています。3 は最も新しいバージョンで、各バージョンでの違いとそれに付随する異なる機能とシンタックスについては、 official docs をご覧ください。

docker-compose.yaml
version: '3'
services:
  product-service:
    build:
      context: ./product-service
    ports:
      - "8000:3000"

OK、一度にたくさんあるので、ブレイク ダウンしていきましょう:

  • services: docker-compose.yaml ファイル全体の中で一つだけあります。: で終わっているやり方にも注意ください。これが必要な場合は有効な構文ではあります。そのことはコマンド一般に言えます。
  • product-service: サービス名として任意で選択した名前。
  • build: Docker Compose がどのようにイメージを build するかの指定。もし既に Ready-made イメージを持っている場合はこれを指定する必要はありません。
  • context: Dockerfile がどこにあるかを Docker Compose に伝えるために必要です。このケースでは、product-service ディレクトリ以下にあります。
  • ports: これはポート フォワーディングで、最初に外部ポート、次に内部ポートを指定します。

これ全ては以下の二つのコマンドに対応します。

docker build -t [default name]/product-service .
docker run -p 8000:3000 --name [default name]/product-service

ええっと、そりゃだいたいあってるけど、まだ イメージのビルド実行や、コンテナーの作成と実行をする Docker Compose についてちゃんと話してない。どのように開始してどのようにイメージをビルドするかについて学習しましょう。

docker-compose build

上記で docker-compose.yaml 内で定義したシングル サービスをビルドします。コマンドの実行結果を見てみましょう。

output of our command

上の内容では、イメージをビルドし、最後の行で compose-experiments_product-service:latest と言うフルネームが与えられていることが分かります。名前はディレクトリー名 compose-experiments からつけられ、他の部分は docker-compose.yaml ファイルから与えられます。OK、以下のようにタイプして起動しましょう:

docker-compose up

これによって docker-compose.yaml が再度読み込まれますが、今度はコンテナーを作成・実行します。バックグラウンドでコンテナーを実行したい場合、-d フラグをつけて呼び出します。フルコマンドは:

docker-compose up -d

OK、上記でサービスが生成されました。docker ps を実行して新しく作られたコンテナーを確認しましょう:どうやら 8000 ポートで実行されていますね。確認してみましょう:OK、ターミナルからコンテナーを作れれたことを確認できました。docker stop と docker kill でコンテナーを終了できることを知っていますが、docker-compose ではどうやるでしょう:

docker-compose down

上記コマンドのログで、コンテナーの停止と削除が分かり、docker stop [id]docker rm [id] の両方が行われているらしい。いけてる:smiley: もしやりたいことがコンテナーの停止なら、次のようにするべきだ:

docker-compose stop

君のことは知らないけど、僕は docker builddocker rundocker stopdocker rm を使って停止の準備ができた。Docker Compose はすべてのライフサイクルをサポートしているように見えます:smiley:

Docker Compose アピール

ここまでを復習しましょう。Docker Compose は僕らのためにサービス管理の全ライフサイクルをケアしてくれます。最もよく使う Docker コマンドとどれが Docker Compose のコマンドが対応するかリストしましょう:

  • docker builddocker-compose build になり、Docker Compose の方は、docker-compose.yaml にあるすべてのサービスをビルドできます。シングル サービスでもビルドできるため、よりきめ細かい制御が可能となります。
  • docker build + docker rundocker-compose up になり、一度に多くのことができます。もし、事前にイメージがビルドされていない場合、ビルドは自動的に行われ、イメージからコンテナーが生成されます。
  • docker stopdocker-compose stop になり、Docker Compose の方はすべてのコンテナーを停止させることも、またはパラメータを与えれば特定のコンテナーを停止することもできます。
  • docker stop && docker rmdocker-compose down になり、最初コンテナーをすべて停止し、その上でコンテナーすべてを削除することで、終了させます。これにより僕らは新たにスタートすることが可能です。

以上はそれ自身すごいことですが、もっとすごいことはソリューションの拡張し続けることや、サービスを更にさらに追加することが容易であることです。

ソリューションをビルドする

他のサービスを追加して、それが如何に簡単で、以下にスケールしやすいか見て行きましょう。以下が必要です:

  • add docker-compose.yaml に新しいサービス エントリーを追加します。
  • build docker-compose build でイメージをビルドします。
  • run docker-compose up

docker-compolse.yaml ファイルを見てみましょう。必要な情報を次のサービスのために追加しましょう。

docker-compose.yaml
version: '3'
services:
  product-service:
    build:
      context: ./product-service
    ports:
      - "8000:3000"
  inventory-service:
    build:
      context: ./inventory-service
    ports:
        - "8001:3000"

OK、それじゃあ、新しいサービスを含んだコンテナー達を up して、実行しよう。

docker-compose up

待って、docker-compose build するんじゃない?ええっと、実際いらないよ。docker-compose up が僕らがやるすべてで、イメージのビルド、コンテナーの作成と実行を行ってくれます。

docker-compose up

注意、そんなに単純じゃありません。事前にイメージがない場合、最初は build + run として実行されます。ですが、もしサービスが変更されたなら、リビルドが必要で、その場合は docker-compose build を最初に行ってから docker-compose up する必要があります。

サマリー

Docker Compose をカバーする最初の半分に来ましたが、とっても内容は多いですね。Docker Compose の背後のモチベーションと Microservice アーキテクチャについて簡単に説明しました。さらに、Docker VS Docker Compose について話し、最後に、Docker Compose と素の Docker との対比と比較ができました。それによって、Docker Compose を使うことと docker-compose.yaml ファイルですべてのサービスを特定することで、如何に簡単になるかを見せることができていればと思います。Docker Compose にはさらに、環境変数、Network、Database のようなことがあると言いましたが、それらは次のパートです。

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

WebStorm + node + dockerでデバッグ

こちらの記事の方法で、WebStormでも問題なく実施、感謝
https://qiita.com/silverbirder/items/ec6d7b61cc61d9501189

1 引数 --inspect=0.0.0.0:9229 を付けて、docker上でアプリ起動
dockerでもdocker-composeでも特に変わらず
9229ポートを開けておくのを忘れずに

2 デバッグの構成、Attach to Node.js/Chrome を構成、ポートに9229を設定

3 dockerが動いてる状態で、デバッグ実行

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

dockerでマルチドメインのWebサーバー構築(�https-portal+php-apache+mysql+phpmyadmin)

目的

最近Dockerを自学しました、忘れないようにもの作りをしたい。

やりたいこと

docker-composeで以下のWeb環境を作りたい。

  1. Webサーバー:
    • php+apache2
    • 複数のドメイン
    • https対応
  2. DBサーバー:
    • mysql
    • phpMyadmin

Dockerの勉強

お世話になったサイトを紹介します!
1. https://knowledge.sakura.ad.jp/13265/
いつもお世話になったさくらのナレッジにある記事です。結構簡単に分かりやすく書いている文書です。
入門として最初これを読んで見た方がいいでしょう

  1. http://docs.docker.jp/
    Docker 公式日本語サイトです。内容が多いですが、ざっくりと読みました。内容としてはちょっと古いところがあるんですが、入門には全然問題ない。

  2. https://youtu.be/RppfZGuLsmA
    次はYoutubeのビデオです。インド人作られた内容です。英語はなかなか慣れていないため、Youtubeの字幕を見ながら内容を見覗きました。

今回の内容で使うDockerイメージ

SteveLTN/https-portal

https-portalはhttps のリクエストを受け取り、他のコンテナの http へ転送するリバースプロキシとして動作する nginx です。

php:5.6-apache

Webサーバーを構築するイメージですが、php7もあるが、今回試験の目的で一旦php5.6を選択した。

phpmyadmin/phpmyadmin

Mysqlデータベースを管理するため、phpmyadminも入れておきたい。

docker-compose.ymlの設計

さっそくですが、docker-compose.ymlの中身を覗きます。


version: '3'
services:
 web:
   image: php:5.6-apache
   container_name: php56-apache-web
   volumes:
    - "./html:/var/www/html"
    - "./apache.conf:/etc/apache2/sites-enabled"
   #ports:
   # - "8080:80"
   networks:
    docker-net-001:
     aliases:
      - example-site1.jp
      - example-site2.jp
 https-portal:
  image: steveltn/https-portal
  ports:
   - 80:80
   - 443:443
  restart: always
  environment:
   STAGE: local
   DOMAINS: 'example-site1.jp -> http://example-site1.jp, example-site2.jp -> http://example-site2.jp'
  networks:
   - docker-net-001
 mysql:
   image: mysql:5.7
   container_name: php56-apache-mysql
   volumes:
    - "./.data/mysql:/var/lib/mysql"
   restart: always
   environment:
    MYSQL_ROOT_PASSWORD: rootpwd@12345
    # MYSQL_DATABASE: wordpress
    # MYSQL_USER: wordpress
    # MYSQL_PASSWORD: wdpwd@12345
   networks:
    - docker-net-001

 phpmyadmin:
   image: phpmyadmin/phpmyadmin
   container_name: php56-apache-phpmyadmin
   environment:
    - PMA_HOST=php56-apache-mysql
   ports:
    - "3333:80"
   networks:
    - docker-net-001


networks:
 docker-net-001:

確認ポイント

Webサーバーでは、マルチドメインを運用するので、Apacheでは、ドメイン名でバーチャルサイトを設定しています。
詳しくはapache.conf/example-site1.jp.confを参考してください。
なので、Webサーバにドメインごとにアクセスできるように設定する必要があるので、以下のようにNetworkでaliasでドメイン名ごとに定義します。

 web:
  ....
   networks:
    docker-net-001:
     aliases:
      - example-site1.jp
      - example-site2.jp

これで、https-portalでドメインごとにWEBのコンテナに設定します。

https-portal:
 ...
 environment:
   STAGE: local
   DOMAINS: 'example-site1.jp -> http://example-site1.jp, example-site2.jp -> http://example-site2.jp'

mysqlとphpmyadminは特に工夫せずに大体同じサンプルコードを使っていました。

最後

今回作ったサンプルは以下のURLから参考してください。
https://github.com/lzs0627/docker-sample01

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

Dockerでエラー Couldn't connect to Docker daemon. You might need to start Docker for Mac.

./scripts/start.sh

で開発環境を開始しようとしたのですが、

ERROR: Couldn't connect to Docker daemon. You might need to start Docker for Mac.

このようなエラーが出ました。
Macの上のバーを見ると クジラがいない!
再起動するとDockerが落ちることがあったので、立ち上げると成功しました。
エラーが出たら再起動する、アプリが立ち上がっているか確かめる、バージョンを確かめる
鉄則ですね!

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

[Rails6]webpacker-dev-serverがheap out of memory(OOM)エラーで落ちる時の対処方法

RailsでWebpackerを用いフロント開発していたところ、
webpacker-dev-serverがやたらとメモリを食い「heap out of memory」エラーが頻出していました。

Docker環境だからか、VM上でDockerを立てていたからか、仕様かわかりませんが、
最大メモリサイズを指定することでエラーがほぼ出なくなったので、同様の現象が起きている方は参考にしてみてください。

rails 6.0.3.1
ruby 2.6.6
webpacker 5.1.1

フロント:React(bin/webpack-dev-server利用)
サーバー:Rails

結論

bin/webpack-dev-serverに最大のメモリサイズを渡すと、最大メモリに近づいた時にメモリ解放を行ってくれました。

NODE_OPTIONSの環境変数に--max-old-space-size=3072を加えると最大メモリサイズを設定することができます。

bin/webpack-dev-server
#...
ENV["NODE_OPTIONS"]  ||= "--max-old-space-size=3072"
#...

参考: Rails5.1のbin/webpacker-dev-serverでJavaScript heap out of memoryが起きた時の対処方

解説

開発はDockerを用いています。
Rails ServerとWebpackerを分けて、composeで管理をしています。

docker-compose.yml
version: '3'
services:
  db:
    image: postgres:11.6-alpine
    volumes:
      - db:/var/lib/postgresql/data:cached
    networks:
      - web
    environment:
      TZ: Asia/Tokyo
  webpacker:
    build: .
    command: ./bin/webpack-dev-server
    volumes:
      - .:/app:cached
      - bundle:/usr/local/bundle:cached
      - yarn:/node_modules:cached
    ports:
      - '3035:3035'
    networks:
      - web
    environment:
      NODE_ENV: development
      RAILS_ENV: development
      WEBPACKER_DEV_SERVER_HOST: 0.0.0.0
  web:
    build: .
    command: sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app:cached
      - bundle:/usr/local/bundle:cached
      - yarn:/node_modules:cached
    ports:
      - '3000:3000'
    depends_on:
      - db
      - webpacker
    networks:
      - web
    tty: true
    stdin_open: true
    environment:
      WEBPACKER_DEV_SERVER_HOST: webpacker
volumes:
  db:
  bundle:
  yarn:
networks:
  web:

webpack-dev-serverを立てている状態でフロント部分を開発していると、JSを更新すると自動コンパイルされます。
コンパイル内容をメモリに格納していくようで、開発を進めていると更新の度にどんどんメモリが圧迫されます。

LIMITを超えたタイミングで「heap out of memory」エラーが発生しDockerを再起動する必要があります。(メモリを開放する)

Dockerのメモリ使用量はdocker statsで確認することができます。

docker stats
=>
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
f697683f3b6e        app_web_1           0.01%               116.4MiB / 3.859GiB   2.95%               84.5kB / 39.1kB     0B / 0B             19
c66e3f0371b1        app_db_1            0.00%               2.633MiB / 3.859GiB   0.07%               11.1kB / 60kB       0B / 0B             7
8bcd63949c32        app_webpacker_1     0.00%               859.9MiB / 3.859GiB   21.76%              92.6kB / 63kB       0B / 0B             11

MEM USAGELIMITPIDSを超えるとOOMエラーとなります。

MEM USAGE / LIMITPIDS
859.9MiB / 3.859GiB 

WebpackerのDockerでは./bin/webpack-dev-serverコマンドを実行してます。

./bin/webpack-dev-serverでは最終的にnode_modules/.bin/web-packer-devを実行しているようです。

参考: Webpacker使うなら最低限これだけは知っておいてほしいこと

./bin/webpack-dev-server上ではnodeに環境変数を渡すことができます。
NODE_OPTIONSに--max-old-space-size=3072を渡すと最大メモリサイズを指定してweb-packer-devを実行するようになります。

ご自身の環境に合わせて、任意のメモリサイズを指定してください。

bin/webpack-dev-server
#!/usr/bin/env ruby

ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
ENV["NODE_ENV"]  ||= "development"
ENV["NODE_OPTIONS"]  ||= "--max-old-space-size=3072"

require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
  Pathname.new(__FILE__).realpath)

require "bundler/setup"

require "webpacker"
require "webpacker/dev_server_runner"

APP_ROOT = File.expand_path("..", __dir__)
Dir.chdir(APP_ROOT) do
  Webpacker::DevServerRunner.run(ARGV)
end

これでwebpack-dev-serverが最大メモリサイズに達したらメモリ解放されるようになりました。

メモリサイズが小さいため、頻繁にコンパイルするとメモリ解放が間に合わずOOMが出る時もありますが、かなり頻度が下がりました。

ご自身のスペックにあったメモリサイズを指定し、快適な開発に用いてください!

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

初学者がlocust 1.0で負荷試験を行う

Webシステムの負荷試験を行う際、最近はjmeterではなくlocustがオススメと聞いたので、試してみました。

  • シナリオをpythonで書けるので複雑なケースに対応できる
  • 多重度を上げる際、jmeterだと1ユーザ1スレッドになるが、locustなら少ないリソースでも負荷をかけやすい

結論から言うと、公式ドキュメント通りにやれば動きます。
が、割と最近に出た1.0で破壊的変更があったようで、少し前の外部記事を元にすると動きません。そしてハマる(実際ハマった)。
この記事はそういった人がエラーメッセージから逆引きするもののつもりで書いています。

なお、いくつか動かない実装として他記事を引用していますが、あくまでlocustの破壊的変更によるものであり、引用元記事が間違っている訳ではないことは注釈しておきます。
細かな仕様や説明は当記事よりそちらの方が詳しいので、ご参考まで。

前提環境

  • 端末: MacBook Pro Mojave
  • 環境: docker for mac 2.0.0.3
  • locust: 1.0.2
$ docker -v
Docker version 18.09.2, build 6247962
$ docker-compose -v
docker-compose version 1.23.2, build 1110ad01

とある事情で、諸々最新ではない。

シングル構成でlocustを実行する

コンテナでlocustをインストール

公式のlocustio/locustを使ってもいいのだが、cmdではなくentrypointでlocustが指定されている。
locustの起動でエラーが起きるとコンテナが即死するだけなので、ログを追いにくくデバッグが辛い。

なのでまずはpythonイメージを元にして試してみる。
Webインターフェースを使えるように8089ポートを繋いでおく。

$ docker run -it -p 8089:8089 python:3.6 /bin/bash

locustのインストールはpipを叩くだけ。
このとき、古いやり方のpip install locustioだとLocust package has moved from 'locustio' to 'locust'. Please update your reference (or pin your version to 0.14.6 if you dont want to update to 1.0)と怒られる。

$ pip install locust

あと、そのままだとエディタがなくて辛いので、vimなどは適宜入れる。

$ apt-get update -y
$ apt-get install -y vim

locustfileを作る

最初、こちらの記事を参考にしたのだが、これはこのままでは動かなかった。

locustfile.py
from locust import HttpLocust, TaskSet, task, between, constant

class UserBehavior(TaskSet):
    @task(1)
    def profile(self):
        self.client.get("/sample", verify=False)

class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    wait_time = constant(0)

実行すると、まず以下のエラーが出る。

ImportError: The HttpLocust class has been renamed to HttpUser in version 1.0. For more info see: https://docs.locust.io/en/latest/changelog.html#changelog-1-0

エラーメッセージ通り、ver1.0 の仕様変更によるもの。
このエラー自体は、単にHttpLocustHttpUserに変えれば良い。
しかしそれ以外にも、この状態で起動すると、あとで実際にリクエストを送るタイミングで以下のエラーが発生する。

10b1687a2aee/ERROR/locust.user.task: Cannot choose from an empty sequence
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/locust/user/task.py", line 280, in run
    self.schedule_task(self.get_next_task())
  File "/usr/local/lib/python3.6/site-packages/locust/user/task.py", line 408, in get_next_task
    return random.choice(self.user.tasks)
  File "/usr/local/lib/python3.6/random.py", line 260, in choice
    raise IndexError('Cannot choose from an empty sequence') from None
IndexError: Cannot choose from an empty sequence

どうやらTaskSet周りの実装方法が変わったらしい。
task_set = UserBehaviortasks = {UserBehavior:1}と書く必要がある。

結果、完成したファイルは以下の通り。
パス指定などについては引用元記事を参照まで。

locustfile.py
from locust import HttpUser, TaskSet, task, between, constant

class UserBehavior(TaskSet):
    @task(1)
    def profile(self):
        self.client.get("/sample", verify=False)

class WebsiteUser(HttpUser):
    tasks = {UserBehavior:1}
    wait_time = constant(0)

locustを起動する

以下を実行する。
(実際にはファイル名がこのままなら-f以降は省略できるが、常にこの名前に縛られるのも不便なので、今から明示的に指定)

$ locust -f ./locustfile.py

このあとでブラウザから http://localhost:8089/ につなぐと、WebUIが出てくる

スクリーンショット 2020-05-28 19.15.06.png

  • 最大ユーザ(接続)数
  • 接続数の増加率(1秒間に何接続増やすか)
  • アクセス先URL

図の例の場合、10ユーザになるまで1秒に2ユーザずつ増やし(1秒で2人、2秒で4人……)、10人に到達したら以降10ユーザでアクセスし続ける(10ユーザになったら止まるわけではない)。

なお、私の場合、アクセス先URLの指定をホスト名のみにしたら(e.g. http://hoge)、以下のエラーが発生した。

ConnectionError(MaxRetryError("HTTPConnectionPool(host='hoge.huga.com', port=80): Max retries exceeded with url: /sample(Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x....>: Failed to establish a new connection: [Errno -5] No address associated with hostname',))",),)

curlではホスト名のみでも正常にアクセスできたが、locustではFQDNにしないと接続できなかった。
詳細は調べていない。

実行した後はWeb上に色々記事があるので、そちらを参照まで。

docker-compose で並列実行する。

せっかくlocustを使うので、master/slave構成で使いたいと言う欲が出る。

docker-credential-desktopのエラー回避

docker for macの2.0.0.3のバグ(?)で、docker-composeの初期化で以下のエラーが出た

$ docker-compose up -d
Creating network "locust_default" with the default driver
Creating volume "locust_tests" with default driver
Pulling locust-master (locustio/locust:)...
Traceback (most recent call last):
  File "docker-compose", line 6, in <module>
  File "compose/cli/main.py", line 71, in main
  File "compose/cli/main.py", line 127, in perform_command
  File "compose/cli/main.py", line 1080, in up
  File "compose/cli/main.py", line 1076, in up
  File "compose/project.py", line 475, in up
  File "compose/service.py", line 352, in ensure_image_exists
  File "compose/service.py", line 1217, in pull
  File "compose/progress_stream.py", line 101, in get_digest_from_pull
  File "compose/service.py", line 1182, in _do_pull
  File "site-packages/docker/api/image.py", line 381, in pull
  File "site-packages/docker/auth.py", line 48, in get_config_header
  File "site-packages/docker/auth.py", line 96, in resolve_authconfig
  File "site-packages/docker/auth.py", line 127, in _resolve_authconfig_credstore
  File "site-packages/dockerpycreds/store.py", line 25, in __init__
dockerpycreds.errors.InitializationError: docker-credential-desktop not installed or not available in PATH
[60116] Failed to execute script docker-compose

確かに、/Applications/Docker.app/Contents/Resources/bin/をみてもdocker-credential-desktopなんて存在しない。

https://github.com/docker/for-mac/issues/3785 によると、2.1.0.0では解消されているので、更新すれば良い。
が、何かしらの事情で更新できない場合、~/.docker/config.jsoncredsStoreを手修正してdesktopからosxkeychainにすれば動くようになる。

config.json
{
  "auths" : {
    "https://index.docker.io/v1/" : {

    }
  },
  "stackOrchestrator" : "swarm",
  "experimental" : "disabled",
  "credsStore" : "desktop <= ここをosxkeychainに変える",
  "credSstore" : "osxkeychain"
}

チケットに書いてある通り、確かにどう見てもtypoに見える。。。

docker-composeファイルを作る

こちらの記事を参考にしたのだが、これもそのままでは動かなかった。

ちなみに、このYAMLはアンカーとエイリアスがあるが、パース後に実際に生成されるオブジェクトが知りたい場合は、YAMLをjsonにするオンラインパーサがあるので、活用するとわかりやすいかも。

docker-compose.yaml
version: "3.4"

x-common: &common
  image: locustio/locust
  environment: &common-env
    TARGET_URL: http://example.com
    LOCUSTFILE_PATH: /tests/basic.py
  volumes:
    - tests/:/tests

services:
  locust-master:
    <<: *common
    ports:
      - 8089:8089
    environment:
      <<: *common-env
      LOCUST_MODE: master

  locust-slave:
    <<: *common
    environment:
      <<: *common-env
      LOCUST_MODE: slave
      LOCUST_MASTER_HOST: locust-master

docker-composeを実行しても、Exited(1)で即死してしまう。
なお、エラーログはdocker logs (コンテナID)で見れる

$ docker-compose up -d
Pulling locust-master (locustio/locust:)...
latest: Pulling from locustio/locust
cbdbe7a5bc2a: Pull complete
26ebcd19a4e3: Pull complete
a29d43ca1bb4: Pull complete
979dbbcf63e0: Pull complete
30beed04940c: Pull complete
d4954e7e8c67: Pull complete
45867b33845c: Pull complete
2be134228162: Pull complete
Creating locust_locust-slave_1  ... done
Creating locust_locust-master_1 ... done
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
2fd911a1ee59        locustio/locust     "locust"            5 minutes ago       Exited (1) 5 minutes ago                       locust_locust-master_1
9c35f427c9eb        locustio/locust     "locust"            5 minutes ago       Exited (1) 5 minutes ago                       locust_locust-slave_1

公式を見ると、パラメータを環境変数から取らなくなった様子。
LOCUSTFILE_PATH: /tests/basic.py と書いても効果はなく、以下のエラーが出る。

Could not find any locustfile! Ensure file ends in '.py' and see --help for available options.

commandlocustコマンドへの引数を渡せるので、それでコントロールする。
entrypointが定義されたコンテナイメージでのcmdの扱いについては、こちらがわかりやすい。

なお、volumesは tests/ではなく./testsと書く。
でないとtestsという名前付きVolumeと見なされ、以下のエラーが発生する。

ERROR: Named volume "tests:/tests:rw" is used in service "locust-master" but no declaration was found in the volumes section.

結果、以下だと動く。

docker-compose.yaml
version: "3.4"

x-common: &common
  image: locustio/locust
  volumes:
    - tests/:/tests

services:
  locust-master:
    <<: *common
    ports:
      - 8089:8089
    command: -f /tests/basic.py --master -H http://example.com

  locust-slave:
    <<: *common
    command: -f /tests/basic.py --worker --master-host locust-master

なお、workerの数を増やしたい場合、yamlを変更する必要はなく、docker-composeを実行する際に--scaleを指定すれば良い。

$ docker-compose up -d --scale locust-slave=3
Creating network "locust_default" with the default driver
Creating locust_locust-slave_1  ... done
Creating locust_locust-slave_2  ... done
Creating locust_locust-slave_3  ... done
Creating locust_locust-master_1 ... done

スクリーンショット 2020-05-29 10.43.36.png

これで気軽に分散負荷試験をできるようになった!

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

Goの30秒で始められるGraphQLサーバー構築

前提

  • dockerとdocker-composeインストール済み
  • prisma cli インストール済み

どのような物を作るか?

単純にHello World!などのメッセージ一覧を返したり
メッセージを新規作成したり更新、削除したりといった
GraphQLのCRUD APIサーバーを構築します。

gqlkitをgit cloneし、コンテナを起動する

まずは、gqlkitというdockerアプリケーションフレームワークをgit cloneし、コンテナを起動しましょう。
これで、半分は作業が完了しました!

git clone git@github.com:gqlkit-lab/gqlkit.git
cd gqlkit
cp .env.example .env
docker-compose up -d

PostgreSQLに、データの雛形をマイグレートする

今回、どのようなデータモデルをマイグレートするのかを確認しておきましょう。
gqlkit-server/prisma/schema.prismaを開くと下記のようになっています。
このファイルを編集してprisma migrateしてやる事で
データベースにデータモデルを作ることができます。

gqlkit-server/prisma/schema.prisma
datasource db {
    provider = "postgresql"
    url      = "postgresql://postgres:postgres@localhost:5432/postgres?schema=public"
}

model messages {
    id   String @default(cuid()) @id
    text String
    created_at String
    updated_at String
}

今回は、このままの内容をマイグレートするのでファイルの内容は編集せず下記を実行します。
これでPostgreSQLにmessagesテーブルが作成されました。

prisma migrate save --experimental
prisma migrate up --experimental

ちなみにschema.prismaに書くモデル名の注意点ですが
gqlkitはgormを採用しているので「messages」のように複数形で「s」が最後に来るように気をつけましょう。
2単語以上の場合はsnake_caseで書いてやります。

GraphQLサーバーを起動する

次の流れとしては、gqlkit-server/graph/schema.graphqlを編集し
gqlkit-server/graph/schema.resolvers.goにリゾルバを書いていくのですが
gqlkitはデフォルトのサンプルとして
messagesのCRUDサンプルはgqlgenでジェネレート済みとなっています。
なので、ここではさらっと
schema.graphqlの中身とschema.resolvers.goの中身を確認だけしておきます。

gqlkit-server/graph/schema.graphql
type Query {
    readMessages: [Message!]!
}

type Mutation {
    createMessage(text: String!): Message!
    updateMessage(id: ID!, text: String!): Message!
    deleteMessage(id: ID!): Message!
}

type Message {
    id: ID!
    text: String!
    created_at: String
    updated_at: String!
}

gqlkit-server/graph/schema.resolvers.go
package graph

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.

import (
    "context"
    "fmt"
    "gqlkit/env"
    "gqlkit/graph/generated"
    "gqlkit/graph/model"
    "time"

    "github.com/jinzhu/gorm"
    _ "github.com/lib/pq"
    uuid "github.com/satori/go.uuid"
)

func (r *mutationResolver) CreateMessage(ctx context.Context, text string) (*model.Message, error) {
    db, err := gorm.Open("postgres", env.DB_CONNECT)
    defer db.Close()
    if err != nil {
        return nil, fmt.Errorf(err.Error())
    }

    id := uuid.Must(uuid.NewV4(), nil)
    loc, _ := time.LoadLocation("Asia/Tokyo")
    now := time.Now().In(loc).Format("2006-01-02T15:04:05+09:00")

    r.message = &model.Message{
        ID:        id.String(),
        Text:      text,
        CreatedAt: &now,
        UpdatedAt: now,
    }

    db.Create(&r.message)
    return r.message, nil
}

func (r *mutationResolver) UpdateMessage(ctx context.Context, id string, text string) (*model.Message, error) {
    db, err := gorm.Open("postgres", env.DB_CONNECT)
    defer db.Close()
    if err != nil {
        return nil, fmt.Errorf(err.Error())
    }

    loc, _ := time.LoadLocation("Asia/Tokyo")
    now := time.Now().In(loc).Format("2006-01-02T15:04:05+09:00")

    db.Where("id = ?", id).First(&r.messages)
    r.message = r.messages[0]
    db.Model(&r.message).Where("id = ?", id).Update("text", text)
    db.Model(&r.message).Where("id = ?", id).Update("updated_at", now)

    return r.message, nil
}

func (r *mutationResolver) DeleteMessage(ctx context.Context, id string) (*model.Message, error) {
    db, err := gorm.Open("postgres", env.DB_CONNECT)
    defer db.Close()
    if err != nil {
        return nil, fmt.Errorf(err.Error())
    }

    db.Where("id = ?", id).First(&r.messages)
    r.message = r.messages[0]
    db.Where("id = ?", id).Delete(&r.messages)

    return r.message, nil
}

func (r *queryResolver) ReadMessages(ctx context.Context) ([]*model.Message, error) {
    db, err := gorm.Open("postgres", env.DB_CONNECT)
    defer db.Close()
    if err != nil {
        return nil, fmt.Errorf(err.Error())
    }

    db.Order("created_at").Find(&r.messages)

    return r.messages, nil
}

// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }

実際に、任意のアプリを開発する際は、
schema.graphqlを編集する

go run github.com/99designs/gqlgenでリゾルバをジェネレートする

schema.resolvers.goを編集する
という感じの流れで開発していきます。

という事で最後に、下記を実行してGraphQLプレイグラウンドを立ち上げましょう!
http://localhost:8080/ でGraphQLプレイグラウンドが立ち上がっています。

cd gqlkit-server
go run server.go

まとめ

現在、マークアップエンジニアからwebエンジニアへと
ステップアップ転職を目指しているのですが

何を思ったか求人の多いRubyに手を出さず
先にGoに手を出してしまいました。

すると、Goのなんと楽にバックエンド開発が終了してしまう事か
これは、下手をするとfirebase + nuxtでアプリ作ってた時以上に楽です。

Goに触れたのは幸なのか不幸なのか
その後、Railsの学習に手をつけてみようと何度かトライするのですが
Goの圧倒的すぎるシンプルさ、簡単さ、スッキリさ、に引きずられてしまい
どうしても、わざわざ学ぼうという意欲が出ないでいます。
Laravelも同じく...
求人は多いのに...(泣)
Goめ!実に罪深い!(褒め言葉)

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

nvidiaのdockerでroot以外のユーザでGPUを使う

はじめに

NvidiaはDokcerのサポートを強化しています。NGCなどから、イメージをダウンロードしコンテナとして実行が可能です。
なにかとバージョン依存の多いdarknetだのopenCVだのをつかうにあたって、dockerがとても便利だとおもって、openCVのイメージ だのdarknetのイメージだのをつくって、さあこれから使おうとおもって、User追加してみたら、rootじゃないとGPUが使えませんでした。

やったこと

なかなか検索してもひっかからなかったので、ここに書いとくことにしました。
解決策は以下
nvidia-docker seems unable to use GPU as non-root user

Dockerfileにすろと、こんなの足しとけばいけました。

RUN useradd -m ${USER} && \
    echo ${USER}:${PASSWD} | chpasswd && \
    echo "${USER} ALL=(ALL) ALL" >> /etc/sudoers
RUN sudo usermod -a -G video ${USER}

上記のdarknetのイメージをもとにするんだったらこんな感じ
ついでに、Dockerでapt-get upgradeもしてからユーザ追加してます。

FROM darknet:100
ENV USER="hoge"
ENV PASSWD="hogehoge"
ENV HOME=/home/${USER}
RUN apt-get -y update
RUN echo '* libraries/restart-without-asking boolean true' |  debconf-set-selections
RUN apt-get -y upgrade
RUN apt -y install xfce4-terminal
RUN useradd -m ${USER} && \
    echo ${USER}:${PASSWD} | chpasswd && \
    echo "${USER} ALL=(ALL) ALL" >> /etc/sudoers
RUN sudo usermod -a -G video ${USER}
USER ${USER}
WORKDIR ${HOME}
CMD ["/bin/bash"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2にDockerをインストールして自作イメージからコンテナを起動してみる。

調べが足りないだけかもしれませんが。。

Dockerのインストールガイドで、AmazonLinuxでのインストール方法が見つからなかったので、自分がやった手順を残しておこうと思います。

環境

以下の環境で試しました。

大項目 中項目
EC2 インスタンスタイプ t2.micro
OS Amazon Linux 2
Dockerリポジトリ Docker Hub

yumでインストールできました。

なんとなく、こんなコマンドでいけるのでは?
と、打ち込んでみたら、大当たり。

sudo yum install docker

systemctlでdockerサービスを起動

インストールしたら、勝手にサービスが上がってくるだろうと思ってたら、そうではないようです。
以下のコマンドで、dockerサービスが起動できます。

sudo systemctl start docker

docker login

docker login時はサーバを指定しました。

sudo docker login https://index.docker.io/v1/

docker pull

ここから、先はつまづくことはなかったです。(イメージ名はサンプルです。)

sudo docker pull myaccount/sampleapp:latest

docker container run

macで作ったRailsのアプリが、いとも簡単に起動しました。

sudo docker container run -d -p 8080:3000 myaccount/sampleapp:latest

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

Docker超入門!Apacheを使ってhtmlを表示してみよう!

目的

  • Dockerを学ぶ最初の一歩として自分の知識を整理しながら基礎をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.3)
ハードウェア MacBook Pro (16-inch ,2019)
プロセッサ 2.6 GHz 6コアIntel Core i7
メモリ 16 GB 2667 MHz DDR4
グラフィックス AMD Radeon Pro 5300M 4 GB Intel UHD Graphics 630 1536 MB
  • ソフトウェア環境
項目 情報 備考
Docker バージョン 19.03.8 こちらの方法で導入→Docker Desktop for Mac をインストールする

概要

  1. イメージのダウンロード

詳細

  1. イメージのダウンロード

    1. 下記コマンドを実行してApacheのイメージのダウンロードを行う。(DockerではApacheはhttpdという名前で扱われている。)

      $ docker pull httpd
      
    2. 下記コマンドを実行して現在インストールされているイメージの一覧を出力しApache(httpd)が存在する事を確認する。

      $ docker images
      >REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
      >httpd                  latest              d4e60c8eb27a        12 days ago         166MB
      
  2. コンテナの作成

    1. 下記コマンドを実行してイメージからコンテナを作成し、作成したコンテナを起動する。

      • -dはデーモンモードにて起動するオプションである。
      • -p 物理マシンのポート番号:コンテナのポート番号はポート設定を行うオプションである。(物理マシンとは、みなさんのMacBookやWindowsPCを指す。)
      • 下記コマンドの意味は「デーモンモードにて、物理マシンのポート番号8080番とコンテナのポート番号80番をつないでイメージからApache(httpd)のコンテナを作成します」である。
      $ docker run -d -p 8080:80 httpd
      
    2. ブラウザにて下記にアクセスする。

    3. 下記の様に表示される事を確認する。

      スクリーンショット 2020-05-28 19.38.55.png

    4. 下記コマンドを実行して現在起動しているコンテナの一覧を表示する。

      $ docker ps
      
    5. 下記コマンドを実行して一旦Apacheのコンテナを停止する。(コンテナ名は先のコマンド$ docker psのNAMESの内容)

      $ docker stop コンテナ名
      
  3. htmlの表示

    1. 下記コマンドを実行してhtmlファイルを格納するディレクトリを作成する。

      $ mkdir -p /tmp/html
      
    2. 下記コマンドを実行してhtmlファイルを作成する。

      $ echo "Hello Japan" >> /tmp/html/index.html
      
    3. 下記を実行してdockerコンテナ先のディレクトリにマウントする。($ docker run -d -p 8080:80 -v "物理マシンにあるhtmlファイルの親ディレクトリのフルパス(マウントするディレクトリ):コンテナ側のディレクトリ(マウント先のディレクトリ)" httpd)

      $ docker run -d -p 8080:80 -v "/tmp/html/:/usr/local/apache2/htdocs/" httpd
      
  4. 確認

    1. ブラウザにて下記にアクセスする。
    2. 下記の様に表示される事を確認する。

      スクリーンショット 2020-05-28 21.18.20.png

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

nuxt.js(vuetify)+Rails(api)+mysql on dockerでHelloWorld!!

前書き

railsとvue.jsを同一コンテナ内で環境構築することはやったことがあったんですが、フロントエンドとバックエンドのコンテナを分けるのが最近のトレンドだと耳にしたのでHelloWorldに挑戦してみました!
こちらの記事を参考にさせていただきました!ありがとうございました!
参考→https://qiita.com/at-946/items/08de3c9d7611f62b1894
postgresqlからmysql、nuxt.jsをnpmからyarn、railsのコンテナをalpineからdebianに変更しています。

今回作る環境

ruby:2.6.5
rails:6.0.3
mysql:8.0
nuxt.js(yarn)
vuetify

コンテナの準備

ディレクトリ構成

/
/
|--front/
|    |--Dockerfile
|--back/
|    |--Dockerfile
|    |--Gemfile
|    |--Gemfile.lock #空ファイル
|    |--entrypoint.sh
|--docker-compose.yml

フロントコンテナ内のDockerfile

front/Dockerfile
FROM node:12.5.0-alpine

ENV HOME="/myapp" \
    LANG=C.UTF-8 \
    TZ=Asia/Tokyo

WORKDIR ${HOME}

RUN apk update && \
    apk upgrade && \
    npm install -g npm && \
    npm install -g @vue/cli

ENV HOST 0.0.0.0
EXPOSE 3000

バックコンテナ内のDockerfile

back/Dockerfile
FROM ruby:2.6.5
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update -qq \
    && apt-get install -y nodejs yarn \
    && mkdir /myapp
ENV HOME="/myapp" \
    LANG=C.UTF-8 \
    TZ=Asia/Tokyo
WORKDIR ${HOME}
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . ${HOME}

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]

Gemfile

back/Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6.0.3', '>= 6.0.3.1'
back/entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
docker-compose.yml
version: '3'
services:
  db:
    container_name: db
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
#3307にしているのは筆者の環境では3306がpc自体のMySQLで使われているためです。3306:3306の方がいいかもです。
    ports:
      - '3307:3306' 
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  back:
    container_name: back
    build: back/
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - ./back:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
    stdin_open: true
    tty: true
    command: bundle exec rails server -b 0.0.0.0
  front:
    container_name: front
    build: front/
    command: yarn dev
    volumes:
      - ./front:/myapp
    ports:
      - 8080:3000
   #depends_onでbackコンテナへのリンクを張っています。
    depends_on:
      - back
volumes:
  mysql-data:
    driver: local

ここまで写し終わったら

$ docker-compose build

これでdockerimageが作成され、コンテナの準備ができました!

nuxt.jsのHelloWorld

参考記事とほとんど変わらないので書くのがはばかられますが、少し違うので一応書いておきます。
まずはnuxt.jsのアプリを作成します。
途中の選択画面でエンターだけでなくスペースを押す必要がある項目もあるので注意してください。

$ docker-compose run --rm front npx create-nuxt-app

? Project name                   --> myapp  # アプリ名
? Project description            --> myapp  # アプリの説明
? Author name                    --> me     # アプリの作成者
? Choose the package manager     --> Yarn
? Choose UI framework            --> Vuetify
? Choose custom server framework --> None
? Choose Nuxt.js modules         --> Axios
? Choose linting tools           --> -
? Choose test framework          --> None
? Choose rendering mode          --> Universal (SSR)

これでnuxtアプリの完成です!
フロントコンテナを立ち上げて確認してみましょう!

$ docker-compose up front

http://localhost:8080にアクセスしていい感じになってたらいい感じです。(語彙)

(参考)ホットリロードがうまくいかない時は冒頭の参考記事に解決法が記載されているのでそちらを参考にしてください。
https://qiita.com/at-946/items/08de3c9d7611f62b1894

Rails(Api)のHelloWorld

apiモードでrailsアプリを作ります。

$ docker-compose run --rm back rails new . -f -d mysql --api

次にDBを接続します。

back/config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch("MYSQL_USERNAME", "root") %>
  password: <%= ENV.fetch("MYSQL_PASSWORD", "password") %>
  host: <%= ENV.fetch("MYSQL_HOST", "db") %>

ドメイン許可の設定をします。行頭に追記してください。
(ここに2日かかりました。これを書きたいがためにこの記事を書いたと言っても過言ではないです。)

config/environments/development.rb
Rails.application.config.hosts << 'back'
##省略##

以降は参考記事と全く同じなので省略します。(サボれるところはサボる主義です。)

gitの設定

axiosでpostできたら、次にgitの設定をします。
railsはnewするときにgit initしてしまうのでbackディレクトリだけgit管理される状況になってしまいます。

/
$ cd ./back
back/
$ sudo rm -rf .git
$ cd ..

sudoはいらないかもです。
この後に

$ git init

を打って完了です。

セキュリティ的にいいのかはわからないんですが、

.gitignore
 #.nuxt 
 #dist 
 #sw.* 
 #node_modules/ 
 #jspm_packages/

と、この5つをコメントアウトしておかないと他のパソコンでdocker-compose upが使えなかったので参考にしてみてください。

あとがき

いやー、ハマりましたね。ただやっぱなんとなくこの構成かっこいい、、かっこよくない?
というわけで参考になれば嬉しいです。よろしければLGTMお願いします!(露骨な媚び)
それではまた!

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

Windows10+WSL2+Ubuntu+Docker

本日、「Windows 10 May 2020 Update」が公開されました。これで、Insider Previewでなくても、wsl2が使えるようになり、Windows10 Home上でDocker環境を簡単に構築できるようになりました。

今回は、Ubuntuを入れて、そこにDocker for Windowsを乗せるところまでの備忘録。最終的にはLaravelを動かす予定。

手順

※再起動が促されたら従うこと。

WSL2の有効化

以下にエクスプローラでアクセスする。

コントロール パネル\プログラム\プログラムと機能

Windowsの機能の有効化または無効化を選択する。

以下の画面が出てくるので、Linux用Windowsサブシステムを有効にする。

1.PNG

PowerShellを管理者権限で開き、以下を実行してWSL2が動くようにする。

Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform

Ubuntuのインストール

Microsoft StoreでUbuntuを検索し、Ubuntu 18.04 LTSをインストールする。
(別のバージョンでも構いませんが、現時点ではこれをオススメ)

Ubuntuが起動されてユーザとパスワードが聞かれるので設定する。

PowerShellで以下を実行して、WSL1からWSL2に切り替える。

wsl --set-version Ubuntu-18.04 2

ここで、「カーネルコンポーネントの更新が必要です」と出たら、以下にアクセスしてLinux カーネル更新プログラム パッケージをダウンロードして実行し、再度上記を実行する。

https://docs.microsoft.com/ja-jp/windows/wsl/wsl2-kernel

PowerShellで以下を実行して、WSL2に切り替わっていることを確認する。

wsl -l -v

VERSIONのところが2になっていたら成功。

Docker for Windowsのインストール

以下のページから、Docker for Windows(stableでOK)をダウンロードしてインストールする。

https://www.docker.com/products/docker-desktop

Docker Desktop for Mac and Windows | DockerLearn why Docker Desktop is the preferred choice for millionswww.docker.com

何も変更せずインストールする。

※最後、Windowsからログアウトするので注意。

再ログインすると、自動的にDocker for Windowsが立ち上がる。チュートリアルは面倒なのでskipしました。

先程作ったWSL2のUbuntu-18.04をリソースとして設定する。

2.PNG

Dockerが使える環境になりました。

次回への準備(Laravel環境を作らないなら必要無し、備忘録のため残しておく)

WindowsのスタートメニューからUbuntu-18.04を立ち上げる。

立ち上がったターミナルで以下を実行していく。
hogehogeさんはプロジェクト名とか任意のものでOK。

cd ~
mkdir hogehoge
cd hogehoge
explorer.exe .

最後のコマンドで、Ubuntu内で現在いるディレクトリのWindows上でのフォルダが開かれる。

次回、この上にLaravel実行環境を構築していく。

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

Docker for Mac の Mutagen-based cachingを使ったらホスト側のファイルの変更が反映されなくなった

何が起きたか

Docker for Mac の Mutagen-based caching で Volume のパフォーマンスが劇的に改善した
こちらの記事を見て試してみたらrspecとかrubocopとか爆速になってうおおおおおおってなったけど
swagger.ymlのホスト側での変更がredocのコンテナ内に反映されなくなってしまった
しかし、railsプロジェクトの方は問題なく反映されていてなんでだろうと思ったら

原因

docker-compose.yml
version: '3'
services:
  redoc:
    image: redocly/redoc:latest
    volumes:
      - ./swagger.yml:/usr/share/nginx/html/swagger.yml
    environment:
      SPEC_URL: swagger.yml
    ports:
      - 8081:80

volumesで
- ./swagger.yml:/usr/share/nginx/html/swagger.yml と書いていたこと
どうやらファイル単位での指定をすると反映がされなくなるようだ
なんでかはわからん、暇な時に気が向いたら調べる

解決策

ディレクトリ単位でマウントする

docker-compose.yml
version: '3'
services:
  redoc:
    image: redocly/redoc:latest
    volumes:
      - ./swagger:/usr/share/nginx/html/swagger
    environment:
      SPEC_URL: swagger/swagger.yml
    ports:
      - 8081:80

ちなみに

Mutagen-based caching使ったらrspecとか4倍ぐらい早くなるので
railsをdocker使って開発してて遅いなぁって困ってる人にはとてもおすすめ
もうdocker for mac遅いなんて言わせない!!!!!

edge版なので何があるかわからんけど

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