技術的な話題 に関する投稿を表示しています

CentOS 6 系から CentOS 7 系にアップグレードしたかった

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

某所の CentOS サーバをいい加減に 7 系にしよう!と思い、アップグレード手順なんかを調べていたら結構大変そうなことになった記録。結論としてまだアップグレードは出来てない。大して yum 以外でモリモリ入れたのも無いし、およそ nginx と php が動けば良いだろうな、入れ直しが手っ取り早そうだ。

公式 Wiki にアップグレードツールの件が記載されている。
TipsAndTricks/CentOSUpgradeTool - CentOS Wiki

目につく部分に警告が書かれている。

DO NOT USE this tool. Warning: use of this tool is currently BROKEN as several system-critical packages are of a higher version number in CentOS 6.7 than they are in CentOS 7 so those do not get upgraded correctly. This renders yum and several other system tools non-functional.

要するに CentOS 6.7 以降のパッケージは CentOS 7 に入っているパッケージよりもバージョンが進んでいるものもあり、うまく動かないらしい。詰まる該当パッケージのバージョン下げれば良いんじゃないか、行けるっしょ、という気持ちでトライしてみる。
※良い子は真似しないでね!

// まずは現状の確認をする
# cat /etc/redhat-release
CentOS release 6.9 (Final)


// アップグレードツールのリポジトリを追加
# vi /etc/yum.repos.d/upg.repo

// 以下を記載

[upg]
name=CentOS-$releasever - Upgrade Tool
baseurl=http://dev.centos.org/centos/6/upg/x86_64/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6


// 必要なツールのインストール
# yum install redhat-upgrade-tool preupgrade-assistant-contents


// 可能なアップグレードを確認
# preupg --list
CentOS6_7


// アップグレード検証をする
# preupg -s CentOS6_7

Preupg tool doesn't do the actual upgrade.
Please ensure you have backed up your system and/or data in the event of a failed upgrade
 that would require a full re-install of the system from installation media.
Do you want to continue? y/n

// y を入力

