mirror of
https://github.com/labring/sealos.git
synced 2026-09-01 15:38:06 +08:00
fixed cluster operator auto to create kuberentes (#2139)
* fixed cluster operator to create kuberentes auto Signed-off-by: fanux <fanux@sealos.io> * add don't download the sealos binfile if already exist * complete test for create kubernetes cluster Signed-off-by: fanux <fanux@sealos.io>
This commit is contained in:
@@ -76,6 +76,11 @@ make manifests
|
||||
|
||||
More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
|
||||
|
||||
## Cluster operator specify the sealos version
|
||||
|
||||
Using the annotations: "sealos.io/sealos/version"
|
||||
Example: "sealos.io/sealos/version=4.1.3"
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2022.
|
||||
|
||||
@@ -21,24 +21,6 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// ClusterSpec defines the desired state of Cluster
|
||||
type ClusterSpec struct {
|
||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
Infra string `json:"infra,omitempty"`
|
||||
Images []string `json:"images,omitempty"`
|
||||
SSH v1beta1.SSH `json:"ssh,omitempty"`
|
||||
}
|
||||
|
||||
// ClusterStatus defines the observed state of Cluster
|
||||
type ClusterStatus struct {
|
||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
type Status int
|
||||
|
||||
const (
|
||||
@@ -64,6 +46,40 @@ func (s Status) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// ClusterSpec defines the desired state of InfraMetadata
|
||||
type ClusterSpec struct {
|
||||
// desired state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
// Foo is an example field of Cluster. Edit Cluster_types.go to remove/update
|
||||
Infra string `json:"infra,omitempty"`
|
||||
Image v1beta1.ImageList `json:"image,omitempty"`
|
||||
SSH v1beta1.SSH `json:"ssh,omitempty"`
|
||||
Hosts []v1beta1.Host `json:"hosts,omitempty"`
|
||||
// Why env not using map[string]string
|
||||
// Because some argument is list, like: CertSANS=127.0.0.1 CertSANS=localhost, if ENV is map, will merge those two values
|
||||
// but user want to InfraMetadata a list, using array we can convert it to {CertSANS:[127.0.0.1, localhost]}
|
||||
Env []string `json:"env,omitempty"`
|
||||
// Entrypoint array. Not executed within a shell.
|
||||
// The docker image's ENTRYPOINT is used if this is not provided.
|
||||
// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
|
||||
// cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced
|
||||
// to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
|
||||
// produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless
|
||||
// of whether the variable exists or not. Cannot be updated.
|
||||
// More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
|
||||
// +optional
|
||||
Command []string `json:"command,omitempty"`
|
||||
}
|
||||
|
||||
// ClusterStatus defines the observed state of Cluster
|
||||
type ClusterStatus struct {
|
||||
Phase v1beta1.ClusterPhase `json:"phase,omitempty"`
|
||||
Mounts []v1beta1.MountImage `json:"mounts,omitempty"`
|
||||
Conditions []v1beta1.ClusterCondition `json:"conditions,omitempty" `
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
//+kubebuilder:subresource:status
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ limitations under the License.
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/labring/sealos/pkg/types/v1beta1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
@@ -30,8 +31,8 @@ func (in *Cluster) DeepCopyInto(out *Cluster) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
out.Status = in.Status
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster.
|
||||
@@ -87,6 +88,29 @@ func (in *ClusterList) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
|
||||
*out = *in
|
||||
if in.Image != nil {
|
||||
in, out := &in.Image, &out.Image
|
||||
*out = make(v1beta1.ImageList, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
out.SSH = in.SSH
|
||||
if in.Hosts != nil {
|
||||
in, out := &in.Hosts, &out.Hosts
|
||||
*out = make([]v1beta1.Host, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Env != nil {
|
||||
in, out := &in.Env, &out.Env
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Command != nil {
|
||||
in, out := &in.Command, &out.Command
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec.
|
||||
@@ -102,6 +126,20 @@ func (in *ClusterSpec) DeepCopy() *ClusterSpec {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) {
|
||||
*out = *in
|
||||
if in.Mounts != nil {
|
||||
in, out := &in.Mounts, &out.Mounts
|
||||
*out = make([]v1beta1.MountImage, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1beta1.ClusterCondition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterStatus.
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
package applier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
cv1 "github.com/labring/sealos/controllers/cluster/api/v1"
|
||||
v1 "github.com/labring/sealos/controllers/infra/api/v1"
|
||||
"github.com/labring/sealos/controllers/infra/common"
|
||||
"github.com/labring/sealos/pkg/ssh"
|
||||
"github.com/labring/sealos/pkg/types/v1beta1"
|
||||
)
|
||||
|
||||
type Applier struct{}
|
||||
|
||||
func NewApplier() Reconcile {
|
||||
return &Applier{}
|
||||
}
|
||||
|
||||
type Reconcile interface {
|
||||
ReconcileCluster(infra *v1.Infra, hosts []v1.Hosts, cluster *cv1.Cluster) error
|
||||
}
|
||||
|
||||
type IP struct {
|
||||
PrivateIP string
|
||||
PublicIP string
|
||||
}
|
||||
|
||||
func (a *Applier) ReconcileCluster(infra *v1.Infra, hosts []v1.Hosts, cluster *cv1.Cluster) error {
|
||||
//todo 在job中去做,能够实现fmt打印导出到前端
|
||||
master := make([]IP, 0)
|
||||
node := make([]IP, 0)
|
||||
images := getImages(cluster)
|
||||
|
||||
for i := range infra.Spec.Hosts {
|
||||
if i >= len(hosts) || infra.Spec.Hosts[i].Count != hosts[i].Count {
|
||||
return fmt.Errorf("there is no expected number of instances")
|
||||
}
|
||||
if getRoleMaster(hosts[i].Roles) {
|
||||
for _, j := range hosts[i].Metadata {
|
||||
master = append(master, IP{getPrivateIP(j.IP), getPublicIP(j.IP)})
|
||||
}
|
||||
} else {
|
||||
for _, j := range hosts[i].Metadata {
|
||||
node = append(node, IP{getPrivateIP(j.IP), getPublicIP(j.IP)})
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(master) == 0 {
|
||||
return fmt.Errorf("zero master")
|
||||
}
|
||||
|
||||
ssh, err := ssh.NewSSHByCluster(&v1beta1.Cluster{Spec: v1beta1.ClusterSpec{SSH: cluster.Spec.SSH}}, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ssh create fail %v", err)
|
||||
}
|
||||
passwd := cluster.Spec.SSH.Passwd
|
||||
if len(node) == 0 {
|
||||
if len(master) == 1 {
|
||||
// 1 master and zero node -> single
|
||||
if err = makeClusterSingle(ssh, master[0].PublicIP, images); err != nil {
|
||||
return fmt.Errorf("make cluster single error :%v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("zero node but more than 1 master")
|
||||
}
|
||||
|
||||
// >= 1 master and >= 1 node
|
||||
sealos := master[0].PublicIP
|
||||
for i := 0; i < len(master); i++ {
|
||||
ip := master[i].PublicIP
|
||||
if err = makeClusterMaster(ssh, ip, passwd, "master"+strconv.Itoa(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(node); i++ {
|
||||
ip := node[i].PublicIP
|
||||
if err = makeClusterNode(ssh, ip, passwd, "node"+strconv.Itoa(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ipMasters, ipNodes := getIPs(master), getIPs(node)
|
||||
|
||||
//sealos run on master
|
||||
cmdSealosRun := fmt.Sprintf("sudo sealos run %s --masters %s --nodes %s --passwd %s", images, ipMasters, ipNodes, passwd)
|
||||
log.Println(cmdSealosRun)
|
||||
if err = ssh.CmdAsync(sealos, cmdSealosRun); err != nil {
|
||||
return fmt.Errorf("execute ssh commend %s,fail:%v", cmdSealosRun, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeClusterSingle(ssh ssh.Interface, ip, images string) error {
|
||||
if err := downLoadSealos(ssh, ip); err != nil {
|
||||
return err
|
||||
}
|
||||
//sealos run on single
|
||||
cmdSealosRun := fmt.Sprintf("sudo sealos run %s --single", images)
|
||||
if err := ssh.CmdAsync(ip, cmdSealosRun); err != nil {
|
||||
return fmt.Errorf("execute commend: %s,error: %v", cmdSealosRun, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeClusterNode configure ssh and root password
|
||||
func makeClusterNode(ssh ssh.Interface, ip, passwd, role string) error {
|
||||
if err := setRootPasswd(ssh, ip, passwd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setHostNameRole(ssh, ip, role); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := modifySSHConfig(ssh, ip); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeClusterMaster(ssh ssh.Interface, ip, passwd, role string) error {
|
||||
if err := makeClusterNode(ssh, ip, passwd, role); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := downLoadSealos(ssh, ip); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downLoadSealos(ssh ssh.Interface, ip string) error {
|
||||
cmd := "sudo wget -c https://github.com/labring/sealos/releases/download/v4.1.3/sealos_4.1.3_linux_amd64.tar.gz && sudo tar zxvf sealos_4.1.3_linux_amd64.tar.gz sealos && sudo rm -rf sealos_4.1.3_linux_amd64.tar.gz && sudo chmod +x sealos && sudo mv sealos /usr/bin"
|
||||
if err := ssh.CmdAsync(ip, cmd); err != nil {
|
||||
return fmt.Errorf("execute commend: %s,error: %v", cmd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func modifySSHConfig(ssh ssh.Interface, ip string) error {
|
||||
cmd := "sudo sed -i '63c PasswordAuthentication yes' /etc/ssh/sshd_config && sudo sed -i 's/# StrictHostKeyChecking ask/StrictHostKeyChecking no/' /etc/ssh/ssh_config && sudo systemctl restart sshd"
|
||||
if err := ssh.CmdAsync(ip, cmd); err != nil {
|
||||
return fmt.Errorf("execute commend: %s,error: %v", cmd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRootPasswd(ssh ssh.Interface, ip string, passwd string) error {
|
||||
cmd := fmt.Sprintf("sudo echo -e %s\"\\n\"%s | sudo passwd root", passwd, passwd)
|
||||
if err := ssh.CmdAsync(ip, cmd); err != nil {
|
||||
return fmt.Errorf("execute commend: %s,error: %v", cmd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setHostNameRole(ssh ssh.Interface, ip string, role string) error {
|
||||
cmd := fmt.Sprintf("sudo hostnamectl set-hostname %s", role)
|
||||
if err := ssh.CmdAsync(ip, cmd); err != nil {
|
||||
return fmt.Errorf("execute commend: %s,error: %v", cmd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImages(cluster *cv1.Cluster) string {
|
||||
images := ""
|
||||
for _, image := range cluster.Spec.Images {
|
||||
images += image + " "
|
||||
}
|
||||
return images
|
||||
}
|
||||
|
||||
func getIPs(ips []IP) string {
|
||||
ip := ""
|
||||
for i := 0; i < len(ips); i++ {
|
||||
if i == len(ips)-1 {
|
||||
ip = ip + ips[i].PrivateIP
|
||||
break
|
||||
}
|
||||
ip = ip + ips[i].PrivateIP + ","
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
func getRoleMaster(Roles []string) bool {
|
||||
for _, j := range Roles {
|
||||
if j == "master" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getPrivateIP(ips []v1.IPAddress) string {
|
||||
for _, ip := range ips {
|
||||
if ip.IPType == common.IPTypePrivate {
|
||||
return ip.IPValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getPublicIP(ips []v1.IPAddress) string {
|
||||
for _, ip := range ips {
|
||||
if ip.IPType == common.IPTypePublic {
|
||||
return ip.IPValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package applier
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
cv1 "github.com/labring/sealos/controllers/cluster/api/v1"
|
||||
v1 "github.com/labring/sealos/controllers/infra/api/v1"
|
||||
"github.com/labring/sealos/pkg/types/v1beta1"
|
||||
)
|
||||
|
||||
func TestApplier_ReconcileCluster(t *testing.T) {
|
||||
type args struct {
|
||||
infra *v1.Infra
|
||||
hosts []v1.Hosts
|
||||
cluster *cv1.Cluster
|
||||
}
|
||||
hosts := []v1.Hosts{
|
||||
{
|
||||
Roles: []string{"master"},
|
||||
Metadata: []v1.Metadata{},
|
||||
},
|
||||
}
|
||||
cluster := &cv1.Cluster{
|
||||
Spec: cv1.ClusterSpec{
|
||||
Images: []string{"labring/kubernetes:v1.24.0", "labring/calico:v3.22.1"},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"test for apply cluster",
|
||||
args{
|
||||
infra: &v1.Infra{Spec: v1.InfraSpec{SSH: v1beta1.SSH{
|
||||
User: "ec2-user",
|
||||
Passwd: "123456",
|
||||
Pk: "/root/hurz_key.pem",
|
||||
Port: uint16(22),
|
||||
}}},
|
||||
hosts: hosts,
|
||||
cluster: cluster,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &Applier{}
|
||||
if err := a.ReconcileCluster(tt.args.infra, tt.args.hosts, tt.args.cluster); (err != nil) != tt.wantErr {
|
||||
t.Errorf("ReconcileCluster() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -33,15 +33,55 @@ spec:
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: ClusterSpec defines the desired state of Cluster
|
||||
description: ClusterSpec defines the desired state of InfraMetadata
|
||||
properties:
|
||||
images:
|
||||
command:
|
||||
description: 'Entrypoint array. Not executed within a shell. The docker
|
||||
image''s ENTRYPOINT is used if this is not provided. Variable references
|
||||
$(VAR_NAME) are expanded using the container''s environment. If
|
||||
a variable cannot be resolved, the reference in the input string
|
||||
will be unchanged. Double $$ are reduced to a single $, which allows
|
||||
for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce
|
||||
the string literal "$(VAR_NAME)". Escaped references will never
|
||||
be expanded, regardless of whether the variable exists or not. Cannot
|
||||
be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
env:
|
||||
description: 'Why env not using map[string]string Because some argument
|
||||
is list, like: CertSANS=127.0.0.1 CertSANS=localhost, if ENV is
|
||||
map, will merge those two values but user want to InfraMetadata
|
||||
a list, using array we can convert it to {CertSANS:[127.0.0.1, localhost]}'
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
hosts:
|
||||
items:
|
||||
properties:
|
||||
env:
|
||||
description: overwrite env
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
ips:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
roles:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
image:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
infra:
|
||||
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
Important: Run "make" to regenerate code after modifying this file'
|
||||
description: 'desired state of cluster Important: Run "make" to regenerate
|
||||
code after modifying this file Foo is an example field of Cluster.
|
||||
Edit Cluster_types.go to remove/update'
|
||||
type: string
|
||||
ssh:
|
||||
properties:
|
||||
@@ -49,12 +89,12 @@ spec:
|
||||
type: string
|
||||
pk:
|
||||
type: string
|
||||
pkData:
|
||||
type: string
|
||||
pkName:
|
||||
type: string
|
||||
pkPasswd:
|
||||
type: string
|
||||
pkdata:
|
||||
type: string
|
||||
pkname:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
user:
|
||||
@@ -64,9 +104,65 @@ spec:
|
||||
status:
|
||||
description: ClusterStatus defines the observed state of Cluster
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: ClusterCondition describes the state of a cluster at
|
||||
a certain point.
|
||||
properties:
|
||||
lastHeartbeatTime:
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
reason:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
mounts:
|
||||
items:
|
||||
properties:
|
||||
cmd:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
entrypoint:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
env:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
imageName:
|
||||
type: string
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
mountPoint:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- imageName
|
||||
- mountPoint
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
phase:
|
||||
type: string
|
||||
status:
|
||||
description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
|
||||
of cluster Important: Run "make" to regenerate code after modifying
|
||||
description: 'Important: Run "make" to regenerate code after modifying
|
||||
this file'
|
||||
type: string
|
||||
type: object
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
apiVersion: infra.sealos.io/v1
|
||||
kind: Infra
|
||||
metadata:
|
||||
name: infra-apply-test
|
||||
spec:
|
||||
ssh:
|
||||
user: ec2-user
|
||||
passwd: "123456"
|
||||
pk: /root/hurz_key.pem
|
||||
pkname: hurz_key
|
||||
port: 22
|
||||
hosts:
|
||||
- roles: [master] # required
|
||||
count: 1 # Required
|
||||
flavor: "t2.large"
|
||||
image: "ami-05248307900d52e3a"
|
||||
- roles: [ node ] # required
|
||||
count: 1 # Required
|
||||
flavor: "t2.medium"
|
||||
image: "ami-05248307900d52e3a"
|
||||
---
|
||||
#apiVersion: infra.sealos.io/v1
|
||||
#kind: Infra
|
||||
#metadata:
|
||||
# name: infra-apply-test
|
||||
#spec:
|
||||
# ssh:
|
||||
# user: ec2-user
|
||||
# passwd: "123456"
|
||||
# pk: /root/hurz_key.pem
|
||||
# pkname: hurz_key
|
||||
# port: 22
|
||||
# hosts:
|
||||
# - roles: [master] # required
|
||||
# count: 1 # Required
|
||||
# flavor: "t2.large"
|
||||
# image: "ami-05248307900d52e3a"
|
||||
# - roles: [ node ] # required
|
||||
# count: 1 # Required
|
||||
# flavor: "t2.medium"
|
||||
# image: "ami-05248307900d52e3a"
|
||||
#---
|
||||
apiVersion: cluster.sealos.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: my-cluster
|
||||
spec:
|
||||
ssh:
|
||||
user: ec2-user
|
||||
passwd: "123456"
|
||||
pk: /root/hurz_key.pem
|
||||
pkname: hurz_key
|
||||
port: 22
|
||||
infra: infra-apply-test
|
||||
images:
|
||||
- labring/kubernetes:v1.24.0
|
||||
- labring/calico:v3.22.1
|
||||
image:
|
||||
- labring/kubernetes:v1.24.0
|
||||
- labring/calico:v3.22.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022.
|
||||
Copyright 2022 labring/sealos.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -19,26 +19,45 @@ package controllers
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/labring/sealos/controllers/cluster/applier"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
infracommon "github.com/labring/sealos/controllers/infra/common"
|
||||
|
||||
"github.com/labring/sealos/pkg/ssh"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/labring/sealos/pkg/types/v1beta1"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
|
||||
"k8s.io/client-go/tools/record"
|
||||
|
||||
v1 "github.com/labring/sealos/controllers/cluster/api/v1"
|
||||
infrav1 "github.com/labring/sealos/controllers/infra/api/v1"
|
||||
"github.com/labring/sealos/controllers/infra/drivers"
|
||||
"github.com/labring/sealos/pkg/utils/logger"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
v1 "github.com/labring/sealos/controllers/cluster/api/v1"
|
||||
const (
|
||||
defaultUser = "root"
|
||||
defaultSealosVersion = "4.1.4-rc1"
|
||||
)
|
||||
const (
|
||||
applyClusterfileCmd = "sealos apply -f /root/Clusterfile"
|
||||
downloadSealosCmd = `sealos version || wget https://github.com/labring/sealos/releases/download/v%s/sealos_%s_linux_amd64.tar.gz && tar -zxvf sealos_%s_linux_amd64.tar.gz sealos && chmod +x sealos && mv sealos /usr/bin`
|
||||
)
|
||||
|
||||
// ClusterReconciler reconciles a Cluster object
|
||||
type ClusterReconciler struct {
|
||||
client.Client
|
||||
driver drivers.Driver
|
||||
applier applier.Reconcile
|
||||
Scheme *runtime.Scheme
|
||||
driver drivers.Driver
|
||||
Scheme *runtime.Scheme
|
||||
recorder record.EventRecorder
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=cluster.sealos.io,resources=clusters,verbs=get;list;watch;create;update;patch;delete
|
||||
@@ -56,41 +75,131 @@ type ClusterReconciler struct {
|
||||
// For more details, check Reconcile and its Result here:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile
|
||||
func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
_ = log.FromContext(ctx)
|
||||
cluster := &v1.Cluster{}
|
||||
|
||||
if err := r.Get(ctx, req.NamespacedName, cluster); err != nil {
|
||||
logger.Debug("ignore not found cluster error: %v", err)
|
||||
r.recorder.Event(cluster, core.EventTypeWarning, "GetCluster", err.Error())
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
infra := &infrav1.Infra{}
|
||||
infra.Name = cluster.Spec.Infra
|
||||
infra.Namespace = cluster.Namespace
|
||||
|
||||
key := client.ObjectKey{Namespace: infra.Namespace, Name: infra.Name}
|
||||
if err := r.Get(ctx, key, infra); err != nil {
|
||||
logger.Debug("ignore not found cluster error: %v", err)
|
||||
r.recorder.Event(cluster, core.EventTypeWarning, "GetInfra", err.Error())
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
hosts, err := r.driver.GetInstances(infra)
|
||||
|
||||
clusterfile, err := generateClusterfile(infra, cluster)
|
||||
if err != nil {
|
||||
logger.Error("get instances error: %v", err)
|
||||
return ctrl.Result{}, err
|
||||
r.recorder.Event(cluster, core.EventTypeWarning, "GenerateClusterfile", err.Error())
|
||||
return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 60}, err
|
||||
}
|
||||
fmt.Println(clusterfile)
|
||||
if err := applyClusterfile(infra, clusterfile, getSealosVersion(cluster)); err != nil {
|
||||
r.recorder.Event(cluster, core.EventTypeWarning, "ApplyClusterfile", err.Error())
|
||||
return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 60}, err
|
||||
}
|
||||
|
||||
if cluster.Spec.SSH.PkData == "" {
|
||||
cluster.Spec.SSH.PkData = infra.Spec.SSH.PkData
|
||||
}
|
||||
err = r.applier.ReconcileCluster(infra, hosts, cluster)
|
||||
if err != nil {
|
||||
logger.Error("reconcile cluster error: %v", err)
|
||||
// update cluster status
|
||||
cluster.Status.Status = infrav1.Running.String()
|
||||
if err := r.Status().Update(ctx, cluster); err != nil {
|
||||
r.recorder.Event(cluster, core.EventTypeWarning, "UpdateClusterStatus", err.Error())
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// Get private ip from metadata
|
||||
func getPrivateIP(meta infrav1.Metadata) string {
|
||||
for _, ip := range meta.IP {
|
||||
if ip.IPType == infracommon.IPTypePrivate {
|
||||
return ip.IPValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Generate Clusterfile by infra and cluster
|
||||
func generateClusterfile(infra *infrav1.Infra, cluster *v1.Cluster) (string, error) {
|
||||
new := cluster.DeepCopy()
|
||||
new.CreationTimestamp = metav1.Time{}
|
||||
new.Spec.SSH = infra.Spec.SSH
|
||||
new.Spec.SSH.User = defaultUser
|
||||
|
||||
for _, host := range infra.Spec.Hosts {
|
||||
for _, meta := range host.Metadata {
|
||||
privateIP := getPrivateIP(meta)
|
||||
if privateIP == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
new.Spec.Hosts = append(new.Spec.Hosts, v1beta1.Host{
|
||||
IPS: []string{privateIP},
|
||||
Roles: host.Roles,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// convert cluster to yaml
|
||||
clusterfile, err := yaml.Marshal(new)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshal cluster [%s] to yaml failed: %v", new.Name, err)
|
||||
}
|
||||
|
||||
return string(clusterfile), nil
|
||||
}
|
||||
|
||||
// Get master0 public ip from infra
|
||||
func getMaster0PublicIP(infra *infrav1.Infra) string {
|
||||
for _, host := range infra.Spec.Hosts {
|
||||
for _, meta := range host.Metadata {
|
||||
for _, ip := range meta.IP {
|
||||
if ip.IPType == infracommon.IPTypePublic && host.Roles[0] == v1beta1.MASTER {
|
||||
return ip.IPValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// get sealos version from cluster
|
||||
func getSealosVersion(cluster *v1.Cluster) string {
|
||||
if v, ok := cluster.Annotations["sealos.io/sealos/version"]; ok && v != "" {
|
||||
return v
|
||||
}
|
||||
return defaultSealosVersion
|
||||
}
|
||||
|
||||
// Apply clusterfile on infra
|
||||
func applyClusterfile(infra *infrav1.Infra, clusterfile, sealosVersion string) error {
|
||||
s := &v1beta1.SSH{
|
||||
User: infra.Spec.SSH.User,
|
||||
PkData: infra.Spec.SSH.PkData,
|
||||
}
|
||||
c := ssh.NewSSHClient(s, true)
|
||||
EIP := getMaster0PublicIP(infra)
|
||||
if EIP == "" {
|
||||
return fmt.Errorf("get master0 public ip failed")
|
||||
}
|
||||
|
||||
if err := ssh.WaitSSHReady(c, 5, EIP); err != nil {
|
||||
return fmt.Errorf("wait ssh ready failed: %v", err)
|
||||
}
|
||||
|
||||
createClusterfile := fmt.Sprintf("echo '%s' > /root/Clusterfile", clusterfile)
|
||||
downloadSealos := fmt.Sprintf(downloadSealosCmd, sealosVersion, sealosVersion, sealosVersion)
|
||||
|
||||
cmds := []string{createClusterfile, downloadSealos, applyClusterfileCmd}
|
||||
if err := c.CmdAsync(EIP, cmds...); err != nil {
|
||||
return fmt.Errorf("write clusterfile to remote failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
driver, err := drivers.NewDriver()
|
||||
@@ -98,7 +207,7 @@ func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return fmt.Errorf("cluster controller new driver failed: %v", err)
|
||||
}
|
||||
r.driver = driver
|
||||
r.applier = applier.NewApplier()
|
||||
r.recorder = mgr.GetEventRecorderFor("sealos-cluster-controller")
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&v1.Cluster{}).
|
||||
|
||||
@@ -3,37 +3,38 @@ package controllers
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/labring/sealos/controllers/cluster/applier"
|
||||
"github.com/labring/sealos/controllers/infra/drivers"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
infrav1 "github.com/labring/sealos/controllers/infra/api/v1"
|
||||
|
||||
v1 "github.com/labring/sealos/controllers/cluster/api/v1"
|
||||
)
|
||||
|
||||
func TestClusterReconciler_SetupWithManager(t *testing.T) {
|
||||
func Test_generateClusterfile(t *testing.T) {
|
||||
type args struct {
|
||||
mgr ctrl.Manager
|
||||
infra *infrav1.Infra
|
||||
cluster *v1.Cluster
|
||||
}
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"test for setup",
|
||||
args{
|
||||
mgr: nil,
|
||||
},
|
||||
"test for generate clusterfile",
|
||||
args{},
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
driver, _ := drivers.NewDriver()
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &ClusterReconciler{
|
||||
driver: driver,
|
||||
applier: applier.NewApplier(),
|
||||
got, err := generateClusterfile(tt.args.infra, tt.args.cluster)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("generateClusterfile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if err := r.SetupWithManager(tt.args.mgr); (err != nil) != tt.wantErr {
|
||||
t.Errorf("SetupWithManager() error = %v, wantErr %v", err, tt.wantErr)
|
||||
if got != tt.want {
|
||||
t.Errorf("generateClusterfile() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func (r *InfraReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
}
|
||||
r.driver = driver
|
||||
r.applier = &drivers.Applier{}
|
||||
r.recorder = mgr.GetEventRecorderFor("salos-infra-controller")
|
||||
r.recorder = mgr.GetEventRecorderFor("sealos-infra-controller")
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&infrav1.Infra{}).
|
||||
|
||||
@@ -210,6 +210,7 @@ func (d Driver) CreateKeyPair(infra *v1.Infra) error {
|
||||
if infra.Spec.SSH.PkName != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
client := d.Client
|
||||
|
||||
Reference in New Issue
Block a user