Hateburo: kazeburo hatenablog

SRE / 運用系小姑 / Goを書くPerl Monger

GitHub Actionsからさくらのクラウド オブジェクトストレージへアクセスする

2月1日よりオープンβとなった新しいさくらのクラウド オブジェクトストレージサービスにGitHub Actionsからアクセスしてみるサンプルです。

オブジェクトストレージは2021年3月31日までオープンベータ期間となります。お試しいただいてフィードバックいただけると嬉しいです! オブジェクトストレージ(β)の利用についてはこちらのニュースを確認してください。

cloud-news.sakura.ad.jp

この新しいオブジェクトストレージの特徴の一つとして、Amazon S3と高い互換性を持つAPIの提供があげられ、AWS CLIを通してオブジェクトの操作が可能です。マニュアルはこちらから

manual.sakura.ad.jp

ということで、GitHub ActionsからAWS CLIを使ってオブジェクトストレージにアクセスを試してみました。

アクセストークンについて

オブジェクトストレージ利用開始時に、 アクセスキーID と シークレットアクセスキー が表示されます。こちらのアクセスキーは全てのバケットを操作できてしまうので、パーミッションメニューからバケットごとにアクセス件が設定なアクセスキーを生成するのがお勧めです。

f:id:kazeburo:20210215094509p:plain (画像はマニュアルより)

アクセスキーを作成したら、GitHub Actionsを設定するrepositoryのSecretsに登録します。

f:id:kazeburo:20210215095118p:plain

Workflow の作成

GitHub ActionsでAWSへのアクセスを行う際には、

github.com

configure-aws-credentials を使うことで設定をシンプルにすることができますが、このactionの中で aws sts get-caller-identity 相当のAPI呼び出しを行うため、オブジェクトストレージのアクセスキーではエラーになってしまい、今回は使うことができません。

なので、環境変数でアクセスキーを渡すようにしてワークフローを作ったところ動きました。

name: release
on:
  push:
    branches:
      - main

jobs:
  renew-cert:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: sync to object storage
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: eu-west-1

        run: |
          aws --endpoint-url=https://s3.isk01.sakurastorage.jp s3 --only-show-errors --delete sync public_html/ s3://my-website-data/public_html/

aws コマンドに --endpoint-url=https://s3.isk01.sakurastorage.jp を渡せばオブジェクトストレージにアクセス可能です。

どうぞお試しあれ~

2021/03/03 追記

GitHub Actionsで

<botocore.awsrequest.AWSRequest object at 0x7f22f2d7d630>

のようなエラーが出る際は環境変数 AWS_REGION を指定するか、awsコマンドに --region オプションの追加をするとなおります。

参考

github.com

dehydrated とさくらのクラウドのDNS機能をつかってワイルドカード・マルチドメイン証明書取得

dehydrated はshell scriptでできた letsencrypt/acme のクライアントです。certbotlegoなど様々なツールがある中で、わりと長いこと使ってますが、安定してワイルドカード・マルチドメイン対応の証明書の取得・更新ができています。

github.com

dehydratedとさくらのクラウドDNS機能と先週末つくったクライアント sacloudns を使うことで、ほぼ自動的に証明書の取得と更新が行えるので、その紹介です。

github.com

こちらのツールは、リリースページからのダウンロードもしくはMacなら homebrew でインストールが可能です。

% brew install kazeburo/tap/sacloudns

dehydrated の準備

dehydrated はGitHubからcloneしてきます

$ git clone https://github.com/dehydrated-io/dehydrated.git
$ cd dehydrated

そして設定ファイルを用意します。

まず、 config ファイルを作成

CERTDIR="${BASEDIR}/certs"
ACCOUNTDIR="${BASEDIR}/accounts"
HOOK="${BASEDIR}/hook.sh"
HOOK_CHAIN="no"
CHALLENGETYPE="dns-01"
KEY_ALGO="prime256v1"

configのサンプルは docs/example 以下にあります。KEY_ALGOにprime256v1を指定することで楕円曲線暗号であるECDSAを使った証明書を作成できます。デフォルトはRSAになります

つぎに証明書を作成するドメイン一覧を記した domains.txt を作ります

# スペース区切りで複数のドメインを記せます。ワイルドカードも使えますが行頭にはかけません
example.com *.example.com example.jp *.example.jp > certalias
chocon.me *.chocon.me

この記事で紹介する方法で証明書を作成する場合、こちらのドメインはすべてさくらのクラウドDNS機能で管理されている必要があります。試してはないですが、Let's Encryptではマルチドメイン証明書のSAN(Subject Alternative Name)に100個までFQDNを追加することができるようです。

そして最後に、dns-01 で利用するDNSレコードの作成などを行う hook.sh を用意します。

こちらはAmazon Route 53のクライアント、 cli53 を使う以下コードを参考にして作りました

https://github.com/whereisaaron/dehydrated-route53-hook-script/blob/master/hook.sh

#!/bin/bash
set -e
# This hook script is written based on dehydrated-route53-hook-script
# https://github.com/whereisaaron/dehydrated-route53-hook-script

