Simplified bootstrapping of a cluster (#40)

* Simplified bootstrapping of a cluster

Introduced a new convention about a pre-defined `bootstrap-admin` account for `orchard controller run`. Providing `ORCHARD_BOOTSTRAP_ADMIN_TOKEN` will auto-create such user for easier configuration. `bootstrap-admin` can be used for creating other service accounts on the first run and after that can be disposed.

Also change `orchard worker run` to expect controller URL as the only parameter and a bootstrap token passed via an argument instead of using a context that might not be created.

* Missing error check
This commit is contained in:
Fedor Korotkov 2023-03-22 15:43:37 -04:00 committed by GitHub
parent 9b5ad09841
commit cdf5c5eb00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 100 additions and 39 deletions

View File

@ -48,21 +48,11 @@ func newCreateCommand() *cobra.Command {
}
func runCreate(cmd *cobra.Command, args []string) error {
addr := args[0]
if !strings.HasPrefix(addr, "https://") && !strings.HasPrefix(addr, "http://") {
addr = "https://" + addr
}
controllerURL, err := url.Parse(addr)
controllerURL, err := netconstants.NormalizeAddress(args[0])
if err != nil {
return err
}
if controllerURL.Port() == "" {
controllerURL.Host += fmt.Sprintf(":%d", netconstants.DefaultControllerPort)
}
// Establish trust
var trustedControllerCertificate *x509.Certificate
@ -82,19 +72,9 @@ func runCreate(cmd *cobra.Command, args []string) error {
}
}
// Check that the API is accessible
privatePool := x509.NewCertPool()
privatePool.AddCert(trustedControllerCertificate)
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS13,
RootCAs: privatePool,
ServerName: netconstants.DefaultControllerServerName,
}
client, err := client.New(
client.WithAddress(controllerURL.String()),
client.WithTLSConfig(tlsConfig),
client.WithTrustedCertificate(trustedControllerCertificate),
client.WithCredentials(serviceAccountName, serviceAccountToken),
)
if err != nil {

View File

@ -20,8 +20,6 @@ var ErrInitFailed = errors.New("controller initialization failed")
var controllerCertPath string
var controllerKeyPath string
var serviceAccountName string
var serviceAccountToken string
func FindControllerCertificate(dataDir *controller.DataDir) (controllerCert tls.Certificate, err error) {
if controllerCertPath != "" || controllerKeyPath != "" {

View File

@ -14,6 +14,7 @@ import (
)
var ErrRunFailed = errors.New("failed to run controller")
var BootstrapAdminAccountName = "bootstrap-admin"
var address string
@ -39,11 +40,6 @@ func newRunCommand() *cobra.Command {
cmd.PersistentFlags().StringVar(&controllerKeyPath, "controller-key", "",
"use the controller certificate key from the specified path instead of the auto-generated one"+
" (requires --controller-cert)")
cmd.PersistentFlags().StringVar(&serviceAccountName, "superuser-account-name", "",
"optional name of a service account with maximum privileges to auto-create")
cmd.PersistentFlags().StringVar(&serviceAccountToken, "superuser-account-token", "",
"token to use when creating a service account with maximum privileges "+
"(required when --admin-account-name is specified)")
return cmd
}
@ -94,14 +90,19 @@ func runController(cmd *cobra.Command, args []string) (err error) {
return err
}
if serviceAccountName != "" {
if adminToken, ok := os.LookupEnv("ORCHARD_BOOTSTRAP_ADMIN_TOKEN"); ok {
err = controllerInstance.EnsureServiceAccount(&v1.ServiceAccount{
Meta: v1.Meta{
Name: serviceAccountName,
Name: BootstrapAdminAccountName,
},
Token: serviceAccountToken,
Token: adminToken,
Roles: v1.AllServiceAccountRoles(),
})
} else {
err = controllerInstance.DeleteServiceAccount(BootstrapAdminAccountName)
}
if err != nil {
return err
}
return controllerInstance.Run(cmd.Context())

View File

@ -1,19 +1,52 @@
package worker
import (
"errors"
"github.com/cirruslabs/orchard/internal/bootstraptoken"
"github.com/cirruslabs/orchard/internal/netconstants"
"github.com/cirruslabs/orchard/internal/worker"
"github.com/cirruslabs/orchard/pkg/client"
"github.com/spf13/cobra"
"go.uber.org/zap"
)
var ErrBootstrapTokenNotProvided = errors.New("no bootstrap token provided")
var bootstrapTokenRaw string
func newRunCommand() *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "run",
RunE: runWorker,
Args: cobra.ExactArgs(1),
}
cmd.PersistentFlags().StringVar(&bootstrapTokenRaw, "bootstrap-token", "",
"a bootstrap token retrieved via `orchard get bootstrap-token <service-account-name-for-workers>`")
return cmd
}
func runWorker(cmd *cobra.Command, args []string) (err error) {
controllerURL, err := netconstants.NormalizeAddress(args[0])
if err != nil {
return err
}
if bootstrapTokenRaw == "" {
return ErrBootstrapTokenNotProvided
}
bootstrapToken, err := bootstraptoken.NewFromString(bootstrapTokenRaw)
if err != nil {
return err
}
controllerClient, err := client.New(
client.WithAddress(controllerURL.String()),
client.WithTrustedCertificate(bootstrapToken.Certificate()),
client.WithCredentials(bootstrapToken.ServiceAccountName(), bootstrapToken.ServiceAccountToken()),
)
if err != nil {
return err
}
// Initialize the logger
logger, err := zap.NewProduction()
if err != nil {
@ -25,10 +58,10 @@ func runWorker(cmd *cobra.Command, args []string) (err error) {
}
}()
worker, err := worker.New(worker.WithDataDirPath(dataDirPath), worker.WithLogger(logger))
workerInstance, err := worker.New(worker.WithClient(controllerClient), worker.WithLogger(logger))
if err != nil {
return err
}
return worker.Run(cmd.Context())
return workerInstance.Run(cmd.Context())
}

View File

@ -129,6 +129,12 @@ func (controller *Controller) EnsureServiceAccount(serviceAccount *v1.ServiceAcc
})
}
func (controller *Controller) DeleteServiceAccount(name string) error {
return controller.store.Update(func(txn storepkg.Transaction) error {
return txn.DeleteServiceAccount(name)
})
}
func (controller *Controller) Run(ctx context.Context) error {
// Run the scheduler so that each VM will eventually
// be assigned to a specific WorkerUID

View File

@ -1,6 +1,28 @@
package netconstants
import (
"fmt"
"net/url"
"strings"
)
const (
DefaultControllerPort = 6120
DefaultControllerServerName = "orchard-controller"
)
func NormalizeAddress(addr string) (*url.URL, error) {
if !strings.HasPrefix(addr, "https://") && !strings.HasPrefix(addr, "http://") {
addr = "https://" + addr
}
controllerURL, err := url.Parse(addr)
if err != nil {
return nil, err
}
if controllerURL.Port() == "" {
controllerURL.Host += fmt.Sprintf(":%d", DefaultControllerPort)
}
return controllerURL, nil
}

View File

@ -1,6 +1,9 @@
package worker
import "go.uber.org/zap"
import (
"github.com/cirruslabs/orchard/pkg/client"
"go.uber.org/zap"
)
type Option func(*Worker)
@ -15,3 +18,9 @@ func WithLogger(logger *zap.Logger) Option {
worker.logger = logger.Sugar()
}
}
func WithClient(client *client.Client) Option {
return func(worker *Worker) {
worker.client = client
}
}

View File

@ -1,6 +1,10 @@
package client
import "crypto/tls"
import (
"crypto/tls"
"crypto/x509"
"github.com/cirruslabs/orchard/internal/netconstants"
)
type Option func(*Client)
@ -10,9 +14,17 @@ func WithAddress(address string) Option {
}
}
func WithTLSConfig(tlsConfig *tls.Config) Option {
func WithTrustedCertificate(cert *x509.Certificate) Option {
return func(client *Client) {
client.tlsConfig = tlsConfig
// Check that the API is accessible
privatePool := x509.NewCertPool()
privatePool.AddCert(cert)
client.tlsConfig = &tls.Config{
MinVersion: tls.VersionTLS13,
RootCAs: privatePool,
ServerName: netconstants.DefaultControllerServerName,
}
}
}