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

テストで使いたくて,DinD (Docker in Docker) でk8sの環境を整えた

TL;DR

事の経緯

  • Kubernetesを制御するツールを書きたい.
  • テストのために,KubernetesクラスタをホストPCに建てるの面倒だし,環境を汚したくない...
  • kind (kubernetes in docker) なるものを発見
  • しかし,アプリの開発をDocker内で行っていると,
    kindとアプリのコンテナは同一ネットワークに参加していないため,ネットワーク的に参照できないという問題が

クラスタを作るときにネットワークを指定する系のissueを見つけた

そこで,DinD (Docker in Docker)

クラスタの作成をホスト側でなく,コンテナ内のDockerで行うことによってネットワーク的に参照可能にする.

kind in DinD

FROM docker:dind
RUN apk --no-cache add curl
RUN curl -Lo /usr/local/bin/kind https://github.com/kubernetes-sigs/kind/releases/download/v0.6.1/kind-$(uname)-amd64 && chmod +x /usr/local/bin/kind

kindでクラスタ作成 (Docker Deamonの起動を待つ必要あり)

以下をコンテナ内で実行すれば環境構築完了です!

# クラスタの作成
 kind create cluster
# KUBECONFIGに,作成したクラスタのkubeconfigパスを登録
 export KUBECONFIG="$(kind get kubeconfig-path --name="kind")"

dindコンテナを立ち上げるときに詰まった点

  • 問題
    • docker run 時に,コマンドを渡すとdocker deamonが起動しなく,kindを実行できなかった
  • 解決策
    • docker run 実行後,execでコマンドを渡すようにする

まとめ

  • kubernetesの環境構築は,minikubeなりkindが,Dockerで構築するオプションを提供してくれていて非常に便利だなと思いました.お試しで気軽に建てるときには自分も重宝しようと思います.
  • DinDは初めて使いましたが,プロジェクトごとにDockerが作れるためホストから見たときのコンテナが散らからなくて管理しやすそうという印象です.
  • Kubernetesアプリとかプロバイダー作っている世の方々はテスト用のクラスタ構築とかどうされているのかなと気になりました.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VS CodeでSSH先のDockerを開発環境に使う

前置き

VS CodeRemote Developmentが追加されしばらくが経ちました。
開発環境をDockerに載せることで、別の端末やメンバー間で環境が手軽に統一できるのでメリットが大きいのですが、いくつか問題があるせいか利用してるところをあまり見たことがありません。

ホストOSにMacOSを使っているとDockerのファイルシステムの読み書きが遅くて、私はJavaScriptの開発を行うことが多いのですがnpm周りのインストールやビルドがホストOS上で実行するのと比べ物にならないぐらい遅くなります。

これはLinux上のDockerがホストOSのカーネルを共有してるのに対し、MacOSの場合はホストOSの上に軽量なDockerの実行環境を載せて動かしているせいで、Dockerのコンテナ側でファイル変更する度にホストOSに通知される2重管理になってるせいのようです。
MacOSの場合の対応はdocker-syncを入れたり、cachedオプションを設定したり、いくつかあるのですが試してみた限りだとどれも多少早くはなるのですがあまり改善されないなって印象でした。

今回の対応

根本的な解決はホストOSにLinuxを用意するしかなさそうなので別マシンで用意しました。
私の場合はちょうど昔使っていたMac miniがあったのでArch Linuxを入れてDocker用マシンにしました。
クラウド上に用意すればいつでも使える便利な環境が用意できると思います。
(そこまでするのは大掛かり過ぎるし、この記事に需要があるかは不明です)

Dockerサーバ(Arch)の準備

SSH接続できるLinux端末を用意してDockerServer APIを設定します。

vim /etc/systemd/system/docker-tcp.socket
docker-tcp.socket
[Unit]
Description=Docker Socket for the API

[Socket]
ListenStream=2375
BindIPv6Only=both
Service=docker.service

[Install]
WantedBy=sockets.target
systemctl enable docker-tcp.socket
systemctl stop docker
systemctl start docker-tcp.socket
systemctl start docker

Dockerクライアント(MacOS)の準備

DockerCLIをインストールします。
MacOS上で動作するDocker環境は要らないのでDocker for Macは入れずにHomebrewからCLIだけをインストールします。Docker for Macが入ってる場合はそのままでも問題ないです。

brew install docker

環境変数にDockerサーバの接続先を指定します。
任意のIPと上記のdocker-tcp.socketで設定したポートを指定します。

.zshrc
export DOCKER_HOST=tcp://192.168.2.2:2375

VS Codeの設定ファイルsetting.jsonDockerサーバのSSH情報を設定します。

setting.json
"docker.host": "ssh://username@192.168.2.2"

あとがき

これでVS CodeRemote DevelopmentDockerサーバー上のコンテナが表示されて接続できるようになりました。
結果的にDockerを動かす端末が別になったのですが環境を意識せずVS CodeDocker CLIをインストールするだけで環境が整うようになったのでアリなのかなと思います。

参考

https://knowledge.sakura.ad.jp/2401/
https://code.visualstudio.com/docs/remote/containers-advanced

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

docker(89) dockerでpython2, python3

機械学習でpythonを使うのにanaconda3を利用してきた。

当方の機械学習の演習・ハッカソン等でWindowsでanaconda3を使うのに、うまく使えない人がいたので資料を作成した。

Windows(M.S.)にPython3(Anaconda3)を導入する(7つの罠)
https://qiita.com/kaizen_nagoya/items/7bfd7ecdc4e8edcbd679

Windows(M.S.) にAnaconda3(python3)を 2019年版
https://qiita.com/kaizen_nagoya/items/c05c0d690fcfd3402534

pythonで 言語処理100本ノック を実行するのには、必ずしもAnacondaである必要はないかもと思った。

docker(19) 言語処理100本ノックをdockerで。python覚えるのに最適。
https://qiita.com/kaizen_nagoya/items/7e7eb7c543e0c18438c4

素のubuntuからpythonを導入する作業もしてみた。

macOS
$ docker run -v /tmp/docker:/tmp/docker -it ubuntu /bin/bash

ubuntuが起動したら

# apt update; apt -y upgrade
# apt install -y python2.7 python3.8 vim curl
# curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
# python2.7 get-pip.py
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
Collecting pip
  Downloading https://files.pythonhosted.org/packages/00/b6/9cfa56b4081ad13874b0c6f96af8ce16cfbc1cb06bedf8e9164ce5551ec1/pip-19.3.1-py2.py3-none-any.whl (1.4MB)
     |################################| 1.4MB 1.9MB/s 
Collecting setuptools
  Downloading https://files.pythonhosted.org/packages/54/28/c45d8b54c1339f9644b87663945e54a8503cfef59cf0f65b3ff5dd17cf64/setuptools-42.0.2-py2.py3-none-any.whl (583kB)
     |################################| 583kB 4.0MB/s 
Collecting wheel
  Downloading https://files.pythonhosted.org/packages/00/83/b4a77d044e78ad1a45610eb88f745be2fd2c6d658f9798a15e384b7d57c9/wheel-0.33.6-py2.py3-none-any.whl
Installing collected packages: pip, setuptools, wheel
Successfully installed pip-19.3.1 setuptools-42.0.2 wheel-0.33.6
# pip install update
Collecting update
  Downloading https://files.pythonhosted.org/packages/9f/c4/dfe8a392edd35cc635c35cd3b20df6a746aacdeb39b685d1668b56bf819b/update-0.0.1-py2.py3-none-any.whl
Collecting style==1.1.0
  Downloading https://files.pythonhosted.org/packages/4c/0b/6be2071e20c621e7beb01b86e8474c2ec344a9750ba5315886f24d6e7386/style-1.1.0-py2.py3-none-any.whl
Installing collected packages: style, update
Successfully installed style-1.1.0 update-0.0.1

# apt install  python3-pip

# pip3 install update
Collecting update
  Cache entry deserialization failed, entry ignored
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/9f/c4/dfe8a392edd35cc635c35cd3b20df6a746aacdeb39b685d1668b56bf819b/update-0.0.1-py2.py3-none-any.whl
Collecting style==1.1.0 (from update)
  Cache entry deserialization failed, entry ignored
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/4c/0b/6be2071e20c621e7beb01b86e8474c2ec344a9750ba5315886f24d6e7386/style-1.1.0-py2.py3-none-any.whl
Installing collected packages: style, update
Successfully installed style-1.1.0 update-0.0.1