deploy_challenge() {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"

    local ZONE=$(find_zone "${DOMAIN}")
    
    if [[ -n "$ZONE" ]]; then
        echo "Creating challenge record for ${DOMAIN} in zone ${ZONE}"
        sacloudns radd --wait --zone ${ZONE} --name _acme-challenge.${DOMAIN}. --ttl 60 --type TXT --data ${TOKEN_VALUE}
    else
        echo "Could not find zone for ${DOMAIN}"
        exit 1
    fi
}

clean_challenge() {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"

    local ZONE=$(find_zone "${DOMAIN}")
    
    if [[ -n "$ZONE" ]]; then
        echo "Deleting challenge record for ${DOMAIN} from zone ${ZONE}"
        sacloudns rdelete --zone ${ZONE} --name _acme-challenge.${DOMAIN}. --type TXT --data ${TOKEN_VALUE}
    else
        echo "Could not find zone for ${DOMAIN}"
        exit 1
    fi
}

deploy_cert() {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
    # NOP
}

unchanged_cert() {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
    # NOP
}

function invalid_challenge {
    local DOMAIN="${1}" RESPONSE="${2}"

    local HOSTNAME="$(hostname)"

    (>&2 echo "Failed to issue SSL cert for ${DOMAIN}: ${RESPONSE}")
}

function get_base_name() {
    local HOSTNAME="${1}"

    if [[ "$HOSTNAME" == *"."* ]]; then
      HOSTNAME="${HOSTNAME#*.}"
      echo "$HOSTNAME"
      return 0
    else
      echo ""
      return 1
    fi
}

function find_zone() {
  local DOMAIN="${1}"

  local ZONELIST=$(sacloudns list | jq -r '.DNS[].DNSZone'|xargs echo -n)

  local TESTDOMAIN="${DOMAIN}"

  while [[ -n "$TESTDOMAIN" ]]; do
    for zone in $ZONELIST; do
      if [[ "$zone" == "$TESTDOMAIN" ]]; then
        echo "$zone"
        return 0
      fi
    done
    TESTDOMAIN=$(get_base_name "$TESTDOMAIN")
  done

  return 1
}

#
# This hook is called at the end of a dehydrated command and can be used
# to do some final (cleanup or other) tasks.
#
exit_hook() {
  :
}

HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then
  "$HANDLER" "$@"
fi

さくらのクラウドAPIキーを環境変数を設定

さくらのクラウドのコントロールパネルからAPIキーを作成し、SAKURACLOUD_ACCESS_TOKENSAKURACLOUD_ACCESS_TOKEN_SECRET環境変数に設定します。

export SAKURACLOUD_ACCESS_TOKEN=xxx
export SAKURACLOUD_ACCESS_TOKEN_SECRET=yyy

sacloudns は作業ディレクトリに、.env ファイルを作成し、

SAKURACLOUD_ACCESS_TOKEN=xxx
SAKURACLOUD_ACCESS_TOKEN_SECRET=yyy

のように記すと、コマンド実行時に自動で読み込むこともできます。

dehydrated の実行

準備ができたのでdehydratedを実行

# 初回はアカウントの作成が必要
$ ./dehydrated --register --accept-terms -f config 
$ ./dehydrated -c -f config 

これで、DNSレコードの作成と検証が行われたのち、証明書が certs ディレクトリ以下に作成されます。

% ls -l certs/chocon.me 
cert-1612763291.csr
cert-1612763291.pem
cert.csr -> cert-1612763291.csr
cert.pem -> cert-1612763291.pem
chain-1612763291.pem
chain.pem -> chain-1612763291.pem
fullchain-1612763291.pem
fullchain.pem -> fullchain-1612763291.
privkey-1612763291.pem
privkey.pem -> privkey-1612763291.pem

証明書は nginx や Apache などのWebサーバや各クラウドにアップロードして使うこともできます。

おまけ: さくらのクラウド エンハンスドロードバランサーへの証明書アップロード

取得した証明書をさくらのクラウドのエンハンスドロードバランサーに設定してみます。

エンハンスドロードバランサーとは大規模なHTTP/HTTPSサービスに最適な高性能・高機能なロードバランサアプライアンスです。詳しくはこちら

manual.sakura.ad.jp

こちらも参考になります。

qiita.com

エンハンスドロードバランサーの構築が済んでいるといるとして、以下のようにすると証明書が登録できます。

$ cat template.jq
{
  "ProxyLB": {
    "PrimaryCert": {
      "ServerCertificate": $ServerCertificate,
      "IntermediateCertificate": $IntermediateCertificate,
      "PrivateKey": $PrivateKey
    },
    "AdditionalCerts": []
  }
}
$ jq -n \
-f template.jq 
--rawfile ServerCertificate certs/chocon.me/cert.pem \
--rawfile IntermediateCertificate certs/chocon.me/chain.pem \
--rawfile PrivateKey certs/chocon.me/privkey.pem \
| \
curl -d @- -X PUT \
-H "Content-Type: application/json" \
--user $SAKURACLOUD_ACCESS_TOKEN:$SAKURACLOUD_ACCESS_TOKEN_SECRET \
https://secure.sakura.ad.jp/cloud/zone/is1a/api/cloud/1.1/commonserviceitem/${リソース番号}/proxylb/sslcertificate

