やったもん勝ち

主にプログラミングのこと。生産性向上の某とかも。

AWSのEC2でAirflow実行環境を構築してチュートリアルを動かしてみる

今、airflowが熱いらしいです。
f:id:benzenetarou:20190228224252j:plain

そこら編の解説は他の有用や記事に任せて、とりあえずチュートリアル動かしてみた備忘録を残しておきます。

AWS環境

Amazon Linux 2

セキュリティグループは
sshの22番
ウェブコンソールの8080番
を開けておきます

大体チュートリアル見てやればうまくいきますが、ちょっとつまづきました。
何をどう見てこうなったかはもはや忘れましたが、ググりまくれば行けるはずです。
これがいいのかはわかりませんが、とりあえず動きます。

Quick Start — Airflow Documentation

Dockerとかもあるみたいなので、環境構築面倒くさいなってなったらそっちをやってみてもいいかもしれません。

以下、ターミナルから

$ sudo su -
# とりあえず諸々インストール
yum install -y sudo python3 gcc git python3-devel zlib-devel bzip2-devel tree tmux tig

# 環境変数セット
export SLUGIFY_USES_TEXT_UNIDECODE=yes

# pipのinstall
pip3 install --upgrade setuptools
pip3 install apache-airflow tenacity==5.0.2

# airflow開始
airflow initdb
airflow webserver -p 8080

これで一旦webコンソール画面は表示されました

f:id:benzenetarou:20190228220647p:plain

ここから、チュートリアルに沿ってやってみます。

Tutorial — Airflow Documentation

チュートリアルは解説もちゃんとあって、丁寧だと思うんですが、ウェブコンソール周りの見方が最初よくわからなかったです。

それの備忘録も兼ねてやってみたいと思います。

正しいかどうか分からないですが、とりあえず初心者が動かしてみたって感じということで許してください。

/root/airflow/airflow.cfgファイルに

dags_folder = /root/airflow/dags

って書いてあるところがあります。
デフォルトではこの下にpythonスクリプトを置いておくと読み込んでくれます。
最初はディレクトリもないので作りましょう。

以下、チュートリアルのまんまです。

このスクリプトの内容はたくさん解説している記事があるので、中身は触れないことにします。
とりあえずtaskの中でBashを実行しています。

"""
Code that goes along with the Airflow tutorial located at:
https://github.com/apache/airflow/blob/master/airflow/example_dags/tutorial.py
"""
from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from datetime import datetime, timedelta


default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2015, 6, 1),
    'email': ['airflow@example.com'],
    'email_on_failure': False,
    'email_on_retry': False,
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
    # 'queue': 'bash_queue',
    # 'pool': 'backfill',
    # 'priority_weight': 10,
    # 'end_date': datetime(2016, 1, 1),
}

dag = DAG('tutorial', default_args=default_args, schedule_interval=timedelta(days=1))

# t1, t2 and t3 are examples of tasks created by instantiating operators
t1 = BashOperator(
    task_id='print_date',
    bash_command='date',
    dag=dag)

t2 = BashOperator(
    task_id='sleep',
    bash_command='sleep 5',
    retries=3,
    dag=dag)

templated_command = """
    {% for i in range(5) %}
        echo "{{ ds }}"
        echo "{{ macros.ds_add(ds, 7)}}"
        echo "{{ params.my_param }}"
    {% endfor %}
"""

t3 = BashOperator(
    task_id='templated',
    bash_command=templated_command,
    params={'my_param': 'Parameter I passed in'},
    dag=dag)

t2.set_upstream(t1)
t3.set_upstream(t1)

これの中の dag = DAG('tutorial', default_args=default_args, schedule_interval=timedelta(days=1)) という記述のtutorialがウェブコンソールでの一つのDAGになっています。

このままだと、すでにtutorialという名前のDAGが存在しているので、確認のためちょっと変更します。

以下、変更点

dag = DAG('tutorial', default_args=default_args, schedule_interval=timedelta(days=1))
↓
dag = DAG('tutorial_man', default_args=default_args, schedule_interval=timedelta(minutes=1))

実行を早く確認したいので、days=1からminutes=1としました。

これを /root/airflow/dags/tutorial_man.pyに保存

これでウェブコンソールを確認してみます。

。。。
。。。。。

と、全然追加されません。

どうやら以下も実行しておかないとだめだったっぽいです。

$ airflow scheduler

これはスケジューラーを登録する用のやつっぽい。
これを実行しないでも、ウェブコンソールがちゃんと表示されているので動いているっぽいけど、新規に追加されないという罠がありました。

さて、どうでしょう。

f:id:benzenetarou:20190228223228p:plain

追加されていました!!!

あとは、offとなっているところをonにポチッとします。

f:id:benzenetarou:20190228223315p:plain

すると数分後、無事タスクがsuccessとなっていましたー f:id:benzenetarou:20190228223408p:plain

あとはログを見てみるなり、GUIの利点を最大限に活かして色々ポチポチして遊んでみるのも良いでしょう。

公式チュートリアルではBashOperatorを使ってBashを実行しましたが、次回PythonOperatorを使ってpythonの関数を実行していきたいと思います。 (呼び出し方に微妙に罠がありました。。。)

いい加減DockerとECSを整理する①

Dockerの概念が腑に落ちないこと早1年。

そろそろこれじゃだめだって思い始めて、復習する万。

install方法は割愛しておくことにする。
というか忘れてしまった。

Mac
High Sierra 10.13.5
$ docker -v
Docker version 17.12.0-ce, build c97c6d6

docker-composeとやらも入っておる。

$ docker-compose -v
docker-compose version 1.18.0, build 8dd22a9

まずはOSを起動してみる centosを使ってみる。 と、その前に、現在の状況を確認

現在起動しているコンテナを確認する。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hogee_web           latest              13cc204c812e        9 days ago          881MB
ruby                2.3.6               7ab6e81790b8        3 months ago        722MB
myapp_web           latest              1b99a62bd7f1        3 months ago        841MB
<none>              <none>              33139e4ab9e2        3 months ago        808MB
mysql               5.7                 5d4d51c57ea8        4 months ago        374MB
ruby                2.3.0               7ca70eb2dfea        2 years ago         725MB

imageにはこんなものたちがある。
ここにはcentosはない。
続いて現在のdockerのプロセスを確認

すべてのコンテナを確認。

docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                    NAMES
67c4c9131574        hogee_web           "bundle exec rails s…"   8 days ago          Exited (1) 8 days ago                                hogee_web_run_6
60fc65437813        hogee_web           "bundle exec rails s…"   8 days ago          Exited (1) 8 days ago                                hogee_web_run_5
0fb85edc2110        hogee_web           "bundle exec rails s…"   8 days ago          Exited (1) 8 days ago                                hogee_web_run_4
2c498cd732f6        hogee_web           "bundle exec rake db…"   9 days ago          Exited (0) 9 days ago                                hogee_web_run_3
e97a1e89c87e        hogee_web           "bundle exec rake db…"   9 days ago          Exited (0) 9 days ago                                hogee_web_run_2
126811cf559f        hogee_web           "bundle exec rails s…"   9 days ago          Exited (1) 8 days ago                                hogee_web_1
e78c4418f446        hogee_web           "rails new . --force…"   9 days ago          Exited (1) 9 days ago                                hogee_web_run_1
faed05fdd12c        mysql:5.7           "docker-entrypoint.s…"   9 days ago          Exited (255) 6 days ago     3306/tcp                 hogee_db_1
5a079a80a50c        myapp_web           "rails db:migrate"       3 months ago        Exited (0) 3 months ago                              myapp_web_run_5
fa4cd6b00acc        myapp_web           "rails g scaffold us…"   3 months ago        Exited (0) 3 months ago                              myapp_web_run_4
13c8f6ce670c        myapp_web           "rails db:create"        3 months ago        Exited (0) 3 months ago                              myapp_web_run_3
2b96f67a9f7a        myapp_web           "bundle exec rails s…"   3 months ago        Exited (255) 3 months ago   0.0.0.0:3000->3000/tcp   myapp_web_1
858bc9dfd0c2        myapp_web           "rails db:create"        3 months ago        Exited (1) 3 months ago                              myapp_web_run_2
038519291a53        33139e4ab9e2        "rails new . --force…"   3 months ago        Exited (0) 3 months ago                              myapp_web_run_1
91592f781321        mysql:5.7           "docker-entrypoint.s…"   3 months ago        Exited (255) 3 months ago   3306/tcp                 myapp_db_1
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

現在動いているdockerのプロセスはない。

$ docker run -it centos:latest bash
Unable to find image 'centos:latest' locally
latest: Pulling from library/centos
7dc0dca2b151: Pull complete
Digest: sha256:b67d21dfe609ddacf404589e04631d90a342921e81c40aeaf3391f6717fa5322
Status: Downloaded newer image for centos:latest
[root@de5bdc2e3f95 /]#

処理終了までに数分かかりました。
docker runはホスト名をつけて起動するコマンド。 この場合は、centosというイメージからdockerを実行せよ。
:latestは、タグで、ここでcentosのバージョンとかを指定できる。 -iは--interactive, Keep STDIN open even if not attached
インタラクティブなシェルとかで使えますと。 -tは--tty, Allocate a pseudo-TTY

pseudo-tty( text-terminal ) = 疑似端末 pseudo = 擬似 tty = 標準入出力となっている端末デバイス(制御端末、controlling terminal)の名前を表示するunixコマンドである。元来ttyとはteletypewriter(テレタイプライター)のことを指す。