# apt install  python3-pip
Reading package lists... 71%
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following additional packages will be installed:
  binutils binutils-common binutils-x86-64-linux-gnu build-essential cpp cpp-7
  dbus dh-python dirmngr dpkg-dev fakeroot g++ g++-7 gcc gcc-7 gcc-7-base
  gir1.2-glib-2.0 gnupg gnupg-l10n gnupg-utils gpg gpg-agent gpg-wks-client
  gpg-wks-server gpgconf gpgsm libalgorithm-diff-perl
  libalgorithm-diff-xs-perl libalgorithm-merge-perl libapparmor1 libasan4
  libassuan0 libatomic1 libbinutils libc-dev-bin libc6-dev libcc1-0
  libcilkrts5 libdbus-1-3 libdpkg-perl libexpat1-dev libfakeroot
  libfile-fcntllock-perl libgcc-7-dev libgdbm-compat4 libgdbm5
  libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libgomp1 libicu60
  libisl19 libitm1 libksba8 liblocale-gettext-perl liblsan0 libmpc3 libmpfr6
  libmpx2 libnpth0 libperl5.26 libpython3-dev libpython3-stdlib
  libpython3.6-dev libquadmath0 libstdc++-7-dev libtsan0 libubsan0 libxml2
  linux-libc-dev make manpages manpages-dev netbase patch perl
  perl-modules-5.26 pinentry-curses python-pip-whl python3 python3-asn1crypto
  python3-cffi-backend python3-crypto python3-cryptography python3-dbus
  python3-dev python3-distutils python3-gi python3-idna python3-keyring
  python3-keyrings.alt python3-lib2to3 python3-minimal python3-pkg-resources
  python3-secretstorage python3-setuptools python3-six python3-wheel
  python3-xdg python3.6 python3.6-dev python3.6-minimal shared-mime-info
  xdg-user-dirs
Suggested packages:
  binutils-doc cpp-doc gcc-7-locales default-dbus-session-bus
  | dbus-session-bus dbus-user-session libpam-systemd pinentry-gnome3 tor
  debian-keyring g++-multilib g++-7-multilib gcc-7-doc libstdc++6-7-dbg
  gcc-multilib autoconf automake libtool flex bison gdb gcc-doc gcc-7-multilib
  libgcc1-dbg libgomp1-dbg libitm1-dbg libatomic1-dbg libasan4-dbg
  liblsan0-dbg libtsan0-dbg libubsan0-dbg libcilkrts5-dbg libmpx2-dbg
  libquadmath0-dbg parcimonie xloadimage scdaemon glibc-doc git bzr gdbm-l10n
  libstdc++-7-doc make-doc man-browser ed diffutils-doc perl-doc
  libterm-readline-gnu-perl | libterm-readline-perl-perl pinentry-doc
  python3-doc python3-tk python3-venv python-crypto-doc
  python-cryptography-doc python3-cryptography-vectors python-dbus-doc
  python3-dbus-dbg gnome-keyring libkf5wallet-bin gir1.2-gnomekeyring-1.0
  python-secretstorage-doc python-setuptools-doc python3.6-venv python3.6-doc
  binfmt-support
The following NEW packages will be installed:
  binutils binutils-common binutils-x86-64-linux-gnu build-essential cpp cpp-7
  dbus dh-python dirmngr dpkg-dev fakeroot g++ g++-7 gcc gcc-7 gcc-7-base
  gir1.2-glib-2.0 gnupg gnupg-l10n gnupg-utils gpg gpg-agent gpg-wks-client
  gpg-wks-server gpgconf gpgsm libalgorithm-diff-perl
  libalgorithm-diff-xs-perl libalgorithm-merge-perl libapparmor1 libasan4
  libassuan0 libatomic1 libbinutils libc-dev-bin libc6-dev libcc1-0
  libcilkrts5 libdbus-1-3 libdpkg-perl libexpat1-dev libfakeroot
  libfile-fcntllock-perl libgcc-7-dev libgdbm-compat4 libgdbm5
  libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libgomp1 libicu60
  libisl19 libitm1 libksba8 liblocale-gettext-perl liblsan0 libmpc3 libmpfr6
  libmpx2 libnpth0 libperl5.26 libpython3-dev libpython3-stdlib
  libpython3.6-dev libquadmath0 libstdc++-7-dev libtsan0 libubsan0 libxml2
  linux-libc-dev make manpages manpages-dev netbase patch perl
  perl-modules-5.26 pinentry-curses python-pip-whl python3 python3-asn1crypto
  python3-cffi-backend python3-crypto python3-cryptography python3-dbus
  python3-dev python3-distutils python3-gi python3-idna python3-keyring
  python3-keyrings.alt python3-lib2to3 python3-minimal python3-pip
  python3-pkg-resources python3-secretstorage python3-setuptools python3-six
  python3-wheel python3-xdg python3.6 python3.6-dev python3.6-minimal
  shared-mime-info xdg-user-dirs
0 upgraded, 105 newly installed, 0 to remove and 0 not upgraded.
Need to get 109 MB of archives.
After this operation, 356 MB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu bionic/main amd64 liblocale-gettext-perl amd64 1.07-3build2 [16.6 kB]
...

# # pip3 install update
Collecting update
  Cache entry deserialization failed, entry ignored
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/9f/c4/dfe8a392edd35cc635c35cd3b20df6a746aacdeb39b685d1668b56bf819b/update-0.0.1-py2.py3-none-any.whl
Collecting style==1.1.0 (from update)
  Cache entry deserialization failed, entry ignored
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/4c/0b/6be2071e20c621e7beb01b86e8474c2ec344a9750ba5315886f24d6e7386/style-1.1.0-py2.py3-none-any.whl
Installing collected packages: style, update
Successfully installed style-1.1.0 update-0.0.1

参考資料(reference)

pip入門
https://qiita.com/kaizen_nagoya/items/50d8be773d6d59a58153

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

Apache Ambari でクラスタ構築

こちらの記事の続きになります。
Apache Ambari を Docker でインストール

Ambari サーバーのコンテナにログインしているものとします。

事前準備

ssh のインストール

Ambari では各ノードへのアクセス時に ssh コマンドを使用するため、インストールしておきます。
(Docker イメージ作成時にインストールしておいた方がよかった)

yum -y install openssh-server
yum -y install openssh-clients
# パスワードなしで接続できるようにしておきます
ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key
ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key
ssh-keygen -q -N "" -t rsa -f /root/.ssh/id_rsa
cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
# ssh サービスを起動します
systemctl start sshd.service
https://[hostname]:8440 is not reachable の回避

このままセットアップを続けていくと、Ambari でのクラスタの登録時に以下のようなエラーに出くわす可能性があります。

INFO 2019-MM-dd 02:07:50,730 NetUtil.py:70 - Connecting to https://host01.blueskyarea:8440/ca
ERROR 2019-MM-dd 02:07:50,735 NetUtil.py:96 - EOF occurred in violation of protocol (_ssl.c:618)
ERROR 2019-MM-dd 02:07:50,735 NetUtil.py:97 - SSLError: Failed to connect. Please check openssl library versions. 
Refer to: https://bugzilla.redhat.com/show_bug.cgi?id=1022468 for more details.
WARNING 2019-MM-dd 02:07:50,736 NetUtil.py:124 - Server at https://host01.blueskyarea:8440 is not reachable, sleeping for 10 seconds...

この状況を避けるため、以下の対応しておきます。
certificate validation の無効化

vi /etc/python/cert-verification.cfg
# -> verify=diable

2. ambari-agent のコンフィグ設定

vi /etc/ambari-agent/conf/ambari-agent.ini
# -> [security]
# force_https_protocol=PROTOCOL_TLSv1_2

クラスタ構築を開始する

ambari サーバースタート

ambari サーバーをスタートします。

[root@host01 /]# ambari-server start
Using python  /usr/bin/python
Starting ambari-server
Ambari Server running with administrator privileges.
Organizing resource files at /var/lib/ambari-server/resources...
(中略)
サインイン & Install Wizard 開始

起動後、http://[hostname or ipaddress]:8080/ にアクセスし、サインインします。
ambari-login.PNG

構築を開始します。"Launch Install Wizard" をクリックします。
ambari-start.PNG

クラスタ命名

好きなクラスタ名を付けます。(ここでは、MyCluster)
ambari-cluster-name.PNG

HDPバージョン選択

