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
2018-02-09 14:27:40 +00:00
import (
2018-02-13 14:10:58 +00:00
goflag "flag"
2018-02-09 14:27:40 +00:00
"fmt"
"net"
"os"
"strconv"
2018-06-26 10:14:05 +00:00
"strings"
2018-02-09 14:27:40 +00:00
"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"
2018-02-09 14:27:40 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-06-26 08:42:13 +00:00
"k8s.io/apimachinery/pkg/runtime"
2018-02-09 14:27:40 +00:00
"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"
2018-06-18 09:55:42 +00:00
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"
2018-02-09 14:27:40 +00:00
)
2018-02-13 12:49:04 +00:00
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"
2018-02-13 12:49:04 +00:00
)
2018-02-09 14:27:40 +00:00
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-02-09 14:27:40 +00:00
}
2018-03-19 10:09:20 +00:00
operatorOptions struct {
2018-05-18 09:54:53 +00:00
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
}
2018-05-18 09:54:53 +00:00
livenessProbe probe . LivenessProbe
deploymentProbe probe . ReadyProbe
deploymentReplicationProbe probe . ReadyProbe
storageProbe probe . ReadyProbe
2018-02-09 14:27:40 +00:00
)
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" )
2018-02-13 12:49:04 +00:00
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" )
2018-05-18 09:54:53 +00:00
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 14:27:40 +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 )
2018-02-09 14:27:40 +00:00
cmdMain . Execute ( )
}
2018-03-05 09:00:23 +00:00
// Show usage
func cmdUsage ( cmd * cobra . Command , args [ ] string ) {
cmd . Usage ( )
}
2018-02-09 14:27:40 +00:00
// Run the operator
func cmdMainRun ( cmd * cobra . Command , args [ ] string ) {
2018-06-26 10:14:05 +00:00
// Get environment
namespace := os . Getenv ( constants . EnvOperatorPodNamespace )
name := os . Getenv ( constants . EnvOperatorPodName )
ip := os . Getenv ( constants . EnvOperatorPodIP )
// Prepare log service
2018-02-09 14:27:40 +00:00
var err error
logService , err = logging . NewService ( logLevel )
if err != nil {
cliLog . Fatal ( ) . Err ( err ) . Msg ( "Failed to initialize log service" )
}
2018-06-26 10:14:05 +00:00
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-02-09 14:27:40 +00:00
2018-03-19 10:09:20 +00:00
// Check operating mode
2018-05-18 09:54:53 +00:00
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
2018-06-26 10:14:05 +00:00
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
2018-06-26 10:14:05 +00:00
// Check environment
2018-02-09 14:27:40 +00:00
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 )
}
2018-02-09 14:27:40 +00:00
// 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 ( )
}
2018-02-09 14:27:40 +00:00
// 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-09 14:27:40 +00:00
}
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 ) {
2018-02-09 14:27:40 +00:00
kubecli , err := k8sutil . NewKubeClient ( )
if err != nil {
2018-02-27 10:50:28 +00:00
return operator . Config { } , operator . Dependencies { } , maskAny ( err )
2018-02-09 14:27:40 +00:00
}
2018-05-11 12:58:49 +00:00
image , serviceAccount , err := getMyPodInfo ( kubecli , namespace , name )
2018-02-09 14:27:40 +00:00
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 ) )
2018-02-09 14:27:40 +00:00
}
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-02-09 14:27:40 +00:00
}
2018-03-05 09:00:23 +00:00
crCli , err := client . NewInCluster ( )
2018-02-09 14:27:40 +00:00
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-02-09 14:27:40 +00:00
}
2018-03-19 10:09:20 +00:00
eventRecorder := createRecorder ( cliLog , kubecli , name , namespace )
2018-02-09 14:27:40 +00:00
2018-02-27 10:50:28 +00:00
cfg := operator . Config {
2018-05-18 09:54:53 +00:00
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-09 14:27:40 +00:00
}
2018-02-27 10:50:28 +00:00
deps := operator . Dependencies {
2018-05-18 09:54:53 +00:00
LogService : logService ,
KubeCli : kubecli ,
KubeExtCli : kubeExtCli ,
CRCli : crCli ,
EventRecorder : eventRecorder ,
LivenessProbe : & livenessProbe ,
DeploymentProbe : & deploymentProbe ,
DeploymentReplicationProbe : & deploymentReplicationProbe ,
StorageProbe : & storageProbe ,
2018-02-09 14:27:40 +00:00
}
return cfg , deps , nil
}
2018-05-11 12:58:49 +00:00
// 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
2018-02-09 14:27:40 +00:00
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
2018-11-15 09:22:10 +00:00
image = k8sutil . GetArangoDBImageIDFromPod ( pod )
2018-05-11 12:58:49 +00:00
if image == "" {
// Fallback in case we don't know the id.
image = pod . Spec . Containers [ 0 ] . Image
}
2018-02-09 14:27:40 +00:00
return nil
}
if err := retry . Retry ( op , time . Minute * 5 ) ; err != nil {
2018-05-11 12:58:49 +00:00
return "" , "" , maskAny ( err )
2018-02-09 14:27:40 +00:00
}
2018-05-11 12:58:49 +00:00
return image , sa , nil
2018-02-09 14:27:40 +00:00
}
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
}