-
Notifications
You must be signed in to change notification settings - Fork 48
Single Node deployment with bootstrap-in-place #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
70ddd20
a649ba9
3e64372
290d78b
6d7910f
0f61445
b3b043f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "errors" | ||
|
|
||
| "github.com/openshift/cluster-bootstrap/pkg/bootstrapinplace" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var ( | ||
| CmdBootstrapInPlace = &cobra.Command{ | ||
| Use: "bootstrap-in-place", | ||
| Short: "Create Ignition based on Fedora CoreOS Config", | ||
| Long: "", | ||
| PreRunE: validateBootstrapInPlaceOpts, | ||
| RunE: runCmdBootstrapInPlace, | ||
| SilenceUsage: true, | ||
| } | ||
|
|
||
| bootstrapInPlaceOpts struct { | ||
| assetDir string | ||
| ignitionPath string | ||
| input string | ||
| Strict bool | ||
| Pretty bool | ||
| } | ||
| ) | ||
|
|
||
| func init() { | ||
| cmdRoot.AddCommand(CmdBootstrapInPlace) | ||
| CmdBootstrapInPlace.Flags().BoolVarP(&bootstrapInPlaceOpts.Strict, "strict", "s", true, "fail on any warning") | ||
| CmdBootstrapInPlace.Flags().BoolVarP(&bootstrapInPlaceOpts.Pretty, "pretty", "p", true, "output formatted json") | ||
| CmdBootstrapInPlace.Flags().StringVar(&bootstrapInPlaceOpts.input, "input", "", "fcc input file path") | ||
| CmdBootstrapInPlace.Flags().StringVar(&bootstrapInPlaceOpts.ignitionPath, "output", "o", "Ignition output file path") | ||
| CmdBootstrapInPlace.Flags().StringVarP(&bootstrapInPlaceOpts.assetDir, "asset-dir", "d", "", "allow embedding local files from this directory") | ||
|
|
||
|
eranco74 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| func runCmdBootstrapInPlace(cmd *cobra.Command, args []string) error { | ||
|
|
||
| bip, err := bootstrapinplace.NewBootstrapInPlaceCommand(bootstrapinplace.BootstrapInPlaceConfig{ | ||
| AssetDir: bootstrapInPlaceOpts.assetDir, | ||
| IgnitionPath: bootstrapInPlaceOpts.ignitionPath, | ||
| Input: bootstrapInPlaceOpts.input, | ||
| Strict: bootstrapInPlaceOpts.Strict, | ||
| Pretty: bootstrapInPlaceOpts.Pretty, | ||
| }) | ||
|
|
||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| return bip.Create() | ||
| } | ||
|
|
||
| func validateBootstrapInPlaceOpts(cmd *cobra.Command, args []string) error { | ||
| if bootstrapInPlaceOpts.ignitionPath == "" { | ||
| return errors.New("missing required flag: --output") | ||
| } | ||
| if bootstrapInPlaceOpts.assetDir == "" { | ||
| return errors.New("missing required flag: --asset-dir") | ||
| } | ||
| if bootstrapInPlaceOpts.input == "" { | ||
| return errors.New("missing required flag: --input") | ||
| } | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // Copyright 2019 Red Hat, Inc | ||
|
|
||
| package bootstrapinplace | ||
|
|
||
| import ( | ||
| "io/ioutil" | ||
| "os" | ||
|
|
||
| "github.com/coreos/fcct/config" | ||
| fcctCommon "github.com/coreos/fcct/config/common" | ||
| "github.com/openshift/cluster-bootstrap/pkg/common" | ||
| ) | ||
|
|
||
| func fail(format string, args ...interface{}) { | ||
| common.UserOutput(format, args...) | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| type BootstrapInPlaceConfig struct { | ||
| AssetDir string | ||
| IgnitionPath string | ||
| Input string | ||
| Strict bool | ||
| Pretty bool | ||
| } | ||
| type BootstrapInPlaceCommand struct { | ||
| config BootstrapInPlaceConfig | ||
| } | ||
|
|
||
| func NewBootstrapInPlaceCommand(config BootstrapInPlaceConfig) (*BootstrapInPlaceCommand, error) { | ||
| return &BootstrapInPlaceCommand{ | ||
| config: config, | ||
| }, nil | ||
| } | ||
|
|
||
| func (i *BootstrapInPlaceCommand) Create() error { | ||
|
eranco74 marked this conversation as resolved.
|
||
|
|
||
| infile, err := os.Open(i.config.Input) | ||
| if err != nil { | ||
| fail("Error occurred while trying to open %s: %v\n", i.config.Input, err) | ||
| } | ||
| defer infile.Close() | ||
|
|
||
| dataIn, err := ioutil.ReadAll(infile) | ||
| if err != nil { | ||
| fail("Error occurred while trying to read %s: %v\n", infile.Name(), err) | ||
| } | ||
|
|
||
| dataOut, r, err := config.TranslateBytes(dataIn, fcctCommon.TranslateBytesOptions{ | ||
| TranslateOptions: fcctCommon.TranslateOptions{FilesDir: i.config.AssetDir}, | ||
| Pretty: i.config.Pretty, | ||
| Strict: i.config.Strict}, | ||
| ) | ||
| common.UserOutput("%s", r.String()) | ||
| if err != nil { | ||
| fail("Error translating config: %v\n", err) | ||
| } | ||
|
|
||
| outfile, err := os.OpenFile(i.config.IgnitionPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) | ||
| if err != nil { | ||
| fail("Failed to open %s: %v\n", i.config.IgnitionPath, err) | ||
| } | ||
| defer outfile.Close() | ||
|
|
||
| if _, err := outfile.Write(append(dataOut, '\n')); err != nil { | ||
| fail("Failed to write config to %s: %v\n", outfile.Name(), err) | ||
| } | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package common | ||
|
|
||
| import "fmt" | ||
|
|
||
| // All start command printing to stdout should go through this fmt.Printf wrapper. | ||
| // The stdout of the start command should convey information useful to a human sitting | ||
| // at a terminal watching their cluster bootstrap itself. Otherwise the message | ||
| // should go to stderr. | ||
| func UserOutput(format string, a ...interface{}) { | ||
|
eranco74 marked this conversation as resolved.
Outdated
|
||
| fmt.Printf(format, a...) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,39 @@ | ||
| package start | ||
|
|
||
| import ( | ||
| "context" | ||
| "crypto/tls" | ||
| "fmt" | ||
| "github.com/openshift/cluster-bootstrap/pkg/common" | ||
| "io" | ||
| "k8s.io/apimachinery/pkg/util/wait" | ||
| "net/http" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
| "time" | ||
| ) | ||
|
|
||
| type bootstrapControlPlane struct { | ||
| assetDir string | ||
| podManifestPath string | ||
| ownedManifests []string | ||
| kubeApiHost string | ||
| } | ||
|
|
||
| // newBootstrapControlPlane constructs a new bootstrap control plane object. | ||
| func newBootstrapControlPlane(assetDir, podManifestPath string) *bootstrapControlPlane { | ||
| func newBootstrapControlPlane(assetDir, podManifestPath string, kubeApiHost string) *bootstrapControlPlane { | ||
| return &bootstrapControlPlane{ | ||
| assetDir: assetDir, | ||
| podManifestPath: podManifestPath, | ||
| kubeApiHost: kubeApiHost, | ||
| } | ||
| } | ||
|
|
||
| // Start seeds static manifests to the kubelet to launch the bootstrap control plane. | ||
| // Users should always ensure that Cleanup() is called even in the case of errors. | ||
| func (b *bootstrapControlPlane) Start() error { | ||
| UserOutput("Starting temporary bootstrap control plane...\n") | ||
| common.UserOutput("Starting temporary bootstrap control plane...\n") | ||
| // Make secrets temporarily available to bootstrap cluster. | ||
| if err := os.RemoveAll(bootstrapSecretsDir); err != nil { | ||
| return err | ||
|
|
@@ -42,7 +51,42 @@ func (b *bootstrapControlPlane) Start() error { | |
| manifestsDir := filepath.Join(b.assetDir, assetPathBootstrapManifests) | ||
| ownedManifests, err := copyDirectory(manifestsDir, b.podManifestPath, false /* overwrite */) | ||
| b.ownedManifests = ownedManifests // always copy in case of partial failure. | ||
| return err | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Wait for kube-apiserver to be available and return. | ||
| return b.waitForApi() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain what problem this is looking to solve?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in case required-pods="" (this is the case when running bootstrap-in-place) cluster-bootstrap will fail to publish an event since kube-apiserver isn't up yet. |
||
| } | ||
|
|
||
| // waitForApi will wait until kube-apiserver readyz endpoint is available | ||
| func (b *bootstrapControlPlane) waitForApi() error { | ||
| common.UserOutput("Waiting up to %v for the Kubernetes API\n", bootstrapPodsRunningTimeout) | ||
| apiContext, cancel := context.WithTimeout(context.Background(), bootstrapPodsRunningTimeout) | ||
| defer cancel() | ||
| customTransport := http.DefaultTransport.(*http.Transport).Clone() | ||
| customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} | ||
| client := &http.Client{Transport: customTransport} | ||
| previousErrorSuffix := "" | ||
| wait.Until(func() { | ||
|
eranco74 marked this conversation as resolved.
Outdated
|
||
| _, err := client.Get(fmt.Sprintf("https://%s/readyz", b.kubeApiHost)) | ||
|
eranco74 marked this conversation as resolved.
Outdated
|
||
| if err == nil { | ||
| common.UserOutput("API is up\n") | ||
| cancel() | ||
| } else { | ||
| chunks := strings.Split(err.Error(), ":") | ||
| errorSuffix := chunks[len(chunks)-1] | ||
| if previousErrorSuffix != errorSuffix { | ||
|
eranco74 marked this conversation as resolved.
Outdated
|
||
| common.UserOutput("Still waiting for the Kubernetes API: %v\n", err) | ||
| previousErrorSuffix = errorSuffix | ||
| } | ||
| } | ||
| }, time.Second, apiContext.Done()) | ||
| if apiContext.Err() == context.Canceled { | ||
| return nil | ||
| } else { | ||
| return fmt.Errorf("time out waiting for Kubernetes API") | ||
| } | ||
| } | ||
|
|
||
| // Teardown brings down the bootstrap control plane and cleans up the temporary manifests and | ||
|
|
@@ -52,7 +96,7 @@ func (b *bootstrapControlPlane) Teardown() error { | |
| return nil | ||
| } | ||
|
|
||
| UserOutput("Tearing down temporary bootstrap control plane...\n") | ||
| common.UserOutput("Tearing down temporary bootstrap control plane...\n") | ||
| if err := os.RemoveAll(bootstrapSecretsDir); err != nil { | ||
| return err | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.