地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【ProxySQL】入門する

概要

ProxySQLを触る機会があったので入門してみた記事。2.1が最新見たいだが仕事で使うのが1.4なのでなるべくそのバージョンに合わせた内容で調べていく。(最新だとこうだね〜みたいなのは書いておくつもり)

proxysql.com

目次

ProxySQLとは

ProxySQLは、ハイパフォーマンスなMySQLSQLプロキシでクエリールールによるルーティングなど柔軟な設定変更が可能だったり変更もgracefulに行えるという特徴を持つ。memcacehdのproxyツールのtwitter/twemproxyあたりをイメージするとわかりやすそうと感じた。ちなみにProxySQLはMysQL以外のDBでも使えるみたいです。

proxysql.com

ここからは主に公式を参考に書いてます。仕事で使うのが1.4とかなので新しすぎる部分はなるべく除いて記載(最新は2.1)。実装はC++がメインみたいで他も結構使われてるみたいでした。(リポジトリ見た感じなので具体的にはわからない。)

主な機能

  • クエリーキャッシュ
    • クエリ分散機能に TTL を設定することで実現可能。selectでも特的のクエリではキャッシュしないみたいな柔軟な設定を書ける
    • キーは「ユーザー名、スキーマ名、およびクエリのハッシュ」になるとのこと
  • クエリールーティング
    • この辺の設定はProxySQLプロセスの再起動を必要とせずに、できるだけ多くの設定を動的に変更可能
  • フェイルオーバー連携
  • ファイアーウォール
  • 自動オフラインバックエンド
    • 死活監視的なものでpingの遅延の値などをみて自動でDBをオフラインにして接続を行わないような設定が可能です。

アーキテクチャ

f:id:ryuichi1208:20210918100023p:plain

  • RUNTIME
    • 起動しているproxysqlプロセスが実際に従うコンフィグデータ。データはメモリ上にあるので設定変更してプロセス再起動すると設定は消えます
  • MEMORY
    • 構成変更するときに主に利用するコンフィグデータ。ここも再起動するとデータは消えます。
  • DISK
    • MEMORYでの設定を永続的に保存するコンフィグデータ。SQLiteのdbに保存される。デフォルトは/var/lib/proxysql/proxysql.db
  • CONFIG FILE
    • テキスト形式で設定を記述する(proxysqlからは参照のみ)。デフォルトは/etc/proxysql.cnf。再起動してもデータは残る

注意点がいくつかあってオンラインで設定した内容はサービス再起動時に消えます。サーバをスケールアウトする際に稼働中のサーバにオンラインで設定した状態だと設定差異が入った状態でサービスインする可能性がありそうという感じですね。ありそうなのはProxySQLの突死とかで意図せずサービス再起動したケースとかも起こりますね。

ログイン

OFFLINE_SOFT/OFFLINE_HARDとは

OFFLINE_HARDに対してOFFLINE_SOFTというステータスがあります。 これは、アクティブなトランザクションとコネクションはそのままで、新しいトラフィックは新しい別のバックエンドに送信するというものらしいです。gracefulに切り替えることでユーザ影響なしで特定のDBを切り離すことが可能になります。ただconnect(2)中とかアプリ管轄外の状態のコネクションに関してはOFFLINE_SOFTしても新規接続は行くのでその辺は念頭に入れておく必要がありそうです。

ちなみにOFFLINE_HARDではサーバーへの新しい接続は作成されず、既存の接続はすぐに削除されます。

監視

github.com

色々プラグインはありそうでどの監視ツールでもおそらく問題なく取れそうな感じがした。知りたいのはスレッド数だったりコネクション数とクエリ発行数とかでその辺あれば概ねことは足りそうでほぼほぼ公式で提供してくれているメトリクスで大丈夫そうでした。あとはスレッドごとのメモリとかTCPレベルでの話は自分で書く必要がありそう。

proxysql.com

負荷分散

weight – the bigger the weight of a server relative to other weights, the higher the probability of the server to be chosen from a hostgroup. ProxySQL default load-balancing algorithm is random-weighted

分散アルゴリズムはデフォルトでWeighted Random Selectionになるらしいです。以下のように書けば3306:3307のクエリ発行割合は1:2になります。

