20190522のdockerに関する記事は16件です。

UploadしてPlotするだけの簡単なhttp経由の可視化ツールを作った

Just Upload & Plot. Easy visualize tool via http.

UplodしてPlotするだけの簡単なhttp経由の可視化ツールです。

csvまたはxlsファイルをブラウザ上のUpload欄に上げるだけでさまざまな形式のグラフを描きます。

Peek 2019-05-22 23-05.gif

csv, xlsを作成時の注意

  • 1行目の1列目はx軸のタイトルになります。
  • 1列目の2行目以降はx軸になります。
  • 1行目の2列目以降は凡例になります。
  • 2行目以降の2列目以降がデータになります。
  • ファイル名はグラフタイトルになります。
  • ファイル名に_が含まれている時、最初の_で区切られて、前半部分がグラフタイトル、後半部分がy軸のタイトルになります。

対応しているグラフ形式

  • 'Line'
  • 'Bar'
  • 'Histogram'
  • 'Pie'
  • 'Polar'
  • 'Box'
  • 'Heatmap'
  • '3D Scatter'
  • '3D Surface'
  • '2D Histogram'

対応予定のグラフ形式

  • 'Contour'
  • 'Candlestick'

Install

pip

あとでpypi登録予定

Github

GitHub u1and0/uplot
クローン後、$ python uplot.py

Dockerhub

Dockerhub u1and0/uplot

$ sudo docker pull u1and0/uplot
$ sudo docker run -d -p 8880:8880 u1and0/uplot

USAGE

  1. サーバーを立ち上げたらブラウザにhttp//:localhost:8880と打ち込みます。
  2. csvかxlsで作成したファイルをドラッグ・アンド・ドロップしてグラフ種類を選択します。

ScreenShots

pic1
pic2
pic3
pic4
pic5
pic6
pic7

作り方

python製
dash (= plotly + flask)でHTMLパーツを配置していって、plotlyで可視化。サーバーをflaskで立てる。

uplot.py
#!/usr/bin/env python3
import base64
import datetime
import io
import os
from collections import defaultdict

import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table

import pandas as pd
import plotly.graph_objs as go

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

CHART_LIST = [
    'Line',
    'Bar',
    'Histogram',
    'Pie',
    'Polar',
    'Box',
    'Heatmap',
    # 'Contour',
    # 'Candlestick',
    '3D Scatter',
    '3D Surface',
    '2D Histogram',
]
CHART_LIST.sort()

app.layout = html.Div(
    [
        # File upload bunner
        dcc.Upload(
            id='upload-data',
            children=html.Div(['Drag and Drop or ',
                               html.A('Select Files')]),
            style={
                'width': '100%',
                'height': '60px',
                'lineHeight': '60px',
                'borderWidth': '1px',
                'borderStyle': 'dashed',
                'borderRadius': '5px',
                'textAlign': 'center',
                'margin': '10px'
            },
            # Allow multiple files to be uploaded
            multiple=True),
        html.H6('chart-type'),
        dcc.Dropdown(id='chart-type',
                     options=[{
                         'label': i,
                         'value': i
                     } for i in CHART_LIST],
                     value='Line'),
        html.H6('x-axis'),
        dcc.RadioItems(id='xaxis-type',
                       options=[{
                           'label': i,
                           'value': i
                       } for i in ['linear', 'log', 'category']],
                       value='linear',
                       labelStyle={'display': 'inline-block'}),
        html.H6('y-axis'),
        dcc.RadioItems(id='yaxis-type',
                       options=[{
                           'label': i,
                           'value': i
                       } for i in ['linear', 'log', 'category']],
                       value='linear',
                       labelStyle={'display': 'inline-block'}),
        html.Div(id='the_graph'),
        html.Div(id='output-data-upload'),
    ], )


def data_graph(
        df,
        filename,
        chart_type,
        xaxis_type,
        yaxis_type,
):
    """アップロードされたデータのグラフを描画"""

    basename = os.path.splitext(filename)[0]
    # ファイル名の1つ目の'_'で区切って、グラフタイトルとY軸名に分ける
    if '_' in basename:
        title, yaxis_name = basename.split('_', 1)
    # ファイル名に'_'がなければグラフタイトル、Y軸名ともにファイル名
    else:
        title, yaxis_name = basename, basename

    def args(i):
        """graph_objs helper func"""
        return {'x': df.index, 'y': df[i], 'name': i}

    # チャートの種類をディクショナリで分岐
    # 内包表記でdfの列の数だけトレース
    data = {
        'Line': [go.Scatter(args(i)) for i in df.columns],
        'Bar': [go.Bar(args(i)) for i in df.columns],
        'Histogram':
        [go.Histogram(x=df[i], name=i, opacity=.5) for i in df.columns],
        'Pie': [
            go.Pie(labels=df.index,
                   values=df[i],
                   name=i,
                   domain={'column': list(df.columns).index(i)})
            for i in df.columns
        ],
        'Polar': [
            go.Scatterpolar(
                r=df[i],
                theta=df.index,
                name=i,
            ) for i in df.columns
        ],
        'Heatmap': [go.Heatmap(x=df.index, y=df.columns, z=df.values)],
        'Box': [go.Box(y=df[i], name=i) for i in df.columns],
        # 'Contour': [go.Contour(x=df.index, y=df.columns, z=df.values)]
        '3D Scatter': [
            go.Scatter3d(x=df.index, y=df.columns, z=df[i], name=i)
            for i in df.columns
        ],
        '3D Surface': [
            go.Surface(x=df.index,
                       y=df.columns,
                       z=df.values,
                       name=yaxis_name,
                       contours=go.surface.Contours(
                           z=go.surface.contours.Z(show=True,
                                                   usecolormap=True,
                                                   highlightcolor="#42f462",
                                                   project=dict(z=True)))),
        ],
        '2D Histogram': [go.Histogram2d(x=df.iloc[:, 0], y=df.iloc[:, 1])]
    }

    # チャートの種類でレイアウトを分岐
    # 分岐にはdefaultdictを使い、デフォルトはlambda式で返す
    layout = defaultdict(
        # default layout
        lambda: go.Layout(title=go.layout.Title(text=title),
                          xaxis={
                              'type': xaxis_type,
                              'title': df.index.name,
                              'rangeslider': dict(visible=False),
                          },
                          yaxis={
                              'type': yaxis_type,
                              'title': yaxis_name,
                          },
                          margin={
                              'l': 60,
                              'b': 50
                          },
                          hovermode='closest'),
        # other layout
        {
            'Histogram':
            go.Layout(title=title,
                      xaxis={'title': 'Value'},
                      yaxis={'title': 'Count'},
                      barmode='overlay',
                      hovermode='closest'),
            'Pie':
            go.Layout(title=go.layout.Title(text=title),
                      grid={
                          'columns': len(df.columns) - 1,
                          'rows': 1
                      },
                      hovermode='closest')
        })
    return dcc.Graph(id='the_graph',
                     figure={
                         'data': data[chart_type],
                         'layout': layout[chart_type]
                     })


def data_table(df):
    """アップロードされたデータの表を描画"""
    df.reset_index(inplace=True)  # indexもテーブルに含めるため
    data = df.to_dict('records')
    columns = [{'name': _i, 'id': _i} for _i in df.columns]
    return dash_table.DataTable(data=data, columns=columns)


def parse_contents(contents, filename, date, chart_type, xaxis_type,
                   yaxis_type):
    content_type, content_string = contents.split(',')

    decoded = base64.b64decode(content_string)
    try:
        if 'csv' in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(io.StringIO(decoded.decode('utf-8')),
                             index_col=0,
                             parse_dates=True)
        elif 'xls' in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded),
                               index_col=0,
                               parse_dates=True)
    except Exception as e:
        print(e)
        return html.Div(['There was an error processing this file.'])

    return html.Div([
        data_graph(df, filename, chart_type, xaxis_type, yaxis_type),
        html.H5(filename),
        html.H6(datetime.datetime.fromtimestamp(date)),
        data_table(df),
        html.Hr(),  # horizontal line

        # For debugging, display the raw contents provided by the web browser
        html.Div('Raw Content'),
        html.Pre(contents[0:200] + '...',
                 style={
                     'whiteSpace': 'pre-wrap',
                     'wordBreak': 'break-all'
                 })
    ])


@app.callback(Output(
    'output-data-upload',
    'children',
), [
    Input('upload-data', 'contents'),
    Input('chart-type', 'value'),
    Input('xaxis-type', 'value'),
    Input('yaxis-type', 'value'),
], [State('upload-data', 'filename'),
    State('upload-data', 'last_modified')])
def update_output(list_of_contents, chart_type, xaxis_type, yaxis_type,
                  list_of_names, list_of_dates):
    if list_of_contents is not None:
        children = [
            parse_contents(c, n, d, chart_type, xaxis_type, yaxis_type)
            for c, n, d in zip(list_of_contents, list_of_names, list_of_dates)
        ]
        return children


if __name__ == '__main__':
    app.run_server(debug=True, host='0.0.0.0', port=8880)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerfileのbuild時の留意点備忘録

はじめに

buildエラーを繰り返しながらDockerfileを作成した際に遭遇した留意点を殴り書く。

留意点殴り書き

  • build時はRUNのコマンドを実行するためのコンテナが自動生成される
  • 自動生成されたコンテナはbuild終了時に自動消去される
  • コンテナイメージもbuild時に自動生成、終了時に自動消去される
  • buildがエラーになると消去されなずにExitedで残る場合がある
  • コンテナイメージもREPOSITORYがで残る場合がある
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rancherコンテナー管理プラットフォーム(V2.2.3)のハンズオン手順

Rancherのインストール(Single Node構成)

★★今回のハンズオンには、こちらからインストール済のサーバーを提供致します為、実施不要★★
SSL証明書オプション:デフォルトは自己署名証明書(Rancherが自動生成)

コマンド:rootユーザーに切替
docker run -d --restart=unless-stopped \
-p 80:80 -p 443:443 \
-v /data/rancher:/var/lib/rancher/ \
rancher/rancher:stable

Kubernetesクラスター構築と管理

・RancherへのログインURLとアカウント情報は下記となります。
※メールの内容をご参照ください。
※ログイン用のアカウント情報は、admin/adminとなっています。

受講者No(NN) ログインURL

・自己署名証明書を利用しています為、ワーニング画面が表示されますが、下記の画面のようにワーニングエラーを無視して接続してください。
image.png

・ユーザーとパスワードを入力してください。
image.png
※UI言語については、日本語版も対応されていますが、今回のハンズオンの説明資料は、English版で作成されています。
image.png

クラスター構築

image.png

・「Add Cluster」画面に、必要な情報を入力して、「Create」ボタンを押下
入力情報
ー構築方式: Amazon EC2アイコン
ーCluster Name: NN-ks ※NNの部分は受講者番号(01~07)に置き換え

ーName Prefix: NN- ※NNの部分は受講者番号(01~07)に置き換え
ーCount: 3 ※クラスタのノード数
ーTemplate: aws-ec2-node ※こちらで事前に用意してあるAWS認証情報が入ってるノードテンプレートです。用意されてない場合は、ノードテンプレートの設定手順で事前にご設定してください。
ーetcd: ☑️ ※etcdのノード数は奇数にする必要がある
ーControl Plane: ☑️ ※kube-Controlのノード数
ーWorker: ☑️ ※ワーカーのノード数

ーCloud Provider: Amazon
image.png
・クラスタのStateが「Active」になってれば、作成完了
image.png
・クラスタのダッシュボードを確認
image.png
image.png
・Rancherの管理対象の階層の確認(クラスタレベル→プロジェクトレベル)
image.png

クラスター証明書の更新

・クラスタダッシュボード画面に、「…」から、「Rotate Certificates」メニューをクリックします。
image.png
・「Rotate all service certificates」を選択して、Saveを押下します。※全てのコンポーネントの証明書を更新します。
image.png
・クラスタのアップデート中の赤いワーニングメッセージ消えるまで待ちます。
image.png

クラスターのバックアップとリストア

・クラスタダッシュボード画面に、「…」から、「Snapshot Now」メニューをクリックします。
image.png
・クラスタダッシュボード画面に、「…」から、「Restore Snapshot」メニューをクリックします。
image.png
・Snapshotリスト、先ほどバックアップしたSnapShotを確認して、一番日付が古いSnapshotを選択して、Saveを押下し、リストアさせます。
image.png
・クラスタのアップデート中の赤いワーニングメッセージ消えるまで待ちます。
image.png

クラスターのバージョンアップ

・クラスタダッシュボード画面に、「…」から、「Edit」メニューをクリックします。
image.png
・クラスタの編集画面の「Kubernetes Version」のところの利用可能のバージョンを確認します。
クラスターのバージョンアップには、時間がかかります為、今回のハンズオンには、実行しません。興味ある方は別途で行ってください。
image.png

クラスターのノードのスケールアウトとスケールイン

■スケールアウト:1つのWorkerノードを追加します。
・クラスタダッシュボード画面に、「…」から、「Edit」メニューをクリックします。
image.png
・クラスタ編集画面に、「Add Node Pool」ボタンをクリックします。
image.png
・追加した「Node Pools」行に、必要な情報を入力して、「Save」ボタンを押下
入力情報
ーName Prefix: NN- ※NNの部分は受講者番号(01~07)に置き換え
ーCount: 1
ーTemplate: aws-ec2-node ※こちらで事前に用意してあるAWS認証情報が入ってるノードテンプレートです。
ーWorker: ☑️ ※ワーカーのノード数
image.png
・ノードの作成中の赤いワーニングメッセージ消えるまで待ちます。
※AWS上にEC2を作成して、インスタンスの初期化処理を行う為、少し、時間かかります。
ワーカーの追加の為、クラスタは使用可能の状態です。待っている間に色々触ってください。
image.png
・クラスタのノード管理画面に作成したノード情報を確認します。
image.png

