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

docker-composeでさくっとhttpsなWordPressを公開する

背景

この度、知り合いにホームページ作成を依頼されました。制作期間が仕事の片手間で1週間しかなく、ほぼ個人サイトということもあり、(ついでに言うとただ働きなので)ここはdocker-composeでさくっと仕上げようと考えました。一応公開するので、Let's Encryptでhttps化も実施しました。また、ついでだから自分用のサイトも公開してしまおうと思い、複数サイト公開可能な構成にしました。
今更なトピックではありますが、自分用の備忘録として手順を残したいと思います。

準備

環境

今回は下記の環境で構築しました。

種別 利用環境       バージョン
サーバ さくらVPS v4
ドメイン取得 お名前.com -
SSL証明書 Let's Encrypt -
サーバOS ubuntu 16.04
クライアントOS macOS Catalina 10.15
docker - 18.09.7
docker-compose - 1.22.0

前提条件

  • OSのインストールされたサーバが起動済みであること
  • 取得したドメインがDNSレコードに登録されていること

構築手順

大まかには、下記の手順で構築していきます。
1. ファイアウォールの設定
2. FTPの導入
3. dockerおよびdocker-composeのインストール
4. 共有コンテナのディレクトリとファイルの作成
5. サイト毎のディレクトリとファイルの作成
6. dockerネットワークの作成
7. アップロードサイズ制限の変更ファイル作成
8. コンテナの起動
9. 2つ目以降のサイト立ち上げ

なお、以降の解説では、サーバのアカウントをubuntu、サイト1のドメインをexample.comとして記述していきます。

ディレクトリ構成は下記のように、
shareというディレクトリにプロキシなどの共通で利用するコンテナのファイルを、
同一ディレクトリにそれぞれのサイト名で、サイト毎のコンテナのファイルをまとめます。
 ~/public_html
 ┣share # リバプロ等、各サイトで共通のコンテナ用ディレクトリ
 ┣ サイト1 # 1つ目のサイト
 ┣ サイト2 # 2つ目のサイト
 ┣ ...

また、WordPressの管理画面からアップロードファイルサイズ制限の変更をしておきます。
デフォルトの2MBでは、ほとんどのテーマがアップロードできないと思うので。

1. ファイアウォールの設定

ターミナルからSSH接続して、ファイアウォールを設定します。
例では80番やFTP関連も開けていますが、必要に応じて取捨選択してください。

# SSHホストキーを一度作成している場合は、接続する前に削除する
$ ssh-keygen -R example.com

# ssh接続
$ ssh ユーザ名@example.com

# ファイアウォールの設定と必要なポートの通信許可
$ sudo ufw enable
$ sudo ufw allow 80
$ sudo ufw allow 443
$ sudo ufw allow 22
$ sudo ufw allow 21
$ sudo ufw allow 20
$ sudo ufw reload

# ステータスの確認
$ sudo ufw status
To                         Action      From
--                         ------      ----
80                         ALLOW       Anywhere                  
443                        ALLOW       Anywhere                  
22                         ALLOW       Anywhere                  
20                         ALLOW       Anywhere                  
21                         ALLOW       Anywhere                  
80 (v6)                    ALLOW       Anywhere (v6)             
443 (v6)                   ALLOW       Anywhere (v6)             
22 (v6)                    ALLOW       Anywhere (v6)             
20 (v6)                    ALLOW       Anywhere (v6)             
21 (v6)                    ALLOW       Anywhere (v6) 

2. FTPの導入

続いてFTPを導入します。手動でバックアップを取るとか、
プラグインをインストールする予定がないのであれば飛ばしてしまって良いと思います。

# パッケージの更新
$ sudo apt update
$ sudo apt upgrade

# vsftpdの導入
$ sudo apt install vsftpd -y

# 設定ファイルを編集する
$ sudo vi /etc/vsftpd.conf

#ファイル内から以降の設定記述箇所を探してコメントアウト、編集する
# 匿名ユーザのアクセス拒否
anonymous_enable=NO
# ファイルシステム変更コマンド許可
write_enable=YES
# アスキーモードのアップロード許可
ascii_upload_enable=YES
# アスキーモードのダウンロード許可
ascii_download_enable=YES
# 設定したディレクトリより上層への移動を禁止する
chroot_local_user=YES
# リストファイルに記述されたユーザはchrootの対象から除外する
chroot_list_enable=YES
# リストファイルの場所を指定
chroot_list_file=/etc/vsftpd.chroot_list
# ディレクトリの一括アップロード・ダウンロードを許可
ls_recurse_enable=YES
# seccomp filterオフを追記
seccomp_sandbox=NO

# リストファイルの作成
$ sudo vi /etc/vsftpd.chroot_list

# 必要なユーザを記述する
ubuntu

# サービスのリスタート
$ sudo service vsftpd restart

3. dockerおよびdocker-composeのインストール

docker-composeはリポジトリからインストールします。

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt install docker.io
$ sudo apt-get install curl
$ sudo curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
# docker-composeコマンドを利用できるようにする
$ sudo chmod +x /usr/local/bin/docker-compose

4. 共有コンテナのディレクトリとファイルの作成

まずディレクトリを作成し、その中にdocker-compose.ymlを作成します。
MySQL、NginxのリバースプロキシとLet's encryptの証明書を取得、更新してくれるコンテナを立ち上げるようにします。

$ sudo mkdir share
$ cd share
$ sudo vi docker-compose.yml

docker-compose.ymlの内容は下記の通りです。

public_html/share/docker-compose.yml
version: "2"

services:
  mysql:
    image: mysql:5.7
    container_name: mysql
    ports:
      - "3306:3306"
    volumes:
      - ./db/mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
    restart: always

  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    privileged: true
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./docker-compose.d/certs:/etc/nginx/certs:ro
      - ./docker-compose.d/htpasswd:/etc/nginx/htpasswd
      - ./conf.d:/etc/nginx/conf.d
      - /etc/nginx/vhost.d
      - /usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
    restart: always

  letsencrypt-nginx:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: letsencrypt-nginx
    privileged: true
    volumes:
      - ./docker-compose.d/certs:/etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
    volumes_from:
      - nginx-proxy
    restart: always

networks:
  default:
    external:
      name: shared

5. サイト毎のディレクトリとファイルの作成

続いてサイト毎のディレクトリとdocker-composeを作成します。

$ cd ..
$ sudo mkdir example
$ cd example
$ sudo vi docker-compose.yml 

docker-compose.ymlは以下の通りに作成します。

public_html/example/docker-compose.yml
version: "3"

services:
  wordpress:
    image: wordpress:latest
    container_name: example
    ports:
      - 9000:80
    volumes:
      - /home/ubuntu/public_html/example/html:/var/www/html
      - ./conf.d/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
    environment:
      VIRTUAL_HOST: example.com # サイトのドメイン
      VIRTUAL_PORT: 9000
      LETSENCRYPT_HOST: example.com # サイトのドメイン
      LETSENCRYPT_EMAIL: account@mail # 証明書を取得するためのアドレス
      LETSENCRYPT_TEST: "false"
      WORDPRESS_DB_PASSWORD: password # MySQLパスワード
      WORDPRESS_DB_NAME: example.com # 任意のDB名
    external_links:
      - mysql
    restart: always

networks:
  default:
    external:
      name: shared

6. dockerネットワークの作成

以下のコマンドを実行して、dockerネットワークを作成します。

$ docker network create --driver bridge shared

7. アップロードサイズ制限の変更ファイル作成

WordPressの管理画面からアップロードできるテーマやメディアファイルのサイズ制限を変更します。
共有コンテナディレクトリおよびサイト毎の「conf.d」ディレクトリを作成して、その中に設定ファイルを追加します。
今回は20MBにしておきます。

$ sudo mkdir share/conf.d
$ sudo vi share/conf.d/max-size.conf

$ sudo mkdir example/conf.d
$ sudo vi example/conf.d/uploads.ini

それぞれのファイルの内容は以下の通りです。

share/conf.d/max-size.conf
client_max_body_size 20M;
example/conf.d/uploads.ini
upload_max_filesize = 20M;
post_max_size = 20M;

8. コンテナの起動

$ cd ../share
$ sudo docker-compose up -d

$ cd ../example
$ sudo docker-compose up -d

上記コマンドを実行した後に、https://example.comにアクセスすると、WordPressインストール画面が開きます。
スクリーンショット 2019-12-02 22.50.41.png

9. 2つ目以降のサイト立ち上げ

今回の構成だと、1つのMySQLに複数サイトのDBを作成して運用することになります。
この場合、WordPressは1つ名のサイトのデータベースは作成してくれますが、
2つ目以降のデータベースは作成してくれないので、インストール前に自分でデータベースを作成する必要があります。
以下の手順でデータベースを作成してください。

# MySQLコンテナにログイン
$ sudo docker exec -it mysql /bin/bash

# データベースの作成
$ mysql -u root -p
mysql> CREATE DATABASE site2 

上記で作成したデータベースに合わせて、「5. サイト毎のディレクトリとファイルの作成」の手順と同じように
2つ目のサイトのディレクトリとdocker-compose.ymlを作成してコンテナを起動すると、
1つ目のサイトと同じように、WordPressインストールサイトが開けます。

あとがき

以上、細かい説明は省いて書いてしまいました。
それぞれの手順で細かく設定したい場合は、専門の記事を探していただければと思います。

今回はWordPressを立ち上げましたが、違うイメージからコンテナを起動すれば他のサービスも立ち上げられると思います。

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

symfony/mailerで試行錯誤したこと

Symfony Advent Calendar 2019 5日目の記事です。
昨日は@77webさんSymfonyで再利用可能なバンドルのコントローラをテストする方法 でした!

はじめに

Symfony 4.4.0は11/21にリリースだったのですね。

私は、その翌日の、11/22からSymfonyを初めました。

LTSだと長くメンテされるし、安定しているだろうと考えて飛びつきましたが、Symfony/Mailerは4.3系から導入されたので、まだまだ成熟には遠く、またノウハウも蓄積されていません。更に検索結果も前任の「swiftmailer」しか出てこないので、試行錯誤を致しました。

参考にしていただければ幸いです。

sendmailで送信する方法

TL;DR

まず、はじめに困ったのがsendmailでの送信方法。
結論を先に書くと、DSNで以下の指定をすると送信ができます。

.env
MAILER_DSN=sendmail+smtp://default
または
MAILER_DSN=sendmail://default

ただし、後述しますが、少し困ったちゃんなのです。

sendmailのDSN調査

Transport Setup」でSMTPやサードパーティの「Amazon SES」、「Gmail」、「Sendgrid」などの設定方法は書いてありますが、「sendmail」はないのです。

未対応なのかな?とソースを確認すると、「SendmailTransportFactory.php」と「SendmailTransport.php」が存在します。

