- 投稿日:2020-07-09T23:34:42+09:00
Ubuntu18.04のDockerイメージを起動する
目的
Ubuntu 18.04 LTS にdockerをインストールするの続き〜Ubuntu18.04のDockerイメージを起動するまでの備忘録です
準備
Dockerfile、docker-compose.yml、起動時に実行するスクリプト(start.sh)を準備する
DockerfileFROM ubuntu:18.04 # コンテナ起動時に実行する ADD start.sh / RUN chmod +x /start.sh CMD ["/start.sh"]docker-compose.ymlversion: '2.0' services: myproject: image: mycontainer:latest container_name: mycontainerstart.sh#!/bin/bash echo "test" > test.txt #コンテナを起動し続ける tail -f /dev/nullコンテナ起動
以下を実行してコンテナ起動すればOK
# build docker build -t mycontainer . # run docker-compose up -d # exec docker exec -it mycontainer /bin/bashdocker composeのインストール
# sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # chmod +x /usr/local/bin/docker-compose # docker-compose -v docker-compose version 1.26.2, build eefe0d31docker-compose up 後に、コンテナが(container name) exited with code 0 となる場合
$ docker-compose up Creating ubuntu-container ... done Attaching to ubuntu-container ubuntu-container exited with code 0正常終了しているので、コンテナ起動後に以下を実行できるようにする。
#コンテナを起動し続ける tail -f /dev/null参考
Ubuntu 18.04 の Docker イメージの作成(Ubuntu 上)
【Linux入門】DockerでUbuntu18.04を構築する方法
Install Docker Compose
docker run -it の「-it」とはなにか
docker-compose upするとコンテナが一瞬でexited with code 1する話
docker コンテナ起動時のシェル実行について
dockerコンテナ起動時にシェルを実行する
- 投稿日:2020-07-09T23:25:43+09:00
【AWS】 CircleCI/CD 自動デプロイでハマったエラーの解消【Capistrano 】
Railsで作成したポートフォリオをAWS EC2にデプロイし、最終段階でCicleCIによるCD(自動デプロイ)を導入していて、大きくハマった点があった為、備忘録かつ誰かの一助になればと思い、記します(理解が誤っている場合がありますので、修正があれば都度修正します)。
自動デプロイの記事について、先人の記事を見ながら実装を進めていたのですが、ssh keysのインストールはできており、最終段階の「Capistrano deploy」で以下のようなエラーがおき、長らくハマっていました。
Net::SSH::AuthenticationFailed: Authentication failed for user エラー
文面からSSH key関連のエラーであることはわかります。しかし、前タスクでInstalling additional ssh keysはSuccessしているしなぜ?と思っていました。ググっているとここでハマる方が多いようでした。
結論としては、
- CircleCIに登録したKeyが間違っていた
- その結果、Fingerprintとしてconfig.ymlに記載していた値が違う為、デプロイできなかった
config.yml- add_ssh_keys: fingerprints: - "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX" # こちらの値ということです。
最初にCircleCIに登録していた鍵は、AWS EC2を構築しているときにダウンロードした、「hoge.pem」でした。
先人の記事を参考に進めていて、書いてあるとおり何も疑わずにEC2からダウンロードしていたhoge.pemをcatしてコピーし、CircleCIに登録していました。
最終的に、自分のケースでは「pem形式の鍵をCircleCIに登録する」という点はあっていたのですが、catする鍵が違っていたようです。
CircleCIに登録すべき鍵は、ssh-keygenして作成した「~rsa」(公開鍵ではない)で、ローカルからEC2にログインする際に使用する秘密鍵でした。
EC2にSSH接続する際、初期の方で、EC2にログイン→.sshに移動→authorized_keys(中身はssh-keygenして作成した~rsa.pub)に対応する秘密鍵を、 ローカルの.sshに置いて~rsaでログインしていると思うのですが、CircleCIに登録すべきSSH keyはそちらでした。
しかし、ssh-keygenして生成した~rsaは、OPENSSH形式であり、そのままではCircleCIに登録できません。実際に登録しようとすると怒られて登録できませんでした。
※CircleCIはOPENSSH形式の鍵に現時点では対応していないとメンターさんから伺いました。登録できない?じゃあ結局どの鍵を登録すればいいんじゃ!とそこでもハマっていたのですが、
結果的にどうするのかというと、rsa鍵(OPENSSH形式)をpem形式(catするとBEGIN RSA PRIVATE KEY---で始まる)の鍵に変換する」というシンプルな解決方法でした。
こちらの記事を参考に、
SSH KeyをOpenSSH形式化からPEM形式に変換localssh-keygen -p -m PEM -f hoge_key_rsaこちらのコマンドで、rsaをpem形式に変換できると思います。
OPENSSH形式ではなくなっていますが、今までのようにlocal$ ssh hoge_key_rsa Last login: ------- 2020 from ec2-xx-xx-xx-xx.compute-1.amazonaws.com __| __|_ ) _| ( / Amazon Linux 2 AMI ___|\___|___| https://aws.amazon.com/amazon-linux-2/EC2にSSH接続は可能です。
pem形式に変換されたhoge_key_rsaをcatすると、
hoge_key_rsa-----BEGIN RSA PRIVATE KEY----- 省略 == -----END RSA PRIVATE KEY-----と出てくると思います。こうなっていればOKで、ハイフンを含めてBEGINからENDまで全てをコピーし、CircleCIに追加します。
ちなみにOPENSSH形式のままの鍵(rsa)を開くと、
local-----BEGIN OPENSSH PRIVATE KEY----- ...と出てきますが、このままではCIrcleCIに登録できないとういう訳です。
先ほどのcatしてコピーしたコードを、CircleCIに登録します。
そして生成されたFingerprintを、localのCircleCIの設定ファイルであるconfig.ymlに記載することで、エラーが解消し、無事デプロイが完了しました。随時加筆・修正していきたいと思います!
- 投稿日:2020-07-09T21:28:13+09:00
【入門】Dockerfileを記述するときの最低限知識
はじめに
Docker は Dockerfile というテキストファイルから命令を読み込んで、自動的にイメージをビルドしますね
docker build
を実行すると、順次コマンドライン命令を自動化した処理が行われて、ビルド結果となるイメージが得られます。
初心者がまず、dockerfileを記述するときのコマンドをまとめました。FROM
ベースとなるDockerのimageを設定します。
FROM <image名>[:<tag>]
ubuntuベースの場合はこちら。:の後にtagを指定することもできます。
デフォルトはlatest
FROM ubuntu:latestRUN
ビルド後にコンテナに対してLinaxのコマンドを実行する。
FROM ubuntu RUN mkdir hoge_dir RUN echo 'hogehoge' > hoge.txtこのように書くことで、できたコンテナ内にhoge_dirディレクトリを作り、hogeテキストファイルを作成し、hogehogeと書き込んでくれます。
ただ、注意しなければならないのは、RUNごとにlayerができてしまう点。一つのコマンドごとにRUNを指定しているとlayerが積み重なってdocker imageが重くなってしまうので
FROM ubuntu RUN mkdir hoge_dir && echo 'hogehoge' > hoge.txt
&&
でつなぎましょう。CMD
大抵はDockerfileの最後に記述してあり、こちらもデフォルトのLinaxコマンドを設定するという意味では、
RUN
と似たようなものです。ただ、
CMD
はlayerを作らないという違いがあります。
どうやって使い分ければ良いのかと言えば、自分はざっくりとRUN
は残しておきたいものでCMD
はそうじゃないものに使うという認識でいいのかなと思ってます。RUN apt-get update CMD ["/bin/bash"]上の場合は一度ビルドしてupdateしたら、二度同じことすると時間がかかるので、layerに残して置きたいですよね。
逆に下の場合はbash使うよという指示だけなんで、layerに残す必要はないです。また、似たものでENTRYPOINTというものもあります。
CMDは設定したコマンドをrunの時に上書きできてしまいますので、そういった場合はENTRYPOINTで設定しましょう。COPYとADD
どちらもローカルからコンテナにファイルをコピーできるものです。
ADD
の方ができることが多いです。COPY <src> <dist> ADD <src> <dist>公式でも基本はCOPYを使うことを推奨しています。
コピー元がファイルでtar形式なんかだとADDが勝手に展開してくれます。
自分はADDは大きなファイルを圧縮してコンテナに持っていきたい時に使っています。WORKDIR
コンテナ内での実行ディレクトリを変更してくれるものです。例えばdockerfileに
RUN mkdir test && cd test RUN touch hoge.txtとしてもhoge.txtはroot直下に作られてしまいます。まあ
&&
で繋げればいい話なんですけど、それじゃdockerfileに書き足していくときに毎回移動しないといけないんでWORKDIRを使うみたいです。WORKDIR <絶対パス>絶対パスにしてくださいね。
ENV
ENVは環境変数の設定をしてくれます。
ENV <key> <value>例えば自分は
ENV PATH /anaconda3/bin:$PATH
のようにPythonのPATHを通すときに使いました。
最後に
自分がとりあえず必要になって触れたコマンドをまとめてみました。
ENTRYPOINTとかLABELとかは必要になってからでいいかなあ。
- 投稿日:2020-07-09T20:16:28+09:00
Docker。自分がよく使うコマンドをaliasに設定「説明付き」
はじめに
私が思うよく使うDockerコマンドは、
bash_profile
や、bash_rc
でaliasに設定して使っています。自分の誹謗録ようなものですが、興味のある方はお試しください。
そして、もし有用なaliasを使っているならぜひ共有頂けると嬉しいです。自分も勉強目的で書いているので、間違った情報があるかもしれません。
そこはご指摘頂けるとありがたいです。alias設定内容
~/.bash_profile
以下の構文を追加
if [ -f ~/.myalias ]; then source ~/.myalias fi~/.myalias
● gist
https://gist.github.com/genie-oh/d73a224e7cb3cffab2868182eb79ccad# show full command & execute alias al='_(){ CMD=$(alias | grep "alias $1=" | cut -d = -f 2- | sed "s:^.\(.*\).$:\1:"); ARG=$(echo $@ | sed "s/^$1//"); CMD="${CMD}${ARG}"; echo "execute : ${CMD}"; echo " "; bash -c "${CMD}"; };_' # docker alias al-dock='cat ~/.myalias | grep dock | sed "s/=/ \t\= /"' alias dock='docker' alias docki='docker images' alias dockps='docker ps -a' alias dockrrm='docker run --rm' alias dockeit='docker exec -it' alias dockrm='docker rm -f' alias dockrmi='docker rmi -f' alias dockrma='docker rm -f $(docker ps -aq)' alias dockrmia='docker rmi -f $(docker images -aq)' alias dockins='docker inspect' alias dockip='docker inspect --format="{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}"' alias docklogs='dock logs --tail 50 --follow --timestamps' alias dockc='docker-compose' alias dockcb='docker-compose build' alias dockcu='docker-compose up -d' alias dockcd='docker-compose down' alias dockccl='grep container_name docker-compose.yml'※説明
al-dock
Docker関連aliasのリストを表示
$ al al-dock execute : cat ~/.myalias | grep dock | sed "s/=/ \t\= /" # docker alias al-dock = 'cat ~/.myalias | grep dock | sed "s/=/ \t\= /"' alias dock = 'docker' alias docki = 'docker images' alias dockps = 'docker ps -a' alias dockrrm = 'docker run --rm' alias dockeit = 'docker exec -it' alias dockrm = 'docker rm -f' alias dockrmi = 'docker rmi -f' alias dockrma = 'docker rm -f $(docker ps -aq)' alias dockrmia = 'docker rmi -f $(docker images -aq)' alias dockins = 'docker inspect' alias dockip = 'docker inspect --format="{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}"' alias docklogs = 'dock logs --tail 50 --follow --timestamps' alias dockc = 'docker-compose' alias dockcb = 'docker-compose build' alias dockcu = 'docker-compose up -d' alias dockcd = 'docker-compose down' alias dockccl = 'grep container_name docker-compose.yml'dockc, dockcb, dockcu, dockcd
docker-compose関連
docker-composeのbuild,up,downalias dockc = 'docker-compose' alias dockcb = 'docker-compose build' alias dockcd = 'docker-compose down' alias dockcu = 'docker-compose up -d'dockccl
docker-compose.yml内のコンテナーネームのリストを出力
alias dockccl = 'grep container_name docker-compose.yml'$ al dockccl execute : grep container_name docker-compose.yml container_name: lamp-web container_name: lamp-php ...省略docki
docker imageのリストを出力
alias docki = 'docker images'$ al docki execute : docker images REPOSITORY TAG IMAGE ID CREATED SIZE centos 7 b5b4d78bc90c 8 weeks ago 203MB docker-lamp-test_php latest e6d67d8f48cf 17 minutes ago 666MB ...省略
dockps
dockerのコンテナーリストと実行状態を表示
alias dockps = 'docker ps -a'$ al dockps execute : docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6116324251dd docker-lamp-test_php "docker-php-entrypoi…" 17 minutes ago Up 17 minutes 0.0.0.0:32769->9000/tcp lamp-php ...省略dockeit
実行中のコンテナーで、interactive&ttyモードでコマンド実行
docker exec --interective --ttyalias dockeit = 'docker exec -it'$ al dockeit lamp-php bash execute : docker exec -it lamp-php bash root@37bcdbd02f92:/var/www/html# ls config gulpfile.js home_root home_sub nodeappdockrrm
コンテナーでコマンドを実行後、即時にコンテナーを終了する
コンテナー作成▶コマンド実行▶コンテナー終了alias dockrrm = 'docker run --rm'$ al dockrrm composer php -v execute : docker run --rm composer php -v PHP 7.4.7 (cli) (built: Jun 11 2020 18:58:32) ( NTS ) ...省略dockins
コンテナーの状態を確認
alias dockins = 'docker inspect'$ al dockins lamp-php execute : docker inspect lamp-php [ { "Id": "6116324251ddffc8090cc605d391f89a951aeb46d32636dd62476a225a894c51", "Created": "2020-07-01T13:30:27.11145Z", ...省略dockip
コンテナーの状態で、IPだけを取得
alias dockip = 'docker inspect --format="{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}"'$ al dockip lamp-php execute : docker inspect --format="{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" lamp-php 172.30.0.5docklogs
コンテナーのログを確認
alias docklogs = 'dock logs --tail 50 --follow --timestamps'$ al docklogs lamp-php execute : docker logs --tail 50 --follow --timestamps lamp-php 2020-07-01T13:30:28.338589500Z [01-Jul-2020 22:30:28] NOTICE: fpm is running, pid 1 2020-07-01T13:30:28.340324600Z [01-Jul-2020 22:30:28] NOTICE: ready to handle connectionsdockrm, dockrmi
特定のコンテナー、またはイメージを削除
alias dockrm = 'docker rm -f' alias dockrmi = 'docker rmi -f'dockrma, dockrmia
すべてのコンテナー、またはイメージを削除
alias dockrma = 'docker rm -f $(docker ps -aq)' alias dockrmia = 'docker rmi -f $(docker images -aq)'
- 投稿日:2020-07-09T19:25:41+09:00
docker エラー (bad flag syntax: -----END)
よくAWSにふれるものです。
docker build(run)する際に次のようなエラーにハマりました。
問題
以下のようなコマンド
$ docker build --build-arg PRIVATE_KEY=$PRIVATE_KEYbad flag syntax: -----END See 'docker build --help'.ちなみにPRIVATE_KEYはpem形式です。
-----BEGIN PRIVATE KEY----- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ... -----END PRIVATE KEY-----解決
pem形式はスペースを含むので、環境変数には引用符をつけなくてはなりません。
$ docker build --build-arg PRIVATE_KEY="$PRIVATE_KEY"
以上で解決。
- 投稿日:2020-07-09T16:25:23+09:00
Dockerとは何か?[入門編]
Dockerについて
コンテナ仮想化を用いてアプリケーションを開発・配置・実行するためのオープンソフトウェアあるいはオープンプラットフォームである。(wikipediaより)
コンテナ?仮想化?何それ状態だと思います。次にそれらについて解説します。
コンテナとは
OS上に区画を作り、アプリケーションを動作させるために必要なライブラリやアプリケーションなどを一つにまとめたものである。
仮想化とは
コンピュータリソースを抽象化することである。
コンピュータそのものをハードウェアからソフトウェア化することを目指す。Dockerのメリット
まずよく比較されるサービスとしてVMwareやVirtualBoxなどの仮想マシンがあります。
こちらがメリットです。
- 簡単に環境開発
- 軽量でスピーディーな開発
- ハードウェアの資源削減
- 共有化されたシステム
- イミュータブル・インフラストラクチャである点
- 物理サーバの考慮を後回しにできる点
- モダンなエコシステム
Dockerのデメリット
- 同一のOSを利用しなければならない
- 提供できるホストの種類が少ない
- Windowsでの利用ハードルの高さ
仮想マシン(VMware・VirtualBox)について
ここからしばらくDockerから少し離れるので気になる方以外は飛ばしてください。
仮想マシン
仮想マシン技術による仮想化の方式としてホストOS型ハイパーバイザー型があります。
ホストOS型
本来入っているOS(WindowsやMac・Linux)上で仮想化ソフトを使い、その上で仮想マシンを立ち上げる方法。
メリット
・ホストOSが対応していれば、ハードウェア環境にほぼ依存しない。
・管理マシンを必要としない。
・ハイパーバイザー型と比べて手軽に環境構築が可能。デメリット
・ホストOSが必要な点
・物理リソースが必要な点ハイパーバイザー型
サーバへ直接インストールし仮想マシンを稼働させる方法
メリット
・ リソースを有効活用できる点
・システムにかかるコストを削減デメリット
・ 運用コストが割高になる可能性
・ 種類によっては知識や技術が必要な点
(高度な管理を実現するためのツールが標準装備されていない可能性があるため。)デメリット
見にくいと思うので、表にしてまとめてみます。
仮想マシン コンテナ技術 起動 遅い(パフォーマンスによる) 早い OS 各仮想マシンに付き一つ 一つで複数稼働可能 コスト 大きい 小さい 構成 ハードウェアに依存 ハードウェアに依存しない メモリ消費 大きい 小さい 続けてDockerでよく使われる言葉について説明します。
Dockerでよく使われるワード
Dockerイメージ
OSやアプリケーションを含んだテンプレートのベースイメージ。読み取り専用。
構築(build)コンポーネントDockerコンテナ
分離された名前空間とアプリケーションの実行環境
実行(run)コンポーネントDockerHub(Dockerレジストリ)
公開されているDockerイメージを提供しているプラットフォーム
配布(distribution)コンポーネントDockerクライアント
ユーザがコマンドを反抗し、Dockerデーモンと通信を行う
Dockerデーモン
ホストマシン上で動いている。直接ユーザとはやりとりはしない。管理者的な役割を果たす。
次回は実際にDockerを動かしていきます。
- 投稿日:2020-07-09T14:15:58+09:00
CentOS 7に「Docker」をインストールしてDocker Swarm Modeを設定します。
Docker Swarmは、Dockerコンテナのネイティブクラスタリングツールで、Dockerノードのクラスタを1つの仮想システムとして管理することができます。
このチュートリアルでは、CentOS 7上で3ノードのDocker Swarmクラスタを構成する方法をステップバイステップで説明します。本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。
必要条件
- CentOS 7をインストールした3台のAlibabaクラウドインスタンス。そのうち1台のサーバーがManagerノードとして動作し、2台のサーバーがWorkerノードとして動作します。
- すべてのインスタンスに固定IPアドレスを設定しています。ここでは、Managerノードに192.168.0.102、Workerノード1に192.168.0.103、Workerノード2に192.168.0.104のIPアドレスを使用します。
アリババクラウドECSインスタンスの起動
まず、https://ecs.console.aliyun.com/?spm=a3c0i.o25424en.a3.13.388d499ep38szxAlibaba Cloud ECS Consoleにログインします。新しいECSインスタンスを作成し、少なくとも2GBのRAMを搭載したオペレーティングシステムとしてCentOS 7を選択します。ECSインスタンスに接続し、rootユーザーとしてログインします。
CentOS 7インスタンスにログインしたら、以下のコマンドを実行して、ベースシステムを最新の利用可能なパッケージでアップデートします。
yum update -y
はじめに
始める前に、各ノードがホスト名で通信できるように、各ノードの/etc/hostsファイルを設定しておく必要があります。
以下のように各ノードの/etc/hostsファイルを更新します。
nano /etc/hosts 192.168.0.102 managernode 192.168.0.103 workernode1 192.168.0.104 workernode2保存して終了したらファイルを閉じます。
次に、/etc/hostsファイルの通りに各ノードのhostnameを設定する必要があります。
以下のコマンドを各ノードで1つずつ実行することで行うことができます。
管理者ノード
hostnamectl set-hostname managernode
Worker node1
hostnamectl set-hostname workernode1
Worker node2
hostnamectl set-hostname workernode2
Dockerエンジンのインストール
次に、すべてのノードにDocker Community Editionをインストールする必要があります。デフォルトでは、最新版のDocker CEはCentOS 7のリポジトリにはありません。そのため、Docker CEのリポジトリをシステムに追加する必要があります。
これは以下のコマンドを全ノードで実行することで行うことができます。
wget https://download.docker.com/linux/centos/docker-ce.repo - O /etc/yum.repos.d/docker.repo
Dockerリポジトリをインストールしたら、以下のコマンドを実行してDocker CEをインストールします。
yum install docker-ce -y
次にDockerサービスを起動し、以下のコマンドで起動時に起動できるようにします。
systemctl start docker systemctl enable dockerファイアウォールの設定
次に、スウォームクラスタが正常に動作するためには、ファイアウォール上のポート7946、4789、2376、2377、および80を開く必要があります。
すべてのノードで以下のコマンドを実行します。
firewall-cmd --permanent --add-port=2376/tcp firewall-cmd --permanent --add-port=2377/tcp firewall-cmd --permanent --add-port=7946/tcp firewall-cmd --permanent --add-port=80/tcp firewall-cmd --permanent --add-port=7946/udp firewall-cmd --permanent --add-port=4789/udp最後に、ファイアウォールとDockerサービスをリロードして、すべての変更を適用します。
firewall-cmd --reload systemctl restart dockerSwarmを作成する
次に、Managerノードで Swarmを初期化する必要があります。これはdocker swarm initコマンドを実行することで行うことができます。このコマンドを実行することで、ノードをManagerノードにしてIPをアドバタイズします。
docker swarm init --advertise-addr 192.168.0.102
以下のような出力が表示されるはずです。
Swarm initialized: current node (viwovkb0bk0kxlk98r78apopo) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-3793hvb71g0a6ubkgq8zgk9w99hlusajtmj5aqr3n2wrhzzf8z- 1s38lymnir13hhso1qxt5pqru 192.168.0.102:2377 To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.注: 上記の出力のトークンを覚えておいてください。これは、後でworkerノードをManagerノードに結合するために使用します。
Swarmクラスタの状態を確認するには、以下のコマンドを使用します。
docker info
出力します。
Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 0 Server Version: 17.12.0-ce Storage Driver: devicemapper Pool Name: docker-253:0-618740-pool Pool Blocksize: 65.54kB Base Device Size: 10.74GB Backing Filesystem: xfs Udev Sync Supported: true Data file: /dev/loop0 Metadata file: /dev/loop1 Data loop file: /var/lib/docker/devicemapper/devicemapper/data Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata Data Space Used: 11.8MB Data Space Total: 107.4GB Data Space Available: 3.817GB Metadata Space Used: 581.6kB Metadata Space Total: 2.147GB Metadata Space Available: 2.147GB Thin Pool Minimum Free Space: 10.74GB Deferred Removal Enabled: true Deferred Deletion Enabled: true Deferred Deleted Device Count: 0 Library Version: 1.02.140-RHEL7 (2017-05-03) Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local Network: bridge host macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog Swarm: active NodeID: viwovkb0bk0kxlk98r78apopo Is Manager: true ClusterID: ttauawqrc8mmd0feluhcr1b0d Managers: 1 Nodes: 1 Orchestration: Task History Retention Limit: 5 Raft: Snapshot Interval: 10000 Number of Old Snapshots to Retain: 0 Heartbeat Tick: 1 Election Tick: 3 Dispatcher: Heartbeat Period: 5 seconds CA Configuration: Expiry Duration: 3 months Force Rotate: 0 Autolock Managers: false Root Rotation In Progress: false Node Address: 192.168.0.102 Manager Addresses: 192.168.0.102:2377 Runtimes: runc Default Runtime: runc Init Binary: docker-init containerd version: 89623f28b87a6004d4b785663257362d1658a729 runc version: b2567b37d7b75eb4cf325b77297b140ea686ce8f init version: 949e6fa Security Options: seccomp Profile: default Kernel Version: 3.10.0-693.11.1.el7.x86_64 Operating System: CentOS Linux 7 (Core) OSType: linux Architecture: x86_64 CPUs: 1 Total Memory: 1.102GiB Name: centOS-7 ID: DN4N:BHHJ:6DJ7:SZPG:FJJC:XP6T:23R4:CESK:E5PO:SJ6B:BOST:HZQ5 Docker Root Dir: /var/lib/docker Debug Mode (client): false Debug Mode (server): false Registry: https://index.docker.io/v1/ Labels: Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: falseまた、以下のコマンドでクラスタ内のノードの一覧を確認することができます。
docker node ls
出力します。
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS viwovkb0bk0kxlk98r78apopo * centOS-7 Ready Active LeaderWorker ノードを Manager ノードに結合する
これでManagerノードの準備は完了です。次に、WorkerノードをManagerノードに追加する必要があります。
以下のように両方のWorkerノードでdocker swarm joinコマンドを実行することで行うことができます。
docker swarm join --token SWMTKN-1-3793hvb71g0a6ubkgq8zgk9w99hlusajtmj5aqr3n2wrhzzf8z-1s38lymnir13hhso1qxt5pqru 192.168.0.102:2377
出力します。
This node joined a swarm as a worker.
Managerノードで、以下のコマンドを実行して、ノードがアクティブかどうか、ノードの状態を確認します。
docker node ls
すべてがうまくいった場合は、以下のような出力が表示されるはずです。
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS viwovkb0bk0kxlk98r78apopo * managernode Ready Active Leader yf6nb2er69pydlp6drijdfmwd workernode1 Ready Active yyavdslji7ovmw5fd3v7l62g8 workernode2 Ready Activeいつでも参加トークンを失った場合。Managerノードで以下を実行することで取得できます。
docker swarm join-token manager -q
Docker Swarmモードでサービスをデプロイする
Docker Swarmクラスタの準備が整いました。いよいよSwarm Modeでサービスをデプロイします。ここでは、Docker Swarm Modeで3つのコンテナでWebサーバサービスをデプロイします。
Managerノードで以下のコマンドを実行して、Webサーバサービスを起動します。
docker service create -p 80:80 --name webservice --replicas 3 httpd
出力します。
bky6uhg2agdxeqgc2b1a5tcsm overall progress: 3 out of 3 tasks 1/3: running [==================================================>] 2/3: running [==================================================>] 3/3: running [==================================================>] verify: Service converged上記のコマンドでwebserviceという名前のサービスを作成し、dockerイメージのhttpdからコンテナを起動します。
ここで、以下のコマンドでサービスの状態を一覧表示して確認することができます。
docker service ls
出力します。
ID NAME MODE REPLICAS IMAGE PORTS bky6uhg2agdx webservice replicated 3/3 httpd:latest *:80->80/tcp docker service ps webservice出力します。
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS xsa5wb0eg2ln webservice.1 httpd:latest managernode Running Running about a minute ago 3sbscs7m9lnh webservice.2 httpd:latest workernode2 Running Running about a minute ago yao2m5wi54kz webservice.3 httpd:latest workernode2 Running Running about a minute agoApacheのWebサービスは3つのノードに分散されていますので、以下のように好きなWebブラウザを使ってWorkerノードとManagerノードのいずれかにアクセスすることで、Webサーバにアクセスすることができます。
http://192.168.0.102
http://192.168.0.103
http://192.168.0.104コンテナのセルフヒーリング
docker スウォームモードの重要な機能の一つに、コンテナの自己修復があります。コンテナがダウンした場合、同じノードでも別のノードでも自動的に再起動されます。
コンテナのセルフヒーリング機能をテストするために、workernode2からコンテナを外して、新しいコンテナが起動するかどうかを見てみましょう。
始める前に、コンテナを削除するにはコンテナIDが必要です。Workernode2上で以下のコマンドを実行することでコンテナIDをリストアウトすることができます。
docker ps
出力します。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0dfc71537e18 httpd:latest "httpd-foreground" 9 minutes ago Up 9 minutes 80/tcp webservice.3.yao2m5wi54kzs7iskefupuq6a 9b01b0a55cb7 httpd:latest "httpd-foreground" 9 minutes ago Up 9 minutes 80/tcp webservice.1.xsa5wb0eg2ln5bud1sbjf9e9eでは、以下のコマンドを実行してID 9b01b0a55cb7のコンテナを削除します。
docker rm 9b01b0a55cb7 -f
ここで、Service from Managerノードを確認し、新しいコンテナが起動されているかどうかを確認します。
docker service ps webservice
下図のように、1つのコンテナが失敗し、別のコンテナがworkernode2上で起動していることがわかるはずです。
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS z1spatkk1jj7 webservice.1 httpd:latest workernode2 Running Preparing 29 seconds ago xsa5wb0eg2ln \_ webservice.1 httpd:latest workernode2 Shutdown Failed 30 seconds ago "task: non-zero exit (137)"また、要件に応じてコンテナをスケールアップしたり、スケールダウンしたりすることもできます。例えば、Managerノードで以下のコマンドを使用して、Webサービスのコンテナを3から5にスケールアップすることができます。
docker service create -p 80:80 --name webservice --replicas 5 httpd
Managerノードで以下のコマンドを実行することで、Webサービスの状態を確認できます。
docker service ps webservice
出力します。
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS xsa5wb0eg2ln webservice.1 httpd:latest managernode Running Running about 10 minutes ago 3sbscs7m9lnh webservice.2 httpd:latest workernode2 Running Running about 10 minutes ago yao2m5wi54kz webservice.3 httpd:latest workernode2 Running Running about 10 minutes ago dfg2mswa52sf webservice.4 httpd:latest workernode1 Running Running about 15 seconds ago kah1j5hs14as webservice.5 httpd:latest workernode1 Running Running about 15 seconds ago上記の出力では、workernode1で2つの新しいインスタンスが起動されていることがわかります。
サーバーの保護
Dockerノードのクラスタをセットアップした後は、セキュリティの追加レイヤーを提供することでサーバを保護することをお勧めします。監視機能とファイアウォール機能の両方を備えたセキュリティソリューションを導入するのが良いでしょう。
Alibaba Cloud Web Application Firewall(WAF)は、SQLインジェクション、クロスサイトスクリプティング(XSS)、悪意のあるボット、コマンド実行の脆弱性、その他の一般的なWeb攻撃を含むWebベースの攻撃からの保護を提供するために使用することができます。WAFは、多数の悪意のあるアクセス試行をフィルタリングし、サーバー上のハイパーテキスト転送プロトコル(HTTP)/HTTPセキュア(HTTPS)フラッド攻撃によるパフォーマンスへの影響を緩和します。
Alibaba CloudのCloudMonitorを使用して、お客様のクラウド展開の詳細な洞察を提供することができます。CloudMonitorは、中央処理装置(CPU)の使用率やレイテンシなどの重要な指標について高度な分析を提供し、ビジネス要件に応じてパラメータをカスタマイズすることもできます。
アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ
- 投稿日:2020-07-09T14:15:58+09:00
CentOS 7に「Docker」をインストールしてDocker Swarm Modeを設定
Docker Swarmは、Dockerコンテナのネイティブクラスタリングツールで、Dockerノードのクラスタを1つの仮想システムとして管理することができます。
このチュートリアルでは、CentOS 7上で3ノードのDocker Swarmクラスタを構成する方法をステップバイステップで説明します。本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。
必要条件
- CentOS 7をインストールした3台のAlibabaクラウドインスタンス。そのうち1台のサーバーがManagerノードとして動作し、2台のサーバーがWorkerノードとして動作します。
- すべてのインスタンスに固定IPアドレスを設定しています。ここでは、Managerノードに192.168.0.102、Workerノード1に192.168.0.103、Workerノード2に192.168.0.104のIPアドレスを使用します。
アリババクラウドECSインスタンスの起動
まず、https://ecs.console.aliyun.com/?spm=a3c0i.o25424en.a3.13.388d499ep38szxAlibaba Cloud ECS Consoleにログインします。新しいECSインスタンスを作成し、少なくとも2GBのRAMを搭載したオペレーティングシステムとしてCentOS 7を選択します。ECSインスタンスに接続し、rootユーザーとしてログインします。
CentOS 7インスタンスにログインしたら、以下のコマンドを実行して、ベースシステムを最新の利用可能なパッケージでアップデートします。
yum update -y
はじめに
始める前に、各ノードがホスト名で通信できるように、各ノードの/etc/hostsファイルを設定しておく必要があります。
以下のように各ノードの/etc/hostsファイルを更新します。
nano /etc/hosts 192.168.0.102 managernode 192.168.0.103 workernode1 192.168.0.104 workernode2保存して終了したらファイルを閉じます。
次に、/etc/hostsファイルの通りに各ノードのhostnameを設定する必要があります。
以下のコマンドを各ノードで1つずつ実行することで行うことができます。
管理者ノード
hostnamectl set-hostname managernode
Worker node1
hostnamectl set-hostname workernode1
Worker node2
hostnamectl set-hostname workernode2
Dockerエンジンのインストール
次に、すべてのノードにDocker Community Editionをインストールする必要があります。デフォルトでは、最新版のDocker CEはCentOS 7のリポジトリにはありません。そのため、Docker CEのリポジトリをシステムに追加する必要があります。
これは以下のコマンドを全ノードで実行することで行うことができます。
wget https://download.docker.com/linux/centos/docker-ce.repo - O /etc/yum.repos.d/docker.repo
Dockerリポジトリをインストールしたら、以下のコマンドを実行してDocker CEをインストールします。
yum install docker-ce -y
次にDockerサービスを起動し、以下のコマンドで起動時に起動できるようにします。
systemctl start docker systemctl enable dockerファイアウォールの設定
次に、スウォームクラスタが正常に動作するためには、ファイアウォール上のポート7946、4789、2376、2377、および80を開く必要があります。
すべてのノードで以下のコマンドを実行します。
firewall-cmd --permanent --add-port=2376/tcp firewall-cmd --permanent --add-port=2377/tcp firewall-cmd --permanent --add-port=7946/tcp firewall-cmd --permanent --add-port=80/tcp firewall-cmd --permanent --add-port=7946/udp firewall-cmd --permanent --add-port=4789/udp最後に、ファイアウォールとDockerサービスをリロードして、すべての変更を適用します。
firewall-cmd --reload systemctl restart dockerSwarmを作成する
次に、Managerノードで Swarmを初期化する必要があります。これはdocker swarm initコマンドを実行することで行うことができます。このコマンドを実行することで、ノードをManagerノードにしてIPをアドバタイズします。
docker swarm init --advertise-addr 192.168.0.102
以下のような出力が表示されるはずです。
Swarm initialized: current node (viwovkb0bk0kxlk98r78apopo) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-3793hvb71g0a6ubkgq8zgk9w99hlusajtmj5aqr3n2wrhzzf8z- 1s38lymnir13hhso1qxt5pqru 192.168.0.102:2377 To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.注: 上記の出力のトークンを覚えておいてください。これは、後でworkerノードをManagerノードに結合するために使用します。
Swarmクラスタの状態を確認するには、以下のコマンドを使用します。
docker info
出力します。
Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 0 Server Version: 17.12.0-ce Storage Driver: devicemapper Pool Name: docker-253:0-618740-pool Pool Blocksize: 65.54kB Base Device Size: 10.74GB Backing Filesystem: xfs Udev Sync Supported: true Data file: /dev/loop0 Metadata file: /dev/loop1 Data loop file: /var/lib/docker/devicemapper/devicemapper/data Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata Data Space Used: 11.8MB Data Space Total: 107.4GB Data Space Available: 3.817GB Metadata Space Used: 581.6kB Metadata Space Total: 2.147GB Metadata Space Available: 2.147GB Thin Pool Minimum Free Space: 10.74GB Deferred Removal Enabled: true Deferred Deletion Enabled: true Deferred Deleted Device Count: 0 Library Version: 1.02.140-RHEL7 (2017-05-03) Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local Network: bridge host macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog Swarm: active NodeID: viwovkb0bk0kxlk98r78apopo Is Manager: true ClusterID: ttauawqrc8mmd0feluhcr1b0d Managers: 1 Nodes: 1 Orchestration: Task History Retention Limit: 5 Raft: Snapshot Interval: 10000 Number of Old Snapshots to Retain: 0 Heartbeat Tick: 1 Election Tick: 3 Dispatcher: Heartbeat Period: 5 seconds CA Configuration: Expiry Duration: 3 months Force Rotate: 0 Autolock Managers: false Root Rotation In Progress: false Node Address: 192.168.0.102 Manager Addresses: 192.168.0.102:2377 Runtimes: runc Default Runtime: runc Init Binary: docker-init containerd version: 89623f28b87a6004d4b785663257362d1658a729 runc version: b2567b37d7b75eb4cf325b77297b140ea686ce8f init version: 949e6fa Security Options: seccomp Profile: default Kernel Version: 3.10.0-693.11.1.el7.x86_64 Operating System: CentOS Linux 7 (Core) OSType: linux Architecture: x86_64 CPUs: 1 Total Memory: 1.102GiB Name: centOS-7 ID: DN4N:BHHJ:6DJ7:SZPG:FJJC:XP6T:23R4:CESK:E5PO:SJ6B:BOST:HZQ5 Docker Root Dir: /var/lib/docker Debug Mode (client): false Debug Mode (server): false Registry: https://index.docker.io/v1/ Labels: Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: falseまた、以下のコマンドでクラスタ内のノードの一覧を確認することができます。
docker node ls
出力します。
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS viwovkb0bk0kxlk98r78apopo * centOS-7 Ready Active LeaderWorker ノードを Manager ノードに結合する
これでManagerノードの準備は完了です。次に、WorkerノードをManagerノードに追加する必要があります。
以下のように両方のWorkerノードでdocker swarm joinコマンドを実行することで行うことができます。
docker swarm join --token SWMTKN-1-3793hvb71g0a6ubkgq8zgk9w99hlusajtmj5aqr3n2wrhzzf8z-1s38lymnir13hhso1qxt5pqru 192.168.0.102:2377
出力します。
This node joined a swarm as a worker.
Managerノードで、以下のコマンドを実行して、ノードがアクティブかどうか、ノードの状態を確認します。
docker node ls
すべてがうまくいった場合は、以下のような出力が表示されるはずです。
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS viwovkb0bk0kxlk98r78apopo * managernode Ready Active Leader yf6nb2er69pydlp6drijdfmwd workernode1 Ready Active yyavdslji7ovmw5fd3v7l62g8 workernode2 Ready Activeいつでも参加トークンを失った場合。Managerノードで以下を実行することで取得できます。
docker swarm join-token manager -q
Docker Swarmモードでサービスをデプロイする
Docker Swarmクラスタの準備が整いました。いよいよSwarm Modeでサービスをデプロイします。ここでは、Docker Swarm Modeで3つのコンテナでWebサーバサービスをデプロイします。
Managerノードで以下のコマンドを実行して、Webサーバサービスを起動します。
docker service create -p 80:80 --name webservice --replicas 3 httpd
出力します。
bky6uhg2agdxeqgc2b1a5tcsm overall progress: 3 out of 3 tasks 1/3: running [==================================================>] 2/3: running [==================================================>] 3/3: running [==================================================>] verify: Service converged上記のコマンドでwebserviceという名前のサービスを作成し、dockerイメージのhttpdからコンテナを起動します。
ここで、以下のコマンドでサービスの状態を一覧表示して確認することができます。
docker service ls
出力します。
ID NAME MODE REPLICAS IMAGE PORTS bky6uhg2agdx webservice replicated 3/3 httpd:latest *:80->80/tcp docker service ps webservice出力します。
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS xsa5wb0eg2ln webservice.1 httpd:latest managernode Running Running about a minute ago 3sbscs7m9lnh webservice.2 httpd:latest workernode2 Running Running about a minute ago yao2m5wi54kz webservice.3 httpd:latest workernode2 Running Running about a minute agoApacheのWebサービスは3つのノードに分散されていますので、以下のように好きなWebブラウザを使ってWorkerノードとManagerノードのいずれかにアクセスすることで、Webサーバにアクセスすることができます。
http://192.168.0.102
http://192.168.0.103
http://192.168.0.104コンテナのセルフヒーリング
docker スウォームモードの重要な機能の一つに、コンテナの自己修復があります。コンテナがダウンした場合、同じノードでも別のノードでも自動的に再起動されます。
コンテナのセルフヒーリング機能をテストするために、workernode2からコンテナを外して、新しいコンテナが起動するかどうかを見てみましょう。
始める前に、コンテナを削除するにはコンテナIDが必要です。Workernode2上で以下のコマンドを実行することでコンテナIDをリストアウトすることができます。
docker ps
出力します。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0dfc71537e18 httpd:latest "httpd-foreground" 9 minutes ago Up 9 minutes 80/tcp webservice.3.yao2m5wi54kzs7iskefupuq6a 9b01b0a55cb7 httpd:latest "httpd-foreground" 9 minutes ago Up 9 minutes 80/tcp webservice.1.xsa5wb0eg2ln5bud1sbjf9e9eでは、以下のコマンドを実行してID 9b01b0a55cb7のコンテナを削除します。
docker rm 9b01b0a55cb7 -f
ここで、Service from Managerノードを確認し、新しいコンテナが起動されているかどうかを確認します。
docker service ps webservice
下図のように、1つのコンテナが失敗し、別のコンテナがworkernode2上で起動していることがわかるはずです。
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS z1spatkk1jj7 webservice.1 httpd:latest workernode2 Running Preparing 29 seconds ago xsa5wb0eg2ln \_ webservice.1 httpd:latest workernode2 Shutdown Failed 30 seconds ago "task: non-zero exit (137)"また、要件に応じてコンテナをスケールアップしたり、スケールダウンしたりすることもできます。例えば、Managerノードで以下のコマンドを使用して、Webサービスのコンテナを3から5にスケールアップすることができます。
docker service create -p 80:80 --name webservice --replicas 5 httpd
Managerノードで以下のコマンドを実行することで、Webサービスの状態を確認できます。
docker service ps webservice
出力します。
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS xsa5wb0eg2ln webservice.1 httpd:latest managernode Running Running about 10 minutes ago 3sbscs7m9lnh webservice.2 httpd:latest workernode2 Running Running about 10 minutes ago yao2m5wi54kz webservice.3 httpd:latest workernode2 Running Running about 10 minutes ago dfg2mswa52sf webservice.4 httpd:latest workernode1 Running Running about 15 seconds ago kah1j5hs14as webservice.5 httpd:latest workernode1 Running Running about 15 seconds ago上記の出力では、workernode1で2つの新しいインスタンスが起動されていることがわかります。
サーバーの保護
Dockerノードのクラスタをセットアップした後は、セキュリティの追加レイヤーを提供することでサーバを保護することをお勧めします。監視機能とファイアウォール機能の両方を備えたセキュリティソリューションを導入するのが良いでしょう。
Alibaba Cloud Web Application Firewall(WAF)は、SQLインジェクション、クロスサイトスクリプティング(XSS)、悪意のあるボット、コマンド実行の脆弱性、その他の一般的なWeb攻撃を含むWebベースの攻撃からの保護を提供するために使用することができます。WAFは、多数の悪意のあるアクセス試行をフィルタリングし、サーバー上のハイパーテキスト転送プロトコル(HTTP)/HTTPセキュア(HTTPS)フラッド攻撃によるパフォーマンスへの影響を緩和します。
Alibaba CloudのCloudMonitorを使用して、お客様のクラウド展開の詳細な洞察を提供することができます。CloudMonitorは、中央処理装置(CPU)の使用率やレイテンシなどの重要な指標について高度な分析を提供し、ビジネス要件に応じてパラメータをカスタマイズすることもできます。
アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ
- 投稿日:2020-07-09T11:12:48+09:00
同じECSインスタンスを使用して複数のウェブサイトをホスティングするDevOpsの方法
このチュートリアルでは、Docker Composeとリバースプロキシを使って、同じECSインスタンスを使って2つ以上のWebサイトを実行する方法を学びます。
本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです 。
なぜこのアプローチなのか?
ほとんどの場合、アリババクラウドECSインスタンスを取得したときには、1つのウェブサイトやアプリケーションにのみ使用します。しかし、家族と写真を共有したり、新しいブログを立ち上げたり、基本的にはコンテンツをホストして提供するために別のECSインスタンスを必要とするような、他のことに使用する必要がある場合もあります。このソリューションは、安定性のために推奨されていますが、時にはやりすぎてしまうこともあります。静的なサイトや小さなブログを運営するために、まったく新しいインスタンスを必要とすることはありません。代わりに、同じマシンでそれらをホストし、リソースを共有することでコストを削減することができます。
サーバーが複数のウェブサイトにサービスを提供する可能性があることをすでに知っていて、ECSインスタンスを最大限に活用したい場合は、1つの単一インスタンスECSで異なるコンテンツを処理するための複数のソリューションがあります。
1、サブフォルダ ("example.com/web1", "example.com/web2", "example.com/web3")
2、ポートベースの仮想ホスティング ("example.com:80", "example.com:8080", "example.com:8181")
3、サブドメイン(「web1.example.com」、「web2.example.com」、「web3.example.com
4、名前ベースのバーチャルホスティング ("web1.com", "web2.com", "web3.com")名前ベースのバーチャルホスティング
それはあなたのサイトのためのよりよい名前を使用するのに役立ちますので、ここでは最高の(そしてより優雅な)解決は、名前ベースの仮想ホスティングを使用することです。マイクロソフトとグーグルの両方が私のサーバー上で自分のウェブサイトをホストすることを決定したことを一瞬想像してみてください。最初のステップは、「microsoft.com」と「google.com」の両方のDNS「A」レコードを更新して、ECSインスタンスのパブリックIPを指すようにすることです。
そして、ここで質問が出てきます。両方のウェブサイトの訪問者が同じIPに指示されている場合、どのようにしてそれぞれのリクエストを提供するページを区別するのでしょうか?ここで、HTTP ヘッダーが来ます。これは HTTP トランザクションのパラメータを定義します。以下の例を見てみましょう。
GET / HTTP/1.1 Host: microsoft.com Connection: keep-alive Cache-Control: no-cache User-Agent: Chrome/66.0.3359.139 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: en-AUご覧のように、このHTTPリクエストのHostパラメータ(HTTP/1.1以降は必須)は "microsoft.com "を要求しています。この情報があれば、私のサーバーはどのページにサービスを提供するかを知っています。この場合、(GETリクエストによると)microsoft.comのURIです。
幸いなことに、HTTP/0.9バージョンを使用しているWebブラウザは残っていないので、リクエストはGETの部分だけをカバーし、ホストについては他には何もありません。
最も一般的には、ポートベースと名前ベースのバーチャルホスティングの両方は、 sites-available という名前のフォルダ内のホスト上の設定ファイルを使用して達成され、 sites-enabled でそれらをリンクしています。これらの設定ファイルは、どのようなドメイン名が使用されているか(例えば、web1.com)とウェブサイトがディスク(例えば、/var/www/html/web1.com/)に格納されている場所を記述します。
しかし、セキュリティ上の理由から、このチュートリアルではこのアプローチを使用しません。名前ベースの仮想ホストを展開するためにこのチュートリアルで使用しようとしているアプローチは、リクエストを管理するためにコンテナ化とリバースプロキシを使用しています。私にとっては、複雑さと DevOps のベストプラクティスを満たすためのステップアップです。
仮想化・コンテナ化
今日では、Dockerなどのソフトウェアは、コンテナ化とも呼ばれるオペレーティングシステムレベルの仮想化を実行しており、実行中のプログラムから見ると、実際のコンピュータのように見えます。これを使用する利点は、異なるコンテナで動作するサービス間の分離が可能になることで、あるコンテナで動作しているウェブサイトは、別のコンテナで動作しているウェブサイトとは(デフォルトでは)一切の接続を持ちません。
このようにして、複数のウェブサイトは、1つのECSインスタンス上で同時にコンテナ(オペレーティングシステム)で実行することができ、完全にお互いを認識していないことになります。彼らはホストで直接実行している場合と同じようにポート80を公開することさえあります。そして、Dockerはホスト内のランダムな利用可能なポートをゲートウェイとして割り当てます。
逆プロキシ
そして、おっしゃる通り、コンテナ化自体では、各サイトの訪問者に適切なサイトを提供するためのHTTPリクエストを処理するという問題はまだ解決していません。そこで必要になるのがリバースプロキシという、コンテナとしても動作し、リクエストをインターセプトして正しい場所に送るサービスで、バーチャルホスティングを扱うためのメインの部分になります。
次の図は、リバースプロキシがインターネットからのリクエストを受け取り、内部ネットワーク内のサーバに転送する様子を示しています。このプロキシにリクエストを出している訪問者は気づかないかもしれません。
私のお気に入りの解決策は、開発者の "jwilder "のオリジナルプロジェクトから "neilpang "が作ったフォークであるneilpang/nginx-proxyです。このフォークには “acme.sh "も含まれており、SSLを取得するためのプロセスを自動化しています。これは、あなたのサイトを保護し、同時に安全にするための本当に良いリバースプロキシであることがわかりました。
公式READMEによると、このnginx-proxyは「nginxとdocker-genを実行しているコンテナをセットアップします。これは nginx のリバースプロキシ設定を生成し、コンテナの起動時と停止時に nginx をリロードします。」つまり、基本的にはホスト上で稼働しているすべてのコンテナを追跡し、コンテナが追加されたときにリアルタイムでルールを作成します。
すべてのパーツをまとめる
OK! これでDocker上で動作し、リバースプロキシを持ち、少なくとも2つのウェブサイトが動作するような名前ベースのバーチャルホストソリューションが必要だということがわかりました。このハウツーのタイトルにあるように、マルチコンテナアプリケーションを定義して実行するための素晴らしいツールであるDocker Composeを使ってすべてをラップします。
docker-compose.yml設定ファイルの各コンテナは “service "と呼ばれ、YAML形式でサービス配列に入ります。このファイルのサービスは以下のようになります。
1、proxy
2、web1 (web1.com)
3、web2 (web2.com)
シンプルにするために、私は両方のウェブサービスでApache(php:7.2-apache)とPHP 7.2の標準的で修正されていない公式イメージを使用しています。”web1 "と “web2 "は、したがって、Apacheからの "It works!"という一般的なメッセージが表示されます。これは、コンテナにコマンドラインで docker exec -it web1 bash と入力することで変更することができます。ECS上での実行
ホストに直接接続
すでにインスタンスを持っている場合は、公式のドキュメントに従ってDockerをインストールし、以下のような内容の「docker-compose.yml」というファイルを作成して、通常のDocker Composeプロジェクトのように実行してください。
docker-compose.yml
version: '3.6' services: proxy: image: neilpang/nginx-proxy network_mode: bridge container_name: proxy restart: on-failure ports: - "80:80" - "443:443" environment: - ENABLE_IPV6=true volumes: - ./certs:/etc/nginx/certs - /var/run/docker.sock:/tmp/docker.sock:ro web1: image: php:7.2-apache network_mode: bridge container_name: web1 restart: on-failure environment: - VIRTUAL_HOST=web1.com web2: image: php:7.2-apache network_mode: bridge container_name: web2 restart: on-failure environment: - VIRTUAL_HOST=web2.comTerraformで
もしまだECSインスタンスを持っていないのであれば、Alibaba Cloudのサポートが充実しているので、Terraformでやることをおすすめします。Terraform for Alicloudをまずローカルマシンで稼働させる方法については、こちらの記事をチェックしてみてください。
その記事の指示に従ってTerraformを動作させたら、いよいよmain.tfとuser-data.shを作成します。
main.tf
variable "access_key" { type = "string" default = "XXXXX" } variable "secret_key" { type = "string" default = "XXXXX" } variable "region" { type = "string" default = "ap-southeast-2" } variable "vswitch" { type = "string" default = "XXX-XXXXX" } variable "sgroups" { type = "list" default = [ "XX-XXXXX" ] } variable "name" { type = "string" default = "multi-tenant" } variable "password" { type = "string" default = "Test1234!" } provider "alicloud" { access_key = "${var.access_key}" secret_key = "${var.secret_key}" region = "${var.region}" } data "alicloud_images" "search" { name_regex = "^ubuntu_16.*_64" } data "alicloud_instance_types" "default" { instance_type_family = "ecs.xn4" cpu_core_count = 1 memory_size = 1 } data "template_file" "user_data" { template = "${file("user-data.sh")}" } resource "alicloud_instance" "web" { instance_name = "${var.name}" image_id = "${data.alicloud_images.search.images.0.image_id}" instance_type = "${data.alicloud_instance_types.default.instance_types.0.id}" vswitch_id = "${var.vswitch}" security_groups = "${var.sgroups}" internet_max_bandwidth_out = 100 password = "${var.password}" user_data = "${data.template_file.user_data.template}" } output "ip" { value = "${alicloud_instance.web.public_ip}" }user-data.sh
#!/bin/bash -v # Create docker-compose.yml cat <<- 'EOF' > /opt/docker-compose.yml version: '3.6' services: proxy: image: neilpang/nginx-proxy network_mode: bridge container_name: proxy restart: on-failure ports: - "80:80" - "443:443" environment: - ENABLE_IPV6=true volumes: - ./certs:/etc/nginx/certs - /var/run/docker.sock:/tmp/docker.sock:ro web1: image: php:7.2-apache network_mode: bridge container_name: web1 restart: on-failure environment: - VIRTUAL_HOST=web1.com web2: image: php:7.2-apache network_mode: bridge container_name: web2 restart: on-failure environment: - VIRTUAL_HOST=web2.com EOF apt-get update && apt-get install -y apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" apt-get update && apt-get install -y docker-ce docker-compose curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose cd /opt && docker-compose up -dそして、いつものように terraform init, terraform plan, terraform apply を実行してください。Terraformから確認の連絡が来てから約4~5分後にパッケージのインストールが開始されます。
まとめ
マシン内のリソースを効率的に共有して複数のサービスを同時に実行する方法を学びました。あなたが覚えておくべき唯一のことは、サイトがより忙しくなる場合は、ECSインスタンスをアップグレードする必要がありますので、使用状況を追跡することです(余分なCPUを追加したり、RAMメモリを増加させることによって)。このソリューションは、多くのトラフィックを持つ小さなウェブサイトに最適であり、あなたは多くのホスティングの概念とその複雑さを学ぶことができます。あなたがまだ理解していない場合は、フォーラムでこの記事を参照してください。
アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ
- 投稿日:2020-07-09T10:42:29+09:00
Docker Install in Ubuntu
Docker on Ubuntu 覚書
Dockerを入れて2日目の初心者が次回インストールするときにも見れるように分かっていることを書く。
インストール方法 公式ガイド: https://docs.docker.com/engine/install/
今回の環境
- OS: Ubuntu Budgie 20.04
インストールから
今回は公式リポジトリからインストールする
1. リポジトリの追加
アップデート
sudo apt -y update && sudo apt -y upgrade sudo apt -y install apt-transport-https ca-certificates curl gnupg-agent software-properties-common公式GPG鍵の追加
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -フィンガープリントでキーの確認
sudo apt-key fingerprint 0EBFCD88下記の表示が出ればOK
pub rsa4096 2017-02-22 [SCEA] 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 uid [ unknown] Docker Release (CE deb) <docker@docker.com> sub rsa4096 2017-02-22 [S]リポジトリ追加 (stable 版)
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"※Linux Mint どでは$(lsb_release -cs)コマンドの箇所をUbuntuなど親の名前に変更する必要があるとのこと
2. Docker Engineのインストール
ここでは最新バージョンのインストールのみ記載する(ガイドにはバージョン指定もある)
sudo apt -y update sudo apt -y install docker-ce docker-ce-cli containerd.iohello-worldで確認
テストイメージがダウンロードされてメッセージが表示されて終了します
sudo docker run hello-world3. 古いバージョンのアンインストール
ガイドの一番上に書いてあったが、まだ使わないだろうしということで下のほうに...
sudo apt-get remove docker docker-engine docker.io containerd runc
- 投稿日:2020-07-09T10:24:08+09:00
DockerでPHP7.4の環境を整えるときの問題点
DockerでPHP7.4の環境構築をした話
結論
PHPのDockerfileに以下を加える。
- DockerでPHP7.4系以上を使うなら、ライブラリの「oniguruma」を入れる。
apk add --update --no-cache oniguruma-dev \
を追記。- DockerでMySQLを使うなら、PDOドライバーを入れよう。
RUN docker-php-ext-install -j$(nproc) pdo_mysql
を追記。対象
- DockerでPHP7.4以上を使いたい方
- DockerでMySQLの環境構築。
環境
- OS Catalina 10.15.4
- Docker 2.3.0.3
- php:7.4.7-fpm-alpine
はじめに
phpenvを用いてPHPのバージョン管理すればDockerなんて使わなくていいやと思ってたんですけど、MySQLを使うことになって、5.7と8.0系で認証方式が違うとか、8.0系の方が早いとかありますけど、手元のpcにbrewで8.0から5.7系にダウングレードしたらお手上げ状態になったので、勉強も兼ねてサクッとDockerを使いました。
なお、Dockerfileのことしか主にコードを開示していないので、その他は頑張ってください。問題点
- php:7.4.0*の環境のDockerfileをビルド時
No package 'oniguruma' found
とエラー。- PHPでMySQLを使おうとすると
could not find driver
というエラー上記にもあげている通り、2点問題に遭遇しました。
php:7.4.*の環境のDockerfileをビルド時
No package 'oniguruma' found
とエラーがでよる。はい、1つ目です。
DockerfileRUN apk upgrade --update \ && apk add --update --no-cache oniguruma-dev \ # ここ && apk --no-cache --virtual .build-deps add make g++ gcc re2c autoconf \ && apk --no-cache add gettext-dev libzip-dev curl-dev \ && docker-php-ext-install -j$(nproc) gettext mbstring zip opcache ctype json bcmath sockets curl \ && pecl channel-update pecl.php.netphp:7.4以降は
libonig-dev
というパッケージをする必要があるそうです。
https://github.com/kkos/oniguruma
上記2行目にも書いていますが、apk add --update --no-cache oniguruma-dev
を追加して下さい。PHPでMySQLを使おうとすると
could not find driver
というエラーはい、2つ目です。
このエラーが出ている人はphpinfo();
をphpファイルに書いてもらって、
PDO項目を見ていただくとわかると思うのですが、sqlite
しかないと思われます。
つまりデフォルトでドライバーはsqliteしか入ってないらしいですね。
これには少しびっくりしたと同時に、コンテナは本当に最小限のものしか入ってないのかと思いましたね。。。
というわけでDockerfileに書きましょう。Dockerfile# PDO driver(MySQL) RUN docker-php-ext-install -j$(nproc) pdo_mysql先ほどと同じDockerfileに追記してください。
buildした後、phpinfo();
のPDO driver項目を見るとmysql
が追加されていると思います。
他のデータベースを使いたい時も同じようにDockerfileにかけば使用できるでしょう。PHPのDockerfile全体
今回はUbuntuでなく、Alpineで軽量化しています。
なので皆さんご存知のapt-getは使えず、apkというパッケージマネージャを使う必要があります。DockerfileFROM php:7.4.7-fpm-alpine ARG environment RUN apk upgrade --update \ && apk add --update --no-cache oniguruma-dev \ && apk --no-cache --virtual .build-deps add make g++ gcc re2c autoconf \ && apk --no-cache add gettext-dev libzip-dev curl-dev \ && docker-php-ext-install -j$(nproc) gettext mbstring zip opcache ctype json bcmath sockets curl \ && pecl channel-update pecl.php.net # PDO(MySQL) RUN docker-php-ext-install -j$(nproc) pdo_mysql
まとめ
- PHP7.4以上を使う場合はパッケージ
oniguruma
をDockerfileに追記する。
- PDOドライバーはデフォルトでsqliteしか入ってない。
参考
- 投稿日:2020-07-09T09:43:15+09:00
circleci ビルドができない、、、
ビルドすることに苦戦しすぎているので、備忘録メモ。まだ完璧に解決できていないが、一歩進んだので投稿
本題の前に一言
circleciのエラー文って不親切やし、めっちゃ難しい。dockerをちゃんと勉強しろってことなのか。
改めて現役エンジニアを尊敬する。参考URL
https://github.com/docker-library/mysql/issues/129#issuecomment-178265632
登場ファイル
- .circleci/cofing.yml
- database.yml
- docker-comopose.yml
今回の抱えている問題
・MySQL、コンテナの立ち上げ失敗←今回で解決できていないが、前進した。
・コンテナの立ち上げに失敗しているからdb:createも失敗
・rubocopを実行できていない(db:createをできていないから?)
・/tmp/test-resultsがあるのに、Not Foundになってしまう←今回で解決できていない。ファイルの中身(エラー発生時)
circleci/cofing.ymlversion: 2.1 orbs: ruby: circleci/ruby@0.1.2 jobs: build: docker: # specify the version you desire here - image: circleci/ruby:2.6.6-stretch-node environment: - RAILS_ENV: 'test' - image: circleci/mysql:8.0 name: "db" command: mysqld --default-authentication-plugin=mysql_native_password environment: - MYSQL_PASSWORD: "password" - MYSQL_ROOT_HOST: '%' # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ # - image: circleci/postgres:9.4 working_directory: ~/アプリ名 steps: - checkout # Download and cache dependencies - restore_cache: keys: - v1-dependencies-{{ checksum "Gemfile.lock" }} # fallback to using the latest cache if no exact match is found - v1-dependencies- - run: bundle install --jobs=4 --retry=3 --path vendor/bundle - save_cache: paths: - ./vendor/bundle key: v1-dependencies-{{ checksum "Gemfile.lock" }} # Database setup - run: bundle exec rake db:create - run: bundle exec rake db:schema:load # rubocop。 - run: name: Rubocop command: bundle exec rubocop -a # rspec # run tests! - run: name: run tests command: | mkdir /tmp/test-results TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ circleci tests split --split-by=timings)" bundle exec rspec \ --format progress \ --format RspecJunitFormatter \ --out /tmp/test-results/rspec.xml \ --format progress \ $TEST_FILES # collect reports - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-resultsdatabase.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= ENV.fetch("MYSQL_USERNAME", "root") %> password: <%= ENV.fetch("MYSQL_PASSWORD", "password") %> host: <%= ENV.fetch("MYSQL_HOST", "db") %> development: <<: *default database: アプリ名_development test: <<: *default database: アプリ名_test production: <<: *default database: <%= ENV['DB_DATABASE'] %> adapter: mysql2 encoding: utf8mb4 charset: utf8mb4 collation: utf8mb4_general_ci host: <%= ENV['DB_HOST'] %> username: <%= ENV['DB_USERNAME'] %> password: <%= ENV['DB_PASSWORD'] %>docker-comopose.ymlversion: '3' services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: password ports: - '3306:3306' command: --default-authentication-plugin=mysql_native_password volumes: - mysql-data:/var/lib/mysql web: build: . command: bundle exec puma -C config/puma.rb environment: RAILS_ENV: development volumes: - .:/アプリ名 - bundle:/usr/local/bundle - /app/vendor - /app/log - /app/.git ports: - "3000:3000" depends_on: - db tty: true stdin_open: true nginx: build: context: . dockerfile: ./nginx/Dockerfile ports: - '80:80' depends_on: - web chrome: image: selenium/standalone-chrome ports: - "4444:4444" shm_size: "2g" volumes: mysql-data: driver: local bundle: driver: localやったこと
開発でMySQLのコンテナの立ち上げに成功しているから、docker-comose.ymlに書いてあることを.circleci/cofing.ymlでも真似すればいけるんじゃないかと考えて、.circleci/cofing.ymlを以下のように修正
circleci/cofing.ymlversion: 2.1 orbs: ruby: circleci/ruby@0.1.2 jobs: build: docker: # specify the version you desire here - image: circleci/ruby:2.6.6-stretch-node environment: - RAILS_ENV: 'test' - image: circleci/mysql:8.0 name: "db" command: mysqld --default-authentication-plugin=mysql_native_password environment: + - MYSQL_ROOT_PASSWORD: password 追記 - - MYSQL_PASSWORD: "password" 削除 - - MYSQL_ROOT_HOST: '%' 削除 # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ # - image: circleci/postgres:9.4 working_directory: ~/アプリ名 steps: - checkout # Download and cache dependencies - restore_cache: keys: - v1-dependencies-{{ checksum "Gemfile.lock" }} # fallback to using the latest cache if no exact match is found - v1-dependencies- - run: bundle install --jobs=4 --retry=3 --path vendor/bundle - save_cache: paths: - ./vendor/bundle key: v1-dependencies-{{ checksum "Gemfile.lock" }} # Database setup - run: bundle exec rake db:create - run: bundle exec rake db:schema:load # rubocop。 - run: name: Rubocop command: bundle exec rubocop -a # rspec # run tests! - run: name: run tests command: | mkdir /tmp/test-results TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ circleci tests split --split-by=timings)" bundle exec rspec \ --format progress \ --format RspecJunitFormatter \ --out /tmp/test-results/rspec.xml \ --format progress \ $TEST_FILES # collect reports - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-results結果
コンテナの作成のところで「!」マークだったのが、「ー」マークに変わった!!(ただしエラーは解決できていないっぽい。。。)「!」と「ー」の違いは何なんやろ?
それでもrubocopは実行できるようになったので、前進はできた。
メモ
解決進んだら、また投稿。
/tmp/test-resultsのエラーがホンマに分からへん。デフォルト同じなはずやのに。。。
- 投稿日:2020-07-09T09:11:44+09:00
GASでなんちゃってORマッパー
はじめに
GASでデータを取るのがめんどくさかったのでORマッパー的なものが欲しくなったので作りました。
主な機能
- テーブル操作
- find,
- select
- discard
- insert
- inner
- join
- outer join
- cross join
- not
- update
- transaction (begin, save, commit, rollback)
- record iterator
- order by
- sort
- sub query
- create
- レコード操作
- get
- set (update)
- validation
- namespace
- create
使い方
プロジェクト新規作成
$ npm run setup:create ※途中でGoogleアカウントログインを要求されます。既存プロジェクトにアップする場合
$ npm run setup:cloneDocker起動・終了
$ npm run docker:up $ npm run docker:downログイン
$ npm run loginソース監視・自動ビルド
$ npm run watchプッシュ
$ npm run pushサンプル用テーブル(シート)作成
global.growSeeeds = function () { growSeeds() }※スクリプトエディタ上でグローバル関数
growSeeeds()
を実行してください。シートとデータが作成されます。Examples
- サンプル用テーブル作成
- テーブル操作
- レコード操作
- Api
一部サンプル
GET
declare var global: any; const users = McTable.use( { name: 'users' } ) // テーブル(シート)名渡す // spreadsheetを指定すると他のスプレットシートのデータを読み込ませられる // const users = McTable.use( { name: 'users', spreadsheet: SpreadsheetApp.getActiveSpreadsheet() } ) const authorities = McTable.use( { name: 'authorities' } ) const countries = McTable.use( { name: 'countries' } ) global.doGet = function ( e ) { const id = +e.parameter.id const resultTable = users // usersとauthoritiesをusersのauth_idとauthoritiesのidをキーに外部結合 .leftOuterJoin( 'auth_id', authorities, 'id' ) // 内部結合したテーブルとcountriesをusers.country_idとcountriesのidをもとに外部結合 .leftOuterJoin( 'users.country_id', countries, 'id' ) // 結合されたテーブルからusersのidをキーにレコード検索 .find( { 'users.id': id } ) // 抽出カラム選択 .select( [ 'users.id', 'users.name', 'countries.id', 'countries.name_ja', 'authorities.auth' ] ) resultTable.each(record => { const countryId = record.get( 'countries.id' ) if ( countryId === 'USA' ) { // 国籍変更 record .set( 'countries.id', 'JPN' ) .set( 'countries.name_ja', 'ジパング' ) } const auth = record // いちいちauthorities.authと入力するのが面倒なのでネームスペースを設定 .setNamespace( 'authorities' ) // authorities.authのデータを取得 .get( 'auth' ) // authorities.authが'admin'の場合にレコードを上書き if ( auth === 'admin' ) { record.set( 'auth', '管理者' ) } const mail = record // ネームスペースをリセット .resetNamespace() .get( 'users.mail' ) // メールダドレスの登録がなければ変わりのアドレスを設定 if ( mail === '' ) { record.set( 'users.mail', 'sample@_gmail.com' ) } }) const result = JSON.stringify({ data: resultTable.hashes() }) return ContentService.createTextOutput( result ); } // <エントリーポイントURL>?id=1 // こんな感じで帰ってくる { "data":[ { "users.id":1, "users.name":"John", "countries.id":"JPN", "countries.name_ja":"ジパング", "authorities.auth":"管理者" } ] }レコード検索
// テーブル取得 const users = McTable.use( { name: 'users' } ) // --------------------------------------------------------------------------------------------------------------------- // SELECT * FROM users WHERE id=1 // --------------------------------------------------------------------------------------------------------------------- users.find( { id: 1 } ) // foundUsers.data //=> [ [ 1, 'John', 'john@_gmail.com', 1, 'USA' ] ] // foundUsers.cols //=> [ 'id', 'name', 'mail', 'auth_id', 'country_id' ] // foundUsers.colsLength //=> 1 // foundUsers.existsSheet //=> true // foundUsers.existsCols //=> true // foundUsers.existsRecords //=> true // foundUsers.records //=> [ { McRecord } ] // foundUsers.recordsLength //=> 1 // foundUsers.firstRecord //=> { McRecord } // foundUsers.lastRecord //=> { McRecord } // foundUsers.mcSS //=> { McSS } // foundUsers.mcSheet //=> { McSheet } // foundUsers.savepoints //=> { McSheet } // foundUsers.latest //=> { McSheet } // --------------------------------------------------------------------------------------------------------------------- // SELECT id, name FROM users WHERE id IN(1, 2) AND name="John" // --------------------------------------------------------------------------------------------------------------------- result = users .find( { id: [ 1, 2 ], name: 'John' } ) .select( [ 'id', 'name' ] ) Logger.log( result.hashes() ) // [ // { // name=John, // id=1.0 // } // ] // --------------------------------------------------------------------------------------------------------------------- // SELECT auth_id FROM (SELECT * FROM users WEHERE id NOT 1) WHERE id IN(1, 2, 3) // --------------------------------------------------------------------------------------------------------------------- result = users.find( { id: [ 1, 2, 3, 4 ] } ).not( { id: 1 } ).select( [ 'auth_id' ] ) Logger.log( result.hashes() ) // [ // { // auth_id=1.0 // }, // { // auth_id=1.0 // }, // { // auth_id=3.0 // } // ] // --------------------------------------------------------------------------------------------------------------------- // SELECT * FROM users ORDER BY id DESC // --------------------------------------------------------------------------------------------------------------------- result = users.orderByDesc( 'id' ) Logger.log( result.hashes() ) // [ // { // id=5.0, // name=pochi, // mail=foo@gmail.com, // country_id=JPN // auth_id=3.0, // }, // { // id=4.0, // name=James, // mail=james@_gmail.com, // country_id=USA // auth_id=3.0, // }, // { // id=3.0, // name=Emma, // country_id=USA, // mail=emma@gmail.com // auth_id=1.0, // }, // { // id=2.0, // name=Dick, // country_id=USA, // mail=dick@_gmail.com, // auth_id=1.0 // }, // { // id=1.0, // name=John // country_id=USA, // mail=foo@_gmail.com, // auth_id=1.0, // } // ]レコード操作1
const users = McTable.use( { name: 'users' } ) const record1 = users.firstRecord //=> [ 1, 'John', 'foo@_gmail.com', 1, 'USA' ] McRecordはArrayクラスを継承しているので、基本的な振る舞いはArrayクラスと同じ record1.get( 'id' ) //=> 1 record1.get( 'name' ) //=> 'John' record1.get( 'mail' ) //=> 'foo@_gmail.com' record1.get( 'auth_id' ) //=> 1 record1.get( 'country_id' ) //=> 'USA' record1.set( 'id', 999 ) //=> [ 999, 'John', 'john@_gmail.com', 1, 'USA' ] record1.set( 'name', 'Smith' ) //=> [ 999, 'Smith', 'john@gmail.com', 1, 'USA' ] .set( 'mail', 'smith@_gmail.com' ) //=> [ 999, 'Smith', 'smith@_gmail.com', 1, 'USA' ] .set( 'auth_id', 2 ) //=> [ 999, 'Smith', 'smith@_gmail.com', 2, 'USA' ] .set( 'country_id', 'JPN' ) //=> [ 999, 'Smith', 'smith@_gmail.com', 2, 'JPN' ] .hash() //=> { id: 999, name: 'Smith' , mail: 'smith@_gmail.com', auth_id: 2, country_id: 'JPN' }レコード操作2
const users = McTable.use( { name: 'users' } ) let record // --------------------------------------------------------------------------------------------------------------------- // デフォルトモード(strict=false)、バリデートを通らなかったデータは''で登録 // --------------------------------------------------------------------------------------------------------------------- record = users.Record({ data: { id : 999, name : 'Smith' , mail : 'smith@_gmail.com', auth_id : 2, country_id: 'JPN' }, validators: { // 条件に一致しない( falseを返す )データを弾く id : McRecord.validators.number, name : McRecord.validators.string, mail : McRecord.validators.string, auth_id : McRecord.validators.string, // 数値型の値に文字列用のバリデーションを設定 country_id: McRecord.validators.string, } }) Logger.log( record ) // [999.0, Smith, smith@_gmail.com, 2.0, JPN] Logger.log( record.hash() ) // {name=Smith, country_id=JPN, mail=smith@_gmail.com, auth_id=, id=999.0} // auth_id=には空白が設定される Logger.log( record.get( 'mail' ) ) // "smith@_gmail.com" // --------------------------------------------------------------------------------------------------------------------- // strictモード(strict=true)、バリデートを通らなかったデータがある場合にエラーになる // --------------------------------------------------------------------------------------------------------------------- record = users.Record({ strict: true, data: { id : 999, name : 'Smith' , mail : 'smith@_gmail.com', auth_id : 2, country_id: 'JPN' }, validators: { // 条件に一致しない( falseを返す )データを弾く // バリデーションが設定されていない場合はデータの検証をしない auth_id: McRecord.validators.string, } }) // Error: Invalid Col [ auth_id ] Data [ 2 ] record = users.Record({ strict: true, data: { id : 999, name : 'Smith' , mail : 'smith@_gmail.com', auth_id : 2, country_id: 'JPN' }, validators: { // 条件に一致しない( falseを返す )データを弾く id : McRecord.validators.number, } }) // set()内でもバリデーションを行う record.set( 'id', 'aaa' ) // Error: Invalid Col [ id ] Data [ aaa ] record = McRecord.create({ strict: true, cols: [ 'id', 'name', 'mail', 'auth_id', 'country_id' ], data: { id : 999, name : 'Smith' , mail : 'smith@_gmail.com', auth_id : 2, country_id: 'JPN' }, validators: { auth_id: McRecord.validators.string, } }) // Error: Invalid Col [ auth_id ] Data [ 2 ] // --------------------------------------------------------------------------------------------------------------------- // 関数を介した作成 // --------------------------------------------------------------------------------------------------------------------- function createUsersRecord ( data: object ): McRecord { const validators = { // 設定されていないデータは検査しない id : McRecord.validators.number, name: McRecord.validators.string, mail: McRecord.validators.string, } return users.Record( { data, validators } ) } record = createUsersRecord({ id : 999, name : 'Smith' , mail : 'smith@_gmail.com', auth_id : 2, country_id: 'JPN' }) // --------------------------------------------------------------------------------------------------------------------- // バリデータ自作 // --------------------------------------------------------------------------------------------------------------------- // バリデーション用関数 function validateMail ( v: any ) { return isString( v ) && /.*@_gmail/.test( v ) } // バリデーション関数を登録 McRecord.setValidator( 'validateName', v => { return isString(v) }) record = users.Record({ data: { id : 999, name : 'Smith' , mail : 'smith@_gmail.com', auth_id : 2, country_id: 'JPN' }, validator: { /** * 条件に一致しない( falseを返す )データを弾く * @validator * @param { * } v * @returns { boolean } */ mail : validateMail, auth_id: v => v === 1 || v === 2 || v === 3, name : McRecord.validators.validateName } })トランザクション
users.begin({ // Set users.savepoints. // The savepoint is updated when the instance is created and when the begin function is executed or when the save function is executed // users.savepoints holds a duplicate instance of an object type // The latest version is recorded in users.latest in numeric type /** * @param { McTable } users */ handler: users => { users.insert( record ).commit() users.insert( records ).commit() users .insert( record ) // insert McRecord .insert( records ) // insert McRecord array .insert( depulicateUsers ) // insert Mctable records .insert({ // insert object id : 999, name : 'Smith', mail : 'smith@_gmail.com', auth_id : 2, country_id: 'JPN' }) .commit() // throw new Error( 'Error occurrence!' ) // It rolls back the data in the sheet using the instance that was saved in the savepoint when the error occurred. }, /** * @param { any } e Error object * @param { McTable } rollbacked */ exceptionHander: ( e, rollbacked ) => { Logger.log( e ) } })テーブル更新
users.begin({ handler ( users: McTable ) { users // レコードを一件インサート .insert(record1) // レコードを一件インサート .insert(record2) // 複数レコードをインサート .insert(records) // テーブルのレコードを纏めてインサート .insert(users) // nameがPochiのレコードを更新 .each(record => { const name = record.get( 'name' ) if ( name === 'Pochi' ) { record.set( 'name', 'Tama' ) } }) // シートへ反映 .commit() }, exceptionHander ( e: any, rollbackedTable: McTable ) { // エラー時にuser.begin()実行時点へシートをロールバック Logger.log( e ) } })
- 投稿日:2020-07-09T09:11:44+09:00
GASでORマッパー
@@ -1,546 +0,0 @@
-## はじめに
-GASでデータを取るのがめんどくさくてORマッパー的なものが欲しくなったので作りました。
-
-## 主な機能
-- テーブル操作
- - find,
- - select
- - discard
- - insert
- - inner
- - join
- - outer join
- - cross join
- - not
- - update
- - transaction (begin, save, commit, rollback)
- - record iterator
- - order by
- - sort
- - sub query
- - create
-- レコード操作
- - get
- - set (update)
- - validation
- - namespace- - create
-Github
-## 使い方
-#### プロジェクト新規作成
-```-$ npm run setup:create
-※途中でGoogleアカウントログインを要求されます。
-
-### 既存プロジェクトにアップする場合
-
-$ npm run setup:clone-```
-#### Docker起動・終了
-
-$ npm run docker:up
-$ npm run docker:down
-
-#### ログイン
-
-$ npm run login
-
-#### ソース監視・自動ビルド
-
-$ npm run watch
-
-#### プッシュ
-```
-$ npm run push-```
-### サンプル用テーブル(シート)作成
-
-
javascript
-global.growSeeds = function () {
- growSeeds()
-}
--※スクリプトエディタ上でグローバル関数
growSeeeds()
を実行してください。シートとデータが作成されます。-## Examples
-- サンプル用テーブル作成
- - users
- - countries
- - authorities
- - itarator
-- テーブル操作
- - query
- - sub query
- - inner join
- - outer join
- - cross join
- - update
- - transaction
-- レコード操作
- - record
- - namespace
- - validator
-- Api- - doGet
-### 一部サンプル
-
-#### GET
-``` javascript-declare var global: any;
-const users = McTable.use( { name: 'users' } ) // テーブル(シート)名渡す
-// spreadsheetを指定すると他のスプレットシートのデータを読み込ませられる
-// const users = McTable.use( { name: 'users', spreadsheet: SpreadsheetApp.getActiveSpreadsheet() } )
-const authorities = McTable.use( { name: 'authorities' } )-const countries = McTable.use( { name: 'countries' } )
-global.doGet = function ( e ) {
- const id = +e.parameter.id
- const resultTable = users
- // usersとauthoritiesをusersのauth_idとauthoritiesのidをキーに外部結合
- .leftOuterJoin( 'auth_id', authorities, 'id' )
- // 内部結合したテーブルとcountriesをusers.country_idとcountriesのidをもとに外部結合
- .leftOuterJoin( 'users.country_id', countries, 'id' )
- // 結合されたテーブルからusersのidをキーにレコード検索
- .find( { 'users.id': id } )
- // 抽出カラム選択
- .select( [
- 'users.id',
- 'users.name',
- 'countries.id',
- 'countries.name_ja',
- 'authorities.auth'
- ] )
- resultTable.each(record => {
- const countryId = record.get( 'countries.id' )
- if ( countryId === 'USA' ) {
- // 国籍変更
- record
- .set( 'countries.id', 'JPN' )
- .set( 'countries.name_ja', 'ジパング' )
- }
- const auth = record
- // いちいちauthorities.authと入力するのが面倒なのでネームスペースを設定
- .setNamespace( 'authorities' )
- // authorities.authのデータを取得
- .get( 'auth' )
- // authorities.authが'admin'の場合にレコードを上書き
- if ( auth === 'admin' ) {
- record.set( 'auth', '管理者' )
- }
- const mail = record
- // ネームスペースをリセット
- .resetNamespace()
- .get( 'users.mail' )
- // メールダドレスの登録がなければ変わりのアドレスを設定
- if ( mail === '' ) {
- record.set( 'users.mail', 'sample@_gmail.com' )
- }
- })
- const result = JSON.stringify({
- data: resultTable.hashes()
- })
- return ContentService.createTextOutput( result ); -} - -// <エントリーポイントURL>?id=1 -// こんな感じで帰ってくる -{
- "data":[
- {
- "users.id":1,
- "users.name":"John",
- "countries.id":"JPN",
- "countries.name_ja":"ジパング",
- "authorities.auth":"管理者"
- }
- ] -} -
- -#### レコード検索 -
javascript - -// テーブル取得 -const users = McTable.use( { name: 'users' } ) - -// --------------------------------------------------------------------------------------------------------------------- -// SELECT * FROM users WHERE id=1 -// --------------------------------------------------------------------------------------------------------------------- - -users.find( { id: 1 } ) -// foundUsers.data //=> [ [ 1, 'John', 'john@_gmail.com', 1, 'USA' ] ] -// foundUsers.cols //=> [ 'id', 'name', 'mail', 'auth_id', 'country_id' ] -// foundUsers.colsLength //=> 1 -// foundUsers.existsSheet //=> true -// foundUsers.existsCols //=> true -// foundUsers.existsRecords //=> true -// foundUsers.records //=> [ { McRecord } ] -// foundUsers.recordsLength //=> 1 -// foundUsers.firstRecord //=> { McRecord } -// foundUsers.lastRecord //=> { McRecord } -// foundUsers.mcSS //=> { McSS } -// foundUsers.mcSheet //=> { McSheet } -// foundUsers.savepoints //=> { McSheet } -// foundUsers.latest //=> { McSheet } - - -// --------------------------------------------------------------------------------------------------------------------- -// SELECT id, name FROM users WHERE id IN(1, 2) AND name="John" -// --------------------------------------------------------------------------------------------------------------------- - -result = users- .find( { id: [ 1, 2 ], name: 'John' } )
- .select( [ 'id', 'name' ] )
-Logger.log( result.hashes() )
-// [
-// {
-// name=John,
-// id=1.0
-// }-// ]
-// ---------------------------------------------------------------------------------------------------------------------
-// SELECT auth_id FROM (SELECT * FROM users WEHERE id NOT 1) WHERE id IN(1, 2, 3)-// ---------------------------------------------------------------------------------------------------------------------
-result = users.find( { id: [ 1, 2, 3, 4 ] } ).not( { id: 1 } ).select( [ 'auth_id' ] )
-Logger.log( result.hashes() )
-// [
-// {
-// auth_id=1.0
-// },
-// {
-// auth_id=1.0
-// },
-// {
-// auth_id=3.0
-// }-// ]
-// ---------------------------------------------------------------------------------------------------------------------
-// SELECT * FROM users ORDER BY id DESC-// ---------------------------------------------------------------------------------------------------------------------
-result = users.orderByDesc( 'id' )
-Logger.log( result.hashes() )
-// [
-// {
-// id=5.0,
-// name=pochi,
-// mail=foo@gmail.com,
-// country_id=JPN
-// auth_id=3.0,
-// },
-// {
-// id=4.0,
-// name=James,
-// mail=james@_gmail.com,
-// country_id=USA
-// auth_id=3.0,
-// },
-// {
-// id=3.0,
-// name=Emma,
-// country_id=USA,
-// mail=emma@gmail.com
-// auth_id=1.0,
-// },
-// {
-// id=2.0,
-// name=Dick,
-// country_id=USA,
-// mail=dick@_gmail.com,
-// auth_id=1.0
-// },
-// {
-// id=1.0,
-// name=John
-// country_id=USA,
-// mail=foo@_gmail.com,
-// auth_id=1.0,
-// }-// ]
-```
-#### レコード操作1
-``` javascript
-const users = McTable.use( { name: 'users' } )
-const record1 = users.firstRecord //=> [ 1, 'John', 'foo@_gmail.com', 1, 'USA' ] McRecordはArrayクラスを継承しているので、基本的な振る舞いはArrayクラスと同じ
-record1.get( 'id' ) //=> 1
-record1.get( 'name' ) //=> 'John'
-record1.get( 'mail' ) //=> 'foo@_gmail.com'
-record1.get( 'auth_id' ) //=> 1-record1.get( 'country_id' ) //=> 'USA'
-record1.set( 'id', 999 ) //=> [ 999, 'John', 'john@_gmail.com', 1, 'USA' ]
-record1.set( 'name', 'Smith' ) //=> [ 999, 'Smith', 'john@gmail.com', 1, 'USA' ]
- .set( 'mail', 'smith@_gmail.com' ) //=> [ 999, 'Smith', 'smith@_gmail.com', 1, 'USA' ]
- .set( 'auth_id', 2 ) //=> [ 999, 'Smith', 'smith@_gmail.com', 2, 'USA' ]
- .set( 'country_id', 'JPN' ) //=> [ 999, 'Smith', 'smith@_gmail.com', 2, 'JPN' ]
- .hash() //=> { id: 999, name: 'Smith' , mail: 'smith@_gmail.com', auth_id: 2, country_id: 'JPN' }-```
-#### レコード操作2
-``` javascript
-const users = McTable.use( { name: 'users' } )
-let record
-// ---------------------------------------------------------------------------------------------------------------------
-// デフォルトモード(strict=false)、バリデートを通らなかったデータは''で登録-// ---------------------------------------------------------------------------------------------------------------------
-record = users.Record({
- data: {
- id : 999,
- name : 'Smith' ,
- mail : 'smith@_gmail.com',
- auth_id : 2,
- country_id: 'JPN'
- },
- validators: {
- // 条件に一致しない( falseを返す )データを弾く
- id : McRecord.validators.number,
- name : McRecord.validators.string,
- mail : McRecord.validators.string,
- auth_id : McRecord.validators.string, // 数値型の値に文字列用のバリデーションを設定
- country_id: McRecord.validators.string,
- }-})
-Logger.log( record )
-// [999.0, Smith, smith@_gmail.com, 2.0, JPN]
-Logger.log( record.hash() )
-// {name=Smith, country_id=JPN, mail=smith@_gmail.com, auth_id=, id=999.0}-// auth_id=には空白が設定される
-Logger.log( record.get( 'mail' ) )
-// "smith@_gmail.com"
-
-// ---------------------------------------------------------------------------------------------------------------------
-// strictモード(strict=true)、バリデートを通らなかったデータがある場合にエラーになる-// ---------------------------------------------------------------------------------------------------------------------
-record = users.Record({
- strict: true,
- data: {
- id : 999,
- name : 'Smith' ,
- mail : 'smith@_gmail.com',
- auth_id : 2,
- country_id: 'JPN'
- },
- validators: {
- // 条件に一致しない( falseを返す )データを弾く
- // バリデーションが設定されていない場合はデータの検証をしない
- auth_id: McRecord.validators.string,
- }-})
-// Error: Invalid Col [ auth_id ] Data [ 2 ]
-record = users.Record({
- strict: true,
- data: {
- id : 999,
- name : 'Smith' ,
- mail : 'smith@_gmail.com',
- auth_id : 2,
- country_id: 'JPN'
- },
- validators: {
- // 条件に一致しない( falseを返す )データを弾く
- id : McRecord.validators.number,
- }-})
-// set()内でもバリデーションを行う
-record.set( 'id', 'aaa' )-// Error: Invalid Col [ id ] Data [ aaa ]
-
-record = McRecord.create({
- strict: true,
- cols: [ 'id', 'name', 'mail', 'auth_id', 'country_id' ],
- data: {
- id : 999,
- name : 'Smith' ,
- mail : 'smith@_gmail.com',
- auth_id : 2,
- country_id: 'JPN'
- },
- validators: {
- auth_id: McRecord.validators.string,
- }-})
-// Error: Invalid Col [ auth_id ] Data [ 2 ]
-
-// ---------------------------------------------------------------------------------------------------------------------
-// 関数を介した作成-// ---------------------------------------------------------------------------------------------------------------------
-function createUsersRecord ( data: object ): McRecord {
- const validators = {
- // 設定されていないデータは検査しない
- id : McRecord.validators.number,
- name: McRecord.validators.string,
- mail: McRecord.validators.string,- }
- return users.Record( { data, validators } ) -} - -record = createUsersRecord({
- id : 999,
- name : 'Smith' ,
- mail : 'smith@_gmail.com',
- auth_id : 2,
- country_id: 'JPN' -}) - - -// --------------------------------------------------------------------------------------------------------------------- -// バリデータ自作 -// --------------------------------------------------------------------------------------------------------------------- - -// バリデーション用関数 -function validateMail ( v: any ) {
- return isString( v ) && /.*@_gmail/.test( v ) -} - -// バリデーション関数を登録 -McRecord.setValidator( 'validateName', v => {
- return isString(v) -}) - - -record = users.Record({
- data: {
- id : 999,
- name : 'Smith' ,
- mail : 'smith@_gmail.com',
- auth_id : 2,
- country_id: 'JPN'
- },
- validator: {
- /**
- * 条件に一致しない( falseを返す )データを弾く
- * @validator
- * @param { * } v
- * @returns { boolean }
- */
- mail : validateMail,
- auth_id: v => v === 1 || v === 2 || v === 3,
- name : McRecord.validators.validateName
- } -}) - -
- - -#### トランザクション -
javascript -users.begin({- // Set users.savepoints.
- // The savepoint is updated when the instance is created and when the begin function is executed or when the save function is executed
- // users.savepoints holds a duplicate instance of an object type
- // The latest version is recorded in users.latest in numeric type
- /**
- * @param { McTable } users
- */
- handler: users => {
- users.insert( record ).commit()
- users.insert( records ).commit()
- users
- .insert( record ) // insert McRecord
- .insert( records ) // insert McRecord array
- .insert( depulicateUsers ) // insert Mctable records
- .insert({ // insert object
- id : 999,
- name : 'Smith',
- mail : 'smith@_gmail.com',
- auth_id : 2,
- country_id: 'JPN'
- })
- .commit()
- // throw new Error( 'Error occurrence!' )
- // It rolls back the data in the sheet using the instance that was saved in the savepoint when the error occurred.
- },
- /**
- * @param { any } e Error object
- * @param { McTable } rollbacked
- */
- exceptionHander: ( e, rollbacked ) => {
- Logger.log( e )
- } -}) -
- -#### テーブル更新 -
javascript -users.begin({- handler ( users: McTable ) {
- users
- // レコードを一件インサート
- .insert(record1)
- // レコードを一件インサート
- .insert(record2)
- // 複数レコードをインサート
- .insert(records)
- // テーブルのレコードを纏めてインサート
- .insert(users)
- // nameがPochiのレコードを更新
- .each(record => {
- const name = record.get( 'name' )
- if ( name === 'Pochi' ) {
- record.set( 'name', 'Tama' )
- }
- })
- // シートへ反映
- .commit()
- },
- exceptionHander ( e: any, rollbackedTable: McTable ) {
- // エラー時にuser.begin()実行時点へシートをロールバック
- Logger.log( e )
- } -}) - -```
- 投稿日:2020-07-09T01:39:07+09:00
Jupyter・RStudioみたいにパッと画像を表示する機能を作る【Docker】
概要
普段neovimでPythonやRのスクリプトを書くのですが、グラフをpngやhtmlで出力した後にいちいちファイルを開いて確認するのが面倒でした。新しいファイルが自動でパっと表示される機能がほしいなと思いdockerで作ってみました。
初めに断っておきますが、大したものは作っていません。「ふーん、そんなこと考える人がいるんだ」くらいのテンションで読んでもらえれば嬉しいです。
使い方
まずは以下のスクリプトを
docker-compose.yml
という名前で保存1。docker-composeは使えるようにしておいてください。docker-compose.ymlversion: "3" services: websocket: image: dr666m1/image_watcher_websocket:version-0.0 volumes: - .:/work/sync ports: - "9999:9999" webserver: image: dr666m1/image_watcher_webserver:version-0.0 volumes: - .:/work/sync ports: - "8888:8888" depends_on: - websocketその後、pngやhtmlを出力する予定のディレクトリに移動し、以下のコマンドで起動・停止(
$FILE_PATH
は先ほどのdocker-compose.yml
)。# 起動 docker-compose -f $FILE_PATH --project-directory $(pwd) up -d # 停止 docker-compose -f $FILE_PATH --project-directory $(pwd) down起動中にブラウザで
http://localhost:8888/
を開くと冒頭に載せたような画面になります。仕組み
docker-compose.yml
を見ての通り、2つのdockerコンテナが動いています。以下、それぞれの役割を簡単に説明します。コードは私のgithubに載せています。websocket (image_watcher_websocket)
WebSocketとは何か、という解説は他の記事にお任せします。このdockerコンテナの役割は以下です。
- png・htmlファイルの作成or更新を数秒置きに検知する
- 検知したファイルの情報をブラウザに送信する
実装にはPythonのwebsocket-serverというパッケージを利用しました。
webserver (image_watcher_webserver)
Webサーバーとは何か、という解説も他の記事にお任せします。このdockerコンテナの役割は以下です。
- ローカルの8888番ポートでリクエストを受け付け
index.html
を返す- png・htmlファイルもリクエストがあれば返す
実装にはPythonのFlaskというパッケージを利用しました。
index.html
の中ではReactを多用しています。こだわり
表示・非表示の切り替え
ファイル名の左の「▶」「▼」で、表示・非表示の切り替えができます。
別画面での表示
ファイル名をクリックすると、そのファイルだけ別画面で表示できます。
その他
htmlファイルのscrollHeightに合わせてiframeの縦幅を自動修正したりとか、トップに戻るボタンの実装とかもちょっとしたこだわりです。
最後に
業務では分析用のPython・R・SQLくらいしか書かないので、Reactで画面を作る作業は勉強になりました。
見たことないdockerイメージが指定されていると思いますが、私が作成したものです。DockerHubで公開しているので普通に
docker pull
できるはずです。 ↩
- 投稿日:2020-07-09T00:11:26+09:00
curl でダウンロードしつつ任意の場所にtar展開する
ruby:2.6.5-slim-stretch
のDockerイメージを使い yarn を入れるのに苦労したので記録を残しておきます。RUN部分の抜粋。
ENV YARN_VERSION=1.22.4 RUN bash -c "curl -sL --compressed https://yarnpkg.com/downloads/${YARN_VERSION}/yarn-v${YARN_VERSION}.tar.gz | tee >(tar zx -C /usr/local/ --strip=1 --wildcards yarn*/bin --no-same-owner --no-same-permissions) | tar zx -C /usr/local/ --strip=1 --wildcards yarn*/lib --no-same-owner --no-same-permissions"3つのコマンドをパイプで連結した文字列を用意して
bash -c "curl..."
で実行するようにしています。はじめに curl を使って指定されたバージョンの yarn を標準出力に表示します。
curl -sL --compressed https://yarnpkg.com/downloads/${YARN_VERSION}/yarn-v${YARN_VERSION}.tar.gztee を使い出力先を分岐します。
/usr/local/bin
に tar内の*/bin
以下を展開します。| tee >(tar zx -C /usr/local/ --strip=1 --wildcards yarn*/bin --no-same-owner --no-same-permissions)同様に
/usr/local/lib
に tar 内の*/lib
以下を展開します。| tar zx -C /usr/local/ --strip=1 --wildcards yarn*/lib --no-same-owner --no-same-permissions
bash -c ""
をしているのは、 2つ目の| tee >(tar...)
にてプロセス展開を行うためです。
docker build
を行う際にsh
環境で実行されるので、プロセス展開部分で、/bin/sh: 1: Syntax error: "(" unexpected
がでてしばらく悩みました…。
- 投稿日:2020-07-09T00:11:17+09:00
Dockerを使ったphp-fpm(+Laravel)とNginx環境の構築
背景
「LaravelってどうやってHTTPサーバと連携するんだ?」と思い、調査しましたが結構はまったので、自分用のメモも兼ねて記事を書きます。
やりたいこと
・Laravelを使ったWebアプリサーバを立てたい
・Dockerを使って、コマンド一発で楽にWebサーバとWebアプリサーバを立てたい特にDockerを使ってWebサーバとWebアプリサーバを立てるのは、Kubernetesを使うのに必須だったりするので、鍛錬もかねて、実施しました。
システムの全体図
システムというほどのものではないですが、下記のようにDockerの環境を構築しました。
・Nginxのコンテナを立てて、80番と443番でHTTP/HTTPSのリクエストを受け付ける
・NginxのコンテナとWebアプリのコンテナは9000番で通信
・Webアプリのコンテナには、外部から直接アクセスはできない負荷分散は今回は未実施です。
(勉強のためKubernetesやりたいので、取り組んだらまた投稿します。)今回もdocker-compose(コンテナをまとめて管理できるツール)を使いました。
Composeファイル
ディレクトリ構造は下記のようにしました。
project/ ├ nginx/ │ └ dockerfile │ └ server.conf ├ php-fpm/ │ └ dockerfile │ └ www.conf ├ web/ │ └ Laravelアプリ/ ├ docker-compose.ymldocker-compose.ymlは下記のようにしました。
docker-compose.ymlversion: '3' services: web: build: context: ./nginx ports: - "80:80" - "443:443" volumes: - ./web/public:/etc/nginx/public - ../ssl/certs/:/etc/pki/tls/certs/ - ../ssl/private/:/etc/pki/tls/private/ depends_on: - app container_name: blog_web app: build: ./php-fpm volumes: - ./web:/var/www/ container_name: blog_appwebがNginxコンテナで、appがphp-fpm+laravelのコンテナです。
Nginxコンテナは
・外部から、80番と443番を受け付けるようにし、appと通信できる
・publicディレクトリにおいてある、cssやjsを直接さわれるようにする(staticファイルはnginxから直接返す)
・sslの証明書と鍵は、アクセスの権限を調整して、マウントして見れるphp-fpm+Laravelコンテナは
・ソースをまるまるマウントするという感じです。
Nginxのコンテナ
下記のように書くだけです。
DockerfileFROM nginx:latest # 設定ファイルを指定の場所に置く COPY ./server.conf /etc/nginx/nginx.conf CMD ["nginx", "-g", "daemon off;"]server.confuser nginx; worker_processes auto; pid /var/run/nginx.pid; events{ worker_connections 2048; multi_accept on; use epoll; } http { charset UTF-8; # versionを表示しない server_tokens off; include /etc/nginx/mime.types; default_type text/plain; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; ssl_protocols TLSv1.1 TLSv1.2; server { listen 80; server_name localhost; # 80番でアクセスしてきた人は、443番のリダイレクトを返す return 301 https://$host$request_uri; } server { listen 443; ssl on; server_name localhost; # 証明書 ssl_certificate /etc/pki/tls/certs/example.crt; # 秘密鍵 ssl_certificate_key /etc/pki/tls/private/example.key; # rootディレクトリ root /var/www/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; # indexファイルの指定 index index.php index.html; charset utf-8; # アクセスしてきたパスに対応するファイルを返す location / { try_files $uri $uri/ /index.php?$query_string; } # staticファイルはnginxが直接返す # cssファイル location /css/ { alias public/css/; } # jsファイル location /js/ { alias public/js/; } # imageファイル location /images/ { alias public/images/; } # fontsファイル location /fonts/ { alias public/fonts/; } # faviconリクエストはログを残さない location = /favicon.ico { access_log off; log_not_found off; } # php-fpmとの連携 location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass app:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } # 指定のパス以外へのアクセスを禁止する location ~ /\.(?!well-known).* { deny all; } # 証明書更新のパスへアクセスできるようにする location /.well-known/ { alias public/.well-known/; } # クローラがアクセスできるようにする location /robots.txt { alias public/robots.txt; } # サイトマップにアクセスできるようにする location /sitemap.xml { alias public/sitemap.xml; } } }まだわかっていない箇所もありますが、
Nginxのコンテナは、fastcgi_pass app:9000;とすることで、Webアプリのコンテナのpublicディレクトリのindex.phpをたたきにいけるようにしています。
私はここの記載の仕方でちょっとはまりました。。。cssファイルやjsファイルなどのstaticファイルは、Nginxコンテナがアプリのpublicディレクトリをマウントしていて、Nginxから直接返せるようにしています。
クローラが見に来れるようにrobots.txtとsitemap.xmlにアクセスできるようにしています。php-fpm+Laravelコンテナ
dockerfileに入る前に、そもそもphp-fpmとは何ぞ???というのが自分の中であったので、調べました。
公式ドキュメントとか見ましたが、一番わかりやすかったのは他者様の記事php-fpmとは
php向けのFastCGIで、メモリでキャッシュを保持することで高速にWebサーバ上でPHPを動作させるアプリケーション
ということです。
この他者様の記事ではコンテナ間の通信のことも書いてありましたが、今回Nginxとphp-fpmはTCPで通信しています。
次やる時はUNIXドメインソケットで通信しようかと思います。さて、Dockerfileですが、下記のようにしました。
DockerfileFROM php:7.4.7-fpm RUN apt update -y RUN apt install -y libfcgi0ldbl curl git unzip wget vim # nginxというユーザを作る RUN useradd -m -s /bin/sh -u 1000 nginx # 設定ファイルを指定の場所に置く COPY ./www.conf /usr/local/etc/php-fpm.d/zzz-www.conf # Composeインストール RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer # nodejs 12インストール RUN apt install -y npm RUN npm install n -g RUN n 12 # ユーザ変更 USER nginx # 作業ディレクトリ WORKDIR /var/www VOLUME ["/var/run/php-fpm"]www.conf[www] user = nginx group = nginx listen = 9000 listen.owner = nginx listen.group = nginx listen.mode = 0660 request_terminate_timeout=30sphp-fpmの設定はwww.confの設定ファイルと公開されているDockerImageで設定は簡単にできました。
あとは、コンテナにComposerとnodejs12を入れたかったので、Dockerfile内でインストールするようにしました。
実際のnpmパッケージのインストールと、composerのビルドは、コンテナを立ち上げた後、コマンドで実行しています。docker exec blog_app composer install --optimize-autoloader --no-dev docker exec blog_app npm install docker exec blog_app npm run productionこのようにしたのは、マウント後にnpmパッケージのインストールやcomposerのビルドをしないと、ソースがない時にdockerビルドが走り、失敗してしまうからです。
dockerfile内で、ソースをとってくる処理を書いている場合は、dockerfile内でnpmパッケージのインストールやcomposerのビルドができます。Composeのビルド
あとは、コンテナのビルド+コンテナ起動して、npmパッケージのインストールやcomposerのビルドして終了です。
docker-compose build docker-compose up -dドメイン設定・SSL対応
ドメインはこちらで買いました。
DNSはAWSのものを使いたかったので、Route53を使用しました。
設定は下記です。
DNSの設定はこちらを参考しました。
最低限の設定は下記で、
・Aレコード:グローバルIPとホスト名の紐付け
・NSレコード:ドメインのDNSサーバ(ドメインを購入したサイトのネームサーバにこの値を登録します。)
・SOAレコード:上位のDNSサーバ
オプションで、
・CNAMEレコード:別ホスト名
・TXTレコード:今回はクローラの認証情報記載
を記載しています。証明書の取得は、お金をかけずにやりたかったので、
他者様の記事を参考にbacmeでオレオレ証書を取得しました。ホスト名とマシンの紐付けができていれば、やることは単純で、
bacmeをgit cloneしてきて、取得したホスト名で./bacme -w /var/www/example/ example.com www.example.comを実行します。wは、.well-knownディレクトリのパスです。
(80番でwell-knownのパスにアクセスできないと↑のコマンドは失敗します。アクセスできるようにNginxの設定を見直してみてください。)
成功すると、bacmeディレクトリ直下に証明書と秘密鍵ができますので、
Nginxのマウント先(/etc/pki/tls/certs/)に指定してください。参考文献
nginx と PHP-FPM の仕組みをちゃんと理解しながら PHP の実行環境を構築する
調べなきゃ寝れない!と調べたら余計に寝れなくなったソケットの話
次は、最低限のセキュリティ設定とKubernetesのことを調べます。