本記事は富士通クラウドテクノロジーズ Advent Calendar 2023 21日目の記事です。
昨日の記事は@ometora_sateyanのPython初学者がToDoリストリマインダーbotを作ってみたでした。
個人的にこういったBotのトリガーにGitLabのPipeline Scheduleを利用するのはおもしろい発想だなーと思いました。軽いBotであれば、Pipeline Schedule -> Gitlab-CI -> Incoming Webhook -> Slackでシュッと作れてますね。
TL;DR
HTTPリクエストのヘッダーに一定の確率でGoodを設定するTraefikのPluginをシュッと作りました。
おわり
はじめに
TraefikはEdge Routerと呼ばれるものであり、リバースプロキシーやロードバランサーとして利用することができるOSSです。詳細は下記を参照してください。
- https://doc.traefik.io/traefik/#welcome
- https://zenn.dev/pitekusu/books/traefik-pitekusu/viewer/introduction
また、Traefikでは、ユーザの要件に合わせてPluginを作成し組み込むことも可能です。
いくつかPlugin Catalogで公開されているので、眺めてみると役に立つPluginがあるかもしれません。
今回はこのPluginをシュッと作りたいと思います。
Pluginの作成
HTTPリクエストの運勢をHeaderに差し込むPluginを作りたいと思います。
作成するPluginの処理としては以下になります。
- HeaderにKey:
X-FORTUNE
で運勢を追加する - 運勢はGoodまたはBadになる
- 確率RateでGoodになる。それ以外はBadになる
はい、シュッと
// Config the plugin configuration.
type Config struct {
Rate int `json:"rate,omitempty"`
}
// CreateConfig creates the default plugin configuration.
func CreateConfig() *Config {
return &Config{
Rate: 0,
}
}
// Lottery.
type Lottery struct {
next http.Handler
rate int
name string
template *template.Template
}
// New created a new Demo plugin.
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
return &Lottery{
rate: config.Rate,
next: next,
name: name,
template: template.New("lottery").Delims("[[", "]]"),
}, nil
}
func (l *Lottery) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
rand.Seed(time.Now().UnixNano())
fortune := rand.Intn(100) + 1
os.Stdout.WriteString(fmt.Sprintf("fortune/Rate: %v/%v \n", fortune, l.rate))
if l.rate < fortune {
req.Header.Set("X-FORTUNE", "Bad")
} else {
req.Header.Set("X-FORTUNE", "Good")
}
l.next.ServeHTTP(rw, req)
}
Defining a Pluginを参照するにPluginで作成する必要があるものは下記の3種類見たいです。
-
type Config struct { ... }
- Dynamic Configurationで設定したい項目(後述)
-
func CreateConfig() *Config
- Configの作成
-
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error)
- Pluginで行う処理
- X-FORTUNEヘッダーを挿入する処理
ログ出力に関して
Logs曰く、Log出力する方法はos.Stdout.WriteString("...")
またはos.Stderr.WriteString("...")
を使用するしかないみたいです。
traefik.yaml
Pluginの実態以外に、.traefik.yaml
が必要になります。
作成しない場合、TraefikでPluginを読み込む際に下記のエラーが発生します。
msg="Plugins are disabled because an error has occurred." error="1 error occurred:\n\t* failed to open the plugin manifest plugins-local/src/github.com/ystkfujii/traefik-plugin-fortune/.traefik.yml: open plugins-local/src/github.com/ystkfujii/traefik-plugin-fortune/.traefik.yml: no such file or directory\n\n"
.traefik.yaml
の設定項目は下記になります。
内容はManifestの内容をコピペして日本語訳したものになります。
#【必須】Plugins Catalog web UIで表示する名前
displayName: Name of your plugin
#【必須】現在は`middleware`のみ設定可能
type: middleware
#【必須】PluginのImport Path
import: github.com/username/my-plugin
#【必須】Pluginの簡単な説明
summary: Description of what my plugin is doing
#【任意】Pluginに関連するメディア
iconPath: foo/icon.png
bannerPath: foo/banner.png
# 【必須】
# Pluginの確認用のデータ
# Plugins Catalogは起動時の有効性のテストのひとつとして、下記のデータでPluginをテストする
testData:
Headers:
Foo: Bar
pluginのパッケージ名について
最初はパッケージ名をmainに設定して作成してたんですが、TraefikでPluginを読み込む際に下記エラーが発生してうまく読み込めませんでした。
Plugins are disabled because an error has occurred." error="failed to eval New: 1:28: undefined: traefik_plugin_fortune"
下記を参照しパッケージ名をPlugin名にすることで解決しました。
Dockerfile
上記で作成したPluginを読み込んで動作確認するためのdocker composeファイルは以下になります。
今回はRateを30に設定しているので、30%でX-FORTUNEヘッダーが"Good"になります。
services:
traefik:
image: "traefik:v2.10"
container_name: "traefik"
command:
#- "--log.level=DEBUG"
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
# Plugin Catalogから読み込む際にコメントアウト
#- "--experimental.plugins.lottery.modulename=github.com/ystkfujii/traefik-plugin-fortune"
#- "--experimental.plugins.lottery.version=v0.0.1"
# ローカルから読み込む
- "--experimental.localPlugins.lottery.moduleName=github.com/ystkfujii/traefik-plugin-fortune"
ports:
- "80:80"
volumes:
- ".:/plugins-local/src/github.com/ystkfujii/traefik-plugin-fortune"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
whoami:
image: "traefik/whoami"
container_name: "simple-service"
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=PathPrefix(`/who`)"
- "traefik.http.routers.whoami.entrypoints=web"
# traefik-plugin-fortuneの設定
- "traefik.http.routers.whoami.middlewares=my-plugin"
- "traefik.http.middlewares.my-plugin.plugin.lottery.Rate=30"
動作確認
dockerイメージの起動
docker compose up
アクセス確認!
30%の確率でX-Fortune: Goodになります。
$ curl localhost/who
Hostname: a3c72290d619
IP: 127.0.0.1
IP: 192.168.144.3
RemoteAddr: 192.168.144.2:39244
GET /who HTTP/1.1
Host: localhost
User-Agent: curl/7.81.0
Accept: */*
Accept-Encoding: gzip
X-Fortune: Good
X-Forwarded-For: 192.168.144.1
X-Forwarded-Host: localhost
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: 6733240255a7
X-Real-Ip: 192.168.144.1
おわり!
明日は@unecchiの「Notion APIでWeb API操作を勉強してみた」です。
お楽しみに!