mysql_servers =
(
    {
        address = "127.0.0.1"
        port = 3306
        weight = 1
        hostgroup = 0
        max_connections = 5
    },
    {
        address = "127.0.0.1"
        port = 3307
        weight = 2
        hostgroup = 0
        max_connections = 1
    },
)

設定確認

listenしているportに接続すればProxySQLの設定情報にアクセスすることができる。デフォルトだと6032とかになる模様

ProxySQL administration console prompt
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.5.30 (ProxySQL Admin Module)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

ProxySQLAdmin>

# サーバ一覧
Admin> select * from mysql_servers;

# 設定一覧
Admin> select * from global_variables where variable_name like '%monitor%';

# 各サーバの情報とか
Admin > SELECT hostgroup_id, hostname, status FROM runtime_mysql_servers;

Docker

hub.docker.com

proxysql/proxysqlで用意されていたのでサクッと触る分にはこれで良さそう。

気になったconfigとか

proxysql.com

この辺からピックアップ

設定値 説明
mysql-connect_retries_delay バックエンドMySQLサーバーへの試行が失敗した後、再接続を試行するまでの遅延
mysql-connect_retries_on_failure 接続の失敗につながるその他のイベントが発生した場合に再接続を試行する必要がある回数
mysql-connect_timeout_server バックエンドサーバーへの接続を1回試行した場合のタイムアウト
mysql-connect_timeout_server_max バックエンドに接続するための最大タイムアウト
mysql-default_max_latency_ms 待ち時間が長すぎる場合にバックエンドサーバーを自動的に無視する際のms

【Linux】JVM系言語以外でも暖気運転は効果があるのかの考察(修正版的な感じ)

記事概要

今見ると色々違う気がしたので前書いたやつの修正版として上げてみた。後半は手をつけれなかった。。

JVM言語で話題の暖気運転についてJVM系言語(JIT)以外でも暖気運転は効果があるのかを考察した記事です。何かしらのベンチマークを取ったとかではないのでご注意ください。

JVMとの比較にはPythonとかPerlとかLL系言語かつpreforkで動くウェブアプリケーションを想定しています。

結論

結論としては効果があるケースもあるがJVM系言語ほどの効果は得られない(はず)

そもそも暖気運転とは

暖気運転とはコンパイル済みの状態で本番トラフィックの処理が可能になるよう、サービスインの前にアプリケーションを実行すること。サービス起動時にある程度のリクエストを飛ばしておくことで実際のユーザアクセス時にプログラムのコンパイル処理を事前に行うという仕組み

techblog.zozo.com

b.chiroito.dev

mod_なんちゃらとは

一方LL言語であるPerlをwebで動かす際にmodperlなんかを使ったりする。modperlを使うとインタプリタ的な実行ではなくプログラムは機械語に変換された後、メモリに常駐するようになる。JVM系言語との違いとしてプログラムの実行時にコンパイル処理は不要となっている。

JVMが行う暖気運転の目的であるプログラムの実行時にコンパイルを行う処理がないLL言語でも暖気運転が効果あるのかを考察していく

LL言語で暖気を行うメリット

LL言語で暖気を行うメリットはぱっと思いつく感じだと以下。それぞれ考察を書いていく。

  • ディスクアクセスを減らせる
  • 外部サービスとのコネクションを事前に張れる
  • TCPでのスロースタート問題に対応できる

ディスクアクセスを減らせる

Linuxの基本として一度アクセスしたファイルはメモリが許す限りはページキャッシュというキャッシュに乗ります。一般的にHDDから読むのとメモリから読むのでは(SATA接続HDDとDDR4構成)約100倍程度のRead/Writeに速度差があるのでファイル読み込み時は極力メモリから読むようにしたいです。

qiita.com

webアプリにおける静的ファイルのアクセスではテンプレートファイルなんかが挙げられます。仮に暖気を行っていない場合はプロセスはディスクアクセスを行ってファルを読み込むが暖気を行っておけばメモリ読み出しのみで済むので有利です(プロセスから外部プロセスを生成する際のバイナリ読み込みみたいなケースで大きめのサイズのファイルの場合は体感しやすいかも)。