jqのテンプレート機能と、rawfileオプションとても便利ですね!

なお、この方法で証明書を登録した場合は、let's encryptでの証明書取得・更新機能は停止しておく必要があります。

エンハンスドロードバランサーのlet's encryptを利用した証明書取得では、1つのロードバランサーあたり、1個のドメインの証明書しか作れませんが、この方法だとそれよりも多くの証明書を登録できるので、たくさんのドメインを利用される場合は便利かもしれません。

最後に、ここまでの方法を一つのシェルスクリプトにまとめるなど自動化しておくのがいいでしょう。

何かのお役に立てれば幸いです。

さくらインターネット株式会社に入社しました

あっという間に1週間経ってしまいましたが、2021年1月18日より、さくらインターネットにてお仕事をしております。

前職中は本当にたくさんの皆様にお世話になりました。ありがとうございます。挨拶もほとんど出ておらず申し訳ありません。

さくらインターネットは20年ほどユーザとして使っており、またmixiやメルカリでもサーバを利用しており、公私共に馴染みのある会社です。

2018年の北海道胆振東部地震の際には対応はもちろん、情報がまとまってない中で直接お話を聞く機会をいただいたり、逐次DMなどで情報をいただくなど大変お世話になったのは強く印象に残っています。そのほか、ISUCON9でも問題の出題を一緒にやらせていただいたり、サイボウズ様、jig.jp様との共催で行われた小学生向けのIoTプログラム教室などにも息子とともに参加させていただきました。このためかさくらインターネットに転職をすることを息子に伝えると、ニンマリと喜んでおりました。

これまでのキャリアではWebサービスのパフォーマンス改善、運用などに携わっていたわけですが、さくらインターネットは少し異なるレイヤーへの挑戦になります。今までの経験を生かしつつ、新しいチャレンジも行い、さくらインターネットのお客様とそのお客様にとってよりよいサービスを提供していけるようやっていきます。

もし、さくらインターネットの利用に関わらずWebサービスのパフォーマンスや運用での悩みなどがありましたら、力になれることがあるかと思いますのでtwitterなどで相談くださいませ。

COVID-19の緊急事態宣言が出ているなか、初日からリモートとなかなか難しい船出になっておりますが、素晴らしい同僚の力を借りつつ、一つ一つやりはじめております。今後ともよろしくお願いします。

GitHub Actionsからさくらクラウドのコンテナレジストリ(Lab)にイメージをpushする

こちらのリポジトリが完成形です。

github.com

ここで紹介しているコンテナレジストリはLabプロダクトになります。「Labプロダクト」では、新機能の動作検証およびフィードバック収集を目的として、さくらのクラウドにおける開発中サービスを実験的に提供しておりますので、ご注意ください。

マニュアルはこちら

manual.sakura.ad.jp

「Labプロダクトとは」も合わせてご確認ください。

manual.sakura.ad.jp

コンテナレジトストリの作成

さくらのクラウドのメニューからコンテナレジトストリを選び、「追加」ボタンを押します。

f:id:kazeburo:20210122172222p:plain

f:id:kazeburo:20210122172400p:plain

公開・非公開は目的に応じて選択してください。

コンテナレジストリができたら、push/pullするためのユーザを作成します。

f:id:kazeburo:20210122172747p:plain

権限も適したものを選びましょう。今回はGitHub Actionsからpushを行うので、「Push & Pull」がよさそうです。

f:id:kazeburo:20210122172836p:plain

このアカウントはコンテナレジストリにのみ使われるアカウントになります。

GitHubリポジトリにシークレット登録

コンテナイメージを作成するリポジトリの Settings -> Repository secrets にて、先ほど作成した id とパスワードを SAKURACR_USER、SAKURACR_PASSWORDとして登録しました。

workflow YAMLの作成

リポジトリ.github/workflow/ 以下にコンテナイメージの作成とコンテナレジストリ(Lab)にpushするためのYAMLファイルを作成します。

さくらのコンテナレジストリ(Lab)はDocker開発元が提供するDocker Hub互換のレジストリサービスであり、プロトコルにも互換性があります。GitHub Container RegistryやAWSのコンテナレジストリにpushする際に利用できる docker/login-actiondocker/build-push-action の Actionを特にカスタマイズすることなく使えます。

github.com

http-dump-requestでは次のようにファイルを作成しました。

name: release
on:
# gitのtagが作られた時
  push:
    tags:
    - "v[0-9]+.[0-9]+.[0-9]+"

jobs:
  build_and_push:
    runs-on: ubuntu-latest
    env:
      IMAGE_NAME: http-dump-request
      REGISTRY: nomadscafe.sakuracr.jp
    steps:
      - name: checkout
        uses: actions/checkout@v2

# tagを入手
      - name: Set tag to environment variable
        id: set-tag
        run: echo ::set-output name=version::${GITHUB_REF#refs/*/}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Login to Sakura Container Registry
        uses: docker/login-action@v1
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.SAKURACR_USER }}
          password: ${{ secrets.SAKURACR_PASSWORD }}

      - name: Build and push
        uses: docker/build-push-action@v2
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.set-tag.outputs.version }}

