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"
"net/http"
"os"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
2018-02-13 14:10:58 +00:00
flag "github.com/spf13/pflag"
2018-02-09 14:27:40 +00:00
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
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"
"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"
2018-02-09 14:27:40 +00:00
)
2018-02-13 12:49:04 +00:00
const (
defaultServerHost = "0.0.0.0"
defaultServerPort = 8528
defaultLogLevel = "debug"
)
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 {
host string
port int
tlsSecretName string
2018-02-09 14:27:40 +00:00
}
2018-03-19 10:09:20 +00:00
operatorOptions struct {
enableDeployment bool // Run deployment operator
enableStorage bool // Run deployment operator
2018-03-30 13:40:11 +00:00
}
chaosOptions struct {
allowed bool
2018-03-19 10:09:20 +00:00
}
2018-04-05 14:31:27 +00:00
livenessProbe probe . LivenessProbe
deploymentProbe 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-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" )
f . BoolVar ( & operatorOptions . enableStorage , "operator.storage" , false , "Enable to run the ArangoLocalStorage operator" )
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" )
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-02-20 07:38:46 +00:00
goflag . CommandLine . Parse ( [ ] string { "-logtostderr" } )
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-03-19 10:09:20 +00:00
// Check operating mode
if ! operatorOptions . enableDeployment && ! operatorOptions . enableStorage {
cliLog . Fatal ( ) . Err ( err ) . Msg ( "Turn on --operator.deployment or --operator.storage or both" )
}
2018-02-09 16:21:06 +00:00
// Log version
cliLog . Info ( ) . Msgf ( "Starting arangodb-operator, version %s build %s" , projectVersion , projectBuild )
2018-02-09 14:27:40 +00:00
// Get environment
namespace := os . Getenv ( constants . EnvOperatorPodNamespace )
if len ( namespace ) == 0 {
cliLog . Fatal ( ) . Msgf ( "%s environment variable missing" , constants . EnvOperatorPodNamespace )
}
name := os . Getenv ( constants . EnvOperatorPodName )
if len ( name ) == 0 {
cliLog . Fatal ( ) . Msgf ( "%s environment variable missing" , constants . EnvOperatorPodName )
}
2018-04-05 11:46:04 +00:00
ip := os . Getenv ( constants . EnvOperatorPodIP )
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" )
}
mux := http . NewServeMux ( )
2018-04-05 14:31:27 +00:00
mux . HandleFunc ( "/health" , livenessProbe . LivenessHandler )
2018-04-05 11:46:04 +00:00
mux . HandleFunc ( "/ready/deployment" , deploymentProbe . ReadyHandler )
mux . HandleFunc ( "/ready/storage" , storageProbe . ReadyHandler )
mux . Handle ( "/metrics" , prometheus . Handler ( ) )
listenAddr := net . JoinHostPort ( serverOptions . host , strconv . Itoa ( serverOptions . port ) )
if svr , err := server . NewServer ( kubecli . CoreV1 ( ) , mux , server . Config {
Address : listenAddr ,
TLSSecretName : serverOptions . tlsSecretName ,
TLSSecretNamespace : namespace ,
PodName : name ,
PodIP : ip ,
} ) ; err != nil {
cliLog . Fatal ( ) . Err ( err ) . Msg ( "Failed to create HTTP server" )
} else {
go svr . Run ( )
}
2018-02-09 14:27:40 +00:00
2018-03-19 10:09:20 +00:00
cfg , deps , err := newOperatorConfigAndDeps ( id + "-" + name , namespace , name )
2018-02-09 14:27:40 +00:00
if err != nil {
2018-02-27 10:50:28 +00:00
cliLog . Fatal ( ) . Err ( err ) . Msg ( "Failed to create operator config & deps" )
2018-02-09 14:27:40 +00:00
}
// startChaos(context.Background(), cfg.KubeCli, cfg.Namespace, chaosLevel)
2018-02-27 10:50:28 +00:00
o , err := operator . NewOperator ( cfg , deps )
2018-02-09 14:27:40 +00:00
if err != nil {
2018-02-27 10:50:28 +00:00
cliLog . Fatal ( ) . Err ( err ) . Msg ( "Failed to create operator" )
2018-02-09 14:27:40 +00:00
}
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-03-19 10:09:20 +00:00
ID : id ,
Namespace : namespace ,
PodName : name ,
ServiceAccount : serviceAccount ,
2018-05-11 12:58:49 +00:00
LifecycleImage : image ,
2018-03-19 10:09:20 +00:00
EnableDeployment : operatorOptions . enableDeployment ,
EnableStorage : operatorOptions . enableStorage ,
2018-03-30 13:40:11 +00:00
AllowChaos : chaosOptions . allowed ,
2018-02-09 14:27:40 +00:00
}
2018-02-27 10:50:28 +00:00
deps := operator . Dependencies {
2018-04-03 08:58:05 +00:00
LogService : logService ,
KubeCli : kubecli ,
KubeExtCli : kubeExtCli ,
CRCli : crCli ,
EventRecorder : eventRecorder ,
2018-04-05 14:31:27 +00:00
LivenessProbe : & livenessProbe ,
2018-04-03 08:58:05 +00:00
DeploymentProbe : & deploymentProbe ,
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-05-11 12:58:49 +00:00
image = k8sutil . ConvertImageID2Image ( pod . Status . ContainerStatuses [ 0 ] . ImageID )
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 ... )
} )
eventBroadcaster . StartRecordingToSink ( & v1core . EventSinkImpl { Interface : v1core . New ( kubecli . Core ( ) . RESTClient ( ) ) . Events ( namespace ) } )
return eventBroadcaster . NewRecorder ( scheme . Scheme , v1 . EventSource { Component : name } )
2018-02-09 10:11:33 +00:00
}