また、テンプレートファイルが特定ディレクトリの一階層に大量のファイル数が存在する場合は暖気で全てをreadをかけてページキャッシュに乗せなくてもディレクトリをstat(2)しておくのも良さそう。この場合dentryなんかをキャッシュしておけばアクセス時はディスクアクセスは発生するもののファイルシステムのルックアップ処理は軽減できたりするのでメモリ使用量がシビアならそれだけでも効果的(なはず)

monoist.atmarkit.co.jp

外部サービスとのコネクションを事前に張れる

modperlもmodpythonもmodphpもpreforkで動かす場合はプロセス空間が独立していて各プロセスがそれぞれ外部サービスとのコネクションを管理します。connectを暖気で行ってプロセス内で永続化しておけばユーザリクエストは全てconenct済のプロセスが処理するのでconnect分の高速化を行うことができる。(サービスディスカバリ前提のアーキテクチャなんかだと名前解決のコストも馬鹿にならない)

(1) ソケット生成   socket()
(2) ソケット接続要求    connect()
=== ここまでを暖気でやっておけばユーザアクセスは(3)だけで済む
(3) 外部サービスのやりとり

例えばMySQLを例に挙げるとTCP 3-way handshake、データベース層のハンドシェイク、データベース層の認証、データベースプロセス側の接続用プロセス/スレッド生成なんかをMySQL側で行った状態でユーザアクセスが来るといった仕組みを作れる。

(ただこれはポスグレみたいなマルチプロセスモデルのDBの場合はDB側に永続化された分だけプロセスが生成されてしまうので負荷量を計測してからやるなどの考慮が必要そう。)

LL言語でpreforkが前提のモデルだとコネクションプールする仕組みが乏しいためこの辺は暖気が効果的な感じがする。この後書いているTCPレイヤでの輻輳制御にも有利となる。MySQL以外でもhttpレイヤでのkeepaliveをしたりしておくことでその辺の認証も飛ばせたりも可能となったりで意外と効果的な感じはする。(ただこの辺は結局どのミドルウェアもオーバヘッドを最小限にする実装してるだろうし正直計測しないと効果の程は全く読めない。。)

blog.yuuk.io

TCPでのスロースタート問題に対応できる

TCPではでコネクションを確立した後、データの送信量を徐々に増やすことで効率的に通信するための仕組みが有ります。コネクト直後はウィンドウサイズを小さく送って輻輳が起きなければ次はもっと大きなデータを送るといった感じで通信していきます。

暖気を行うことで前述した外部サービス連携で先にコネクションを貼ってある程度通信しておけばこのスロースタートをバイパスすることが可能となる。

gihyo.jp

またmodperlやらmodpythonを直接出してるケースじゃなければLBやらproxyサーバとの通信もTCPを使ってるはずなのでインターナルなサービス間でもこのメリットは活きてくる。(keepaliveをOFFってない限りは)

まとめ

結局、大局的にみたらwebシステムの場合はC-S間のネットワークなり描画なりがボトルネックになって詰まるだろうし正直全体の数%を改善したところでって感じはしますが多分それなりに意味はあると思う。ただ暖気が必要なほどシビアならここで挙げたメリットは対策方法が他にもあるのでそっちの方が良いかも。

具体的には

  • ディスクアクセスを減らせる -> 静的ファイルはtmpfs使ってメモリに乗せておく
  • 外部サービスとのコネクションを事前に張れる -> サービス起動シーケンスに入れよう
  • TCPでのスロースタート問題に対応できる -> 同上

構成を変えずに暖気を行いたいケースがあれば使えそう。(多分)

【k8s】ArgoRolloutsでカナリアリリースを試す

概要

github.com

ArgoRolloutsを使ってさくっとカナリアリリースを試す記事です

ArgoRolloutsとは

KubernetesのCRDで標準ではできないブルーグリーンデプロイやカナリアリリースなどの高度なデプロイ機能を提供するOSSです。ArgoCDと組み合わせることでGitOpsのさらなる広がりを感じることのできるツールです。

Progressive Deliveryとは

