いま知っておきたいLinux─WebアプリがOSのプロセスとしてどのように見えるか? を運用に生かす

Webアプリを動かして負荷をかけると、OSのプロセスという観点ではどのように見えるのでしょう? それを通して運用やトラブルシューティングではどういったことが分かるのでしょう? Linuxカーネルの開発者でもある武内覚(sat)さんによる解説です。

いま知っておきたいLinux─WebアプリがOSのプロセスとしてどのように見えるか? を運用に生かす

こんにちは、sat@satoru_takeuchiと申します。

コンピュータが誕生してから現在まで、最終的にエンドユーザが意識するアプリケーション開発はどんどん楽になっています。先人たちのたゆまぬ努力の結果、アプリ開発者はOSや、そのさらに下にあるハードウェアのことをほとんど意識することなく開発ができるようになりました。

しかし、「作ったアプリが、OSレベルでどのように動いているか?」が今一つピンと来なくて、モヤモヤしていないでしょうか。それが分からないため、実運用においてトラブルシューティングがうまくできなかったり、トラブルが起きないように何をすればいいか分からなかったりしていないでしょうか。

本記事では、そのようなことで困っている方々に向けて、基本的なOSについての知識、とくにLinuxについての知識について述べた上で、さらにどのようなことに配慮すればいいのかについて説明します。

本記事を読むために必要な前提知識は、次の3つです。

  • 簡単なプログラムを作れる。言語は問わない
  • Linuxのコマンドラインツールの基本的な使い方を知っている
  • Linuxにおけるプロセスがどういうものかを知っている

あれもこれもと一度に説明しても消化不良になるだけですので、ここでは話をLinuxのプログラム実行単位であるプロセスに絞ります。具体的には、Webアプリを実行する際に、Linuxのプロセスのレベルではどのように見えるのかについて書きます。

本記事は、単に概念の説明をするだけではなく、実際にコマンドを実行することを重視しています。ぜひ、皆さん自身の環境でも試してみてください。

本記事における実行例は、全て以下の環境において実施したものです。

CPU
AMD Ryzen 5 PRO 2400GE(SMTは無効化1
OS
Ubuntu 18.04
パッケージ
qemu-kvm、docker、go、curl

なお、本記事ではソフトウェアのインストール方法および使い方については述べません。

実験に使う「おみくじ」アプリについて

本記事で使用する実験プログラムは、次のように非常に単純な「おみくじ」アプリです。

  1. ユーザが、WebブラウザなどからURLにアクセスする
  2. Webブラウザから、サーバサイドプログラムにリクエストを送る
  3. サーバサイドプログラムが、ランダムに選ばれた結果を示すページをWebブラウザに返す
  4. 結果をもとに、Webブラウザが文字列を表示する

このWebアプリのサーバサイドプログラムのソース(Go言語)は次の通りです。ここでは名前をfortuneとします。

package main

import (
        "fmt"
        "log"
        "math/rand"
        "net"
        "net/http"
        "strconv"

        "golang.org/x/net/netutil"
)

const Port = 8080

func main() {
        listener, err := net.Listen("tcp", ":"+strconv.Itoa(Port))
        if err != nil {
                log.Fatal(err)
        }
        http.HandleFunc("/", HandleTopPage)
        limitListener := netutil.LimitListener(listener, 1000)
        err = http.Serve(limitListener, http.DefaultServeMux)
        if err != nil {
                log.Fatal(err)
        }
}

func HandleTopPage(w http.ResponseWriter, r *http.Request) {
        for i := 0; i < 100000000; i++ {
        }
        results := []string{"大吉", "吉", "小吉", "凶", "大凶"}
        fmt.Fprintf(w, "<html><body><p>%s</p></body></html>\n",
                results[rand.Intn(len(results))])
}

HandleTopPageの中にあるfor文は、本記事における説明をしやすくするため、リクエストのたびにCPUに疑似的な負荷を与える処理です。あまり気にする必要はありません。

アプリを動かす

fortuneをインストールするには、次のようなコマンドを発行します。

$ go get github.com/satoru-takeuchi/engineerhub-article-sample/cmd/fortune

この後に次のコマンドを発行するとfortuneが起動し、このシステムの8080番ポートにアクセスするとWebアプリにアクセスできるようになります。

$ ./fortune &
$ ps
  PID TTY          TIME CMD
...
28600 pts/0    00:00:00 fortune
...
$

Webアプリにアクセスする

このWebアプリには、http://fortuneを動かしているマシンのIPアドレス:8080というURL経由でアクセスできます。ここではWebブラウザからではなく、curlコマンドを使ってWebアプリに数回アクセスした結果を次に示します。

$ curl http://192.168.0.32:8080    # fortuneを動かしているマシンのIPアドレスが192.168.0.32
<html><body><p></p></body></html>
$ curl http://192.168.0.32:8080
<html><body><p>小吉</p></body></html>
$ curl http://192.168.0.32:8080
<html><body><p>小吉</p></body></html>
$

別の端末上でtopコマンドを実行しながら同じことをしてみると、topの上位にはfortuneプログラムはほとんど、あるいは全く出てきません。なぜなら、1つのリクエストを処理する程度では、CPUには全く負荷がかからないからです。

レスポンス時間も見てみましょう。

$ curl -sS http://192.168.0.32:8080 -o /dev/null -w "%{time_total}\n"
0.028721

出力の単位は秒なので、上記リクエストのレスポンス時間はおよそ30ミリ秒だと分かりました。この後、10回程度実行しましたが、どれも同じような数値でした。つまり、何も負荷がかかっていない状態では、Webアプリのレイテンシは30ミリ秒程度ということです。

Webアプリに負荷をかける

エンジニアHubに会員登録すると
続きをお読みいただけます(無料)。
登録のメリット
  • すべての過去記事を読める
  • 過去のウェビナー動画を
    視聴できる
  • 企業やエージェントから
    スカウトが届く