Gathering logs used by preupgrade assistant:
All installed packages : 01/11 ...finished (time 00:01s)
All changed files      : 02/11 ...finished (time 04:51s)
Changed config files   : 03/11 ...finished (time 00:00s)
All users              : 04/11 ...finished (time 00:00s)
All groups             : 05/11 ...finished (time 00:00s)
Service statuses       : 06/11 ...finished (time 00:00s)
All installed files    : 07/11 ...finished (time 00:04s)
All local files        : 08/11 ...finished (time 00:40s)
All executable files   : 09/11 ...finished (time 00:07s)
RedHat signed packages : 10/11 ...finished (time 00:00s)
CentOS signed packages : 11/11 ...finished (time 00:00s)
Assessment of the system, running checks / SCE scripts:
001/096 ...done    (Configuration Files to Review)
002/096 ...done    (File Lists for Manual Migration)
003/096 ...done    (Bacula Backup Software)
004/096 ...done    (MySQL configuration)
005/096 ...done    (Migration of the MySQL data stack)
006/096 ...done    (Changes related to moving from MySQL to MariaDB)
007/096 ...done    (PostgreSQL upgrade content)
008/096 ...done    (GNOME Desktop Environment underwent several design modifications in CentOS 7 release)
009/096 ...done    (KDE Desktop Environment underwent several design modifications in CentOS 7 release)
010/096 ...done    (several graphic drivers not supported in CentOS 7)
011/096 ...done    (several input drivers not supported in CentOS 7)
012/096 ...done    (several kernel networking drivers not available in CentOS 7)
013/096 ...done    (several kernel storage drivers not available in CentOS 7)
014/096 ...done    (Names, Options and Output Format Changes in arptables)
015/096 ...done    (BIND9 running in a chroot environment check.)
016/096 ...done    (BIND9 configuration compatibility check)
017/096 ...done    (Move dhcpd/dhcprelay arguments from /etc/sysconfig/* to *.service files)
018/096 ...done    (DNSMASQ configuration compatibility check)
019/096 ...done    (Dovecot configuration compatibility check)
020/096 ...done    (Compatibility Between iptables and ip6tables)
021/096 ...done    (Net-SNMP check)
022/096 ...done    (Squid configuration compatibility check)
023/096 ...done    (Reusable Configuration Files)
024/096 ...done    (VCS repositories)
025/096 ...done    (Added and extended options for BIND9 configuration)
026/096 ...done    (Added options in DNSMASQ configuration)
027/096 ...done    (Packages not signed by CentOS)
028/096 ...done    (Obsoleted rpms)
029/096 ...done    (w3m not available in CentOS 7)
030/096 ...done    (report incompatibilities between CentOS 6 and 7 in qemu-guest-agent package)
031/096 ...done    (Removed options in coreutils binaries)
032/096 ...done    (Removed options in gawk binaries)
033/096 ...done    (Removed options in netstat binary)
034/096 ...done    (Removed options in quota tools)
035/096 ...done    (Removed rpms)
036/096 ...done    (Replaced rpms)
037/096 ...done    (GMP library incompatibilities)
038/096 ...done    (package downgrades)
039/096 ...done    (restore custom selinux configuration)
040/096 ...done    (General)
041/096 ...done    (samba shared directories selinux)
042/096 ...done    (CUPS Browsing/BrowsePoll configuration)
043/096 ...done    (CVS Package Split)
044/096 ...done    (FreeRADIUS Upgrade Verification)
045/096 ...done    (httpd configuration compatibility check)
046/096 ...done    (bind-dyndb-ldap)
047/096 ...done    (Identity Management Server compatibility check)
048/096 ...done    (IPA Server CA Verification)
049/096 ...done    (NTP configuration)
050/096 ...done    (Information on time-sync.target)
051/096 ...done    (OpenLDAP /etc/sysconfig and data compatibility)
052/096 ...done    (OpenSSH sshd_config migration content)
053/096 ...done    (OpenSSH sysconfig migration content)
054/096 ...done    (Configuration for quota_nld service)
055/096 ...done    (Disk quota netlink message daemon moved into quota-nld package)
056/096 ...done    (SSSD compatibility check)
057/096 ...done    (Luks encrypted partition)
058/096 ...done    (Clvmd and cmirrord daemon management.)
059/096 ...done    (State of LVM2 services.)
060/096 ...done    (device-mapper-multipath configuration compatibility check)
061/096 ...done    (Removal of scsi-target-utils)
062/096 ...done    (Configuration for warnquota tool)
063/096 ...done    (Disk quota tool warnquota moved into quota-warnquota package)
064/096 ...done    (Architecture Support)
065/096 ...done    (Binary rebuilds)
066/096 ...done    (Debuginfo packages)
067/096 ...done    (Cluster and High Availability)
068/096 ...done    (Quorum implementation)
069/096 ...done    (fix krb5kdc config file)
070/096 ...done    (File Systems, Partitions and Mounts Configuration Review)
071/096 ...done    (Read Only FHS directories)
072/096 ...done    (Sonamebumped libs)
073/096 ...done    (SonameKept Reusable Dynamic Libraries)
074/096 ...done    (Removed .so libs)
075/096 ...done    (In-place Upgrade Requirements for the /usr/ Directory)
076/096 ...done    (CA certificate bundles modified)
077/096 ...done    (Developer Tool Set packages)
078/096 ...done    (Hyper-V)
079/096 ...done    (Content for enabling and disabling services based on CentOS 6 system)
080/096 ...done    (Check for ethernet interface naming)
081/096 ...done    (User modification in /etc/rc.local and /etc/rc.d/rc.local)
082/096 ...done    (cgroups configuration compatibility check)
083/096 ...done    (Plugable authentication modules (PAM))
084/096 ...done    (Foreign Perl modules)
085/096 ...done    (Python 2.7.5)
086/096 ...done    (Ruby 2.0.0)
087/096 ...done    (SCL collections)
088/096 ...done    (System kickstart)
089/096 ...done    (YUM)
090/096 ...done    (Check for usage of dangerous range of UID/GIDs)
091/096 ...done    (Incorrect usage of reserved UID/GIDs)
092/096 ...done    (NIS ypbind config files back-up)
093/096 ...done    (NIS Makefile back-up)
094/096 ...done    (NIS server maps check)
095/096 ...done    (NIS server MAXUID and MAXGID limits check)
096/096 ...done    (NIS server config file back-up)
Assessment finished (time 06:32s)
I/O warning : failed to load external entity "/usr/share/openscap/xsl/security-guide.xsl"
compilation error: file /usr/share/preupgrade/xsl/preup.xsl line 40 element import
xsl:import : unable to load /usr/share/openscap/xsl/security-guide.xsl
I/O warning : failed to load external entity "/usr/share/openscap/xsl/oval-report.xsl"
compilation error: file /usr/share/preupgrade/xsl/preup.xsl line 41 element import
xsl:import : unable to load /usr/share/openscap/xsl/oval-report.xsl
I/O warning : failed to load external entity "/usr/share/openscap/xsl/sce-report.xsl"
compilation error: file /usr/share/preupgrade/xsl/preup.xsl line 42 element import
xsl:import : unable to load /usr/share/openscap/xsl/sce-report.xsl
OpenSCAP Error:: Could not parse XSLT file '/usr/share/preupgrade/xsl/preup.xsl' [oscapxml.c:416]
Unable to open file /root/preupgrade/result.html
Usage: preupg [options]

preupg: error: [Errno 2] No such file or directory: '/root/preupgrade/result.html'

やっぱりうごかない。調べると openscap のバージョンを下げることで先にすすめるらしい。
CentOS6.8 から CentOS7 へのアップグレード - demandosigno

// バージョン確認
# rpm -qa | grep openscap
openscap-1.2.13-2.el6.x86_64


// 古いものを入れる
# rpm -Uhv --oldpackage http://dev.centos.org/centos/6/upg/x86_64/Packages/openscap-1.0.8-1.0.1.el6.centos.x86_64.rpm
http://dev.centos.org/centos/6/upg/x86_64/Packages/openscap-1.0.8-1.0.1.el6.centos.x86_64.rpm を取得中
準備中...                ########################################### [100%]
   1:openscap               ########################################### [100%]


// 確認
# rpm -qa | grep openscap
openscap-1.0.8-1.0.1.el6.centos.x86_64


// 再度ツール実行
# preupg -s CentOS6_7

~~

Assessment finished (time 06:21s)

Result table with checks and their results for main contents:
---------------------------------------------------------------------------------------------------------------
|Bacula Backup Software                                                                    |notapplicable     |
|MySQL configuration                                                                       |notapplicable     |
|Migration of the MySQL data stack                                                         |notapplicable     |
|Changes related to moving from MySQL to MariaDB                                           |notapplicable     |
|PostgreSQL upgrade content                                                                |notapplicable     |
|GNOME Desktop Environment underwent several design modifications in CentOS 7 release      |notapplicable     |
|KDE Desktop Environment underwent several design modifications in CentOS 7 release        |notapplicable     |
|several graphic drivers not supported in CentOS 7                                         |notapplicable     |
|several input drivers not supported in CentOS 7                                           |notapplicable     |
|Names, Options and Output Format Changes in arptables                                     |notapplicable     |
|BIND9 running in a chroot environment check.                                              |notapplicable     |
|BIND9 configuration compatibility check                                                   |notapplicable     |
|Move dhcpd/dhcprelay arguments from /etc/sysconfig/* to *.service files                   |notapplicable     |
|DNSMASQ configuration compatibility check                                                 |notapplicable     |
|Dovecot configuration compatibility check                                                 |notapplicable     |
|Net-SNMP check                                                                            |notapplicable     |
|Squid configuration compatibility check                                                   |notapplicable     |
|Added and extended options for BIND9 configuration                                        |notapplicable     |
|Added options in DNSMASQ configuration                                                    |notapplicable     |
|w3m not available in CentOS 7                                                             |notapplicable     |
|report incompatibilities between CentOS 6 and 7 in qemu-guest-agent package               |notapplicable     |
|restore custom selinux configuration                                                      |notapplicable     |
|samba shared directories selinux                                                          |notapplicable     |
|CUPS Browsing/BrowsePoll configuration                                                    |notapplicable     |
|FreeRADIUS Upgrade Verification                                                           |notapplicable     |
|bind-dyndb-ldap                                                                           |notapplicable     |
|Identity Management Server compatibility check                                            |notapplicable     |
|IPA Server CA Verification                                                                |notapplicable     |
|OpenLDAP /etc/sysconfig and data compatibility                                            |notapplicable     |
|SSSD compatibility check                                                                  |notapplicable     |
|Clvmd and cmirrord daemon management.                                                     |notapplicable     |
|device-mapper-multipath configuration compatibility check                                 |notapplicable     |
|Removal of scsi-target-utils                                                              |notapplicable     |
|Quorum implementation                                                                     |notapplicable     |
|fix krb5kdc config file                                                                   |notapplicable     |
|cgroups configuration compatibility check                                                 |notapplicable     |
|System kickstart                                                                          |notapplicable     |
|NIS ypbind config files back-up                                                           |notapplicable     |
|NIS Makefile back-up                                                                      |notapplicable     |
|NIS server maps check                                                                     |notapplicable     |
|NIS server MAXUID and MAXGID limits check                                                 |notapplicable     |
|NIS server config file back-up                                                            |notapplicable     |
|several kernel networking drivers not available in CentOS 7                               |pass              |
|several kernel storage drivers not available in CentOS 7                                  |pass              |
|OpenSSH sshd_config migration content                                                     |pass              |
|Configuration for quota_nld service                                                       |pass              |
|Disk quota netlink message daemon moved into quota-nld package                            |pass              |
|Luks encrypted partition                                                                  |pass              |
|Configuration for warnquota tool                                                          |pass              |
|Architecture Support                                                                      |pass              |
|Debuginfo packages                                                                        |pass              |
|Cluster and High Availability                                                             |pass              |
|Read Only FHS directories                                                                 |pass              |
|In-place Upgrade Requirements for the /usr/ Directory                                     |pass              |
|Developer Tool Set packages                                                               |pass              |
|Hyper-V                                                                                   |pass              |
|Check for ethernet interface naming                                                       |pass              |
|Plugable authentication modules (PAM)                                                     |pass              |
|Ruby 2.0.0                                                                                |pass              |
|SCL collections                                                                           |pass              |
|Incorrect usage of reserved UID/GIDs                                                      |pass              |
|Compatibility Between iptables and ip6tables                                              |informational     |
|VCS repositories                                                                          |informational     |
|Removed options in coreutils binaries                                                     |informational     |
|Removed options in gawk binaries                                                          |informational     |
|Removed options in netstat binary                                                         |informational     |
|Removed options in quota tools                                                            |informational     |
|GMP library incompatibilities                                                             |informational     |
|CVS Package Split                                                                         |informational     |
|httpd configuration compatibility check                                                   |informational     |
|NTP configuration                                                                         |informational     |
|Information on time-sync.target                                                           |informational     |
|Disk quota tool warnquota moved into quota-warnquota package                              |informational     |
|File Systems, Partitions and Mounts Configuration Review                                  |informational     |
|Sonamebumped libs                                                                         |informational     |
|SonameKept Reusable Dynamic Libraries                                                     |informational     |
|Foreign Perl modules                                                                      |informational     |
|YUM                                                                                       |informational     |
|Reusable Configuration Files                                                              |fixed             |
|Replaced rpms                                                                             |fixed             |
|package downgrades                                                                        |fixed             |
|OpenSSH sysconfig migration content                                                       |fixed             |
|State of LVM2 services.                                                                   |fixed             |
|Configuration Files to Review                                                             |needs_inspection  |
|File Lists for Manual Migration                                                           |needs_inspection  |
|Obsoleted rpms                                                                            |needs_inspection  |
|Binary rebuilds                                                                           |needs_inspection  |
|CA certificate bundles modified                                                           |needs_inspection  |
|Python 2.7.5                                                                              |needs_inspection  |
|Check for usage of dangerous range of UID/GIDs                                            |needs_inspection  |
|Packages not signed by CentOS                                                             |needs_action      |
|Removed rpms                                                                              |needs_action      |
|General                                                                                   |needs_action      |
|Removed .so libs                                                                          |needs_action      |
|Content for enabling and disabling services based on CentOS 6 system                      |needs_action      |
|User modification in /etc/rc.local and /etc/rc.d/rc.local                                 |needs_action      |
---------------------------------------------------------------------------------------------------------------
Tarball with results is stored here /root/preupgrade-results/preupg_results-170926155342.tar.gz .
The latest assessment is stored in directory /root/preupgrade .
Summary information:
We found some potential in-place upgrade risks.
Read the file /root/preupgrade/result.html for more details.
Upload results to UI by command:
e.g. preupg -u http://127.0.0.1:8099/submit/ -r /root/preupgrade-results/preupg_results-*.tar.gz .

出てきた。結構いろいろみてくれるのね、しゅごい。

/root/preupgrade/result.html に詳細があるそうなので、ドキュメントルートに持っていって、ブラウザから確認すると次のような画面になった。

  • 半分くらいの項目はちゃんと確認しないと危ない気がする
  • がんばってアップグレードしてもとっちらかった状態になりそう
  • web サーバとして簡単に使っている程度なので、バックアップ全部とって入れ替えるもあり

というところから、入れ替えちゃったほうが早そうだなあ、という結論。

まずは docker だったりで CentOS 7 を準備して、稼働中のサービス、設定ファイルや鍵を移しつつ、いろいろ動作するか確認したのち、まるっと入れ替える、という流れになりそーう。

つづく。

Node.js から Headless Chrome を操作できる puppeteer を試す

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

いい感じに Javascript がゴリゴリ書かれていて、認証が必要なサイトを定期的にキャプチャを取る、という謎タスクが生まれ、ちょうど最近話題の Headless Chrome でも試してみるかな~~と思ったら puppeteer なるものがあるそうなので、これを使ってみる。

GoogleChrome/puppeteer: Headless Chrome Node API

GoogleChrome 配下にいるので Chrome 公式のパッケージだ。

何も気にせず動かしてみる

// puppeteer 導入
$ npm init
$ npm install puppeteer

ドキュメントにもあるサンプルソースを入れてみる。

// index.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({path: 'example.png'});

  await browser.close();
})();

https://example.com へとアクセスし、そのページをキャプチャして ./example.png に保存するソースだ。 await で簡単わかりやすい書き方になるのが素晴らしい。これを動かしてみる。

$ node index.js
(node:30285) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Failed to launch chrome!
/var/www/html/crawl/bitbucket_billing/node_modules/puppeteer/.local-chromium/linux-497674/chrome-linux/chrome: error while loading shared libraries: libXss.so.1: cannot open shared object file: No such file or directory


TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md

(node:30285) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

かなしみ…。どうにも libXss.so.1 が開けずにエラーっぽい。パッケージを探してインストールし、再度動かしてみる。

$ yum provides */libXss.so.1
...

libXScrnSaver-1.2.2-6.1.el7.i686 : X.Org X11 libXss runtime library
Repo        : base
Matched from:
Filename    : /usr/lib/libXss.so.1

libXScrnSaver-1.2.2-6.1.el7.x86_64 : X.Org X11 libXss runtime library
Repo        : base
Matched from:
Filename    : /usr/lib64/libXss.so.1


$ sudo yum install libXScrnSaver
...

$ node index.js
(node:1345) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Failed to launch chrome!
/var/www/html/crawl/bitbucket_billing/node_modules/puppeteer/.local-chromium/linux-497674/chrome-linux/chrome: error while loading shared libraries: libgtk-3.so.0: cannot open shared object file: No such file or directory


TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md

(node:1345) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

GTK+3 が要求される。都合によって CentOS6 なのだが GTK+3 は入れるのがめっちゃ困難だった記憶。とりあえず GTK+3 というか Chrome を入れる方法を調べたら必要なライブラリも揃うだろう、と判断して調べる。

Google Chrome 60 Released - Install on RHEL/CentOS 7/6 and Fedora 26-20

Yes, they’ve discontinued support for RHEL 6.X version as of Google Chrome and on other side, latest Firefox and Opera browsers run successfully on the same platforms.

CentOS6 では Chrome は動かない、ダメ。Richard Lloyd 氏がスクリプトを作っているそうだが、そのサイトでも、いつまでも古いものを使うな、って書いてあった。

Site has been shut down

You have two choices really (ignoring Chromium, which no-one seems to be keeping up-to-date for RHEL/CentOS 6):

1. Use Mozilla Firefox ESR via "yum install firefox" that is shipped with RHEL/CentOS 6. Unfortunately, this can be up to a year out of date w.r.t. features compared to the more "normal" Mozilla Firefox that you can download from Mozilla (but that "normal" Mozilla Firefox is now also using GTK+3 and doesn't work on RHEL/CentOS 6 either).

2. Move to RHEL/CentOS 7, perhaps first in a VM before upgrading on bare metal once the VM is to your satisfaction. The latest Google Chrome installs and runs out-of-the-box on RHEL/CentOS 7 (see below).

気を新たにdockerで動かす

まあ、一旦使ってみるなら docker でもいいか。

$ docker run -it -d -v $(pwd):/var/www/puppeteer_test centos:latest
Unable to find image 'centos:latest' locally
latest: Pulling from centos
6c5159923047: Pull complete
acec82331181: Pull complete
1c1b67b33c28: Pull complete
Digest: sha256:57c91a43765de2147fa666a128477bc6101d6fda660feaaa5fbb1a8b110c934f
Status: Downloaded newer image for centos:latest
2be8ec65e6e4db3d852d563b82cc4c90ca90f62b4aaa6304d6e00f06d6a4a9e7

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
2be8ec65e6e4        centos:latest       "/bin/bash"         16 seconds ago      Up 16 seconds                           romantic_sinoussi

$ docker exec -it 2be /bin/bash

# cd /var/www/puppeteer_test/
# ls
index.js  node_modules  package-lock.json  package.json

# node -v
bash: node: command not found

// nodeがいないので入れる
# curl -sL https://rpm.nodesource.com/setup_8.x | bash -
# yum install nodejs

# node index.js
(node:114) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Failed to launch chrome!
/var/www/puppeteer_test/node_modules/puppeteer/.local-chromium/linux-497674/chrome-linux/chrome: error while loading shared libraries: libpangocairo-1.0.so.0: cannot open shared object file: No such file or directory


TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md

(node:114) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

// とりあえず yum provides で探しながら一個ずつ入れていく
// をまとめたものがこちら
# yum groupinstall "Development Tools"
# yum install pango libXScrnSaver libXcomposite libXcursor libXi libXtst cups-libs libXrandr GConf GConf2 alsa-lib atk gtk3

// 起動してみる
# node index.js
(node:11985) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Failed to launch chrome!
[0920/101508.987383:ERROR:zygote_host_impl_linux.cc(88)] Running as root without --no-sandbox is not supported. See https://crbug.com/638180.


TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md

(node:11985) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

root で --no-sandbox なしは認められないらしいのでソースを変える。

// index.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({args: ['--no-sandbox']});
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({path: 'example.png'});

  await browser.close();
})();