Progressive DeliveryはCD++みたいな感じで言われていてCDで解決できなかった問題を解決する手段を兼ね備えたCDのような考え方。全てのユーザー向けに新しい機能を公開する前に、デプロイしたアプリケーションに対する評価を行うフェーズを設けて評価の結果次第で進めるかロールバックを行うかを決める。従来のデリバリーに「分析」という観点を加えることでこれまでのデプロイで行っていた動作確認なんかをカナリアリリース中に自動で行うというもの。ArgoRolloutsではこの分析なんかまで設定を行うことができて分析結果次第でロールバックなんかの判断を自動で行うことができる。

準備

① ArgoRolloutsのインストール

$ kubectl create namespace argo-rollouts
$ kubectl apply -n argo-rollouts -f https://raw.githubusercontent.com/argoproj/argo-rollouts/stable/manifests/install.yaml

$ kubectl get crd rollouts.argoproj.io
NAME                   CREATED AT
rollouts.argoproj.io   2021-01-11T13:02:10Z

② ArgoRolloutsのプラグインのインストール

$ curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
$ chmod +x ./kubectl-argo-rollouts-linux-amd64
$ sudo mv ./kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts

$ kubectl argo rollouts version
kubectl-argo-rollouts: v0.10.2+54343d8
  BuildDate: 2020-12-17T20:46:09Z
  GitCommit: 54343d8c9eb1e4b9f7e87b3da533530199916733
  GitTreeState: clean
  GoVersion: go1.13.1
  Compiler: gc
  Platform: linux/amd64

マニフェストの全容から

ざっくり以下のようにyamlを書くことでカナリアリリースを行うことができます。基本的には.spec.strategy.canary.stps以外は実はDeploymentと共通の項目をかけるので標準機能のローリングアップデートを使っているなら特に困ることなく導入できてしまいます。

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollouts-demo
spec:
  replicas: 5
  strategy:
    canary:
      steps:
      - setWeight: 20
      - pause: {}
      - setWeight: 60
      - pause: {duration: 10}
      - setWeight: 80
      - pause: {duration: 10}
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollouts-demo
  template:
    metadata:
      labels:
        app: rollouts-demo
    spec:
      containers:
      - name: rollouts-demo
        image: ryuichi1208/color:blue
        ports:
        - name: http
          containerPort: 5000
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: rollout
spec:
  type: NodePort
  ports:
  - name: node-port
    protocol: TCP
    port: 5001
    targetPort: 5000
  selector:
    app: rollouts-demo

カナリアリリースをやっている箇所は具体的には以下です。

      - setWeight: 20
      - pause: {}
      - setWeight: 60
      - pause: {duration: 10}
      - setWeight: 80
      - pause: {duration: 10}

setWeightは、送信する必要のあるトラフィックの割合を示します。割合なので今回のサンプルだと20%で1つのpodとなり第一フェーズでは1つのpodのみが新規のpodとなります。

pause構造体はロールアウトに一時停止するように指示します。durationを指定することでそのフェーズの時間を指定することができます。何も指定しない場合は外部からアクションを行わない限りは次フェーズへも行かないしロールバックも起きません。ログなどから人間が判断して先に進めるかを決断します。

# 先に進める場合
$ kubectl argo rollouts promote rollouts-demo

# 異常があってロールバックする場合
$ kubectl argo rollouts abort rollouts-demo

サービスディスカバリに関するメモ書き的なもの

Qiitaより転載

概要

サービスディスカバリを初めてみたのでそのメモ

サービスディスカバリとは

サービスディスカバリ(Service Discovery、サービス検出[1])はサービスのインスタンスがもつネットワーク上の位置を決定することである[2]。

引用元: サービスディスカバリ - Wikipedia

とても簡単に言えばプログラムがサーバを発見する際にドメイン名なんかを識別名としてIPアドレスを特定していく技術のこと。(実態間参照とかいうらしい)。サーバの数も位置も動的にスケールインアウトするようなクラウドネイティブな世界ではIPアドレスリストなんかを持つことが不可能なためよく聞く言葉となっている。       ちなみにKubernetesでは、サービスディスカバリの機能をServiceが提供している。Aレコードでやるやり方以外にも

  • SRVレコードを利用したサービスディスカバリ
  • 環境変数を利用したサービスディスカバリ