このファイルをコミットしgit tagを作成すると、狙い通りイメージの作成とさくらのコンテナレジトストリ(Lab)へのpushができました。

2021年1月現在、コンテナレジストリ(Lab)にはイメージの一覧機能がないので、今回つくったコンテナは以下のコマンドでレジストリからpullして実行ができることで動作を確認しました。

$ docker run -p 3000:3000 nomadscafe.sakuracr.jp/http-dump-request

おまけ

http-dump-request について

似たようなモノはたくさんありそうですが、検証の際に使える http request をそのままレスポンスとして返す Go のサーバです。

f:id:kazeburo:20210125153518p:plain

こんな感じで表示できます。上記で紹介しておりますが、 nomadscafe.sakuracr.jp/http-dump-request から docker pullして利用できます。

2020年に買って良かったもの

ほぼ1年間家で仕事をすることになり、自宅の机まわりの環境をこれまで以上に整えてきた1年だったので「#買って良かった2020」というお題で書いてみる。

ヘッドホンとマグカップ

f:id:kazeburo:20201225133336p:plain

奥さんから誕生日にもらったソニーの WH-H910N。色はアッシュグリーンです。ありがとう。

www.sony.jp

ワイヤレスで軽くてつけ心地がよく、ノイズキャンセルもあってとても気に入っています。

夏は冷たい飲み物、冬は暖かい飲み物を飲むために買ったKEYUKAのマグカップ。偶然にもヘッドホンと同じ色味でいい感じです。

www.keyuca.com

また、Apple Musicのサブスクリプションも加入しました。

www.apple.com

仕事しながら聞いてますが、「おしりたんてい」の「ププっとふむっと解決ダンス」だったり鬼滅の刃の曲が最もよくかかっているような気がします。

椅子

家で長く座って作業ができる椅子がない、ということで在宅勤務になってまず購入したのが椅子。

f:id:kazeburo:20201225134524p:plain

オフィスチェアの多くは黒い色ですが、割と大きい黒い物体が家にあると、家の中が暗くなりそうということで、オカムラのシルフィーのホワイトフレーム、座面はベージュを選びました。

届くまでに1ヶ月以上かかり、その間は折り畳み椅子でなんとかなんとか過ごしてましたが、背面フレームのデザインもよく満足。

www.okamura.co.jp

椅子と言えば、ISUCON10個人スポンサーでいただいたアウトドアチェアもよい。

f:id:kazeburo:20201225134738p:plain

子供たちがよく座っています。941さんありがとう。

ちなみに机は場所的な問題で、ニトリの小さめの机

www.nitori-net.jp

キーボード

f:id:kazeburo:20201225141211p:plain

今はBAROCCO MD770 RGB BTを使ってます。真ん中にMagic Trackpadをおくスタイル。左右分離型だいぶ慣れてきましたが、電話をしながら片手で打てないという弱点があります。

archisite.co.jp

www.apple.com

Magic Trackpadをそのままおくと低すぎて使いにくいので、百均で買った耐震マットを二枚重にして下にくっつけてます。

普段はFILCOの左右分離型のウレタンリストレストをキーボードの前において使っています。

www.diatec.co.jp

冬対策

窓の近くに机を置いているので、寒いということで、こちらのパネルヒーターを買ってみた。

足の裏はもちろん、ブランケットを上にかければ簡易こたつになりとても暖かい。奥さんも小さいホットカーペットを買って机の下で使っています。

Apple Watch

Series 6を初購入。

f:id:kazeburo:20201225140105p:plain

Apple Watch Hermèsの赤色のレザーストラップにしました。1時間に1度立ち上がったり、20秒手を洗っているだけで褒めてくれるので自己肯定感がある。運動も気にし。。。

www.apple.com

スタンドはEINBANDさんのAppleWatch木製スタンド。充電器のところはぴったりとハマったりではないので剥がせるタイプの両面テープで固定している。

einbandwatch.com

最近はRaspberry Piを買ったので、homebridgeいれてApple WatchのホームアプリやSiriからLチカして喜んでいます。

homebridge.io

来年は何買うかな~

ISUCON10予選問題で EC2 c6g.2xlarge vs c5.2xlarge をやってみる

少し前にISUCON10予選問題のスコアアップに取り組んでいたので、その成果を使い、EC2 Graviton2 インスタンスIntel CPUのインスタンスでスコアを比較してみます。

ISUCON10予選問題チャレンジをしていた時のスレはこちら

ここではISUCON10予選時のように複数台用意するのではなく、1台のサーバでGoのアプリケーションとMySQLを動作させ、同じサーバ上からベンチマークを実行しています。

準備

初期データを作る際のdockerコンテナでarm64対応していないものがあるため、Intel CPUのインスタンスで作成してscpする方法を取りました。

まず、 c5.2xlargeインスタンスを起動して準備していきます

# 依存のインストール
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common python3-pip git

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# Dockerをいれる
sudo add-apt-repository \
   "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

sudo gpasswd -a $USER docker

# 一度logout & login

