Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ jobs:
target: "config-reloader"
- dir: "./victoriametrics-datasource"
container-image: "victoriametrics-datasource"
- dir: "./websocket-keepalive"
container-image: "websocket-keepalive"
build_container:
needs: prepare_matrix
strategy:
Expand Down
8 changes: 8 additions & 0 deletions maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ In case of components whose Go source code are in neco-containers, all dependent
- [victoriametrics](#victoriametrics)
- [victoriametrics-datasource](#victoriametrics-datasource)
- [victoriametrics-operator](#victoriametrics-operator)
- [websocket-keepalive](#websocket-keepalive)

---

Expand Down Expand Up @@ -1162,3 +1163,10 @@ Only the base image and module dependency should be updated.
2. Check upstream Makefile and Dockerfile, and update our Dockerfile if needed.
3. Update `VICTORIAMETRICS_OPERATOR_VERSION` in `Dockerfile`.
4. Update `TAG` file.

## websocket-keepalive

![Regular Update](./regular_update.svg)

1. Upgrade direct dependencies listed in `go.mod`. Use `go get` or your editor's function.
2. Update `TAG` files.
1 change: 1 addition & 0 deletions websocket-keepalive/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
websocket-keepalive
16 changes: 16 additions & 0 deletions websocket-keepalive/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM ghcr.io/cybozu/golang:1.24-noble AS build

COPY . /work
WORKDIR /work

RUN CGO_ENABLED=0 go build -o websocket-keepalive .

FROM scratch
LABEL org.opencontainers.image.source="https://github.com/cybozu/neco-containers"

COPY --from=build /work/websocket-keepalive /usr/local/bin/websocket-keepalive

USER 10000:10000
EXPOSE 9000

ENTRYPOINT ["/usr/local/bin/websocket-keepalive"]
17 changes: 17 additions & 0 deletions websocket-keepalive/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
TAG ?= dev

.PHONY: build
build:
go build -o websocket-keepalive .

.PHONY: image
image:
docker build -t ghcr.io/cybozu/websocket-keepalive:$(TAG) .

.PHONY: test
test:
go test -v ./...

.PHONY: clean
clean:
rm -f websocket-keepalive
62 changes: 62 additions & 0 deletions websocket-keepalive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Websocket keepalive

websocket-keepalive は websocket サーバ・クライアントの疎通の継続性を検証するためのプログラムである。

## Server

サーバは websocket-keepalive クライアントからの接続を待ち受け、接続したクライアントからの Ping メッセージに Pong メッセージで応答を返すのみである。

```console
$ ./websocket-keepalive server -h
Start a WebSocket server that handles client connections

Usage:
websocket-keepalive server [flags]

Flags:
-h, --help help for server
-l, --listen string Host to listen on (default "0.0.0.0")
-i, --ping-interval duration Ping interval in seconds (default 5s)
-p, --port int Port to listen on (default 9000)

Global Flags:
--log-level string Log level (debug, info, warn, error) (default "info")
```

## Client

クライアントは `--host` と `--port` で指定された宛先のサーバに対して Websocket のコネクションを接続する。
接続を確立すると、`--ping-interval` で指定された間隔でサーバに対して Ping メッセージを送信し、サーバからの Pong メッセージを待ち受ける。
Pong メッセージの待ち受けは `--ping-interval` で指定された値の 2 倍の時間である。この期間 Pong メッセージが返ってこない場合は、クライアントから Ping メッセージを再送する。
`--max-retry-limit` で指定された回数再送を繰り返しても Pong メッセージが返ってこない場合は、コネクションが破棄されたものとして接続を切り、プログラムを終了する。

```console
$ ./websocket-keepalive client -h
Start a WebSocket client that sends periodic ping messages

Usage:
websocket-keepalive client [flags]

Flags:
-h, --help help for client
-H, --host string Server host to connect to (default "localhost")
-r, --max-retry-limit int Limit for retrying to send ping (default 3)
-m, --metrics Enable metrics (default true)
-a, --metrics-server string Metrics server address and port (default "0.0.0.0:8080")
-i, --ping-interval duration Interval for sending ping messages (default 10s)
-p, --port int Server port to connect to (default 9000)

Global Flags:
--log-level string Log level (debug, info, warn, error) (default "info")
```

## Metrics

メトリクスはクライアントのみが `--metrics-server` で指定されたポートで出力する。

出力するメトリクスは以下のようになっている。

- `established`
- Websocket で接続が確立していることを示す。確立していたら 1 を出力する。
- `ping_retry_count_total`
- Ping メッセージを再送した回数を示すカウンタ。
1 change: 1 addition & 0 deletions websocket-keepalive/TAG
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.0.1
38 changes: 38 additions & 0 deletions websocket-keepalive/cmd/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cmd

import (
"log/slog"
"os"
"time"

"github.com/cybozu/neco-containers/websocket-keepalive/internal/client"
"github.com/cybozu/neco-containers/websocket-keepalive/internal/metrics"

"github.com/spf13/cobra"
)

var clientConfig = client.Config{}
var clientMetricsConfig = &metrics.Config{}

var clientCmd = &cobra.Command{
Use: "client",
Short: "Start WebSocket client",
Long: "Start a WebSocket client that sends periodic ping messages",
Run: func(cmd *cobra.Command, args []string) {
slog.Info("Starting WebSocket client", "host", clientConfig.Host, "port", clientConfig.Port)
if err := client.RunWithConfig(&clientConfig, clientMetricsConfig); err != nil {
slog.Error("Failed to run WebSocket client", "error", err)
os.Exit(1)
}
},
}

func init() {
clientCmd.Flags().StringVarP(&clientConfig.Host, "host", "H", "localhost", "Server host to connect to")
clientCmd.Flags().IntVarP(&clientConfig.Port, "port", "p", 9000, "Server port to connect to")
clientCmd.Flags().DurationVarP(&clientConfig.PingInterval, "ping-interval", "i", 10 * time.Second, "Interval for sending ping messages")
clientCmd.Flags().IntVarP(&clientConfig.MaxPingRetries, "max-retry-limit", "r", 3, "Limit for retrying to send ping")
clientCmd.Flags().BoolVarP(&clientMetricsConfig.Export, "metrics", "m", true, "Enable metrics")
clientCmd.Flags().StringVarP(&clientMetricsConfig.AddrPort, "metrics-server", "a", "0.0.0.0:8080", "Metrics server address and port")

}
55 changes: 55 additions & 0 deletions websocket-keepalive/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cmd

import (
"log/slog"
"os"

"github.com/spf13/cobra"
)

var (
logLevel string
)

var rootCmd = &cobra.Command{
Use: "websocket-keepalive",
Short: "WebSocket keepalive tool",
Long: "A tool for testing WebSocket connections with keepalive functionality",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
setupLogging()
},
}

func Execute() error {
return rootCmd.Execute()
}

func init() {
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Log level (debug, info, warn, error)")
rootCmd.AddCommand(clientCmd)
rootCmd.AddCommand(serverCmd)
}

func setupLogging() {
var level slog.Level
switch logLevel {
case "debug":
level = slog.LevelDebug
case "info":
level = slog.LevelInfo
case "warn":
level = slog.LevelWarn
case "error":
level = slog.LevelError
default:
level = slog.LevelInfo
}

opts := &slog.HandlerOptions{
Level: level,
}

handler := slog.NewJSONHandler(os.Stdout, opts)
logger := slog.New(handler)
slog.SetDefault(logger)
}
34 changes: 34 additions & 0 deletions websocket-keepalive/cmd/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cmd

import (
"log/slog"
"os"

"github.com/cybozu/neco-containers/websocket-keepalive/internal/metrics"
"github.com/cybozu/neco-containers/websocket-keepalive/internal/server"

"github.com/spf13/cobra"
)

var serverConfig = server.Config{}
var serverMetricsConfig = &metrics.Config{}

var serverCmd = &cobra.Command{
Use: "server",
Short: "Start WebSocket server",
Long: "Start a WebSocket server that handles client connections",
Run: func(cmd *cobra.Command, args []string) {
slog.Info("Starting WebSocket server", "host", serverConfig.Host, "port", serverConfig.Port)
if err := server.RunWithConfig(&serverConfig, serverMetricsConfig); err != nil {
slog.Error("Failed to run WebSocket server", "error", err)
os.Exit(1)
}
},
}

func init() {
serverCmd.Flags().StringVarP(&serverConfig.Host, "listen", "l", "0.0.0.0", "Host to listen on")
serverCmd.Flags().IntVarP(&serverConfig.Port, "port", "p", 9000, "Port to listen on")
serverCmd.Flags().BoolVarP(&serverMetricsConfig.Export, "metrics", "m", true, "Enable metrics")
serverCmd.Flags().StringVarP(&serverMetricsConfig.AddrPort, "metrics-server", "a", "0.0.0.0:8081", "Metrics server address and port")
}
17 changes: 17 additions & 0 deletions websocket-keepalive/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module github.com/cybozu/neco-containers/websocket-keepalive

go 1.24.5

require (
github.com/VictoriaMetrics/metrics v1.39.1
github.com/gorilla/websocket v1.5.3
github.com/spf13/cobra v1.9.1
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/valyala/fastrand v1.1.0 // indirect
github.com/valyala/histogram v1.2.0 // indirect
golang.org/x/sys v0.15.0 // indirect
)
20 changes: 20 additions & 0 deletions websocket-keepalive/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
github.com/VictoriaMetrics/metrics v1.39.1 h1:AT7jz7oSpAK9phDl5O5Tmy06nXnnzALwqVnf4ros3Ow=
github.com/VictoriaMetrics/metrics v1.39.1/go.mod h1:XE4uudAAIRaJE614Tl5HMrtoEU6+GDZO4QTnNSsZRuA=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading
Loading