■スケールイン:追加したのWorkerノードを削除します。
・クラスタダッシュボード画面に、「…」から、「Edit」メニューをクリックします。
image.png
・クラスタ編集画面に、追加した「Node Pools」行の最後の「ー」ボタンをクリックして、「Save」ボタンを押下
※Workerしか☑️してない行です。削除対象を間違わないようにしてください。
image.png
・クラスタのノード管理画面に削除後のノード情報を確認します。
image.png

クラスターのプライベートなDockerレジストリ追加(オプショナル:プライベートなDockerレジストリを持つ、且つ興味ある方はやってください)

・クラスタダッシュボード画面に、「…」から、「Edit」メニューをクリックします。
image.png
・クラスタ編集画面に、下記の画面操作ように、プライベートなDockerレジストリ追加して、「Save」ボタンを押下
image.png
image.png
image.png

クラスタ配下のプロジェクト管理

・クラスタ階層のメニューバーに、「Projects/NameSpaces」メニューをクリックし、Projects管理画面に「Add Project」を押下
image.png
・Project追加画面に、下記の情報を入力し、「Create」ボタンを押下
ーProject Name:InfraTeam
image.png
・メニューバーに、クラスター配下に、InfraTeamプロジェクトが入っていることを確認し、InfraTeamプロジェクトをクリックします
image.png
・「NameSpaces」をクリックし、「Add Namespace」ボタンを押下
image.png
image.png
・Namespace追加画面に、下記の情報を入力し、「Create」ボタンを押下
ーName:InfraTeamSub1
image.png

※「GKE」と「Custom」アイコンからのクラスタ作成いついて、最後に時間の余裕があれば、デモします

ユーザー認証

ユーザー認証方式 - 認証プロバイダ経由の認証

・Globalのメニュー階層で、「Authentication」メニューをクリック
image.png
・Authentications設定画面に、対象の認証プロバイダを選択して、接続情報や設定情報を入力して、保存します。※認証プロバイダは用意していない為、接続までの操作はハンズオン対象外です。興味ある方は、別途に試してください。
image.png

ユーザー認証方式 - ローカル認証

・Globalのメニュー階層で、「Users」メニューをクリック
image.png

構築したnn-ksクラスタにクラスタ管理者ユーザー(infra-admin)を追加

・「Users」画面に、右上の「Add User」ボタンを押下し、下記の画面のように、ユーザーを作成します。
image.png
image.png
image.png
・クラスタダッシュボード画面に、「…」から、「Edit」メニューをクリックします。
image.png
・権限付与。クラスタ編集画面に、「Add Member」ボタンを押下し、追加された入力行に作成したユーザーを「Owner」として入力し、「Save」ボタンを押下
image.png

構築したnn-ksクラスタにプロジェクト管理者ユーザー(project-admin)を追加

・「Users」画面に、右上の「Add User」ボタンを押下し、下記の画面のように、ユーザーを作成します。
image.png
image.png
image.png
・メニューバーに、クラスター配下に、InfraTeamプロジェクトが入っていることを確認し、InfraTeamプロジェクトをクリックします
image.png
・「Member」メニューをクリックし、「Add Member」ボタンを押下
image.png
・権限付与。プロジェクトメンバー追加画面に、下記の画面ように入力して、「Add Member」ボタンを押下
image.png
image.png

クラスタ管理者ユーザー(infra-admin)の動作確認

・infra-adminユーザーでログインして、「クラスター証明書の更新」ができることを確認します。※手順は、「クラスター証明書の更新」をご参照ください。
image.png

プロジェクト管理者ユーザー(project-admin)の動作確認

・project-adminユーザーでログインして、「NameSpaces作成」ができることを確認します。
・「NameSpaces」をクリックし、「Add Namespace」ボタンを押下
image.png
image.png

アプリデプロイとアプリ管理

・adminユーザーでログインして、下記の手順を行います。

nginxアプリをデプロイ

・メニューバーに、クラスター配下のInfraTeamをクリックし、InfraTeamプロジェクトに遷移します。
image.png
・右上の「Deploy」ボタンをクリックし、開く「Deploy Workload」画面に、必要な情報入力して、「Launch」ボタンを押下
image.png
入力情報
ーName:nginx-test
ーDocker Image:nginx:1.9
image.png
image.png

クラスタの外部からnginxアプリにアクセス - NodePort方式

・メニューバーの下の「Service Discovery」タブをクリックして、「Service Discovery」画面を開きます。
image.png
image.png
・「Service Discovery」画面の右上の「Add Record」ボタンを押下し、開く「Add Record」画面の「Show advanced options」をさらにクリックします。
image.png
image.png
・「Add Record」画面に、下記のような内容を入力して、「Create」ボタンを押下。
image.png
・nginx-nodeportサービスが作成されていることを確認し、サービス名のnginx-nodeport直下のportのリンクをクリックし、Nginx画面が表示されることを確認。
image.png
image.png

クラスタの外部からnginxアプリにアクセス - LoadBalancer方式

・メニューバーの下の「Service Discovery」タブをクリックして、「Service Discovery」画面を開きます。
image.png
image.png
・「Service Discovery」画面の右上の「Add Record」ボタンを押下し、開く「Add Record」画面の「Show advanced options」をさらにクリックします。
image.png
image.png
・「Add Record」画面に、下記のような内容を入力して、「Create」ボタンを押下。
image.png
・nginx-lbサービスが作成されていることを確認し、サービス名のnginx-lb直下のportのリンクをクリックし、Nginx画面が表示されることを確認。
image.png
image.png
※LoadBalancerはAWS側で非同期で作成されるため、アクセスできるまで2、3分かかる場合があります。

クラスタの外部からnginxアプリにアクセス - Ingress方式

・メニューバーの下の「Load Balancing」タブをクリックし、開く「Load Balancing」画面の右上の「Add Ingress」ボタンをクリックします。
image.png
image.png
・「Add Ingress」画面に、下記のような内容を入力して、「Create」ボタンを押下。
image.png
・nginx-ingressが作成されていることを確認し、URLのリンクをクリックし、Nginx画面が表示されることを確認。
image.png
image.png

nginxアプリのスケールアウト

・メニューバーの下の「Workloads」タブをクリックし、開く「Workloads」画面のリストに、nginx-testのリンクをクリックします。
image.png
・開く「Workload: nginx-test」画面に、「+」ボタンを押下します。
image.png
・nginx-testアプリのPODが2つになっていることを確認します。
image.png

nginxアプリのバージョンアップとロールバック

・「Workloads」画面のリストに、nginxアプリの行の「…」から、「Edit」メニューをクリックします。
image.png
「Upgrade Service」画面に、Docker Imageを「nginx:1.10」に変更して、「Upgrade」ボタンを押下します。
image.png
・「Workloads」画面のリストに、nginxアプリが「nginx:1.10」に変更されていることを確認します。
image.png

・「Workloads」画面のリストに、nginxアプリの行の「…」から、「Rollback」メニューをクリックします。
image.png
・一番古いバージョン(初期のバージョン)を選択して、「Rollback」バタンを押下
image.png
・「Workloads」画面のリストに、nginxアプリが「nginx:1.9」に戻っていることを確認します。
image.png

アプリのデバッグ ー コンテナー接続

・メニューバーの下の「Workloads」タブをクリックし、開く「Workloads」画面のリストに、nginx-testのリンクをクリックします。
image.png
・開く「Workload: nginx-test」画面に、任意のPODの行の「…」から、「Execute Shell」リンクをクリックします。
image.png
image.png

アプリのデバッグ ー コンテナーログ参照

・開く「Workload: nginx-test」画面に、任意のPODの行の「…」から、「View Logs」リンクをクリックします。
image.png
image.png

永続化ストレージの使用

Storage Classの作成

・クラスタメニューの階層に、「Storage」→「Storage Classes」メニューをクリックします。
image.png
・開く「Storage」画面の右上の「Add Class」ボタンをクリックします。開く「Add Storage Class」画面に、NameとProvisionerを入力して、「Save」ボタンをクリックします
image.png
image.png
image.png

Storage Classの使用(Storage Classに指定しているProvisionerで動的にPersistent Volumeを作成させる)

・プロジェクト(infraTeam)メニューの階層に、Workloads画面に、右上の「Deploy」ボタンをクリックします。
image.png
・開く「Deploy Workload」画面、下記のように、設定情報を入力して、「Add Volume」ボタンをクリックします。
image.png
・「Add a Volume Claim Template」メニューをクリックします。
image.png
・「Add Volume Claim Template」画面、下記のように、設定情報を入力して、「Define」ボタンをクリックします。
image.png
・「Deploy Workload」画面、「Mount Point」を入力して、「Launch」ボタンをクリックします。
image.png
image.png
※aws側で、Provisionerで動的にVolumeを生成して、PODがいるインスタンスにマウントしますため、少しかかります。

Storage Classによる動的に生成したPVの確認

・クラスタメニューの階層に、「Storage」→「Persistent Volumes」メニューをクリックします。動的に生成したPVを確認します。
image.png
・PVのリンクをクリックして、PVの詳細を確認します。
image.png

運用監視機能

モニタリング機能の有効化

・クラスタメニューの階層に、「Tools」→「Monitoring」メニューをクリックします。
image.png
・下記の操作ように、デフォルトで搭載されているモニタリング機能を有効にします。
image.png
・下記の赤いワーニングが消えるまで、待ちます。※モーニングの関連リーソスがクラスタにデプロイされています。
image.png
image.png

モニタリング機能の確認

・クラスタのダッシュボード上に、メトリックのサマリー情報を確認
image.png
・Grafana上に、メトリック情報を確認
image.png
image.png
・Grafana上に、先ほどの手順にデプロイしたアプリのメトリックも確認
image.png

アラートの連携・通知先の設定

・クラスタメニューの階層に、「Tools」→「Notifiers」メニューをクリックします。
image.png
・「Add Notifier」ボタンをクリックします
image.png
・「Notifier」画面、下記のように、設定情報を入力し、「TEST」ボタンでテストしてから、「Add」ボタンをクリックします。
入力情報(SlackのWebhookとチャンネルはこちら用意してある下記のものを設定)
ーName:slack-test
ーURL:Slack側のhookのURL
ーDefault Channel:#rancher-alert
image.png
image.png

アラート機能の確認

・クラスタメニューの階層に、「Tools」→「Alerts」メニューをクリックします。
image.png
・デフォルトで、etcd、kube-componetns、eventとnodeなどの基本のアラートグループとアラートルールが設定されています。
image.png

アラートグループとアラートルールをカスタマイズで作成

・「Cluster Alert Groups」画面の右上の「Add Alert Group」ボタンをクリックします。
image.png
・「Add Alert Group」画面、下記のように設定情報を入力し、「Create」ボタンをクリックします。
ーExpression:sum(node_load1) by (instance) / count(node_cpu_seconds_total{mode="system"}) by (instance) * 100
image.png

image.png
・Slackのチャンネルにメッセージが通知されるメッセージを確認します。約1分間後に通知されます。
image.png
image.png

カタログ機能

※カタログ機能について、最後に時間の余裕があれば、デモします。

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

Dockerの使い方

Dockerの基本概念

Dockerとはアプリケーションを開発・移動・実行するためのプラットフォームです。
設計思想は開発者が簡単にアプリケーションを動かす環境を作る。

Docker Container

  • 実際動かすアプリケーションの環境
  • コンテナ毎にリソースをもつ
  • 仮想環境と同じような考え方ですが、コンテナはアプリケーション単位
  • 整合性の高いアプリケーションや、それに関する環境設定を同じコンテナに設定すればよい

Docker Image

  • コンテナを実行するためのファイルシステム
  • 仮想環境のイメージファイルと相当
  • バージョン管理はGitlabなどのWebサービスを利用してもよいし、ローカルでも管理出来ます。

Docker File

  • イメージを作成するための設定ファイル。
  • Imageの環境構築手順を書けます。
  • ソースファイルと一緒に管理し、デプロイする度に呼び、該当バージョンのイメージを作成

Docker yaml

  • DockerFileとほば同じ役割
  • DockerFileより、書きやすい
  • その他は勉強中。。。

Dockerのインストール

最新版をダウンロードして、インストールだけ
検証手順:https://qiita.com/kurkuru/items/127fa99ef5b2f0288b81

Docker Fileの作り方

  • ファイル名「Dockerfile」としてファイルを新規作成
  • FROMでベースになるイメージを指定
  • RUNCMDなどで実行コマンドを必要に応じて追加
FROM python:3.6

COPY . /app
WORKDIR /app

RUN pip install -r /app/requirements.txt
CMD python DemoApp/src/run_server.py

Docker Imageの使う方

DockerFileからイメージを作成する

docker build -t admin/httpd .

現在のフォルダにあるDockerFileにより、「admin/httpd」という名前のイメージを作成する

コンテナからイメージを作成する

docker commit demo-container demo/img:qa 

コンテナ「demo-container」をベースに名前「demo/img」タグ「qa」のイメージを作成する

イメージ一覧の表示

docker images

現在ローカルにあるイメージの一覧を表示する

イメージの削除

docker rmi demo/img