せっかくなので、ここで選べる最新版を選択します。
※そもそもインストールしたHDPのバージョンがこの執筆時点での最新ではないです
パブリックリポジトリからソフトウェアをインストールするため、"Use Public Repository"が選択されている状態で次に進みます。

ambari-select-version.PNG

インストール対象サーバーのホスト名および、秘密鍵の設定

とりあえず、1台構成でチャレンジしてみたいと思います。
host01.blueskyarea
秘密鍵は直接貼り付けています。
ambari-install-option.PNG

インストールが開始されたようです。
ambari-installing.PNG

しかしながら、失敗してしまいました。
ambari-confirm-failure.PNG

エラー対応

ambari-agent のログを見てみると、サーバーへの接続に失敗しているようです。

# cat /var/log/ambari-agent/ambari-agent.log
->
INFO 2019-MM-dd 13:14:10,449 NetUtil.py:70 - Connecting to https://host01.blueskyarea:8440/ca
ERROR 2019-MM-dd 13:14:10,466 NetUtil.py:96 - EOF occurred in violation of protocol (_ssl.c:618)
ERROR 2019-MM-dd 13:14:10,466 NetUtil.py:97 - SSLError: Failed to connect. Please check openssl library versions. 
Refer to: https://bugzilla.redhat.com/show_bug.cgi?id=1022468 for more details.
WARNING 2019-MM-dd 13:14:10,466 NetUtil.py:124 - Server at https://host01.blueskyarea:8440 is not reachable, sleeping for 10 seconds...

このエラーへの対策は、先に実施した ”certificate validation の無効化” で行ったはずですが、それだけでは足りなかったようです。
/etc/ambari-agent/conf/ambari-agent.ini を開き、[security]の項に以下の1行を追加します。

[security]
(中略)
force_https_protocol=PROTOCOL_TLSv1_2

ambari-agent を再起動します。

ambari-agent restart
Restarting ambari-agent
(中略)

Retry Failed ボタンをクリックしてリトライさせると、今度は Success が出ました。
ambari-confirm-success.PNG

サービスの選択

インストールしたいサービスを選択します。
HDFS と YARN(+MapReduce2)だけ選択しましたが、Zookeeper と Ambari Metrics も必須ということで強制的に選択されました。

Master のアサイン

これらのサービスの Master が1つのホスト(正確には Docker のコンテナ)にインストールされることになります。
※個人のローカルPCには負担が大きそうです
ambari-masters.PNG

Slave and Client のアサイン

Slave と Client も1つのホストにインストールされることになります。
※ますます個人のローカルPCには負担が大きそうです
ambari-slave.PNG

Customize Service

サービスによって、admin 用のパスワードの設定を要求されますので、適当に設定します。
ambari-customize.PNG

Review

Review 画面が表示されますが、特に問題はなければ、Deploy ボタンをクリックします。
admin-review.PNG

Install, Start and Test

各サービスのインストールが開始されました。
ambari-install-service.PNG

完了までに一時間ほど掛かりました。
ambari-install-service2.PNG
ambari-summary.PNG
ダッシュボードが起動されました。
ambari-dashboard2.PNG

HDFS へのアクセス

HDFS にアクセスしてみます。

[root@host01 ~]# hdfs dfs -ls /
Found 7 items
drwxrwxrwx   - yarn   hadoop          0 2019-12-29 14:17 /app-logs
drwxr-xr-x   - yarn   hadoop          0 2019-12-29 14:05 /ats
drwxr-xr-x   - hdfs   hdfs            0 2019-12-29 14:06 /hdp
drwxr-xr-x   - mapred hdfs            0 2019-12-29 14:05 /mapred
drwxrwxrwx   - mapred hadoop          0 2019-12-29 14:07 /mr-history
drwxrwxrwx   - hdfs   hdfs            0 2019-12-29 14:08 /tmp
drwxr-xr-x   - hdfs   hdfs            0 2019-12-29 14:04 /user

なんとか動作してくれているようです。
手間のかかる HDFS のインストールを Ambari を使用することで簡単に実現できました。
と言いたいところですが、Ambari のインストールからセットアップまでが個人的にはエラー続出で大変でした。
あと、PCスペックの懸念から、最小限のサービスしか起動していません。
本来の目的である HiveやSparkなどを試すことになった場合に果たして動作可能であるのか、引き続き試行していくつもりです。

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

DockerHubとGitHubを連携させる2019/12版

やりたいこと

  • DockerHubとGitHubの連携

なぜこの記事を書いたか

  • ネットに転がっている手順だと、「DockerHubのページの右上の”Create”から〜〜〜」というものしかなかった。
  • 自分の環境だとWebでCreate Repositoryできなかった。
  • 試しながらやりたいことができたので今後の備忘のために残しておく。

スクリーンショット 2019-12-30 16.54.03.png
こんな感じでWebからCreateできそうな雰囲気無し。

※DockerHubとGitHubの連携自体の詳細については参考URLのものを参考にしています。

環境

  • macOS Catalina
  • Chrome 79.0.3945.88

事前準備

やったこと

  • DockerHubのAccout SettingsのLinked AccountsからGitHubアカウントを連携
  • Dockerfile作成
  • Dockerfileからimageをbuild
  • DockerHubにimageをpush
  • GitHubにリポジトリ作成
  • GitHubにDockerfileをpush
  • DockerHubのリポジトリのページ(以下のURLの場所)の"Builds"を開く
    • https://hub.docker.com/repository/docker/[アカウント名]/[image名]
  • GitHubを選択
  • ブランチとかDockerfileの場所とかを設定
    Source Type、Source、Docker Tag はあとから変更できない。

スクリーンショット 2019-12-30 16.45.20.png

確認

GitHubに変更をpushしてDockerHubが自動でBuildすることを確認する。
ビルド結果がSuccessになっていれば設定完了。

スクリーンショット 2019-12-30 16.51.43.png

参考URL

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

Dockerで始めるStackstorm再入門3/3(pythonスクリプトとmockを使ったテストコードを書く)

本記事はDockerで始めるStackstorm再入門1/3(環境構築からOrquestaで書いたWorkflowの結果をslackに通知する)の第3部です。

0. 目次と構成図

  • 1. Actionの実行スクリプトをshellscriptとpythonで書く場合の違い
  • 2. pythonで書いたActionの実行スクリプトの書き方
  • 3. mockでActionの実行スクリプトをテストする
  • 4. Workflowで引数や結果に応じて分岐させる書き方(python-script編)

本記事では第2部で予告していたとおり、pythonでActionの実行スクリプト、より柔軟なworkflowの書き方についてお話していきます。

shellscriptでリモートリポジトリのステータスの確認とコンテナの再立ち上げを行いましたが、今度はそれをpythonでおこなってみます。
pythonでActionの実行スクリプトを書くと、shellscriptに比べてActionの返却値(return value)を柔軟に定義することができたり、mockを使ってテストコードも書くことができるので、よりきめ細かくActionを書くことができます。

Dockerで始めるStackstorm再入門2/3(条件分岐させるWorkflowと定期実行させるRuleの書き方)より

本記事でお話する構成図

Screenshot from 2019-12-16 22-01-15.png
※1
Stackstormコンテナ内から同コンテナ外にあるアプリ(flask/nginx)コンテナに接続するために、
ホストのDockerデーモンのソケットファイル(var/run/docker.sock)をマウントさせています。

※2
Gtihubからリポジトリをcloneする際、ssh鍵やパスワードを使わずにおこなうべく、Personal access tokensを使いました。
トークンの取得方法は下記の記事をご参照ください。
私はrepoをスコープとしてトークンを取得しました。

参考
Creating a personal access token for the command line - GitHub Help

1. Actionの実行スクリプトをshellscriptとpythonで書く場合の違い

Actionの成否判定として使えるものがshellscriptではリターンコードとstdout/stderrですが、pythonでは、加えてrunメソッドの任意の返却値も使えます。
※シェルスクリプトでいうreturn_codeはpythonスクリプトでは、exit_codeと表記されています。

  • local-shell-script (shellscript)
    • return_code(0はActionのsuceeded, 0以外がfailed)
    • stdout/stderr
  • python-script
    • exit_code(0はActionのsuceeded, 0以外がfailed)
    • stdout/stderr
    • runメソッドの返却値

2. pythonで書いたActionの実行スクリプトの書き方

コードをひととおり確認したい方はGithubをご参照ください。
mydemo_pack/actions/scripts/git_status.py