再び実行。

# node index.js

# ls
example.png  index.js  node_modules  package-lock.json  package.json

キャプチャ取れたので、画像を確認。

勝ち。

というわけで puppeteer 動いた。CentOS6 で CentOS7 が動いてて~ってちょっとアレなので、バージョン上げてまた試……、じゃない、本来の目的だった認証通してキャプチャできるかをやらなくちゃ。続く。

Rx と Promise と。

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

Rx 云々を見かけるたびに Web でそんな出番ないような…?非同期やすとりーむや配列的なのをよろしくするやつ?と、もはや知ることを放棄していて。今頃もったいないなーと思ったので、改めてちょっと調べるの巻。

Rx について

ReactiveX - Intro

オブザーバーパターンを拡張したもので、シーケンスなデータやイベントをサポートするため、スレッドやノンブロックIOとかを抽象化して組み立てることができる。オブザーバーパターン = 監視する人と通知する人がいるよー、というイメージでとりあえずはよい。

Promise について

JavaScript Promiseの本

非同期処理を上手くパターン化して書くための、統一的なインターフェース。ここではオブザーバーという話はない。 コードを見ても、非同期を取りまわすためのいい感じのインターフェースである、というイメージ(個人の感想です)

Rx とPromise はどう使い分けるとよさそう?