`pseudo-tty` とは? - kz-engineer -SCRAP-

だいたいココらへんは$docker run --helpでオプションの意味が出てくるので、都度調べる。-itは頻出。 bashはシェルの指定。
本来、bash以外のシェルでもログインできるか試してみたいところだが、あいにくbashzshしか知らない。。。
zshはなんかinstallしなきゃいけない感じだった気がするので、こうするとbash以外に試せるものがない。(調べたら出てきたけど、サクッとはできなさそうで、そっちで詰まるのは本筋ではないので、割愛.)

一応zshでも試みてみるも、

$ docker run -it centos:latest zsh
docker: Error response from daemon: OCI runtime create failed: container_linux.go:296: starting container process caused "exec: \"zsh\": executable file not found in $PATH": unknown.

そらそうやな。 ほかのとりあえず名前知っているシェルを手当たり次第試してみる。

$ docker run -it centos:latest tcsh
docker: Error response from daemon: OCI runtime create failed: container_linux.go:296: starting container process caused "exec: \"tcsh\": executable file not found in $PATH": unknown.
ERRO[0000] error waiting for container: context canceled

だめ。

$ docker run -it centos:latest tcsh
docker: Error response from daemon: OCI runtime create failed: container_linux.go:296: starting container process caused "exec: \"tcsh\": executable file not found in $PATH": unknown.
ERRO[0000] error waiting for container: context canceled

だめ。

$ docker run -it centos:latest sh
sh-4.2#
sh-4.2# echo $SHELL
/bin/bash

やった!入れた!と思ってシェルを確認してみると、、、
これただのbashやんけ!!
と、そもそものbashとshの違いを調べてみると、
shはbashシンボリックリンクを貼っているとのこと。

$ docker run -it centos:latest bash
# ll /bin/ | grep sh
-rwxr-xr-x  1 root root  964544 Apr 11 00:53 bash
lrwxrwxrwx  1 root root      10 May 31 18:01 bashbug -> bashbug-64
-rwxr-xr-x  1 root root    6964 Apr 11 00:52 bashbug-64
-rws--x--x  1 root root   23960 Apr 11 06:50 chsh
-rwxr-xr-x  1 root root   15864 Apr 12 18:44 lchsh
lrwxrwxrwx  1 root root      19 May 31 18:02 setup-nsssysinit -> setup-nsssysinit.sh
-rwxr-xr-x  1 root root    1539 May 16 15:20 setup-nsssysinit.sh
lrwxrwxrwx  1 root root       4 May 31 18:01 sh -> bash
-rwxr-xr-x  1 root root   37448 Apr 11 04:35 sha1sum
-rwxr-xr-x  1 root root   41600 Apr 11 04:35 sha224sum
-rwxr-xr-x  1 root root   41600 Apr 11 04:35 sha256sum
-rwxr-xr-x  1 root root   41592 Apr 11 04:35 sha384sum
-rwxr-xr-x  1 root root   41592 Apr 11 04:35 sha512sum
-rwxr-xr-x  1 root root   10371 Apr 11 04:16 show-changed-rco
-rwxr-xr-x  1 root root   16572 Apr 11 04:16 show-installed
-rwxr-xr-x  1 root root   54216 Apr 11 04:35 shred
-rwxr-xr-x  1 root root   50320 Apr 11 04:35 shuf
-rwxr-xr-x  1 root root   15904 Apr 11 06:50 unshare

確かにシンボリックってる。
それはさておき、基本的にはbashとshはおんなじような動きをするらしい。 posixうんたらが違いだとか。

/bin/sh と /bin/bash の違い - 双六工場日誌

脱線したが、戻します。 dockerのcentosにログイン後、centosで有ることを確認しておきます。

[root@75ff963973c6 /]# cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)

いいですね。

コンテナを確認

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
de5bdc2e3f95        centos:latest       "bash"              6 minutes ago       Up 6 minutes                            vigilant_hoover
$ d ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                    NAMES
de5bdc2e3f95        centos:latest       "bash"                   7 minutes ago       Up 7 minutes                                         vigilant_hoover
67c4c9131574        hogee_web           "bundle exec rails s…"   8 days ago          Exited (1) 8 days ago                                hogee_web_run_6
.
.
.

STATUSがUPになっておりまする。

dockerのimageはどこに保存されるのか

調べてみると、docker for macのアプリののPreferenceから確認できると。 f:id:benzenetarou:20180704020006p:plain

ネットにあった情報と微妙に違うが頑張って探した。

$ cd /Users/yoshii/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/
$ ls -lh
total 13994832
-rw-r--r--@ 1 yoshii  staff    64G  7  4 01:50 Docker.raw
-rw-r--r--  1 yoshii  staff    64K  7  3 21:53 console-ring
-rw-r--r--  1 yoshii  staff    64K  7  3 21:53 database.iso
-rw-r--r--  1 yoshii  staff   3.3K  7  3 21:53 hyperkit.json
-rw-r--r--  1 yoshii  staff     3B  7  3 21:53 hyperkit.pid
-rw-r--r--  1 yoshii  staff     0B  2  9 18:10 lock
drwxr-xr-x  2 yoshii  staff    64B  2  9 18:10 log
-rw-r--r--  1 yoshii  staff    36B  2  9 18:10 nic1.uuid
-rw-r--r--  1 yoshii  staff     2B  7  3 21:53 pid
-rw-r--r--  1 yoshii  staff   2.2K  7  3 21:53 syslog
lrwxr-xr-x  1 yoshii  staff    12B  7  3 21:53 tty -> /dev/ttys000

おいおい知らぬ間に64Gも使ってたのかよコイツ。。。
なんの情報を持ってんだが。。。 と思って、よくよく先程のDocker for Macの情報を見てみると、
f:id:benzenetarou:20180704020006p:plain

Virtual disk image size: 64.0 GB (allocated: 7.2GB)
とな。 多分自分で最初の設定のときにこのくらいいったれーって64GBを用意しておいたけど、実際には7.2GBしか使われていないってことな気がしてきた。

確認します。

ll
total 13994832
-rw-r--r--@ 1 yoshii  staff  68719476736  7  4 01:50 Docker.raw
-rw-r--r--  1 yoshii  staff        65536  7  3 21:53 console-ring
-rw-r--r--  1 yoshii  staff        65536  7  3 21:53 database.iso
-rw-r--r--  1 yoshii  staff         3419  7  3 21:53 hyperkit.json
-rw-r--r--  1 yoshii  staff            3  7  3 21:53 hyperkit.pid
-rw-r--r--  1 yoshii  staff            0  2  9 18:10 lock
drwxr-xr-x  2 yoshii  staff           64  2  9 18:10 log
-rw-r--r--  1 yoshii  staff           36  2  9 18:10 nic1.uuid
-rw-r--r--  1 yoshii  staff            2  7  3 21:53 pid
-rw-r--r--  1 yoshii  staff         2256  7  3 21:53 syslog
lrwxr-xr-x  1 yoshii  staff           12  7  3 21:53 tty -> /dev/ttys000

68,719,476,736B
おっとそもそも64GBじゃないんかーいと思ったが、これは

>>> 64 * 1024 * 1024 * 1024
68719476736

でしたな。失敬。

$ docker run -it ubuntu:latest bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
6b98dfc16071: Pull complete
4001a1209541: Pull complete
6319fc68c576: Pull complete
b24603670dc3: Pull complete
97f170c87c6f: Pull complete
Digest: sha256:5f4bdc3467537cbbe563e80db2c3ec95d548a9145d64453b06939c4592d67b6d
Status: Downloaded newer image for ubuntu:latest
root@f0e98565c20b:/#
root@f0e98565c20b:/# cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

はい、ubuntuが入りました。

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hogee_web           latest              13cc204c812e        9 days ago          881MB
ubuntu              latest              113a43faa138        3 weeks ago         81.2MB
centos              latest              49f7960eb7e4        4 weeks ago         200MB
ruby                2.3.6               7ab6e81790b8        3 months ago        722MB
myapp_web           latest              1b99a62bd7f1        3 months ago        841MB
<none>              <none>              33139e4ab9e2        3 months ago        808MB
mysql               5.7                 5d4d51c57ea8        4 months ago        374MB
ruby                2.3.0               7ca70eb2dfea        2 years ago         725MB

docker imagesによると、ubuntuは81.2MBらしいです。

先程のファイルサイズが増加しているか確認しようと思ったけど、 これは$ ls -l /Users/yoshii/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/する必要もないですね。
そもそも/Users/yoshii/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.rawのファイル容量がきりの良すぎる数字の時点でおかしかった。
これはDocker.rawファイルに全部情報がいい感じに入っているようだ。僕らからは確認のしようがない。

ja.stackoverflow.com とな。

linuxだと、/var/lib/docker配下にできるらしいな。 一応やってみます。

linuxでdockerを起動

こういうときはEC2のt2.microインスタンスをサクッと立ち上げる。富豪だ。 ネットワークもこっちのほうが早いから、すぐAWS使っちゃう。富豪だ。

sudo su -
yum install -y docker 
service docker start
docker run -it centos:latest bash

とりあえずサクッとcentosがinstallできた。 一旦exitして、実態の在り処を探す。