image.png

SendmailTransportFactory.phpの中にsendmailのスキーマの記載がありました!

SendmailTransportFactory.php
final class SendmailTransportFactory extends AbstractTransportFactory
{
~~~省略~~~

    protected function getSupportedSchemes(): array
    {
        return ['sendmail', 'sendmail+smtp'];
    }
}

dockerの開発環境で送信ができない。。。

これでsendmail用のDSNがわかったので、「sendmailでメール送信ができる!」とワクワクして、「Creating & Sending Messages」のサンプルコードを実行しました。

Connection to "process /usr/sbin/sendmail -bs" has been closed unexpectedly.

image.png

どうやらsendmail -bsでないと送信ができないようです。
確認した環境は、docker上のAlpine+ssmtpなのでsendmail -tには対応していますが、sendmail -bsには未対応なのです。

postfixを入れたコンテナ作るのも面倒ですしね。

レンタルサーバで確認 & 困ったことが。。。

面倒ですが、サンプルコードをさくらのレンタルサーバにアップして確認をしてみました。

きちんと送信ができて、以下のように受信できました。
image.png

メールの構造は以下のような感じです。

Delivered-To: fuga@gmail.com
Received: by 2002:a5d:841a:0:0:0:0:0 with SMTP id i26csp4330355ion;
        Mon, 25 Nov 2019 22:05:37 -0800 (PST)

~~~中略~~~

Return-Path: <hoge@example.com>
Received: from www1970.sakura.ne.jp (localhost [127.0.0.1]) by www1970.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id xAQ65aic007417 for <fuga@gmail.com>; Tue, 26 Nov 2019 15:05:36 +0900 (JST) (envelope-from hoge@example.com)
Received: from [127.0.0.1] (hoge@localhost) by www1970.sakura.ne.jp (8.15.2/8.15.2/Submit) with SMTP id xAQ65aQW007416 for <fuga@gmail.com>; Tue, 26 Nov 2019 15:05:36 +0900 (JST) (envelope-from hoge@example.com)
X-Authentication-Warning: www1970.sakura.ne.jp: hoge owned process doing -bs
From: hoge@example.com
To: fuga@gmail.com
Subject: Time for Symfony Mailer!
Message-ID: <0ab330134ce12286539d0ae3112374a4@example.com>
MIME-Version: 1.0
Date: Tue, 26 Nov 2019 15:05:36 +0900
Content-Type: multipart/alternative; boundary="_=_symfony_1574748336_057222644d28500198a452dc1b84b1b1_=_"

--_=_symfony_1574748336_057222644d28500198a452dc1b84b1b1_=_
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Sending emails is fun again!
--_=_symfony_1574748336_057222644d28500198a452dc1b84b1b1_=_
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

<p>See Twig integration for better HTML integration!</p>
--_=_symfony_1574748336_057222644d28500198a452dc1b84b1b1_=_--

よくよく見ると、ヘッダーに警告が追加されています。

X-Authentication-Warning: www1970.sakura.ne.jp: hoge owned process doing -bs

こちらをググると、
メールヘッダに X-Authentication-Warning: が付かないようにする」が詳しく紹介されています。

MTAの設定を変更するというのは、できれば避けたいところですね。。。

sendmail -t で送信する(ペンディング)

さきほどDSNを調査している時に、
SendmailTransport.php」で、sendmail -bssendmail -tに変更する記載があったことを思い出しました。