# docker-compose を pip でインストール
sudo pip3 install -U docker-compose

# Goのインストール
curl -fsSL https://golang.org/dl/go1.15.6.linux-$(dpkg --print-architecture).tar.gz |  sudo tar -C /usr/local -xzf -
export PATH=$PATH:/usr/local/go/bin
echo 'export PATH=$PATH:/usr/local/go/bin' | sudo tee /etc/profile.d/go.sh

# 初期データ作成で使うコマンドのインストール
go get github.com/orisano/wayt

export PATH=$PATH:$HOME/go/bin
echo 'export PATH=PATH=$PATH:$HOME/go/bin' | tee -a ~/.bashrc

# ISUCON10 予選問題をgit cloneして初期データ作成
git clone https://github.com/isucon/isucon10-qualify.git
cd isucon10-qualify/initial-data
pip3 install -r requirements.txt
make

初期データ作成時に一度止まってしまいましたが、Ctrl-Cしてもう一度実行したらうまく動きました。

MySQLをインストールして、ユーザ作成をします。簡単なパスワードになるので気をつけましょう。

sudo apt install mysql-server

sudo mysql
> CREATE USER 'isucon'@'%' IDENTIFIED BY 'isucon';
> GRANT ALL ON *.* TO `isucon`@`%`;
> CREATE DATABASE isuumo;

ここまでできたら c6g.2xlarge 側の準備をします。

c6g.2xlargeインスタンスを起動し、

sudo apt-get update
sudo apt-get install -y mysql-server git make

export PATH=$PATH:/usr/local/go/bin
echo 'export PATH=$PATH:/usr/local/go/bin' | sudo tee /etc/profile.d/go.sh

sudo mysql
> CREATE USER 'isucon'@'%' IDENTIFIED BY 'isucon';
> GRANT ALL ON *.* TO `isucon`@`%`;
> CREATE DATABASE isuumo;

このあと、Intel CPUのインスタンスから、isucon10-qualify ディレクトリを scpで Graviton2 のインスタンスにコピーすれば準備完了です。

初期状態でのスコア比較

まず、Intel CPUである c5.2xlarge での初期スコア

isuumoアプリを make して起動

cd isucon10-qualify/webapp/go
make
./isuumo

別ターミナルを開き、ベンチマークを実行します。

cd isucon10-qualify/bench
make
./bench

初期スコア

初期状態ではタイムアウトも多くますが、最終的に以下のスコアになりました。

c5.2xlarge での初期スコア

2020/12/23 13:26:06 bench.go:102: 最終的な負荷レベル: 8
{"pass":true,"score":1335,"messages":[{"text":"POST /api/estate/nazotte: リクエストに失敗しました (タイムアウトしました)","count":34}],"reason":"OK","language":"go"}

c6g.2xlarge でも同じようにターミナルを2つ開いてアプリケーションとベンチマークを実行します。

c6g.2xlarge での初期スコア

2020/12/23 14:18:14 bench.go:102: 最終的な負荷レベル: 9
{"pass":true,"score":1552,"messages":[{"text":"POST /api/estate/nazotte: リクエストに失敗しました (タイムアウトしました)","count":48}],"reason":"OK","language":"go"}

初期状態では、c6g.2xlarge の方が 16% ほどスコアがよいという結果になりました。

ベンチマーク時の負荷の様子を top コマンドでみると次のようになっておりました。

c5.2xlarge

top - 14:15:39 up 10 min,  3 users,  load average: 7.18, 2.64, 0.96
Tasks: 146 total,   2 running, 144 sleeping,   0 stopped,   0 zombie
%Cpu(s): 85.6 us,  3.2 sy,  0.0 ni, 10.3 id,  0.5 wa,  0.0 hi,  0.4 si,  0.0 st
MiB Mem :  15549.2 total,  12448.3 free,    929.9 used,   2171.0 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  14411.4 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    777 mysql     20   0 2989500 506064  37208 S 638.9   3.2   9:38.23 mysqld
   1331 ubuntu    20   0 1526204  34796   7164 S  44.9   0.2   0:36.05 isuumo
   1486 ubuntu    20   0 1521292 139536   7692 S  30.0   0.9   0:07.15 bench

c6g.2xlarge

top - 14:18:03 up 16 min,  3 users,  load average: 5.98, 2.22, 1.13
Tasks: 189 total,   2 running, 187 sleeping,   0 stopped,   0 zombie
%Cpu(s): 92.9 us,  4.0 sy,  0.0 ni,  1.7 id,  0.3 wa,  0.0 hi,  1.1 si,  0.0 st
MiB Mem :  15709.3 total,  11525.9 free,    880.3 used,   3303.1 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  14661.9 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   3516 mysql     20   0 3049328 490156  34860 S 707.0   3.0  11:29.75 /usr/sbin/mysqld
   4896 ubuntu    20   0 1598624  40468   6208 S  39.5   0.3   0:46.90 ./isuumo
   5193 ubuntu    20   0 1661656 153140   6860 S  33.9   1.0   0:13.45 ./bench

明らかにMySQLボトルネックですが、c6g.2xlarge の方がidleが少なくCPUをよく使えているようにみえます。