名前「demo/img」のイメージをローカルから削除する

イメージの更新

docker pull demo/img

名前「demo/img」のイメージの最新版を取得

Docker Containerの使う方

既存コンテナの一覧を表示

docker ps

各コンテナの状態も見えます。

イメージファイルからコンテナを生成し、起動する

docker run admin/httpd

名前「admin/httpd」のイメージでコンテナを生成し、起動する

よく使うオプション:

  • --name コンテナ名を指定
  • -d 起動後にバックグランドで実行
  • -i 起動時に STDOUT を開きっぱなしにしてくれる
  • -e 環境変数の指定
  • -p ポートの開放
  • -v ホストディレクトリをコンテナにマウントする

作成したコンテナに対する操作

  • コンテナを起動
docker start admin/httpd
  • コンテナを停止
docker stop admin/httpd
  • コンテナを再起動
docker restart admin/httpd

コンテナを削除

docker rm admin/httpd

削除前に停止する必要があります。

Docker yamlの作り方

学習中
https://qiita.com/yuta-ushijima/items/d3d98177e1b28f736f04
https://qiita.com/zembutsu/items/9e9d80e05e36e882caaa

参考リンク

https://qiita.com/curseoff/items/a9e64ad01d673abb6866
https://www.slideshare.net/zembutsu/docker-images-containers-and-lifecycle
https://qiita.com/tera_shin/items/8a43e904bd15990d3129#%EF%BC%97%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E4%B8%80%E8%A6%A7

後続の調査方向:Kubernetes

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

MacでCassandra環境をローカルで構築する

概要

環境構築の備忘録。
Cassandra をローカルで動かしたかったので、そのための手順を残しておく。

手順

Cassandra 本体の準備

今回はDockerで動かしてみる

docker-compose.yml
version: '2'
services:
  cassandra:
      image: cassandra:3.9
      container_name: cassandra
      ports:
        - "9042:9042"
      environment:
        - "MAX_HEAP_SIZE=256M"
        - "HEAP_NEWSIZE=128M"
      restart: always
      volumes:
        - ./out/cassandra_data:/var/lib/cassandra0

※ 細かい設定は適宜変更

cql(Cassandra版のSQL) をインストール

1. pip をインストール

※ インストールされている場合はスキップで OK

$ sudo easy_install pip

2. pip で cql をインストール

$ sudo pip install cql

私の環境の場合は以下のエラーが出た。

ERROR: Cannot uninstall 'six'. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall.

オプションを付けることで回避できた。

$ sudo pip install cql -I six

pip のオプション一覧

3. Cassandra へ接続

Docker を立ち上げてから接続

$ cqlsh
Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 3.9 | CQL spec 3.4.2 | Native protocol v4]
Use HELP for help.
cqlsh>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacでCassandraをローカルに構築する

概要

環境構築の備忘録。
Cassandra をローカルで動かしたかったので、そのための手順を残しておく。

手順

Cassandra 本体の準備

今回はDockerで動かしてみる

docker-compose.yml
version: '2'
services:
  cassandra:
      image: cassandra:3.9
      container_name: cassandra
      ports:
        - "9042:9042"
      environment:
        - "MAX_HEAP_SIZE=256M"
        - "HEAP_NEWSIZE=128M"
      restart: always
      volumes:
        - ./out/cassandra_data:/var/lib/cassandra0

※ 細かい設定は適宜変更

cql(Cassandra版のSQL) をインストール

1. pip をインストール

※ インストールされている場合はスキップで OK

$ sudo easy_install pip

2. pip で cql をインストール

$ sudo pip install cql

私の環境の場合は以下のエラーが出た。

ERROR: Cannot uninstall 'six'. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall.

オプションを付けることで回避できた。

$ sudo pip install cql -I six

pip のオプション一覧

3. Cassandra へ接続

Docker を立ち上げてから接続

$ cqlsh
Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 3.9 | CQL spec 3.4.2 | Native protocol v4]
Use HELP for help.
cqlsh>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Redis Commander を Dockerで

Redis Commander

Redis CommanderはブラウザでRedisに接続できるものですが
Dockerコンテナで dbIndex 0 と dbIndex 1 など,複数表示するのにはどうすればいいか?


こんな感じにしたい

Redis Commander: Home - Google Chrome_002.png


試した環境

VirtualBoxの中の CentOS Linux release 7.6.1810 (Core)


こうすれば出来ました


docker-compose.yml

version: '3'
services:

  redis:
    container_name: redis-container
    image: redis:5
    command: redis-server --appendonly yes --requirepass foobared
    volumes:
       - /home/vagrant/redis-data/redis:/data
    ports:
      - "6379:6379"
    environment:
      TZ: JST-9

  redisCommander:
    container_name: redis-commander-container
    image: rediscommander/redis-commander:latest
    environment:
      REDIS_HOSTS: Index0:redis-container:6379:0:foobared,Index1:redis-container:6379:1:foobared
      TZ: JST-9
    ports:
      - "8081:8081"
    depends_on:
      - redis

ポイント1

DockerHubのドキュメントにDockerの欄があって

REDIS_PORT
REDIS_HOST
REDIS_SOCKET
REDIS_TLS
REDIS_PASSWORD

