1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00
kube-arangodb/main.go

315 lines
11 KiB
Go
Raw Normal View History

2018-02-09 10:11:33 +00:00
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package main
import (
2018-02-13 14:10:58 +00:00
goflag "flag"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
2018-02-13 14:10:58 +00:00
flag "github.com/spf13/pflag"
2018-06-26 08:42:13 +00:00
appsv1beta2 "k8s.io/api/apps/v1beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-06-26 08:42:13 +00:00
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/record"
2018-03-13 15:25:33 +00:00
"github.com/arangodb/kube-arangodb/pkg/client"
scheme "github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned/scheme"
2018-03-13 15:25:33 +00:00
"github.com/arangodb/kube-arangodb/pkg/logging"
"github.com/arangodb/kube-arangodb/pkg/operator"
2018-04-05 11:46:04 +00:00
"github.com/arangodb/kube-arangodb/pkg/server"
2018-03-13 15:25:33 +00:00
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
2018-04-03 08:58:05 +00:00
"github.com/arangodb/kube-arangodb/pkg/util/probe"
2018-03-13 15:25:33 +00:00
"github.com/arangodb/kube-arangodb/pkg/util/retry"
2019-05-07 08:10:23 +00:00
v1 "k8s.io/api/core/v1"
"k8s.io/klog"
)
const (
2018-07-06 10:44:43 +00:00
defaultServerHost = "0.0.0.0"
defaultServerPort = 8528
defaultLogLevel = "debug"
defaultAdminSecretName = "arangodb-operator-dashboard"
2018-07-06 12:22:25 +00:00
defaultAlpineImage = "alpine:3.7"
)
var (
projectVersion = "dev"
projectBuild = "dev"
maskAny = errors.WithStack
cmdMain = cobra.Command{
Use: "arangodb_operator",
Run: cmdMainRun,
}
2018-04-05 11:46:04 +00:00
logLevel string
cliLog = logging.NewRootLogger()
logService logging.Service
serverOptions struct {
2018-07-06 10:44:43 +00:00
host string
port int
tlsSecretName string
adminSecretName string // Name of basic authentication secret containing the admin username+password of the dashboard
allowAnonymous bool // If set, anonymous access to dashboard is allowed
}
2018-03-19 10:09:20 +00:00
operatorOptions struct {
enableDeployment bool // Run deployment operator
enableDeploymentReplication bool // Run deployment-replication operator
enableStorage bool // Run local-storage operator
2018-07-06 12:22:25 +00:00
alpineImage string
2018-03-30 13:40:11 +00:00
}
chaosOptions struct {
allowed bool
2018-03-19 10:09:20 +00:00
}
livenessProbe probe.LivenessProbe
deploymentProbe probe.ReadyProbe
deploymentReplicationProbe probe.ReadyProbe
storageProbe probe.ReadyProbe
)
func init() {
f := cmdMain.Flags()
2018-04-05 11:46:04 +00:00
f.StringVar(&serverOptions.host, "server.host", defaultServerHost, "Host to listen on")
f.IntVar(&serverOptions.port, "server.port", defaultServerPort, "Port to listen on")
f.StringVar(&serverOptions.tlsSecretName, "server.tls-secret-name", "", "Name of secret containing tls.crt & tls.key for HTTPS server (if empty, self-signed certificate is used)")
2018-07-06 10:44:43 +00:00
f.StringVar(&serverOptions.adminSecretName, "server.admin-secret-name", defaultAdminSecretName, "Name of secret containing username + password for login to the dashboard")
f.BoolVar(&serverOptions.allowAnonymous, "server.allow-anonymous-access", false, "Allow anonymous access to the dashboard")
f.StringVar(&logLevel, "log.level", defaultLogLevel, "Set initial log level")
2018-03-19 10:09:20 +00:00
f.BoolVar(&operatorOptions.enableDeployment, "operator.deployment", false, "Enable to run the ArangoDeployment operator")
f.BoolVar(&operatorOptions.enableDeploymentReplication, "operator.deployment-replication", false, "Enable to run the ArangoDeploymentReplication operator")
2018-03-19 10:09:20 +00:00
f.BoolVar(&operatorOptions.enableStorage, "operator.storage", false, "Enable to run the ArangoLocalStorage operator")
2018-07-06 12:22:25 +00:00
f.StringVar(&operatorOptions.alpineImage, "operator.alpine-image", defaultAlpineImage, "Docker image used for alpine containers")
2018-03-30 13:40:11 +00:00
f.BoolVar(&chaosOptions.allowed, "chaos.allowed", false, "Set to allow chaos in deployments. Only activated when allowed and enabled in deployment")
2019-05-07 08:10:23 +00:00
}
2018-02-09 10:11:33 +00:00
func main() {
2018-02-13 14:10:58 +00:00
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
cmdMain.Execute()
}
2018-03-05 09:00:23 +00:00
// Show usage
func cmdUsage(cmd *cobra.Command, args []string) {
cmd.Usage()
}
// Run the operator
func cmdMainRun(cmd *cobra.Command, args []string) {
// Get environment
namespace := os.Getenv(constants.EnvOperatorPodNamespace)
name := os.Getenv(constants.EnvOperatorPodName)
ip := os.Getenv(constants.EnvOperatorPodIP)
// Prepare log service
var err error
logService, err = logging.NewService(logLevel)
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to initialize log service")
}
logService.ConfigureRootLogger(func(log zerolog.Logger) zerolog.Logger {
podNameParts := strings.Split(name, "-")
operatorID := podNameParts[len(podNameParts)-1]
cliLog = cliLog.With().Str("operator-id", operatorID).Logger()
return log.With().Str("operator-id", operatorID).Logger()
})
2019-05-07 08:10:23 +00:00
klog.SetOutput(logService.MustGetLogger("klog"))
klog.Info("nice to meet you")
klog.Flush()
2018-03-19 10:09:20 +00:00
// Check operating mode
if !operatorOptions.enableDeployment && !operatorOptions.enableDeploymentReplication && !operatorOptions.enableStorage {
cliLog.Fatal().Err(err).Msg("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage or any combination of these")
2018-03-19 10:09:20 +00:00
}
2018-02-09 16:21:06 +00:00
// Log version
cliLog.Info().
Str("pod-name", name).
Str("pod-namespace", namespace).
Msgf("Starting arangodb-operator, version %s build %s", projectVersion, projectBuild)
2018-02-09 16:21:06 +00:00
// Check environment
if len(namespace) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodNamespace)
}
if len(name) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodName)
}
2018-04-05 11:46:04 +00:00
if len(ip) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodIP)
}
// Get host name
id, err := os.Hostname()
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to get hostname")
}
2018-04-05 11:46:04 +00:00
// Create kubernetes client
kubecli, err := k8sutil.NewKubeClient()
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create Kubernetes client")
}
2018-07-06 10:44:43 +00:00
secrets := kubecli.CoreV1().Secrets(namespace)
2018-04-05 11:46:04 +00:00
2018-07-02 10:40:32 +00:00
// Create operator
cfg, deps, err := newOperatorConfigAndDeps(id+"-"+name, namespace, name)
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create operator config & deps")
}
o, err := operator.NewOperator(cfg, deps)
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create operator")
}
2018-04-05 11:46:04 +00:00
listenAddr := net.JoinHostPort(serverOptions.host, strconv.Itoa(serverOptions.port))
2018-07-02 07:54:19 +00:00
if svr, err := server.NewServer(kubecli.CoreV1(), server.Config{
2018-07-02 16:24:42 +00:00
Namespace: namespace,
2018-04-05 11:46:04 +00:00
Address: listenAddr,
TLSSecretName: serverOptions.tlsSecretName,
TLSSecretNamespace: namespace,
PodName: name,
PodIP: ip,
2018-07-06 10:44:43 +00:00
AdminSecretName: serverOptions.adminSecretName,
AllowAnonymous: serverOptions.allowAnonymous,
2018-07-02 07:54:19 +00:00
}, server.Dependencies{
2018-07-02 10:40:32 +00:00
Log: logService.MustGetLogger("server"),
2018-07-02 07:54:19 +00:00
LivenessProbe: &livenessProbe,
DeploymentProbe: &deploymentProbe,
DeploymentReplicationProbe: &deploymentReplicationProbe,
StorageProbe: &storageProbe,
2018-07-02 10:40:32 +00:00
Operators: o,
2018-07-06 10:44:43 +00:00
Secrets: secrets,
2018-04-05 11:46:04 +00:00
}); err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create HTTP server")
} else {
go svr.Run()
}
// startChaos(context.Background(), cfg.KubeCli, cfg.Namespace, chaosLevel)
2018-07-02 10:40:32 +00:00
// Start operator
2018-03-19 10:09:20 +00:00
o.Run()
}
2018-02-27 10:50:28 +00:00
// newOperatorConfigAndDeps creates operator config & dependencies.
2018-03-19 10:09:20 +00:00
func newOperatorConfigAndDeps(id, namespace, name string) (operator.Config, operator.Dependencies, error) {
kubecli, err := k8sutil.NewKubeClient()
if err != nil {
2018-02-27 10:50:28 +00:00
return operator.Config{}, operator.Dependencies{}, maskAny(err)
}
image, serviceAccount, err := getMyPodInfo(kubecli, namespace, name)
if err != nil {
2018-02-27 10:50:28 +00:00
return operator.Config{}, operator.Dependencies{}, maskAny(fmt.Errorf("Failed to get my pod's service account: %s", err))
}
kubeExtCli, err := k8sutil.NewKubeExtClient()
if err != nil {
2018-02-27 10:50:28 +00:00
return operator.Config{}, operator.Dependencies{}, maskAny(fmt.Errorf("Failed to create k8b api extensions client: %s", err))
}
2018-03-05 09:00:23 +00:00
crCli, err := client.NewInCluster()
if err != nil {
2018-02-27 10:50:28 +00:00
return operator.Config{}, operator.Dependencies{}, maskAny(fmt.Errorf("Failed to created versioned client: %s", err))
}
2018-03-19 10:09:20 +00:00
eventRecorder := createRecorder(cliLog, kubecli, name, namespace)
2018-02-27 10:50:28 +00:00
cfg := operator.Config{
ID: id,
Namespace: namespace,
PodName: name,
ServiceAccount: serviceAccount,
LifecycleImage: image,
EnableDeployment: operatorOptions.enableDeployment,
EnableDeploymentReplication: operatorOptions.enableDeploymentReplication,
EnableStorage: operatorOptions.enableStorage,
AllowChaos: chaosOptions.allowed,
2018-07-06 12:22:25 +00:00
AlpineImage: operatorOptions.alpineImage,
}
2018-02-27 10:50:28 +00:00
deps := operator.Dependencies{
LogService: logService,
KubeCli: kubecli,
KubeExtCli: kubeExtCli,
CRCli: crCli,
EventRecorder: eventRecorder,
LivenessProbe: &livenessProbe,
DeploymentProbe: &deploymentProbe,
DeploymentReplicationProbe: &deploymentReplicationProbe,
StorageProbe: &storageProbe,
}
return cfg, deps, nil
}
// getMyPodInfo looks up the image & service account of the pod with given name in given namespace
// Returns image, serviceAccount, error.
func getMyPodInfo(kubecli kubernetes.Interface, namespace, name string) (string, string, error) {
var image, sa string
op := func() error {
pod, err := kubecli.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
if err != nil {
cliLog.Error().
Err(err).
Str("name", name).
Msg("Failed to get operator pod")
return maskAny(err)
}
sa = pod.Spec.ServiceAccountName
image = k8sutil.GetArangoDBImageIDFromPod(pod)
if image == "" {
// Fallback in case we don't know the id.
image = pod.Spec.Containers[0].Image
}
return nil
}
if err := retry.Retry(op, time.Minute*5); err != nil {
return "", "", maskAny(err)
}
return image, sa, nil
}
func createRecorder(log zerolog.Logger, kubecli kubernetes.Interface, name, namespace string) record.EventRecorder {
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(func(format string, args ...interface{}) {
log.Info().Msgf(format, args...)
})
2019-05-07 08:10:23 +00:00
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubecli.CoreV1().RESTClient()).Events(namespace)})
2018-06-26 08:42:13 +00:00
combinedScheme := runtime.NewScheme()
scheme.AddToScheme(combinedScheme)
v1.AddToScheme(combinedScheme)
appsv1beta2.AddToScheme(combinedScheme)
return eventBroadcaster.NewRecorder(combinedScheme, v1.EventSource{Component: name})
2018-02-09 10:11:33 +00:00
}