# cd /var/lib/docker
ll
total 48
drwx------ 2 root root 4096 Jul  3 17:36 builder
drwx--x--x 3 root root 4096 Jul  3 17:36 containerd
drwx------ 3 root root 4096 Jul  3 17:36 containers
drwx------ 3 root root 4096 Jul  3 17:36 image
drwxr-x--- 3 root root 4096 Jul  3 17:36 network
drwx------ 6 root root 4096 Jul  3 17:36 overlay2
drwx------ 4 root root 4096 Jul  3 17:36 plugins
drwx------ 2 root root 4096 Jul  3 17:36 runtimes
drwx------ 2 root root 4096 Jul  3 17:36 swarm
drwx------ 2 root root 4096 Jul  3 17:36 tmp
drwx------ 2 root root 4096 Jul  3 17:36 trust
drwx------ 2 root root 4096 Jul  3 17:36 volumes
#
#
# du -sh ./*
20K ./builder
116K  ./containerd
48K ./containers
736K  ./image
52K ./network
211M  ./overlay2
20K ./plugins
4.0K  ./runtimes
4.0K  ./swarm
4.0K  ./tmp
4.0K  ./trust
28K ./volumes

怪しいのはoverlay2ですね。

# ll overlay2/
total 16
drwx------ 4 root root 4096 Jul  3 17:38 2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8
drwx------ 4 root root 4096 Jul  3 17:36 2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8-init
drwx------ 3 root root 4096 Jul  3 17:36 eb694a3cfd827ea603a03347037a4f50e537151e18aff3c8ab80482d274ebca0
drwx------ 2 root root 4096 Jul  3 17:36 l
#
#
[root@ip-172-31-44-122 docker]# du -sh overlay2/*
32K overlay2/2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8
40K overlay2/2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8-init
211M  overlay2/eb694a3cfd827ea603a03347037a4f50e537151e18aff3c8ab80482d274ebca0
16K overlay2/l
find /var/lib/docker/overlay2/. |wc
  10705   10705 1268385

どうやらたくさんのファイル群が入っているようだ。
あまり踏み込まないようにしよう。

docker run -it ubuntu:latest bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
6b98dfc16071: Pull complete
4001a1209541: Pull complete
6319fc68c576: Pull complete
b24603670dc3: Pull complete
97f170c87c6f: Pull complete
Digest: sha256:5f4bdc3467537cbbe563e80db2c3ec95d548a9145d64453b06939c4592d67b6d
Status: Downloaded newer image for ubuntu:latest
root@67da5f8e8b2c:/#

容量を確認する。

[root@ip-172-31-44-122 docker]# du -sh ./*
20K ./builder
116K  ./containerd
92K ./containers
1.1M  ./image
52K ./network
297M  ./overlay2
20K ./plugins
4.0K  ./runtimes
4.0K  ./swarm
4.0K  ./tmp
4.0K  ./trust
28K ./volumes

詳細も確認する。

[root@ip-172-31-44-122 overlay2]# ll /var/lib/docker/overlay2
total 44
drwx------ 3 root root 4096 Jul  3 17:45 06142c28a84ecc2757409fad89ad24c6e8d5e0f787937245c0c7ef34a8157db1
drwx------ 4 root root 4096 Jul  3 17:45 17cd521c63cb54de57f1918706cb950920b2664c0b93390f3c8ef60d55cef536
drwx------ 4 root root 4096 Jul  3 17:45 2d85d5672c17ce9441e77328e210bdcaad440cfadbbce39f32a46870aa51c6fc
drwx------ 4 root root 4096 Jul  3 17:45 2d85d5672c17ce9441e77328e210bdcaad440cfadbbce39f32a46870aa51c6fc-init
drwx------ 4 root root 4096 Jul  3 17:38 2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8
drwx------ 4 root root 4096 Jul  3 17:36 2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8-init
drwx------ 4 root root 4096 Jul  3 17:45 4c69f1a2eea8fba87437187838a942c75fea48b50744718288c6f7e72529827e
drwx------ 4 root root 4096 Jul  3 17:45 5e834467fed8e25a3ee8ce8c39b565171dd97bf7d4de5d9d86772144e18cea22
drwx------ 3 root root 4096 Jul  3 17:36 eb694a3cfd827ea603a03347037a4f50e537151e18aff3c8ab80482d274ebca0
drwx------ 4 root root 4096 Jul  3 17:45 f42660ffc61032bf307beaab41da57fe93acff57a59f8cc028a6baf1866c8b06
drwx------ 2 root root 4096 Jul  3 17:45 l

ubuntuのものと思われるものが増えている。

なるほど。
なるほど。

Dockerのファイル保存場所とか整理できたので、一旦ここまでにする。

顔認識・画像認識ライブラリAPIを使ってみる

経緯

個人的な趣味で、顔認識のAPIを使いたいなと思い立ちました。
OpenCVで自前でできないかなーとも思ったのですが、なかなか難しいっぽく、諦めてAPIを使うという選択になった次第です。

どこがAPIを提供しているのか、以下のリンクがとても参考になりました。
qiita.com

このリンクをもとに、それぞれ調べてみました。
調べる際の観点は「口の開閉が判別できるか」「その精度は確かか」という2点で調査をしました。
なお、調査したのは2017年の年末頃なので、この記事を書いている時点ですでに仕様が更新されている可能性があります。

目次

detectFace()

detectFace(); - 顔検出Webサービス → 2018.04.04にサービス提供終了になりました。

何より簡単に使えて、しかも無料というのがいい。 こんな感じのページ。
f:id:benzenetarou:20171125232836p:plain
このAPIでは50個の特徴量を返してくれるみたいだ。
f:id:benzenetarou:20171125232850p:plain
その結果、笑ってるか怒ってるかとかを判定したいと思ったらこっちで別途実装しなきゃいけないみたいだ。
それはそれで問題はないけども。

丁寧にサンプルページがあるので、実装する前に雰囲気確かめられる。
f:id:benzenetarou:20171125233223p:plain 早速でもページから画像を投げてみる。

帰ってくるxmlはこんな感じ

<?xml version='1.0'?>
<faces>
  <face id='0'>
    <bounds x='328' y='550' width='100' height='100'/>
    <right-eye x='365' y='591'/>
  </face>
  <face id='1'>
    <bounds x='292' y='549' width='106' height='106'/>
  </face>
  <face id='2'>
    <bounds x='706' y='256' width='709' height='709'/>
    <right-eye x='907' y='538'/>
    <left-eye x='1196' y='527'/>
    <features s-avg='0.83' s-min='0.66' s-max='0.95'>
      <point id='PR' x='921' y='536' s='0.80'/>
      <point id='PL' x='1192' y='525' s='0.94'/>
      <point id='BR2' x='1004' y='447' s='0.67'/>
      <point id='BL2' x='1111' y='431' s='0.82'/>
      <point id='N1' x='1057' y='531' s='0.78'/>
      <point id='N5' x='1064' y='689' s='0.86'/>
      <point id='M1' x='1068' y='796' s='0.85'/>
      <point id='M5' x='1071' y='895' s='0.74'/>
      <point id='M3' x='965' y='831' s='0.74'/>
      <point id='M7' x='1176' y='830' s='0.82'/>
      <point id='EL4' x='1257' y='534' s='0.93'/>
      <point id='EL1' x='1139' y='545' s='0.92'/>
      <point id='ER1' x='969' y='556' s='0.82'/>
      <point id='ER4' x='871' y='538' s='0.84'/>
      <point id='M2' x='1011' y='797' s='0.87'/>
      <point id='M8' x='1109' y='793' s='0.91'/>
      <point id='M4' x='1014' y='882' s='0.71'/>
      <point id='M6' x='1145' y='872' s='0.75'/>
      <point id='BR3' x='948' y='428' s='0.70'/>
      <point id='BL3' x='1187' y='409' s='0.84'/>
      <point id='N2' x='965' y='706' s='0.87'/>
      <point id='N4' x='1149' y='703' s='0.92'/>
      <point id='F1' x='1043' y='196' s='0.70'/>
      <point id='F2' x='842' y='260' s='0.89'/>
      <point id='F10' x='1252' y='286' s='0.69'/>
      <point id='ER2' x='949' y='525' s='0.82'/>
      <point id='ER3' x='891' y='522' s='0.81'/>
      <point id='ER5' x='897' y='560' s='0.83'/>
      <point id='ER6' x='936' y='567' s='0.81'/>
      <point id='EL2' x='1164' y='508' s='0.92'/>
      <point id='EL3' x='1231' y='508' s='0.93'/>
      <point id='EL5' x='1229' y='547' s='0.94'/>
      <point id='EL6' x='1170' y='551' s='0.92'/>
      <point id='BR1' x='994' y='489' s='0.72'/>
      <point id='BR4' x='890' y='436' s='0.79'/>
      <point id='BR5' x='841' y='492' s='0.82'/>
      <point id='BR6' x='919' y='449' s='0.77'/>
      <point id='BL1' x='1111' y='468' s='0.81'/>
      <point id='BL4' x='1240' y='414' s='0.83'/>
      <point id='BL5' x='1298' y='450' s='0.90'/>
      <point id='BL6' x='1226' y='437' s='0.85'/>
      <point id='N3' x='1068' y='737' s='0.92'/>
      <point id='M9' x='1068' y='834' s='0.83'/>
      <point id='F3' x='779' y='558' s='0.87'/>
      <point id='F9' x='1346' y='535' s='0.95'/>
      <point id='F4' x='817' y='741' s='0.81'/>
      <point id='F8' x='1323' y='720' s='0.90'/>
      <point id='F6' x='1041' y='1041' s='0.66'/>
      <point id='F5' x='849' y='880' s='0.78'/>
      <point id='F7' x='1307' y='879' s='0.83'/>
    </features>
  </face>
</faces>