感想戦ブランチでのスコア比較

次に、私のブランチへ切り替えてスコア比較をしてみます。

ブランチはこちら

github.com

効果があまりなかったものもありますが、主な変更点は次のようになります。

  • nazotte検索に空間インデックスを利用
  • 検索用テーブルと、データ用テーブルを分離してMySQLの負荷を削減
  • 検索用インデックスの追加。
  • 検索時にrangeクエリにならないようrangeIDカラムを作成しtriggerにてデータ投入
  • 検索時にSQL_CALC_FOUND_ROWSを利用。結果をキャッシュしておき、同条件での検索時はSQL_CALC_FOUND_ROWSをつけずにキャッシュを利用する
  • chairとestateは全てインメモリキャッシュ。検索時は SELECT id をする
  • 検索結果やchairとestateをキャッシュする際は、JSON化したものをキャッシュする
  • bot判定を最適化
  • fiber (fasthttp) へのフレームワーク変更
  • MySQLに秘伝のタレを入れる

いれたMySQLに秘伝のタレ

disable-log-bin
innodb_doublewrite = 0
innodb_flush_log_at_trx_commit = 0
innodb_flush_method = O_DIRECT
innodb_adaptive_hash_index = 0

感想戦ブランチでのスコア

c5.2xlarge での感想戦ブランチスコア

$ ./bench
2020/12/23 14:34:27 bench.go:78: === initialize ===
2020/12/23 14:34:32 bench.go:90: === verify === 
2020/12/23 14:34:32 bench.go:100: === validation ===
2020/12/23 14:34:34 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:34:36 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:34:37 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:34:37 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:34:37 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:34:38 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:34:38 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:34:38 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:34:38 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:34:39 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:34:39 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:35:32 bench.go:102: 最終的な負荷レベル: 11
{"pass":true,"score":41462,"messages":[],"reason":"OK","language":"go"}

初期状態では負荷レベルが最大の11までには届きませんでしたが、ここでは7秒ほどで最大に達し、最終スコアは4万を超えてきました。これはMacbook Pro上でやっていたときより若干良い結果

c6g.2xlarge での感想戦ブランチスコア

ubuntu@ip-10-0-11-141:~/isucon10-qualify/bench$ ./bench
2020/12/23 14:36:20 bench.go:78: === initialize ===
2020/12/23 14:36:30 bench.go:90: === verify ===
2020/12/23 14:36:31 bench.go:100: === validation ===
2020/12/23 14:36:33 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:36:34 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:36:35 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:36:36 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:36:36 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:36:36 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:36:37 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:36:37 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:36:37 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:36:37 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:36:38 load.go:181: 負荷レベルが上昇しました。
2020/12/23 14:37:31 bench.go:102: 最終的な負荷レベル: 11
{"pass":true,"score":39460,"messages":[],"reason":"OK","language":"go"}

Intel CPUに比べると5%ほど低いスコアです。何回か実行してみましたが、4万に届くことはありませんでした。

ベンチマーク実行中の負荷をtopコマンドでみると次のようになっていました。

c5.2xlarge

top - 14:45:24 up 39 min,  3 users,  load average: 3.68, 2.66, 2.15
Tasks: 147 total,   2 running, 145 sleeping,   0 stopped,   0 zombie
%Cpu(s): 57.6 us,  4.3 sy,  0.0 ni, 36.8 id,  0.0 wa,  0.0 hi,  1.3 si,  0.0 st
MiB Mem :  15549.2 total,  10447.3 free,   2096.7 used,   3005.3 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  13346.3 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   3879 ubuntu    20   0 1663700 163836   7724 S 277.3   1.0   1:00.39 bench
   1691 mysql     20   0 3335408 651500  37268 S 189.7   4.1  22:25.35 mysqld
   3650 ubuntu    20   0 2572288   1.0g  15200 R  45.0   6.7   2:13.37 isuumo 

c6g.2xlarge

top - 14:40:43 up 39 min,  3 users,  load average: 1.44, 1.86, 1.69
Tasks: 185 total,   1 running, 184 sleeping,   0 stopped,   0 zombie
%Cpu(s): 58.2 us,  3.6 sy,  0.0 ni, 37.0 id,  0.0 wa,  0.0 hi,  1.2 si,  0.0 st
MiB Mem :  15709.3 total,   9707.4 free,   2011.2 used,   3990.7 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  13669.2 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   7423 ubuntu    20   0 1810080 205848   6788 S 268.8   1.3   0:59.13 ./bench
   5417 mysql     20   0 3724652 652440  35008 S 203.0   4.1  18:10.73 /usr/sbin/mysqld
   7352 ubuntu    20   0 2499408 986528  14492 S  38.9   6.1   0:50.62 ./isuumo

感想戦ブランチでは全体のCPU使用率が下がっています。大きな差ではありませんが、Intel CPUの方がMySQLのCPU使用率が小さくベンチマークとisuumoアプリのCPU使用率が大きいようにみえます。

感想など

tasksetコマンドを使い、benchコマンドが使うCPUを1個に絞った結果も合わせたグラフが次ようになります。

