end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

pythonのdecoratorやcontextlibによる前後への処理追加

先程のentryに関連?として、以下のqiitaからの写経です。

https://qiita.com/chocomintkusoyaro/items/214f87a6300a88d15363

# cf. https://qiita.com/chocomintkusoyaro/items/214f87a6300a88d15363
import contextlib
import sys

def main():
    msg = "this is test message"
    decorated_1( msg )
    decorated_2( msg )
    decorated_3( msg )
    decorated_4( msg )

def decorator_1(org_func):
    func_name = sys._getframe().f_code.co_name
    
    def wrapper(content):
        print(func_name,"start")
        result = org_func(content)
        print(func_name,"goal")
        return result
    return wrapper

@decorator_1
def decorated_1(content):
    print(content)

#### decoratorに引数を渡す場合。ソースが分かりづらいですが
def decorator_2(name):
    def sub_decorator(org_func):
        def wrapper(content):
            print(name,"start")
            result = org_func(content)
            print(name,"goal")
            return result

        return wrapper
    return sub_decorator

@decorator_2('deco2')
def decorated_2(content):
    print(content)
    
    
#### decorator_2の別の書き方。
@contextlib.contextmanager
def decorator_3(name):
    print("{} start".format(name))
    yield  ## 処理の一時停止
    print('{} end'.format(name))

def decorated_3(content):
    func_name = sys._getframe().f_code.co_name
    
    with decorator_3( func_name):
        print(content)


#### decorator_3の更に別の書き方。
class decorator_4(contextlib.ContextDecorator):
    def __init__(self, name):
        self.name = name
        self.start_tag = '<{}>'.format(name)
        self.end_tag = '</{}>'.format(name)

    # デコレーションに入ったときの動作を定義
    def __enter__(self):
        print(self.start_tag)

    # デコレーションを抜けるときの動作を定義
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(self.end_tag)

def decorated_4(content):
    with decorator_4('decorated'):
        print( content )
    
if __name__ == '__main__':
    main()

↑こう書くと、↓こう表示されます

>python foo.py
decorator_1 start
this is test message
decorator_1 goal
deco2 start
this is test message
deco2 goal
decorated_3 start
this is test message
decorated_3 end
<decorated>
this is test message
</decorated>

GoF Decorator for python

pythonにdecorator機能というものがあることを知りました。

pythonのdecorator機能とは、 「関数やクラスの前後に特定の処理を追加できる」ようですが、 GoFのデコレータパターンとは異なるようです。

で、pythonのdecorator機能を試す前に、 GoFデザインパターンの振り返りとして、 以下の「レベルアップJavaデザインパターン編~」を pythonで実装します。

https://www.amazon.co.jp/dp/B086D2HLKK

実装例

以下の「ミックスアイス」で理解できると思います。

クラス図

┌IceCream(Abstract Class)─────┐
├─────────────────┤
│+get_price():int(Abstract Method) ├←┐
│+get_name() :str(Abstract Method) │  │
└───┬────────┬────┘  │
        △                △           集約
       継承              継承           ◇
┌Corn─┴────┐┌Ice ┴──────┴┐
│-price:int      ││#ice_cream:IceCream │
│-name:str       ││                    │
├────────┤├──────────┤
│+get_price():int││+get_price():int    │
│+get_name() :str││+get_name() :str    │
└────────┘└─┬──┬─────┘
                        △    △               
                       継承  継承      
              ┌Vanilla ┴┐┌┴Chocolate ┐
              │-price:int││-price:int  │
              │-name:str ││-name:str   │
              ├─────┤├──────┤
              └─────┘└──────┘    

python script

# -*- coding: utf-8 -*-
import abc # Abstract Base Classes モジュール

def main():
    corn = Corn()
    vanilla = Vanilla(corn)
    choco   = Chocolate(corn)
    mix     = Chocolate(Vanilla(corn))
    
    print(vanilla.get_name(),vanilla.get_price() )
    print(choco.get_name(),  choco.get_price() )
    print(mix.get_name(),    mix.get_price() )