自分の使いたい用途に合わせるとしたら、どこか口以外の基準点から標準化するポイントを複数見つけておいて、画像のサイズを標準化してから、口の周りの特徴点の距離を調べるという方法だろうか。

AWS Rekognito

画像認識に関連したいろいろなサービスがある感じ。
これが人工知能というくくりのサービスなのはちょっと変な感じするが。
f:id:benzenetarou:20171125235645p:plain
デモページもあった。
f:id:benzenetarou:20171125235803p:plain
ただ、これを見ると、でもページの画像でさえ口開いてるのに閉じてるって判定されてたので(しかも信頼度72%)、精度はそこまで良くないのかもしれない。
試しに自分で取った写真をアップロードしてみても、ちょくちょく誤判定されてしまっている。

JSONも返してくれるが、特徴量が少ない気がする。特に口周り。

{
    "FaceDetails": [
        {
            "BoundingBox": {
                "Width": 0.2618750035762787,
                "Height": 0.3930581510066986,
                "Left": 0.14937500655651093,
                "Top": 0.13414634764194489
            },
            "AgeRange": {
                "Low": 26,
                "High": 43
            },
            "Smile": {
                "Value": true,
                "Confidence": 99.0987319946289
            },
            "Eyeglasses": {
                "Value": true,
                "Confidence": 99.99999237060547
            },
            "Sunglasses": {
                "Value": true,
                "Confidence": 95.7325439453125
            },
            "Gender": {
                "Value": "Female",
                "Confidence": 100
            },
            "Beard": {
                "Value": false,
                "Confidence": 99.90460205078125
            },
            "Mustache": {
                "Value": false,
                "Confidence": 92.63785552978516
            },
            "EyesOpen": {
                "Value": true,
                "Confidence": 85.72091674804688
            },
            "MouthOpen": {
                "Value": false,
                "Confidence": 72.2423095703125
            },
            "Emotions": [
                {
                    "Type": "HAPPY",
                    "Confidence": 99.69184112548828
                },
                {
                    "Type": "CALM",
                    "Confidence": 1.0618865489959717
                },
                {
                    "Type": "ANGRY",
                    "Confidence": 0.44966936111450195
                }
            ],
            "Landmarks": [
                {
                    "Type": "eyeLeft",
                    "X": 0.2459116131067276,
                    "Y": 0.2963947355747223
                },
                {
                    "Type": "eyeRight",
                    "X": 0.32498690485954285,
                    "Y": 0.28320804238319397
                },
                {
                    "Type": "nose",
                    "X": 0.2972606122493744,
                    "Y": 0.338502436876297
                },
                {
                    "Type": "mouthLeft",
                    "X": 0.24349376559257507,
                    "Y": 0.43834927678108215
                },
                {
                    "Type": "mouthRight",
                    "X": 0.32762306928634644,
                    "Y": 0.4251043200492859
                },
                {
                    "Type": "leftPupil",
                    "X": 0.2514769434928894,
                    "Y": 0.2966562509536743
                },
                {
                    "Type": "rightPupil",
                    "X": 0.3339778780937195,
                    "Y": 0.28375154733657837
                },
                {
                    "Type": "leftEyeBrowLeft",
                    "X": 0.2145199030637741,
                    "Y": 0.2463952600955963
                },
                {
                    "Type": "leftEyeBrowUp",
                    "X": 0.2372017651796341,
                    "Y": 0.22838076949119568
                },
                {
                    "Type": "leftEyeBrowRight",
                    "X": 0.26641586422920227,
                    "Y": 0.2389357089996338
                },
                {
                    "Type": "rightEyeBrowLeft",
                    "X": 0.2992517352104187,
                    "Y": 0.23876728117465973
                },
                {
                    "Type": "rightEyeBrowUp",
                    "X": 0.3239707052707672,
                    "Y": 0.22483669221401215
                },
                {
                    "Type": "rightEyeBrowRight",
                    "X": 0.3491824269294739,
                    "Y": 0.23105363547801971
                },
                {
                    "Type": "leftEyeLeft",
                    "X": 0.22946621477603912,
                    "Y": 0.3019390404224396
                },
                {
                    "Type": "leftEyeRight",
                    "X": 0.2616650462150574,
                    "Y": 0.29498758912086487
                },
                {
                    "Type": "leftEyeUp",
                    "X": 0.24515913426876068,
                    "Y": 0.2873624563217163
                },
                {
                    "Type": "leftEyeDown",
                    "X": 0.24701011180877686,
                    "Y": 0.3033584654331207
                },
                {
                    "Type": "rightEyeLeft",
                    "X": 0.30755841732025146,
                    "Y": 0.2871529757976532
                },
                {
                    "Type": "rightEyeRight",
                    "X": 0.3417114317417145,
                    "Y": 0.2818377912044525
                },
                {
                    "Type": "rightEyeUp",
                    "X": 0.3245350122451782,
                    "Y": 0.27441540360450745
                },
                {
                    "Type": "rightEyeDown",
                    "X": 0.3257909119129181,
                    "Y": 0.2907133102416992
                },
                {
                    "Type": "noseLeft",
                    "X": 0.2787094712257385,
                    "Y": 0.38133224844932556
                },
                {
                    "Type": "noseRight",
                    "X": 0.308928519487381,
                    "Y": 0.37534570693969727
                },
                {
                    "Type": "mouthUp",
                    "X": 0.2901398241519928,
                    "Y": 0.4091244339942932
                },
                {
                    "Type": "mouthDown",
                    "X": 0.29399216175079346,
                    "Y": 0.4643388092517853
                }
            ],
            "Pose": {
                "Roll": -8.045867919921875,
                "Yaw": 17.916576385498047,
                "Pitch": 13.156755447387695
            },
            "Quality": {
                "Brightness": 40.16537857055664,
                "Sharpness": 99.9980239868164
            },
            "Confidence": 99.82343292236328
        }
    ]
}

Google Cloud Vision

顔認識もできるし、いろいろな情報も併せてくれる。
f:id:benzenetarou:20171126002419p:plain こんな感じでJSONを返してくれたりもするみたいだ。

