mkr plugin install 時の403 API rate limit exceededエラーを回避する方法
この記事はMackerel Advent Calendar 2021の14日目の記事です。
最近、さくらのクラウドの一部のサービスの監視にmackerelを導入し始めました! そして今年もいくつかのmackerel pluginを作成しています。
ログをメトリクスにするプラグイン
インターフェイスごとのエラーや送受信したパケットを可視化するプラグイン
100%上限のCPU使用率グラフ、ロードアベレージをコア数で割ったメトリックを作成するプラグイン
そのほか、mackerel-plugin-axslogにも新しい機能が増えています。
この記事は既存のサーバにこれらのmackerel pluginをansibleで導入していった際に出たエラーと回避策のお話です。
mkr plugin install時のrate limitエラー
次のようにAnsibleのplaybookを書き、mackerelプラグインのインストールを実行していたところ、
- name: install mkr plugins become: yes shell: "mkr plugin install --upgrade {{ item }}" with_items: - kazeburo/mackerel-plugin-linux-memory - kazeburo/mackerel-plugin-axslog - kazeburo/mackerel-plugin-linux-netdev - kazeburo/mackerel-plugin-linux-usage - kazeburo/mackerel-plugin-log-counter
GitHubのAPIのrate limitに引っかかりました
failed: [192.168.0.1] (item=kazeburo/mackerel-plugin-axslog) => {"changed": true, "cmd": "mkr plugin install --upgrade kazeburo/mackerel-plugin-axslog", "delta": "0:00:00.071010", "end": "2021-11-01 17:12:53.402650", "item": "kazeburo/mackerel-plugin-axslog", "msg": "non-zero return code", "rc": 1, "start": "2021-11-01 17:12:53.331640", "stderr": "\u001b[0;31m error\u001b[0m Failed to install plugin while making a download URL: GET https://api.github.com/repos/kazeburo/mackerel-plugin-maxcpu/releases/latest: 403 API rate limit exceeded for 203.0.113.147. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.) [rate reset in 12m04s]", "stderr_lines": ["\u001b[0;31m error\u001b[0m Failed to install plugin while making a download URL: GET https://api.github.com/repos/kazeburo/mackerel-plugin-maxcpu/releases/latest: 403 API rate limit exceeded for 203.0.113.147. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.) [rate reset in 12m04s]"], "stdout": "", "stdout_lines": []}
これはmkrがプラグインの’最新のバージョンを調べるために、GitHubのAPIにアクセスしているところで発生します。
回避策はmackerelのマニュアルで紹介されています。
mkr plugin installはGithubから最新のリリースを探すためにGithub APIを利用します。そのため、Githubの設定画面から取得できるアクセストークンを指定しておかなければ、Github APIのRate Limitの制限にあたり、インストールが失敗する可能性があります。
もうひとつは明示的にバージョンを指定する方法です
mkr plugin installではGithub Releasesのリリースタグが明示的に指定された場合、GithubのAPIにアクセスしないため、Rate Limitの制限にかかることはありません。そのため、サーバプロビジョニングツールから利用するときは、リリースタグを明示的に指定することをおすすめします。
今回、セットアップしている環境ではすぐに用意できるアクセストークンはなかったため、前者の方法は取りにくく、後者を行うことになります。
- name: install mkr plugins become: yes shell: "mkr plugin install --upgrade {{ item }}" with_items: - kazeburo/mackerel-plugin-linux-memory@v0.0.6 - kazeburo/mackerel-plugin-axslog@v0.3.1
ただ、pluginを作成した直後はバージョンアップ回数が増えるため、都度バージョンをあげるのは面倒です。(今回は自前のプラグインのため最新バージョンでも問題がないことがわかっている前提がありますが、安定した運用のためにはバージョン固定がベストプラクティスです)
そこで、releaseTagを取得してキャッシュするサーバを書きました
releaseTag キャッシュサーバ
readmeもないですが作成しました。
実行すると次のレスポンスが得られます。
% curl -sSf 127.0.0.1:8080/kazeburo/chocon|jq . { "release": "v0.12.5", "has_error": false, "erorr": "", "assets": [ { "name": "chocon_0.12.5_checksums.txt", "download_url": "https://github.com/kazeburo/chocon/releases/download/v0.12.5/chocon_0.12.5_checksums.txt" }, { "name": "chocon_darwin_amd64.zip", "download_url": "https://github.com/kazeburo/chocon/releases/download/v0.12.5/chocon_darwin_amd64.zip" }, { "name": "chocon_linux_amd64.zip", "download_url": "https://github.com/kazeburo/chocon/releases/download/v0.12.5/chocon_linux_amd64.zip" }, { "name": "chocon_linux_arm.zip", "download_url": "https://github.com/kazeburo/chocon/releases/download/v0.12.5/chocon_linux_arm.zip" }, { "name": "chocon_linux_arm64.zip", "download_url": "https://github.com/kazeburo/chocon/releases/download/v0.12.5/chocon_linux_arm64.zip" } ] }
結果は5分キャッシュされるように作り、mkrのソースコードを参考にGITHUB_TOKENの環境変数がセットされていれば使うようにしています。
サーバは趣味全開で作り、フレームワークは github.com/gofiber/fiber、キャッシュの有効活用のために golang.org/x/sync/singleflight を使ってます。
これをさくらのクラウドの Hacobune にデプロイし、Ansibleのplaybookを
- name: install latest mkr plugins become: yes shell: "mkr plugin install --upgrade {{ item }}@$(curl -fs 'https://example.com/{{ item }}?plain')" with_items: - kazeburo/mackerel-plugin-linux-memory - kazeburo/mackerel-plugin-linux-netdev - kazeburo/mackerel-plugin-linux-usage - kazeburo/mackerel-plugin-log-counter - kazeburo/mackerel-plugin-maxcpu
のようにしました。
これでRate Limitエラーは避けられ、常に新しいバージョンをいれることができました。
まとめ
今後も必要なpluginを揃え、メトリクスを充実させながら、mackerelを使ってクラウドの安定運用やっていきます。
HAProxyにコントリビュートした話
さくらインターネット Advent Calendar 2021 10日目の記事です。
日頃、運用や新機能の開発を行っているさくらのクラウドの「エンハンスドロードバランサ」はL7のロードバランサのソフトウェアとしてHAProxyを使っています。
こちらの記事でシステム構成について紹介しております。
また、本blogにてlibslzによるHTTPレスポンスのGZIP圧縮の紹介もしています。
この記事はHAProxyの運用で問題を発見し解決した話と、HAProxyにissue報告した話になります。
発見から問題特定まで
とある作業後、エンハンスドロードバランサのL7ロードバランサであるHAProxyのうちの一つのプロセスが異常にCPUを使っているのを発見しました。
このHAProxyのプロセスはCPU 1コアを使い切っている状態になっておりました。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 28007 haproxy 20 0 570272 11612 924 S 99.0 0.2 0:31.57 /usr/local/sbin/haproxy
エンハンスドロードバランサでは、1つのロードバランサ設定ごとにhaproxyのプロセスが割り当てられます。他を確認したところ、特定の1つの設定でのみ起きている問題で、他のお客様の設定では発生しておりませんでした。
負荷の原因としてロードバランサへの攻撃や定期的な突発アクセスを想定し、アクセスログの調査をしましたが、そのような形跡はありませんでした。
次にtopコマンドで観察していると、CPUがbusyとなる状態は定期的に発生し、10秒程度継続して元にもどるように見えたので、同じ間隔で設定されている実サーバへのヘルスチェック時になにか起きていそうだと当たりをつけ、今度は strace でそのタイミングを捉えてみると、次のようなトレースが取得できました
[pid 28015] connect(31, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("198.51.100.123")}, 16) = -1 EINPROGRESS (Operation now in progress) [pid 28015] epoll_ctl(30, EPOLL_CTL_ADD, 31, {EPOLLIN|EPOLLOUT|EPOLLRDHUP, {u32=31, u64=31}}) = 0 [pid 28015] clock_gettime(CLOCK_THREAD_CPUTIME_ID, {89, 485590665}) = 0 [pid 28015] epoll_wait(30, [{EPOLLOUT, {u32=31, u64=31}}], 200, 2556) = 1 [pid 28015] clock_gettime(CLOCK_THREAD_CPUTIME_ID, {89, 485637316}) = 0 [pid 28015] recvfrom(31, 0x7f14740342d0, 16320, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) [pid 28015] sendto(31, "HEAD /health-check.html HTTP/1."..., 93, MSG_DONTWAIT|MSG_NOSIGNAL, NULL, 0) = 93 [pid 28015] epoll_ctl(30, EPOLL_CTL_MOD, 31, {EPOLLIN|EPOLLRDHUP, {u32=31, u64=31}}) = 0 [pid 28015] clock_gettime(CLOCK_THREAD_CPUTIME_ID, {89, 485774729}) = 0 [pid 28015] epoll_wait(30, [{EPOLLIN|EPOLLRDHUP, {u32=31, u64=31}}], 200, 2554) = 1 [pid 28015] clock_gettime(CLOCK_THREAD_CPUTIME_ID, {89, 485839265}) = 0 [pid 28015] recvfrom(31, "<!DOCTYPE HTML PUBLIC \"-//IETF//"..., 16320, 0, NULL, NULL) = 565 #ここで止まる
この結果から、ヘルスチェックのため実サーバである 198.51.100.123 のポート443に対してアクセスし、このタイミングでCPU負荷が上がる状態に陥っていることがわかり、また、(おそらく)SSL有効なポートに対して生TCPでアクセス(お客様の設定の間違いのようですが)してしまった結果、HTTPのレスポンスヘッダがなく、いきなりエラーを知らせるHTMLが返ってきていることもこのトレースからわかりました。
HTTPSのポートにHTTP通信を行った際に、HTTPレスポンスヘッダがなくコンテンツが返るのはイレギュラーのようで、手元にあるいくつかのWebサーバ調べましたが、Nginxなど大体のWebサーバはHTTPヘッダを返しています。
検証とパッチ作成
このHTTPヘッダを返さない実サーバが問題を引き起こしているのではないかということで、Go言語で同じ動きをするサーバを作成し、手元で動かして検証しました。
動かしたGoのサーバはこれです。Goは雑(すぐ)にかけていいですね
問題が再現できるhaproxyの最小限の設定を作成し
global log stdout format raw local0 defaults timeout connect 5000ms timeout client 50000ms timeout server 50000ms frontend 113300002882-163.43.241.14:80 mode http bind 0.0.0.0:8080 default_backend 113300002882-backend-default backend 113300002882-backend-default mode http option httpchk GET /live server 127.0.0.1:12345 127.0.0.1:12345 check inter 10s
ヘルスチェックのコードにてprintf debugで問題箇所の特定を行い、以下のpatchを作成しました。
1日がかりで結構時間かけた割には、変更は30文字にも満たないpatchとなりました。
問題の原因としては、通信が切れたにもかかわらず、HTTPヘッダを探すためヘルスチェックのタイムアウトまで次のパケットを読み込もうとしてループしてしまうことで、このpatchで通信が切れていた場合、次のデータをまたずに即時エラーとするようにしています。
このpatchを、エンハンスドロードバランサの開発環境に適用し、busy loopが解消していること、また他の問題のでないことを確認し、順次本番環境へも導入していきました。
勝利の瞬間です
haproxyへのコントリビュート
HAProxyはOSSですから、この問題についてissueをあげてコントリビュートをすることにしました。
登録したissueはこちら
送ったpatchは少し内容が変わりましたが、問題箇所の認識は間違ってなかったようで
取り込まれて、haproxy 2.4.8 に含まれる形でリリースされました。
http://www.haproxy.org/download/2.4/src/CHANGELOG
- BUG/MEDIUM: tcpcheck: Properly catch early HTTP parsing errors
これが今回の修正にあたります。
非常にニッチなものではあり、感想も月並みではありますが、サービスの中で使用しているOSSに対するissue報告とコントリビュートができて良かったです。
OSSなソフトウェアの開発・運用上発見した問題があれば、今後とも積極的にコントリビュートしていきます。
さくらのクラウド DNSアプライアンスで HTTPS RR を試してみた
さくらインターネット Advent Calendar 2021 7日目の記事です。
DNSコンテンツサーバ機能を提供するさくらのクラウドの「DNSアプライアンス」では、2021年9月からHTTPSリソースレコード・SVCBリソースレコードをサポートしています。
こちらの紹介記事になります。
HTTPSリソースレコード(HTTPS RR)とは、DNSでHTTPSサーバに接続する際のALPNなどのパラメータを渡すことができる新しいDNSのレコードタイプで、CNAMEと異なりZone APEXにも設定ができるのが特徴です。また、HTTPSレコードがある場合、初回からHTTPS接続を行うHSTS(HTTP Strict Transport Security)としても利用されます。CDNのcloudflareやiOSのSafariではすでにデフォルトで使われております。
HTTPS RR・SVCB RRについては以下の記事も参考になります
HTTP RRの仕様は近いうちにRFCになるかと思います。
HTTP/3対応のサーバの準備
まず、実験に使うサーバを用意します。
さくらのクラウドの仮想サーバを1台作成します。今回はOSにRocky Linux 8.5を使いました。
サーバ起動後、必要なポートを開けます
$ sudo firewall-cmd --permanent --add-service=http $ sudo firewall-cmd --permanent --add-service=https $ sudo firewall-cmd --permanent --add-port=443/udp $ sudo firewall-cmd --reload
今回の実験ではWebサーバとしてHTTP/3が利用できる Caddy というサーバを使います。
CaddyはGoで書かれており、HTTPS周りの設定を自動で行ったり、Let's EncryptやZeroSSLから証明書を取得してインストールしてくれるなど便利なやつです。
Caddyのインストールはこちらにドキュメントがあります。今回はdnf/rpm系OSなので、
$ sudo dnf install 'dnf-command(copr)' $ sudo dnf copr enable @caddy/caddy $ sudo dnf install caddy $ sudo systemctl start caddy $ sudo systemctl enable caddy
この手順でインストールと起動ができました。
Caddyのデフォルトの設定は /etc/caddy/Caddyfile
にあり、コメントを除くと
:80 { # Set this path to your site's directory. root * /usr/share/caddy # Enable the static file server. file_server }
のようになってました。癖のある設定ファイルですが、 /usr/share/caddy
をドキュメントルートして、静的なファイル配信を行うサーバが port 80 で起動します。
ブラウザからIPアドレスでアクセスするとやや斜めに傾いたデフォルトのページがみれました。
これでCaddyのセットアップは一旦OKです。
DNSレコードの登録
まずは、通常のAレコードからさくらのクラウドのコントロールパネルから追加します。
次に、HTTPSレコードも登録
HTTP RRでは、Svc Priorityに 0 を入れるとエイリアスモードとなります。Zone APEXに指定する際などに使います。今回はエイリアスではないので適当な数値 10
を入れています。
Target Nameはホスト名になりますが、.
(ドット) を入れることで同名のAレコードを参照するよう指定できます。
Svc ParamsにサーバにHTTPS接続する際のパラメータを入力します。
alpn=h3,h3-29,h2 ipv4hint=198.51.100.133
CaddyはHTTP/3(h3, h3-29)、HTTP2に対応しているのでALPNをこのように指定しました。ipv4hintのIPはAレコードのIPアドレスと同じです。
登録ができたら、Google Public DNSを使って確認してみます。
{ "Status": 0, "TC": false, "RD": true, "RA": true, "AD": false, "CD": false, "Question": [ { "name": "caddy.nomadscafe.tokyo.", "type": 65 } ], "Answer": [ { "name": "caddy.nomadscafe.tokyo.", "type": 65, "TTL": 30, "data": "10 . alpn=h3,h3-29,h2 ipv4hint=198.51.100.133" } ], "Comment": "Response from ." }
Caddyの設定
デフォルトではポート80をlistenしているだけなので、HTTPSが利用できるよう設定を変更します。
{ servers :443 { protocol { experimental_http3 } } } caddy.nomadscafe.tokyo { log root * /usr/share/caddy file_server }
これでサーバを再起動すると、HTTP/3を有効にして caddy.nomadscafe.tokyo の証明書取得も行ってくれました。
ブラウザからのHTTPSのアクセスも問題なくできました。Firefoxの開発ツールでHTTP/3で通信していることも確認できました。
Firefoxでは、HTTPS RRの使用が、DNS over HTTPS (DoH)を使用したときに限られています。about:networking
とURL入力欄に打ち込むとDNS照会ツールが使えるので、これを使って確認します。
まず、DoHを使用しない場合
HTTP RRは空っぽです。次に DoHを有効にした場合 (cloudflareを使いました)
HTTP RRの欄にHTTPSレコードに入力したものが表示されました。
Aレコードを消して HTTPS レコードを試す
このままでは HTTPS レコードを使ってアクセスしているのか分かりにくいので、Aレコードを消してみることにしました。
svc.nomadscafe.tokyo として新しいAレコードを置き、caddy.nomadscafe.tokyo の Target Nameをそちらに向け、caddy.nomadscafe.tokyo の Aレコードを削除しました。
IPアドレスがUNKOWNになりましたが、HTTPS レコードは表示されます。DoHの有効のFirefoxブラウザでのアクセスもできました。
ただし、リロードを繰り返すとタイミングによって名前解決に失敗したエラーがでることがあります。これは about:config
で network.dns.force_waiting_https_rr
を true
にすることで出なくなりますが、Aレコードがないのがおそらく異常なので、この設定を常用する必要はありません。
macOS CatalinaのSafariはHTTPS RRに対応していないのでアクセスができません。
macOS Big SurやiOS 15のSafariでは特に設定なくアクセスできました。
Chrome(96.0.4664.93)は対応していないのかDoHを有効にしてもアクセスできませんでした。
まとめ
ということで、HTTPSリソースレコードを使って、実験をしてみました。現状メリットが大きいものではないですが、今後使われるようになっていくのでしょう。お試しあれ~
PerlなプロジェクトをGithub Actionsでコンテナ化 (ARM64対応)
この記事は Perl Advent Calendar 2021 の6日目の記事です。
業務では日頃はGo、Perl、PHPを使ってクラウドのバックエンドを作り、JavaScript/TypeScriptでフロントエンドを作っています
この記事は昔からherokuで動かしていたサービスをコンテナ化してGraviton2なEC2インスタンス上でk3sでシングルサーバクラスタを作成して動かしているよという紹介です。
対象は以下の2つ
それぞれのGitHubのrepositoryはこちら
このサービスで使うDockerコンテナはGitHub Actionsで作成し、GitHubのContainer Registoryにあげています。
サーバがGraviton2なので、ARM64に対応したコンテナが必要となりますが、それもGitHub Actionsでビルドしています。
Dockerfile の用意
Perlの公式コンテナイメージは多くののプラットフォームをサポートし、ARM64にも対応しています。
なのでこれを元にするDockerfileを作成しました。
tanzakではこんな感じ
FROM perl:5.32 RUN mkdir -p /opt/app RUN cpanm -n Carton COPY ./cpanfile /opt/app/cpanfile COPY ./cpanfile.snapshot /opt/app/cpanfile.snapshot WORKDIR /opt/app RUN carton install --deployment EXPOSE 5000 COPY . /opt/app CMD carton exec -- plackup -s Starlet --max-workers=3 --port 5000 -a app.psgi
workflowファイル
そしてDockerビルドするGitHub Actionsのworkflowファイルはこんな感じです
name: Build and Publish Docker on: push: branches: - master jobs: build_and_push: runs-on: ubuntu-latest env: IMAGE_NAME: tanzak steps: - name: checkout uses: actions/checkout@v2 - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - name: Login to GitHub Container Registry uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.CR_PAT }} - name: Get short SHA id: slug run: echo "::set-output name=sha8::$(echo ${{ github.sha }} | cut -c1-8)" - name: Build and push uses: docker/build-push-action@v2 with: context: . push: true platforms: linux/amd64,linux/arm64 tags: | ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ steps.slug.outputs.sha8 }}
マルチアーキテクチャのビルドをするために、 docker/setup-qemu-action および docker/setup-buildx-action を利用し、docker/build-push-action で platformsとして linux/amd64,linux/arm64 を指定しています。
これでビルドはできました。
GitHub Container Registoryでも、amd64/arm64の2つのアーキテクチャでイメージが上がっているのが確認できます。
https://github.com/kazeburo/tanzak/pkgs/container/tanzak
はげしく便利ではあるのですが、すごーく時間がかかります
まとめ
PerlなプロジェクトでもGitHub Actionsを使いARM64のDockerイメージがビルドできました。これで昔々から動かしているサービスでもGraviton2の恩恵を受けてお安く運用できてます。
Graviton2を採用したAWSのEC2はx86系と比較して20%安くなっているとされています。各種マネージドサービスもそろってきました。開発環境やテスト環境がx86の場合の課題もありますが、うまく使っていけるとよさそうです。
sacloud/autoscaler とさくらのクラウドのアクティビティグラフで実現するオートスケール
前回の続きになります。
前回は autoscaler のコアサーバを起動し、cliからサーバの台数を増減させる水平スケールを試しました。今回はさくらのクラウドに備わるアクティビティグラフの情報を使い、自動でスケールアウト・インを行うオートスケールを試します。
設定
2022/3/4 追記
autoscaler v0.5.0 からコマンド体系および、設定が変更になっています。 以下のgistに動作確認した設定ファイルを置いています
autoscalerのインストールおよび基本的な設定は前回の記事を参考にしてください。 autoscaler_coreの設定は以下のようにしました。
resources: - type: ELB name: "hscale-elb" selector: names: ["tk-elb"] resources: - type: ServerGroup name: "hscale-group" #サーバのプリフィックスにもなります zone: "tk1b" min_size: 2 max_size: 5 shutdown_force: false plans: - name: smallest size: 2 - name: medium size: 3 - name: largest size: 5 template: # tags: [ "tag1", "tag2" ] description: "hscale-group" interface_driver: "virtio" plan: core: 1 memory: 1 dedicated_cpu: false network_interfaces: - upstream: "shared" # 共有セグメント expose: ports: [80] #エンハンスドLBで使われるport番号 server_group_name: "gr1" #エンハンスドLBで使われるグループ名 disks: - os_type: "almalinux" plan: "ssd" connection: "virtio" size: 20 edit_parameter: disabled: false password: "" disable_pw_auth: true enable_dhcp: false change_partition_uuid: true ssh_keys: - "ssh-ed25519 **********" startup_scripts: - | #!/bin/bash sudo yum install -y nginx sudo systemctl enable nginx echo -e "keepalive_requests 10;\ngzip_proxied any;\ngzip on;\ngzip_http_version 1.0;\ngzip_comp_level 9;\ngzip_types text/html;" > /etc/nginx/conf.d/gzip.conf curl https://ja.wikipedia.org/wiki/%E4%B8%96%E7%95%8C%E9%81%BA%E7%94%A3 > /usr/share/nginx/html/sekai.html sudo systemctl start nginx echo "server name: {{ .Name }}" > /usr/share/nginx/html/index.html echo "OK" > /usr/share/nginx/html/live firewall-cmd --permanent --add-service http firewall-cmd --reload # オートスケーラーの動作設定 autoscaler: cooldown: 540 # ジョブの連続実行を抑止するためのクールダウン期間を秒数で指定。デフォルト: 600(10分)
ほぼ同じですが、nginxに負荷をかけるため大きめのHTMLとgzipの設定を追加しています。
アクティビティグラフの情報取得
アクティビティグラフは、コントールパネルにも表示されています。5分ごとにCPUやトラフィック情報を収集し、表示しています。データはAPIでも取得可能です。
アクティビティグラフのデータからオートスケールを簡単に実現できるよう、名前が指定したprefixのサーバ群のCPU使用率を取得し、コア数でCPU_TIMEを割り、最大・最小・平均のCPU使用率を出力するコマンドを新たに作りました。
実行例すると以下のように表示されます。さくらのクラウドのAPIを呼び出すためのTOKENなどは --env-from
で指定したファイルから読み出せるようにしてあります。autoscalerの設定時に /etc/autoscaler/core.config
にTOKENを書いているのでそれを利用します。
$ /usr/local/bin/sacloud-cpu-usage --env-from /etc/autoscaler/core.config --zone tk1b --prefix hscale-group --time 2 2021/09/24 17:11:15 hscale-group-001 cores:1 cpu:0.036667 time:2021-09-24 17:00:00 +0900 JST 2021/09/24 17:11:15 hscale-group-001 cores:1 cpu:0.033333 time:2021-09-24 17:05:00 +0900 JST 2021/09/24 17:11:15 hscale-group-001 avg:3.500000 2021/09/24 17:11:15 hscale-group-002 cores:1 cpu:0.033333 time:2021-09-24 17:00:00 +0900 JST 2021/09/24 17:11:15 hscale-group-002 cores:1 cpu:0.033333 time:2021-09-24 17:05:00 +0900 JST 2021/09/24 17:11:15 hscale-group-002 avg:3.333333 {"75pt":3.5000000000000004,"90pt":3.5000000000000004,"95pt":3.5000000000000004,"99pt":3.5000000000000004,\ "avg":3.4166666666500003,"max":3.5000000000000004,"min":3.3333333333,"servers":[{"avg":3.5000000000000004,\ "cores":1,"monitors":[{"cpu_time":0.036666666667,"time":"2021-09-24 17:00:00 +0900 JST"},{"cpu_time":0.033333333333,\ "time":"2021-09-24 17:05:00 +0900 JST"}],"name":"hscale-group-001"},{"avg":3.3333333333,"cores":1,"monitors":\ [{"cpu_time":0.033333333333"time":"2021-09-24 17:00:00 +0900 JST"},{"cpu_time":0.033333333333,"time":\ "2021-09-24 17:05:00 +0900 JST"}],"name":"hscale-group-002"}]}
標準の出力はJSON形式でされますが、—query
オプションで jq のシンタックスで表示するデータを絞ることができます。
$ /usr/local/bin/sacloud-cpu-usage --env-from /etc/autoscaler/core.config --zone tk1b --prefix hscale-group --time 1 --query '.avg|round' 2021/09/24 17:12:52 hscale-group-001 cores:1 cpu:0.033333 time:2021-09-24 17:10:00 +0900 JST 2021/09/24 17:12:52 hscale-group-001 avg:3.333333 2021/09/24 17:12:53 hscale-group-002 cores:1 cpu:0.033333 time:2021-09-24 17:10:00 +0900 JST 2021/09/24 17:12:53 hscale-group-002 avg:3.333333 3
testコマンドでの評価は、floatを扱えないので、jqの round
関数で四捨五入してます。このqueryの処理にはgojqを使ってます。ライブラリ的にも使えて最高便利です!
オートスケールのcronの設置から最初のサーバの起動
sacloud-cpu-usage を使ったshell scriptを書きます
#!/bin/bash set -e cpu_usage=$(/usr/local/bin/sacloud-cpu-usage --env-from /etc/autoscaler/core.config --zone tk1b --prefix hscale-group --time 1 --query '.avg|round') if [ $cpu_usage -gt 50 ]; then echo "Scale up" /usr/local/sbin/autoscaler inputs direct up --dest unix:/var/run/autoscaler/autoscaler.sock --resource-name hscale-group elif [ $cpu_usage -lt 20 -a $cpu_usage -gt 1]; then echo "Scale down" /usr/local/sbin/autoscaler inputs direct down --dest unix:/var/run/autoscaler/autoscaler.sock --resource-name hscale-group else echo "Keep" /usr/local/sbin/autoscaler inputs direct up --dest unix:/var/run/autoscaler/autoscaler.sock --desired-state-name smallest --resource-name hscale-group fi
sacloud-cpu-usageとgojqのおかげですごく簡単になりました。
そしてcronで起動します。
SHELL=/bin/bash */2 * * * * bash /etc/autoscaler/autoscale.sh |& logger -t autoscale
コマンドの出力を syslogに送るため、|& logger
をコマンドの後ろに付けています。SHELL
にてbashを指定しているのは、autoscalerを実行しているサーバがubuntuなのでデフォルトシェルがdashとなり、そのままでは |&
が利用できないからです。
初回起動時、サーバなければ、最後の else
句にあるコマンドからサーバが作られます。
Sep 27 09:20:30 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:20:30+09:00 level=info message="autoscaler core started" address=/var/run/autoscale /autoscaler.sock Sep 27 09:22:01 bastion1 autoscale: Keep Sep 27 09:22:01 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:22:01+09:00 level=info request=Up message="request received" Sep 27 09:22:01 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:22:01+09:00 level=info request=Up source=default resource=hscale-group status=JOB_ACCEPTED Sep 27 09:22:01 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:22:01+09:00 level=info request=Up source=default resource=hscale-group status=JOB_RUNNING Sep 27 09:22:01 bastion1 autoscale: status: JOB_ACCEPTED, job-id: hscale-group Sep 27 09:22:02 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:22:02+09:00 level=info request=Up source=default resource=hscale-group type=ServerGroupInstance zone=tk1b id="(known after handle)" name=hscale-group-001 step=Handle handler=server-horizontal-scaler status=ACCEPTED Sep 27 09:22:02 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:22:02+09:00 level=info request=Up source=default resource=hscale-group type=ServerGroupInstance zone=tk1b id="(known after handle)" name=hscale-group-001 step=Handle handler=server-horizontal-scaler status=RUNNING Sep 27 09:22:02 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:22:02+09:00 level=info request=Up source=default resource=hscale-group type=ServerGroupInstance zone=tk1b id="(known after handle)" name=hscale-group-001 step=Handle handler=server-horizontal-scaler status=RUNNING log=creating... Sep 27 09:22:04 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:22:04+09:00 level=info request=Up source=default resource=hscale-group type=ServerGroupInstance zone=tk1b id="(known after handle)" name=hscale-group-001 step=Handle handler=server-horizontal-scaler status=RUNNING log="created: {ID:113301705264, Name:hscale-group-001}" Sep 27 09:22:04 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:22:04+09:00 level=info request=Up source=default resource=hscale-group type=ServerGroupInstance zone=tk1b id="(known after handle)" name=hscale-group-001 step=Handle handler=server-horizontal-scaler status=RUNNING log="creating disk[0]..." Sep 27 09:23:12 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:23:12+09:00 level=info request=Up source=default resource=hscale-group type=ServerGroupInstance zone=tk1b id="(known after handle)" name=hscale-group-001 step=Handle handler=server-horizontal-scaler status=RUNNING log="created disk[0]: {ID:113301705268, Name:hscale-group-001-disk001, ServerID:113301705264}" Sep 27 09:23:12 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:23:12+09:00 level=info request=Up source=default resource=hscale-group type=ServerGroupInstance zone=tk1b id="(known after handle)" name=hscale-group-001 step=Handle handler=server-horizontal-scaler status=RUNNING log=starting... ....
サーバが作られているところ
2台起動後、負荷がかかるまでは、elif [ $cpu_usage -lt 20 -a $cpu_usage -gt 1]; then
の条件にマッチするためScale Downが実行されます。ただし、min_sizeが2台となっているため、これ以上サーバが削除されたりはしていません。
Sep 27 09:38:01 bastion1 autoscale: 2021/09/27 09:38:01 hscale-group-001 cores:1 cpu:0.030000 time:2021-09-27 09:35:00 +0900 JST Sep 27 09:38:01 bastion1 autoscale: 2021/09/27 09:38:01 hscale-group-001 avg:3.000000 Sep 27 09:38:02 bastion1 autoscale: 2021/09/27 09:38:02 hscale-group-002 cores:1 cpu:0.033333 time:2021-09-27 09:35:00 +0900 JST Sep 27 09:38:02 bastion1 autoscale: 2021/09/27 09:38:02 hscale-group-002 avg:3.333333 Sep 27 09:38:02 bastion1 autoscale: Scale down Sep 27 09:38:02 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:38:02+09:00 level=info request=Down message="request received" Sep 27 09:38:02 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:38:02+09:00 level=info request=Down source=default resource=hscale-group status=JOB_ACCEPTED Sep 27 09:38:02 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:38:02+09:00 level=info request=Down source=default resource=hscale-group status=JOB_RUNNING Sep 27 09:38:02 bastion1 autoscale: status: JOB_ACCEPTED, job-id: hscale-group Sep 27 09:38:03 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:38:03+09:00 level=info request=Down source=default resource=hscale-group status=JOB_DONE Sep 27 09:40:02 bastion1 autoscale: 2021/09/27 09:40:02 hscale-group-001 cores:1 cpu:0.030000 time:2021-09-27 09:35:00 +0900 JST Sep 27 09:40:02 bastion1 autoscale: 2021/09/27 09:40:02 hscale-group-001 avg:3.000000 Sep 27 09:40:03 bastion1 autoscale: 2021/09/27 09:40:03 hscale-group-002 cores:1 cpu:0.033333 time:2021-09-27 09:35:00 +0900 JST Sep 27 09:40:03 bastion1 autoscale: 2021/09/27 09:40:03 hscale-group-002 avg:3.333333 Sep 27 09:40:03 bastion1 autoscale: Scale down Sep 27 09:40:03 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:40:03+09:00 level=info request=Down message="request received" Sep 27 09:40:03 bastion1 autoscaler[691521]: timestamp=2021-09-27T09:40:03+09:00 level=info request=Down source=default resource=hscale-group status=JOB_IGNORED message="job is in an unacceptable state" Sep 27 09:40:03 bastion1 autoscale: status: JOB_DONE, job-id: hscale-group, message: job is in an unacceptable state
スケールアップの確認
別のサーバから ApacheBench でアクセス負荷をかけることで、オートスケールされるかを検証してみます。
ApacheBench の実行
$ watch "ab -k -H 'Accept-Encoding: deflate, gzip, br' -c 3 -t 600 http://tk-elb.kazeburo.work/sekai.html"
アクティビティグラフで負荷が上がってきているのが確認できます。
そしてcronからもCPU負荷が上がっているのが検知され、オートスケールが開始されます。
Sep 27 10:00:01 bastion1 autoscale: 2021/09/27 10:00:01 hscale-group-001 cores:1 cpu:0.606667 time:2021-09-27 09:55:00 +0900 JST Sep 27 10:00:01 bastion1 autoscale: 2021/09/27 10:00:01 hscale-group-001 avg:60.666667 Sep 27 10:00:02 bastion1 autoscale: 2021/09/27 10:00:02 hscale-group-002 cores:1 cpu:0.606667 time:2021-09-27 09:55:00 +0900 JST Sep 27 10:00:02 bastion1 autoscale: 2021/09/27 10:00:02 hscale-group-002 avg:60.666667 Sep 27 10:00:02 bastion1 autoscale: Scale up Sep 27 10:00:02 bastion1 autoscaler[691943]: timestamp=2021-09-27T10:00:02+09:00 level=info request=Up message="request received" Sep 27 10:00:02 bastion1 autoscaler[691943]: timestamp=2021-09-27T10:00:02+09:00 level=info request=Up source=default resource=hscale-group status=JOB_ACCEPTED Sep 27 10:00:02 bastion1 autoscaler[691943]: timestamp=2021-09-27T10:00:02+09:00 level=info request=Up source=default resource=hscale-group status=JOB_RUNNING Sep 27 10:00:02 bastion1 autoscale: status: JOB_ACCEPTED, job-id: hscale-group Sep 27 10:00:04 bastion1 autoscaler[691943]: timestamp=2021-09-27T10:00:04+09:00 level=info request=Up source=default resource=hscale-group type=ServerGroupInstance zone=tk1b id="(known after handle)" name=hscale-group-003 step=Handle handler=server-horizontal-scaler status=ACCEPTED Sep 27 10:00:04 bastion1 autoscaler[691943]: timestamp=2021-09-27T10:00:04+09:00 level=info request=Up source=default resource=hscale-group type=ServerGroupInstance zone=tk1b id="(known after handle)" name=hscale-group-003 step=Handle handler=server-horizontal-scaler status=RUNNING Sep 27 10:00:04 bastion1 autoscaler[691943]: timestamp=2021-09-27T10:00:04+09:00 level=info request=Up source=default resource=hscale-group type=ServerGroupInstance zone=tk1b id="(known after handle)" name=hscale-group-003 step=Handle handler=server-horizontal-scaler status=RUNNING log=creating...
ApacheBenchを止めると、CPU負荷が納まりScale Downされます。
まとめ
sacloud/autoscaler とアクティビティグラフのデータを用いたオートスケールが実現できました。アクティビティグラフを利用することで、監視ツールなどを別途用意する必要がなくなり、オートスケールを楽に導入することができそうです。
ISUCON 11本選に「チーム中目黒乗り過ごし」で参加して、結果はFailでした
結果はこちら
ISUCON11本選にhanabokuro氏、mtokioka氏と参加して、結果はFailで失格となりました。スコア的には5位ぐらいかなと
去年も本選Failなので、2年連続本選でスコア残せませんでした。難しい。
なかなか厳しい問題でしたが、ベンチマークの実行やサーバ環境は快適で、集中して取り組むことはできました。出題の皆様、運営の皆様ありがとうございます。
やったこと
チーム紹介スライドつくりました。おじさんむけ旅雑誌ぽくしてみた
スコアの履歴はこのようになりました、
スコアが伸びない時間がながく、Failも多くありました。
Repositoryはこちらで公開しております
PRつくってマージするスタイルでやっておりましたが、最終的にマージしたPRが38個、マージしてないPRが3個となりました。 うちRevertのPRが7個です。ベンチマークの不整合チェックに引っ掛かり、ブランチで作業し、戻したものもたくさんあります。
最終的にサーバ3台
として利用しました。MySQLの負荷がどうしても下がらず、レプリケーションを行ったのですが、利用できる箇所は本当に少なく、不安定になってしまい、戦略ミスでした。
そのほか、zipを作成ところにhard link(os.Link)を利用し、圧縮率を -1
にしたり、singleflightおよびsync.WaitGroupを使ったSQLの並列実行の制御、不必要だったトランザクションの削除などを行いましたが、やっぱり厳しい感じでした。
機会があれば、出せなかったGetGradesのクエリ改善を試したり、業務ではもう何年も使ってない外部キーが性能にどう影響したのか調査をしてみたい。
最後に
今回、事前にインタビュー記事などに載せていただりしました。ありがとうございます。
学生が強くなり、我々は勝つのが難しいのではないか(これも素晴らしいことです)ということも言われておりましたが、今回はfujiwara組の優勝(おめでとうございます!!)と我々平均年齢45歳オーバーのチームでも本選出場とここまではできたので、定年を乗り過ごしてもう少し頑張っていけるのではいかと思います。
今は燃え尽きておりますが、ISUCONが来年も様々な背景をもつ多くのエンジニアと競いあえる場所として開かれ、また挑戦できるといいなと思ってます。
ISUCON11予選に「チーム中目黒乗り過ごし」で参加し本選出場決まりました
ISUCON11の予選に参加し、なんとか本選出場を決めました!チームは去年と同じ「中目黒乗り過ごし」でメンバーも変わらず、hanabokuro氏、mtokioka氏と参加しました。
チーム中目黒乗り過ごしとして、2年連続で9位で本選出場です。去年の予選記事はこちら。
ちなみに、去年の本選はFailでした。
先日ISUCONのインタビュー記事に載りましたので、こちらもどうぞ
今回の問題
ISUのコンディションを収集し、可視化するサイトということで、IoTや大量のネットワーク機器の監視メトリクスの扱いが課題のネタとなっていたように思いました。
世界観が面白く、ISUが投擲の道具じゃなく、愛される存在になったのだと感慨深いものがありました。
構成とスコア
当初は1台でやっておりましたが、最終的に3台を次のように使いました。
getTrendの生成負荷が高かったので、nginxとgetTrendだけを処理するappを用意し、その他を別のappに寄せました。
スコアはこんな感じで推移。
ベストスコアは 684,624 ですが最終的には 289,148 でした。
17時の時点では、トップでしたのでパブリックスコアボードでしばらく1位でした。ちょっとうれしい。
使ったツール・言語
DBのクエリの分析に、pt-query-digest を使い、アプリケーションの方にはGCPの Cloud Profiler をいれてどこに時間がかかっていそうかを見ていました。
これは 11:20ごろのprofileのスクリーンショットです。
言語は Go になります。わかりにくいところは Perl も参考にさせていただきました!
やったこと
使った repository はここで公開しています
前半
まずtopをみて、DBの負荷が目立ち、INSERTも多くあることがわかったので、MariaDBの設定を行いました。MySQLではなかったのは一瞬悩みましたがそのままにしています。。
sync_binlog = 0 innodb_doublewrite = 0 innodb_flush_log_at_trx_commit = 2 innodb_flush_method = O_DIRECT_NO_FSYNC innodb_adaptive_hash_index = 0
複数台にする際にbind-addressを消した以外はいれたのはこれだけです。
そして、getTrendの生成をbackgroudに回したり、icon画像が不要なところではselectしないようにしたり、必要なindexを張ったりしていたのが序盤です。スコアは2万を超えたあたり。
中盤 (13時すぎ)
Goのapplicationはアクセスログを吐くようになっていて、syslogに負荷がかかり、またログが見づらくなるので、ログのミドルウェアを外しつつdebugモードも解除。
さらに1台ではスコア向上が難しいと考え、複数台構成に変更し、このタイミングでデプロイを簡単にするMakefileを作りました。
この時間帯には、getIsuConditionsFromDB の負荷を下げるため、isu_condition テーブルに condition_level カラムを追加した。
スコアは 10万近くまで上昇。
終盤 (15時すぎ)
捨てるデータの割合であるdropProbabilityを0.5まで下げてスコアが上がるのを確認。
generateIsuGraphResponse でDBからデータを取ってくるところにBETWEENを入れ、不要なデータを取得しないよう対応し、getIsuListのN+1を解消、getTrendにLIMIT 1を追加するなどして、16:50ぐらいには、dropProbabilityをもっと下げて、ベストスコアの68万点がでた。
途中中断
17時ちょっと過ぎたところで、スコアがぐっと下がってしまい 26万点となってしまう。この理由が分かっていない状態で、中断となったのが我々には痛かった。中断の間は、kernelパラメータいれたり、再起動の試験をして過ごす。
再開後、applicationのmax file descriptorに引っかかっているがわかったので、それをいれてみるなどしたが、スコアは戻らず、最終スコアは32万点でおわり、あとはFailしていないことだけを祈る形となった。
まとめ
Failせず、本選出場できるスコアが出せたのは嬉しい。中断前のスコアがなぜ出せなかったのかは、今後の課題としたい。
今回の問題、ぱっと見シンプルなようで、ボトルネックが変わっていくところなど、奥が深いように感じました。加点ルールをより生かせれば、もっと高いスコアが出せるような気がするので、時間があるときに挑戦したいと思います。
また、環境構築、ベンチマーカやポータルも丁寧に作られており、競技に集中できるようなっておりました。おそらく準備は大変だったと思います。運営のみなさまありがとうございました。本選も楽しみです。よろしくお願いします。
今回、ちょうど17時にトップに立てたので、家族にはいいところを見せられたようです。いつも応援ありがとー。本選もFailしないよう頑張ります。