いくつかサイトを眺めてみて。

JavaScriptの非同期処理には何を使うべきか - Qiita
State, Promise & Reactive プログラミング
What are the differences between observables and promises in JavaScript? - Stack Overflow

で、 Stackoverflow の回答が結構それっぽいのかなあと。

Simply put: A promise resolves to a single value asynchronously, an observable resolves to (or emits) multiple values asynchronously (over time).

ざっくり訳すと。

Observable (= Rx として) は多数の非同期を継続的に処理したい
Promise は単発の非同期処理をしたい

なので。

Rx は配列操作とか、画面操作とか、とかと相性がよい
Promise は単発になりがちのもの、外部呼出し(Ajaxとか)と相性がよい

このボタンおしたら Ajax して、待機状態にして、結果が来たらここを書き換えて、イベント設定して、… なんてものは Rx と Promise を組み合わせるといいのかもしれない。が、昨今のフロントエンドにおいては Flux なんかでライブラリの中でよろしくやっていることが多いので、あえて開発する人が関与していくことは少ない…?

どちらかというとアプリ系で Rx が盛り上がってる気がしていて、 Web フロントではそうでもないような…。どっちも知識薄くてわからんのだけど、言語が違っても画面とロジック、裏でデータがほいほい、みたいなことってどちらも一緒のことだと思うんだけど、その差はなんなんなん?

docker を使っていてポートを指定するときに IP アドレスを指定できる

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

開発用だったり検証用だったりで docker をぼちぼち使っているのですが、ゲストのポートをホストのポートへバインドするときに、IPアドレスを指定できます。

$ docker run -it -d -p 127.0.0.1:10080:80 myapp

どこで使うんだよって話ですが、セキュリティを考えるときには使うんじゃないっすかね、たぶん。

IPアドレスの指定がないと 0.0.0.0 で待ち受けるので、接続をどこからでも受け付けます。そのため、例えばホスト側で nginx 等を使って何かしらの認証が必要なリバースプロキシをしていたとしても、指定ポートでコンテナに直接アクセスができてしまいます。

nginx で不特定多数から見られないよう Basic 認証を設定していたのですが、見れるのかな?ってポート指定してアクセスしたら無事に見れてしまって、うふふってなってました。

$ docker run -it -d -p 10080:80 nginx
485e21a84ea180bc69db198bcdb7e417fb61d2747642059b8bd4435873aa1503

$ netstat -nao | grep 10080
tcp        0      0 :::10080                    :::*                        LISTEN      off (0.00/0/0)

$ docker rm -f 485
485

$ docker run -it -d -p 127.0.0.1:10080:80 nginx
94ea5a9d74578fc69777cf0dc3b7dc918ce2e4ec74fef52441521a8d163680d8

$ netstat -nao | grep 10080
tcp        0      0 127.0.0.1:10080             0.0.0.0:*                   LISTEN      off (0.00/0/0)

ドキュメントはここ
https://docs.docker.com/engine/reference/commandline/run/#publish-or-expose-port--p-expose

NGINX Unit なるものリリースされたらしいのでとりあえず PHP でも動かしてみる

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

NGINX Unit なるものがリリースされたらしい。

・NGINX 公式サイトの情報
NGINX Unit

・NGINX Unit 公式サイト
NGINX Unit

・見かけた記事
NGINXからアプリケーションサーバ「NGINX Unit」がオープンソースで登場。PHP、Go、Pythonに対応。Java、Node.jsにも対応予定 - Publickey

ドキュメントに書かれているインストール方法や設定を見てたらパッと出来そうだったので思いたってやったったの巻。

どうにも公式サイトのドキュメンテーションを見ると CentOS と Ubuntu 向けにはパッケージが適当されているっぽい。なのでコレを利用する。また nginx が既にいる環境でやると便利そうなので docker で立ち上げていく

// ホストにて
$ docker run --name nginx-unit -d -p 8080:80 nginx
$ docker exec -it nginx-unit bash

// 以降はコンテナ内、ゲストで作業

// 準備
# apt-get update
# apt-get install curl net-tools vim less

// キーの登録
# curl http://nginx.org/keys/nginx_signing.key > /tmp/nginx_signing.key
# apt-key add /tmp/nginx_signing.key

// /etc/apt/sources.list に追記
# vim /etc/apt/sources.list
deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx

// nginx-unit のインストール
# apt-get update
# apt-get install unit


// コンフィグ確認
# cat /etc/init.d/unit
CONFIG=/etc/unit/config

// /var/run/control.unit.sock が設定用のソケットファイルっぽい
    dumpconfig)
        curl -sS --unix-socket /var/run/control.unit.sock localhost >${CONFIG}.new



# vim /etc/unit/config
{
     "listeners": {
         "*:8300": {
             "application": "myapp"
         }
     },
     "applications": {
         "myapp": {
              "type": "php",
              "workers": 3,
              "root": "/var/www/html/",
              "index": "index.php"
         }
     }
}

// 起動
# service unitd start

// コンフィグの確認
# curl --unix-socket /var/run/control.unit.sock http://localhost/
{
        "listeners": {},
        "applications": {}
}

// コンフィグの変更してみる
# service unitd stop
# mv /etc/unit/config /etc/unit/myconfig.json
# service unitd start

# curl -X PUT -d @/etc/unit/myconfig.json --unix-socket /var/run/control.unit.sock http://localhost/
curl: (52) Empty reply from server

# curl --unix-socket /var/run/control.unit.sock http://localhost/

// レスポンスが返ってこない

// ログ確認
# less /var/log/unitd.log

// ... とくにエラーっぽいものなし。