dandaIMGL5048_TP_V.jpg
 {
  "faceAnnotations": [
    {
      "boundingPoly": {
        "vertices": [
          {
            "x": 583,
            "y": 49
          },
          {
            "x": 1029,
            "y": 49
          },
          {
            "x": 1029,
            "y": 567
          },
          {
            "x": 583,
            "y": 567
          }
        ]
      },
      "fdBoundingPoly": {
        "vertices": [
          {
            "x": 649,
            "y": 161
          },
          {
            "x": 975,
            "y": 161
          },
          {
            "x": 975,
            "y": 487
          },
          {
            "x": 649,
            "y": 487
          }
        ]
      },
      "landmarks": [
        {
          "type": "LEFT_EYE",
          "position": {
            "x": 719.5262,
            "y": 253.72295,
            "z": 0.000031506435
          }
        },
        {
          "type": "RIGHT_EYE",
          "position": {
            "x": 849.03937,
            "y": 267.75647,
            "z": -36.321598
          }
        },
        {
          "type": "LEFT_OF_LEFT_EYEBROW",
          "position": {
            "x": 686.8778,
            "y": 221.65237,
            "z": 27.712809
          }
        },
        {
          "type": "RIGHT_OF_LEFT_EYEBROW",
          "position": {
            "x": 754.03094,
            "y": 225.36835,
            "z": -30.87542
          }
        },
        {
          "type": "LEFT_OF_RIGHT_EYEBROW",
          "position": {
            "x": 816.0152,
            "y": 231.25845,
            "z": -48.467567
          }
        },
        {
          "type": "RIGHT_OF_RIGHT_EYEBROW",
          "position": {
            "x": 902.0392,
            "y": 247.53323,
            "z": -33.053932
          }
        },
        {
          "type": "MIDPOINT_BETWEEN_EYES",
          "position": {
            "x": 780.317,
            "y": 254.34734,
            "z": -45.376167
          }
        },
        {
          "type": "NOSE_TIP",
          "position": {
            "x": 760.82666,
            "y": 325.50342,
            "z": -93.64924
          }
        },
        {
          "type": "UPPER_LIP",
          "position": {
            "x": 758.0111,
            "y": 379.47757,
            "z": -70.47521
          }
        },
        {
          "type": "LOWER_LIP",
          "position": {
            "x": 750.76904,
            "y": 417.12973,
            "z": -67.296265
          }
        },
        {
          "type": "MOUTH_LEFT",
          "position": {
            "x": 709.657,
            "y": 398.31903,
            "z": -23.435837
          }
        },
        {
          "type": "MOUTH_RIGHT",
          "position": {
            "x": 818.4644,
            "y": 414.50378,
            "z": -52.383175
          }
        },
        {
          "type": "MOUTH_CENTER",
          "position": {
            "x": 756.7259,
            "y": 397.63107,
            "z": -63.729393
          }
        },
        {
          "type": "NOSE_BOTTOM_RIGHT",
          "position": {
            "x": 805.1426,
            "y": 346.799,
            "z": -55.52658
          }
        },
        {
          "type": "NOSE_BOTTOM_LEFT",
          "position": {
            "x": 733.29297,
            "y": 340.7746,
            "z": -35.59469
          }
        },
        {
          "type": "NOSE_BOTTOM_CENTER",
          "position": {
            "x": 763.22516,
            "y": 350.47217,
            "z": -67.04768
          }
        },
        {
          "type": "LEFT_EYE_TOP_BOUNDARY",
          "position": {
            "x": 721.1245,
            "y": 244.94334,
            "z": -8.15667
          }
        },
        {
          "type": "LEFT_EYE_RIGHT_CORNER",
          "position": {
            "x": 747.6377,
            "y": 259.79794,
            "z": -7.71021
          }
        },
        {
          "type": "LEFT_EYE_BOTTOM_BOUNDARY",
          "position": {
            "x": 718.8527,
            "y": 263.2192,
            "z": -2.942062
          }
        },
        {
          "type": "LEFT_EYE_LEFT_CORNER",
          "position": {
            "x": 699.73505,
            "y": 254.79391,
            "z": 18.149515
          }
        },
        {
          "type": "LEFT_EYE_PUPIL",
          "position": {
            "x": 719.1574,
            "y": 254.79868,
            "z": -3.427162
          }
        },
        {
          "type": "RIGHT_EYE_TOP_BOUNDARY",
          "position": {
            "x": 849.89404,
            "y": 260.44193,
            "z": -44.4995
          }
        },
        {
          "type": "RIGHT_EYE_RIGHT_CORNER",
          "position": {
            "x": 883.94965,
            "y": 276.54755,
            "z": -32.000946
          }
        },
        {
          "type": "RIGHT_EYE_BOTTOM_BOUNDARY",
          "position": {
            "x": 849.20135,
            "y": 277.8363,
            "z": -39.35131
          }
        },
        {
          "type": "RIGHT_EYE_LEFT_CORNER",
          "position": {
            "x": 823.90643,
            "y": 269.1588,
            "z": -29.444563
          }
        },
        {
          "type": "RIGHT_EYE_PUPIL",
          "position": {
            "x": 851.3009,
            "y": 270.67163,
            "z": -40.867477
          }
        },
        {
          "type": "LEFT_EYEBROW_UPPER_MIDPOINT",
          "position": {
            "x": 720.2593,
            "y": 205.07675,
            "z": -7.762258
          }
        },
        {
          "type": "RIGHT_EYEBROW_UPPER_MIDPOINT",
          "position": {
            "x": 859.88257,
            "y": 221.919,
            "z": -47.19713
          }
        },
        {
          "type": "LEFT_EAR_TRAGION",
          "position": {
            "x": 670.9434,
            "y": 339.6975,
            "z": 171.91898
          }
        },
        {
          "type": "RIGHT_EAR_TRAGION",
          "position": {
            "x": 963.58167,
            "y": 374.80206,
            "z": 89.242195
          }
        },
        {
          "type": "FOREHEAD_GLABELLA",
          "position": {
            "x": 783.9658,
            "y": 224.99454,
            "z": -44.277527
          }
        },
        {
          "type": "CHIN_GNATHION",
          "position": {
            "x": 748.6527,
            "y": 486.20532,
            "z": -57.60541
          }
        },
        {
          "type": "CHIN_LEFT_GONION",
          "position": {
            "x": 658.22473,
            "y": 415.8591,
            "z": 103.73505
          }
        },
        {
          "type": "CHIN_RIGHT_GONION",
          "position": {
            "x": 922.45325,
            "y": 447.898,
            "z": 29.103714
          }
        }
      ],
      "rollAngle": 9.122541,
      "panAngle": -15.808093,
      "tiltAngle": 7.937726,
      "detectionConfidence": 0.8733366,
      "landmarkingConfidence": 0.5498749,
      "joyLikelihood": "VERY_UNLIKELY",
      "sorrowLikelihood": "VERY_UNLIKELY",
      "angerLikelihood": "VERY_UNLIKELY",
      "surpriseLikelihood": "VERY_UNLIKELY",
      "underExposedLikelihood": "VERY_UNLIKELY",
      "blurredLikelihood": "VERY_UNLIKELY",
      "headwearLikelihood": "VERY_UNLIKELY"
    }
  ],
  "labelAnnotations": [
    {
      "mid": "/m/01xyhv",
      "description": "suit",
      "score": 0.855479
    },
    {
      "mid": "/m/01qkbx",
      "description": "professional",
      "score": 0.84650135
    },
    {
      "mid": "/m/0n5szg6",
      "description": "business executive",
      "score": 0.81015223
    },
    {
      "mid": "/m/012t_z",
      "description": "businessperson",
      "score": 0.7535335
    },
    {
      "mid": "/m/01kq3x",
      "description": "white collar worker",
      "score": 0.67039
    },
    {
      "mid": "/m/0289fz",
      "description": "executive officer",
      "score": 0.6614492
    },
    {
      "mid": "/m/09x_r",
      "description": "entrepreneur",
      "score": 0.638664
    },
    {
      "mid": "/m/09s1f",
      "description": "business",
      "score": 0.5803496
    },
    {
      "mid": "/m/05s9tm",
      "description": "talent manager",
      "score": 0.51430815
    },
    {
      "mid": "/m/019p5q",
      "description": "gentleman",
      "score": 0.50934017
    }
  ],
  "safeSearchAnnotation": {
    "adult": "VERY_UNLIKELY",
    "spoof": "VERY_UNLIKELY",
    "medical": "VERY_UNLIKELY",
    "violence": "VERY_UNLIKELY"
  },
  "imagePropertiesAnnotation": {
    "dominantColors": {
      "colors": [
        {
          "color": {
            "red": 230,
            "green": 230,
            "blue": 237
          },
          "score": 0.5333071,
          "pixelFraction": 0.5140667
        },
        {
          "color": {
            "red": 26,
            "green": 26,
            "blue": 28
          },
          "score": 0.21879509,
          "pixelFraction": 0.26526666
        },
        {
          "color": {
            "red": 205,
            "green": 203,
            "blue": 213
          },
          "score": 0.1390053,
          "pixelFraction": 0.11186667
        },
        {
          "color": {
            "red": 45,
            "green": 43,
            "blue": 47
          },
          "score": 0.029963521,
          "pixelFraction": 0.0282
        },
        {
          "color": {
            "red": 225,
            "green": 190,
            "blue": 189
          },
          "score": 0.01627379,
          "pixelFraction": 0.012933333
        },
        {
          "color": {
            "red": 164,
            "green": 159,
            "blue": 165
          },
          "score": 0.014597795,
          "pixelFraction": 0.0132
        },
        {
          "color": {
            "red": 226,
            "green": 183,
            "blue": 174
          },
          "score": 0.007874987,
          "pixelFraction": 0.014666666
        },
        {
          "color": {
            "red": 123,
            "green": 118,
            "blue": 121
          },
          "score": 0.0060422462,
          "pixelFraction": 0.0058
        },
        {
          "color": {
            "red": 246,
            "green": 214,
            "blue": 214
          },
          "score": 0.005779971,
          "pixelFraction": 0.0045333332
        },
        {
          "color": {
            "red": 86,
            "green": 83,
            "blue": 85
          },
          "score": 0.005673399,
          "pixelFraction": 0.0055333334
        }
      ]
    }
  },
  "cropHintsAnnotation": {
    "cropHints": [
      {
        "boundingPoly": {
          "vertices": [
            {
              "x": 351
            },
            {
              "x": 1215
            },
            {
              "x": 1215,
              "y": 1065
            },
            {
              "x": 351,
              "y": 1065
            }
          ]
        },
        "confidence": 1,
        "importanceFraction": 0.95
      },
      {
        "boundingPoly": {
          "vertices": [
            {
              "x": 255
            },
            {
              "x": 1343
            },
            {
              "x": 1343,
              "y": 1065
            },
            {
              "x": 255,
              "y": 1065
            }
          ]
        },
        "confidence": 1,
        "importanceFraction": 0.98999995
      },
      {
        "boundingPoly": {
          "vertices": [
            {
              "x": 127
            },
            {
              "x": 1423
            },
            {
              "x": 1423,
              "y": 1065
            },
            {
              "x": 127,
              "y": 1065
            }
          ]
        },
        "confidence": 1,
        "importanceFraction": 0.98999995
      }
    ]
  },
  "webDetection": {
    "webEntities": [
      {
        "entityId": "/g/12bprj7th",
        "score": 1.1322
      },
      {
        "entityId": "/g/1tgwpw14",
        "score": 1.0244
      },
      {
        "entityId": "/m/09s1f",
        "score": 0.7011,
        "description": "Business"
      },
      {
        "entityId": "/m/01m2bt",
        "score": 0.6569,
        "description": "Venture capital"
      },
      {
        "entityId": "/m/03bxgrp",
        "score": 0.6054,
        "description": "Company"
      },
      {
        "entityId": "/m/018s5w",
        "score": 0.5943,
        "description": "Capital"
      },
      {
        "entityId": "/m/02_7t",
        "score": 0.5428,
        "description": "Finance"
      },
      {
        "entityId": "/g/1203l_bt9",
        "score": 0.5194
      },
      {
        "entityId": "/m/02nwq",
        "score": 0.5148,
        "description": "Entrepreneurship"
      },
      {
        "entityId": "/m/03m3ym9",
        "score": 0.5101,
        "description": "Angel investor"
      },
      {
        "entityId": "/m/023k2",
        "score": 0.4496,
        "description": "Corporation"
      },
      {
        "entityId": "/m/03jzl9",
        "score": 0.4287,
        "description": "Share"
      },
      {
        "entityId": "/m/0dlrgy",
        "score": 0.4206,
        "description": "All China Lawyers Association"
      },
      {
        "entityId": "/m/0h3z7",
        "score": 0.4193,
        "description": "Initial public offering"
      },
      {
        "entityId": "/m/0lbmv",
        "score": 0.20668001,
        "description": "Shenzhen"
      }
    ],
    "fullMatchingImages": [
      {
        "url": "http://timscholten.com/wp-content/uploads/2017/05/TimScholten-5.jpg"
      },
      {
        "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048.jpg"
      },
      {
        "url": "http://stat.profile.ameba.jp/profile_images/20130317/19/1e/9f/j/o180025201363516460689.jpg"
      },
      {
        "url": "http://www.gdszlvshi.com/UploadFiles/201662015263786.jpg"
      },
      {
        "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V.jpg"
      },
      {
        "url": "https://image.jimcdn.com/app/cms/image/transf/none/path/s4684c34cfcc8a80d/image/i400bb0104d9c3726/version/1454214695/image.jpg"
      },
      {
        "url": "http://felvidek.ma/wp-content/uploads/2017/10/Becse-Norbert-fot%C3%B3-MKP.jpg"
      },
      {
        "url": "http://www.wscom.com.br/arqs/arquivos/arquivos/201303081003240000008330.jpg"
      },
      {
        "url": "http://upload-images.jianshu.io/upload_images/2548788-875d2c3997d23c47.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
      },
      {
        "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V4.jpg"
      },
      {
        "url": "http://www.prachachat.net/online/2016/10/14775681281477568341l.jpg"
      },
      {
        "url": "http://www.yemacaijing.com/kindeditor/attached/image/20160212/20160212155959_73709.jpg"
      },
      {
        "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V1.jpg"
      },
      {
        "url": "http://stat.profile.ameba.jp/profile_images/20160930/02/f3/Ot/j/o045204531475169331139.jpg"
      },
      {
        "url": "https://image.jimcdn.com/app/cms/image/transf/dimension=210x1024:format=jpg/path/s4684c34cfcc8a80d/image/i400bb0104d9c3726/version/1454214695/image.jpg"
      },
      {
        "url": "http://happymakeproject.com/wp-content/uploads/2016/02/image.jpg"
      },
      {
        "url": "https://media.licdn.com/mpr/mpr/shrinknp_200_200/AAEAAQAAAAAAAATyAAAAJDEyMmM3MWY3LTQwZmYtNGI1Yi05ZjA1LTIxNzJhOGM1NjU4Ng.jpg"
      },
      {
        "url": "https://media.licdn.com/mpr/mpr/shrinknp_200_200/AAEAAQAAAAAAAA3VAAAAJGY0ZThlMDg3LTdlMTYtNDAxMC05NzZhLTJiYWRmNjA5OTM0Yw.jpg"
      },
      {
        "url": "http://img2.cyzone.cn/uploadfile/2015/0710/fa4005041f412e73d576693097584382.jpg"
      },
      {
        "url": "https://media.licdn.com/mpr/mpr/shrink_100_100/AAEAAQAAAAAAAAxMAAAAJDliODg1ZWJmLTM5OTctNDk1Yi1iMTg0LTc5MWQ4YjAxZGQ3Yw.jpg"
      }
    ],
    "partialMatchingImages": [
      {
        "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V2.jpg"
      }
    ],
    "pagesWithMatchingImages": [
      {
        "url": "https://www.linkedin.com/in/andrewbwillis"
      },
      {
        "url": "http://timscholten.com/media-sheet/images/"
      },
      {
        "url": "https://sg.linkedin.com/in/amy-aw-1804433a"
      },
      {
        "url": "https://holaconnect.com/profile/margaux-lefort-email-phone-93fee623"
      },
      {
        "url": "http://felvidek.ma/2017/10/becse-norbert-mindent-megtesz-a-komaromi-jaras-fellenditese-erdekeben/"
      },
      {
        "url": "http://www.yemacaijing.com/index/view/id/84.html"
      },
      {
        "url": "http://www.yemacaijing.com/Index/view/id/84.html"
      },
      {
        "url": "http://www.wscom.com.br/noticias/economia/JOAO+PESSOA+GANHA+CONCESSIONARIA+AUDI+-145150"
      },
      {
        "url": "https://www.pakutaso.com/person/"
      },
      {
        "url": "http://www.cyzone.cn/d/20150601/470.html"
      },
      {
        "url": "http://happymakeproject.com/8678/"
      },
      {
        "url": "https://dandashokai.com/8667"
      },
      {
        "url": "http://www.cyzone.cn/f/20150710/1664.html"
      },
      {
        "url": "https://www.prachachat.net/news_detail.php?newsid=1477568128"
      },
      {
        "url": "https://www.pakutaso.com/person/man/"
      },
      {
        "url": "http://www.jianshu.com/p/3580429d0a50"
      },
      {
        "url": "http://m.prachachat.net/news_detail.php?newsid=1477568128"
      },
      {
        "url": "http://profile.ameba.jp/dramamaster/"
      },
      {
        "url": "http://profile.ameba.jp/panicgekitai"
      },
      {
        "url": "http://www.gdszlvshi.com/showteam_1.html"
      },
      {
        "url": "http://www.gdszlvshi.com/team.html"
      },
      {
        "url": "https://panickaiketsu.jimdo.com/%E4%B8%89%E6%9C%A8%E3%83%92%E3%83%AD%E3%82%B7%E3%81%AE%E3%83%97%E3%83%AD%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB/"
      },
      {
        "url": "http://hyperacy.site/%E0%B9%82%E0%B8%A1%E0%B8%A1%E0%B8%B9%E0%B8%AB%E0%B8%B0-%E0%B8%A3%E0%B8%B2%E0%B8%84%E0%B8%B2/"
      },
      {
        "url": "http://www.10800.com/investor/details/id/14309"
      },
      {
        "url": "https://holaconnect.com/profile/maman-bureau-email-phone-acf2ca62"
      }
    ],
    "visuallySimilarImages": [
      {
        "url": "http://www.realestateforachangingworld.co.uk/wp-content/uploads/2017/10/andy_martin-500x500.jpg"
      },
      {
        "url": "https://www.irep.co.jp/global/wp-content/uploads/2016/03/Samuel3-e1485150192823.png"
      },
      {
        "url": "http://www.bu.ac.th/wp-content/uploads/2016/07/%E0%B8%84%E0%B8%B8%E0%B8%93%E0%B8%A8%E0%B8%B8%E0%B8%A0%E0%B8%8A%E0%B8%B1%E0%B8%A2.jpg"
      },
      {
        "url": "https://ireward.superghs.com/resource/htoo/page/Mr-Manish-Gupta.jpg"
      },
      {
        "url": "http://www.recruit-rgf.com/csr/img/csr_common_president01.jpg"
      },
      {
        "url": "https://www.capitalfirst.com/front-end/img/board-members/bm2.jpg"
      },
      {
        "url": "https://www.dialog.lk/dialogdocroot/content/images/pr/supun.jpg"
      },
      {
        "url": "https://lookaside.fbsbx.com/lookaside/crawler/media/?media_id=1116763798456374"
      },
      {
        "url": "https://8iholdings.com/wp-content/uploads/2017/02/Profile-Joshua-440x440.jpg"
      }
    ]
  }
}

