1
0
mirror of https://github.com/rancher/os.git synced 2025-09-06 09:14:31 +00:00

Various fixes

This commit is contained in:
Darren Shepherd
2021-10-22 23:22:09 -07:00
parent 06fc7fc32e
commit b00581c2d7
14 changed files with 189 additions and 103 deletions

3
Dockerfile.docs Normal file
View File

@@ -0,0 +1,3 @@
FROM squidfunk/mkdocs-material
RUN pip install mkdocs-markdownextradata-plugin
RUN apk add -U git openssh

View File

@@ -1,62 +0,0 @@
# RancherOS v2
# WORK IN PROGRESS
RancherOS v2 is an immutable Linux distribution built to run Rancher and
it's corresponding Kubernetes distributions [RKE2](https://rke2.io)
and [k3s](https://k3s.io). It is built using the [cOS-toolkit](https://rancher-sandbox.github.io/cos-toolkit-docs/docs/)
and based on openSUSE. Initial node configurations is done using only a
cloud-init style approach and all further maintenance is done using
Kubernetes operators.
## Use Cases
RancherOS is intended to be ran as the operating system beneath a Rancher Multi-Cluster
Management server or as a node in a Kubernetes cluster managed by Rancher. RancherOS
also allows you to build stand alone Kubernetes clusters that run an embedded
and smaller version of Rancher to manage the local cluster. A key attribute of RancherOS
is that it is managed by Rancher and thus Rancher will exist either locally in the cluster
or centrally with Rancher Multi-Cluster Manager.
## Architecture
### OCI Image based
RancherOS v2 is an A/B style image based distribution. One first runs
on a read-only image A and to do an upgrade pulls a new read only image
B and then reboots the system to run on B. What is unique about
RancherOS v2 is that the runtime images come from OCI Images. Not an
OCI Image containing special artifacts, but an actual Docker runnable
image that is built using standard Docker build processes. RancherOS is
built using normal `docker build` and if you wish to customize the OS
image all you need to do is create a new `Dockerfile`.
### rancherd
RancherOS v2 includes no container runtime, Kubernetes distribution,
or Rancher itself. All of these assests are dynamically pulled at runtime. All that
is included in RancherOS is [rancherd](https://github.com/rancher/rancherd) which
is responsible for bootstrapping RKE2/k3s and Rancher from an OCI registry. This means
an update to containerd, k3s, RKE2, or Rancher does not require an OS upgrade
or node reboot.
### cloud-init
RancherOS v2 is initially configured using a simple version of `cloud-init`.
It is not expected that one will need to do a lot of customization to RancherOS
as the core OS's sole purpose is to run Rancher and Kubernetes and not serve as
a generic Linux distribution.
### RancherOS Operator
RancherOS v2 includes an operator that is responsible for managing OS upgrades
and assiting with secure device onboarding (SDO).
### openSUSE Leap
RancherOS v2 is based off of openSUSE Leap. There is no specific tie in to
openSUSE beyond that RancherOS assumes the underlying distribution is
based on systemd. We choose openSUSE for obvious reasons, but beyond
that openSUSE Leap provides a stable layer to build upon that is well
tested and has paths to commercial support, if one chooses.

View File

@@ -3,6 +3,14 @@ kind: ClusterRole
metadata:
name: rancheros-operator
rules:
- apiGroups:
- ""
resources:
- 'secrets'
verbs:
- 'get'
- 'watch'
- 'list'
- apiGroups:
- rancheros.cattle.io
resources:
@@ -15,10 +23,19 @@ rules:
- 'bundles'
verbs:
- '*'
- apiGroups:
- provisioning.cattle.io
resources:
- 'clusters'
verbs:
- 'get'
- 'watch'
- 'list'
- apiGroups:
- management.cattle.io
resources:
- 'settings'
- 'clusterregistrationtokens'
verbs:
- 'get'
- 'watch'

View File

@@ -11,7 +11,7 @@ import (
)
var (
output = flag.Bool("automatic", false, "Check for and run automatic installation")
automatic = flag.Bool("automatic", false, "Check for and run automatic installation")
printConfig = flag.Bool("print-config", false, "Print effective configuration and exit")
configFile = flag.String("config-file", "", "Config file to use, local file or http/tftp URL")
)
@@ -31,7 +31,7 @@ func main() {
return
}
if err := install.Run(*output, *configFile); err != nil {
if err := install.Run(*automatic, *configFile); err != nil {
logrus.Fatal(err)
}
}

View File

@@ -1,4 +1,4 @@
# This is required for booting a squashfs from network
add_dracutmodules+=" livenet "
install_items+=" /etc/ssl "
install_optional_items+=" /var/lib/ca-certificates "
install_items+=" /etc/ssl/* "
install_optional_items+=" /var/lib/ca-certificates/* /var/lib/ca-certificates/*/* "

View File

@@ -13,4 +13,4 @@ stages:
entity: |
kind: "shadow"
username: "root"
password: "cos"
password: "ros"

View File

@@ -12,8 +12,6 @@ metadata:
namespace: fleet-local
spec:
osImage: "${IMAGE}"
clusterTargets:
- clusterName: local
EOF
}

View File

@@ -18,7 +18,6 @@ type ManagedOSImage struct {
}
type ManagedOSImageSpec struct {
Paused bool `json:"paused,omitempty"`
OSImage string `json:"osImage,omitempty"`
NodeSelector *metav1.LabelSelector `json:"nodeSelector,omitempty"`
Concurrency *int64 `json:"concurrency,omitempty"`

View File

@@ -15,7 +15,10 @@ type Install struct {
NoFormat bool `json:"noFormat,omitempty"`
Debug bool `json:"debug,omitempty"`
TTY string `json:"tty,omitempty"`
Password string `json:"password,omitempy"`
ServerURL string `json:"-"`
Token string `json:"-"`
Role string `json:"-"`
Password string `json:"password,omitempty"`
}
type Config struct {
@@ -24,13 +27,20 @@ type Config struct {
}
type YipConfig struct {
Stages map[string][]Stage `json:"stages,omitempty"`
Stages map[string][]Stage `json:"stages,omitempty"`
Rancherd Rancherd `json:"rancherd,omitempty"`
}
type Stage struct {
Users map[string]User `json:"users,omitempty"`
}
type Rancherd struct {
Server string `json:"server,omitempty"`
Role string `json:"role,omitempty"`
Token string `json:"token,omitempty"`
}
type User struct {
Name string `json:"name,omitempty"`
PasswordHash string `json:"passwd,omitempty"`

View File

@@ -54,6 +54,35 @@ func mapToEnv(prefix string, data map[string]interface{}) []string {
return result
}
func readFileFunc(path string) func() (map[string]interface{}, error) {
return func() (map[string]interface{}, error) {
return readFile(path)
}
}
func readNested(data map[string]interface{}) (map[string]interface{}, error) {
var (
nestedConfigFiles = convert.ToStringSlice(values.GetValueN(data, "rancheros", "install", "configUrl"))
funcs []reader
)
if len(nestedConfigFiles) == 0 {
return data, nil
}
values.RemoveValue(data, "rancheros", "install", "configUrl")
for _, nestedConfigFile := range nestedConfigFiles {
funcs = append(funcs, readFileFunc(nestedConfigFile))
}
funcs = append(funcs, func() (map[string]interface{}, error) {
return data, nil
})
return merge(funcs...)
}
func readFile(path string) (result map[string]interface{}, _ error) {
result = map[string]interface{}{}
defer func() {
@@ -109,26 +138,17 @@ func merge(readers ...reader) (map[string]interface{}, error) {
if err := schema.Mapper.ToInternal(newData); err != nil {
return nil, err
}
newData, err = readNested(newData)
if err != nil {
return nil, err
}
d = values.MergeMapsConcatSlice(d, newData)
}
return d, nil
}
func readConfigMap(cfg string) (map[string]interface{}, error) {
var (
err error
data map[string]interface{}
)
return merge(func() (map[string]interface{}, error) {
data, err = merge(readCmdline,
func() (map[string]interface{}, error) {
return readFile(cfg)
},
)
return data, err
}, func() (map[string]interface{}, error) {
return readFile(convert.ToString(values.GetValueN(data, "rancheros", "install", "configUrl")))
})
return merge(readCmdline, readFileFunc(cfg))
}
func ReadConfig(cfg string) (result Config, err error) {

View File

@@ -2,6 +2,7 @@ package managedos
import (
"context"
"fmt"
"github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
provv1 "github.com/rancher/os2/pkg/apis/rancheros.cattle.io/v1"
@@ -71,6 +72,10 @@ func (h *handler) OnChange(mos *provv1.ManagedOSImage, status provv1.ManagedOSIm
return nil, status, err
}
if mos.Namespace == "fleet-local" && len(mos.Spec.Targets) > 0 {
return nil, status, fmt.Errorf("spec.targets should be empty if in the fleet-local namespace")
}
bundle := &v1alpha1.Bundle{
ObjectMeta: metav1.ObjectMeta{
Name: name.SafeConcatName("mos", mos.Name),
@@ -79,12 +84,15 @@ func (h *handler) OnChange(mos *provv1.ManagedOSImage, status provv1.ManagedOSIm
Spec: v1alpha1.BundleSpec{
Resources: resources,
BundleDeploymentOptions: v1alpha1.BundleDeploymentOptions{},
Paused: mos.Spec.Paused,
RolloutStrategy: mos.Spec.ClusterRolloutStrategy,
Targets: mos.Spec.Targets,
},
}
if mos.Namespace == "fleet-local" {
bundle.Spec.Targets = []v1alpha1.BundleTarget{{ClusterName: "local"}}
}
status, err = h.updateStatus(status, bundle)
return []runtime.Object{
bundle,

View File

@@ -26,6 +26,10 @@ func Ask(cfg *config.Config) error {
if err := AskPassword(cfg); err != nil {
return err
}
if err := AskServerAgent(cfg); err != nil {
return err
}
}
return nil
@@ -50,9 +54,33 @@ func AskInstallDevice(cfg *config.Config) error {
return nil
}
func isServer(cfg *config.Config) (bool, bool, error) {
func AskToken(cfg *config.Config, server bool) error {
var (
token string
err error
)
if cfg.RancherOS.Install.Token != "" {
return nil
}
msg := "Token or cluster secret"
if server {
msg += " (optional)"
}
if server {
token, err = questions.PromptOptional(msg+": ", "")
} else {
token, err = questions.Prompt(msg+": ", "")
}
cfg.RancherOS.Install.Token = token
return err
}
func isServer() (bool, bool, error) {
opts := []string{"server", "agent", "none"}
i, err := questions.PromptFormattedOptions("Run as server or agent?", 0, opts...)
i, err := questions.PromptFormattedOptions("Run as server or agent (Choose none if building an image)?", 0, opts...)
if err != nil {
return false, false, err
}
@@ -60,6 +88,35 @@ func isServer(cfg *config.Config) (bool, bool, error) {
return i == 0, i == 1, nil
}
func AskServerAgent(cfg *config.Config) error {
if cfg.RancherOS.Install.ServerURL != "" || cfg.RancherOS.Install.Silent {
return nil
}
server, agent, err := isServer()
if err != nil {
return err
}
if !server && !agent {
return nil
}
if server {
cfg.RancherOS.Install.Role = "server"
return AskToken(cfg, true)
}
cfg.RancherOS.Install.Role = "agent"
url, err := questions.Prompt("URL of server: ", "")
if err != nil {
return err
}
cfg.RancherOS.Install.ServerURL = url
return AskToken(cfg, false)
}
func AskPassword(cfg *config.Config) error {
if cfg.RancherOS.Install.Silent || cfg.RancherOS.Install.Password != "" {
return nil

View File

@@ -55,7 +55,13 @@ func runInstall(cfg config.Config, output string) error {
}
if cfg.RancherOS.Install.ConfigURL == "" && !cfg.RancherOS.Install.Silent {
yip := config.YipConfig{}
yip := config.YipConfig{
Rancherd: config.Rancherd{
Server: cfg.RancherOS.Install.ServerURL,
Token: cfg.RancherOS.Install.Token,
Role: cfg.RancherOS.Install.Role,
},
}
if cfg.RancherOS.Install.Password != "" || len(cfg.SSHAuthorizedKeys) > 0 {
yip.Stages = map[string][]config.Stage{
"network": {{

View File

@@ -124,22 +124,31 @@ RUN cd /iso && \
luet-makeiso iso.yaml
FROM iso-build AS qcow-build
ARG ACCEL=tcg
RUN SUFFIX= && \
FIRMWARE= && \
if [ "$(uname -m)" = "aarch64" ]; then SUFFIX=-arm64; FIRMWARE=/usr/share/qemu/qemu-uefi-aarch64.bin; fi && \
PACKER_LOG=1 packer build \
echo '#!/bin/bash' > /usr/bin/image && \
echo 'set -e -x' >> /usr/bin/image && \
echo PACKER_LOG=1 packer build \
-var "root_password=ros" \
-var "firmware=${FIRMWARE}" \
-var "memory=1024" \
-var "iso=/iso/output.iso" \
-var "accelerator=tcg" \
-only qemu.cos${SUFFIX} .
RUN mkdir /output && \
mv *.box /output/output.box && \
pigz -dc *.tar.gz | tar xvf - && \
cat cOS | pigz -c > /output/output.qcow.gz
-var "accelerator=${ACCEL}" \
-only qemu.cos${SUFFIX} . >> /usr/bin/image && \
chmod +x /usr/bin/image
RUN echo 'mkdir /output &&' >> /usr/bin/image && \
echo 'mv *.box /output/output.box' >> /usr/bin/image && \
echo 'pigz -dc *.tar.gz | tar xvf -' >> /usr/bin/image && \
echo 'cat cOS | pigz -c > /output/output.qcow.gz'>> /usr/bin/image
CMD ["bash", "-x", "/usr/bin/image"]
FROM qcow-build AS qcow-build2
RUN bash -x /usr/bin/image
FROM scratch AS qcow
COPY --from=qcow-build /output/ /
COPY --from=qcow-build2 /output/ /
FROM scratch AS iso
COPY --from=iso-build /iso/output.iso /
@@ -153,12 +162,17 @@ ARG NAME=RancherOS-Image-dev
ARG VERSION=1
ARG GIT_COMMIT=HEAD
RUN packer build \
-var "root_password=ros" \
-var "cos_version=${VERSION}" \
-var "git_sha=${GIT_COMMIT}" \
-var 'aws_source_ami_filter_owners=["053594193760"]' \
-var "aws_cos_deploy_args=cos-deploy --no-verify --docker-image ${IMAGE}" \
-var "name=${NAME}" \
-only amazon-ebs.cos .
FROM scratch AS default
COPY --from=iso / /
COPY --from=qcow / /
EOF
}
@@ -170,7 +184,22 @@ iso()
qcow()
{
build --target qcow -o build/
ID=qcow-${RANDOM}
if docker run -it --device /dev/kvm busybox /bin/true; then
build --target qcow-build --build-arg ACCEL=kvm -t $ID
docker run --net=host -it --device /dev/kvm --name $ID $ID
else
build --target qcow-build --build-arg ACCEL=tcg -t $ID
docker run --net=host -it --name $ID $ID
fi || {
docker rm -fv $ID
docker rmi $ID
exit 1
}
mkdir -p build/
docker export $ID | tar xvf - -C build/ output/ --strip-components=1
docker rm -fv $ID
docker rmi $ID
}
ami()
@@ -204,6 +233,11 @@ VERSION=${IMAGE##*:}
NAME=${IMAGE%%:${VERSION}}
NAME=${NAME//[^a-zA-Z0-9-@.\/_]/-}
if [ "$1" == dockerfile ]; then
dockerfile
exit 0
fi
if [ -z "${OUTPUT}" ] || [ -z "${IMAGE}" ] || echo "$@" | grep -q -- -h; then
usage
exit 1
@@ -222,10 +256,6 @@ fi
iso)
iso
;;
dockerfile)
dockerfile
exit 0
;;
*)
echo Unknown format $i
exit 1