パッケージのものだとうまく動かない???

ということでソースから入れる路線を試す。

// パッケージからのものを消す
# apt-get remove unit

// 準備
# apt-get install git build-essential php php-dev libphp-embed
# cd /tmp
# git clone https://github.com/nginx/unit
# cd unit

// ビルド
# ./configure --prefix=/usr/share/unit/
# ./configure php
# make all

// インストール
# make install
# ln -s /usr/share/unit/sbin/unitd /sbin/unitd

// 起動
# unitd

// 設定してみる
# vim /tmp/unit_config.json

{
     "listeners": {
         "*:8300": {
             "application": "myapp"
         }
     },
     "applications": {
         "myapp": {
              "type": "php",
              "workers": 3,
              "root": "/var/www/html/",
              "index": "index.php"
         }
     }
}


# curl -X PUT -d @/tmp/unit_config.json --unix-socket /usr/share/unit/control.unit.sock http://localhost/
{
        "success": "Reconfiguration done."
}

# curl --unix-socket /usr/share/unit/control.unit.sock http://localhost/
{
        "listeners": {
                "*:8300": {
                        "application": "myapp"
                }
        },

        "applications": {
                "myapp": {
                        "type": "php",
                        "workers": 3,
                        "root": "/var/www/html/",
                        "index": "index.php"
                }
        }
}

// 動作確認
# echo "<?php echo 'hello php';" > /var/www/html/index.php
# curl localhost:8300
hello php

// nginx からプロキシしてみる
# vim /etc/nginx/conf.d/default.conf

upstream unit_backend {
    server 127.0.0.1:8300;
}

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /var/www/html;
        index  index.html index.htm;
    }

    location ~ \.php$ {
        proxy_pass http://unit_backend;
        proxy_set_header Host $host;
    }
}

// 動作確認
# service nginx reload
Reloading nginx: nginx.

# curl localhost/index.php
hello php

出来ているようなので、もう少しちゃんと PHP を動かしてみる。

// phpモジュール追加
# apt-get install php-mbstring php-zip

// composer 導入
# curl https://getcomposer.org/installer > installer
# php installer
# rm installer
# chmod +x composer.phar

// Laravelプロジェクトインストール
# ./composer.phar create-project laravel/laravel app

// Laravel プロジェクトの色々設定
# chmod -R 777 /var/www/html/app/storage/
# php artisan key:generate

// nginx-uniti のルートディレクトリを変更
#  curl -X PUT -d '"/var/www/html/app/public/"' --unix-socket /usr/share/unit/control.unit.sock http://localhost/applications/myapp/root
{
        "success": "Reconfiguration done."
}

// 確認
# curl --unix-socket /usr/share/unit/control.unit.sock http://localhost/
{
        "listeners": {
                "*:8300": {
                        "application": "myapp"
                }
        },

        "applications": {
                "myapp": {
                        "type": "php",
                        "workers": 3,
                        "root": "/var/www/html/app/public/",
                        "index": "index.php"
                }
        }
}


// nginx のルートディレクトリを変更
# vim /etc/nginx/conf.d/defualt.conf

    location ~ / {
        proxy_pass http://unit_backend;
        proxy_set_header Host $host;
    }

# service nginx reload


// 動作確認
# curl localhost

// 何か出ていそうなのでブラウザで開く…!

イエーイ! NGINX Unit で Laravel アプリケーションが動いたぞ~~~空っぽだけど…。

ご覧とおり php-fpm は動いてなくて unit のワーカーが動いてるだけですねー、しゅごい。

# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  32648  3464 ?        Ss   00:50   0:00 nginx: master process nginx -g daemon off;
root         8  0.0  0.0  18224  2176 ?        Ss+  00:51   0:00 bash
root     26019  0.0  0.0  22196   852 ?        Ss   03:00   0:00 unit: main [unitd]
nobody   26021  0.0  0.0  32440   736 ?        S    03:00   0:00 unit: controller
nobody   26022  0.0  0.0 204620  1440 ?        Sl   03:00   0:00 unit: router
nobody   27293  0.0  0.7 269004 28232 ?        S    03:28   0:00 unit: "myapp" application
nginx    27330  0.0  0.0  33088  2152 ?        S    03:31   0:00 nginx: worker process
root     27331  1.2  0.0  18140  2064 ?        Ss   03:41   0:00 bash
root     27337  0.0  0.0  36572  1588 ?        R+   03:41   0:00 ps aux

現状では Go / PHP / Python に対応しているようで言語環境の準備と configure 時に言語指定すれば使えるようです。

手元にいい感じにお試せるアプリケーションがいないので細かい挙動の様子をみるなど出来ていないのですが、リリースニュース直後からはてぶが盛り上がったり github も盛り上がりを見せている(ように見える)ので、ちょっとしたバグなどなどあってももりもり直されていくんじゃないかなーという予感です。

そのうち Apache + mod_php / nginx + php-fpm / nginx + unit などでパフォーマンスも比べていきたいですねー!

Python で階層型クラスタリング

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

最近は Python をちょこまかといじることが増えてきたように感じています。というところで、クラスタリングのことを調べていたのですが、なんとなく k-means しておけばいいっしょ、くらいだったのですが、もっと奥深い色々が出てきて、クラスタリングわからないマンになりつつあります。そんなわけで、階層型クラスタリングを調べて、やってみたのでまとめておきます。

 

クラスタリングとは

クラスタリングとはデータを良い具合にまとめるという、教師なし学習の典型例です。何かしらの特徴量に沿って、データの流れや雰囲気をアルゴリズム的に見抜き、その様子でデータをまとめることができます。

クラスタリングとしてもっとも一般的なものは k-means でしょうか。k-means は非階層型クラスタリングと呼ばれています。具体的には以下のようなアルゴリズムでクラスタリングを行います。

  1. 事前に設定されるクラスタ数 K を元に、ランダム(※)にクラスタの中心を決定する
  2. 各要素から一番近いクラスタの位置を探し、それを各要素のクラスタとして設定する
  3. 各クラスタの重心を計算し、その重心位置をクラスタの中心として設定する
  4. 各要素から一番近いクラスタの位置を探し、それを各要素のクラスタとして設定する
  5. 各クラスタの重心を計算し、その重心位置をクラスタの中心として設定する
  6. ...これを重心の移動がしきい値を下回るまで、あるいは指定の回数を超えるまで実施する
  7. 最終的に、各要素から一番近いクラスタの位置が、その要素のクラスタとして決定される

(イメージ図@雑)

※ランダムにするか、程よい位置にするか(k-means++)などの初期位置決定の方法があります

後述する階層型との比較でも挙げますが、初期位置によってある程度クラスタの別れ方が分岐することも往々にして発生します。また、クラスタリング結果が毎回異なる可能性もあり、厳密な分類がほしい、なんとなく纏めるでは嫌、といった場合の要望をかなえることはできません。かといって階層型クラスタリングがこの要望を常に叶えられるわけでもないのですが...

今回取り上げる階層型との比較として後述しますが k-means では、事前にクラスタ数を設定する必要があるため、それが難しいような状況では大きな力を発揮することが難しいです。

ちなみにアルゴリズムとして x-means という、クラスタ数を自動で設定できるものもあります。K=2 として再帰的に k-means を実施し BIC と呼ばれるベイズ情報量規準が、設定したしきい値を下回るまで実行していくようなものになっています。詳しいことはぼくもあまりわからないので、その話が上がっている論文を見ると幸せになれると思います。

http://www.rd.dnc.ac.jp/~tunenori/xmeans.html