Microsoft Face API

返ってくるJSONはこんな感じらしい。

Response:
[
  {
    "faceAttributes": {
      "accessories": [],
      "age": 22.9,
      "blur": {
        "blurLevel": "low",
        "value": 0.06
      },
      "emotion": {
        "anger": 0.0,
        "contempt": 0.0,
        "disgust": 0.0,
        "fear": 0.0,
        "happiness": 0.0,
        "neutral": 0.986,
        "sadness": 0.009,
        "surprise": 0.005
      },
      "exposure": {
        "exposureLevel": "goodExposure",
        "value": 0.67
      },
      "facialHair": {
        "beard": 0.0,
        "moustache": 0.0,
        "sideburns": 0.0
      },
      "gender": "female",
      "glasses": "NoGlasses",
      "hair": {
        "bald": 0.0,
        "hairColor": [
          {
            "color": "brown",
            "confidence": 1.0
          },
          {
            "color": "black",
            "confidence": 0.87
          },
          {
            "color": "other",
            "confidence": 0.51
          },
          {
            "color": "blond",
            "confidence": 0.08
          },
          {
            "color": "red",
            "confidence": 0.08
          },
          {
            "color": "gray",
            "confidence": 0.02
          }
        ],
        "invisible": false
      },
      "headPose": {
        "pitch": 0.0,
        "roll": 0.1,
        "yaw": -32.9
      },
      "makeup": {
        "eyeMakeup": true,
        "lipMakeup": true
      },
      "noise": {
        "noiseLevel": "low",
        "value": 0.0
      },
      "occlusion": {
        "eyeOccluded": false,
        "foreheadOccluded": false,
        "mouthOccluded": false
      },
      "smile": 0.0
    },
    "faceId": "49d55c17-e018-4a42-ba7b-8cbbdfae7c6f",
    "faceRectangle": {
      "height": 162,
      "left": 177,
      "top": 131,
      "width": 162
    }
  }
]

あまり詳細な表情の情報は渡してくれないみたいだ。
もうこっちがやっとくから、君たちは結果だけ見てればいいの。感が強い。ヤダ。

Face++

Face Landmark SDK - Face++ Cognitive Services

なんと顔の特徴料が106個あると。
しかも無料だと。
なんやかんやこれが一番良さそうだ。

ただ、使ってみるまでがちょっと面倒くさいです。
まずアカウントを登録して(メールが届くのが5分くらいかかった!)、携帯電話番号とかも登録しなくちゃAPIKeyが取得できません。
これで使い物にならなかったら腹立ちますね。 中国のサービスなので、電話番号とか悪用されないかなと心配してたのですが、この会社割りと有名な会社だったみたいですね。恥ずかしい。