おおむね書き方はpythonでプログラムを書く場合と同じですが、いくつかStackstorm独自の点があるので、そこだけ共有します。

  • エントリーポイントはrunメソッド
  • Actionの結果はstatusexit_codeの他に任意のrunメソッドの返却値で制御可能
    • mydemo_packではdict型のresultを用意(write_resultメソッドで定義)
  • __init__メソッドで/opt/stackstorm/configs/*.yamlを読み込み、self.configとして保持
    • mydemo_packでは用意していないので実質不要

参考:
- Actions — StackStorm 3.1.0 documentation

/opt/stackstorm/packs/mydemo_pack/actions/scripts/git_status.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import re
import traceback
from st2actions.runners.pythonrunner import Action

from common_mydemo import Common

class GitStatusAction(Action):
    """Action class for st2"""

    def __init__(self, config):
        self.config = config
        self.result = {}
        for r in [
            "command", "branch", "expected", "bool", "stdout", "stderr"        
        ]:
            self.result[r] = None 

        self.common = Common()
        return
    ()


    def check_stdout(self, branch, expected, stdout):
        success = False
        ptn = self._set_regex(branch, expected)
        for line in stdout:
            if not success and ptn.search(line):
                success = True
            else:
                pass

        return success, stdout
    ()


    def write_result(self, command, branch, expected, bool, stdout, stderr):
        self.result.update({
            "command": command,
            "branch": branch,
            "expected": expected,
            "bool": bool,
            "stdout": self._to_str(stdout),
            "stderr": self._to_str(stderr),
        })

        return self.result


    def run(self, working_dir, branch, expected):
        """ Entrypoint for st2 """

        bool = False
        command = ''
        stdout = []
        stderr = []

        try:
            # git fetchのコマンドを生成
            cmd = self._git_fetch(working_dir, branch)
            # git fetchを実行するが、成否だけ知りたいのでself.common.execute_commandメソッドの返却値のstdout, stderrは_とし、明示的に不要とした
            bool, _, _ = self.common.execute_command(cmd)
            if bool:
                # git statusのコマンドを生成
                command = self.set_command(working_dir)
                # git statusを実行し、bool==Trueである場合、stdoutの結果を後続のself.check_stdoutで判定
                bool, stdout, stderr = self.common.execute_command(command)
                if bool:
                    bool, stdout = self.check_stdout(branch, expected, stdout)
                    self.result = self.write_result(command, branch, expected, bool, stdout, stderr)
        except:
            # try文のなかでエラーを引いた場合、tracebackモジュールでキャッチし、stderrに代入
            stderr = traceback.format_exc()
            self.result = self.write_result(command, branch, expected, bool, stdout, stderr)

        #finally:
        #    self.result = self.write_result(command, branch, expected, bool, stdout, stderr)


        return self.result

コマンドの実行はsubprocessモジュールを使ってactions/scripts/common_mydemo.pyとしてまとめています。
こちらのpythonスクリプトは任意のActionのpythonスクリプトが内部的に呼ぶのでStackstormが認識する必要はないため、/opt/stackstorm/packs/mydemo_pack/*.yamlのメタファイルを別途用意する必要もないです。

/opt/stackstorm/packs/mydemo_pack/actions/scripts/common_mydemo.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import traceback
from subprocess import Popen, PIPE


class Common:
    """ Common for mydemo """

    def __init__(self):
        pass

    def execute_command(self, command):
        bool = False
        try:
            stdout, stderr = Popen(
                command, shell=True, stdout=PIPE, stderr=PIPE
            ).communicate()
            stdout = stdout.splitlines()
            stderr = stderr.splitlines()
            bool = True
        except:
            stdout = None
            stderr = traceback.format_exc()

        return bool, stdout, stderr

3. mockでActionの実行スクリプトをテストする

サンプルのmydemo_packが手元にある方は/opt/stackstorm/packs/mydemo_pack/tests/の下にテストコードなどが用意されていることが確認できるはずです。

root@$HOSTNAME:/# tree -L 3 /opt/stackstorm/packs/mydemo_pack/tests/
/opt/stackstorm/packs/mydemo_pack/tests/
├── fixtures              # runメソッドに渡す引数を定義
│   ├── git_status.yaml
│   └── rebuild_app.yaml
├── git_status       # mockオブジェクトに渡すsubmoduleメソッドの値のstdout/stderrを定義
│   └── response.yaml
├── rebuild_app       # mockオブジェクトに渡すsubmoduleメソッドの値のstdout/stderrを定義
│   └── response.yaml
├── test_git_status.py  # テストコード
└── test_rebuild_app.py   # 同上

3 directories, 7 files

mockを使わず、そのままテストコードからActionの実行スクリプトを走らせてみる

  • 対象のテストコードは/opt/stackstorm/packs/mydemo_pack/tests/test_git_status.py
  • テストはStackstormが提供するst2tests.base.BaseActionTestCaseを継承してTestGitStatusActionインスタンスを生成して実行
  • 生成したActionインスタンスの直下にaction_cls = $CLASSを書く
    • e.g. action_cls = GitStatusAction
  • runメソッドに渡す引数は/opt/stackstorm/packs/mydemo_pack/tests/fixtures/内の任意のyamlで定義
    • test_git_status.pyの場合は/opt/stackstorm/packs/mydemo_pack/tests/fixtures/git_status.yaml
  • テストの対象のActionインスタンスは以下のように生成
    • action = self.get_action_instance()

テストの実行コマンド

  • st2-run-pack-tests -p \$PACKPATH
    • /opt/stackstorm/packs/tests/*.pyのテストコードを実行
root@$HOSTNAME:/# st2-run-pack-tests -p /opt/stackstorm/packs/mydemo_pack
  • st2-run-pack-tests -p \$PACKPATH -f \$TESTFILENAME
    • /opt/stackstorm/packs/tests/\$TESTFILENAMEを実行
root@$HOSTNAME:/# st2-run-pack-tests -p /opt/stackstorm/packs/mydemo_pack \
> -f test_git_status
  • st2-run-pack-tests -p \$PACKPATH -f \$TESTFILENAME:$METHOD
    • /opt/stackstorm/packs/tests/\$TESTFILENAMEの$METHODを実行
root@$HOSTNAME:/# st2-run-pack-tests -p /opt/stackstorm/packs/mydemo_pack \
> -f test_git_status:TestGitStatusAction.test00_no_mock_st2

参考:
Pack Testing — StackStorm 3.1.0 documentation Instantiating and obtaining class instances

テストコード

/opt/stackstorm/packs/mydemo_pack/tests/test_git_status.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

from st2tests.base import BaseActionTestCase
from mock import MagicMock, patch
import json
import yaml
import os
import sys
import re

BASE_DIR = '/opt/stackstorm/packs/mydemo_pack'
sys.path.append(BASE_DIR)
sys.path.append(BASE_DIR + '/actions/scripts')
sys.path.append('/opt/stackstorm/virtualenvs/mydemo_pack/lib/python2.7/site-packages')
sys.path.append('/opt/stackstorm/st2/lib/python2.7/site-packages')

input_file = "git_status.yaml"
res_file = BASE_DIR + "/tests/git_status/response.yaml"

from git_status import GitStatusAction

class TestGitStatusAction(BaseActionTestCase):

    # テスト対象のActionの実行スクリプトのclass名を書く
    action_cls = GitStatusAction

    def test00_no_mock_st2(self):
        input = yaml.load(
            self.get_fixture_content(input_file), Loader=yaml.FullLoader
        )

        # Actionインスタンスを生成
        action = self.get_action_instance()
        # runメソッドを実行
        result = action.run(**input)
        print('result: {r}'.format(r=result))

        self.assertEquals(len(result), 6)
        self.assertEqual(result["bool"], True)

runメソッドの引数が書かれた\$PACK/tests/fixtures/*.yaml

/opt/stackstorm/packs/mydemo_pack/tests/fixtures/git_status.yaml
"working_dir": "/usr/src/app/flask-docker"
"branch": "devel-views"
"expected": "up_to_date"

mockを使って、任意のメソッドの返却値を書き換える

/opt/stackstorm/packs/mydemo_pack/tests/test_git_status.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

from st2tests.base import BaseActionTestCase
from mock import MagicMock, patch
import json
import yaml
import os
import sys
import re

BASE_DIR = '/opt/stackstorm/packs/mydemo_pack'
sys.path.append(BASE_DIR)
sys.path.append(BASE_DIR + '/actions/scripts')
sys.path.append('/opt/stackstorm/virtualenvs/mydemo_pack/lib/python2.7/site-packages')
sys.path.append('/opt/stackstorm/st2/lib/python2.7/site-packages')

input_file = "git_status.yaml"
res_file = BASE_DIR + "/tests/git_status/response.yaml"

from git_status import GitStatusAction

class TestGitStatusAction(BaseActionTestCase):

    action_cls = GitStatusAction

    ()

    # mockオブジェクトで書き換えるメソッド
    @patch("common_mydemo.Common.execute_command")
    def test01_mock_st2(self, execute):
        input = yaml.load(
            self.get_fixture_content(input_file), Loader=yaml.FullLoader
        )

        def _execute_command(_cmd):
            bool = True
            #stdout = ["Your branch is up-to-date with 'origin/devel-views'"]
            #stderr = [""]

            # yamlからstdout/stderrの値を取得
            res = yaml.load(open(res_file), Loader=yaml.FullLoader)
            stdout = res["succeeded"]["up_to_date"]["stdout"]
            stderr = res["succeeded"]["up_to_date"]["stderr"]

            return bool, stdout, stderr

        # _execute_commandメソッドの値をside_effectに代入
        execute.side_effect = _execute_command

        # actionインスタンスを生成し、runメソッドを実行
        action = self.get_action_instance()
        result = action.run(**input)
        print('result: {r}'.format(r=result))

        self.assertEquals(len(result), 6)
        self.assertEqual(result["bool"], True)
        self.assertEqual(
            result["command"], 
            "cd /usr/src/app/flask-docker && sudo git status"
        )
        ans = yaml.load(open(res_file), Loader=yaml.FullLoader)
        self.assertEqual(
            result["stdout"], 
            '\n'.join(map(str, ans["succeeded"]["up_to_date"]["stdout"]))
        )
        self.assertEqual(
            result["stderr"], 
            '\n'.join(map(str, ans["succeeded"]["up_to_date"]["stderr"]))
        )

    @patch("common_mydemo.Common.execute_command")
    def test02_mock_st2_not_up_to_date(self, execute):
        input = yaml.load(
            self.get_fixture_content(input_file), Loader=yaml.FullLoader
        )

        # runメソッドの引数の値はテストコードのなかで書き換える
        input.update({
            'expected': 'not_up_to_date'
        })

        def _execute_command(_cmd):
            bool = True
            #stdout = ["Your branch is up-to-date with 'origin/devel-views'"]
            #stderr = [""]

            res = yaml.load(open(res_file), Loader=yaml.FullLoader)
            stdout = res["succeeded"]["not_up_to_date"]["stdout"]
            stderr = res["succeeded"]["not_up_to_date"]["stderr"]

            return bool, stdout, stderr

        execute.side_effect = _execute_command

        action = self.get_action_instance()
        result = action.run(**input)
        print('result: {r}'.format(r=result))

        self.assertEquals(len(result), 6)
        self.assertEqual(result["bool"], True)
        self.assertEqual(
            result["command"], 
            "cd /usr/src/app/flask-docker && sudo git status"
        )
        ans = yaml.load(open(res_file), Loader=yaml.FullLoader)
        self.assertEqual(
            result["stdout"], 
            '\n'.join(map(str, ans["succeeded"]["not_up_to_date"]["stdout"]))
        )
        self.assertEqual(
            result["stderr"], 
            '\n'.join(map(str, ans["succeeded"]["not_up_to_date"]["stderr"]))
        )

※異常処理の動作確認

def _execute_command(_cmd):
  raise Exception("err_message")

execute.side_effect = _execute_command

/opt/stackstorm/packs/mydemo_pack/tests/git_status/response.yaml
"succeeded":
  "up_to_date":
    "stdout":
      - "'On branch devel-views'"
      - "Your branch is up-to-date with 'origin/devel-views'."
      - "''"
      - "'nothing to commit, working directory clean'"
    "stderr":
      - ""
  "not_up_to_date":
    "stdout":
      - "hoge"
    "stderr":
      - ""
"failed":
  "up_to_date":
    "stdout":
      - ""
    "stderr":
      - ""
  "not_up_to_date":
    "stdout":
      - ""
    "stderr":
      - "hoge"

4. Workflowで引数や結果に応じて分岐させる書き方(python-script編)

  • 基本的には引数の値に応じて分岐させるときと同じだが、boolean値やintegerのときはstringに型変換する必要がある
    • 値そのものはresult().$RETURN_VALUEで取得できる
next:
  - when: <% str(result().result.bool) = "true" %>
  • Actionの返却値を次のAction以降で使うために変数化(publish)する方法
next:
  - when: <% succeeded() and str(result().result.bool) = "true" and (ctx().expected = 'up_to_date') %>
    do: last
    publish:
      - bool: <% result().result.bool %>
      - commnad: <% result().result.command %>
      - expected: <% result().result.expected %>
      - stdout: <% result().result.stdout %>
      - stderr: <% result().result.stderr %>
  • 値を複数行に分けて書く方法
key: |-
   val1 <% result().result.val1 %>
   val2 <% result().result.val2 %>

Workflow全体

/opt/stackstorm/packs/mydemo_pack/actions/workflows/poll-repo-python.yaml
version: 1.0

description: poll remote repo

input:
  - working_dir
  - branch
  - expected
  - ptns
  - timeout

output:
  - failed: <% ctx().failed %>
  - action_name: <% ctx().action_name %>

tasks:
  init: # failedフラグを初期化(False)とするだけのAction
    action: core.noop
    next:
    - publish:
        - failed: False
        - action_name: 'poll-repo-python'
      do: git_status_before_merged

  git_status_before_merged:   # ローカルリポジトリは最新であることを想定してgit status
    action: mydemo_pack.git_status_python
    input:
      working_dir: <% ctx().working_dir %>
      branch: <% ctx().branch %>
      expected: <% ctx().expected %>   #デフォルト値は'up_to_date'
      timeout: <% ctx().timeout %>
    next:
     # ローカルリポジトリは最新であることが確認できた
      - when: <% succeeded() and str(result().result.bool) = "true" and (ctx().expected = 'up_to_date') %>
        do: last
        publish:
          - action_result: |-
              [bool] <% result().result.bool %>
              [commnad] <% result().result.command %>
              [expected] <% result().result.expected %>
              [stdout] <% result().result.stdout %>
              [stderr] <% result().result.stderr %>
    # ローカルリポジトリは最新ではないことが確認できた
      - when: <% succeeded() and str(result().result.bool) = "true" and (ctx().expected = 'not_up_to_date') %>
        do: git_merge
        publish:
          - failed: False
          - action_result: |-
              [bool] <% result().result.bool %>
              [commnad] <% result().result.command %>
              [expected] <% result().result.expected %>
              [stdout] <% result().result.stdout %>
              [stderr] <% result().result.stderr %>
      # ローカルリポジトリは最新であることが確認できなかった
      - when: <% succeeded() and str(result().result.bool) = "false" and (ctx().expected = 'up_to_date') %>
        do: git_status_before_merged
        publish:
          - failed: True
          - expected: 'not_up_to_date'
      - when: <% failed() %>
        do: post_msg
        publish:
          - failed: True
          - action_result: |-
              [bool] <% result().result.bool %>
              [commnad] <% result().result.command %>
              [expected] <% result().result.expected %>
              [stdout] <% result().result.stdout %>
              [stderr] <% result().result.stderr %>

  git_merge:   # git_status_before_mergedで、ローカルリポジトリは最新ではないことが確認出来きた場合のみ実行
    action: core.local
    input:
      cmd: sudo git merge origin/<% ctx().branch %>
      cwd: <% ctx().working_dir %>
      timeout: <% ctx().timeout %>
    next:
      - when: <% succeeded() %>
        do: git_status_after_merged
        publish:
          - expected: 'up_to_date'
      - when: <% failed() %>
        do: post_msg
        publish:
          - failed: True
          - action_result: |-
              [result]
              <% result() %>
  git_status_after_merged: # ローカルリポジトリは最新であるとしてgit_status_after_mergedを実行
    action: mydemo_pack.git_status_python
    input:
      working_dir: <% ctx().working_dir %>
      branch: <% ctx().branch %>
      expected: 'up_to_date'
      timeout: <% ctx().timeout %>
    next:
      - when: <% succeeded() and str(result().result.bool) = "true" %>
        do: rebuild_app
        publish:
          - action_result: |-
              [bool] <% result().result.bool %>
              [commnad] <% result().result.command %>
              [expected] <% result().result.expected %>
              [stdout] <% result().result.stdout %>
              [stderr] <% result().result.stderr %>
      - when: <% failed() %>
        do: post_msg
        publish:
          - failed: True
          - action_result: |-
              [bool] <% result().result.bool %>
              [commnad] <% result().result.command %>
              [expected] <% result().result.expected %>
              [stdout] <% result().result.stdout %>
              [stderr] <% result().result.stderr %>


  rebuild_app: # コンテナが既に立ち上がっていれば、停止/削除し、再立ち上げを図る
    action: mydemo_pack.rebuild_app_python
    input:
      working_dir: <% ctx().working_dir %>
      ptns: <% ctx().ptns %>
      timeout: <% ctx().timeout %>
    next:
      - when: <% succeeded() and str(result().result.bool) = "true" %>
        do: post_msg
        publish:
          - action_result: |-
              [bool] <% result().result.bool %>
              [commnad] <% result().result.command %>
              [stdout] <% result().result.stdout %>
              [stderr] <% result().result.stderr %>
      - when: <% failed() or str(result().result.bool) = "false" %>
        do: post_msg
        publish:
          - failed: True
          - action_result: |-
              [bool] <% result().result.bool %>
              [commnad] <% result().result.command %>
              [stdout] <% result().result.stdout %>
              [stderr] <% result().result.stderr %>

  post_msg:
    action: slack.post_message
    input:
      message: |-
        [action_name] 
        <% ctx().action_name %>
        [failed]
        <% ctx().failed %>
        [action_result]
        <% ctx().action_result %>
    next:
      - do: last

  last:
    action: core.noop
    next:
      - when: <% ctx().failed %>
        do: fail

参考

P.S. Twitterもやってるのでフォローしていただけると泣いて喜びます:)

@gkzvoice

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

DockerとSquidを用いて、テスト用の認証プロキシサーバを構築する

ここ数回、UiPath製品群をプロキシ環境内で運用する手順を整理しています。

残るは認証プロキシーを越える件を整理したいのですが、認証プロキシっていわゆる業務環境やお客さま環境にしかなかったりして、テストがしづらいです。ということで手元に「認証プロキシ」があればなーってことで、今回は「認証プロキシ」自体の構築手順を整理しました。

具体的には、

  • 仮想マシンを立ち上げるための Vagrant を用いてLinux環境を構築
  • そのLinuxにDockerをインストール
  • そのDocker上に、OSSのプロキシサーバ「Squid」のコンテナを構築
  • そのコンテナ上で、SquidのForward Proxy の機能で認証プロキシを設定

となります。書くと長いですが、Vagrantで構築はワンタッチです :-) 便利な世の中です。

Vagrant_Docker_squid.png

この記事の対象の方

  • 顧客環境でテストができないので、手元に認証プロキシサーバが欲しいヒト
  • 自宅のネット環境を認証プロキシ経由にしたいヒト :-)

あ、ちなみにコレで構築した認証プロキシは性能やセキュリティなどは考慮しておりませんのでご注意ください1。あくまでテスト環境ということで。

前提

一応 MacのVagrantで構築しますが、最終的にはDockerなので環境はなんでもダイジョブかと。また最後に、Dockerでなく通常のUbuntu Linuxへの構築手順も記載しておきます。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.2
BuildVersion:   19C57

$ vagrant --version
Vagrant 2.0.4
$

Vagrant上のDockerにSquidのコンテナをたてる

GitHubから、Vagrantfileなど設定情報を取得

$ git clone https://github.com/masatomix/environment_template.git
$ cd environment_template
$ cat Vagrantfile
..
..
config.vm.provision :shell, :path => "./script/provision_docker_squid.sh",:privileged   => false
$

↑デフォルトではSquidのコンテナ構築は無効にしてあるので、アンコメントして有効にします。

VagrantでUbuntu Linux環境構築、Dockerその他もインストール。その後 sshログイン

$ vagrant up
... いろいろインストールで結構時間がかかる、、、
$ vagrant ssh

Welcome to Ubuntu 16.04.6 LTS (GNU/Linux 4.4.0-154-generic x86_64)

*** System restart required ***
Last login: Sun Dec 29 21:15:59 2019 from 10.0.2.2

vagrant@ubuntu-xenial:~$

IPアドレス 確認

構築したLinuxのアドレスが、プロキシサーバのIPアドレスになります。下記の192.168.xx.xx にあるアドレスをメモっておきましょう。

vagrant@ubuntu-xenial:~$ /sbin/ifconfig
..
enp0s8    Link encap:イーサネット  ハードウェアアドレス xxx
          inetアドレス:192.168.xx.xx  ブロードキャスト:192.168.xx.255  マスク:255.255.255.0

起動確認・停止・起動

構築が正しく行われていると、Docker上のSquidはすでに起動しているはずです。

vagrant@ubuntu-xenial:~$ cd /vagrant/squid/
vagrant@ubuntu-xenial:/vagrant/squid$ 

起動確認
vagrant@ubuntu-xenial:/vagrant/squid$ sudo docker-compose ps
           Name                    Command         State           Ports
---------------------------------------------------------------------------------
squid_squid_1_d48da617dc4d   /sbin/entrypoint.sh   Up      0.0.0.0:3128->3128/tcp ← Stateが'Up'ならOK


停止
vagrant@ubuntu-xenial:/vagrant/squid$ sudo docker-compse down


起動
vagrant@ubuntu-xenial:/vagrant/squid$ sudo docker-compse up -d

疎通確認しましょう。インターネットオプションにプロキシとして上記の http://[IPアドレス]:3128 を指定して、ネットへアクセスしてみましょう。
プロキシ認証画面がでればOKです。 あらかじめおいてあるユーザ情報ファイル.htpasswdにはID/Passが

user01/user01

で登録されているので、プロキシ認証画面に入力してみてください。

p01.png

もしくは curlなどで、

$ curl https://qiita.com/ -x http://user01:user01@192.168.xx.xx:3128 -I

とうってみて、HTTP/2 200 などの応答が返ってくればOKです。

構築した環境の稼働確認は以上です。

ユーザの作成

今回はテスト環境なのでユーザの追加などは不要かもしれませんが、認証プロキシのユーザを追加したいときは以下のコマンドで。

vagrant@ubuntu-xenial:/vagrant/squid$ sudo htpasswd /vagrant/squid/.htpasswd user02

構築した環境の設定

Docker上に構築したSquidコンテナの設定ファイル xxや、Squid自体の設定ファイルsquid.confは以下の通りに設定してあります。

まずはdocker-compose.yml

vagrant@ubuntu-xenial:~$ cat /vagrant/squid/docker-compose.yml
docker-compose.yml
version: '2'
services:
  squid:
    image: sameersbn/squid:3.5.27-2
    ports:
      - "3128:3128"
    volumes:
      - /srv/docker/squid/cache:/var/spool/squid
      - ./squid.conf:/etc/squid/squid.conf
      - ./.htpasswd:/etc/squid/.htpasswd
    restart: always

つづいてsquid.conf

vagrant@ubuntu-xenial:~$ cat /vagrant/squid/squid.conf
squid.conf
acl SSL_ports port 443
acl Safe_ports port 80          # http
acl Safe_ports port 21          # ftp
acl Safe_ports port 443         # https
acl Safe_ports port 70          # gopher
acl Safe_ports port 210         # wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280         # http-mgmt
acl Safe_ports port 488         # gss-http
acl Safe_ports port 591         # filemaker
acl Safe_ports port 777         # multiling http
acl CONNECT method CONNECT

http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager
#http_access allow all
#http_access allow localhost
#http_access deny all

http_port 3128

coredump_dir /var/spool/squid
refresh_pattern ^ftp:           1440    20%     10080
refresh_pattern ^gopher:        1440    0%      1440
refresh_pattern -i (/cgi-bin/|\?) 0     0%      0
refresh_pattern (Release|Packages(.gz)*)$      0       20%     2880
refresh_pattern .               0       20%     4320

auth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid/.htpasswd
auth_param basic children 5
auth_param basic realm Squid Basic Authentication (user:[user01]/pass:[user01])
auth_param basic credentialsttl 2 hours
auth_param basic casesensitive off
acl pauth proxy_auth REQUIRED

http_access allow pauth

no_cache deny all

forwarded_for off
request_header_access Referer deny all
request_header_access X-FORWARDED-FOR deny all
request_header_access Via deny all
request_header_access Cache-Control deny all

logformat mycombined   %>a %[ui %[un [%tl] "%rm %>ru HTTP/%rv" %>Hs %<st "%{Referer}>h" "%{User-Agent}>h" %Ss:%Sh
access_log daemon:/var/log/squid/access.log mycombined
$

おつかれさまでした。

参考:Linux上にインストール

コンテナでなくLinuxへ直接Squidを入れたい場合の手順を記しておきます。
Ubuntu 16.04.6 LTS (Xenial Xerus) の例。

$ sudo apt install -y squid
$ sudo apt install -y apache2-utils
$ sudo htpasswd -c /etc/squid/.htpasswd user01

でパスワードファイルまでつくっておきましょう。
設定ファイルsquid.conf は上記GitHubにあるモノそのままを /etc/squid/squid.conf へ配置してください。

あとは起動・停止コマンド。

$ sudo systemctl status  squid
● squid.service - LSB: Squid HTTP Proxy version 3.x
   Loaded: loaded (/etc/init.d/squid; bad; vendor preset: enabled)
   Active: active (running) since 日 2019-12-29 21:44:26 JST; 11min ago
     Docs: man:systemd-sysv-generator(8)
  Process: 2043 ExecStop=/etc/init.d/squid stop (code=exited, status=0/SUCCESS)
  Process: 2079 ExecStart=/etc/init.d/squid start (code=exited, status=0/SUCCESS)
    Tasks: 5
   Memory: 21.1M
      CPU: 568ms
   CGroup: /system.slice/squid.service
           ├─2119 /usr/sbin/squid -YC -f /etc/squid/squid.conf
           ├─2121 (squid-1) -YC -f /etc/squid/squid.conf
           ├─2122 (logfile-daemon) /var/log/squid/access.log
           ├─2123 (pinger)
           └─2127 (basic_ncsa_auth) /etc/squid/.htpasswd

12月 29 21:44:26 ubuntu-xenial systemd[1]: Starting LSB: Squid HTTP Proxy version 3.x...
12月 29 21:44:26 ubuntu-xenial squid[2079]:  * Starting Squid HTTP Proxy squid
12月 29 21:44:26 ubuntu-xenial squid[2119]: Squid Parent: will start 1 kids
12月 29 21:44:26 ubuntu-xenial squid[2079]:    ...done.
12月 29 21:44:26 ubuntu-xenial systemd[1]: Started LSB: Squid HTTP Proxy version 3.x.
12月 29 21:44:26 ubuntu-xenial squid[2119]: Squid Parent: (squid-1) process 2121 started

停止
$ sudo systemctl stop  squid

起動
$ sudo systemctl start  squid

おつかれさまでした。

関連リンク


  1. 認証プロキシ経由でのみネット接続可能にするには、そもそもプロキシ経由以外のネット接続を遮断するなど、ネットワークレベルでの制御が必要ですしね。。 

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

vscodeと開発環境をdockerにまとめてみた(2019年版)

[記事の概要]

  • 取ってきたらX転送でVisual Studio Codeが試せる環境。
  • ubuntu:18.04をベース
  • Visual Studio Codeインストール済み
  • X転送設定済み
  • 日本語入力(Mozc)設定済み
  • chromeも入っているヨ
  • これを下地にVsCode+デバッグ環境構築済みの環境とかも作れると思う。
  • X転送over sshなので、通常に使うよりもかなり高スペックが必要です。

 はじめに

2018年に同様の内容の記事を書きました。
少しするとVSCode側の問題から日本語入力が出来なくなっていました...。
今回はそのあたりにも対応しました。


準備(Windows 10)

Docker for windows

説明は不要かと思います。
入れてください。

VcXsrv

Windows上で動作するXサーバです。
インストールに関して特に難しい事は無いので、以下サイトが参考になります。

参考
VcXsrv のインストール

なお、お金を使って良い人はX410が本当にインストールするだけで動くのでお勧めです。

X410

putty

sshクライアントです。
実際は何でもよいのですが、色々と試した結果、VcXsrvとの相性が一番良かったのがこれだったので推奨します。
(最初はCygwinのsshでX転送していたけど、上手く動く時とそうでない時があり原因が良くわからなかったため...。)

Windowsでsshクライアント「PuTTY」を使う


使い方

docker image取得

docker huubからイメージを取得します。

docker pull yamada28go/docker-x-japanese-vscode-base:ubuntu_18.04-lts

docker 起動

取ってきたイメージを起動します。
起動コマンドの実行例は以下です。

[コマンド例]

docker run --rm -p 23001:22 --shm-size=512m --name VsCodeTest yamada28go/docker-x-japanese-vscode-base:ubuntu_18.04-lts

[意味]
- sshポートをローカルポートの23001に転送
- 停止したらコンテナ消去
- コンテナ名は「VsCodeTest」
- プロセス間の共有メモリは512mbyte

chromeを使用する場合、「--shm-size=512m」は重要なオプションとなります。
何も設定しない状態のdockerの場合、この共有メモリ領域が極端に小さいため、メモリが不足してchromeが安定しません。

[参考]
Dockerコンテナ上のChromeの動きが悪い時の対応方法

 VcXsrvの起動

Xサーバを先に起動します。
普通に起動すれば使えますが、設定に関して質問が出てきます。
設定方法は上の参考リンクを参照してください。

 puttyの設定

puttyから接続します。
何か所か設定が必要なので、以下説明します。

  • 接続ポート

コンテナ起動時に指定したポートを指定します。

1.png

  • X転送の有効化

X転送の有効化をします。

3.png

 puttyからの接続

設定が出来たら接続します。
ログイン情報は以下です。

ユーザー名 パスワード 備考
dev dev ←こっちを使う
root hoge 管理者用

接続するとX転送が有効な状態となっています。
puttyから「xeyes」と入力するとお馴染みの目玉が出てきます。
「code」と入力するとVisual Studio Codeが立ち上がってきます。
クリップボードも有効になっているので、windowsアプリが開いているような状態で扱う事が出来ます。

sc.png

 日本語入力設定

日本語入力の切り替えキー設定は以下コマンドで実行します。

fcitx-config-gtk3

Mozc自体の設定ダイアログは、以下のコマンドで開けます。

$ /usr/lib/mozc/mozc_tool --mode=config_dialog

[参考]
WSL(Ubuntu 18.04)上でVS Codeを動かす


その他

chromeの起動

dockerの中でchromeを起動する場合、sandbox を切っておかないと起動する事が出来ません。
このため、以下のようなエイリアスを張っています。
ターミナルより「chrome&」と入力する事でchromeが起動可能となります。

alias chrome="google-chrome-stable --no-sandbox --disable-dev-shm-usage"

インストール済みコマンド一覧

コマンド プログラム
chrome google chreom
code Visual Studio Code
/usr/lib/mozc/mozc_tool --mode=config_dialog Mozcの設定

参考

DockerでポータブルなLinux開発環境(GUI付き)を構築する

基本的にはこの記事をベースにさせて頂きました。

install nvm in docker

dockerの中でnvmしようとしたらはまりました...。

【Linux小技】 scp(ssh)での暗号化方式の違いによる転送速度ベンチマーク

X転送が遅いのでsshの暗号を切り替えました。
その参考です。

WSL(Ubuntu 18.04)上でVS Codeを動かす
18.04の環境に更新する時に参考にさせて頂きました。

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

DockerfileのRUNやCMDをBashのログインシェルで実行させる

または、sdkman, rbenv, nvmなどのパッケージマネージャをDockerfileやコンテナで動かす方法について。

TL;DR

DockerfileのRUNがbashかつログインシェルで動いてほしいとき

Dockerfile
SHELL ["/bin/bash", "-l", "-c"]

DockerfileのCMDや、docker runに渡したコマンドがbashのログインシェルで動いてほしいとき

Dockerfile
RUN echo '#!/bin/bash\nexec /bin/bash -l -c "$*"' > /entrypoint.sh && \
    chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

経緯

諸事情でJava, Gradle, Ruby, Node.jsをそれぞれ指定されたバージョンでインストール済みのコンテナイメージを作る必要があった。
apt-getなどOS標準のパッケージマネージャで過去のバージョンを指定してインストールしたり、個別にインストールするのが面倒に感じたため、sdkman, rbenv, nvmといったバージョンマネージャをDockerfile内でインストールして使うことにした(いろいろあって結局使うのをやめたが……)。

しかしこれらのツールは、Dockerfile内ではうまく動作しなかった。
なぜならsdkmanなどのバージョンマネージャは

  • bashを前提としているものがある(関数やsourceコマンドなど)
  • .bashrcで初期化スクリプト(PATHの設定など)が実行される

つまり、動作にはbashかつログインシェルで実行される必要がある。一方、DockerfileのRUN命令はデフォルトで

  • /bin/sh -cの引数として実行される
  • bashでもログインシェルでもないため.bash_profile.bashrcが実行されない

解決策

SHELL命令を使うとDockerfile内のRUNがどのシェル+引数によって実行されるかを指定できる。

SHELL ["/bin/bash", "-l", "-c"] 

また、ENTRYPOINT命令でCMDdocker runに渡したコマンドを実行するプロセスを指定できる。これは以下のシェルスクリプトを実行させると大抵の場合うまくいくという結論に達した。

/entrypoint.sh
#!/bin/bash
exec /bin/bash -l -c "$*"
ENTRYPOINT ["/entrypoint.sh"]

ENTRYPOINTの指定について

上のentrypoint.shはずいぶん回りくどく感じると思うので、いくつか他にも試して問題があったケースを挙げておく。

SHELLと同じ指定をした場合

ENTRYPOINT ["/bin/bash", "-l", "-c"]

-cオプションが引数を1つしか受け取らないため、CMDdocker runでコマンドをまとめて1つの文字列として渡す必要があり、かなり面倒。

shebangでログインシェルを指定してexecだけで実行させた場合

/enrtypoint.sh
#!/bin/bash -l
exec $*

execコマンドの第1引数には実行ファイルを指定する必要があるため、関数やbash組み込みのコマンドが実行できない。
sdk, rbenv, nvmはそれぞれ関数として実装されているため実行できなかった。

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

docker-lambda + minioで環境構築

AWS S3互換であるminioと、lamda function開発用のdocker imageであるdocker-lambdaを使って、ローカルで開発環境を作ろうというお話です。

Minio

MinioはAWS S3互換のObject Strageで、ローカルにS3環境を作ることができます。
公式ドキュメントも用意されており、Dockerがあれば手軽にはじめられます。

起動するとこんな感じです。
Screenshot from 2019-12-26 18-45-04.png

Screenshot from 2019-12-26 18-45-41.png

minio server

サーバーの立ち上げは以下のコマンド。

$ docker run --name minio -p 9000:9000 -it minio/minio server /data

上記コマンドを打った後、おそらく以下のようなものが表示される。mcコマンドについては次に説明する。

Endpoint: http://172.17.0.2:9000 http://127.0.0.1:9000
AccessKey: minioadmin
SecretKey: minioadmin

Browser Access:
   http://172.17.0.2:9000 http://127.0.0.1:9000

Command-line Access: https://docs.min.io/docs/minio-client-quickstart-guide
   $ mc config host add minio http://172.17.0.2:9000 minioadmin minioadmin

minio client

このminioにはコマンドも用意されている。これによってローカルで立てたminioサーバーに対してコマンドから操作を行うことができる。
mcを起動後、さきほどminio serverを立ち上げたときに表示されていたmcではじまるコマンドをコピペする。
これでminio serverとの接続が完了。この先はコマンドでminio serverを操作することが可能となる。

$ docker pull minio/mc
$ docker run --name mc -it --entrypoint=/bin/sh minio/mc
# mc config host add minio http://172.17.0.2:9000 minioadmin minioadmin

docker-lambda

そしてlambda fuction開発用のdocker imageであるdocker-lambda
Amazon Lambda Functionの中身はAmazon linuxらしく(知らなかった)、それをDocker内で環境構築し、必要なパッケージをインストールしてzipで固めて〜...を簡単に行えるようにしたもの。

コマンドの基本形は以下

$ docker run --rm -v <code_dir>:/var/task lambci/lambda:<runtime> <filename>.<funcname>

以下のファイルを適当なディレクトリで作成。

function.py
def my_handler(event, context):
    message = 'Hello {} {}!'.format(event['first_name'],
                                    event['last_name'])
    return {
        'message' : message
    }

上記ファイルがあるディレクトリで以下のコマンドを叩くと、それでテストができる

$ docker run --rm -v "$PWD":/var/task lambci/lambda:python3.7 function.my_handler \
'{"first_name": "Kenji", "last_name": "Yamada"}'

上記コマンドを叩いた場合はイベントが起こった前提でのテストができ、即終了となるが、DOCKER_LAMBDA_STAY_OPEN=1というオプションをつけることでAPIサーバーとしてdocker-lambdaを起動することができる。

$ docker run --rm \
  -e DOCKER_LAMBDA_STAY_OPEN=1 \
  -p 9001:9001 \
  -v "$PWD":/var/task \
  lambci/lambda:python3.7 \
  function.my_handler

以下のコマンドでイベントを投げることができる。

$ curl -d '{"first_name": "Kenji", "last_name": "Yamada"}' http://localhost:9001/2015-03-31/functions/myfunction/invocations

本題

ここまでが今回使うツールの説明である。
そして上記で説明したものはだいたい検索すれば出てくる。
今回やりたいことは以下の2つ

  • Minioにはwebhookの設定があるらしい
  • Minioにファイルをおいたタイミングでdocker-lambdaを動かしたい

ネットワークの設定

複数のコンテナを立ち上げて通信するため、まずはネットワークの設定を行う

$ docker network create minio-lambda-net

lambdaをAPIサーバーとして立ち上げ

まず、以下のようなlambda functionを作成する。
内容はシンプルに、S3にファイルをおいたタイミングでS3の情報を表示するだけ。
今回はS3の代わりにMinioを使っているので、endpointにminioのurlをしていする。

function.py
from __future__ import print_function

import os
import json
import urllib
import boto3

print('Loading function')

print('AWS_ACCESS_KEY_ID: ', os.environ.get('AWS_ACCESS_KEY_ID'))
print('AWS_SECRET_ACCESS_KEY: ', os.environ.get('AWS_SECRET_ACCESS_KEY'))

s3_endpoint_url = 'http://minio:9000'
#http://boto3.readthedocs.io/en/latest/reference/services/s3.html#client
s3 = boto3.client('s3', endpoint_url=s3_endpoint_url)


def lambda_handler(event, context):

    print(event)
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    print('==== bucket information =====')
    print(bucket_name)
    print('=============================')


    #https://github.com/bloomberg/chef-bcs/blob/master/cookbooks/chef-bcs/files/default/s3-example-boto3.py
    print('==== your bucket list ====')
    buckets = s3.list_buckets()
    for bucket in buckets['Buckets']:
        print(bucket.get('Name'))

    print('==== file list in bucket ====')
    # https://github.com/boto/boto3/issues/134
    AWS_S3_BUCKET_NAME = 'test'
    s3_resource = boto3.resource('s3', endpoint_url=s3_endpoint_url)
    bucket = s3_resource.Bucket(AWS_S3_BUCKET_NAME)
    result = bucket.meta.client.list_objects(Bucket=bucket.name, Delimiter='/')
    for o in result.get('Contents'):
        print(o.get('Key'))


    print('==== uploaded file ====')
    for rec in event['Records']:
        print(rec['s3']['object']['key'])
    print('=============================')

    print("Received event: " + json.dumps(event, indent=2))

サーバーの起動は以下のコマンド。

$ docker run --rm --name d-lambda -p 9001:9001 --net=minio-lambda-net -e DOCKER_LAMBDA_STAY_OPEN=1 -v "$PWD":/var/task lambci/lambda:python3.7 function.lambda_handler

Minio serverのwebhookの設定

Minio serverにはBucket Notificationという機能があり、方法は色々あるがwebhookを使ってMinioでのイベントを通知してみようというお話。

注意すべき点はendpointの設定。さっきd-lambdaという名前でサーバーを起動しているので、ホスト名をd-lambdaにする。

# mc admin config get myminio/ notify_webhook
# mc admin config set myminio notify_webhook:1 queue_limit="0"  endpoint="http://d-lambda:9001/2015-03-31/functions/myfunction/invocations" queue_dir=""

ここで一度サーバーを再起動するように指示されるので、それに従う。
# mc admin service restart myminio

# mc mb myminio/test
# mc event add myminio/test arn:minio:sqs::1:webhook --event put --suffix .mp4
# mc event list myminio/test

上記の設定を行うことで、testという名前のbucketに*.mp4というファイルが送られたら、webhookの通知を行うことができる。

以上で設定は完了となる。
http://localhost:9000 にアクセスするとMinioのWebUIが表示されるので、そこからファイルをアップロードしてみましょう。
lambdaを立てたコンソールで結果が見えるはずです。

参考

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