機械学習について触れていると、似たような「クラス分類」といったワードを見かけたこともあるかと思います。こちらはクラスタリングとは異なり、教師あり学習で「あるデータがどのクラスに属しているかを当てる」という全く別の問題になります。リンゴは果物、キャベツは野菜、というような、明確に正解があるようなデータを分類していくときには、クラス分類になります。

 

階層型クラスタリングとは

階層型クラスタリングは以下のようなアルゴリズムで処理されます。

  1. すべての要素に固有のクラスタを設定する
  2. クラスタを2つ取り上げていき、最も距離が近いもの(※)をくっつけて、新しいクラスタを作成する
  3. これを再帰的に実行し、最終的にクラスタが1つになるまで実施する

※「距離」の定義は設計次第です。ユークリッド距離、コサイン距離、など様々な計算方法があります。

※くっつけて出来上がったクラスタ間の距離計算は設計次第です。

  • クラスタが含む要素をそれぞれ比較し、最も近いものを距離とする(最近隣法)
  • クラスタが含む要素をそれぞれ比較し、最も遠いものを距離とする(最遠隣法)
  • クラスタが含む要素をそれぞれ比較し、平均的な距離とする(平均法)
  • クラスタが含む要素をそれぞれ比較し、それぞれの距離について分散を利用して程よい値を取る(ウォード法、よく使われているらしい)

(イメージ図@雑)

そうして出来上がったクラスタリング結果ですが、このままでは木構造のようになっており、データとして扱うにはどれがどういうクラスタに属しているのかを明確に決定しにくいです。実際にあるタスクで利用したときには、最大距離の7割を基準にして、クラスタを分割していました。状況によってはパラメータの調整する必要がありそうな予感がしています。

計算量と実際にかかる時間を考慮すると、既に説明した k-means や、この階層型クラスタリングが高速に処理でき、「それっぽい結果」が得られるので、解決したい問題にも寄ってくるとは思いますが、クラスタリングにおける選択肢として現実的なのかなあと思います。

 

階層型・非階層型(k-means)のそれぞれの使いどころ

階層型が活用できそうなところは以下のようなところではないでしょうか?

  • データセットのうち、それぞれの距離に何かしらの特徴がありそう
  • どれくらいのクラスタに分けたらいいかわからない

階層型の場合。クラスタ間の距離で並べ替えることも容易にできるはずなので、しきい値の設定が小さいせいでクラスタを分けすぎてしまっても、ある程度までは前後関係もそれっぽく追いついてきます。なので、出来上がった後に、人力でカバーすることも可能なのではないかなあと思います(それってどうなの感はありますが...)あるいはクラスタ数がいくつになるか想定できないような場合にも、全てのデータを一度まとめあげるので、自動で出てきた結果の様子を見つつ、手動でクラスタ分けしていくことができると思います。

一方で非階層型...もとい k-means の活用できそうなところは以下のようなところではないでしょうか?

  • データセットの雰囲気がわからないけど、いったん分割してみたい
  • 明確に K 個の分類をしたい

k-means では最初に K を決定しないといけないので、どうあがいても K 個のクラスタができあがります。この分ける数が重要になるような、クラス分類に近いようなものの場合であれば k-menas は非常に有効なのではないでしょうか。ただし、それぞれのクラスタ間で相関がないことや、中途半端な位置のデータはどちらとも言えないような結果になることは注意がいるかもしれません。

また、どんなデータを入れても、なんとなくまとめてくれるので、人力で確認したときも、「ああーこういう見方もあるのね、なるほど」という謎の納得感が得られやすいとも思います。

 

階層型クラスタリングを Python でやってみる

それでは Python で 階層型クラスタリングをやっていきます。ここでは sciki-learn に入っているアイリスデータセットを対象に pandas で読み込んで scipy.cluster.hierarchy を使っていきます。

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
from sklearn.datasets import load_iris

# iris データセットの読み込み
iris = load_iris()
iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)
iris_labels = list(map(lambda t: iris.target_names[t], iris.target))

# 階層型クラスタリングの実施
# ウォード法 x ユークリッド距離
linkage_result = linkage(iris_df, method='ward', metric='euclidean')

# クラスタ分けするしきい値を決める
threshold = 0.7 * np.max(linkage_result[:, 2])

# 階層型クラスタリングの可視化
plt.figure(num=None, figsize=(16, 9), dpi=200, facecolor='w', edgecolor='k')
dendrogram(linkage_result, labels=iris_labels, color_threshold=threshold)
plt.show()

# クラスタリング結果の値を取得
clustered = fcluster(linkage_result, threshold, criterion='distance')

# クラスタリング結果を比較
print(clustered)
print(iris.target)

出力結果

様子も見ずデータを絞ったりせず全てぶっこんで、というのはちょっと微妙なところですが setosa だけキレイに分かれましたね。setosa と、それ以外とで距離に開きがあるので、setosa だけ非常に特徴的ってことがわかります。それで versicolor と virginica はぼちぼち似ている、なんてところですね。

 

 

というわけで、階層型クラスタリングの話でした。

余談にはなりますが、そんなこんなで出来たやつをいい感じにクラスタ分けしたよ~^^って渡したら、分岐したやつを画像じゃなくて csv とかで欲しいって言われたのでそれはまた。

docker で MySQL を立ち上げたい。

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

タイトルそのまま。

// たぶん mysql 5.7 がプルされる
// が、わからんので DockerHub を要確認
$ docker pull mysql:latest

// mysqlコンテナの起動
//   オプションについて
//     -e    : 環境変数
//     --name: コンテナ名
//     -d    : バックグラウンド
//     -p    : ポートバインディング(ホスト:ゲスト)
//   環境変数について
//     MYSQL_DATABASE     : 作るデータべース
//     MYSQL_USER         : 作るユーザ
//     MYSQL_PASSWORD     : 作るユーザのパスワード
//     MYSQL_ROOT_PASSWORD: ルートパスワード
$ docker run --name my_mysql \
  -e MYSQL_DATABASE=homestead \
  -e MYSQL_USER=homestead \
  -e MYSQL_PASSWORD=secret \
  -e MYSQL_ROOT_PASSWORD=secret \
  -d \
  -p 13306:3306 \
  mysql

// 接続確認
//   -h : ホスト名
//   -P : ポート番号
//   -u : ユーザ名
//   -p : パスワードを入力するプロンプトを出す
$ mysql -h 127.0.0.1 -P 13306 -u root -p

記事書くときに Docker Hub を確認したら 5.7.19 が latest で指定されているっぽい。というか 8.0 系も指定できるのか、検証なんかにも便利そうだ。

library/mysql - Docker Hub

でもって docker で MySQL が立ち上がると、こうさ、ほら、ユニットテスト回すときなんかに、こうしてクリーンな DB を提供できてよさそうだなーって。
イメージとしてはこんな感じ。

{
    ...
    "scripts": {
        "test": "phpunit --verbose",
        "docker-test": [
            "composer docker-db-remove"
            "composer docker-db-create",
            "composer test",
            "composer docker-db-remove"
        ],
        "docker-db-create": "docker run --name test_mysql -e MYSQL_DATABASE=homestead -e MYSQL_USER=homestead -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=secret -d -p 13306:3306 mysql",
        "docker-db-remove": "docker rm --force test_mysql",
        "docker-db-connect": [
          "#  Copy this command!",
          "#  mysql --host=127.0.0.1 --port=13306 --user=homestead --password=secret homestead"
        ]
    },
    ...
}