REDIS_PASSWORD これを書こうとしてましたが、接続できない X(

 redisCommander:
    image: rediscommander/redis-commander:latest
    environment:
      - REDIS_HOSTS=local:redis:6379
      - REDIS_PASSWORD=foobared

Error: Ready check failed: NOAUTH Authentication required. にも同じ失敗した人がいました。

ちゃんと Valid host strings として

label:hostname:port:dbIndex:password

書式がありました。

ポイント2

dbIndex 複数は、どうやって定義するのか?

Specify multiple hosts with labels の例をみると , で区切ってたので

, 区切りで続けて書けば出来ました。

REDIS_HOSTS: Index0:redis-container:6379:0:foobared,Index1:redis-container:6379:1:foobared
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(No.5) おじさんが、LINE風アプリを開発する - メッセージの送受信を考えてみるよ

前回までのあらすじ

メッセージを送受信するイメージ(もうそう)

おじさん、LINE風の仕組みをつくるために、まずは、メッセージの送受信を妄想してみるよ。

メッセージを宛先(スマホやPC)に直接送信する案と、問題点について

  • おじさん、下図のようにスマホ同士で、直接メッセージを送信すれば良いと考えたよ。
  • おじさんからのメッセージ「こんにちは」を、Aさんのスマホに送信するイメージだよ。 image.png
  • でもLINEを調べてみると、スマホやPCやタブレットなど、複数の環境で、過去に受信済みのメッセージを見ることが出来るらしいんだ・・・。
    • 後でAさんがPCやタブレットにLINEをインストールするケースを考えると、Aさんのスマホに直接メッセージを投げる方法では実現出来ないことかも・・・。
    • 「こんにちは」は、PCやタブレットを導入する前に、スマホに送信したメッセージなのだから・・・。
  • 単純におじさんが送信するメッセージを、宛先のスマホやPCに送信する方法だと、LINEとは異なる動きのアプリになりそうなんだ。(おじさんの考えた案は、トランシーバのようなものに近いかもだよ・・・) image.png

問題を解決するアイディア

  • 前述の案だと、随分と後になってアプリをインストールした端末上では、過去のメッセージを見ることが出来ない問題が出てきたよ。
    • この問題は、宛先の端末に直接メッセージを送信することによって、発生する問題と思うんだよ。
      • 解決方法は、おじさんの送信したメッセージを、誰でも見れるような場所に置いておく必要があると思うんだ。(町内掲示板みたいなイメージだよ)
  • 下図のアイディアでは、おじさんのメッセージを入れておく、ナゾ箱を設置することにしたんだよ。
    • 直接相手にメッセージを送信するのではなくて、ナゾの箱におじさんはメッセージを送信することにしたよ。
    • 宛先のAさんは、気が向いたときにナゾ箱の中身を確認して、メッセージが届いていることを確認すれば良いんだよ。
    • この方法だと、スマホからメッセージを読むことも可能だし、後で購入したPC・タブレットから読むことも可能だよね。
  • メッセージを読み書きすることが出来るような、共有の箱、ナゾ箱を設置する必要性を感じたんだよね(照)。 image.png

ナゾ箱

  • ここまでで、メッセージを読み書きする箱の必要性がわかった(ような)気がするよ。
  • ナゾ箱に、手紙を入れておくわけにもいかないから、ナゾ箱=コンピュータであることは、おじさんでも想像が出来るよ。
  • おじさんのコンピュータをナゾ箱として設置するのは、ちょっと気が引けるよ。 ずっと電源入れておかなくちゃいけないし、侵入されたら、おじさんの恥ずかしいファイルが流出しそうで怖いよ・・・。
  • ナゾ箱について調べてみたけど、今回のようにメッセージを書き残したり、公開したり、何かしらの機能やサービスを提供するコンピュータのことを、世間では「サーバ」と呼ぶらしいよ。
    • 以降、機能やサービスを提供するコンピュータのことを「サーバ」と呼ぶことにするね。おじさん、横文字にもなれてきた気がするよ(照)。
  • 続いて、サーバをどうやって準備すれば良いのか考えることにするよ。

サーバを準備するにあたって懸念すること

  • ここまでの振返り
    • サーバと呼ばれるコンピュータの必要性がわかった気がするよ。たぶん。
    • おじさんのメッセージを書き込みしたり、誰かにメッセージを読み込ませたりする目的で、サーバを利用するよ。
  • おじさんがサーバを準備するにあたって懸念すること
    • サーバ=コンピュータってことは、サーバも様々な種類があるってことだよ。
      • おじさんのコンピュータはApple製だよ(自慢だよ)。
    • おじさんのような素人が、コンピュータを楽に・便利に活用するには、OS(オペレーティングシステム)が必要だよ。
    • コンピュータも製造するメーカが様々だけど、OSも種類が様々あるみたいだよ・・・。
      • おじさんが現在利用しているmacOS
      • Windows
      • Linuxなんて呼ばれるものもあるみたいだよ・・・
    • 所有するコンピュータや、これから調達するサーバによって、開発する方法や考慮しておくべきことが変化しそうだよ。
      • 素人のおじさんでも想像出来るけど、例えば、WindowsとmacOSでは、異なる点が色々ありそうな気がするよ。
        • 実際、Windows用のアプリを、そのままmacOSに持ってきても動作しないよね?
        • おじさんのQiitaでの目的は、おじさんが自習して、職を得ることだけど、おじさん以外の人もおじさんと同じことが出来るように記録を残したいんだよ。(よくばりすぎかな?)
          • 調達するサーバによって、プログラムの作り方や、考慮が変化するとなると、おじさん個人では対応しきれないよ・・・。
          • サーバやOSを1種類に限定するとしても、そもそもおじさんの持っているコンピュータはApple製で、OSはmacOSだよ。
          • おじさん以外の人が、おじさんと同じ作業を追体験するには、おじさんと同じコンピュータとmacOSが必要になるってことだよ。
          • コンピュータやOSの違いが問題になりそうだよ。

懸念点のまとめ

  • サーバの種類やOSによって、その上で動作するプログラムの作り方や考慮が変化するかもだよ。
    • おじさん以外の人でもおじさんの作業を追体験出来るようにしたいよ。
      • でも、おじさんのコンピュータはApple製で、OSはmacOSだよ。
        • おじさんのコンピュータと条件が異なるコンピュータを利用する場合は、おじさんが意図しない問題が出てくるかもしれないし、それを事前に検証することも出来ないよ。
        • Windowsは対象外になってしまうよ。
  • アプリ開発時に利用するおじさんのコンピュータと、不特定多数に公開するサーバと、おじさんの作業を追体験するおじさん以外の人のコンピュータのそれぞれを、可能な限り同じ条件に合わせたいんだよ。

開発環境(おじさん)、サーバ、おじさん以外の人のコンピュータの条件を可能な限り合わせるには

あとがき

  • コンピュータやOSの種類による影響を最小限にしたいので、仮想環境やコンテナについて、少しずつ調べていくよ。
  • サーバを準備して、複数の環境からメッセージを送受信出来るようにしたいと考えているよ。
  • 先は長そうだけど、いつか出来ると信じて取り組むとするよ。おじさん、無駄に時間だけはあるからね(涙)。
  • どこかの誰かが出来ていることは、おじさんにも出来ることだと、昔励まされたことがあったよ。それを今思い出したよ(涙)。

参照・メモ

サーバ
ハイパーバイザ
仮想機械
What is a Container?
Get Started, Part 1: Orientation and setup
Docker overview
Docker道場「Dockerの基本概念」0825インフラ勉強会資料
Docker ドキュメント日本語化プロジェクト
AWSでDockerを扱うためのベストプラクティス

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

AWS CodeBuildのコンテナから、RDSへ直接マイグレーションを実行する方法

概要

サービスを全部ECS + Fargateで行うサービスで、簡単かつCoolにマイグレーション実行する方法を記します。
具体的にはCodePipeline & CodeBuildを設定し、ビルド中にマイグレーションが走るように設定します。

この記事が役に立ちそうな人

  • ECS + Fargateでサービスを構築しようとしている人
  • サービスにはRDSを用いているが、マイグレーションをどこから行おうか悩んでいる人

背景

マイグレーションをいかにCoolに行うかはサーバーサイドの至上命題と言っても過言ではありません。(?)

前述の通りECS + Fargateでサービスを構築すると、FargateにはSSHできないのは周知の事実です。
よってコンテナに入れないため、フレームワークの機能を用いたマイグレーションを実行できません。
そして、マイグレーションは必ずソースコードのデプロイ前に実行される必要があります。

以上のことから、ソースコードのデプロイを行うCodePipelineのフローでマイグレーションを行うフェーズを追加し、マイグレーション含めたアプリケーションのデプロイを管理できたらCoolだなと思った次第です。

こちらの方法で実はその例が示されているのですが、タスク定義をjsonで書く必要があり、ステージング環境などの環境ごと対応が手間のように見受けられました。

もっと簡単でセキュアな方法はないかと考えていたところ、CodeBuildのドキュメントにCodeBuildが特定VPC, サブネットで実行できると書いていました。

RDSにはプライベートサブネットからしかアクセスできないようにしていましたが、これならCodeBuildインスタンスに立ち上げたコンテナから、マイグレーションを直接実行できるのでは? と思いついたわけです。

実際に

プライベートサブネット上に分離された Amazon RDS データベース内のデータに対して、ビルドから統合テストを実行する。

とあるように、RDSへのアクセスが想定されているようです。

目的

  • CodePipelineのフェーズ中にmigrationフェーズを追加し、ソースコードのデプロイに加えてマイグレーションも制御したい
  • ECSタスク定義を書きたくない
  • もちろんセキュリティは維持する
  • フレームワークのマイグレーション機能を使いたい 1

インフラ構成の説明

CBMirationDesc.png

これが全貌ではありませんが、プライベートサブネットについてはこの構成です。AZの冗長化は端折っています。
ECSのセキュリティグループとは別のSGを用意し、CodeBuildにはそれを設定しておきます。

セキュリティグループ3にはセキュリティグループ1のインバウンドトラフィックを許可しておきます。
セキュリティグループ1には特に設定は不要です。

具体的な設定

CodeBuild

vpcsettings.png

上記画像のようなVPCに関する設定があります。
ここにインフラ構成で記した通りの設定をしておきます。

あと環境変数AWS_ENVIRONMENTを設定しておきます。
これでどの環境のデータベースへ接続するかを、laravelのenvファイルの読み込みを切り替えることで選択できるようになります。
つまり後述のbuildspecは全環境で共通利用できます。

加えて使用するbuildspecは後述のbuildspec-migration.ymlを指定してください。

buildspec

実例を示します。

buildspec-migration.yml
version: 0.2
phases:
  install:
    runtime-versions:
      docker: 18
    commands:
      - echo Install started on `date`
      - cat /etc/issue
      - curl -L "https://github.com/docker/compose/releases/download/1.21.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
      - docker-compose --version
      - docker -v
      - pwd && ls -l
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - sed -e 's/AWS_ENVIRONMENT/$AWS_ENVIRONMENT/g' docker-compose-migration.yml > docker-compose.yml
      - docker-compose build
      - docker-compose up -d
      - docker ps
      - docker exec laravel-api bash /project/build.sh
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Start migrate
      - docker exec laravel-api php project/artisan migrate

私はdocker-composeを用いてコンテナを立ち上げています。2

sed -e 's/AWS_ENVIRONMENT/$AWS_ENVIRONMENT/g' docker-compose-migration.yml
これで環境ごとにdocker-compose.ymlを生成します。これでこのymlは全環境で使いまわせます。

docker exec laravel-api bash /project/build.sh
ここではcomposer installを行うだけのシェルを実行しています。でないとmigrateが動かないので。

docker exec laravel-api php project/artisan migrate
ここでついにmigrationを実行しています。

docker-compose

私はdocker-composeを用いているので、その設定も例を示します。

docker-compose-migration.yml
version: '3.3'

services:
  # web - api
  api:
    build: ./alpine-apache-php7
    container_name: laravel-api
    environment:
      - APP_ENV=AWS_ENVIRONMENT
    volumes:
      - "./project:/project"
    ports:
      - "18080:80"

APP_ENVには設定すればdevelopmentなどの任意のテキストを入れられるので、読み込む環境設定ファイルの制御ができます。

CodePipeline

パイプラインの例を示します。

cp.png

上記のようにECRへのビルドフェーズと、マイグレーション含むデプロイフェーズは分けています。
こうすることでマイグレーションとデプロイが自動的に実行されないようになります。

参考文献

LaravelをElastic BeanstalkからFargateに移行しました
https://qiita.com/hareku/items/73f7730c1adc01bbe5a0

AWS公式CodeBuildのVPCサポートについてのドキュメント
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/vpc-support.html


  1. フレームワークはLaravelを使っています。多分他のフレームワーク、言語でも同じことができるはずです。 

  2. どこかのタイミングからCodeBuildでdocker-composeを動かすとエラーを吐くようになりました。どうやら標準イメージを使うようになってから追加の設定が必要になったようです。このように別のソフトをインストールするときは、CodeBuildの環境設定においてEnable this flag if you want to build Docker images or want your builds to get elevated privileges.にチェックを入れないとエラーとなります。注意してください。 

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

WordPress on Dockerを下層ディレクトリで動かす

背景

  • 色々やり方がでてきますがやりたいことが微妙に違うので備忘録として。
  • WordPressをAPIとして使いたいが、APIサーバーは分けたくない。
  • 下層ディレクトリwpにDockerを使用してWordPressをインストールしたいときに環境構築に若干躓いた。

環境

  • macOS Mojave
  • Docker for Mac

やりたいこと

  • 公式のWordPressイメージだとドキュメント直下にしかWordPressを置けない
  • そのためdocker-compose.ymlとdocker-entrypoint.shを編集する

docker-compose.ymlの構成

version: "3.3"
services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
  wordpress:
    depends_on:
      - db
    volumes:
      - ./:/var/www/html
      - ./docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh
    image: wordpress:latest
    working_dir: /var/www/html
    ports:
      - 80:80
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_SUBDIRECTORY: wp
  node: # 使わないなら不要
    image: node:8.15.1-alpine #Nodeは各々のVersionで
    user: node
    working_dir: /home/node/app
    volumes:
      - ./:/home/node/app #package.jsonがあるディレクトリをmount
volumes:
  db_data: {}

docker-entrypoint.sh

公式のdocker-entrypoint.shを元に編集

#!/bin/bash
set -euo pipefail

# usage: file_env VAR [DEFAULT]
#    ie: file_env 'XYZ_DB_PASSWORD' 'example'
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
#  "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
file_env() {
    local var="$1"
    local fileVar="${var}_FILE"
    local def="${2:-}"
    if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
        echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
        exit 1
    fi
    local val="$def"
    if [ "${!var:-}" ]; then
        val="${!var}"
    elif [ "${!fileVar:-}" ]; then
        val="$(< "${!fileVar}")"
    fi
    export "$var"="$val"
    unset "$fileVar"
}

if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then
+追加 if ! [ -z "$WORDPRESS_SUBDIRECTORY" ]; then
+追加         # force relative path
+追加         WORDPRESS_SUBDIRECTORY=`echo $WORDPRESS_SUBDIRECTORY | sed 's/^\///g'`
+追加         mkdir -p $WORDPRESS_SUBDIRECTORY
+追加         cd $WORDPRESS_SUBDIRECTORY
+追加 fi
    if [ "$(id -u)" = '0' ]; then
        case "$1" in
            apache2*)
                user="${APACHE_RUN_USER:-www-data}"
                group="${APACHE_RUN_GROUP:-www-data}"

                # strip off any '#' symbol ('#1000' is valid syntax for Apache)
                pound='#'
                user="${user#$pound}"
                group="${group#$pound}"
                ;;
            *) # php-fpm
                user='www-data'
                group='www-data'
                ;;
        esac
    else
        user="$(id -u)"
        group="$(id -g)"
    fi

    if [ ! -e index.php ] && [ ! -e wp-includes/version.php ]; then
        # if the directory exists and WordPress doesn't appear to be installed AND the permissions of it are root:root, let's chown it (likely a Docker-created directory)
        if [ "$(id -u)" = '0' ] && [ "$(stat -c '%u:%g' .)" = '0:0' ]; then
            chown "$user:$group" .
        fi

        echo >&2 "WordPress not found in $PWD - copying now..."
        if [ -n "$(ls -A)" ]; then
            echo >&2 "WARNING: $PWD is not empty! (copying anyhow)"
        fi
        sourceTarArgs=(
            --create
            --file -
            --directory /usr/src/wordpress
            --owner "$user" --group "$group"
        )
        targetTarArgs=(
            --extract
            --file -
        )
        if [ "$user" != '0' ]; then
            # avoid "tar: .: Cannot utime: Operation not permitted" and "tar: .: Cannot change mode to rwxr-xr-x: Operation not permitted"
            targetTarArgs+=( --no-overwrite-dir )
        fi
        tar "${sourceTarArgs[@]}" . | tar "${targetTarArgs[@]}"
        echo >&2 "Complete! WordPress has been successfully copied to $PWD"
        if [ ! -e .htaccess ]; then
            # NOTE: The "Indexes" option is disabled in the php:apache base image
            cat > .htaccess <<-'EOF'
                # BEGIN WordPress
                <IfModule mod_rewrite.c>
                RewriteEngine On
                RewriteBase /
                RewriteRule ^index\.php$ - [L]
                RewriteCond %{REQUEST_FILENAME} !-f
                RewriteCond %{REQUEST_FILENAME} !-d
                RewriteRule . /index.php [L]
                </IfModule>
                # END WordPress
            EOF
            chown "$user:$group" .htaccess
        fi
    fi

    # allow any of these "Authentication Unique Keys and Salts." to be specified via
    # environment variables with a "WORDPRESS_" prefix (ie, "WORDPRESS_AUTH_KEY")
    uniqueEnvs=(
        AUTH_KEY
        SECURE_AUTH_KEY
        LOGGED_IN_KEY
        NONCE_KEY
        AUTH_SALT
        SECURE_AUTH_SALT
        LOGGED_IN_SALT
        NONCE_SALT
    )
    envs=(
        WORDPRESS_DB_HOST
        WORDPRESS_DB_USER
        WORDPRESS_DB_PASSWORD
        WORDPRESS_DB_NAME
        WORDPRESS_DB_CHARSET
        WORDPRESS_DB_COLLATE
        "${uniqueEnvs[@]/#/WORDPRESS_}"
        WORDPRESS_TABLE_PREFIX
        WORDPRESS_DEBUG
        WORDPRESS_CONFIG_EXTRA
    )
    haveConfig=
    for e in "${envs[@]}"; do
        file_env "$e"
        if [ -z "$haveConfig" ] && [ -n "${!e}" ]; then
            haveConfig=1
        fi
    done

    # linking backwards-compatibility
    if [ -n "${!MYSQL_ENV_MYSQL_*}" ]; then
        haveConfig=1
        # host defaults to "mysql" below if unspecified
        : "${WORDPRESS_DB_USER:=${MYSQL_ENV_MYSQL_USER:-root}}"
        if [ "$WORDPRESS_DB_USER" = 'root' ]; then
            : "${WORDPRESS_DB_PASSWORD:=${MYSQL_ENV_MYSQL_ROOT_PASSWORD:-}}"
        else
            : "${WORDPRESS_DB_PASSWORD:=${MYSQL_ENV_MYSQL_PASSWORD:-}}"
        fi
        : "${WORDPRESS_DB_NAME:=${MYSQL_ENV_MYSQL_DATABASE:-}}"
    fi

    # only touch "wp-config.php" if we have environment-supplied configuration values
    if [ "$haveConfig" ]; then
        : "${WORDPRESS_DB_HOST:=mysql}"
        : "${WORDPRESS_DB_USER:=root}"
        : "${WORDPRESS_DB_PASSWORD:=}"
        : "${WORDPRESS_DB_NAME:=wordpress}"
        : "${WORDPRESS_DB_CHARSET:=utf8}"
        : "${WORDPRESS_DB_COLLATE:=}"

        # version 4.4.1 decided to switch to windows line endings, that breaks our seds and awks
        # https://github.com/docker-library/wordpress/issues/116
        # https://github.com/WordPress/WordPress/commit/1acedc542fba2482bab88ec70d4bea4b997a92e4
        sed -ri -e 's/\r$//' wp-config*

        if [ ! -e wp-config.php ]; then
            awk '
                /^\/\*.*stop editing.*\*\/$/ && c == 0 {
                    c = 1
                    system("cat")
                    if (ENVIRON["WORDPRESS_CONFIG_EXTRA"]) {
                        print "// WORDPRESS_CONFIG_EXTRA"
                        print ENVIRON["WORDPRESS_CONFIG_EXTRA"] "\n"
                    }
                }
                { print }
            ' wp-config-sample.php > wp-config.php <<'EOPHP'
// If we're behind a proxy server and using HTTPS, we need to alert Wordpress of that fact
// see also http://codex.wordpress.org/Administration_Over_SSL#Using_a_Reverse_Proxy
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    $_SERVER['HTTPS'] = 'on';
}