SendmailTransport.php
class SendmailTransport extends AbstractTransport
{
    private $command = '/usr/sbin/sendmail -bs';
    private $stream;
    private $transport;
    /**
     * Constructor.
     *
     * If using -t mode you are strongly advised to include -oi or -i in the flags.
     * For example: /usr/sbin/sendmail -oi -t
     * -f<sender> flag will be appended automatically if one is not present.
     *
     * The recommended mode is "-bs" since it is interactive and failure notifications are hence possible.
     */
    public function __construct(string $command = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
    {
        parent::__construct($dispatcher, $logger);
        if (null !== $command) {
            if (false === strpos($command, ' -bs') && false === strpos($command, ' -t')) {
                throw new \InvalidArgumentException(sprintf('Unsupported sendmail command flags "%s"; must be one of "-bs" or "-t" but can include additional flags.', $command));
            }
            $this->command = $command;
        }
        $this->stream = new ProcessStream();
        if (false !== strpos($this->command, ' -bs')) {
            $this->stream->setCommand($this->command);
            $this->transport = new SmtpTransport($this->stream, $dispatcher, $logger);
        }
    }

コンストラクタの第1引数の$commandに、'/usr/sbin/sendmail -oi -t'を指定できれば実現できそうです。

しかし、呼び出し元を見ると無情にもNULL指定でした><

SendmailTransportFactory.php
final class SendmailTransportFactory extends AbstractTransportFactory
{
    public function create(Dsn $dsn): TransportInterface
    {
        if ('sendmail+smtp' === $dsn->getScheme() || 'sendmail' === $dsn->getScheme()) {
   ここ!! =>   return new SendmailTransport(null, $this->dispatcher, $this->logger);
        }
~~省略~~
    }
}

まだ、完全に使い方を理解していないDIでなんとかできないかな?と調べてみたり、SlackのSymfony Devsのsupportで質問してみましたが、無理そうです。

提案されたのは、「自分でクラスを拡張したらよいよ!」ということでした。

image.png
https://symfony-devs.slack.com/archives/C3EQ7S3MJ/p1574840970312800?thread_ts=1574811879.302500&cid=C3EQ7S3MJ

最終手段で拡張しましょう。。。

後日、見つけましたが、senmail -tについては、Issueも上がっていました。Feature指定でした;;
=> Mailer component: change sendmail command

SMTPで配信する

SMTP配信なら使えるということで、MailDevに接続を試みます。

本家がDocker Hubで公開していますし、日本語メールに対応されたバージョンもあります。

docker composeで構成したサービス名がmaildevなので、それを指定します。

.env
MAILER_DSN=smtp://maildev

Creating & Sending Messages」のサンプルコードを実行して完了!と思ったら。。。

image.png

25ポートで接続をするはずのに、465ポートのSMTPSで接続しようとしています。。。
まだまだ、symfony/mailerには知らない秘密があるようです。

ポート番号を指定してみました。

.env
MAILER_DSN=smtp://maildev:25

今度は、25番ポートに接続をするのですが、SSL通信をしようとして失敗しているようです。

image.png

ErrorExceptionをよく見ると、EsmtpTransport.php(112)startTLSを実行しています。

SMTPSは、SSL通信をするために465ポートを用意する必要があります。
しかし、465ポートを用意できない場合があるため、startTLSは、通信をしているポートを利用して、平文から暗号化通信に切り替えるための仕組みのようです。
STARTTLS by ウィキペディア

該当のソースを確認をすると、startTLSを実行する条件がわかります。

EsmtpTransport.php(112)
    protected function doHeloCommand(): void
    {
           ~~中略~~
        /** @var SocketStream $stream */
        $stream = $this->getStream();
        if (!$stream->isTLS() && \defined('OPENSSL_VERSION_NUMBER') && \array_key_exists('STARTTLS', $capabilities)) {
            $this->executeCommand("STARTTLS\r\n", [220]);

           ~~中略~~
        }

まず、startTLSになるための1つ目の条件である!$stream->isTLS()というのは、465ポートを指定していない場合になります。

以下、EsmtpTransport.php(48)のコンストラクタの処理の抜粋になります。

EsmtpTransport.php(48)
    public function __construct(string $host = 'localhost', int $port = 0, bool $tls = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
    {

         ~~~中略~~~

        if (null === $tls) {
            if (465 === $port) {
                $tls = true;
            } else {
                $tls = \defined('OPENSSL_VERSION_NUMBER') && 0 === $port && 'localhost' !== $host;
            }
        }

        if (!$tls) {
            $stream->disableTls();
        }
        if (0 === $port) {
            $port = $tls ? 465 : 25;
        }

$tlsはデフォルトNULLなので、ポート番号が465の場合に$tlsがTrueになります。また、ポート番号が465でない場合は、52行目のelseに入りますが、ポート番号が0でlocalhost以外の場合は、$tlsがTrueになります。

話はかわりますが、はじめに、DSNでMAILER_DSN=smtp://maildevの場合は、465に接続されたのは、この判定のためですね。

さて、2回目のDSNでは25番ポートを指定しましたので$tlsはfalseになり、結果として、!$stream->isTLS()となります。

startTLSになる2つ目の条件は、OPENSSL_VERSION_NUMBERのバージョンです。こちら最新の環境なら問題になることはないと思います。

startTLSになる最後の判定は、\array_key_exists('STARTTLS', $capabilities)になります。

$capabilitiesは、EsmtpTransport.php(130)にありますが、通信対象のサーバ、今回はMailDevから送られてくるもののようです。

EsmtpTransport.php(130)
    private function getCapabilities(string $ehloResponse): array
    {
        $capabilities = [];
        $lines = explode("\r\n", trim($ehloResponse));
        array_shift($lines);
        foreach ($lines as $line) {
            if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) {
                $value = strtoupper(ltrim($matches[2], ' ='));
                $capabilities[strtoupper($matches[1])] = $value ? explode(' ', $value) : [];
            }
        }
        return $capabilities;
    }

MailDevのSMTPサーバは、NodeMailerを使っているので、startTLSには対応しているようなのですが、MailDevはISSUE(Support for TLS (via ngrok or somehow?) )もあがっているのですが、対応をしてないようです。

対応していないなら、対応していないで、startTLSが使えるなんてMailDevが言わなきゃいいのです。

NodeMailerのオプションを見ていくと、options.hideSTARTTLSというコマンドがありました。

image.png

MailDevの方にもオプションがありました!

image.png

docker-compose.ymlで、MailDevの起動オプションに、--hide-extensions STARTTLSを追加します。

  maildev:
    image: kanemu/maildev-with-iconv
    ports:
      - 8025:80
    command: bin/maildev -w 80 -s 25 --hide-extensions STARTTLS

これで「Creating & Sending Messages」のサンプルコードを実行したら、無事に送信ができました!

image.png

image.png

最後に

HTMLメールを送信したいのですが、テキストを配信するだけでいろいろと躓いてしまいました。

枯れている技術のswiftmailerに浮気しようか悩み中。

でも、sendgridとか拡張性もあるので、将来性を買って、もう少し使ってみます。

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

手軽にPC上でチャットボットの開発環境を作る

創作意欲が湧き、チャットボットを新たに作ろうと思い立ち、まずは環境どうしようかと試行錯誤した結果、Botpressで構築してみることにしました。

探し始めるとBotkitが有名だったようですが調べたらMicrosoftに買収されてたんですね。
他にないかと探してみたら、Botpressにたどり着きました。

詳細はこちらで
https://botpress.io/

では、インストールをしていきましょう。
その前にDockerを動くようにしておいてください。
私はWindows10 Proで構築しています。

PowerShell起動

image.png

Windows Terminalで起動してみました

BotpressのDockerコンテナを作成

>docker run --detach --name=botpress --publish 3000:3000 --volume botpress_data:/botpress/data botpress/server:latest

Unable to find image 'botpress/server:latest' locally
C:\Program Files\Docker\Docker\Resources\bin\docker.exe: Error response from daemon: manifest for botpress/server:latest not found: manifest unknown: manifest unknown.
See 'C:\Program Files\Docker\Docker\Resources\bin\docker.exe run --help'.

あれ?エラーになる。。。

docker hubで探して最新のタグ名を取得
https://hub.docker.com/r/botpress/server/tags

image.png

docker run --detach --name=botpress --publish 3000:3000 --volume botpress_data:/botpress/data botpress/server:2019-11-28-misunderstood

無事起動しました。

>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aec2835d2e79 botpress/server:2019-11-28-misunderstood "/bin/sh -c './duckl…" 11 minutes ago Up 11 minutes 0.0.0.0:3000->3000/tcp botpress

起動も確認できました。

ブラウザでBotpressを呼び出す

http://localhost:3000/

image.png

ボットを作成してみる

image.png

image.png

image.png

image.png

image.png

動きました。
フローで視覚的に見れて、エミュレーターもあるので便利だと思いました。

今回はここまで。

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

Traefik さえ使えば、nginx がリバースプロキシとして動くのは不要!

背景

同一のサーバ上で複数サービスをホストする際に普段 nginx または apache を使いましたが、設定を調整するたびに何を書いたかなかなか覚えられませんので。。

そこで、 Traefik を使えばいいです!

Traefik とは

公式 HP より:

An open-source reverse proxy and load balancer for HTTP and TCP-based applications that is easy, dynamic, automatic, fast, full-featured, production proven, provides metrics, and integrates with every major cluster technology... No wonder it's so popular!

Traefik は、HTTP・TCP アプリのリバースプロキシかつロードバランサです。いろんな技術と連携があり、どんなスタックでも簡単に導入することができます。

特に Docker との相性が良くて気に入り、この記事は Docker + Traefik を中心にしたいと思います。

Example

今回は、myserver.test という架空なドメインでブログと Wiki サービスを立ち上げたく、各サービスの詳細は以下のようです。

サービス アプリ URL
Blog Ghost blog.myserver.test
Wiki DokuWiki wiki.myserver.test

ドメインを用意

実際に持っているドメインであればドメインの A レコードを追加し、なければ host ファイルに以下の項目を追加すればローカルで動いて試すことができます。

# /etc/hosts
127.0.0.1     blog.myserver.test
127.0.0.1     wiki.myserver.test

Docker を用意

複数 Docker のコンテナを管理すると Docker Compose 便利なので、早速コンフィグファイルを作りました。

## docker-compose.yml

version: '2'

services:
  ghost:
    image: ghost:3
    ports:
      - 5001:2368
    volumes:
      - ./data/ghost:/var/lib/ghost/content

  dokuwiki:
    image: bitnami/dokuwiki:0
    ports:
      - 5002:80
    volumes:
      - ./data/dokuwiki:/bitnami

これで docker-composer up を実行すると、 ブログは http://localhost:5001 からアクセスでき、Wiki の方は http://localhost:5002 からアクセスできます。

Traefik を導入

せっかく docker を使っているため、 Traefik の Docker イメージを導入しましょう。

## docker-compose.yml

services:
  # ghost,dokuwiki を省略
  traefik:
    image: traefik:2.0
    ports:
      - 5003:8080 # webUI
      - 80:80
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./config/traefik/traefik.yml:/etc/traefik/traefik.yml

マウントする volumes ですが、Traefik の設定が定義されるファイル以外は、Docker API にアクセスできるため docker.sock もマウントすることが必要です。Traefik は、サービスのステータスを常に監視し、サービスが動いてない時はリバースプロキシを無効するという機能が付いています。

Traefik を設定

volumes で定義されたコンフィグファイルを作りましょう。

# ./config/traefik/traefik.yml

# Providers config
providers:
  docker: {} # Docker との連携を有効

# API/Dashboard config
api:
  insecure: true # WebUI にアクセスできるように設定

Traefik のホスト名を各サービスに定義

最後に、各コンテナにホスト名、つまり URL を指定すれば完了です。

# docker-compose.yml
  ghost:
    labels:
      - "traefik.http.routers.ghost.rule=Host(`blog.myserver.test`)"

  dokuwiki:
    labels:
      - "traefik.http.routers.dokuwiki.rule=Host(`wiki.myserver.test`)"

Traefik を起動

これで準備が整ったので、再度で docker-compose up を実行すると Traefik が起動されます。 http://localhost:5003 から WebUI を見れます。

image.png

blog.myserver.test にアクセスするとこんな感じ:

image.png

wiki.myserver.test にアクセスするとこんな感じです。

image.png

【おまけ】 HTTPS 化も楽勝!

Traefik では Let's Encrypt との連携もあり、traefik.yml に以下を定義すれば簡単に HTTPS を導入することができます。

# ./config/traefik/traefik.yml
entryPoints:
  web:
    address: ":80"

  web-secure:
    address: ":443"

certificatesResolvers:
  sample:
    acme:
      email: webmaster@myserver.test
      storage: acme.json
      httpChallenge:
        entryPoint: web

詳しくはドキュメンテーションをご参照ください。

まとめ

特に複雑なルールの設定が必要なく、シンプルなリバースプロキシが望ましい場合は、 Traefik を試す価値があるじゃないかと思います。

Reference

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

開発を加速させるためにDocker設定を見直した話をする

パーソンリンクアドベントカレンダー 3日目の投稿です。

はじめに

今担当しているプロジェクトでは、フルDockerの開発環境でRailsとVue.jsを使ったWebサービスを開発しています。

昔は個人個人で環境作ったりしていたこともあり、細かなライブラリのバージョン違いで起きるトラブルとか、そういった手順をまとめたりするためのコストとか、いろいろと大変なことも多かったりしました。それが今やDockerさえ入っていればコマンド一発で誰が動かしても同じ環境が作れる。それこそProductionでも大きな変更を入れることなく使えてしまう。大変便利になったものですね!

本日は、そんな便利なDocker開発開発で遭遇したつらみや、どうやって改善したかについて語って行きたいと思います(`・ω・´)

どういう課題があったか

自分が今のプロジェクトにJoinした時、すでにDockerを使った開発が実施されていました。
docker-compose.ymlに構成を書いて、それを使って開発が行われていました。構成的にはこんな感じですね。

docker.png

Nginxでリクエストを受けて、Rails側にproxyするやつです。あ、図には書き忘れたんですが、RailsはUnicornを使ってリクエストを受け付けるようにしています。まあよく見る構成だと思います。このプロジェクトっではAWSを使ってリリースする予定だったので開発時点からそれに合わせています。

さて、当初自分がJoinしたときは以下の用な問題がありまして、めちゃくちゃ辛いと言うものではないけれど地味に面倒な現象に見舞われてました。

  • コンテナに直接入ってmigration, パッケージアップデート
  • docker buildすると都度Gemのフルインストールしてしまう
  • .envと重複しているenvironment
  • 個人ごとにパスを書き直さないといけないdocker-sync
  • まれにdbに接続できず落ちるappコンテナ

1つ1つは大したことないのですが、これが積み重なっていくととってもイライラします。

やってられるかー( ゚д゚)
という気持ちから、早めにこの問題を片付けることを誓ったのです。

どう考えて実施したのか

本来、dockerの特性として「その時のサーバーの状態が固定されており、素早く同じ構成を量産できる」ことがあります。
軽量で高速に起動・停止できることがDockerの一番の強みだと考えていて、特に本番環境で同じ構成を一瞬で増やして早期にスケールできることを狙っているのだと考えています。完成した状態でイメージを作りあげるために柔軟性を捨てています。

一方、開発環境は柔軟でなければいけません。追加され続けるmigration、都度増えていくライブラリ等。Dockerは状態を固定化するものなので、色々と工夫する必要が出てきます。

今回はBtoBで且つ計画メンテが前提のプロジェクトで、Dockerや、Kubernatesを採用する意味が薄かったこともあり、開発環境だけでDockerを使うことだけを考えられるのでシンプルでした。が、ProductionにDockerなりを採用しているところでは、Production用と開発用で異なるDockerfile, docker-composeをそれぞれ用意することになるかと思います。もし参考にするのであればそういう運用をオススメします。

どう解決していったのか

コンテナに直接入ってmigration, パッケージアップデート

都度コンテナの中に入って作業していました。
docker psでコンテナID探して、docker execで入る。そしてコマンド実行。

...都度これをやるのは面倒なので、こんなMakefileを作成した上で、一つ構成を変更しました。

rails/bundle:
    docker-compose run --rm app bundle install

rails/migrate:
    docker-compose run --rm app bundle exec rails db:create db:migrate

yarn/install:
    docker-compose run --rm app yarn install

docker run --rmで実行しています。docker execを使う方法も考えたんですが、この場合docker-composeをup済みにしていないと実行できません。なのでいつでも気軽に叩けるようにdocker run --rmし、余計なコンテナを活かし続けないように、処理が終わったらすぐ落とすようにしています。

docker buildすると都度Gemのフルインストールしてしまう

さて、docker run --rmなのでそのまま実行すると処理が終わった後はコンテナが消えてしまいます。そうすれば当然bundle installしたGemも居なくなってしまいます。

これを防ぐため、docker-composeで以下の設定を追加しています。

volumes:
  bundle-gems:
    external: true

app:
  environment:
    BUNDLE_PATH: /app/vendor/bundle
  volumes:
    - bundle-gems:/app/vendor/bundle

bundle installで保存する先をボリュームコンテナに移してこれをマウントします。インストールしたGemはコンテナを消しても削除されず、ボリュームコンテナに残りっぱなしになります。Gemの入れ直しがないため、なにかの拍子で環境がおかしくなっても気軽にbuildを叩くことができます。

BUNDLE_PATHを指定しているのは、bundle install時に明示的にパスを指定しなくてもこの設定に合わせて配置してくれるからです。
Bundler: The best way to manage a Ruby application's gems

Productionで動かす場合は、Dockerfile側にBUNDLE_PATHを書くべきですが、今回は2箇所で管理したくなかったのでdocker-composeで書いてます。
こうして、docker buildが走っても都度フルインストールせずに済むようになりました。

唯一例外があるとすればベースImageを変更した時くらいでしょうか。
このときはGemをビルドし直さないといけないのですが、ほぼ切り替えることはないので、手動でやることにしています。

.envと重複しているenvironment

担当しているプロジェクトでは昔ながらの環境変数の管理をしており、.envが現役です。
もちろんバージョン管理はされていないファイルで管理していたのですが、ふとdocker-composeを見ると.envと同じ設定が存在していました。

environment:
  DATABASE_USERNAME: 'xxxx'
  DATABASE_PASSWORD: 'xxxx'
  DATABASE_HOST: 'xxxx'
  RAILS_ENV: 'developemt'
  ...

これがバージョン管理されている。ローカルの開発環境なので別に漏れても大した問題にはならないのですが、

  • 本来隠すべき情報が乗ってしまっている
  • 2箇所で同じ設定値が定義されている

が気になり、手をいれました。

docker-composeには、env_fileというまさにこの.envを読み込むための設定があります。これを使うと、

env_file: 
  - .env
environment:
  - '.envで定義していないやつ'

と書け、.envで設定しているものは書かなくて良くなります。なお、.envでの設定値は、environmentで上書きすることができるので、共通の.envを読みつつ、あるコンテナだけは設定を切り替えることができるようになります。

実は、docker-compose.ymlと同じ階層の.envを読み取ってくれる機能があるため、今回のケースであれば設定不要です。が、明示的に読み込んでいることを示すためにあえて書くようにしています。
Declare default environment variables in file | Docker Documentation

これで重複している設定がなくなってスッキリし、秘匿情報も書かなくて良くなりました。

個人ごとにパスを書き直さないといけないdocker-sync

弊社では好きなPCをある程度選ぶことができるのですが、Mac率が高い会社です。
そのため、Dockerを使うときはdocker-syncを併用するようにしています。事情が色々あってコンテナ中のデータやnode_moduleをホスト側と同期する必要があり大量のファイルがやり取りされるので、ホストとコンテナのボリューム同期だと転送追いつかないのが理由です。

さて、このdocker-syncですが、こう設定されているの見たことありませんか?

version: '2'
options:
  max_attempt: 200
syncs:
  app-sync-volume:
    src: '~/workspace/app/' # <- これ
    sync_excludes: ['.git']

sync対象のパスを直接書いているケース。これ結構イライラものでした。syncするディレクトリは当然個人個人でバラバラです。特に自分はghqを使ってgitリポジトリをcloneしているので独特のパスになっています。いちいち書き換えるのがめんどう(´・ω・`)

しかもこのファイルはgit管理されているので、個人でパスを変更しているといつまで経ってもdiffに残り続ける。実際間違ってコミットされて、コンフリクトが発生するという事態もおきていました。

こんなふうに設定を変更することで、個人毎のディレクトリ指定が不要になります。

version: '2'
options:
  max_attempt: 200
  project_root: 'config_path' # <- 追加
syncs:
  app-sync-volume:
    src: '.'                  # <- 変更
    sync_excludes: ['.git']  

project_root: 'config_path'は、docker-sync.ymlのパスと同じものが入ります。docker-sync.ymlをリポジトリのルートディレクトリに配置しているので、srcにcurrent pathを設定するだけで済みます。

Configuration — docker-sync 0.5.11 documentation

これでまた一つ面倒なことが減りました。

まれにdbに接続できず落ちるappコンテナ

問題が発生する時のdocker-compose.ymlはこんな設定をしていました。

  db:
    ports:
      - '3306:3306'
    volumes:
      - ./containers_data/mysql:/var/lib/mysql
  app:
    command: bundle exec rm -f ./tmp/pids/unicorn.pid && bundle exec unicorn_rails -p 3000 -c config/unicorn.rb
    depends_on:
      - db
    ports:
      - "3000:3000"
    tty: true
    stdin_open: true

app側にdbへのdepends_onを設定してし、dbコンテナが立ち上がった後にappを立ち上げる設定になっています。まあよくあるやり方だと思います。実際、これはこのままでもうまく動きました。ちゃんとMySQLが立ち上がってからRailsが上がる。問題ない。

ところが、まれにRailsとMySQLとの疎通が失敗して、コンテナが立ち上がらない現象が発生します。そのたびに一度止めてdocker-compose upみたいな運用をしており、発生するたびにそういう手作業をやる必要に迫られました。

depends_onでは、対象のコンテナが起動できたか、しか見てくれません。そのため、何らかの要因でdbコンテナ中のMySQLの起動が遅延すると、Railsが動き出す時にDBへの疎通が取れずunicorn_railsで落ちる現象が発生してしまうのです。

それを回避するため、appコンテナ中でdbコンテナのMySQLへの疎通をチェックできるようにしました。

  db:
    ports:
      - '13306:3306'
    volumes:
      - ./containers_data/mysql:/var/lib/mysql
  app:
    command: sh ./containers/app/entrypoint.sh # <- 変更
    depends_on:
      - db
    ports:
      - "3000:3000"
    tty: true
    stdin_open: true

commandにentrypoint.shを指定して、この中で疎通チェックをしています。

#!/bin/bash
set -e

until mysqladmin ping -h db --silent; do
  >&2 echo "mysql wake up......"
  sleep 3
done

>&2 echo "=== mysql < Hi, got up now. ===="

bundle exec rm -f ./tmp/pids/unicorn.pid && bundle exec unicorn_rails -p 3000 -D -c config/unicorn.rb
echo "=== start unicorn. pids: `cat ./tmp/pids/unicorn.pid` ===="
bash

3秒間隔でMySQLが起動しているかをチェックし、起動していたらunicornを起動する。
Railsが起動しているときは、かならずMySQLが起動済みなので、unicorn_railsで落ちる現象は発生しなくなりました。

これでまた一つ心に平穏が訪れます。

どうなったのか?

  • コンテナに直接入ってmigration, パッケージアップデート
  • docker buildすると都度Gemのフルインストールしてしまう
  • .envと重複しているenvironment
  • 個人ごとにパスを書き直さないといけないdocker-sync
  • まれにdbに接続できず落ちるappコンテナ

以上遭遇していた、ちょっと面倒なやつが消え去ったおかげで、開発のスピードを落とす要因がいなくなり、これまでよりストレスフリーで開発ができるようになりました(`・ω・´)

おわりに

普段開発に追われていると、ちょっとした面倒は放置されがちです。
優先しなければ行けないものがあると、こういった面倒は後回しにしてしまいます。
しかし、それが積もり重なっていくと、いつの間にか無視できない労力がかかる問題になってしまいます。

面倒だなと思った時。それは開発時のちょっとした困りごとを倒す、またとない機会になるのだと思います。


明日のパーソンリンクアドベントカレンダーは、mgmgOmOさんのLookerのお話です。BIプラットフォーム構築している人の希望になりそうなLooker。その概要を説明してくれるとのことで期待です。

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

SSH 接続できる Amazon Linux 2 の Docker イメージを作成してシステム構築してみる

はじめに

オークファンでは多くのシステムを AWS で稼働しており、OS は特別な理由がない限り以下の理由で Amazon Linux 2 を選択しています。

  • AWS が公式にサポートしている
  • セキュリティアップデートやメンテナンスアップデートが継続的に提供される
  • 追加料金なし

Amazon Linux 2 は RedHat ベースですが本家とは違っている部分もあるので、システム構築のための各種コマンドを試したくなる場面があります。そのたびにコスト管理をしてくれているインフラグループの顔色を伺いながら実際の AWS 環境に EC2 インスタンスを立ち上げるのも申し訳ないので、開発 PC の Docker 上に実際の EC2 と近い状態の Amazon Linux 2 を起動して動作検証を実施しています。

Docker イメージの作成

まずはベースとなる EC2 と近い状態の Docker イメージを作成します。
Amazon Linux 2 の Docker イメージは 公式の Docker Hub に公開されているのですが、まっさらな状態なので必要なものをインストールします。設定の大枠は以下となっています。

  • 必要なパッケージをインストール
  • SSH サーバを起動するように設定
  • ec2-user を作成

具体的なディレクトリ構成はここでは以下とします。あとで SSH 接続するための公開鍵 (ここでは id_rsa.pub としています) を Dockerfile と同じ階層に配置しています。

amazonlinux2
├── Dockerfile
└── id_rsa.pub

Dockerfile に以下を記述します。

# ベースイメージ
FROM amazonlinux:2

# 同一ディレクトリの公開鍵ファイルを Docker イメージの /tmp ディレクトリにコピーする
COPY ./id_rsa.pub /tmp/

# インストール済みのパッケージを最新版にアップデート
RUN yum -y update && \
    # 追加で必要なパッケージをインストール
    yum -y install \
        sudo \
        shadow-utils \
        procps \
        wget \
        openssh-server \
        openssh-clients \
        which \
        iproute \
        e2fsprogs && \
    # キャッシュを削除
    yum clean all && \
    # setuptools をインストール
    wget https://bootstrap.pypa.io/ez_setup.py -O - | sudo python && \
    # SSH サーバを起動
    systemctl enable sshd.service && \
    # パスワード認証による SSH アクセスを禁止
    echo "PasswordAuthentication no" >> /etc/ssh/sshd_config && \
    # ec2-user を追加
    useradd ec2-user && \
    # ec2-user で sudo コマンドを使用できるようにする
    echo "ec2-user ALL=NOPASSWD: ALL" >> /etc/sudoers && \
    # ec2-user のホームディレクトリ下に公開鍵を配置
    sudo -u ec2-user mkdir -p /home/ec2-user/.ssh && \
    mv /tmp/id_rsa.pub /home/ec2-user/.ssh/ && \
    chmod -R go-rwx /home/ec2-user/.ssh && \
    # 公開鍵を登録
    cat /home/ec2-user/.ssh/id_rsa.pub >> /home/ec2-user/.ssh/authorized_keys && \
    # ロケールを設定
    echo "export LANG=en_US.UTF-8" >> /home/ec2-user/.bash_profile

他にも必要なパッケージがある場合は yum -y install の部分に追加してください。ただし、あくまでも環境構築を検証するためのイメージなので、EC2 にデフォルトで入っていないものはここではインストールしないようにしています。

Docker イメージをビルドします。ここではタグ名を amazonlinux2-sshd としています。
amazonlinux2 ディレクトリに移動して以下のコマンドを実行します。

$ docker build -t amazonlinux2-sshd .

ビルド完了後に docker images コマンドで amazonlinux2-sshd が作成されていることを確認してみてください。

正常にビルドができたことを確認できたら以下のコマンドで起動してみます。ここではコンテナ名を amazonlinux2-sshd-container とし、22 番ポートに 2222 番ポートから接続できるようにしています。

ここで --privileged を付加しないと SSH 接続が許可されないようです。さらに --rm を付加して終了時にコンテナを削除するようにしていますが、削除しない場合はこのオプションを除外してください。

$ docker run --privileged --rm -d -p 2222:22 --name amazonlinux2-sshd-container amazonlinux2-sshd /sbin/init

起動したコンテナに SSH 接続してみます。普通に SSH 接続してもいいのですが、コンテナを作成するたびに信用するサーバを登録するのは面倒なので、無条件にサーバに接続するための alias コマンド ssh-igk (他の別名でも OK) を登録してそちらで接続することにします。

こちらの作業はホスト OS 側で実施します。ホームディレクトリにある .bashrc の末尾に alias の行を追加します。

$ vi ~/.bashrc
~/.bashrc
# (前略)

alias ssh-igk='ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no'

登録した alias を使用するために以下を実行しておきます。

$ source ~/.bashrc

登録した ssh-igk コマンドを使用して ec2-user で SSH ログインしてみます。

$ ssh-igk -i ~/.ssh/id_rsa ec2-user@localhost -p 2222
Warning: Permanently added '[localhost]:2222' (ECDSA) to the list of known hosts.
Last login: Wed Jul  3 12:21:43 2019 from gateway
[ec2-user@98a69d756c81 ~]$

警告のメッセージが出ますが問題なくログインできました。

以上で準備ができたので以下のコマンドでコンテナを停止しておきます。

$ docker stop amazonlinux2-sshd-container

システムを構築してみる

ベースとなる EC2 に近いイメージが作成できたので、こちらを使用してシステム構築を検証する環境を構築できます。ここでは sample-system を作成する例を説明します。

ディレクトリ構成は以下とします。
(あらかじめ前述の amazonlinux2-sshd をビルドしておく必要があります。)

sample-system
├── docker-compose.yml
├── sample-core-api
│   └── Dockerfile
└── sample-front-ui
    └── Dockerfile

以下の要素でシステムが構成されている前提とします。

  • sample-front-ui: フロント画面を提供するシステムを想定
  • sample-core-api: データを提供する API システムを想定
  • sample-db: 永続化のための RDB を想定 (実際の AWS 環境では RDS や Aurora など)

作成したそれぞれのファイルに以下を記述します。

sample-front-ui/Dockerfile

# あらかじめ amazonlinux2-sshd をビルドしておく必要があります
FROM amazonlinux2-sshd

# 以下はサンプルです
RUN amazon-linux-extras enable php7.3 && \
    yum install -y php

sample-core-api/Dockerfile

# あらかじめ amazonlinux2-sshd をビルドしておく必要があります
FROM amazonlinux2-sshd

# 以下はサンプルです
RUN amazon-linux-extras install -y java-openjdk11
docker-compose.yml
version: '3'

services:

  # フロント画面を提供するシステムを想定
  sample-front-ui:
    build:
      # sample-front-ui/Dockerfile のイメージを使用
      context: ./sample-front-ui
    container_name: sample-front-ui
    restart: always
    privileged: true
    command: /sbin/init
    ports:
      # ホスト OS からの SSH ログイン用
      - "2223:22"
      # ホスト OS からの Web ページ動作確認用
      - "8880:80"
    networks:
      samplenet:
        ipv4_address: 172.20.0.10

  # データを提供する API システムを想定
  sample-core-api:
    build:
      # sample-core-api/Dockerfile のイメージを使用
      context: ./sample-core-api
    container_name: sample-core-api
    restart: always
    privileged: true
    command: /sbin/init
    ports:
      # ホスト OS からの SSH ログイン用
      - "2224:22"
      # ホスト OS からの API 動作確認用
      - "8881:8080"
    networks:
      samplenet:
        # フロントシステムからの接続を想定
        ipv4_address: 172.20.0.11

  # 永続化のための RDB を想定
  sample-db:
    # その時点の Aurora で使用できる PostgreSQL のバージョンと合わせていたりします
    image: postgres:10
    container_name: sample-db
    restart: always
    environment:
      POSTGRES_PASSWORD: changeit
    ports:
      # ホスト OS からの DB 接続用
      - "5444:5432"
    networks:
      samplenet:
        # API システムからの接続を想定
        ipv4_address: 172.20.0.12

# ネットワーク設定
networks:
  samplenet:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/24

sample-system ディレクトリに移動して以下のコマンドを実行します。

$ docker-compose up -d

イメージがビルドされていない場合はビルドが実行され、各システムが実行されます。
以下のコマンドで稼働状況を確認できます。

$ docker ps
CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                                          NAMES
a4acb453f41a        sample-system_sample-front-ui   "/sbin/init"             2 minutes ago       Up 2 minutes        0.0.0.0:2223->22/tcp, 0.0.0.0:8880->80/tcp     sample-front-ui
acf6c2276129        sample-system_sample-core-api   "/sbin/init"             2 minutes ago       Up 2 minutes        0.0.0.0:2224->22/tcp, 0.0.0.0:8881->8080/tcp   sample-core-api
6980309c6d57        postgres:10                     "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        0.0.0.0:5444->5432/tcp                         sample-db

フロントシステムに SSH ログインして環境構築作業を検証する場合は以下を実行します。

$ ssh-igk -i ~/.ssh/id_rsa ec2-user@localhost -p 2223
Warning: Permanently added '[localhost]:2223' (ECDSA) to the list of known hosts.
[ec2-user@a4acb453f41a ~]$

検証できたコマンドを sample-front-ui/Dockerfilesample-core-api/Dockerfile などの RUN に追記していけば最終的に開発 PC 上にシステム全体を自動構築することも可能です。

立ち上げたシステムをまとめて停止する場合は以下を実行してください。

$ docker-compose down --rmi all

上記では停止時にイメージをすべて削除するので、削除しない場合は --rmi all の部分を除外してください。

おわりに

今回ご紹介した内容は Docker の一般的な使用方法とは少し異なるかと思います。一般的には 1 つのコンテナには 1 プロセスを実行させるように構成するので、Amazon Linux 2 にいろいろインストールするのではなく、PHP、Nginx、Java、PostgreSQL などをそれぞれの Docker イメージから起動していく方針となるはずです。あとそもそもコンテナに SSH ログインすることはあまりないと思います。

また、今後 AWS において EC2 を直接起動するのではなく Fargate、ECS、EKS を使用することが多くなりそうなので、今回のような EC2 を前提としたシステム構築を検証する頻度は減ってきそうです。

と、最後に記事を全否定ですがw 小さなシステムなどはまだ EC2 を使用して構築する場合もあるかと思いますのでその際はコストを気にすることなく動作検証をする方法の一つとして試してみてください。

最後に今回の手順はすべて Windows 10 Pro で実施していますが、Mac や Linux でも同様に実施できると思います。もしうまく動かない部分や記事に誤りなどありましたらご指摘ください。:bow:

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

プライベートDocker Registryの構築方法

プライベートDocker Registryを構築する方法を記載します。
この記事では、Docker Registryへの通信をhttpsで行っています。
社内で運用する目的で構築するので、http通信でも問題ありませんが、Docker Registryの通信方法は、デフォルトではhttpsになっており、httpよりもhttpsの方が簡単に構築できました。自己証明書でも運用には問題がないので、自己証明書で構築するようにしています。
最終的に構築したDocker Registryにローカル(Docker Registryが置かれたサーバー)からイメージのpush/pullを行えるようになります。

記事の中で<>で囲っているものは、名前が任意のものであることを示しています。

環境

ubuntu 18.04
docker-ce 19.03.4
OpenSSL 1.1.0g

構築方法

イメージのダウンロード

Docker Resistryは、Docker hubに公式のイメージがあるので、それをダウンロードします。

$ docker pull registry

自己証明書の作成

https通信用の証明書を作成します。

秘密鍵の作成

秘密鍵の名前をserver.keyとします。

$ openssl genrsa -aes256 -out <server.key> 2048  
Generating RSA private key, 2048 bit long modulus
....................+++++
...........................+++++
e is 65537 (0x010001)
Enter pass phrase for <server.key>:(任意のパスフレーズ)
Verifying - Enter pass phrase for <server.key>:

※注意
秘密鍵にパスワードが設定されていると、エラーによりDocker Registryコンテナが起動しません。
しかし、秘密鍵作成時に以下のように必ずパスワードを要求してくるので、適当なパスワードを設定し、後から削除します。
You must type in 4 to 1023 characters

秘密鍵のパスワードを削除

$ openssl rsa -in <server.key> -out <server.key> -passin pass:(秘密鍵のパスワード)

証明書発行要求の作成

証明書発行要求の名前をserver.csrとします。

$ openssl req -new -key <server.key> -out <server.csr>

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]: (未入力)
State or Province Name (full name) [Some-State]:(未入力)
Locality Name (eg, city) []:(未入力)
Organization Name (eg, company) [Internet Widgits Pty Ltd]:(未入力)
Organizational Unit Name (eg, section) []:(未入力)
Common Name (e.g. server FQDN or YOUR name) []:<myregistry>(Docker Resistryがあるノードのドメインを入力)
Email Address []:(未入力)
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:(未入力)
An optional company name []:(未入力)

自己証明書作成

証明書の名前をserver.crtとします。

$ openssl x509 -days 3650 -in <server.csr> -req -signkey <server.key> -out <server.crt>
Signature ok
subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = <myregistry>
Getting Private key

Docker Registryの起動

証明書を<registry-certs>ディレクトリに置きます。

$ mkdir -p /<registry-certs>
$ mv <server.key> <server.csr> <server.crt> /<registry-certs>

コンテナを起動します。
なお、今回はホスト側のポートを5000番にマッピングしてますが、ホスト側のポートは、任意です。コンテナ側のポートは、公式イメージで5000番がエクスポートされているので、固定です。

$ docker run \
  -d \
  -p 5000:5000 \
  -v /<registry-certs>:/<certs> \
  -v /<registry-data>:/var/lib/registry \
  -e REGISTRY_HTTP_TLS_CERTIFICATE="/<certs>/<server.crt>" \
  -e REGISTRY_HTTP_TLS_KEY="/<certs>/<server.key>" \
  -e REGISTRY_HTTP_TLS_ADDR="0.0.0.0:5000" \
  registry

オプションの説明

 -d:コンテナをバックグラウンドで起動します。
 -p:ホスト側のポートとコンテナ側のポートをマッピングします。<host_port>:<container_port>
 -v:コンテナのボリュームをホストにマウントします。<host_volume>:<container_volume>
 -e:コンテナに環境変数を設定します。

動作確認

イメージをpushする

確認用のイメージをpullします。

$ docker pull hello-world

Registryにイメージをpushするときは、以下の形式で書きます。

<repositry>:<registry_port>/<project>/<image_name>:<tag>

<repositry>の箇所は、docker hub上のリポジトリを指しています。
今回は、プライベートレジストリなのでリポジトリは、ありません。
このような場合、<repository>は、Docker Registryのあるノード名に置き換えられます。
<tag>はデフォルトでは、latestとなります。

$ docker tag hello-world localhost:5000/<hello-project>/<hello-world>

イメージをpushします。

$ docker push localhost:5000/<hello-project>/<hello-world>

イメージがpushされます。/registry-data以下にイメージが置かれていることが確認できます。
また、pushをpullに変えれば、pullも行えます。

参考文献

1.「レジストリ・サーバのデプロイ」,
  http://docs.docker.jp/registry/deploying.html#running-a-domain-registry ,
  2019年11月17日アクセス
2.「オレだよオレオレ認証局で証明書つくる」,
  https://qiita.com/ll_kuma_ll/items/13c962a6a74874af39c6 ,
  2019年11月17日アクセス

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

code-server (3) VSCode の Plugin を 利用してみる

これは、2019年 code-server に Advent Calender の 第3日目の記事です。
2日目 の続きです。
今回も、code-server って何だろう?と言う事を解説していきます。

(1) code-server って何?
(2) Dockerで独自のcode-server 環境を作って見る
(3) VSCode の Plugin を 利用してみる
(..) ローカルで、DBなどの環境も含めて構築するには
(..) オンライン上に置くには?
(..) K8Sなどの最近の流行りの環境と連携するには?
(..) Code-Serverを改造して、より良くしたい

code-server は vscode の plugin が利用できます。

vscode の plugin を利用できます。オートコンプリート などの 補助機能やリファクタリング機能を利用して、お手軽にプログラムを書けるようになります。

python で、作成してみます。

FROM python:3.8.0-buster

RUN apt-get update
# code-server を取得するのに wget を install しておく
RUN apt-get install -y wget

# 作業ディレクトリを /works にする。どこでも良いです
WORKDIR /works

# code-server のバイナリーを取得
RUN wget https://github.com/cdr/code-server/releases/download/2.1692-vsc1.39.2/code-server2.1692-vsc1.39.2-linux-x86_64.tar.gz

# code-server を /works 配下に解凍する
RUN tar -xzf code-server2.1692-vsc1.39.2-linux-x86_64.tar.gz -C ./ --strip-components 1 

WORKDIR /works/app
ENV PYTHONPATH=/works/app

# python の plugin をインストール 
RUN /works/code-server --install-extension ms-python.python
RUN /usr/local/bin/python -m pip install -U pylint --user

# デフォルトは、/works/app で起動するようにする。
CMD [ "/works/code-server", "--allow-http", "--auth", "none", "--port", "8443", "/works/app"]



ubuntu に python を インストールしても良かったのですが、公式の python image を、利用しています。

RUN /works/code-server --install-extension として、 python 向けの plugin を インストールしています。

PYTHONPATH を指定して、ルートフォルダーをしてしています。

試してみる

docker build -t cs03 .
docker run -v "$PWD:/works/app" -p "8443:8443" -it cs03  

ブラウザーを開いて、何か書く

Screen Shot 2019-12-03 at 5.39.02.png

お、補完が効いていますね!!

次回

作成したImageを配布してみましょう。 一度、作成したImageは、ほぼほぼ、同じ状態で動かす事ができます。

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

Re:ゼロから始めるコンテナ生活

グレンジ Advent Calendar 2019 5日目の記事を担当しますkitaji_ngzkと申します。
昨年に引き続き、同プロジェクトでPHPを扱うサーバーサイドエンジニアをやってます。

自分が属するプロジェクトには専任のインフラエンジニアがいないため、サーバーサイドエンジニアが兼任する形で担当しています。

ので、必然的にインフラ知識も俄然学ぶ必要があるので、今回個人的に「コンテナ」という技術に触れてみたいと思い、Dockerでローカル環境の構築からクラウドにあげて公開してみるところまで実践した軌跡をご紹介したいと思います。

ここから始めましょう!1から、、いいえゼロから!!

▼今回やってみたこと

Chapter.1 Docker公式のWordPressイメージを用いて、超速構築!
Chapter.2 GCPにMySQLサーバー作成
Chapter.3 Dockerイメージの作成とGCRにアップロード
Chapter.4 GKEのクラスタ作成
Chapter.5 クラスタにデプロイ

▼環境

macOS Mojave10.14.6
Docker for mac 2.1.0.5

Chapter.1 Docker公式のWordPressイメージを用いて、超速構築!

まず手始めにローカル環境でコンテナに触れてみます。
※前提としてDockerがインストールされていることから話を進めます。

公式のWordPress, MySQLのイメージをローカルにpullします。
↓WordPressイメージのタグ一覧
https://hub.docker.com/_/wordpress
pullする時、指定のタグを:つなぎで指定できますが、指定しなければlatestがpullされます。
Wordpressはlatestで問題ないので、以下の通り

$ docker pull wordpress

続けてMySQLもpullしますが、ここで気をつけないといけないのは、MySQLのlatestは8系で認証方法が変わっていて、WordPressの接続エラーという罠が待っているので、バージョンを指定して落としましょう。(8系でも認証方式を昔のものに戻せば、使えるっちゃあ使えます)
↓MySQLイメージのタグ一覧
https://hub.docker.com/_/mysql
8系以外の最新では現時点で5.7系があったのでそれにします。ので、以下の通り

$ docker pull mysql:5.7

さて、両方ちゃんと落とせたか確認します。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
wordpress           latest              e4bd752aeb0d        7 days ago          539MB
mysql               5.7                 cd3ed0dfff7e        5 weeks ago         437MB

こんな感じで表示されてれば成功。

ではこの二つのイメージをそれぞれ使って、それぞれのコンテナを作成していきます。
まずはMySQLコンテナから

$ docker run --name sample-mysql -e MYSQL_ROOT_PASSWORD=任意のパス -d mysql:5.7

各オプションについてはdocker run --helpとかで調べるか、「docker run オプション」とかで調べましょう。なお、sample-mysqlという部分は任意のコンテナ名です。
(また、ぶっちゃけいちいち先にpullしなくても、このrunコマンドで勝手に持ってきてくれるっちゃあくれます。)

では、無事にコンテナが起動しているか、確認しましょう。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                 NAMES
8703def027e0        mysql:5.7           "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        3306/tcp, 33060/tcp   sample-mysql

このように表示されてれば成功です。

それではこのMySQLサーバーにつなげるWordPressコンテナを作成していきます。

$ docker run --name sample-wordpress --link sample-mysql:mysql -d -p 8080:80 wordpress
8703def027e0e9b6cce6409ee01544285836f8cddd4293331fa07b7e1930f56b
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
6eca366c2b0c        wordpress           "docker-entrypoint.s…"   24 seconds ago      Up 23 seconds       0.0.0.0:8080->80/tcp   sample-wordpress
8703def027e0        mysql:5.7           "docker-entrypoint.s…"   6 minutes ago       Up 6 minutes        3306/tcp, 33060/tcp    sample-mysql

無事起動したようだ。それでは上記のコマンドにもある通り、ポート8080をあけたので、
http://localhost:8080
にアクセスしてみましょう
localhost_wordpress.png

このように表示されれば成功です。あとは画面通りに設定項目埋めていけば、WordPressのローカル環境はできあがりです。
ただし、今は/var/www/htmlをホストにマウントしていないので、いじりたいhtmlやphpファイルをエディタでいじいじできないという壁があります。(vim愛好家の方はdocker execでコンテナ内に入れば、用を足せると思います。ただ、このコンテナにはvimを入れてないので、vimをapt-getでインストールする必要があります。)

ではwp-admin画面まで表示されたら、一度以下のコマンドで、ホストの任意のディレクトリにDocker内のファイルをコピーしておきます。

$ docker cp sample-wordpress:/var/www/html ホストPCのディレクトリ

終わったら、ホストPCの任意のディレクトリにコピーされてるか確認しときましょう

$ ls -al コピー先のディレクトリ

でhtml以下のWordPressのファイルがもろもろあればOKです。

ここまできたら、DEDEATHDEATHDE~~~ATH!
20160922041023.png
作成したWordPressコンテナのみ削除して死に戻り!

$ docker stop sample-wordpress
sample-wordpress
$ docker rm sample-wordpress
sample-wordpress
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                 NAMES
8703def027e0        mysql:5.7           "docker-entrypoint.s…"   33 minutes ago      Up 33 minutes       3306/tcp, 33060/tcp   sample-mysql

戻ってきたら、当然先ほどのlocalhostにアクセスしても、応答がないと思います。ので、WordPressコンテナを作り直します。今度は先ほどコピーしたディレクトリをマウントさせて作ります。

$ docker run --name sample-wordpress-2 -v コピー先の絶対パス:/var/www/html --link sample-mysql:mysql -d -p 8080:80 wordpress
72d97a8374d383e4f366e1adfba3684e6d83aaffef428e23c7849be9abd216be
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
72d97a8374d3        wordpress           "docker-entrypoint.s…"   2 seconds ago       Up 1 second         0.0.0.0:8080->80/tcp   sample-wordpress-2
8703def027e0        mysql:5.7           "docker-entrypoint.s…"   39 minutes ago      Up 39 minutes       3306/tcp, 33060/tcp    sample-mysql

ここで、先ほど同様localhost:8080にアクセスしてみましょう。
localhost_sample_wordpress-2.png
最初に設定した内容そのままに復元できました。
ホストにマウントされているので、これでテーマをインストールしたりしても、ホスト側にも反映されるようになり、ホスト側をgitで管理したりすれば開発が複数人でできるようになると思います。

さて、超速でローカル環境を構築できたものの、この構成をクラウド上でも実現したいと思います。ただ、ローカルではMySQLコンテナを作りましたが、クラウド上ではDBはスケールさせないのでVMインスタンスで十分だと思います。(データの永続化という方法もありますが)
ということで、クラウド(今回はGCP)上にMySQLサーバー作っていきます。

Chapter.2 GCPにMySQLサーバー作成

チャプターにはしてみたものの、あんまりコンテナ関係ないのでさらっといきます
①MySQLコンテナに入ってまるごとdumpfile作成

$ docker exec -it sample-mysql /bin/bash
root@~~# mysqldump -uroot -p -A > sample-wordpress.dump
root@~~# exit

②ホストにコピーしてくる

$ docker cp sample-mysql:/sample-wordpress.dump ./

↑コピー先は./入れてますが、任意のディレクトリで.(ぶっちゃけマウントしてるディレクトリにもともと格納すればホストに落ちてくるという)

③のちのちのためにVPCネットワーク作成
④VMインスタンス作成→MySQL5.7入れる(←ここで入れるMySQLのバージョンはローカルで使ってたやつと一緒にするように)
⑦VMインスタンスに②のdumpファイル転送&ぶっこみ

これでDockerで作られてるMySQLコンテナの中と同じ構成のサーバーができたはず。
ここまでで、もちろんホストPCからもDBサーバーのMySQLにアクセスできるはずなので、確かめてみます。

$ mysql -h外部IPアドレス -uroot -p
Enter password:ルートパスワード
ERROR 2003 (HY000): Can't connect to MySQL server on '外部IPアドレス' (61)

!?むむ!つながらぬ。。なぜ。。
お試しでローカルからアクセスしてみようと思い、一時的にIPとポート全開放(超非推奨)してましたが、ssh用のポートはtelnetコマンドで確認してみても問題なかったので、おそらくポートは開いているだろう。
また、サーバー自体に繋がってなかったら、エラーがUnkown hostとかになりそうな気もするので、サーバー内のMySQLまでは到達していそう。
となると、MySQLの設定周りが怪しいかもってことで、調べていると以下の情報に行き着きました。
https://gist.github.com/koudaiii/10696132

念の為リンク切れちゃった時用に一部以下抜粋しておきますが、自分が引っかかったのはここでした。

2.my.cnfのbind-address設定

my.confのbind-addressの設定を確認してみる。

$ vi /etc/mysql/my.cnf

bind-address = 127.0.0.1
bind-address = (接続したいマシンのIPアドレス)
追加したい接続先のIPを書いた「bind-address」を追加していけばOK。

どのIPからも接続許可する場合はbind-addressをコメントアウトすればOK。

たしかにサーバー内のmy.confの設定内にlocalhostのみ許可のデフォ設定がありました。。(ここにたどり着くまでに自分は4,5回死に戻り(=削除して再作成したり)してます笑)
ので、教えの通りにコメントアウトし、再度ホストPCからtelnetしてみると、接続ができたので、MySQLコマンドを再度叩いてみる。

$ mysql -h外部IPアドレス -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.28 MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

:raised_hands::raised_hands::raised_hands::raised_hands:やったーーー!!:raised_hands::raised_hands::raised_hands::raised_hands:

ついにローカルからもアクセスできるようになりました。
WordPressコンテナの/var/www/html配下はホストにマウントしてあると思うので、wp-config.phpを自分の好きなエディタで編集してDB_HOSTの向き先を試しに変更してみます。以下の部分です。

/** MySQL hostname */
define( 'DB_HOST', 'mysql');

↓↓↓

/** MySQL hostname */
define( 'DB_HOST', '外部IPアドレス');

これで
http://localhost:8080
にアクセスしてみましょう。
localhost_sample_wordpress-2 2.png
少しローディングが長く感じますが、これでローカルのWordPressコンテナからクラウド上のMySQLサーバーに接続することができました!(無事MySQLをGCPに作れたようだ←これはこれで個人的にできたの嬉しい笑)

ただ、注意を一つですが、この時点でMySQLのユーザはこうなってるはずです。

mysql> SELECT user, host FROM mysql.user;
+---------------+-----------+
| user          | host      |
+---------------+-----------+
| root          | %         |
| mysql.session | localhost |
| mysql.sys     | localhost |
| root          | localhost |
+---------------+-----------+
4 rows in set (0.00 sec)

root権限がどんなIPからでも使えちゃう怖い状況なので、しっかり削除
そして、WordPressからアクセスする用のユーザを作成して、WordPressで使用するDBのみ許可する権限を与えておきましょう!→最終的に同じVPNネットワークにする予定なので、プライベートネットワークのみに制限
WordPressで使用するユーザを作成したら、wp-config.phpに反映することを忘れずに!(・・・ていうか、これ最初からやっとけばよかったんじゃね?|ω・`))
さて、もし同じようにローカルから接続してみた場合は、忘れずにIPとポートを閉めて、wp-config.phpの設定もローカルのmysqlコンテナ向きに戻しておきましょう。

Chapter.3 Dockerイメージの作成とGCRにアップロード

今できているWordPressコンテナをイメージ化する方法としては、コンテナを停止して、docker commitのコマンドを使用するか、DockerFileを作成してイメージをビルドするかなどがあります。

実際にGKEで動作させる際、WordPressのDB_HOSTは先ほど作成したMySQLサーバーを向いて欲しかったりします。となるとローカルコンテナをそっくりそのままイメージ化しても動かないでしょう。
ので、これまでやってきた流れをDockerfileで再現する形でイメージをビルドしてGCRにあげてみたいと思います。

Dockerfileは以下のように作成しました。一番最後に「クラウド用の設定でwp-config.phpを上書き」とありますが、最終的にGKEとMySQLサーバーが同VPCネットワーク内であればプライベートIPで接続できるはずなので、DB_HOSTの向き先をそのように変えてあるファイルを取り込んでいます。

FROM wordpress:latest
LABEL maintainer="kitaji_ngzk"

# vimインストール
RUN ["apt-get", "update"]
RUN ["apt-get", "install", "-y", "vim"]

# ホストのソースコードを取り込む
WORKDIR ローカルのWordPressコンテナをマウントした直上ディレクトリ
ADD /html/ /var/www/html/

# クラウド用の設定でwp-config.phpを上書き
ADD /gcp/wp-config.php /var/www/html/wp-config.php

イメージをビルドします。

$ docker build -t sample-wordpress:1.0.0 .
Sending build context to Docker daemon  52.51MB
Step 1/8 : FROM wordpress:latest
 ---> e4bd752aeb0d
Step 2/8 : LABEL maintainer="kitaji_ngzk"
 ---> d34337359340
Step 3/8 : RUN ["apt-get", "update"]
 ---> 0e10ffbdafc1
Step 4/8 : RUN ["apt-get", "install", "-y", "vim", "net-tools"]
 ---> 75c64ff2bacc
Step 5/8 : WORKDIR ローカルのWordPressコンテナをマウントした直上ディレクトリ
 ---> 5d913bf121e2
Step 6/8 : ADD /html/ /var/www/html/
 ---> b963ce7d1331
Step 7/8 : WORKDIR /var/www/html
 ---> 9d9e0ebbce17
Step 8/8 : EXPOSE 80
 ---> d7e54c624845
Successfully built d7e54c624845
Successfully tagged sample-wordpress:1.0.0

成功したようなので、確認します。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
sample-wordpress    1.0.0               5e28a38a1758        36 minutes ago      591MB
wordpress           latest              e4bd752aeb0d        2 weeks ago         539MB
mysql               5.7                 cd3ed0dfff7e        6 weeks ago         437MB

これでイメージ化できました。では、一応このイメージを使ってコンテナを起動できるかローカルで試してみて(docker run)、同じようにWordPress管理画面が表示されればOKです。
(実際プロジェクトとして開発する時は、ローカルのソースコードじゃなくて、ちゃんとgit管理されたmasterブランチのソースコードをpullしてきて、それをADDするべきかなと思います)

次に、GCRに上げるには、イメージにレジストリ名をタグ付けする必要があるので、以下の通りタグ付けします。

$ docker tag sample-wordpress:v1.0.0 gcr.io/kitaji_sample/sample-wordpress:v1.0.0
$ docker images
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
sample-wordpress                        v1.0.0              0465bcd14635        11 hours ago        539MB
gcr.io/kitaji_sample/sample-wordpress   v1.0.0              0465bcd14635        11 hours ago        539MB
wordpress                               latest              e4bd752aeb0d        2 weeks ago         539MB
mysql                                   5.7                 cd3ed0dfff7e        6 weeks ago         437MB

kitaji_sampleとなっているところはGCPのプロジェクト名です。続けて、GCRにイメージをpushします。

$ docker push gcr.io/kitaji_sample/sample-wordpress:v1.0.0
The push refers to repository [gcr.io/kitaji_sample/sample-wordpress]
d79a1d170eaf: Pushed 
d5b61126e77c: Pushed 
182f6bf618c4: Pushed 
02e5b86b9d8d: Pushed 
f190d03dd9cf: Pushed 
31edc4603d49: Pushed 
a6c798e344c1: Pushed 
7ad4f8a271af: Pushed 
8b9e7bae16d7: Pushed 
4c31d76c6594: Pushed 
983090088b87: Pushed 
53f8a4a17b10: Pushed 
01af4509e166: Pushed 
37a065eea6b3: Pushed 
8067bb2f50e2: Pushed 
ba31a1dbfcfb: Pushed 
3e84f33ac944: Pushed 
9f9d470ac131: Pushed 
86569e4ec54b: Pushed 
12fe3564ccac: Pushed 
4e9b2aba858c: Pushed 
b67d19e65ef6: Pushed 
v1.0.0: digest: sha256:a6326341ec7a3c2596125ea424460826e504265924e66e4b34f24bc762a82dce size: 4915

GCRのコンソールで確認してみると、
gcr.png
お、あがってるあがってる(ちなみにこのリポジトリ内を見ると、なんと脆弱性もチェックしてくれてて、350個もありました笑 うち重大は2個w 恒久的に外部に公開する前には重大:中以上は解決しておかないとですね汗)
インスタンスもそうですが、GCRにあげたイメージも課金対象なので、必要ない時は削除をおすすめします。詳しくはGCRの公式ドキュメント参照。

Chapter.4 GKEのクラスタ作成

先ほどのGCRへのpushもそうですが、これ以降のやり方はGCPの公式ドキュメントに詳細に書かれているので、それを参考にやってみた方がいいかと思います。(特にオプション周りとか)
自分は実際に実行したコマンドを並べて紹介していきたいと思います。完全にお試しクラスタなので、オプションで不十分だったりするところあるんですが、ご容赦ください。(autoscaleとか設定してなかったり、stackdriverの設定だったり..←後で消すのめんどくさいと思った)

$ gcloud container clusters create sample-wordpress-web --region asia-east1 --machine-type g1-small --subnetwork kitaji-sample-subnet --disk-size 10GB --network kitaji-sample-network --max-nodes-per-pool 100 --num-nodes 1
WARNING: Currently VPC-native is not the default mode during cluster creation. In the future, this will become the default mode and can be disabled using `--no-enable-ip-alias` flag. Use `--[no-]enable-ip-alias` flag to suppress this warning.
WARNING: Newly created clusters and node-pools will have node auto-upgrade enabled by default. This can be disabled using the `--no-enable-autoupgrade` flag.
WARNING: Starting in 1.12, default node pools in new clusters will have their legacy Compute Engine instance metadata endpoints disabled by default. To create a cluster with legacy instance metadata endpoints disabled in the default node pool, run `clusters create` with the flag `--metadata disable-legacy-endpoints=true`.
WARNING: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s). 
This will enable the autorepair feature for nodes. Please see https://cloud.google.com/kubernetes-engine/docs/node-auto-repair for more information on node autorepairs.
Creating cluster sample-wordpress-web in asia-east1... Cluster is being health-checked (master is healthy)...done.                                                                             
Created [https://container.googleapis.com/v1/projects/kitaji-sample/zones/asia-east1/clusters/sample-wordpress-web].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/asia-east1/sample-wordpress-web?project=kitaji-sample
kubeconfig entry generated for sample-wordpress-web.
NAME                  LOCATION    MASTER_VERSION  MASTER_IP       MACHINE_TYPE  NODE_VERSION    NUM_NODES  STATUS
sample-wordpress-web  asia-east1  1.13.11-gke.14  35.201.158.151  g1-small      1.13.11-gke.14  3          RUNNING

クラスタの作成は少し時間がかかります。
今回WARNINGたくさんあるけど、取り急ぎなんかできたっぽい。
ほんとならyamlとか作って体系的に書けるんだろうけど、いったんその辺置いといて、GKEコンソールで確認(gcloud container clusters describe クラスタ名とかでも確認できる)
gke.png

きっとあとはIngress(→LB)、Service、Deploymentの設定を入れたげれば、公開されるはずっ。

Chapter.5 クラスタにデプロイ

いよいよ最後デプロイと公開工程です。

まず先ほど作成したクラスタをkubectlコマンドで使えるようにする

$ gcloud container clusters get-credentials sample-wordpress-web --region asia-east1 --project kitaji-sample
Fetching cluster endpoint and auth data.
kubeconfig entry generated for sample-wordpress-web.

これで先ほどのクラスタに対しkubectlコマンドが使えるようになりました。kubectl get nodes とか実行したら、ぶら下がってるnodeが出てくるはず。

さて、それでは公開に向けて、Ingress、Service、Deploymentの設定を作成していきます。
設定はそれぞれyamlファイルをローカルに作成して、kubectl applyのコマンドでデプロイするだけです。(なんて簡単!)

ただ、Ingressを作るたびにIPアドレスが変わられるとインフラ開発工程上厄介になり得るので、先に静的IPアドレスを予約しておきます。

$ gcloud compute addresses create sample-wordpress-web --global
Created [https://www.googleapis.com/compute/v1/projects/kitaji-sample/global/addresses/sample-wordpress-web].
$ gcloud compute addresses list --global
NAME                  ADDRESS/RANGE  TYPE      PURPOSE  NETWORK  REGION  SUBNET  STATUS
sample-wordpress-web  XX.XXX.XX.XX   EXTERNAL                                    RESERVED

予約が完了したので、それでは各yamlファイルを作成します。それぞれ以下の通り作成しました。

sample-wordpress_deployment.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: sample-wordpress-deploy
  labels:
    app: sample-wordpress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample-wordpress
  template:
    metadata:
      labels:
        app: sample-wordpress
    spec:
      containers:
        - name: sample-wordpress-web
          image: gcr.io/kitaji-sample/sample-wordpress:1.3.0
          imagePullPolicy: Always
          ports:
            - containerPort: 80
              protocol: TCP
          readinessProbe:
            httpGet:
              path: /health.html
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 5
sample-wordpress_service.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-wordpress-service
  labels:
    app: sample-wordpress
spec:
  selector:
    app: sample-wordpress
  type: NodePort
  ports:
    - port: 8080
      targetPort: 80
      protocol: TCP
sample-wordepress_ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: sample-wordpress-ingress
  annotations:
    kubernetes.io/ingress.global-static-ip-name: sample-wordpress-web
spec:
  backend:
    serviceName: sample-wordpress-service
    servicePort: 8080

あとはkubectl applyでデプロイするだけ!(たぶん!)

$ kubectl apply -f sample-wordpress_deployment.yml
deployment.apps/sample-wordpress-deploy  created
$ kubectl apply -f sample-wordpress_service.yml
service/sample-wordpress-service created
$ kubectl apply -f sample-wordpress_ingress.yml
ingress.extensions/sample-wordpress-ingress created

あっさり作られました。一応作られてるか確認します

$ kubectl get deployment
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
sample-wordpress-deploy   1/1     1            1           10h
$ kubectl get service
NAME                       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes                 ClusterIP   XX.XX.XXX.X     <none>        443/TCP          11h
sample-wordpress-service   NodePort    XX.XX.XXX.XXX   <none>        8080:30585/TCP   10h
$ kubectl get ingress
NAME                       HOSTS   ADDRESS        PORTS   AGE
sample-wordpress-ingress   *                      80      10h

ん?Ingressのアドレスが空欄だ・・・。さっき静的IPを予約したはず。
コンソールでもみてみると、まだ、ステータスがCreating Ingressでした。なるほどingressは時間がかかるんかぁ。
しばし待ちます。

:hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand:

再度ingressだけ確認してみます

$ kubectl get ingress
NAME                       HOSTS   ADDRESS        PORTS   AGE
sample-wordpress-ingress   *       XX.XXX.XX.XX   80      12m

お、無事に作られてそう。と思いきや、コンソールで見ると、ステータスにWARNアイコン。
nothealth.png
なにやらバックエンドサービスでヘルスチェックが通っていない的なことを言っている。
実際、割り当てられた外部IPアドレスにアクセスしてみると、502。

ここからかなり時間を費やしました。。
いろいろKubernetesのヘルスチェックを調べていると、ヘルスチェック(今回指定しているのはreadinessProbe)で指定したパスに対して、200が返ってこないとダメらしい。そしてStackdriverのContainerLogを見ると、返しているのは301。。なるほど。

wordpressのあれかな、.htaccessによるリダイレクトがいけないのかなってことで、この際、health.htmlなるファイル(中身はHello.と書いただけ)を/var/www/htmlに置いてみて、readinessProbeで指定するパスを/health.htmlに変えてもう一度死に戻り、全て再applyしてみます。

すると、
ingress_ok.png
ついにOK〜〜〜!!(←泣きそう)

もしかして、これで、、アクセスできるんちゃうか!?

・・・ドキドキ・・・ドキドキ・・・・

404.png
ぐぬぬぬぬ!!!!

ただ、502ではなくなったので、進撃はしているようだ。
今度はこの404を倒す・・・このくらいの絶望で俺が止まると思うなよ!!

とは言ったものの、LBからポッドまで通信はきていそうだし、/var/www/html内にも各ファイルが配置されているし、所有者とグループがwww-dataではなく、rootになっていたので直してみたし、、と悩みまくって禿げそうだったので、先輩エンジニアの方々に相談に乗ってみてもらったところ、ついに、原因が特定できましたっ!

なんとWordPressのこの設定部分でした

mysql> SELECT * FROM wp_options WHERE option_name IN ('home','siteurl');
+-----------+-------------+------------------------+----------+
| option_id | option_name | option_value           | autoload |
+-----------+-------------+------------------------+----------+
|         2 | home        | http://localhost:8080/ | yes      |
|         1 | siteurl     | http://localhost:8080/ | yes      |
+-----------+-------------+------------------------+----------+
2 rows in set (0.00 sec)

なんというチョンボ・・・orz
管理画面でいうと「設定>一般」の赤枠部分です。
address.png
ここを

mysql> UPDATE wp_options SET option_value = 'http://Ingressに割り当てられた外部IPアドレス/' where option_name IN ('home');
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> UPDATE wp_options SET option_value = 'http://Ingressに割り当てられた外部IPアドレス/' where option_name IN ('siteurl');
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

に変更します。外部IPアドレス部分はドメインを取得していたらドメインでもOKなやつ

そしてついにっ、、、
スクリーンショット 2019-12-03 0.32.44.png

ごちそうさまです♪

最後に

やってたら意外と盛りだくさんで時間が最後たらなくなって急ぎ足になってしまいました。。
本当はこの後、Spinnakerチャレンジとかしてみたかった。

これ書くまでにいろいろグレンジの先輩エンジニアの方々にみなさん年末の鬼多忙の中、片手間でもアドバイスをいただいて、なんとか最後までできましたm(._.)m
僕の先輩方はみんな超鬼がかってました!!

グレンジには「困っていたら助けるのは当たり前」なたっち・みーさん的エンジニアがたくさんいるふれんどりぃな会社です^^
これを最後まで読んでくれた心優しきあなたのJOINをいつでもお待ちしています!!笑
https://www.grenge.co.jp/recruit/

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

VirtualBoxのインストールやdocker-QuickstartTerminalのhost-only adapterの作成が失敗して、再起動やVBのバージョン変更でも直らなかった時用の記事

背景1

windows 10 Proでdocker-QuickstartTerminalを実行しても下記のようなエラーメッセージが表示されてインストールが完了しなかった…

Running pre-create checks...
Creating machine...
(default) Copying C:\Users\me\.docker\machine\cache\boot2docker.iso to C:\Users\me\.docker\machine\machines\default\boot2docker.iso...
(default) Creating VirtualBox VM...
(default) Creating SSH key...
(default) Starting the VM...
(default) Check network to re-create if needed...
(default) Windows might ask for the permission to create a network adapter. Sometimes, such confirmation window is minimized in the taskbar.
(default) Creating a new host-only adapter produced an error: C:\Program Files\Oracle\VirtualBox\VBoxManage.exe hostonlyif create failed:
(default) 0%...
(default) Progress state: E_FAIL
(default) VBoxManage.exe: error: Failed to create the host-only adapter
(default) VBoxManage.exe: error: Querying NetCfgInstanceId failed (0x00000002)
(default) VBoxManage.exe: error: Details: code E_FAIL (0x80004005), component HostNetworkInterfaceWrap, interface IHostNetworkInterface
(default) VBoxManage.exe: error: Context: "enum RTEXITCODE __cdecl handleCreate(struct HandlerArg *)" at line 71 of file VBoxManageHostonly.cpp
(default)
(default) This is a known VirtualBox bug. Let's try to recover anyway...
Error creating machine: Error in driver during machine creation: Error setting up host only network on machine start: The host-only adapter we just created is not visible. This is a well known VirtualBox bug. You might want to uninstall it and reinstall at least version 5.0.12 that is is supposed to fix this issue
Looks like something went wrong in step ´Checking if machine default exists´... Press any key to continue...

GitHubのissueでも似たようなケースが報告されているが、再起動やVBのアップグレード、パーミッションの変更で解決している。が、なぜか幣PCでは解決しない、やむ(夢見りあむ)

背景2

あと大学の講義でVBをインストールする際、ほぼ全員のPCでインストールが失敗して阿鼻叫喚だったりのケースも下記の対策で解決したので併記しておきます。

対策

(インターネットから切り離してから)セキュリティソフトを止めてみてください(弊環境ではウイルスバスターcorpでした)
止めてから再インストールすると無事に入ります。

理由

VirtualBoxでのホストオンリーアダプター作成時、セキュリティソフトの影響で作成が失敗してるもようです。

VBoxManage.exe: error: Failed to create the host-only adapter

GUIからのインストールだとエラーメッセージが表示されないのでわかりづらいですが、ログファイルみるとおそらく同じような記載があると思われます。
ウイルス定義ファイル内にホストオンリーアダプターの作成が含まれてたりするんですかね…

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