composer docker-test とやったらコンテナが立ち上がり 127.0.0.1:13306 で待ち受けているので、そこを指定するように phpunit.xml だとか .env だとかで DB 接続用の値を設定すればよい。テストの後はコンテナがポイされるので、テスト用のDBはいつでもクリーンに。テストでコケても大丈夫なように、最初にコンテナをポイしたほうがいいのかな。

composer スクリプトで I/O を全部引き渡す方法がわからなかったので docker-db-connect タスクが非常にあやしい感じだけど、これくらいでもまあまあ運用していけるんじゃないかなーー

Laravel でマイグレーションなどの artisan コマンドを実行するときのエラーを詳し表示した

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

artisan コマンドでエラーが出た場合、そのまま実行していると例外クラスとメッセージが出る程度で、どこでエラーになっとんねん!がわかりません。

実は自分で作った artisan コマンド含めてすべての artisan コマンドは verbose なオプションをサポートしています。そのためオプションを指定するだけで、例外を詳しく見ることができます。

$ php artisan migrate

  [Illuminate\Database\QueryException]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists (SQL: alter table `my_tables` drop `created_at`, drop `updated_at`)

  [Doctrine\DBAL\Driver\PDOException]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists
  
  [PDOException]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists 


$ php artisan migrate -v
                                                                                       
  [Illuminate\Database\QueryException (42000)]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists (SQL: alter table `my_tables` drop `created_at`, drop `updated_at`)

Exception trace:
 () at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:647
 Illuminate\Database\Connection->runQueryCallback() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:607
 Illuminate\Database\Connection->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:450
 Illuminate\Database\Connection->statement() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php:86
 Illuminate\Database\Schema\Blueprint->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:239
 Illuminate\Database\Schema\Builder->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:148
 Illuminate\Database\Schema\Builder->table() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:221
 Illuminate\Support\Facades\Facade::__callStatic() at /var/www/myapp/database/migrations/2017_08_28_190905_modify_my_tables.php:21
 ModifyMstGraphTransitionTerms11->up() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:357
 Illuminate\Database\Migrations\Migrator->Illuminate\Database\Migrations\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:363
 Illuminate\Database\Migrations\Migrator->runMigration() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:177
 Illuminate\Database\Migrations\Migrator->runUp() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:146
 Illuminate\Database\Migrations\Migrator->runPending() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:95
 Illuminate\Database\Migrations\Migrator->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php:69
 Illuminate\Database\Console\Migrations\MigrateCommand->fire() at n/a:n/a
 call_user_func_array() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
 Illuminate\Container\BoundMethod::Illuminate\Container\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:87
 Illuminate\Container\BoundMethod::callBoundMethod() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:31
 Illuminate\Container\BoundMethod::call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/Container.php:539
 Illuminate\Container\Container->call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:182
 Illuminate\Console\Command->execute() at /var/www/myapp/vendor/symfony/console/Command/Command.php:264
 Symfony\Component\Console\Command\Command->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:167
 Illuminate\Console\Command->run() at /var/www/myapp/vendor/symfony/console/Application.php:874
 Symfony\Component\Console\Application->doRunCommand() at /var/www/myapp/vendor/symfony/console/Application.php:228
 Symfony\Component\Console\Application->doRun() at /var/www/myapp/vendor/symfony/console/Application.php:130
 Symfony\Component\Console\Application->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:122
 Illuminate\Foundation\Console\Kernel->handle() at /var/www/myapp/artisan:35


  [Doctrine\DBAL\Driver\PDOException (42000)]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists

Exception trace:
 () at /var/www/myapp/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:107
 Doctrine\DBAL\Driver\PDOStatement->execute() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:449
 Illuminate\Database\Connection->Illuminate\Database\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:640
 Illuminate\Database\Connection->runQueryCallback() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:607
 Illuminate\Database\Connection->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:450
 Illuminate\Database\Connection->statement() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php:86
 Illuminate\Database\Schema\Blueprint->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:239
 Illuminate\Database\Schema\Builder->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:148
 Illuminate\Database\Schema\Builder->table() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:221
 Illuminate\Support\Facades\Facade::__callStatic() at /var/www/myapp/database/migrations/2017_08_28_190905_modify_my_tables.php:21
 ModifyMstGraphTransitionTerms11->up() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:357
 Illuminate\Database\Migrations\Migrator->Illuminate\Database\Migrations\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:363
 Illuminate\Database\Migrations\Migrator->runMigration() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:177
 Illuminate\Database\Migrations\Migrator->runUp() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:146
 Illuminate\Database\Migrations\Migrator->runPending() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:95
 Illuminate\Database\Migrations\Migrator->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php:69
 Illuminate\Database\Console\Migrations\MigrateCommand->fire() at n/a:n/a
 call_user_func_array() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
 Illuminate\Container\BoundMethod::Illuminate\Container\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:87
 Illuminate\Container\BoundMethod::callBoundMethod() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:31
 Illuminate\Container\BoundMethod::call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/Container.php:539
 Illuminate\Container\Container->call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:182
 Illuminate\Console\Command->execute() at /var/www/myapp/vendor/symfony/console/Command/Command.php:264
 Symfony\Component\Console\Command\Command->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:167
 Illuminate\Console\Command->run() at /var/www/myapp/vendor/symfony/console/Application.php:874
 Symfony\Component\Console\Application->doRunCommand() at /var/www/myapp/vendor/symfony/console/Application.php:228
 Symfony\Component\Console\Application->doRun() at /var/www/myapp/vendor/symfony/console/Application.php:130
 Symfony\Component\Console\Application->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:122
 Illuminate\Foundation\Console\Kernel->handle() at /var/www/myapp/artisan:35


  [PDOException (42000)]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists

Exception trace:
 () at /var/www/myapp/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:105
 PDOStatement->execute() at /var/www/myapp/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:105
 Doctrine\DBAL\Driver\PDOStatement->execute() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:449
 Illuminate\Database\Connection->Illuminate\Database\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:640
 Illuminate\Database\Connection->runQueryCallback() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:607
 Illuminate\Database\Connection->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:450
 Illuminate\Database\Connection->statement() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php:86
 Illuminate\Database\Schema\Blueprint->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:239
 Illuminate\Database\Schema\Builder->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:148
 Illuminate\Database\Schema\Builder->table() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:221
 Illuminate\Support\Facades\Facade::__callStatic() at /var/www/myapp/database/migrations/2017_08_28_190905_modify_my_tables.php:21
 ModifyMstGraphTransitionTerms11->up() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:357
 Illuminate\Database\Migrations\Migrator->Illuminate\Database\Migrations\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:363
 Illuminate\Database\Migrations\Migrator->runMigration() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:177
 Illuminate\Database\Migrations\Migrator->runUp() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:146
 Illuminate\Database\Migrations\Migrator->runPending() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:95
 Illuminate\Database\Migrations\Migrator->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php:69
 Illuminate\Database\Console\Migrations\MigrateCommand->fire() at n/a:n/a
 call_user_func_array() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
 Illuminate\Container\BoundMethod::Illuminate\Container\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:87
 Illuminate\Container\BoundMethod::callBoundMethod() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:31
 Illuminate\Container\BoundMethod::call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/Container.php:539
 Illuminate\Container\Container->call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:182
 Illuminate\Console\Command->execute() at /var/www/myapp/vendor/symfony/console/Command/Command.php:264
 Symfony\Component\Console\Command\Command->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:167
 Illuminate\Console\Command->run() at /var/www/myapp/vendor/symfony/console/Application.php:874
 Symfony\Component\Console\Application->doRunCommand() at /var/www/myapp/vendor/symfony/console/Application.php:228
 Symfony\Component\Console\Application->doRun() at /var/www/myapp/vendor/symfony/console/Application.php:130
 Symfony\Component\Console\Application->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:122
 Illuminate\Foundation\Console\Kernel->handle() at /var/www/myapp/artisan:35