class IceCream(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def get_price(self) -> int:
        raise NotImplementedError()
    
    @abc.abstractmethod
    def get_name(self) -> str:
        raise NotImplementedError()
    
class Corn(IceCream):
    def __init__(self):
        self.price = 100
        self.name  = "アイスクリーム"

    def get_price(self) -> int:
        return self.price
    
    def get_name(self) -> str:
        return self.name

class Ice(IceCream):
    def __init__(self,ice_cream):
        self.ice_cream = ice_cream
        
    def get_price(self) -> int:
        return self.price + self.ice_cream.get_price()
    
    def get_name(self) -> str:
        return self.name + self.ice_cream.get_name()
    
class Vanilla(Ice):
    def __init__(self,ice_cream):
        super().__init__(ice_cream)
        self.price = 100
        self.name  = "バニラ"
    
class Chocolate(Ice):
    def __init__(self,ice_cream):
        super().__init__(ice_cream)
        self.price = 70
        self.name  = "チョコ"
    
if __name__ == '__main__':
    main()

↑こう書くと↓こう表示されます

>python foo.py
バニラアイスクリーム 200
チョコアイスクリーム 170
チョコバニラアイスクリーム 270

weakref for python を使用した弱参照化によるメモリリーク回避

「ゼロから作るディープラーニング 3 」の 「ステップ17 メモリ管理と循環参照」において、 weakrefによる弱参照化が紹介されていましたので、メモ

weakref.ref() を使用する場合

参考url https://yumarublog.com/python/weakref/

import sys
import weakref

def main():
    mem_leak_proc()
    no_mem_leak_proc()
    
def mem_leak_proc():
    member1 = Member(1, "tim")
    ref1 = member1  # 強参照 作成
    del member1     # 参照元 object削除

    # 強参照の為、上記でdelしているが、参照先では残ります
    print(sys._getframe().f_code.co_name, ref1.name)

def no_mem_leak_proc():
    member1 = Member(1, "tim")

    # 弱参照 生成
    ref1 = weakref.ref( member1 )
    print(sys._getframe().f_code.co_name, ref1().name)

    # 参照元 object削除
    del member1

    if ref1() == None:
        print(sys._getframe().f_code.co_name, "ref1 not exist")
    else:
        print(sys._getframe().f_code.co_name, "ref1 exists")

class Member:
    def __init__(self, id, name):
        self.id = id
        self.name = name

if __name__ == '__main__':
    main()

↑こう書くと↓こう表示されます

> python foo_2.py
mem_leak_proc tim
no_mem_leak_proc tim
None
no_mem_leak_proc ref1 not exist

weakref.WeakValueDictionary() を使用する場合

参考url https://qiita.com/pashango2/items/fb1e5e79589279c5a861

import sys
import weakref

def main():
    mem_leak_proc()
    no_mem_leak_proc()
    
def mem_leak_proc():
    # idをkeyにした辞書
    members_dict = {
        x.id : x for x in [ Member(1, "tim"),
                            Member(2, "matthew"),
                            Member(3, "Jack") ]
    }
    # 上記を元にnameをkeyにした辞書.(循環参照)
    names_dict = {x.name: x for x in members_dict.values()}

    del members_dict[1]
    # 上記でdelしているが、循環参照により、以下は値が残り、
    # このメモリリーク?を改善するには、del names_dict['tim'] も必要
    name_key = "tim"
    if name_key in names_dict:
        print(sys._getframe().f_code.co_name, name_key,"exists.")
    else:
        print(sys._getframe().f_code.co_name, name_key,"don't exist.")

def no_mem_leak_proc():
    # idをkeyにした辞書
    members_dict = {
        x.id : x for x in [ Member(1, "tim"),
                            Member(2, "matthew"),
                            Member(3, "Jack") ]
    }

    names_dict = weakref.WeakValueDictionary(
        {x.name: x for x in members_dict.values()}
    )
    
    del members_dict[1]
    name_key = "tim"
    if name_key in names_dict:
        print(sys._getframe().f_code.co_name, name_key,"exists.")
    else:
        print(sys._getframe().f_code.co_name, name_key,"don't exist.")

class Member:
    def __init__(self, id, name):
        self.id = id
        self.name = name

if __name__ == '__main__':
    main()

↑こう書くと↓こう表示されます

>python foo.py
mem_leak_proc tim exists.
no_mem_leak_proc tim don't exist.

自動微分における順伝播と逆伝播(バックプロバゲーション)

自動微分における順伝播と逆伝播は、合成関数の微分によりますが、 十分には理解できていない気がしていますので、振り返り。

「ゼロから作るディープラーニング 3 」の写経です

目次

数式と、計算グラフ(svg)

p.25付近の内容です

【順伝播】 数式 a = A(x) 、b = B(a) 、y = C(b) ⇒ y = C( B( A(x) ) ) 凡例: □数式、○値 計算グラフ x A a B b C y 【逆伝播】 計算グラフ dy/dx dz/dx dy/da db/da dy/db dy/db dy/dy 数式 dy/dx = ( ( ( dy/dy・dy/db ) db/da ) da/dx )

python script

以下のurlにあるstep7~8の写経です。

https://github.com/oreilly-japan/deep-learning-from-scratch-3

Define-by-Run に該当しますかね

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import numpy as np

def main():
    A = Square()
    B = Exp()
    C = Square()

    x = Variable(np.array(0.5))
    a = A(x)
    b = B(a)
    y = C(b)

    # backward
    y.grad = np.array(1.0)
    y.backward()
    print(x.grad)

class Variable:
    def __init__(self, data):
        self.data = data
        self.grad = None
        self.creator = None
        
    def set_creator(self, func):
        self.creator = func

    def backward(self):
        funcs = [self.creator]
        while funcs:
            f = funcs.pop()  # 1. Get a function
            x, y = f.input, f.output  # 2. Get the function's input/output
            x.grad = f.backward(y.grad)  # 3. Call the function's backward

            if x.creator is not None:
                funcs.append(x.creator)

class Function:
    def __call__(self, input):
        x = input.data
        y = self.forward(x)
        output = Variable(y)
        output.set_creator(self)
        self.input = input
        self.output = output
        return output

    def forward(self, x):
        raise NotImplementedError()

    def backward(self, gy):
        raise NotImplementedError()

class Square(Function):
    def forward(self, x):
        y = x ** 2
        return y

    def backward(self, gy):
        x = self.input.data
        gx = 2 * x * gy
        return gx

class Exp(Function):
    def forward(self, x):
        y = np.exp(x) # ネイピア数e が底の指数関数
        return y

    def backward(self, gy):
        x = self.input.data
        gx = np.exp(x) * gy
        return gx

if __name__ == '__main__':
    main()

svg marker による矢印

https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker

上記urlを参考にすると、以下

<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="100">
  <defs>
    <style>
      circle,line, marker, path
                    { stroke      : #20285A;
                      fill        : none;
                      stroke-width: 1;           }
      line, path    { marker-end  : url(#arrow); }
      text.math_exp { stroke     : #20285A;
                      fill       : #20285A;
                      font-size  : 20px;
                      font-weight: normal;
                      font-family: "Times New Roman";
                      font-style : italic;
                      text-anchor: middle; /* text中心で座標指定 */
      }
    </style>
    <marker id="arrow"
            viewBox="0 0 10 10"
            refX="10" refY="5"
            markerWidth="10" markerHeight="10"
            orient="auto-start-reverse">
      <path d="M 0 0 L 10 5 L 0 10" />
    </marker>
  </defs>

  <circle cx="100" cy="50" r="15"></circle>
  <text   x ="100" y ="55" class="math_exp">x</text>
  <path   d ="M115,50 l100,0" />
</svg>
x

今後、kubernetes ( minikube )を手習いする際のメモ

kubernetesは複数のサーバを必要としますので、minikubeを使用すると思いますが、 minikubeは、2cpu以上が必須ですので、virtualboxを主に使用している私の手習いは だいぶ先かも

参考url

Docker Swarmは学びません

Docker/Kubernetes 実践コンテナ開発入門 (以下のurl)の 「4. Swarmによる実践的なアプリケーアプリケーション構築」を 写経しようとしましたが、

$ docker container exec -it manager \
    docker stack deploy -c ./stack/todo-mysql.yml todo_mysql

の実行でエラー。

コンテナオーケストレーションツールとしては、Docker Swarmよりも Kubernetes が、世の中的には、標準かと思いますので、 このentryの下部に様々記載してますが、Docker Swarmは学びません。

www.amazon.co.jp

目次

git clone https://github.com/gihyodocker/tododb

全体構成

┌stack(frontend) ──────────────────┐
│┌service(frontend_nginx) ────────────┐│
││┌──────────────────────┐││
│││container x3                                │││
│└┴──────────┰───────────┴┘│
│                       ┃HTTP(Proxy)               │
│┌service(frontend_web) ╂────────────┐│
││┌──────────┸───────────┐││
│││container x3                                │││
└┴┴──────────┰───────────┴┴┘
                          ┃HTTP
┌stack(app)───────╂─────────────┐
│┌service(app_nginx)──╂────────────┐│
││┌──────────┸───────────┐││
│││container x3                                │││
│└┴──────────┰───────────┴┘│
│                       ┃HTTP(Proxy)               │
│┌service(app_api)───╂────────────┐│
││┌──────────┸───────────┐││
│││container x3                                │││
└┴┴────┰───────────┰─────┴┴┘
              ┃write                 ┃read
┌stack(mysql)╂───────────╂───────┐
│┌─────╂───┐        ┌──╂──────┐│
││┌────┸───┤replica ├──┸─────┐││      
│││container x1    ┝━━━━┥container x3    │││
││└────────┤        ├────────┘││
│ service(mysql_master)         service(mysql_slave) │
└──────────────────────────┘

ノードの確認 (master x1 , worker x3)

$ docker container exec -it manager docker node ls
    HOSTNAME       STATUS   MANAGER STATUS   ENGINE VERSION
*   98e4c5b93225   Ready    Leader           20.10.23
    120608a59677   Ready                     20.10.23
    d4dc2bd958ec   Ready                     20.10.23
    e54446b93f5e   Ready                     20.10.23

todoappという名の overlayネットワーク作成

$ docker container exec -it manager \
    docker network create --driver=overlay --attachable todoapp

$ vi tododb/etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
pid-file    = /var/run/mysqld/mysqld.pid
socket      = /var/run/mysqld/mysqld.sock
datadir     = /var/lib/mysql
#log-error  = /var/log/mysql/error.log
# By default we only accept connections from localhost
#bind-address   = 127.0.0.1
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
relay-log=mysqld-relay-bin 
relay-log-index=mysqld-relay-bin 

log-bin=/var/log/mysql/mysql-bin.log

$ vi tododb/add-server-id.sh

先程の mysqld.cnf に server-id を追記する為のものです

#!/bin/bash -e
OCTETS=(`hostname -i | tr -s '.' ' '`)

MYSQL_SERVER_ID=`expr ${OCTETS[2]} \* 256 + ${OCTETS[3]}`
echo "server-id=$MYSQL_SERVER_ID" >> /etc/mysql/mysql.conf.d/mysqld.cnf

$ vi tododb/prepare.sh

Master/Slave間のレプリケーション設定用

#!/bin/bash -e

# (1) MasterとSlaveを環境変数で制御する
if [ ! -z "$MYSQL_MASTER" ]; then
  echo "this container is master"
  exit 0
fi

echo "prepare as slave"

# (2) SlaveからMasterへの疎通確認をする
if [ -z "$MYSQL_MASTER_HOST" ]; then
  echo "mysql_master_host is not specified" 1>&2
  exit 1
fi

while :
do
  if mysql -h $MYSQL_MASTER_HOST -u root -p$MYSQL_ROOT_PASSWORD -e "quit" > /dev/null 2>&1 ; then
    echo "MySQL master is ready!"
    break
  else
    echo "MySQL master is not ready"
  fi
  sleep 3
done

# (3) Masterにレプリケーション用のユーザーと権限の作成
IP=`hostname -i`
IFS='.'
set -- $IP
SOURCE_IP="$1.$2.%.%"
mysql -h $MYSQL_MASTER_HOST -u root -p$MYSQL_ROOT_PASSWORD \
  -e "CREATE USER IF NOT EXISTS '$MYSQL_REPL_USER'@'$SOURCE_IP' IDENTIFIED BY '$MYSQL_REPL_PASSWORD';"
mysql -h $MYSQL_MASTER_HOST -u root -p$MYSQL_ROOT_PASSWORD \
  -e "GRANT REPLICATION SLAVE ON *.* TO '$MYSQL_REPL_USER'@'$SOURCE_IP';"

# (4) Masterのbinlogのポジションを取得
MASTER_STATUS_FILE=/tmp/master-status
mysql -h $MYSQL_MASTER_HOST -u root -p$MYSQL_ROOT_PASSWORD \
  -e "SHOW MASTER STATUS\G" > $MASTER_STATUS_FILE
BINLOG_FILE=`cat $MASTER_STATUS_FILE | grep File | xargs | cut -d' ' -f2`
BINLOG_POSITION=`cat $MASTER_STATUS_FILE | grep Position | xargs | cut -d' ' -f2`
echo "BINLOG_FILE=$BINLOG_FILE"
echo "BINLOG_POSITION=$BINLOG_POSITION"

# (5) レプリケーションを開始する 
mysql -u root -p$MYSQL_ROOT_PASSWORD \
  -e "CHANGE MASTER TO MASTER_HOST='$MYSQL_MASTER_HOST', MASTER_USER='$MYSQL_REPL_USER', MASTER_PASSWORD='$MYSQL_REPL_PASSWORD', MASTER_LOG_FILE='$BINLOG_FILE', MASTER_LOG_POS=$BINLOG_POSITION;"
mysql -u root -p$MYSQL_ROOT_PASSWORD -e "START SLAVE;"

echo "slave started"

$ vi tododb/Dockerfile

FROM mysql:5.7

# (1) パッケージアップデートとwgetインストール
RUN apt-get update
RUN apt-get install -y wget

# (2) entrykitのインストール
RUN wget https://github.com/progrium/entrykit/releases/download/v0.4.0/entrykit_0.4.0_linux_x86_64.tgz
RUN tar -xvzf entrykit_0.4.0_linux_x86_64.tgz
RUN rm entrykit_0.4.0_linux_x86_64.tgz
RUN mv entrykit /usr/local/bin/
RUN entrykit --symlink

# (3) スクリプトと各種設定ファイルのコピー
COPY add-server-id.sh /usr/local/bin/
COPY etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/mysql.conf.d/
COPY etc/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/
COPY prepare.sh /docker-entrypoint-initdb.d
COPY init-data.sh /usr/local/bin/
COPY sql /sql

# (4) スクリプトとmysqldの実行
ENTRYPOINT [ \
  "prehook", \
    "add-server-id.sh", \
    "--", \
  "docker-entrypoint.sh" \
]

CMD ["mysqld"]

tododb/Dockerfile の build 、更にタグ付けし、repositoryへpush

$ docker image build -t ch04/tododb:latest .

$ docker image tag ch04/tododb:latest localhost:5000/ch04/tododb:latest
$ docker image push localhost:5000/ch04/tododb:latest

Swarm上の Master/Slaveサービス定義 ($ vi stack/todo-mysql.yml )

version: "3"

services:
  master:
    image: registry:5000/ch04/tododb:latest
    deploy:
      replicas: 1
      placement:
        constraints: [node.role != manager]
    environment:
      MYSQL_ROOT_PASSWORD: gihyo 
      MYSQL_DATABASE: tododb 
      MYSQL_USER: gihyo 
      MYSQL_PASSWORD: gihyo 
      MYSQL_MASTER: "true" 
    networks:
      - todoapp

  slave:
    image: registry:5000/ch04/tododb:latest
    deploy:
      replicas: 2
      placement:
        constraints: [node.role != manager]
    depends_on:
      - master
    environment:
      MYSQL_MASTER_HOST: master
      MYSQL_DATABASE: tododb 
      MYSQL_USER: gihyo 
      MYSQL_PASSWORD: gihyo 
      MYSQL_ROOT_PASSWORD: gihyo 
      MYSQL_REPL_USER: repl 
      MYSQL_REPL_PASSWORD: gihyo 
    networks:
      - todoapp

networks:
  todoapp:
    external: true
$ docker container run -d --name todo_mysql localhost:5000/ch04/tododb:latest

$ docker container exec -it manager \
    docker stack deploy -c ./stack/todo-mysql.yml todo_mysql
ERROR open ./stack/todo-mysql.yml: no such file or directory

Docker Swarm 、 Service 、Stack

以下の書籍「Docker/Kubernetes 実践コンテナ」にある

  • 3.5.1 Docker Swarm
  • 3.5.2 Service
  • 3.5.3 Stack
  • 3.5.4 ServiceをSwarm外から利用する

の写経。が、本日時点では、自分の理解が怪しい

www.amazon.co.jp

目次

Docker Swarm

┌─Docker───────────────────┐
│┌Registry  ─────┐        ┌Manager ──┤
││                    │port: ┏┥docker in d │      
││                    ┝━━━┫└──────┤
││┌Dir ───────┤ 5000 ┃┌Worker1~3─┤
│││/var/lib/registry │      ┗┥docker in d │
└┴┴──┰──────┴────┴──────┘
          ┃volume mount
┌─┬Dir ┸──────┬───────────┐
│  │registry-data     │                      │
│  └─────────┘                      │
└─Host────────────────────┘

$ vi docker-compose.yml

version: "3"
services:
  registry:
    container_name: registry
    image: registry:2.6
    ports:
      - 5000:5000
    volumes:
      - "./registry-data:/var/lib/registry"
  manager:
    container_name: manager
    image: docker:20.10.23-dind
    # hostの全deviceに接続okになるらしいが、詳細は理解:未
    privileged: true
    # cf. https://zenn.dev/hohner/articles/43a0da20181d34
    tty: true
    ports:
      - 8000:80
      - 9000:9000
    depends_on:
      - registry
    # hostには公開されない、container側のport
    expose:
      - 3375
    # 以下は https以外のhttp:5000で アクセスOKにする為
    command: "--insecure-registry registry:5000"
    volumes:
      - "./stack:/stack"
  worker01:
    container_name: worker01
    image: docker:20.10.23-dind
    privileged: true
    tty: true
    depends_on:
      - manager
      - registry
    expose:
      - 7946
      - 7946/udp
      - 4789/udp
    command: "--insecure-registry registry:5000"
  worker02:
    container_name: worker02
    image: docker:20.10.23-dind
    privileged: true
    tty: true
    depends_on:
      - manager
      - registry
    expose:
      - 7946
      - 7946/udp
      - 4789/udp
    command: "--insecure-registry registry:5000"

docker compose 起動と、起動していることの確認

$ docker compose up -d

$ docker container ls
CONTAINER ID  PORTS                                                     NAMES
3247a5969ab6  2375-2376/tcp,4789/udp,7946/tcp,7946/udp                  worker02
0aecc7a262b6  2375-2376/tcp,4789/udp,7946/tcp,7946/udp                  worker03
9a8f932cc2f9  2375-2376/tcp,4789/udp,7946/tcp,7946/udp                  worker01
cd870561d281  2375-2376/tcp,3375/tcp,:::9000->9000/tcp,:::8000->80/tcp  manager
32d8ae5003e2  :::5000->5000/tcp                                         registry

managerコンテナをswarnのマネージャに設定

$ docker container exec -it manager docker swarm init

Swarm initialized: current node (u6xgdpg665085m0qf64cnct0u) is now a manager.
To add a worker to this swarm, run the following command:
  docker swarm join --token \
    SWMTKN-1-4byqlau1bcz52zaj9jpmigudblrk3d482v8arwfd4syvywsfbo-50ow5mvipbyfagm4m8uken59k 172.18.0.3:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

worker01~03をswarnのノードに設定

$ docker container exec -it worker01 docker swarm join --token \
    SWMTKN-1-4byqlau1bcz52zaj9jpmigudblrk3d482v8arwfd4syvywsfbo-50ow5mvipbyfagm4m8uken59k \
    manager:2377
$ docker container exec -it worker02 docker swarm join --token \
    SWMTKN-1-4byqlau1bcz52zaj9jpmigudblrk3d482v8arwfd4syvywsfbo-50ow5mvipbyfagm4m8uken59k \
    manager:2377
$ docker container exec -it worker03 docker swarm join --token \
    SWMTKN-1-4byqlau1bcz52zaj9jpmigudblrk3d482v8arwfd4syvywsfbo-50ow5mvipbyfagm4m8uken59k \
    manager:2377

swarnノードの確認

$ docker container exec -it manager docker node ls
ID                            HOSTNAME       STATUS   MANAGER STATUS
u6xgdpg665085m0qf64cnct0u *   98e4c5b93225   Ready    Leader        
ssgz4ker8lvrfbpj3mtctjtsh     120608a59677   Ready                  
q5nuauip96qcgdctl4fmdj4j9     d4dc2bd958ec   Ready                  
lt3r8tnj48riejbozf7itsq1l     e54446b93f5e   Ready                  

dockerレジストリへ、イメージをpush

https://end0tknr.hateblo.jp/entry/20230125/1674635932

以前、上記のentryで作成した Dockerfile と main.go のイメージをタグ付けし、 Registry(port:5000)へ push

$ docker image tag example/echo:latest localhost:5000/example/echo:latest
$ docker image push localhost:5000/example/echo:latest

worker01で、先程、登録したイメージをpull

$ docker container exec -it worker01 docker image pull registry:5000/example/echo:latest

$ docker container exec -it worker01 docker image ls
REPOSITORY                   TAG       IMAGE ID       CREATED        SIZE
registry:5000/example/echo   latest    2cc31d7badf8   45 hours ago   803MB

Serviceの作成

$ docker container exec -it manager \
  docker service create --replicas 1 --publish 8000:8080 --name echo \
  registry:5000/example/echo:latest

$ docker container exec -it manager docker service ls
ID             NAME      MODE         REPLICAS   IMAGE                               PORTS
se9z57opyzi9   echo      replicated   1/1        registry:5000/example/echo:latest   *:8000->8080/tcp

6コのcontainerで分散実行

$ docker container exec -it manager docker service scale echo=6

$ docker container exec -it manager docker service ps echo | grep Running
echo.1    registry:5000/example/echo:latest   Running 15 minutes ago             
echo.2    registry:5000/example/echo:latest   Running  3 minutes ago              
echo.3    registry:5000/example/echo:latest   Running  3 minutes ago              
echo.4    registry:5000/example/echo:latest   Running  2 minutes ago              
echo.5    registry:5000/example/echo:latest   Running  2 minutes ago              
echo.6    registry:5000/example/echo:latest   Running  2 minutes ago

serviceを削除する場合、rm

$ docker container exec -it manager docker service rm echo
$ docker container exec -it manager docker service ls

Docker Stack

1コのイメージのみを扱うServiceに対し、Stackは複数Serviceをグルーピング

ch03という名称のoverlayネットワーク作成

$ docker container exec -it manager docker network create \
    --driver=overlay --attachable ch03

$ vi ./stack/ch03-webapi.yml

version: "3"
services:
  nginx:
    image: gihyodocker/nginx-proxy:latest
    deploy:
      replicas: 3
      placement:
        constraints: [node.role != manager]
    environment:
      SERVICE_PORTS: 80
      BACKEND_HOST: echo_api:8080
    depends_on:
      - api
    networks:
      - ch03
  api:
    image: registry:5000/example/echo:latest
    deploy:
      replicas: 3
      placement:
        constraints: [node.role != manager]
    networks:
      - ch03
networks:
  ch03:
    external: true

stackの配備

「echo」というstack名称で、deploy

$ docker container exec -it manager docker stack deploy \
  -c ./stack/ch03-webapi.yml echo

stack配備結果の確認

docker container exec -it manager docker stack ps echo

$ docker container exec -it manager docker stack services echo
NAME         MODE         REPLICAS   IMAGE                            
echo_api     replicated   3/3        registry:5000/example/echo:latest   
echo_nginx   replicated   3/3        gihyodocker/nginx-proxy:latest   

$ docker container exec -it manager docker stack ps echo
NAME           IMAGE                               NODE           CURRENT STATE
echo_api.1     registry:5000/example/echo:latest   e54446b93f5e   Running 4 minutes ago
echo_api.2     registry:5000/example/echo:latest   120608a59677   Running 4 minutes ago
echo_api.3     registry:5000/example/echo:latest   d4dc2bd958ec   Running 4 minutes ago
echo_nginx.1   gihyodocker/nginx-proxy:latest      e54446b93f5e   Running 4 minutes ago
echo_nginx.2   gihyodocker/nginx-proxy:latest      120608a59677   Running 4 minutes ago
echo_nginx.3   gihyodocker/nginx-proxy:latest      d4dc2bd958ec   Running 4 minutes ago

visualizerによる stack配備結果の確認

vi ./stack/visualizer.yml

version: "3"

services:
  app:
    image: dockersamples/visualizer
    ports:
      - "9000:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mode: global
      placement:
        constraints: [node.role == manager]

visualizerによる stack配備結果の確認

$ docker container exec -it manager docker stack deploy \
    -c ./stack/visualizer.yml \
    visualizer

上記を実行後、ブラウザで、http://192.168.56.113:9000/ へ アクセスすると、以下が表示されます。

stackの削除

$ docker container exec -it manager docker stack rm echo

HAProxyによるswarmクラスタ外からのservice利用

vi ./stack/ch03-ingress.yml

version: "3"

services:
  haproxy:
    image: dockercloud/haproxy
    networks: 
      - ch03 
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mode: global
      placement:
        constraints:
          - node.role == manager
    ports:
      - 80:80
      - 1936:1936 # for stats page (basic auth. stats:stats)

networks:
  ch03:
    external: true

配備

$ docker container exec -it manager docker stack deploy \
  -c /stack/ch03-webapi.yml echo

$ docker container exec -it manager docker stack deploy \
  -c /stack/ch03-ingress.yml ingress

$ docker container exec -it manager docker service ls
NAME            MODE       REPLICAS IMAGE                             PORTS
echo            replicated 6/6      registry:5000/example/echo:latest *:8000->8080/tcp
echo_api        replicated 3/3      registry:5000/example/echo:latest 
echo_nginx      replicated 3/3      gihyodocker/nginx-proxy:latest    
ingress_haproxy global     1/1      dockercloud/haproxy:latest        *:80->80/tcp, *:1936->1936/tcp
visualizer_app  global     1/1      dockersamples/visualizer:latest   *:9000->8080/tcp

$ curl http://localhost:8000/
Hello Docker!!

docker in docker (dind)

以下の書籍「Docker/Kubernetes 実践コンテナ」にある 「3.5.1 Docker Swarm」の写経です。

www.amazon.co.jp

目次

書籍記載の docker-compose.yml は、docker:18.05.0-ce-dind で古い

この為、当初、以下のようなエラーとなりました。

このエラーの修正法はあるかもしれませんが、 結局、docker:20.10.23-dind へバージョンアップすることで、解消しました。

Error starting daemon: Devices cgroup isn't mounted

cf. https://qiita.com/Suzuki09/items/f3f31901f3bf0a929668

$ sudo vi /etc/default/grub

  old GRUB_CMDLINE_LINUX="【略】rd.lvm.lv=almalinux/swap"
  new GRUB_CMDLINE_LINUX="【略】rd.lvm.lv=almalinux/swap systemd.unified_cgroup_hierarchy=0"

$ sudo grub2-mkconfig -o /boot/efi/EFI/almalinux/grub.cfg
$ sudo reboot

iptables v1.6.1: can't initialize iptables

Error starting daemon: Error initializing network controller:
error obtaining controller instance: failed to create NAT chain DOCKER:
iptables failed: iptables -t nat -N DOCKER: iptables v1.6.1:
can't initialize iptables table `nat':
Table does not exist (do you need to insmod?)

$ vi docker-compose.yml

version: "3"
services:
  registry:
    container_name: registry
    image: registry:2.6
    ports:
      - 5000:5000
    volumes:
      - "./registry-data:/var/lib/registry"
  manager:
    container_name: manager
    image: docker:20.10.23-dind
    privileged: true
    tty: true
    ports:
      - 8000:80
      - 9000:9000
    depends_on:
      - registry
    expose:
      - 3375
    command: "--insecure-registry registry:5000"
    volumes:
      - "./stack:/stack"
  worker01:
    container_name: worker01
    image: docker:20.10.23-dind
    privileged: true
    tty: true
    depends_on:
      - manager
      - registry
    expose:
      - 7946
      - 7946/udp
      - 4789/udp
    command: "--insecure-registry registry:5000"
  worker02:
    container_name: worker02
    image: docker:20.10.23-dind
    privileged: true
    tty: true
    depends_on:
      - manager
      - registry
    expose:
      - 7946
      - 7946/udp
      - 4789/udp
    command: "--insecure-registry registry:5000"

※ 「--insecure-registry registry:5000」は https以外のhttp:5000で、 registryでアクセス可にする為のものです

docker compose 起動と、起動していることの確認

$ docker compose up -d

$ docker compose ps
NAME      IMAGE                 COMMAND               SERVICE   PORTS
manager   docker:20.10.23-dind  "dockerd-entrypoint." manager   2375-2376/tcp,3375/tcp,:::9000->9000/tcp,:::8000->80/tcp
registry  registry:2.6          "/entrypoint.sh /etc" registry  0.0.0.0:5000->5000/tcp, :::5000->5000/tcp
worker01  docker:20.10.23-dind  "dockerd-entrypoint." worker01  2375-2376/tcp,4789/udp,7946/tcp,7946/udp
worker02  docker:20.10.23-dind  "dockerd-entrypoint." worker02  2375-2376/tcp,4789/udp,7946/tcp,7946/udp

docker data volume によるデータの永続化と、data volume containerからのexport

目次

data volume container 作成

$ vi Dockerfile

FROM busybox

VOLUME /var/lib/mysql

CMD ["bin/true"]

containerのbuildと実行

$ docker image build -t example/mysql-data:latest .
$ docker container run -d --name mysql-data example/mysql-data:latest

data volume へのデータ登録

mysql container 実行

$ docker container run -d --rm --name mysql \
  -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" \
  -e "MYSQL_DATABASE=volume_test" \
  -e "MYSQL_USER=example" \
  -e "MYSQL_PASSWORD=example" \
  --volumes-from mysql-data \
  mysql:5.7

mysql への接続

パスワードは不要です

$ docker container exec -it mysql mysql -u root -p volume_test

mysql へのデータ登録と、確認

mysql> CREATE TABLE user(
  id int PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;

mysql> INSERT INTO user (name)
       VALUES ('gihyo'), ('docker'), ('Solomon Hykes');
mysql> SELECT * FROM user;
+----+---------------+
| id | name          |
+----+---------------+
|  1 | gihyo         |
|  2 | docker        |
|  3 | Solomon Hykes |
+----+---------------+
3 rows in set (0.00 sec)

data volume からのexport

$ docker container run -v ${PWD}:/tmp \
  --volumes-from mysql-data \
  busybox \
  tar cvzf /tmp/mysql-backup.tar.gz /var/lib/mysql
tar: removing leading '/' from member names
var/lib/mysql/
var/lib/mysql/ibdata1
var/lib/mysql/ib_logfile1
var/lib/mysql/ib_logfile0
【略】
var/lib/mysql/volume_test/user.ibd
var/lib/mysql/ib_buffer_pool
var/lib/mysql/ibtmp1
$ ls -l
-rw-r--r-- 1 root     root     6924099 Jan 29 20:12 mysql-backup.tar.gz

deployment of master/slave jenkins into AlmaLinux docker

https://end0tknr.hateblo.jp/entry/20230128/1674863423

前回のentry に続き、docker compose ver.2 の手習い。

当初、以下のurlを参考に進めましたが、 最近のjenkins は jdk8→jdk11をサポート対象としている為、 参考urlの手順からは、少々変更しています。

参考url https://kacfg.com/aws-ec2-docker-jenkins

dockerは、最近では随分、浸透してきた印象がありますが、 「インターネット上に情報がいろいろある」とまではないらしく、 実サービスで利用するには、自身にまだまだ知識が必要。

という印象です。

目次

master jenkins の構築

$ vi docker-compose.yml

version: "3"
services:
  jenkins:
    container_name: master
    image: jenkins/jenkins:lts-jdk11
    ports:
      - 8080:8080
    volumes:
      - ./jenkins_home:/var/jenkins_home

https://hub.docker.com/r/jenkins/jenkins

$ mkdir jenkins_home; $ sudo chown 1000:1000 jenkins_home

ymlのvolumes によりコンテナ内のデータをmountし、永続化できます。 ただし、コンテナ内のjenkinsユーザが uid:gid=1000:1000の為、以下を実行します。

$ mkdir jenkins_home;
$ sudo chown 1000:1000 jenkins_home

上記を実行しないと、以下のエラーになります

$ docker compose up
Attaching to jenkins
jenkins  | Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
jenkins  | touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied

jenkins実行と、初期パスワード確認

$ docker compose up -d
  :
$ cat jenkins_home/secrets/initialAdminPassword
26f161b117464d968d0faafb20926714

ブラウザアクセスで、初期設定

http://192.168.56.113:8080 へ、ブラウザでアクセスし、 先程の初期パスワードでログイン。

プラグインのインストール

管理者アカウントを作成

最後にjenkinsへのアクセス用urlを確定し、完了

master jenkins 用のssh key作成

以降のstepで、ssh接続によるslaveを作成しますが、 このssh接続に必要な ssh keyを作成します。

$ docker container exec -it master ssh-keygen -t rsa -C ""
Generating public/private rsa key pair.
Enter file in which to save the key (/var/jenkins_home/.ssh/id_rsa): 
Created directory '/var/jenkins_home/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /var/jenkins_home/.ssh/id_rsa
Your public key has been saved in /var/jenkins_home/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:yYWsy0igixQ8acbqiUKO4zlIjH+wDSc201aEiCEhS3s 
The key's randomart image is:
+---[RSA 3072]----+
|== . .           |
|B.+ . .. .       |
|.X.E .  o .      |
|+.+.  .o o       |
|=o .... S        |
|X+O.+o .         |
|X* @. o          |
|+o+ o            |
| o..             |
+----[SHA256]-----+

ssh keyの作成完了後、一旦、dockerを停止して構いません。

$ docker compose down

slave jenkins の構築

$ vi docker-compose.yml

先程作成した docker-compose.yml に slave用の設定を追加

version: "3"
services:
  jenkins:
    container_name: master
    image: jenkins/jenkins:lts-jdk11
    ports:
      - 8080:8080
    volumes:
      - ./jenkins_home:/var/jenkins_home
  slave01:
    container_name: slave01
    build: .
    environment:
      - JENKINS_SLAVE_SSH_PUBKEY=ssh-rsa AAAAB3N【略】
      # cf. jenkins_home/.ssh/id_rsa.pub

$ vi Dockerfile

slaveでは、javaがpathに含まれておらず、ln -s を必要とする為、 slave用にDockerfile を作成します。

FROM jenkinsci/ssh-slave
RUN ln -s /usr/local/openjdk-8/bin/java /usr/local/bin/java

https://hub.docker.com/r/jenkins/ssh-slave/

master & slave の起動

$ docker compose up -d
$ docker compose ps
NAME    IMAGE                     COMMAND         SERVICE  PORTS
master  jenkins/jenkins:lts-jdk11 "/usr/bin/tini" jenkins  0.0.0.0:8080->8080/tcp
slave01 26-slave01                "setup-sshd"    slave01  22/tcp

slaveの追加設定

再び、ブラウザで http://192.168.56.113:8080 へ、アクセスし、 以下の設定を行ってください。

左ペイン → Jenkinsの管理

新規ノード作成

「ノード名:slave01」「Permanent Agent:チェック」






完了

ノード管理画面で、エラーメッセージがなければ、完了です

docker compose ver.2 による jenkins

目次

参考書籍

vi docker-compose.yml

version: "3"
services:
  jenkins:
    container_name: jenkins
    image: jenkins/jenkins:latest
    ports:
      - 8080:8080
    volumes:
      - ./jenkins_home:/var/jenkins_home

mkdir jenkins_home; chmod 777 jenkins_home

$ mkdir jenkins_home
$ chmod 777 jenkins_home

↑上記を実行しないと、↓以下のエラーになります。

対処方法は、いくつかあるようですが、今回は chmod 777 を利用

参考url https://tech-blog.rakus.co.jp/entry/20200826/docker

$ docker compose up
Attaching to jenkins
jenkins  | Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
jenkins  | touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied

jenkins実行と、初期パスワード確認、ブラウザアクセス

$ docker compose up -d
  :
$ cat jenkins_home/secrets/initialAdminPassword
26f161b117464d968d0faafb20926714

http://192.168.56.113:8080 へ、ブラウザでアクセスし、 先程の初期パスワードでログイン。

alma linuxで、docker手習い

殆ど使用したことがないので、練習です。

また、centosが、rhelのアップストリームとなった為、 試しに alma linux を使用します。

目次

参考書籍や参考url

install docker

$ sudo yum install yum-utils
$ sudo yum-config-manager \
    --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin
=====================================================================
 Package               Architecture Version          Repository      
=====================================================================
Installing:
 containerd.io         x86_64       1.6.15-3.1.el9   docker-ce-stable
 docker-ce             x86_64       3:20.10.23-3.el9 docker-ce-stable
 docker-ce-cli         x86_64       1:20.10.23-3.el9 docker-ce-stable
 docker-compose-plugin x86_64       2.15.1-3.el9     docker-ce-stable
<略>
$ docker --version
Docker version 20.10.23, build 7155243
  • docler-ce:Dockerデーモン本体. ceは、Community Edition
  • docker-ce-cli:Docker操作用コマンド群
  • containerd.io:Dockerデーモンが動作するために必要なパッケージ

gpasswd -a ~ docker

以下を実行しない場合、「sudo docker ~」のようにsudoでの実行要

$ sudo gpasswd -a end0tknr docker
Adding user end0tknr to group docker

docker の daemon起動

$ sudo systemctl start  docker
$ sudo systemctl enable docker

$ sudo docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Docker Buildx (Docker Inc., v0.10.0-docker)
  compose: Docker Compose (Docker Inc., v2.15.1)
  scan: Docker Scan (Docker Inc., v0.23.0)
<略>
 Docker Root Dir: /var/lib/docker

極基本的なdockerコマンド

## docker hubからgihyodocker/echoをdownloadし
## port;9000→8080のport forwardで起動
$ docker container run -t -p 9000:8080 gihyodocker/echo:latest

## curlコマンドで、アクセス
$ curl http://localhost:9000
Hello Docker!!

## コンテナ停止
$ docker container stop  $(docker container ls -q)

Dockerfile の使用例

vi Dockerfile

FROM golang:1.13

RUN mkdir /echo
COPY main.go /echo

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

vi main.go

package main
import (
    "fmt"
    "log"
    "net/http"
)
func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        log.Println("received request")
        fmt.Fprintf(w, "Hello Docker!!")
    })
    log.Println("start server")
    server := &http.Server{Addr: ":8080"}
    if err := server.ListenAndServe(); err != nil {
        log.Println(err)
    }
}

buildや実行、停止

##「example/echo:latest」という名前で「.」以下をbuild
$ docker image build -t example/echo:latest .

## build結果の確認
$ docker image ls
REPOSITORY         TAG       IMAGE ID       CREATED         SIZE
example/echo       latest    ccdda0ad9a49   7 seconds ago   803MB
golang             1.13      d6f3656320fe   2 years ago     803MB

## buildされたcontainerをバックグランド(-d)実行
$ docker container run -d -p 9000:8080 example/echo:latest

$ docker container ls
CONTAINER ID IMAGE               COMMAND                CREATED         
c970166f457c example/echo:latest "go run /echo/main.go" 9 seconds ago

## curlコマンドでアクセスし、テスト
$ curl http://localhost:9000

## 停止
$ docker container stop $(docker container ls --filter "ancestor=example/echo" -q)

終了コンテナの表示や、一括削除

$ docker container ls -a

$ docker system prune
Are you sure you want to continue? [y/N] y

docker composeの使用例

最近では、docker compose の別途installは不要

$ docker compose version
Docker Compose version v2.15.1

vi docker-compose.yml

以下で、先程のDockerfileと、main.goと同様の動作を行えます。

version: "3"
services:
  echo:
    image: example/echo:latest
    ports:
    - 9000:8080

実行、テスト、停止

## 実行
$ docker compose up -d

## curlコマンドでアクセスし、テスト
$ curl http://localhost:9000

## 停止
$ docker compose down