EOPHP
            chown "$user:$group" wp-config.php
        elif [ -e wp-config.php ] && [ -n "$WORDPRESS_CONFIG_EXTRA" ] && [[ "$(< wp-config.php)" != *"$WORDPRESS_CONFIG_EXTRA"* ]]; then
            # (if the config file already contains the requested PHP code, don't print a warning)
            echo >&2
            echo >&2 'WARNING: environment variable "WORDPRESS_CONFIG_EXTRA" is set, but "wp-config.php" already exists'
            echo >&2 '  The contents of this variable will _not_ be inserted into the existing "wp-config.php" file.'
            echo >&2 '  (see https://github.com/docker-library/wordpress/issues/333 for more details)'
            echo >&2
        fi

        # see http://stackoverflow.com/a/2705678/433558
        sed_escape_lhs() {
            echo "$@" | sed -e 's/[]\/$*.^|[]/\\&/g'
        }
        sed_escape_rhs() {
            echo "$@" | sed -e 's/[\/&]/\\&/g'
        }
        php_escape() {
            local escaped="$(php -r 'var_export(('"$2"') $argv[1]);' -- "$1")"
            if [ "$2" = 'string' ] && [ "${escaped:0:1}" = "'" ]; then
                escaped="${escaped//$'\n'/"' + \"\\n\" + '"}"
            fi
            echo "$escaped"
        }
        set_config() {
            key="$1"
            value="$2"
            var_type="${3:-string}"
            start="(['\"])$(sed_escape_lhs "$key")\2\s*,"
            end="\);"
            if [ "${key:0:1}" = '$' ]; then
                start="^(\s*)$(sed_escape_lhs "$key")\s*="
                end=";"
            fi
            sed -ri -e "s/($start\s*).*($end)$/\1$(sed_escape_rhs "$(php_escape "$value" "$var_type")")\3/" wp-config.php
        }

        set_config 'DB_HOST' "$WORDPRESS_DB_HOST"
        set_config 'DB_USER' "$WORDPRESS_DB_USER"
        set_config 'DB_PASSWORD' "$WORDPRESS_DB_PASSWORD"
        set_config 'DB_NAME' "$WORDPRESS_DB_NAME"
        set_config 'DB_CHARSET' "$WORDPRESS_DB_CHARSET"
        set_config 'DB_COLLATE' "$WORDPRESS_DB_COLLATE"

        for unique in "${uniqueEnvs[@]}"; do
            uniqVar="WORDPRESS_$unique"
            if [ -n "${!uniqVar}" ]; then
                set_config "$unique" "${!uniqVar}"
            else
                # if not specified, let's generate a random value
                currentVal="$(sed -rn -e "s/define\(\s*(([\'\"])$unique\2\s*,\s*)(['\"])(.*)\3\s*\);/\4/p" wp-config.php)"
                if [ "$currentVal" = 'put your unique phrase here' ]; then
                    set_config "$unique" "$(head -c1m /dev/urandom | sha1sum | cut -d' ' -f1)"
                fi
            fi
        done

        if [ "$WORDPRESS_TABLE_PREFIX" ]; then
            set_config '$table_prefix' "$WORDPRESS_TABLE_PREFIX"
        fi

        if [ "$WORDPRESS_DEBUG" ]; then
            set_config 'WP_DEBUG' 1 boolean
        fi

        if ! TERM=dumb php -- <<'EOPHP'
<?php
// database might not exist, so let's try creating it (just to be safe)

$stderr = fopen('php://stderr', 'w');

// https://codex.wordpress.org/Editing_wp-config.php#MySQL_Alternate_Port
//   "hostname:port"
// https://codex.wordpress.org/Editing_wp-config.php#MySQL_Sockets_or_Pipes
//   "hostname:unix-socket-path"
list($host, $socket) = explode(':', getenv('WORDPRESS_DB_HOST'), 2);
$port = 0;
if (is_numeric($socket)) {
    $port = (int) $socket;
    $socket = null;
}
$user = getenv('WORDPRESS_DB_USER');
$pass = getenv('WORDPRESS_DB_PASSWORD');
$dbName = getenv('WORDPRESS_DB_NAME');

$maxTries = 10;
do {
    $mysql = new mysqli($host, $user, $pass, '', $port, $socket);
    if ($mysql->connect_error) {
        fwrite($stderr, "\n" . 'MySQL Connection Error: (' . $mysql->connect_errno . ') ' . $mysql->connect_error . "\n");
        --$maxTries;
        if ($maxTries <= 0) {
            exit(1);
        }
        sleep(3);
    }
} while ($mysql->connect_error);

if (!$mysql->query('CREATE DATABASE IF NOT EXISTS `' . $mysql->real_escape_string($dbName) . '`')) {
    fwrite($stderr, "\n" . 'MySQL "CREATE DATABASE" Error: ' . $mysql->error . "\n");
    $mysql->close();
    exit(1);
}

$mysql->close();
EOPHP
        then
            echo >&2
            echo >&2 "WARNING: unable to establish a database connection to '$WORDPRESS_DB_HOST'"
            echo >&2 '  continuing anyways (which might have unexpected results)'
            echo >&2
        fi
    fi

    # now that we're definitely done writing configuration, let's clear out the relevant envrionment variables (so that stray "phpinfo()" calls don't leak secrets from our code)
    for e in "${envs[@]}"; do
        unset "$e"
    done
fi

exec "$@"

操作

docker-compose up -d

を実行したあと、localhost/wpにアクセスでWordPressインストール画面が出ます。

エラーが出る場合は

chmod +x docker-entrypoint.sh 

を実行する。

Nodeを使いたいときは

docker-compose run node npm install

等のコマンドを叩いてください。

参考

Dockerの公式WordPressイメージを使い、下層ディレクトリにある既存のWordPressを動かす - Docker入門を大いに参考にさせていただきました(というかこちらの方が遥かに良記事です)。

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

Dockerで"A server is already running"でコンテナから抜けてしまうが、server.pidが見つからない場合

環境

macOS High Sierra 10.13.6
Doker Version: 18.09.2
rails 5.2.1

問題

docker-compose upでコンテナを立ち上げたところ、途中までうまくいくが、最後以下のようになりコンテナを抜けてしまう。
しかしtmp/pids/server.pidが見つからない。

=> Booting Puma
=> Rails 5.2.1 application starting in development
=> Run `rails server -h` for more startup options
A server is already running. Check /[rootディレクトリ]/tmp/pids/server.pid.
[rootディレクトリ]_1  | Exiting

解決法

Ctrl-cのあとdocker-compose stopでコンテナを停止させた後、もう一度doker-compose upで立ち上げる。
立ち上げ〜コンテナから抜けてしまう間に、別ウィンドウで素早く以下コマンドを入力。

$ docker-compose exec [rootディレクトリ] /bin/bash
~@~:/[rootディレクトリ]# rm tmp/pids/server.pid

1行目でコンテナ内のbashに入り、2行目でファイルを削除。

考察

自分の場合、ローカルにはtmp/pids/server.pidが見つからなかったが、コンテナ内には存在していた。
コンテナを停止している間はコンテナ内のbashに入れなかったのでこうしたアホなやり方になってしまった。
しかしこれで立ち上げることはできました。

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

Python・Redis・Nginxを使って始めるDocker Tutorial

Docker Tutorial — Getting Started with Python, Redis, and Nginx.という海外の記事が凄くdocker初学者向けで、人気だったので分かりやすく翻訳してみました。

はじめに

これはDockerコンテナに関する入門チュートリアルです。この記事を読み終わると、ローカルマシン上でDockerを使う方法を知る事が出来ます。
この記事ではPython、Redis、Nginxコンテナを実行します。
今回、この記事の読者は上記の3つの技術の基本概念に精通していると仮定しています。
シェルを使った例を沢山使うので、ターミナルを用意して読み進めて下さい!

Dockerとは何か

Dockerとは、ソフトウェアコンテナ内のアプリケーションの開発を自動化する為に作られたオープンソースツールです。
このDockerの概念を理解する一番簡単な方法は、現実世界の運送コンテナと比較することでしょう。
ここでは、あくまで一例として読んでください。

昔、運送会社は次のような問題に直面していました。
・どうやって、異なるタイプの品物(食べ物、化学薬品、ガラス製品、レンガなど)を並べて輸送すればいいのか
・同じ車両を使って、どうやって異なるサイズの荷物を運べばいいのか

しかし、コンテナを導入した後にこれらの問題を解決する事が出来ました。
レンガはガラスの上に置き、化学製品は食べ物の隣に置き、異なるサイズの荷物はあらかじめ規定されたコンテナに入れ、同じ車両を使って荷物を下ろしたり積んだりする事が出来るようになりました。

理解出来たでしょうか?それでは、ソフトウェア開発におけるコンテナに話を戻します。

アプリケーションを開発する際に、ライブラリやWebサーバー、データベースなどの関係を意識してコードを書くと思いますが、実行環境がそれぞれのコンピュータで異なる時にアプリケーションが作動しなくなる経験をした事があるかもしれません。

しかし、この問題はアプリケーションとシステムを分離させることにより解決する事が出来ます。

普通の仮想化とは何が違うのか?

従来は、仮想マシンを使って上記の問題を回避していました。しかし、この仮想マシンの問題は、ホストOS上にある“余分なOS”が数GBの容量を使ってしまう事でした。ほとんどの場合、サーバーはさらに多くのスペースを占有する仮想マシンをホストすることになってしまいます(余談ですが今現在、大半のクラウドサーバーサービスはその余分なスペースの分を請求しています)
また、もう一つの重大な欠点は起動が遅い事です。

DockerはホストOSの別々のプロセスとして、OSのカーネルを全てのコンテナをまたいで共有することにより、上記のような問題を全て解決してくれます。

image.png

Dockerが唯一のコンテナ化プラットフォームではないですが、現時点では市場で最も大きく、ユーザー数も多いです。

なぜDockerが必要なのか

下記にメリットをまとめておきました。

・開発プロセスの迅速にする事ができる
・アプリケーションを手軽にまとめられる
・local/dev/staging/production環境で同じ動作が可能になる
・簡単で分かりやすい監視が出来る
・拡張が簡単になる

開発プロセスの迅速にする事ができる

PostgreSQL, Redis, Elasticsearchのような外部のアプリをシステム上にインストールする必要はありません。コンテナを起動させるだけで実行する事が出来ます!

Dockerを使うことによって、同じアプリの異なるバージョンを同時に実行する事も出来ます。
例えば、PostgreSQLの古いバージョンを新しいバージョンに手動で移行させる必要があるとします。最新の外部のアプリを使った新しいマイクロサービスを作りたい時に、マイクロサービスの設計でこのような状況になることは頻繁にあります。この時、1つのホストOS上で同じアプリの異なるバージョンを同時に実行するのは非常に困難です。

しかし、Dockerならこの問題を完璧に解決することが出来ます。Dockerを使えばサービスと外部のアプリをそれぞれ分離した独自の環境を構築出来るからです。

アプリケーションを手軽にまとめられる

Dockerを使えば、アプリケーションを1つのカタマリとして運用することが出来ます。

大半のプログラミング言語、フレームワーク、オペレーションシステムはそれぞれパッケージマネージャを持っています。たとえもしそれぞれのパッケージマネージャでアプリをまとめたとしても、他のシステムでそのアプリを使うのは難しいです。

しかし、Dockerなら異なるホストやクラウドサービスの垣根を越えて、あるフォーマットに統一されたイメージを作ることによって他のシステムでもアプリを使う事が出来ます。

local/dev/staging/production環境で同じ動作が可能になる

どんな技術にもヒューマンファクタがあるため、Dockerはlocal/dev/staging/productionが全て同じ環境であることを保証することは出来ません。
しかし、OSやシステムの依存関係によって起こるエラーの確率をほとんどゼロに減らしてくれます。

正しい方法でDocker imageをビルドする事が出来れば、同じOSやシステム上の依存関係を持ったアプリを使う事が出来ます!

*ヒューマンファクタ・・・人間や組織・機械・設備等で構成されるシステムが、安全かつ経済的に動作・運用できるために考慮しなければならない人間側の要因のこと。一言でいえば「人的要因」である。

簡単で分かりやすい監視が出来る

起動しているコンテナのログファイルを読むための統一された方法がDockerにはあります。
どこにログファイルを保存しておくかも覚える必要はなく、外部のログドライバーとモニターを1つの場所にまとめておく事が出来ます。

拡張が簡単になる

設計上Dockerは環境変数の設定、TCP/UDPポートを通した通信などの主要な原則に沿って使う事が出来ます。
正しくアプリを完成させればDockerの中だけではなく、色々な所にスケールを広げる事が簡単に出来ます。

対応プラットフォーム

DockerのネイティブプラットフォームはLinuxです。これはLinuxカーネルに基づいてDockerが作られているからです。しかし、macOSやWindows上で使う事が出来ます。
Linuxとの唯一の違いはmacOSとWindows上のDockerは小さな仮想マシンにカプセル化されている事ですが、現在では、macOSとWindows上のDockerは本来のDockerと同じくらい使いやすくなっています。

インストール

Dockerのインストール方法はここから確認する事が出来ます。

(もしLinuxでDockerを実行しているなら、以下のすべてのコマンドをrootとして実行するか、ユーザーをdockerグループに追加して再ログインする必要があります。)

sudo usermod -aG docker $(whoami)`

用語

コンテナ
必要なソフトウェアをまとめたインスタンスのことです。コンテナはイメージから作られ、他のコンテナや外部と通信するためにポートとボリュームを公開することが出来ます。また、コンテナは簡単に短時間でキルしたり削除したり再構築したりすることが出来ます。
そして、コンテナは状態を保持しません。

イメージ
コンテナの元となる要素のことです。イメージによって構築する時に時間がかかることがあります。一方、コンテナはイメージからすぐに構築する事が出来ます。

ポート
元々の意味ではTCP / UDPポートという意味です。ですが、もっと分かりやすく理解するために、他のコンテナやホストOSから接続可能な外部のネットワークと通信する時に必要なものと考えてください。

ボリューム
Dockerにおける共有フォルダのことです。コンテナを作ると初期化されます。ボリュームはデータを保持しておくために設計されており、コンテナの動作には影響されません。

レジストリ
イメージを保存しているサーバーのことです。Githubと比較すると分かりやすいかもしれません。
ローカルでイメージを使う時にレジストリからイメージをpullしたり、ローカルで作ったイメージをレジストリにpushする事が出来ます。

DockerHub
Docker Inc.が提供するWebインターフェイスを備えたレジストリのことです。様々なソフトウェアを含むDockerイメージを多数保存しています。
Docker Hubは、Dockerチーム、またはオリジナルのソフトウェア製造者と共同で作成された「公式の」Dockerイメージのソースです。
公式のイメージはそれらの潜在的な脆弱性をリストにまとめてあります。このリストはDockerHubにログインしているすべてのユーザーが閲覧する事が出来ます。
無料と有料の両方のアカウントがあります。
パブリックイメージは無制限に持つことができますが、プライベートイメージは1つのアカウントに1つまでです。
Docker Hubと非常によく似たサービスに、Docker Storeもあります。評価、レビューなどが掲載されています。

Example 1: hello world

それでは、最初のコンテナを動かしてみましょう!

docker run ubuntu /bin/echo 'Hello world'
コンソールの出力結果
Unable to find image 'ubuntu:latest' locally  
latest: Pulling from library/ubuntu  
6b98dfc16071: Pull complete  
4001a1209541: Pull complete  
6319fc68c576: Pull complete  
b24603670dc3: Pull complete  
97f170c87c6f: Pull complete  
Digest:sha256:5f4bdc3467537cbbe563e80db2c3ec95d548a9145d64453b06939c4592d67b6d  
Status: Downloaded newer image for ubuntu:latest  
Hello world

・docker runはコンテナを実行するためのコマンドです。

・ubuntuは今実行しているイメージです。たとえば、UbuntuOSのイメージを使う時に、イメージを指定すると、Dockerは最初にDockerホスト上のイメージを探します。イメージがローカルに存在しない場合、そのイメージはパブリックイメージレジストリ=Docker Hubから取得してきます。

/bin/echo 'Hello world'は作ったコンテナの中で実行されるコマンドです。このコンテナは単に「Hello world」と表示して実行を停止します。

それでは、Dockerコンテナ内でシェルを作ってみましょう!

docker run -i -t --rm ubuntu /bin/bash

-tは、作成するコンテナ内に疑似的なターミナルを割り当てる設定をしてます

-iは、コンテナーの標準入力(STDIN)を取得し、双方向の接続をする設定をしてます。

-rmは、プロセスが終了する時にコンテナを削除する設定をしてます。デフォルトだと、コンテナは削除されません。このrmの設定によって作られたコンテナは、シェルのセッションを維持する間ずっと残りますが、リモートサーバーとのssh通信などのようにセッションが無くなると削除されます。

セッション終了後もコンテナを実行し続けたい場合は、デーモン化する必要があります。

docker run --name daemon -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

--name daemonは「daemon」という名前でコンテナに割り当てる設定をしてます。名前を明示的に指定しない場合、Dockerは自動的に名前を生成し割り当てます。

-dはバックグラウンドでコンテナを実行させる設定をしてます。(つまり、デーモン化です)

それでは今どんなコンテナを持っているのか見てみましょう。

docker ps -a
コンソールの出力結果
CONTAINER ID  IMAGE   COMMAND                 CREATED             STATUS                         PORTS  NAMES  
1fc8cee64ec2  ubuntu  "/bin/sh -c 'while..."  32 seconds ago      Up 30 seconds                         daemon  
c006f1a02edf  ubuntu  "/bin/echo 'Hello ..."  About a minute ago  Exited (0) About a minute ago         gifted_nobel

docker psはコンテナをリスト表示するためのコマンドです。

-aは全てのコンテナは表示させるためのコマンドです。(docker psだけだと起動中のコンテナしかリスト表示されません)

docker psをしてみると、現在2つのコンテナを持っていることが分かります。

*gifted_nobel は、「Hello,World」と出力させた、最初に作ったコンテナです。 (このコンテナの名前は自動的に生成されます。ご自身のコンピュータによって名前は異なります。)

*daemon は、デーモンとして作った3番目のコンテナです。

注:2番目のコンテナ(対話型シェルを持つコンテナ)はありません。何故なら、-rmオプションを設定しているからです。そのため、2番目のコンテナは実行された時に自動的に削除されています。

それでは、ログを見てデーモンコンテナが何をしているのか見てみましょう!

docker logs -f daemon
コンソールの出力結果
...
hello world  
hello world  
hello world

docker logを使うとdockerのログを取得することが出来ます。
-fを使うとログの出力結果を追跡することが出来ます。

では、デーモンコンテナを停止してみましょう!

docker stop daemon

停止しているかどうか確かめて見てください。

docker ps -a
コンソールの出力結果
CONTAINER ID  IMAGE   COMMAND                 CREATED        STATUS                      PORTS  NAMES  
1fc8cee64ec2  ubuntu  "/bin/sh -c 'while..."  5 minutes ago  Exited (137) 5 seconds ago         daemon  
c006f1a02edf  ubuntu  "/bin/echo 'Hello ..."  6 minutes ago  Exited (0) 6 minutes ago           gifted_nobel

コンテナが停止していますね。もう一度、起動させてみましょう。

docker start daemon

起動しているか確かめてみてください。

docker ps -a
コンソールの出力結果
CONTAINER ID  IMAGE   COMMAND                 CREATED        STATUS                    PORTS  NAMES  
1fc8cee64ec2  ubuntu  "/bin/sh -c 'while..."  5 minutes ago  Up 3 seconds                     daemon  
c006f1a02edf  ubuntu  "/bin/echo 'Hello ..."  6 minutes ago  Exited (0) 7 minutes ago         gifted_nobel

起動出来ていますね。では、今度はもう一度停止して、手動で消してみましょう!

docker stop daemon  
docker rm <your first container name>  
docker rm daemon

全てのコンテナを消したい場合は、次のコマンドを使用します。

docker rm -f $(docker ps -aq)

docker rm はコンテナを削除するためのコマンドです。
-f $() は実行中のコンテナを停止させてます(つまり、強制的に消すという事です)。
-q(ps用) はコンテナIDのみを表示させるためのコマンドです。

Example 2: 環境変数とボリューム

この例を始める前に、私のGithubレポジトリ から新たなファイルを追加してください。

私のレポジトリをクローンするか、このリンクをクリックしてファイルをダウンロードしてください。

それでは、ここからはNginxのような、もっと難易度の高いコンテナを作っていきましょう!

examples/nginxにカレントディレクトリを切り替えて下さい!

docker run -d --name "test-nginx" -p 8080:80 -v $(pwd):/usr/share/nginx/html:ro nginx:latest

-p は、HOST PORT(8080)をCONTAINER PORT(80)としてマッピングしてます。

-v は、HOST DIRECTORY(pwd)をCONTAINER DIRECTORY(/usr/share/nginx/html:ro)にボリュームをマウントさせてます。

注:このコマンドは非常にコンピュータに負荷がかかりますが、環境変数とボリュームを説明するためにあえて分かりやすくしています。実際の開発では、こんな風に手動でDockerコンテナをスタートさせたりせずにオーケストレーションサービスを使うか、カスタムスクリプトを書いてスタートさせます。

コンソールの出力結果
Unable to find image 'nginx:latest' locally  
latest: Pulling from library/nginx  
683abbb4ea60: Pull complete  
a470862432e2: Pull complete  
977375e58a31: Pull complete  
Digest: sha256:a65beb8c90a08b22a9ff6a219c2f363e16c477b6d610da28fe9cba37c2c3a2ac  
Status: Downloaded newer image for nginx:latest  
afa095a8b81960241ee92ecb9aa689f78d201cff2469895674cec2c2acdcc61c

重要:runコマンドは絶対パスのみ受け付けます。この例では、カレントディレクトリの絶対パスとして$(pwd)を使ってます。

また、/example/nginx/index.html(コンテナの中に/usr/share/nginx/htmlディレクトリとしてマウントされてるファイル)を変更してページを更新することも出来ます。

それでは、test-nginxコンテナに関する情報を入手しましょう。

docker inspect test-nginx

このコマンドはDockerのインストールに関するシステム全体の情報を表示します。
この情報には、カーネルのバージョン、コンテナーとイメージの数、公開ポート、マウント済みボリュームなどが含まれます。

Example 3: 初めてのDockerfile

DockerイメージをビルドするためにはDockerfileを作る必要があります。Dockerfileとはコンテナなどの説明や実行させたい命令などを書いておくテキストファイルのことです。

今回の例で使う命令の説明はこんな感じです。

  • FROM:ベースとなるイメージを設定する
  • RUN:コンテナ内でコマンドを実行
  • ENV:環境変数を設定する
  • WORKDIR:作業ディレクトリを設定する
  • VOLUME:ボリュームのマウントする場所を設定する
  • CMD:コンテナを※実行可能コンテナとして設定する

※「実行可能コンテナ」とは、他のコンテナのベースイメージとして利用するのではなく、docker runで直接実行することを目的としたコンテナイメージのこと。

詳細はDockerfile referenceで見ることが出来ます。

では、URLを使ってWebサイトの中身を取得するイメージを作り、テキストファイルに保存してみましょう!

『SITE_URL』という環境変数を経由してWebサイトのURLをパスする必要があります。実行結果のファイルはボリュームとしてマウントされて、ディレクトリの中に配置されます。

Dockerfileという名前のファイルをexamples/curlディレクトリの中に配置して、次のような内容を書いてください。

Dockerfile
FROM ubuntu:latest  
RUN apt-get update \  
    && apt-get install --no-install-recommends --no-install-suggests -y curl \
    && rm -rf /var/lib/apt/lists/*
ENV SITE_URL http://example.com/  
WORKDIR /data  
VOLUME /data  
CMD sh -c "curl -Lk $SITE_URL > /data/results"

Dockerfileの準備が出来ました。それでは、実際にイメージをビルドしてみましょう!
examples/curlディレクトリに移動して、次のコマンドを実行してイメージをビルドしてください。

docker build . -t test-curl
コンソールの出力結果
Sending build context to Docker daemon  3.584kB  
Step 1/6 : FROM ubuntu:latest  
 ---> 113a43faa138
Step 2/6 : RUN apt-get update     && apt-get install --no-install-recommends --no-install-suggests -y curl     && rm -rf /var/lib/apt/lists/*  
 ---> Running in ccc047efe3c7
Get:1 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]  
Get:2 http://security.ubuntu.com/ubuntu bionic-security InRelease [83.2 kB]  
...
Removing intermediate container ccc047efe3c7  
 ---> 8d10d8dd4e2d
Step 3/6 : ENV SITE_URL http://example.com/  
 ---> Running in 7688364ef33f
Removing intermediate container 7688364ef33f  
 ---> c71f04bdf39d
Step 4/6 : WORKDIR /data  
Removing intermediate container 96b1b6817779  
 ---> 1ee38cca19a5
Step 5/6 : VOLUME /data  
 ---> Running in ce2c3f68dbbb
Removing intermediate container ce2c3f68dbbb  
 ---> f499e78756be
Step 6/6 : CMD sh -c "curl -Lk $SITE_URL > /data/results"  
 ---> Running in 834589c1ac03
Removing intermediate container 834589c1ac03  
 ---> 4b79e12b5c1d
Successfully built 4b79e12b5c1d  
Successfully tagged test-curl:latest
  • docker buildはローカルで新しいイメージをビルドしてます。
  • -tはnameタグをイメージに取り付けてます。

これで新しいイメージを持つことが出来ました。ここで、今現在持っているイメージを一覧で見てみましょう。

docker images
コンソールの出力結果
REPOSITORY  TAG     IMAGE ID      CREATED         SIZE  
test-curl   latest  5ebb2a65d771  37 minutes ago  180 MB  
nginx       latest  6b914bbcb89e  7 days ago      182 MB  
ubuntu      latest  0ef2e08ed3fa  8 days ago      130 MB

イメージからコンテナを作ったり、実行させる事が出来ます。デフォルトのパラメータを使ってやってみましょう。

docker run --rm -v $(pwd)/vol:/data/:rw test-curl

実行した結果がファイルに保存されたことを確認するには、次のコマンドを実行します。

cat ./vol/results

Facebook.comで試してみましょう。

docker run --rm -e SITE_URL=https://facebook.com/ -v $(pwd)/vol:/data/:rw test-curl

実行した結果がファイルに保存されたことを確認するには、次のコマンドを実行します。

cat ./vol/results

効率よくイメージを作るためのbest practice

  • 必要なコンテキストのみを含める
    .dockerignoreファイルを使用する (gitの.gitignoreのように)

  • 不要なパッケージをインストールしない :
    余分なディスク容量が消費されます。

  • キャッシュを使用する
    Dockerfileの最後に大きな変更を加えるコンテキスト(プロジェクトのソースコードなど)を追加します。Dockerキャッシュを効果的に利用します。

  • ボリュームに注意する
    ボリューム内のデータが何であるかを覚えておく必要があります。ボリュームは永続的であり、コンテナーと共存しないため、次のコンテナーは前のコンテナーによって作成されたボリュームのデータを使用します。

  • 環境変数を使用する(RUN、EXPOSE、VOLUMEなど):
    Dockerfileを変更しやすくなります。

Alpineイメージについて

多くのDockerイメージ(イメージのバージョン)がAlpine Linux上に作成されています。これは容量を分散化させてDockerイメージの全体サイズを縮小するためです。

Redis, Postgresなどのサードパーティサービスには、Alpineベースのイメージを使うことをオススメします。
Appイメージにはbuildpackベースのイメージを使用してください。コンテナ内でデバッグするのが簡単になりますし、システム全体に必要な要素が事前にインストールされてます。

どのベースイメージを使用するかを決めるだけではなく、このように1つの基本イメージを使用することによって最大の効果を得ることができます。これは、キャッシュがより効果的に使用されるからです。

Example 4:コンテナ同士を接続してみよう

Docker composeを使っていきます。
Docker composeとはコンテナ同士を接続するためのCLIです。
pip経由でインストールする事が出来ます。

sudo pip install docker-compose

今回の例では、PythonコンテナとRedisコンテナを接続してみたいと思います。

version: '3.6'  
services:  
  app:
    build:
      context: ./app
    depends_on:
      - redis
    environment:
      - REDIS_HOST=redis
    ports:
      - "5000:5000"
  redis:
    image: redis:3.2-alpine
    volumes:
      - redis_data:/data
volumes:  
  redis_data:

example/composeにカレントディレクトリを移動して、次のコマンドを実行してみてください。

docker-compose up
コンソールの結果
Building app  
Step 1/9 : FROM python:3.6.3  
3.6.3: Pulling from library/python  
f49cf87b52c1: Pull complete  
7b491c575b06: Pull complete  
b313b08bab3b: Pull complete  
51d6678c3f0e: Pull complete  
09f35bd58db2: Pull complete  
1bda3d37eead: Pull complete  
9f47966d4de2: Pull complete  
9fd775bfe531: Pull complete  
Digest: sha256:cdef88d8625cf50ca705b7abfe99e8eb33b889652a9389b017eb46a6d2f1aaf3  
Status: Downloaded newer image for python:3.6.3  
 ---> a8f7167de312
Step 2/9 : ENV BIND_PORT 5000  
 ---> Running in 3b6fe5ca226d
Removing intermediate container 3b6fe5ca226d  
 ---> 0b84340fa920
Step 3/9 : ENV REDIS_HOST localhost  
 ---> Running in a4f9a1d6f541
Removing intermediate container a4f9a1d6f541  
 ---> ebe63bf5959e
Step 4/9 : ENV REDIS_PORT 6379  
 ---> Running in fd06aa65fd33
Removing intermediate container fd06aa65fd33  
 ---> 2a581c31ff4f
Step 5/9 : COPY ./requirements.txt /requirements.txt  
 ---> 671093a12829
Step 6/9 : RUN pip install -r /requirements.txt  
 ---> Running in b8ea53bc6ba6
Collecting flask==1.0.2 (from -r /requirements.txt (line 1))  
  Downloading https://files.pythonhosted.org/packages/7f/e7/08578774ed4536d3242b14dacb4696386634607af824ea997202cd0edb4b/Flask-1.0.2-py2.py3-none-any.whl (91kB)
Collecting redis==2.10.6 (from -r /requirements.txt (line 2))  
  Downloading https://files.pythonhosted.org/packages/3b/f6/7a76333cf0b9251ecf49efff635015171843d9b977e4ffcf59f9c4428052/redis-2.10.6-py2.py3-none-any.whl (64kB)
Collecting click>=5.1 (from flask==1.0.2->-r /requirements.txt (line 1))  
  Downloading https://files.pythonhosted.org/packages/34/c1/8806f99713ddb993c5366c362b2f908f18269f8d792aff1abfd700775a77/click-6.7-py2.py3-none-any.whl (71kB)
Collecting Jinja2>=2.10 (from flask==1.0.2->-r /requirements.txt (line 1))  
  Downloading https://files.pythonhosted.org/packages/7f/ff/ae64bacdfc95f27a016a7bed8e8686763ba4d277a78ca76f32659220a731/Jinja2-2.10-py2.py3-none-any.whl (126kB)
Collecting itsdangerous>=0.24 (from flask==1.0.2->-r /requirements.txt (line 1))  
  Downloading https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz (46kB)
Collecting Werkzeug>=0.14 (from flask==1.0.2->-r /requirements.txt (line 1))  
  Downloading https://files.pythonhosted.org/packages/20/c4/12e3e56473e52375aa29c4764e70d1b8f3efa6682bef8d0aae04fe335243/Werkzeug-0.14.1-py2.py3-none-any.whl (322kB)
Collecting MarkupSafe>=0.23 (from Jinja2>=2.10->flask==1.0.2->-r /requirements.txt (line 1))  
  Downloading https://files.pythonhosted.org/packages/4d/de/32d741db316d8fdb7680822dd37001ef7a448255de9699ab4bfcbdf4172b/MarkupSafe-1.0.tar.gz
Building wheels for collected packages: itsdangerous, MarkupSafe  
  Running setup.py bdist_wheel for itsdangerous: started
  Running setup.py bdist_wheel for itsdangerous: finished with status 'done'
  Stored in directory: /root/.cache/pip/wheels/2c/4a/61/5599631c1554768c6290b08c02c72d7317910374ca602ff1e5
  Running setup.py bdist_wheel for MarkupSafe: started
  Running setup.py bdist_wheel for MarkupSafe: finished with status 'done'
  Stored in directory: /root/.cache/pip/wheels/33/56/20/ebe49a5c612fffe1c5a632146b16596f9e64676768661e4e46
Successfully built itsdangerous MarkupSafe  
Installing collected packages: click, MarkupSafe, Jinja2, itsdangerous, Werkzeug, flask, redis  
Successfully installed Jinja2-2.10 MarkupSafe-1.0 Werkzeug-0.14.1 click-6.7 flask-1.0.2 itsdangerous-0.24 redis-2.10.6  
You are using pip version 9.0.1, however version 10.0.1 is available.  
You should consider upgrading via the 'pip install --upgrade pip' command.  
Removing intermediate container b8ea53bc6ba6  
 ---> 3117d3927951
Step 7/9 : COPY ./app.py /app.py  
 ---> 84a82fa91773
Step 8/9 : EXPOSE $BIND_PORT  
 ---> Running in 8e259617b7b5
Removing intermediate container 8e259617b7b5  
 ---> 55f447f498dd
Step 9/9 : CMD [ "python", "/app.py" ]  
 ---> Running in 2ade293ecb25
Removing intermediate container 2ade293ecb25  
 ---> b85b4246e9f8
Successfully built b85b4246e9f8  
Successfully tagged compose_app:latest  
WARNING: Image for service app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.  
Creating compose_redis_1 ... done  
Creating compose_app_1   ... done  
Attaching to compose_redis_1, compose_app_1  
redis_1  | 1:C 08 Jul 18:12:21.851 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf  
redis_1  |                 _._  
redis_1  |            _.-``__ ''-._  
redis_1  |       _.-``    `.  `_.  ''-._           Redis 3.2.12 (00000000/0) 64 bit  
redis_1  |   .-`` .-```.  ```\/    _.,_ ''-._  
redis_1  |  (    '      ,       .-`  | `,    )     Running in standalone mode  
redis_1  |  |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379  
redis_1  |  |    `-._   `._    /     _.-'    |     PID: 1  
redis_1  |   `-._    `-._  `-./  _.-'    _.-'  
redis_1  |  |`-._`-._    `-.__.-'    _.-'_.-'|  
redis_1  |  |    `-._`-._        _.-'_.-'    |           http://redis.io  
redis_1  |   `-._    `-._`-.__.-'_.-'    _.-'  
redis_1  |  |`-._`-._    `-.__.-'    _.-'_.-'|  
redis_1  |  |    `-._`-._        _.-'_.-'    |  
redis_1  |   `-._    `-._`-.__.-'_.-'    _.-'  
redis_1  |       `-._    `-.__.-'    _.-'  
redis_1  |           `-._        _.-'  
redis_1  |               `-.__.-'  
redis_1  |  
redis_1  | 1:M 08 Jul 18:12:21.852 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.  
redis_1  | 1:M 08 Jul 18:12:21.852 # Server started, Redis version 3.2.12  
redis_1  | 1:M 08 Jul 18:12:21.852 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.  
redis_1  | 1:M 08 Jul 18:12:21.852 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.  
redis_1  | 1:M 08 Jul 18:12:21.852 * The server is now ready to accept connections on port 6379  
app_1    |  * Serving Flask app "app" (lazy loading)  
app_1    |  * Environment: production  
app_1    |    WARNING: Do not use the development server in a production environment.  
app_1    |    Use a production WSGI server instead.  
app_1    |  * Debug mode: on  
app_1    |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)  
app_1    |  * Restarting with stat  
app_1    |  * Debugger is active!  
app_1    |  * Debugger PIN: 170-528-240

このリンクをクリックしてブラウザで確認してみてください。

docker-composeの使い方は今回の趣旨とはズレるので、割愛させて頂きます。
最初はDocker Hubのイメージで試してみるのがいいでしょう。もし、自分のイメージを作りたい時は上記に乗せた「効率よくイメージを作るためのbest practice」を参照してみてください。
docker-composeを使うことに関していうと、docker-compose.ymlを書く時になるべく分かりやすい名前をボリュームにつけてください。
これをやるだけで、将来ボリュームを調べる時に様々な問題に苦しまずに済みます。

version: '3.6'  
services:  
  ...
  redis:
    image: redis:3.2-alpine
    volumes:
      - redis_data:/data
volumes:  
  redis_data:

この場合、redis_dataはdocker-compose.ymlファイル内で名付けられた名前となります。
最終的なボリューム名は、プロジェクト名を接頭辞につけたものとなります。

ボリュームを見てみましょう。

docker volume ls
コンソールの結果
DRIVER              VOLUME NAME  
local               apptest_redis_data

Docker Way

Dockerにはいくつかの制限や必要条件があり、それらはシステム(コンテンの中に入っているアプリ)の構成によって変化します。もちろん、これらの制限を無視したり、一時的に対処したりする事も出来ますが、Dockerの恩恵を100%受ける事が出来なくなってしまいます。
私は下記の条件に従うことを強くオススメします。

  • 1アプリケーション= 1コンテナ

  • プロセスをフォアグラウンドで実行する(systemd、upstart、その他の類似のツールは使用しないでください)。

  • データをコンテナに入れずに、ボリュームを使用してください。

  • SSHを使用しないでください(コンテナーの中に入る必要がある場合は、docker execコマンドを使用できます)。

  • コンテナの中で手動で設定しないで下さい

最後に

最後にまとめると、DockerはIDEやGitのように開発者にとって必須なツールとなりました。すぐに使えて、豊富なサービスを備えているサービスと言えます。

Dockerは、プロジェクトの規模や複雑さに関係なく、色々な場面で使用できます。はじめにcomposeとSwarmから始めて、プロジェクトが拡大したらAmazon Container ServicesやKubernetesなどのクラウドサービスに移行する事が出来ます。

貨物輸送で使用される実際のコンテナと同様に、コードをDockerコンテナに入れると、より迅速で効率的なCI / CDプロセスを構築するのに役立ちます。
これは、ギーク達によって促進されている単なる技術トレンドではありません。
DockerはPayPal、Visa、Swisscom、General Electric、Splinkなどの大企業で設計時にすでに使用されています!

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

Go+Postgres環境をDockerComposeで作ってみる

前回、Goの環境を作りました。
Webサーバーらしく、DBアクセスとかをする前に、DockerComposeで環境を整えようかと思います。

環境

  • ホストOS:Windows + VirtualBOX + Vagrant
  • ゲストOS:CentOS7+Docker

ディレクトリ構成

ゲストOSのディレクトリ構成は以下の通り

  • ~/docker/
    • docker-compose.yml
    • Dockerfile
    • main.go → 前回作成したもの

各種ファイル

docker-compose.yml
version: '2'
services:
  go-web:
    build: .
    ports:
      - "8080:8080"
    links:
      - go-db
    working_dir: /develop/go
    environment:
      - "DB_NAME=postgres"
      - "DB_USER=postgres"
      - "DB_PASSWORD=postgres"
    volumes:
      - "./:/develop/go:z"

  go-db:
    image: postgres:latest
    ports:
      - "5432:5432"
    environment:
      - "POSTGRES_USER=postgres"
      - "POSTGRES_PASSWORD=postgres"
    volumes:
      - "/data/db:/var/lib/postgresql/data:z"
FROM golang:latest

RUN go get github.com/gin-gonic/gin

EXPOSE 8080

CMD ["go", "run", "main.go"]

立ち上げてみる

  • 前回作成したmain.goを上記の2ファイルと同じ場所に格納
  • docker-compose upを実行

image.png
→立ち上がったので、ブラウザでアクセス

image.png
→アクセスできました

image.png
→サーバー側のログも出ています

最後に

今回、Postgresコンテナを立ち上げてはいるが、使っていないため今後はWebサーバーらしくDBアクセス等のコードも書いてみようと思う。

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

Prometheusで永続化ディレクトリがpermission deniedになる時の対処法

問題

Prometheus公式のDocekrイメージを使い、以下の設定でPrometheusをデプロイ。

docker-compose.yml
volumes:
  - /data/prometheus:/prometheus

デプロイ後にログを確認すると、

err="opening storage failed: lock DB directory: open /prometheus/lock: permission denied"

というエラーでコンテナが停止していた。

原因

Prometheus公式のDockerfile

Dockerfile
...
RUN mkdir -p /prometheus && \
    chown -R nobody:nogroup etc/prometheus /prometheus

USER       nobody
EXPOSE     9090
...

/data/prometheusのディレクトリ情報 (例)

$ ls -l /data
drwxrwxr-x  3 user1 users 4096  5月 21 00:00 prometheus/

Dckerfileを見るとUSER nobodyが設定されている。
そのためnobodyユーザでディレクトリに書き込みが行われるが、
マウントした/data/prometheusディレクトリの所有者がnobodyではなかったためエラーになってしまった。

ちなみに、Prometheusコンテナの/prometheusディレクトリのパーミッションは、
デフォルトで以下のようになっている。

$ ls -l /
drwxr-xr-x    3 nobody   nogroup       4096 May 21 00:00 prometheus

解決策

1. 実行ユーザをrootに変更する

rootユーザでPrometheusを実行する。
rootユーザで書き込みが行われるためエラーにならないが、
コンテナに必要以上の権限を持たせてしまうためおすすめしない。

docker-compose.ymlにuser: rootを追記

docker-compose.yml
version: "2"
services:
  prometheus:
    image: prom/prometheus:v2.9.2
    container_name: prometheus
    user: root #追記
    ports:
      - 9090:9090
    volumes:
      - /data/prometheus:/prometheus

2. その他ユーザにread, write権限を追加する

誰でも読み書きできるようになってしまうので注意が必要。

chmodでパーミッションを変更

$ sudo chmod 777 /data/prometheus

3. ディレクトリの所有者を変更する

chownで所有者を変更

$ sudo chown nobody /data/prometheus

合わせてディレクトリのグループを管理者のグループに設定しておく。

$ sudo chgrp monitor /data/prometheus

参考

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

SSH proxy コマンドの使い方について

ある機器へSSHアクセスする際、他のサーバ(jump server)を経由しないといけない場合は、SSHのproxyコマンドは有効である。簡単な環境でその使い方について説明する。

環境

  • OS X: Mojave
  • Docker desktop ver 2.0.0.3

環境構築

仮に以下のようなな環境ある。ユーザが target server へアクセスしたい。もしくは、targetサーバとscpでファイル交換したい。しかし、セキュリティ制限によって、ユーザから直接にtarget serverへアクセスするのは許可されていない。かならずproxy serverを経由する。一番シンプルなのは、順次にproxyサーバへログインしてから、targetサーバへログインして、作業さうる

毎回2段ログインすると大変である。より簡単な方法はSSHのproxyコンマンドを使うことである。簡単にSSH proxyコマンドを説明するために、以下のNWをdockerイメージを使い説明する。

+-------------+   +--------------+   +---------------+
|  ユーザ      +---+ proxy server +---+ target server |
+-------------+   +--------------+   +---------------+

Dockerイメージ

まず、簡単に環境をつくるため、sshサーバを動くdockerイメージを作成する。

以下のDockerfileを準備する。

FROM centos:centos7

# OpenSSH サーバをインストールする
RUN yum -y install openssh-server openssh-clients

# root でログインできるようにする
RUN sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config

# root のパスワードを 設定
RUN echo 'rootpassword!' | passwd --stdin root

# 使わないにしてもここに公開鍵を登録しておかないとログインできない
RUN ssh-keygen -t rsa -N "" -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -t ecdsa -N "" -f /etc/ssh/ssh_host_ecdsa_key
RUN ssh-keygen -t ed25519 -N '' -f /etc/ssh/ssh_host_ed25519_key

# sshd の使うポートを公開する
EXPOSE 22

# sshd を起動する
CMD ["/usr/sbin/sshd", "-D"]

必要に応じて rootpassword!を変更し、Dockerfileと同じフォルダーでイメージを作成する。

$ docker build -t centos7-sshd .

成功すると、以下のようなイメージが出来上がる。

bash-3.2# docker images centos7-sshd
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos7-sshd        latest              d9c6d7216802        22 hours ago        299MB

このイメージを使い、2つコンテナーを起動させる。なお、proxyサーバ を従来のport 22の代わりに、20022を待ち受けるようにした。

$ docker run -d  --hostname centos7-sshd-proxy  -p 20022:22 --name centos7-sshd-proxy centos7-sshd:latest
$ docker run -d  --hostname centos7-sshd-target --name centos7-sshd-target centos7-sshd:latest

ここで2つsshサーバのコンテナが動くようになる。次回、コンテナーを起動したいときは簡単にstartでできる。

$ docker start centos7-sshd-proxy centos7-sshd-target

2段ログイン

前節で作った2つのコンテナのSSH portは以下のようになる。

+-------------+            +--------------+        +---------------+
|  ユーザ      +----(20022)-+ proxy server +---(22)-+ target server |
+-------------+            +--------------+        +---------------+

ユーザがホストから127.0.0.2:20022へ proxyサーバへアクセスできる。

$ ssh -p 20022 root@127.0.0.1
Enter passphrase for key '/Users/<省略>/.ssh/id_rsa':
root@127.0.0.1's password:
X11 forwarding request failed on channel 0
Last login: Mon May 20 11:33:29 2019 from gateway

更に、そこからport 22でtargetサーバへSSHログインできる。ここで、proxyからtargetへアクセスする際、docker内部のNWを使う。そのIPは以下のように確認できる

$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' centos7-sshd-target
172.17.0.3

このIPを使い、targetへSSHをしてみる

[root@centos7-sshd-proxy ~]# ssh root@127.17.0.3
<省略>
root@127.17.0.3's password:
Last login: Mon May 20 11:40:34 2019 from gateway
[root@centos7-sshd-proxy ~]#

proxyサーバを経由してtargetへログインする環境を構築して、確認できた。

proxyコマンド

上のように2段ログインより、proxyコマンドを使うとこより、直接にtargetへログインすることができる。

bash-3.2# ssh -o ProxyCommand='ssh -p 20022 -W %h:%p root@127.0.0.1' root@172.17.0.3
root@127.0.0.1's password:
root@172.17.0.3's password:
Last login: Mon May 20 11:47:38 2019 from 172.17.0.2
[root@centos7-sshd-target ~]#

パスワードが2回に聞かれ、1回目はproxyサーバのもので、2回目はtargetのものである。proxyコマンドは以下のようなフォーマットになる。

ssh -o ProxyCommand='ssh -l root -p 20022 -W %h:%p <proxyユーザ>@<proxyサーバ>' <targetユーザ>@<targetアドレス>

もうちょっと遊ぶ

とりあえず、やりたいことはできた。しかし、よく%h:%p はデフォルトホスト名とデフォルトポートだと説明されるが、少し意味わからない。ちょっといじってみた

bash-3.2# ssh -o ProxyCommand='ssh -p 20022 -W 172.17.0.3:22 root@127.0.0.1' xxx
root@127.0.0.1's password:
root@xxx's password:
Last login: Mon May 20 11:57:13 2019 from 172.17.0.2
[root@centos7-sshd-target ~]#

これで、proxy経由でtargetへログインできた。それに、途中でなぞののroot@xxx's password: 表示になっている。もうちょっと調べてみる。

まず、-W についてsshのmanは以下のように書いてある

-W ホスト :ポート
クライアントの標準入力と標準出力を、安全な通信路を通してホスト のポート 上に接続します。このオプションは、-N ,-T ,ExitOnForwardFailureおよびClearAllForwardingsを暗黙のうちに含んでいますが、これらは設定ファイルまたは-o オプションにより上書きすることができます。

-Wはフォワードの設定するものである。今回の目的であれば、proxyサーバへログインした後、全てinput/output は %h:%p へフォワードされている。すなわち、ユーザからの操作はすべてtargetへフォワードされている。なお、%h,%pはProxyCommand中の特別意味を持っている。%hはProxyCommandの後(本来接続したい)のホストで、%pはその接続の時にポート番号である。%hは明示的に書かれている場合、ホストはあまり意味がもっていなくなる(上のxxx)

sshログインと同様に、scpとProxyコマンドを使い、`targetサーバ`とフィアルやりとりも可能

$ scp -o ProxyCommand='ssh -p 20022 -W %h:%p root@127.0.0.1' test.txt root@172.17.0.3:/tmp/

まとめ

簡単にsshサーバのdockerイメージをサクセスおよびそのイメージを使い、sshのproxyコマンドの追加たを説明した。

参考

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

DockerImageをカスタマイズしてDockerHubに登録する

DockerImageをカスタマイズしDockerHubに登録する

はじめに  

この記事は、公式で公開されているDockerImageに一部アプリケーションの設定に変更を加えたDockerImageを作成し、DockerHubに登録する手順を記したものです。
私自身Dockerはあまり詳しくなく、デフォルトのDockerImageに少しだけ変更を加えてコンテナを建てる場合にはどうすればいいのかが分からずに、色々調べたり試したりしてみて最終的に実践した方法をまとめています。

この記事の対象者

  • DockerImageを少しだけ変えて保存したんだけれども、どうするんだ?って人

この記事を見てできること

  • DockerImageに変更を加えたものを新たにDockerImageとして登録できる
  • 上記のDockerImageをDockerHubに登録できる

なぜDockerImageをカスタマイズしたくなったのか

具体的には、以下の作業を実施している時に、困りごとが2つありました。
Docker-composeでECS上にコンテナを起動してログ収集・分析基盤(ELK)を構築する

公式で公開されているDockerImageだとメモリの関係でコンテナが起動しない

最初は、EC2のt2.smallインスタンス(メモリ2GB)の中でElasticsearchとLogstashを起動しようと試みていました。しかし、公式で公開されているDockerImageの初期設定では、これらのアプリケーションで占有するメモリがそれぞれ1GBでした。
つまり、そのままのDockerImageではElasticsearchとLogstashが両方起動出来ずに、コンテナが落ちてしまいます(ぴったり2GBだし起動出来るじゃん!!となる訳もありませんでした…)。そこで、コンテナ起動前にアプリケーションで占有するメモリの設定を変更する必要がありました。
system.006.jpeg

コンテナを起動する度に、特定のファイルを都度配置する必要があった(面倒)

Logstashではconfファイルの設定に基づいて、アプリケーションが動作します。公式で公開されているDockerImageからコンテナを起動した時点では、このconfファイルが配置されておりません。そのため、コンテナ起動後に手動でconfファイルを配置するという、イケていない運用をしていました。

対応策として、Dockerのボリューム(Data Volume)を使用してマウントすることで、データを永続化すればいいじゃんという話もあるかと思います。しかし、ECインスタンス上でコンテナを起動しており、単純にローカルのディレクトリとコンテナ間でボリュームをマウントすることが出来ませんでした(ただ単に知識不足というのもあります。またEBSボリュームも使いたくなかった)。

そんなことがあり、「このDockerImageをベースとして、メモリの設定ファイルだけ書き換えたいな・・・、このconfファイルをコンテナ起動時に配置したいな・・・」と思うことが度々ありました。

方法:設定ファイルの書き換え & confファイルの配置

  1. docker-composeで環境変数を設定する
    元々docker-composeを使って、ECSでコンテナを起動していたため、docker-compose.ymlの環境変数の設定を追記しました。アプリケーションのメモリの設定については、こちらで対応できました。

  2. DockerImageをカスタマイズしてDockerHubに登録する
    既にconfファイルが配置された状態のDockerImageを作成して、docker-composeで読み込むイメージにしてしまおうという発想に至りました。confファイルの配置については、こちらで対応できました。

手順

  1. docker-composeで環境変数を設定する
    単純に環境変数(environment)に設定するだけでした。 環境変数って、どこまで設定できて・設定できないものは何なのかイマイチ分かっていません。公式ページやググった結果、環境変数を設定している事例を漁りながら設定していました。この辺は何を見ればわかるのでしょうか…
docker-compose.yml
version: "2"
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.0.0
    mem_limit: 1g
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    networks:
      - elastic-stack

  logstash:
    image: gogogonkun/logstash_pub
    mem_limit: 768m
    ports:
      - "5044:5044"
    environment:
      - "LS_JAVA_OPTS=-Xms512m -Xmx512m"
    networks:
      - elastic-stack

networks:
  elastic-stack:

  1. DockerImageをカスタマイズしてDockerHubに登録する
    こっちが本題だったような… まぁ気にせずに…

以下のページが参考になりました。
独自のDockerImageを作成する
DockerImageをDockerHubに登録の仕方

まずは編集したいDockerImageからコンテナを起動する。

docker run -it gogogonkun/logstash bash  

適当にファイルを配置したり、編集したりする

vi /usr/share/logstash/pipeline/movhis.conf

input {
    http {
        port => 5044
        response_headers => {
            "Access-Control-Allow-Origin" => "*"
            "Content-Type" => "text/plain"
            "Access-Control-Allow-Headers" => "Origin, X-Requested-With, Content-Type, Accept"
        }
    }
}
※省略

変更内容から新たにDockerImageを作成

docker container ps
docker container commit [CONTAINER ID] [イメージ名(DockerHubのリポジトリ名と合わせる)]:[タグ]
例:docker container commit a94665bc5f13 gogogonkun/logstash_pub:latest

DockerHubへイメージをプッシュする

docker push gogogonkun/logstash_pub:latest

DockerHubから確認してみます
スクリーンショット 2019-05-22 2.41.28.png

DockerHubに登録できています。
docker-composeで起動するコンテナのイメージとして使用する場合には、登録されたリポジトリ名を指定すると、カスタマイズしたDockerImageが起動します。

おわりに

今回の対応方法が正しいかどうかは分かりません。詳しい人が居たら教えて欲しいです。あと、日本語ガバガバなのは大目に見つつもご指摘いただければ嬉しいです。

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