cmd/clickhouse/entrypoint.go (173 lines of code) (raw):
package main
import (
"context"
_ "embed"
"errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/Altinity/clickhouse-backup/pkg/status"
clickhousebackup "github.com/JetBrains/ij-perf-report-aggregator/pkg/clickhouse-backup"
"github.com/nats-io/nats.go"
"go.deanishe.net/env"
)
//go:embed config.xml
var clickhouseConfig []byte
func main() {
clickhouseExecutable := "/usr/bin/clickhouse"
isLocalRun := os.Getenv("KUBERNETES_SERVICE_HOST") == ""
ctx := context.Background()
if isLocalRun {
clickhouseExecutable = "/Users/maxim.kolmakov/clickhouse"
clickhousebackup.SetS3EnvForLocalRun(ctx)
}
bucket := getEnvOrFile("S3_BUCKET", "/etc/s3/bucket")
s3AccessKey := getEnvOrFile("S3_ACCESS_KEY", "/etc/s3/accessKey")
s3SecretKey := getEnvOrFile("S3_SECRET_KEY", "/etc/s3/secretKey")
restoreData := os.Getenv("RESTORE_DB") == "true"
configFile := "/var/lib/clickhouse/config.xml"
if isLocalRun {
workingDir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
configFile = filepath.Join(workingDir, "deployment", "ch-local-config.xml")
}
if restoreData {
err := prepareConfigAndDir(isLocalRun, bucket, s3AccessKey, s3SecretKey, configFile)
if err != nil {
log.Fatal(err)
}
}
cmd := exec.CommandContext(ctx, clickhouseExecutable, "server", "--config-file="+configFile)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
if restoreData {
defer func() {
err = cmd.Process.Signal(syscall.SIGTERM)
if err != nil {
log.Println(err)
_ = cmd.Process.Kill()
}
err = cmd.Wait()
if err != nil {
log.Println(err)
}
}()
err = restoreDb()
if err != nil {
log.Fatal(err)
}
return
}
go func() {
// wait for clickhouse server start
time.Sleep(10 * time.Second)
requestClearCache()
}()
err = cmd.Wait()
if err != nil {
log.Fatal(err)
}
}
func prepareConfigAndDir(isLocalRun bool, bucket string, s3AccessKey string, s3SecretKey string, configFile string) error {
chDir := "/var/lib/clickhouse"
if isLocalRun {
chDir = env.GetString("CLICKHOUSE_DATA_PATH", "/Volumes/data/ij-perf-db/clickhouse")
}
entries, err := os.ReadDir(chDir)
if err != nil && !os.IsNotExist(err) {
return err
}
for _, entry := range entries {
err = os.RemoveAll(filepath.Join(chDir, entry.Name()))
if err != nil && !os.IsNotExist(err) {
return err
}
}
if !isLocalRun {
s3Url := "https://" + bucket + ".s3.eu-west-1.amazonaws.com/data/"
log.Print("S3 URL: " + s3Url)
s := strings.NewReplacer(
"$S3_URL", s3Url,
"$S3_ACCESS_KEY", s3AccessKey,
"$S3_SECRET_KEY", s3SecretKey,
).Replace(string(clickhouseConfig))
// /etc is not writeable
err = os.WriteFile(configFile, []byte(s), 0o666)
if err != nil {
return err
}
}
return nil
}
func restoreDb() error {
// wait a little bit for clickhouse start
time.Sleep(4 * time.Second)
backuper := clickhousebackup.CreateBackuper()
attemptCount := 3
var backupName string
main:
for i := range attemptCount {
backups, err := backuper.GetRemoteBackups(context.Background(), true)
if err != nil {
if i < attemptCount {
time.Sleep(time.Duration((i+1)*3) * time.Second)
continue
}
return fmt.Errorf("%w", err)
}
if len(backups) != 0 {
for j := len(backups) - 1; j > 0; j-- {
if backups[j].Broken == "" {
backupName = backups[j].BackupName
break main
}
}
}
}
if backupName == "" {
return errors.New("no remote backup")
}
err := backuper.RestoreFromRemote(backupName, "", nil, nil, false, false, false, false, false, false, false, status.NotFromAPI)
if err != nil {
return fmt.Errorf("%w", err)
}
log.Println("DB is restored (backup=" + backupName + ")")
return nil
}
func requestClearCache() {
url := os.Getenv("NATS")
if url == "" {
url = "nats://nats:4222"
}
nc, err := nats.Connect(url, nats.Name("NATS Sample Publisher"))
if err != nil {
log.Fatal(err)
}
defer nc.Close()
err = nc.Publish("server.clearCache", []byte("clickhouse"))
if err != nil {
log.Fatal(err)
}
err = nc.Flush()
if err != nil {
log.Fatal(err)
}
}
func getEnvOrFile(envName string, file string) string {
v := os.Getenv(envName)
if v == "" {
b, err := os.ReadFile(file)
if err != nil {
log.Fatal(err)
}
return string(b)
}
return v
}