さくらのクラウド DNSサーバのレコード編集時の反映時間を可視化する
dehydratedとさくらのクラウドのDNSを使った証明書取得の記事を書いておりますが、
DNS-01 チャレンジを使って証明書を作成しようとする際に気になるのはDNSレコードの登録から、実際に反映するまでの待ち時間だったりします。dehydratedのhookスクリプトの例をみていると、30秒ほどsleepしているものもあったりします。
そこで、さくらのクラウドのDNS機能のクライアントである sacloudns
コマンドとmackerelをつかって、ゾーンへのレコード追加削除など編集から実際にDNSへの反映にかかる時間を可視化してみました。
計測方法
sacloudnsコマンドを使って適当なレコードを更新するshell scriptを書きます。
$ cat time-taken.sh #!/bin/sh set -e export SAKURACLOUD_ACCESS_TOKEN="xxx" export SAKURACLOUD_ACCESS_TOKEN_SECRET="xxx" TOKEN_VALUE=$(openssl rand -hex 18) sacloudns rset --wait --wait-timeout=80s --zone example.com --name time-taken --ttl 30 --type TXT --data ${TOKEN_VALUE}
sacloudnsコマンドは、TXTレコードを追加・編集した際にのみ --wait
というオプションが使えます。--wait
オプションを指定して実行すると、ゾーン編集リクエストをさくらのクラウドのAPIに送信したのち、指定されたゾーンのNSレコードからDNSサーバを調べ、そのDNSサーバに対して、2秒おきに実際に更新されているか名前解決を行うことで、実際に更新されるまで待つことができる機能です。ACMEのdns-01チャレンジをスムーズに行うためにつくったものですが、今回これを利用します。
このshell scriptをcronで実行し、mackerelに送信するのが次のcrontabの設定です。
*/5 * * * * mkr wrap -n sacloud-dns-rset -d -a -- bash -c 'set -e -o pipefail; \ /opt/mackerel-agent/plugins/bin/mackerel-plugin-command-status \ --timeout 90s -n sacloud-dns-rset -- sh /path/to/time-taken.sh | mkr throw -s my-service'
mkr wrap
はコマンドを実行しエラーがあった場合、mackerelのアラートを発生させるコマンドです。sacloudnsコマンドが失敗した際にわかるようにしています。
mackerel-plugin-command-status
は与えられたコマンドを実行し、その終了コードとかかった時間をメトリクスにするmackerelのプラグインです。
こちらで紹介してますが、バージョン 0.0.2 でかかった時間もメトリクスとして取得できるようになりました。
ホストのメトリクスとして使うことを想定したプラグインではありますが、crontabを実行するサーバを変更してもメトリクスが残るよう、mackerel-plugin-command-statusの結果をサービスメトリクスとして mkr throw
を使って送信しています。
これで5分ごとにDNSを更新し、かかった時間をmackerelに投稿できました。
可視化結果
結果はこんな感じ。
グラフをみると、大体は20秒強、あるいは45秒前後で終わっているようです。また、APIのレスポンスに時間がかかったのか、反映の待ち時間が長かったのかはここではわかりませんが、たまに60秒以上かかることもあるようです。
これらを指標としつつ、改善していきたい所存です。
Go net/httpで任意のタイミングでchunked レスポンスを返す
調査のため、chunked レスポンスを行っている合間に遅延をいれる必要があったので、Go net/httpでの実現方法を調べました。
こちらにあった Flusher.Flush()
を使うことになります。
FizzBuzzっぽいものを遅延をいれつつ書き出すにはこんな感じ
func fizzBuzz(w http.ResponseWriter, r *http.Request) { flusher, ok := w.(http.Flusher) if !ok { w.WriteHeader(500) w.Write([]byte("expected http.ResponseWriter to be an http.Flusher")) return } for i := 1; i <= 15; i++ { p := fmt.Sprintf("#%03d ", i) if i%3 == 0 { p += "Fizz" } if i%5 == 0 { p += "Buzz" } p = strings.TrimSpace(p) p += "\n" w.Write([]byte(p)) flusher.Flush() time.Sleep(300 * time.Millisecond) } }
telnetで試すと意図した通り chunk に分かれてレスポンスされてくるのがわかる
]% telnet localhost 3000 Trying ::1... Connected to localhost. Escape character is '^]'. GET /demo/fizzbuzz_stream HTTP/1.1 Host: example.com HTTP/1.1 200 OK Vary: Accept-Encoding Date: Wed, 10 Mar 2021 01:46:27 GMT Content-Type: text/plain; charset=utf-8 Transfer-Encoding: chunked 5 #001 5 #002 a #003 Fizz 5 #004 a #005 Buzz a #006 Fizz 5 #007 5 #008
このFlushは github.com/NYTimes/gziphandler
にて圧縮をかけていても有効でした。
gziphandlerの簡単な使い方
// fizzbuzzのためMinSizeを小さくしておく。デフォルト 1400 gz, _ := gziphandler.NewGzipLevelAndMinSize(gzip.DefaultCompression, 5) http.Handle("/", gz(http.HandlerFunc(fizzBuzz)))
テスト・検証用で作っている http-dump-request というサーバにこのFizzBuzzを返すエンドポイントを追加しています。このサーバを今後は監視にも利用していくつもりです。
GitHub Actionsからさくらのクラウド オブジェクトストレージへアクセスする
2月1日よりオープンβとなった新しいさくらのクラウド オブジェクトストレージサービスにGitHub Actionsからアクセスしてみるサンプルです。
オブジェクトストレージは2021年3月31日までオープンベータ期間となります。お試しいただいてフィードバックいただけると嬉しいです! オブジェクトストレージ(β)の利用についてはこちらのニュースを確認してください。
この新しいオブジェクトストレージの特徴の一つとして、Amazon S3と高い互換性を持つAPIの提供があげられ、AWS CLIを通してオブジェクトの操作が可能です。マニュアルはこちらから
ということで、GitHub ActionsからAWS CLIを使ってオブジェクトストレージにアクセスを試してみました。
アクセストークンについて
オブジェクトストレージ利用開始時に、 アクセスキーID と シークレットアクセスキー が表示されます。こちらのアクセスキーは全てのバケットを操作できてしまうので、パーミッションメニューからバケットごとにアクセス件が設定なアクセスキーを生成するのがお勧めです。
(画像はマニュアルより)
アクセスキーを作成したら、GitHub Actionsを設定するrepositoryのSecretsに登録します。
Workflow の作成
GitHub ActionsでAWSへのアクセスを行う際には、
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
オプションの追加をするとなおります。
参考
dehydrated とさくらのクラウドのDNS機能をつかってワイルドカード・マルチドメイン証明書取得
dehydrated
はshell scriptでできた letsencrypt/acme のクライアントです。certbotやlegoなど様々なツールがある中で、わりと長いこと使ってますが、安定してワイルドカード・マルチドメイン対応の証明書の取得・更新ができています。
dehydratedとさくらのクラウドのDNS機能と先週末つくったクライアント sacloudns
を使うことで、ほぼ自動的に証明書の取得と更新が行えるので、その紹介です。
こちらのツールは、リリースページからのダウンロードもしくは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_TOKEN
と SAKURACLOUD_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サービスに最適な高性能・高機能なロードバランサアプライアンスです。詳しくはこちら
こちらも参考になります。
エンハンスドロードバランサーの構築が済んでいるといるとして、以下のようにすると証明書が登録できます。
$ 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日より、さくらインターネットにてお仕事をしております。
先月から長めの冬休みにさせていただいておりましたが、本日さくらインターネットに入社させて頂きました。公私ともに長くお世話になっているこの会社でいろんなサービス支えていきます。
— スタンドの時間です! (@kazeburo) 2021年1月18日
1日目からリモートなので写真はイメージです pic.twitter.com/LqyOAoA5dA
前職中は本当にたくさんの皆様にお世話になりました。ありがとうございます。挨拶もほとんど出ておらず申し訳ありません。
さくらインターネットは20年ほどユーザとして使っており、またmixiやメルカリでもサーバを利用しており、公私共に馴染みのある会社です。
2018年の北海道胆振東部地震の際には対応はもちろん、情報がまとまってない中で直接お話を聞く機会をいただいたり、逐次DMなどで情報をいただくなど大変お世話になったのは強く印象に残っています。そのほか、ISUCON9でも問題の出題を一緒にやらせていただいたり、サイボウズ様、jig.jp様との共催で行われた小学生向けのIoTプログラム教室などにも息子とともに参加させていただきました。このためかさくらインターネットに転職をすることを息子に伝えると、ニンマリと喜んでおりました。
これまでのキャリアではWebサービスのパフォーマンス改善、運用などに携わっていたわけですが、さくらインターネットは少し異なるレイヤーへの挑戦になります。今までの経験を生かしつつ、新しいチャレンジも行い、さくらインターネットのお客様とそのお客様にとってよりよいサービスを提供していけるようやっていきます。
もし、さくらインターネットの利用に関わらずWebサービスのパフォーマンスや運用での悩みなどがありましたら、力になれることがあるかと思いますのでtwitterなどで相談くださいませ。
COVID-19の緊急事態宣言が出ているなか、初日からリモートとなかなか難しい船出になっておりますが、素晴らしい同僚の力を借りつつ、一つ一つやりはじめております。今後ともよろしくお願いします。
GitHub Actionsからさくらクラウドのコンテナレジストリ(Lab)にイメージをpushする
こちらのリポジトリが完成形です。
ここで紹介しているコンテナレジストリはLabプロダクトになります。「Labプロダクト」では、新機能の動作検証およびフィードバック収集を目的として、さくらのクラウドにおける開発中サービスを実験的に提供しておりますので、ご注意ください。
マニュアルはこちら
「Labプロダクトとは」も合わせてご確認ください。
コンテナレジトストリの作成
さくらのクラウドのメニューからコンテナレジトストリを選び、「追加」ボタンを押します。
公開・非公開は目的に応じて選択してください。
コンテナレジストリができたら、push/pullするためのユーザを作成します。
権限も適したものを選びましょう。今回はGitHub Actionsからpushを行うので、「Push & Pull」がよさそうです。
このアカウントはコンテナレジストリにのみ使われるアカウントになります。
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-action
と docker/build-push-action
の Actionを特にカスタマイズすることなく使えます。
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 のサーバです。
こんな感じで表示できます。上記で紹介しておりますが、 nomadscafe.sakuracr.jp/http-dump-request
から docker pull
して利用できます。
2020年に買って良かったもの
ほぼ1年間家で仕事をすることになり、自宅の机まわりの環境をこれまで以上に整えてきた1年だったので「#買って良かった2020」というお題で書いてみる。
ヘッドホンとマグカップ
奥さんから誕生日にもらったソニーの WH-H910N。色はアッシュグリーンです。ありがとう。
ワイヤレスで軽くてつけ心地がよく、ノイズキャンセルもあってとても気に入っています。
夏は冷たい飲み物、冬は暖かい飲み物を飲むために買ったKEYUKAのマグカップ。偶然にもヘッドホンと同じ色味でいい感じです。
また、Apple Musicのサブスクリプションも加入しました。
仕事しながら聞いてますが、「おしりたんてい」の「ププっとふむっと解決ダンス」だったり鬼滅の刃の曲が最もよくかかっているような気がします。
椅子
家で長く座って作業ができる椅子がない、ということで在宅勤務になってまず購入したのが椅子。
オフィスチェアの多くは黒い色ですが、割と大きい黒い物体が家にあると、家の中が暗くなりそうということで、オカムラのシルフィーのホワイトフレーム、座面はベージュを選びました。
届くまでに1ヶ月以上かかり、その間は折り畳み椅子でなんとかなんとか過ごしてましたが、背面フレームのデザインもよく満足。
椅子と言えば、ISUCON10個人スポンサーでいただいたアウトドアチェアもよい。
子供たちがよく座っています。941さんありがとう。
ちなみに机は場所的な問題で、ニトリの小さめの机
キーボード
今はBAROCCO MD770 RGB BTを使ってます。真ん中にMagic Trackpadをおくスタイル。左右分離型だいぶ慣れてきましたが、電話をしながら片手で打てないという弱点があります。
Magic Trackpadをそのままおくと低すぎて使いにくいので、百均で買った耐震マットを二枚重にして下にくっつけてます。
普段はFILCOの左右分離型のウレタンリストレストをキーボードの前において使っています。
冬対策
窓の近くに机を置いているので、寒いということで、こちらのパネルヒーターを買ってみた。
足の裏はもちろん、ブランケットを上にかければ簡易こたつになりとても暖かい。奥さんも小さいホットカーペットを買って机の下で使っています。
Apple Watch
Series 6を初購入。
Apple Watch Hermèsの赤色のレザーストラップにしました。1時間に1度立ち上がったり、20秒手を洗っているだけで褒めてくれるので自己肯定感がある。運動も気にし。。。
スタンドはEINBANDさんのAppleWatch木製スタンド。充電器のところはぴったりとハマったりではないので剥がせるタイプの両面テープで固定している。
最近はRaspberry Piを買ったので、homebridgeいれてApple WatchのホームアプリやSiriからLチカして喜んでいます。
来年は何買うかな~