f:id:kazeburo:20201224103305p:plain

Graviton2はIntel CPUのインスタンスに比べてシングルスレッドのパフォーマンスが低めというベンチマーク結果もありますが、

www.anandtech.com

この結果からも初期状態ではMySQLでのマルチスレッド性能が、感想戦ブランチではベンチマーカーのシングルスレッド性能がスコアに影響しているのかなと考えております。

感想戦ブランチではGraviton2のインスタンスの方がスコアが低いという結果になってしまいましたが、インスタンスのコストはGraviton2の方が20%ほど低く、スコアをコストで割った数値ではGraviton2の方が高くなるでしょう。

armなインスタンスの利用ではミドルウェアのパッケージやdockerのイメージが用意されていないことがあるなど、いくつか困難にぶつかることはありますが、パフォーマンスがよくコストメリットも大きいのでやはり使っていくといいんじゃないかと思いますね~

Re: 結局、Go言語をやめる理由はなかった件

すみません、ISUCONのアレに火がついてしまったので..。 Advent Calendarとはとくに関係がありません。

qiita.com

qiita.com

こちらの記事をみて、気になってしまったので interpolateParams の追加とMySQLチューニングをしてベンチマークを回してみました。

ベンチマークの環境が公開されているのは素晴らしいですね。

github.com

8vCPU/16GB Memのc5a.2xlargeなEC2のインスタンスを起動して、以下の手順にしたがってdockerとdocker-composeをインストールしました。OSはUbuntu 20.04を使いました。

docs.docker.com

docs.docker.com

上記のbenchmarkのrepositoryをgit cloneし、compose build && compose upします。

$ git clone https://github.com/okdyy75/bench-docker.git
$ sudo docker-compose build
$ sudo docker-compose up -d

まず何も変更しない状態でのスコア結果は、

Python

$ sudo docker-compose run --rm python sh -c 'cd python; pip install -r ./requirements.txt && time python bench.py'
平均秒数:9.670429
real    1m 36.73s
user    0m 6.25s
sys     0m 2.79s

Go言語

$ sudo docker-compose run --rm golang sh -c 'cd go; go build . && time ./go'
平均秒数:11.70815524 (0x0,0x0)
real    1m 57.08s
user    0m 3.84s
sys     0m 10.83s

たしかに、Go言語の結果の方が少し悪くなっています。

ここで元記事にてmethaneさんから指摘のあった interpolateParams の設定をいれます。

diff --git a/www/go/bench.go b/www/go/bench.go
index 5c56714..a4b86d0 100644
--- a/www/go/bench.go
+++ b/www/go/bench.go
@@ -37,7 +37,7 @@ func getDB() *sql.DB {
        }
 
        dsn := fmt.Sprintf(
-               "%s%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true",
+               "%s%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true&interpolateParams=true",
                user, password, host, port, name)
 
        db, err := sql.Open("mysql", dsn)

そうすると、結果は

平均秒数:10.540287
real    1m 45.40s
user    0m 2.99s
sys     0m 6.26s

となり、pythonに追いつかないまでも、改善はしています。

ところで、ベンチマークを何回か動かしながら気になったのは、スコアが振れが大きく、iowaitがそれなりでているとところです。(Maki-Daisukeさんはioの影響を抑えるためNVMe SSDのついたインスタンスを使われておりました)

$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  1      0 11964048 140004 3405756    0    0     0  7872 8115 13468  1  2 95  2  0
 0  0      0 11964048 140004 3405932    0    0     0  7656 6939 11857  1  2 93  4  0
 0  1      0 11956236 140004 3414672    0    0     0 16284 7068 12074  1  1 94  4  0
 0  1      0 11956236 140004 3414672    0    0     0  7768 8375 14349  1  2 94  3  0
 1  0      0 11956236 140004 3414672    0    0     0  7464 7463 12275  2  1 96  2  0
 0  1      0 11955480 140004 3415720    0    0     0  8780 7971 12701  2  1 97  0  0
 0  1      0 11955480 140004 3415720    0    0     0  7712 8561 13845  1  2 97  0  0
 0  1      0 11955480 140004 3415720    0    0     0  7780 7966 13032  1  1 96  1  0
 0  1      0 11954472 140004 3416496    0    0     0  8788 7051 12944  2  1 90  7  0
 0  1      0 11964536 140004 3405576    0    0     0  9592 9176 16006  2  1 92  4  0
 2  0      0 11964284 140004 3405832    0    0     0  8220 7737 13731  1  1 92  5  0
 1  0      0 11964252 140004 3405928    0    0     0  8056 8078 12893  1  1 97  0  0
 0  1      0 11955416 140004 3414756    0    0     0 16132 6565 11829  1  1 91  7  0
 2  0      0 11955416 140004 3414756    0    0     0  7056 7333 12482  1  1 94  3  0

また、CPUも全然使えてません。

これはMySQL側がボトルネックになっている可能性が高いとみて、対ISUCON用秘伝のタレを投入して、再度実行してみました。

my.cnf を作成。4つのパラメータを追加

$ cat docker/mysql/my.cnf
[mysqld]
innodb_doublewrite = 0
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT_NO_FSYNC
innodb_adaptive_hash_index = 0