なんかがある。Aレコードでやるやり方はDNSラウンドロビン的な感じで1レコードの回答に複数値を入れると行ったやり方。これが一番わかりやすい。サンプルとしては以下のようにtargetのIPアドレスを問い合わせると3IPが帰ってきてプログラムからはこのIP3つを使ってアクセスしていくというやり方。

dig @127.0.0.1 target

;; ANSWER SECTION:
target.         0   IN  A   192.168.1.1
target.         0   IN  A   192.168.1.2
target.         0   IN  A   192.168.1.3

これで嬉しいのはprometheusみたいなpull型の監視ツール。例えば監視対象をtargetと指定しサービスディスカバリを使用するように設定しておけばサーバがスケールアウトしたとしてもprometheus的には何も変えずに自動的にインスタンスを検出してくれる仕組み。jenkinsやらCircleCIなんかでもデプロイ先をドメインにしておけばデプロイ対象が増えてもレコードの修正のみで済む。すごい便利。

Aレコードだけでいいじゃん!って思うがマイクロサービスの登場によってこれだけで済むかというと最近はそんなこともないらしい。大変。。

従来のAレコードで解決してくやり方で直面する問題。

1つ目がDNSラウンドロビンでも同じことが起きる死活監視問題。Aレコードでやるサービスディスカバリはその特性上仮にサーバが死んだとしても問い合わせを行うプログラムからは判別することが難しい。 (もちろん問い合わせ->処理の前にチェックを挟めば問題なし)

2つ目はAレコードでは得られる情報がIPのみ問題。1IP1サービスであれば何も問題は無いが8080と8081で別のサービスをあげたい場合に問題が起きる。

1つ目の解決策としては自動的に登録の削除ができるようなツールを使う。この辺はetcdとかがトレンドになるのだろうか。正直さっぱり分からない。死活監視あたりの資料はちゃんと読もうと思う。

本番環境でのKubernetesの監視 | Sysdig Monitor | Sysdigブログ | コンテナ・Kubernetes環境向けセキュリティ・モニタリング プラットフォーム

2つ目の解決策はSRVレコードを使う。

SRVレコードとは、DNSで定義されるそのドメインについての情報の種類の一つで、そのドメインで提供されているサービスの詳細な情報を記述するためのもの。

引用元: SRVレコード(service record)とは - IT用語辞典 e-Words

例えばプログラムがパラメータでDNS SRVレコードへ問い合わせることでIPアドレスとportを返してくれる仕組みを実装することで解決する。また以下のようなメリットがある。

  • 負荷分散サービスの提供
  • 冗長性の確保
  • サービスポート番号の通知

またSRVのフォーマットを見ればわかるがweigthと行った値を持たせることでノードごとに通信の割合も定義が可能となっている。サービスエンドポイントを検出するのにとても便利。(マイクロサービス・アーキテクチャみたいなインフラの動的展開なんかをサポートするにはSRVが使われることが多いのかな)

(SRV自体はActiveDirectoryあたりで触ったきりだけど正直どこで使ってるんだろうってずっと思ってたがここにきて登場するとはと行った感じでしたw)

_Service._Proto.Name  TTL Class  SRV Priority  Weight  Port  Target

ちなみにNginxでSRVレコードを使えるかと思いきやその機能自体は有料版らしい。昨今のモダンなマイクロサービスでは当たり前に必須の機能なだけに残念な気もするがその辺はどのくらいの企業が使ってるんだろうか。やってることはupstreamを動的に検出する際にportまで検出可能となってる模様。Nginx -> k8sみたいな構成も取れるしで便利そう。

http - Can Nginx resolves upstream server name given as SRV DNS records? - Stack Overflow

(久々にplusのページ見たけど面白そうな機能がたくさんあることに気づいた)

www.nginx.co.jp

まとめ

サービスディスカバリ = プログラムがサーバを検出する方法。って意味で良さそう。

色々調べていて思ったのがサービスディスカバリ自体は目新しい言葉でも機能でも無さそう。普段使ってるドメイン参照してのリクエストやら自宅で運用してるサーバたちのドメインはサービスディスカバリに通じていた。気がする。

SRVレコードを使ってサービスディスカバリをする方法

pythonあたりでやる予定。そのうち。。w