ちなみにサポートするオプションと、表示レベルは以下のようになっています。


// /vendor/laravel/framework/src/Illuminate/Console/Command.php
// ※抜粋

use Symfony\Component\Console\Output\OutputInterface;

protected $verbosity = OutputInterface::VERBOSITY_NORMAL;

protected $verbosityMap = [
    'v' => OutputInterface::VERBOSITY_VERBOSE,
    'vv' => OutputInterface::VERBOSITY_VERY_VERBOSE,
    'vvv' => OutputInterface::VERBOSITY_DEBUG,
    'quiet' => OutputInterface::VERBOSITY_QUIET,
    'normal' => OutputInterface::VERBOSITY_NORMAL,
];
オプション レベル 生数値
v OutputInterface::VERBOSITY_VERBOSE 64
vv OutputInterface::VERBOSITY_VERY_VERBOSE 128
vvv OutputInterface::VERBOSITY_DEBUG 256
quiet OutputInterface::VERBOSITY_QUIET 16
normal OutputInterface::VERBOSITY_NORMAL 32

まあ、これってソースのとこにもあるんですけど、ベースになってる Symfony2 のものなんですけどね。

Verbosity Levels (current)

Symfony2 で定義されている値を使って artisan コマンドも同じように verbose な指定をサポートしているんですねー。もし自分で作るコマンドも verbose な指定をサポートするなら、指定されている定数を目安に出力すると良さそう。

Google Analytics API を使ってアシストコンバージョンを取得したかった

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

Google Analytics の API を使ってアシストコンバージョンをとりたいが、通常のセッション数などを取得できる Core Reporting API にはアシストコンバージョンという指標は無く取得することができない。 Multi-Channel Funnels Reporting API を使うことでその指標は取得できるようになる。がトラップがあり、やりたいことはできなかったので、タイトルが過去形。

なお MCF API のドキュメントにあるサンプルでは Java で書かれているが、言語別のライブラリが持っているインターフェースはおよそ同じなので、読み換えればよい。

Multi-Channel Funnels Reporting API - デベロッパー ガイド  |  アナリティクス Multi-Channel Funnels Reporting API  |  Google Developers

PHP ならこんな具合にして MCF をコールできる。

// セッション準備
session_start();

// API利用のための認証
//==================================================
$client = new \Google_Client();

// クライアントキー、シークレットの設定
// Developer Console で登録すると発行される
$client->setClientId('...');
$client->setClientSecret('...');

// オフラインアクセスを付与するとリフレッシュトークンを使って無期限にアクセスできる
$client->setAccessType('offline');

// 認証画面を都度表示する
$client->setApprovalPrompt('force');

// リダイレクトURLの設定
// Developer Console で設定した値と合わせないとエラーになる
$client->setRedirectUri('.....');

// アクセス権限の設定
$client->addScope([\Google_Service_Analytics::ANALYTICS_READONLY]);


// 認証が通ってくるとcodeがついてくるのでコレを利用して認証を通す
if (!empty($_GET['code'])) {
    unset($_SESSION['refresh_token']);
    $client->fetchAccessTokenWithAuthCode($_GET['code']);
    $_SESSION['refresh_token'] = $client->getRefreshToken();

} else {

    // リフレッシュトークンがなければ認証URLに飛ばす
    // とりあえず試すならセッションから読み込むと良い。
    if (empty($_SESSION['refresh_token'])) {
        header('Location: ' . $client->createAuthUrl());
        exit;
    }

    // リフレッシュトークンで認証する
    $client->refreshToken($_SESSION['refresh_token']);
}


// Google Analytics とのやり取りするオブジェクト準備
//==================================================
$analytics = new \Google_Service_Analytics($client);


// MCF API を実行する
//==================================================
$gaRequestResult = $analytics->data_mcf->get(
    // プロファイルID
    'ga:......',

    // 開始日
    '30daysAgo',
    // 終了日
    'yesterday',

    // 取得するメトリクス
    'mcf:assistedConversions',

    // 追加のオプション
    [
        'dimensions' => 'mcf:source',
        'filters' => 'mcf:source=@.',
    ]
);


// 結果確認
//==================================================
var_dump($gaRequestResult);

この例だと、流入元別のアシストコンバージョンを30日前から昨日まで範囲で取得する。また流入元の値に "." が含まれていることを条件とする。 流入元とは言うが (direct) のような値も入ってきて厄介。そこで「"." が含まれていること」にすればドメインが付いているものだけ抜けるという仕組み。

Google Developer Console で、このあたりから API 登録をすると動かせるようになる。

https://console.cloud.google.com/apis/dashboard

しかし、実はこの MCF API にはトラップがある。それはディメンションの設定にある。

Google Analytics の画面上からはデバイス別のアシストコンバージョンを確認することができるが API からはこれを確認するすべはない。ドキュメントを見ても MCF API のディメンションにはデバイスの話はない。

ディメンションと指標のリファレンス  |  アナリティクス Multi-Channel Funnels Reporting API  |  Google Developers

また MCF API を呼ぶときにディメンションに Core Reporting API の値を設定してもエラーになってしまう。逆に Core Reporting API を呼び出すときに MCF API のもの(アシストコンバージョンなど)を設定してもエラーになる。

ちなみに Core Reporting API で使える指標はここ。

Dimensions & Metrics Explorer  |  アナリティクス Reporting API v4  |  Google Developers

まあ。アシストコンバージョンそんな重要じゃねーからよくね、って話なのかもしれない。

Chrome の console.log で CSS プロパティが指定できるよ

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

以前からごみばこいんでは、コンソールにようこそメッセージを出していました。

Chrome Devtools のドキュメントを見ると console.log の詳細が書かれています。

診断とコンソールへのログ出力  |  Web  |  Google Developers

ドキュメントには CSS プロパティが利用できるとあるのですが、なぜかこれまでは色とサイズが変えられる程度だと思っていました。試すと text-shadow が出来たり border が出来たりと、様々なプロパティが効くようです。

var properties1 = [
    'font-size: 8em;',
    'color: white;',
    'padding: 5px;',
    'background: linear-gradient(red, orange, yellow, green, aqua, blue, violet);',
    'text-shadow: white 0 0 15px;'
];

var properties2 = [
    'font-size: 8em;',
    'color: #222;',
    'text-shadow: white 0 0 3px;',
    'margin-left: calc(-15em - 6px);',
];
console.log(
    '%cうぇるかむとぅーごみばこいん!%cうぇるかむとぅーごみばこいん!',
    properties1.join(''),
    properties2.join('')
);

いくつかプロパティを試してみたのですが display だったり float だったり position 、 top や left あたりは無効化されているようです。。

で。実は Inspector を使うと、ここも HTML/JS/CSS で構成されていることがわかります。
Inspector は URL バーに chrome://inspect/ と入力するとアクセス出来ます。その中でも Other の項目を見ると Developer Tools について Inspect することができます。

DevTools の DevTools ができるなんてちょっと不思議な光景ですねw

こうやって console.log した結果を眺めてみると %c で指定したものは span タグの style 属性に入っています。
これでは :beforeなどのセレクタが使えなかったり、階層構造が持てないのでアニメーションしたりなどなども出来ないです。先にあった使えないプロパティのところもあるので、凝ったことをするのはできなさそうですねー。残念。

1 2 3 4 5 6 7 8 9 10 11 12 13