compose.ymlに追記して、mysqlを再起動

$ git diff
diff --git a/docker-compose.yml b/docker-compose.yml
index 94b3dc7..e39de9b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,6 +15,7 @@ services:
     volumes:
       - "./.data/mysql:/var/lib/mysql"
       - "./log/mysql:/var/log/mysql"
+      - "./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf"
     ports:
       - "3306:3306"
   # php-fpm ################################################

$ sudo docker-compose restart mysql
Restarting bench-docker_mysql_1 ... done

結果は

Python

平均秒数:1.518821
real    0m 15.21s
user    0m 6.12s
sys     0m 1.69s

Go言語(interpolateParams=false)

平均秒数:1.770360
real    0m 17.70s
user    0m 3.04s
sys     0m 7.85s

Go言語(interpolateParams=true)

平均秒数:1.196632
real    0m 11.96s
user    0m 2.26s
sys     0m 4.95s

interpolateParamsが有効のGo言語が最速になりました。最初の10倍近い高速化ですね。

MySQLが高速化したことで順位が入れ替わるところはもう少し深堀してもいいかと思いますが、Pythonにしたってかなり速くなってますので、これくらいなら正直どの言語でやってもいいのではないかと思います。仲良くしましょう

Go言語を実行中のvmstatは以下のようになりました。

 vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 12075152 140028 3363636    0    0    10   453  441   22  1  1 98  1  0
 0  0      0 12075152 140028 3363636    0    0     0     0  139  257  0  0 100  0  0
 0  1      0 12015176 140028 3387952    0    0     0 25772 1950 2773  5  1 93  1  0
 1  0      0 11968016 140028 3391408    0    0     0 12732 31447 60372  9  7 84  1  0
 1  0      0 11967732 140028 3391376    0    0     0  4660 42484 86361  7  8 85  0  0
 1  0      0 11967748 140028 3391360    0    0     0 14592 35933 73675  6  6 87  1  0
 1  0      0 11967256 140028 3391228    0    0     0  6964 38245 77879  7  7 86  0  0
 2  0      0 11966928 140028 3391416    0    0     0 13028 36928 75262  6  7 87  0  0
 2  0      0 11966816 140028 3391440    0    0     0  7352 37506 76638  7  7 86  0  0
 1  0      0 11968984 140028 3391440    0    0     0 14120 32657 71858  8  7 85  0  0
 1  0      0 11969976 140028 3391468    0    0     0 15564 33490 69433  7  7 86  1  0
 2  0      0 11969976 140028 3391468    0    0     0  3392 40593 82689  7  8 85  0  0
 1  1      0 11971740 140028 3391440    0    0     0 15568 35473 72960  6  7 86  1  0
 2  0      0 11973520 140028 3391476    0    0     0  5420 36698 76628  8  7 85  0  0
 1  0      0 11973344 140028 3391476    0    0     0 13000 37315 77527  6  7 87  0  0
 1  0      0 11973392 140028 3391536    0    0     0  8284 34950 71476  6  8 86  0  0
 2  0      0 11973408 140028 3391536    0    0     0 13840 38778 79834  6  7 86  1  0
 1  0      0 11974132 140028 3391544    0    0     0  8692 37283 74883  7  7 86  0  0
 3  0      0 11974132 140028 3391544    0    0     0 10444 37224 77928  6  8 86  0  0
 2  0      0 11973108 140028 3391608    0    0     0 15688 34599 71052  6  7 86  1  0
 0  0      0 11980788 140028 3391728    0    0     0  6868 33311 72294  7  8 84  0  0
 0  0      0 12069692 140028 3363668    0    0     0  2588 2979 4393  1  1 98  0  0

iowaitが小さくなり、user, systemが以前より高くあがるようになりました。

topだと

top - 07:43:15 up  2:16,  2 users,  load average: 0.43, 0.11, 0.05
Tasks: 169 total,   1 running, 168 sleeping,   0 stopped,   0 zombie
%Cpu(s):  6.2 us,  3.9 sy,  0.0 ni, 85.9 id,  0.4 wa,  0.0 hi,  3.6 si,  0.0 st
MiB Mem :  15827.2 total,  11689.1 free,    689.4 used,   3448.7 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  14833.8 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                          
  44962 systemd+  20   0 1377796 216128  20612 S  77.4   1.3   1:29.73 mysqld                                                                                                           
  47046 root      20   0  710064  10828   3836 S  60.5   0.1   0:06.25 go   

ようになり、1vCPU 使い切りそうなぐらいまできてました。

ちなみに、MySQLのデータディレクトリをみんな大好きtmpfsにおくと、

Go言語(interpolateParams=true)

平均秒数:1.106469
real    0m 11.06s
user    0m 2.45s
sys     0m 5.07s

Python

平均秒数:1.394543
real    0m 13.97s
user    0m 6.15s
sys     0m 1.70s

もう少しだけ速くなります。

まとめ

ミドルウェア含めたベンチマークを行う時は、ミドルウェアやOSの設定なども気を配るのがいいですね。広い視点を保つためもにもISUCONは役に立つ。