中国の顔認証スタートアップFace++が2500万米ドルを調達、Jack Ma(馬雲)氏も顔認証決済を導入へ - THE BRIDGE(ザ・ブリッジ)

{
  "image_id": "5XkklHlGoc1WTKaw25w5ww==",
  "request_id": "1511624338,e08986b0-891b-46fb-9b1a-2c77a83dccbb",
  "time_used": 427,
  "faces": [
    {
      "landmark": {
        "mouth_upper_lip_left_contour2": {
          "y": 384,
          "x": 719
        },
        "mouth_upper_lip_left_contour3": {
          "y": 396,
          "x": 728
        },
        "mouth_lower_lip_right_contour3": {
          "y": 415,
          "x": 772
        },
        "mouth_upper_lip_left_contour1": {
          "y": 372,
          "x": 743
        },
        "left_eye_upper_left_quarter": {
          "y": 248,
          "x": 706
        },
        "left_eyebrow_lower_middle": {
          "y": 220,
          "x": 716
        },
        "contour_chin": {
          "y": 511,
          "x": 750
        },
        "left_eyebrow_lower_left_quarter": {
          "y": 219,
          "x": 699
        },
        "right_eyebrow_lower_left_quarter": {
          "y": 235,
          "x": 832
        },
        "mouth_lower_lip_right_contour1": {
          "y": 402,
          "x": 783
        },
        "mouth_lower_lip_left_contour2": {
          "y": 408,
          "x": 717
        },
        "left_eye_bottom": {
          "y": 260,
          "x": 717
        },
        "mouth_lower_lip_bottom": {
          "y": 412,
          "x": 753
        },
        "contour_left9": {
          "y": 497,
          "x": 717
        },
        "mouth_lower_lip_top": {
          "y": 394,
          "x": 755
        },
        "right_eyebrow_upper_middle": {
          "y": 217,
          "x": 859
        },
        "right_eyebrow_left_corner": {
          "y": 232,
          "x": 806
        },
        "right_eye_bottom": {
          "y": 279,
          "x": 849
        },
        "contour_left7": {
          "y": 448,
          "x": 680
        },
        "contour_left6": {
          "y": 416,
          "x": 673
        },
        "contour_left5": {
          "y": 382,
          "x": 671
        },
        "contour_left4": {
          "y": 349,
          "x": 671
        },
        "contour_left3": {
          "y": 317,
          "x": 672
        },
        "contour_left2": {
          "y": 287,
          "x": 677
        },
        "contour_left1": {
          "y": 257,
          "x": 684
        },
        "left_eye_lower_left_quarter": {
          "y": 258,
          "x": 705
        },
        "mouth_upper_lip_top": {
          "y": 376,
          "x": 757
        },
        "contour_right3": {
          "y": 371,
          "x": 992
        },
        "contour_right2": {
          "y": 328,
          "x": 997
        },
        "mouth_left_corner": {
          "y": 405,
          "x": 701
        },
        "contour_right4": {
          "y": 414,
          "x": 982
        },
        "contour_right7": {
          "y": 504,
          "x": 893
        },
        "left_eyebrow_left_corner": {
          "y": 219,
          "x": 683
        },
        "nose_right": {
          "y": 346,
          "x": 814
        },
        "right_eye_upper_right_quarter": {
          "y": 266,
          "x": 867
        },
        "nose_tip": {
          "y": 321,
          "x": 757
        },
        "contour_right5": {
          "y": 453,
          "x": 963
        },
        "nose_contour_lower_middle": {
          "y": 354,
          "x": 762
        },
        "right_eye_top": {
          "y": 261,
          "x": 850
        },
        "mouth_lower_lip_left_contour3": {
          "y": 410,
          "x": 735
        },
        "right_eye_right_corner": {
          "y": 275,
          "x": 881
        },
        "right_eye_lower_right_quarter": {
          "y": 278,
          "x": 866
        },
        "mouth_upper_lip_right_contour2": {
          "y": 393,
          "x": 794
        },
        "right_eyebrow_lower_right_quarter": {
          "y": 239,
          "x": 883
        },
        "left_eye_left_corner": {
          "y": 253,
          "x": 695
        },
        "mouth_right_corner": {
          "y": 417,
          "x": 811
        },
        "mouth_upper_lip_right_contour3": {
          "y": 402,
          "x": 784
        },
        "right_eye_lower_left_quarter": {
          "y": 276,
          "x": 834
        },
        "left_eyebrow_right_corner": {
          "y": 224,
          "x": 752
        },
        "left_eyebrow_lower_right_quarter": {
          "y": 223,
          "x": 734
        },
        "right_eye_center": {
          "y": 272,
          "x": 850
        },
        "left_eye_upper_right_quarter": {
          "y": 251,
          "x": 731
        },
        "mouth_lower_lip_left_contour1": {
          "y": 396,
          "x": 728
        },
        "contour_left8": {
          "y": 475,
          "x": 695
        },
        "nose_left": {
          "y": 336,
          "x": 726
        },
        "right_eyebrow_lower_middle": {
          "y": 236,
          "x": 858
        },
        "left_eye_top": {
          "y": 247,
          "x": 718
        },
        "left_eye_center": {
          "y": 255,
          "x": 718
        },
        "left_eye_lower_right_quarter": {
          "y": 260,
          "x": 729
        },
        "nose_contour_right1": {
          "y": 269,
          "x": 805
        },
        "contour_right9": {
          "y": 516,
          "x": 798
        },
        "right_eye_left_corner": {
          "y": 272,
          "x": 819
        },
        "left_eyebrow_upper_left_quarter": {
          "y": 207,
          "x": 698
        },
        "left_eye_pupil": {
          "y": 253,
          "x": 718
        },
        "right_eyebrow_upper_left_quarter": {
          "y": 219,
          "x": 831
        },
        "contour_right8": {
          "y": 514,
          "x": 846
        },
        "right_eyebrow_right_corner": {
          "y": 243,
          "x": 906
        },
        "right_eye_upper_left_quarter": {
          "y": 264,
          "x": 833
        },
        "left_eyebrow_upper_middle": {
          "y": 206,
          "x": 718
        },
        "right_eyebrow_upper_right_quarter": {
          "y": 223,
          "x": 886
        },
        "nose_contour_left1": {
          "y": 265,
          "x": 758
        },
        "nose_contour_left2": {
          "y": 312,
          "x": 738
        },
        "mouth_upper_lip_right_contour1": {
          "y": 375,
          "x": 771
        },
        "contour_right1": {
          "y": 285,
          "x": 999
        },
        "nose_contour_right2": {
          "y": 320,
          "x": 806
        },
        "mouth_lower_lip_right_contour2": {
          "y": 417,
          "x": 792
        },
        "contour_right6": {
          "y": 484,
          "x": 933
        },
        "nose_contour_right3": {
          "y": 352,
          "x": 788
        },
        "nose_contour_left3": {
          "y": 346,
          "x": 742
        },
        "left_eye_right_corner": {
          "y": 258,
          "x": 740
        },
        "left_eyebrow_upper_right_quarter": {
          "y": 211,
          "x": 737
        },
        "right_eye_pupil": {
          "y": 269,
          "x": 849
        },
        "mouth_upper_lip_bottom": {
          "y": 395,
          "x": 755
        }
      },
      "attributes": {
        "emotion": {
          "sadness": 0.044,
          "neutral": 31.453,
          "disgust": 21.391,
          "anger": 47.017,
          "surprise": 0.032,
          "fear": 0.032,
          "happiness": 0.032
        },
        "gender": {
          "value": "Male"
        },
        "age": {
          "value": 37
        },
        "eyestatus": {
          "left_eye_status": {
            "normal_glass_eye_open": 1.785,
            "no_glass_eye_close": 0,
            "occlusion": 0.355,
            "no_glass_eye_open": 97.712,
            "normal_glass_eye_close": 0.003,
            "dark_glasses": 0.145
          },
          "right_eye_status": {
            "normal_glass_eye_open": 0.004,
            "no_glass_eye_close": 0,
            "occlusion": 0.005,
            "no_glass_eye_open": 99.991,
            "normal_glass_eye_close": 0,
            "dark_glasses": 0
          }
        },
        "glass": {
          "value": "None"
        },
        "headpose": {
          "yaw_angle": 22.35016,
          "pitch_angle": -8.229241,
          "roll_angle": 9.0831785
        },
        "blur": {
          "blurness": {
            "threshold": 50,
            "value": 0.378
          },
          "motionblur": {
            "threshold": 50,
            "value": 0.378
          },
          "gaussianblur": {
            "threshold": 50,
            "value": 0.378
          }
        },
        "smile": {
          "threshold": 30.1,
          "value": 3.299
        },
        "facequality": {
          "threshold": 70.1,
          "value": 6.392
        },
        "ethnicity": {
          "value": "Asian"
        }
      },
      "face_rectangle": {
        "width": 316,
        "top": 201,
        "left": 657,
        "height": 316
      },
      "face_token": "06ae41e4d9d9fe3f614ea82bfbdeb289"
    }
  ]

まずはDetect APIで顔を検出してから、その顔のIDを渡すことでAnalyze APIが使えるようです。

IBM

デモページはこんな感じ
f:id:benzenetarou:20171127231201p:plain
Sweetheart(恋人)かどうか判定できるってのはすごいな〜
男女二人組を恋人なのかどうか見分けられるってすごいな〜

でも特徴量とかはくれないみたいだ。
JSONはこんな感じ。

結果とスコアしかくれない。

{
  "classes": [
    {
      "class": "person",
      "score": 0.851
    },
    {
      "class": "sweetheart",
      "score": 0.581,
      "type_hierarchy": "/person/sweetheart"
    },
    {
      "class": "people",
      "score": 0.597,
      "type_hierarchy": "/person/people"
    },
    {
      "class": "couple",
      "score": 0.518,
      "type_hierarchy": "/person/couple"
    },
    {
      "class": "woman",
      "score": 0.556,
      "type_hierarchy": "/person/female/woman"
    },
    {
      "class": "female",
      "score": 0.5
    },
    {
      "class": "Indian red color",
      "score": 0.988
    }
  ],
  "classifier_id": "default",
  "name": "default"
}

結論

結論は、Face++を使ってみることにしました。
理由としては、特徴量が一番多く、精度も高そうだったためです。
(と、まとめるにあたってもう一度見渡してみたら、無料だしdetectFaceでも良かったのではないかな…と思っています。
と思って改めて見てみたら2018/0404にサービス終了していました。そういえば、これを採用しなかった理由は、提供元があまり聞いたことない会社だったのとで、いつサービス終了するかわからなかったのと、http通信なので、自分の顔写真を送るのには抵抗があったからでした。あの時の自分は正しかった!) あとGoogle Cloud APIも悪くなかったと思うのですが、若干Face++よりもお高かったので、お見送りになりました。
次回は実際にFace++を用いて自己満サービスを作りたいと思います。

Amazon linuxで/etc/cron.d/配下または/etc/cron.hourly/配下にcronを設定する.

cronの設定方法はいくつかあって、どこにどう設定すればいいのかの、ベストプラクティスはまだよく分かっていません。
いったんpart1として/etc/cron.d/と/etc/cron.hourly/はいかに設定する方法をまとめておきます。

環境

まずはcron化したり作ると作成します。
僕の常套手段として、適当なpythonスクリプトを作ってslackに通知させます。
ログを出力してtail -f でもいいんですが、ログを出力するために画面を2つ出さなきゃいけないのがちょっと面倒くさいので、slackで受動的に確認します。

cron.d配下

cron化させるslack通知pythonスクリプト

import requests
import json

webhook_url = "https://XXXXXXXXXXXXXX"
requests.post(webhook_url, data = json.dumps({
    'text': u'Test', # 投稿するテキスト
    'username': u'me', # 投稿のユーザー名
    'icon_emoji': u':ghost:', # 投稿のプロフィール画像に入れる絵文字
    'link_names': 1, # メンションを有効にする
}))

これは/home/ec2-user/slack.pyにおいておきます。
cronがrootユーザーとして実行させることにするので、実行権限を付与しておきます。
$ sudo chmod 755 /home/ec2-user/slack.py

cron設定

続いて$ sudo vi /etc/cron.d/slack.py

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
  * * * * *  root /usr/bin/python36 /home/ec2-user/slack.py

これだでけOKです。
特にcronのrestartとかいらないです。
これで1分ごとにslack通知が流れてきました~

cron.hourly配下

cron化させるslack通知pythonスクリプト

こちらは同じものを使っていきます。 配置場所も同じく/home/ec2-user/slack.pyです。

import requests
import json

webhook_url = "https://XXXXXXXXXXXXXX"
requests.post(webhook_url, data = json.dumps({
    'text': u'Test', # 投稿するテキスト
    'username': u'me', # 投稿のユーザー名
    'icon_emoji': u':ghost:', # 投稿のプロフィール画像に入れる絵文字
    'link_names': 1, # メンションを有効にする
}))

cron設定

$ sudo vi /etc/cron.hourly/slackを作成します。
ファイルの中身は以下です。

#!/bin/sh

/usr/bin/python36 /home/ec2-user/slack.py

ここで罠なのですが、このファイル自体にも実行権限を与えないと動かないみたいです。
$ sudo chmod 755 /etc/cron.hourly/slack

hourlyなので、1時間に1回なのですが、確認のために最大1時間も待つなんてとんでもないです。
以下より、一時的に設定を変更します。

$ sudo vi /etc/cron.d/0hourly

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
30 * * * * root run-parts /etc/cron.hourly

今回は**:30のときに実行されるように変更しました。

実際通知が流れてきました。
成功です。

python36で別のホストのmysqlに接続する。

環境

接続元

接続先

準備

MySQLサーバ

事前にDBとテーブルを作っておきます。 ユーザー、パスワードとかは特に作成せず、起動したてのままでOKです。 SQLは適当に作成します。

mysql > CREATE DATABASE testdb;
mysql > USE testdb;
mysql > CREATE TABLE employee(name varchar(20), age int, job varchar(20), salary int);
mysql > INSERT INTO employee (name, age, job, salary) VALUES ('tanaka',20,'engineer',400);
mysql > GRANT ALL PRIVIEGES ON *.* TO root@aaa.aaa.aaa.aaa;

AWS

セキュリティグループをMySQLのport3306を開けておいてください。

python

sudo pip-3.6 install pymysqlが必要です。

実装コード

import pymysql

db = pymysql.connect(
    host='xxx.xxxx.xxx.xxx',
    user='root',
    password='',
    db='testdb',
    charset='utf8',
    cursorclass=pymysql.cursors.DictCursor,
)


cur = db.cursor()
sql = "select * from employee"
cur.execute(sql)
employees = cur.fetchall()
print(employees)
[{'name': 'tanaka', 'age': 20, 'job': 'engineer', 'salary': 400}]

と、listになって返ってくるので、あとは煮るなり焼くなり好きにしてです。

sudoers.dファイルでコマンドのオプションはどこまで制限できるのか

sudoers.dで指定する、許可されるコマンドは、どこまで寛容なのか。 一字一句そのコマンドじゃなきゃいけないのか。 それとも、オプションだとかパスはよしなに指定させてくれるのか。 検証する。

# rootになっておく  
$ sudo su -
# まずは、現在のユーザー一覧を確認
$ ll /home/
合計 2
drwx------. 5 vagrant   vagrant   4096  1月 12 10:52 vagrant

# userを追加
$ useradd test-user

# ユーザー追加されたのを確認
$ ll /home

# パスワードを設定
$ passwd test-user

# test-userにログイン
$ su test-user

# sudoが必要なコマンドを実行してみます
$ yum install -y git
Loaded plugins: fastestmirror
You need to be root to perform this command.

# rootに戻ります
$ exit

# sudoers権限をいじります
$ visudo

# 下記を追加
# test-userに root権限でのyumの使用を許可します。
# /bin/yumじゃなくていいのかな?
# yumのオプションは何でも使えるのかな?
test-user   ALL=(root)     yum


編集を抜けると
visudo: >>> /etc/sudoers: syntax error near line 121 <<<
What now?
Options are:
  (e)dit sudoers file again
  e(x)it without saving changes to sudoers file
  (Q)uit and save changes to sudoers file (DANGER!)

What now? e
と、
Qをやってしまったが最後、二度とsudoを使えなくなってしまいました。
rootにログインすることも叶わなかった。。。
VM立ち上げ直した。。。


$ visudo 
# 以下のように追加
test-user    ALL=(root)      NOPASSWD: /bin/yum
:wqでちゃんと抜けられました。

# test-userにログイン
$ su test-user

# 権限を確かめる
$ sudo yum install -y tig
->OK! yumのコマンドなら全部使えるっぽい。
権限をもう少し絞ってみます。

# rootになる
$ exit

$ visudo
# 以下の設定に変更
test-user    ALL=(root)      NOPASSWD: /bin/yum install -y tig

# test-userになる
$ su test-usre


# 確かめる
$ sudo yum install -y tig
出来ました。

# 他のコマンドは使えない?
$ sudo yum install -y tree
ダメでした。

# どこまで使えるのか
$ visudo
# 次のように設定
test-user    ALL=(root)      NOPASSWD: /bin/yum install
#これでyum installなら何でも使えるかな?

# 試してみます
$ su test-user
$ sudo yum install -y tree
↓
なんかパスワード求められた。。。
なんかダメみたいだ。
なので、方法としては、
完全に決め打ちのコマンドを一つずつ書くか、
オプションは指定しないで、実行ファイルのパスだけを書くか。
のどちらかっぽい。
コマンドの処理の仕方とかもっと深く知れば「はいはい、そりゃそうだ」って言えるようになる気がする。

40秒で支度しな!AWSのEC2にpython36で最速でflask実行環境を作成する

AWSのEC2、amazon linuxです。

$ sudo yum install -y python36
$ sudo pip-3.6 install flask
$ vi ~/hello.py
# 以下の内容で作成作成
from flask import Flask
app = Flask(__name__)
 
@app.route('/')
def hello():
    return 'Hello World!'
 
if __name__ == '__main__':
    app.run(host='0.0.0.0')
$ python36 hello.py
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

localhost:5000をcurlしてみる。

$ curl http://127.0.0.1:5000
127.0.0.1 - - [20/Feb/2018 16:20:52] "GET / HTTP/1.1" 200 -
Hello World!

いい感じです。

これをブラウザから確認したい。
xxx.xxx.xxx.xxx:5000

Hello World!

が表示されました。

当然セキュリティグループから、アクセスしようとしているIPの5000番ポートを開けておく必要がありやす。

どうでしょう。40秒で支度できましたか?
f:id:benzenetarou:20180221014836j:plain