Heroku で Linux コマンドを巧みに使いこなす

今年もやってまいりました、[Heroku Advent Calendar 2019](https://qiita.com/advent-calendar/2019/heroku) 3日目です!!

昨日は[@zunda](https://qiita.com/zunda)さんの[Heroku Dataclipsでお手軽公開クエリ](https://qiita.com/zunda/items/1562f8f02de97110ca17) で、明日は我らが Heroku SE [@masamis](https://qiita.com/masamis) さんの[Heroku Flow の強化、Review Apps (New)の強化点とその効用](https://qiita.com/masamis/items/96d4c82d2fefd1086cef)ネタです。Heroku チーム続きますね、テンション上がります!!!

しかし、その後が続いていないので、めっちゃ募集してます。新しい機能も提供されつつありますから、ぜひ、色々とお試しください。そして、フィードバックをください!!

# Heroku で Linux コマンドを使う

Heroku は Ubuntu ベースで、[さまざまなコマンド・パッケージ](https://devcenter.heroku.com/articles/stack-packages) が使えるようになっています。みんな大好き [One-Off Dynos](https://devcenter.heroku.com/articles/one-off-dynos)上で、ちょっとLinuxコマンドを使うなんてときにも、非常に便利です。

```:bash
$ heroku run bash
Running bash on ⬢ still-plateau-40494... up, run.6406 (Free)

~ $ curl oshiire.to -D - -s -o /dev/null
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Mon, 02 Dec 2019 01:45:27 GMT
Content-Type: text/html; charset=iso-8859-1
Content-Length: 227
Connection: keep-alive
Location: https://oshiire.to/
```
こんな感じで、地味に `curl`コマンドも入っているのは、お手軽便利です。ちょっとした Linuxホストがほしいんだけど、手元に Docker も Virtualbox も Vagrant もない、なんてときに Linuxコマンドを使える Internet 上のサーバは役に立つこともあるでしょう。

とはいえ、随分と頑丈にハードニングをしていますから、使いたいコマンドがないというケースは非常に多いと思います。

# Heroku で好きなコマンドを使えるようにセットアップする

できます。ubuntuですから`apt`コマンドを使って追加ができます。とは言っても、One-Off Dynos 上では

```
~ $ apt-get install redis-tools
Reading package lists... Done
Building dependency tree
Reading state information... Done
W: Not using locking for read only lock file /var/lib/dpkg/lock-frontend
W: Not using locking for read only lock file /var/lib/dpkg/lock
E: Unable to locate package redis-tools
~ $ sudo apt-get install redis-tools
bash: sudo: command not found
```
sudo もないですし、ファイルシステムがロックされているので、そりゃもう無理です。

しかし、ここで投げ出さないのが男の子。Heroku から[heroku-buildpack-apt](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-apt)なるものが提供されています(本番利用はお控えください。Herokuチームが作っているだけで、原則サポート対象外です)。

通常では、動かしたいWebアプリケーションから利用したいコマンドがないようなときに、それを使えるようにするために使います。がしかし、これ、なんと単体でも動きます。

なので、接続している Redis のパフォーマンステストしたい... なんてときに、`redis-tools` コマンドがあればすぐにお試しすることができる、ということです。一時的にちょっと使うのに、ちょーど良い感じです。

しかも、準備が必要なのは、この buildpack と `Aptfile` 一つだけです。やってみましょう。

```
$ cat Aptfile
redis-tools

$ heroku buildpacks:add https://github.com/heroku/heroku-buildpack-apt
Buildpack added. Next release on still-plateau-40494 will use https://github.com/heroku/heroku-buildpack-apt.
Run git push heroku master to create a new release using this buildpack.

$ git push heroku master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 217 bytes | 217.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Apt app detected
remote: -----> Detected Aptfile or Stack changes, flushing cache
remote: -----> Adding custom repositories
remote: -----> Updating apt caches
remote:        Get:1 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]
remote:        Get:2 http://apt.postgresql.org/pub/repos/apt bionic-pgdg InRelease [46.3 kB]
remote:        Get:3 http://apt.postgresql.org/pub/repos/apt bionic-pgdg/main amd64 Packages [286 kB]
remote:        Get:4 http://apt.postgresql.org/pub/repos/apt bionic-pgdg/11 amd64 Packages [2,483 B]
remote:        Get:5 http://archive.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB]
remote:        Get:6 http://archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB]
remote:        Get:7 http://archive.ubuntu.com/ubuntu bionic/main amd64 Packages [1,344 kB]
remote:        Get:8 http://archive.ubuntu.com/ubuntu bionic/universe amd64 Packages [11.3 MB]
remote:        Get:9 http://archive.ubuntu.com/ubuntu bionic-security/universe amd64 Packages [789 kB]
remote:        Get:10 http://archive.ubuntu.com/ubuntu bionic-security/main amd64 Packages [739 kB]
remote:        Get:11 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 Packages [1,035 kB]
remote:        Get:12 http://archive.ubuntu.com/ubuntu bionic-updates/universe amd64 Packages [1,316 kB]
remote:        Fetched 17.3 MB in 3s (6,118 kB/s)
remote:        Reading package lists...
remote: -----> Fetching .debs for redis-tools
remote:        Reading package lists...
remote:        Building dependency tree...
remote:        The following additional packages will be installed:
remote:          libjemalloc1
remote:        Suggested packages:
remote:          ruby-redis
remote:        The following NEW packages will be installed:
remote:          libjemalloc1 redis-tools
remote:        0 upgraded, 2 newly installed, 0 to remove and 57 not upgraded.
remote:        Need to get 598 kB of archives.
remote:        After this operation, 2,840 kB of additional disk space will be used.
remote:        Get:1 http://archive.ubuntu.com/ubuntu bionic/universe amd64 libjemalloc1 amd64 3.6.0-11 [82.4 kB]
remote:        Get:2 http://archive.ubuntu.com/ubuntu bionic-security/universe amd64 redis-tools amd64 5:4.0.9-1ubuntu0.2 [516 kB]
remote:        Fetched 598 kB in 1s (803 kB/s)
remote:        Download complete and in download only mode
remote: -----> Installing libjemalloc1_3.6.0-11_amd64.deb
remote: -----> Installing redis-tools_5%3a4.0.9-1ubuntu0.2_amd64.deb
remote: -----> Writing profile script
remote: -----> Rewrite package-config files
remote: -----> Discovering process types
remote:        Procfile declares types -> (none)
remote:
remote: -----> Compressing...
remote:        Done: 1.2M
remote: -----> Launching...
remote:        Released v3
remote:        https://still-plateau-40494.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/still-plateau-40494.git
 * [new branch]      master -> master
```

`Aptfile` に必要な(Ubuntuの)パッケージの名前を入れて、buildpack-apt を加えて、いつもどおりに `git push heroku master` するだけ。準備したのは `Aptfile` 一つだけです!

では、実際に Heroku 内で実行してみましょう。

```
$ heroku run bash
Running bash on ⬢ still-plateau-40494... up, run.3591 (Free)

~ $ redis-benchmark --help
Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests>] [-k <boolean>]

 -h <hostname>      Server hostname (default 127.0.0.1)
 -p <port>          Server port (default 6379)
 -s <socket>        Server socket (overrides host and port)
 -a <password>      Password for Redis Auth
 -c <clients>       Number of parallel connections (default 50)
 -n <requests>      Total number of requests (default 100000)
 -d <size>          Data size of SET/GET value in bytes (default 3)
 --dbnum <db>       SELECT the specified db number (default 0)
 -k <boolean>       1=keep alive 0=reconnect (default 1)
 -r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD
  Using this option the benchmark will expand the string __rand_int__
  inside an argument with a 12 digits number in the specified range
  from 0 to keyspacelen-1. The substitution changes every time a command
  is executed. Default tests use this to hit random keys in the
  specified range.
 -P <numreq>        Pipeline <numreq> requests. Default 1 (no pipeline).
 -e                 If server replies with errors, show them on stdout.
                    (no more than 1 error per second is displayed)
 -q                 Quiet. Just show query/sec values
 --csv              Output in CSV format
 -l                 Loop. Run the tests forever
 -t <tests>         Only run the comma separated list of tests. The test
                    names are the same as the ones produced as output.
 -I                 Idle mode. Just open N idle connections and wait.

Examples:

 Run the benchmark with the default configuration against 127.0.0.1:6379:
   $ redis-benchmark

 Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:
   $ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20

 Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:
   $ redis-benchmark -t set -n 1000000 -r 100000000

 Benchmark 127.0.0.1:6379 for a few commands producing CSV output:
   $ redis-benchmark -t ping,set,get -n 100000 --csv

 Benchmark a specific command line:
   $ redis-benchmark -r 10000 -n 10000 eval 'return redis.call("ping")' 0

 Fill a list with 10000 random elements:
   $ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__

 On user specified command lines __rand_int__ is replaced with a random integer
 with a range of values selected by the -r option.
```

ご覧のとおり、標準では搭載されていない `redis-benchmark` コマンドが利用できるようになりました!

# One-Off Dynos のプランを指定したい

これは便利だな!!! と思ったところで、One-Off Dynos ってプラン変えられないんだよなぁーなんて勘違いしている方いらっしゃいませんか？

**できます**

`heroku run` するときに `--size=` でプランを指定するだけです。詳細は[One-off dyno type](https://devcenter.heroku.com/articles/one-off-dynos#one-off-dyno-type)

ちょっと一時的に Performance-L で、Linux コマンドを打ちたい...

```
heroku run bash --size=performance-l
Running bash on ⬢ still-plateau-40494... up, run.8643 (Performance-L)
~ $
```

ほら、ご覧のとおり、Performance-L Dyno を無事に起動することができました!!

ちょっと Linux(Ubuntu) のコマンドが使いたいけど、手元に自由になる Linuxサーバーがない... なんていう、私のためにあるかのような仕組みです。使い終わったら、とっとと削除しておきましょう(そのコマンドが脆弱性にならないとも限りません)。

では、健やかな Heroku ライフを。