package main
|
|
import (
|
"bytes"
|
"context"
|
"crypto/tls"
|
"encoding/json"
|
"fmt"
|
"log"
|
"net/http"
|
"os"
|
"strings"
|
"time"
|
|
"golang.org/x/crypto/ssh"
|
)
|
|
type Cluster struct {
|
ID string `json:"id"`
|
Name string `json:"name"`
|
}
|
|
type ClustersResponse struct {
|
Data []Cluster `json:"data"`
|
}
|
|
type RegistrationTokenResponse struct {
|
Command string `json:"command"`
|
}
|
|
type NodeCommandResponse struct {
|
NodeCommand string `json:"nodeCommand"`
|
}
|
|
func createRegistrationToken(serverURL, bearerToken, clusterID string) (string, error) {
|
url := fmt.Sprintf("%s/v3/clusterregistrationtokens", serverURL)
|
|
payload := strings.NewReader(fmt.Sprintf(`{
|
"clusterId": "%s"
|
}`, clusterID))
|
|
req, err := http.NewRequest("POST", url, payload)
|
if err != nil {
|
return "", fmt.Errorf("failed to create registration token request: %v", err)
|
}
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", bearerToken))
|
req.Header.Set("Content-Type", "application/json")
|
|
client := createHTTPClient()
|
|
resp, err := client.Do(req)
|
if err != nil {
|
return "", fmt.Errorf("failed to create registration token: %v", err)
|
}
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusCreated {
|
return "", fmt.Errorf("failed to create registration token: unexpected status code %d", resp.StatusCode)
|
}
|
|
var tokenResp RegistrationTokenResponse
|
err = json.NewDecoder(resp.Body).Decode(&tokenResp)
|
if err != nil {
|
return "", fmt.Errorf("failed to decode registration token response: %v", err)
|
}
|
|
return tokenResp.Command, nil
|
}
|
|
func getNodeCommand(serverURL, bearerToken, clusterID string) (string, error) {
|
url := fmt.Sprintf("%s/v3/clusterregistrationtokens", serverURL)
|
|
payload := map[string]interface{}{
|
"clusterId": clusterID,
|
}
|
|
payloadBytes, err := json.Marshal(payload)
|
if err != nil {
|
return "", fmt.Errorf("failed to marshal payload: %v", err)
|
}
|
|
req, err := http.NewRequest("POST", url, bytes.NewReader(payloadBytes))
|
if err != nil {
|
return "", fmt.Errorf("failed to create node command request: %v", err)
|
}
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", bearerToken))
|
req.Header.Set("Content-Type", "application/json")
|
|
client := createHTTPClient()
|
|
resp, err := client.Do(req)
|
if err != nil {
|
return "", fmt.Errorf("failed to get node command: %v", err)
|
}
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusCreated {
|
return "", fmt.Errorf("failed to get node command: unexpected status code %d", resp.StatusCode)
|
}
|
|
var nodeCommandResponse NodeCommandResponse
|
err = json.NewDecoder(resp.Body).Decode(&nodeCommandResponse)
|
if err != nil {
|
return "", fmt.Errorf("failed to decode node command response: %v", err)
|
}
|
|
return nodeCommandResponse.NodeCommand, nil
|
}
|
|
func getClusterID(serverURL, bearerToken, clusterName string) (string, error) {
|
url := fmt.Sprintf("%s/v3/clusters", serverURL)
|
|
req, err := http.NewRequest("GET", url, nil)
|
if err != nil {
|
return "", fmt.Errorf("failed to create cluster list request: %v", err)
|
}
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", bearerToken))
|
|
client := createHTTPClient()
|
|
resp, err := client.Do(req)
|
if err != nil {
|
return "", fmt.Errorf("failed to get cluster list: %v", err)
|
}
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
return "", fmt.Errorf("failed to get cluster list: unexpected status code %d", resp.StatusCode)
|
}
|
|
// Parse the API response
|
var clustersResponse ClustersResponse
|
err = json.NewDecoder(resp.Body).Decode(&clustersResponse)
|
if err != nil {
|
return "", fmt.Errorf("failed to decode cluster list response: %v", err)
|
}
|
|
// Print cluster names
|
for _, cluster := range clustersResponse.Data {
|
if cluster.Name == clusterName {
|
fmt.Printf("Cluster ID: %s, Name: %s\n", cluster.ID, cluster.Name)
|
return cluster.ID, nil
|
}
|
}
|
|
return "", fmt.Errorf("cluster '%s' not found", clusterName)
|
}
|
|
// Create an HTTP client with insecure TLS configuration
|
func createHTTPClient() *http.Client {
|
transport := &http.Transport{
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
}
|
return &http.Client{Transport: transport}
|
}
|
|
func sshExec(nodeIP, sshUsername, sshPassword, remoteSSHCommand string, sshPort int) (string, error) {
|
// SSH 连接配置
|
config := &ssh.ClientConfig{
|
User: sshUsername,
|
Auth: []ssh.AuthMethod{
|
ssh.Password(sshPassword),
|
},
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
}
|
|
// 连接到远程服务器
|
client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", nodeIP, sshPort), config)
|
if err != nil {
|
return "", fmt.Errorf("failed to connect to node %s: %v", nodeIP, err)
|
}
|
defer client.Close()
|
|
// 创建会话
|
session, err := client.NewSession()
|
if err != nil {
|
return "", fmt.Errorf("failed to create SSH session: %v", err)
|
}
|
defer session.Close()
|
|
// 创建一个具有超时的上下文
|
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
|
defer cancel()
|
|
// 通过会话执行远程命令
|
outputChan := make(chan string)
|
errorChan := make(chan error)
|
go func() {
|
// 创建一个连接标准输入的管道
|
stdinPipe, err := session.StdinPipe()
|
if err != nil {
|
errorChan <- fmt.Errorf("failed to create stdin pipe: %v", err)
|
return
|
}
|
|
// 启动会话
|
if err := session.Start(fmt.Sprintf("sudo -SE %s", remoteSSHCommand)); err != nil {
|
errorChan <- fmt.Errorf("failed to start command: %v err:%v", remoteSSHCommand, err)
|
return
|
}
|
|
// 将密码写入标准输入管道
|
_, err = fmt.Fprintf(stdinPipe, "%s\n", sshPassword)
|
if err != nil {
|
errorChan <- fmt.Errorf("failed to write password to stdin: %v", err)
|
return
|
}
|
|
// 等待会话结束
|
if err := session.Wait(); err != nil {
|
errorChan <- fmt.Errorf("command execution failed: %v err:%v", remoteSSHCommand, err)
|
return
|
}
|
|
outputChan <- ""
|
}()
|
|
// 等待结果或超时
|
select {
|
case <-ctx.Done():
|
// 关闭会话以终止远程命令
|
session.Close()
|
// 等待会话关闭的 goroutine 结束
|
<-outputChan
|
return "", fmt.Errorf("SSH command execution timed out")
|
case err := <-errorChan:
|
return "", err
|
case <-outputChan:
|
fmt.Printf("Command: %v executed on the remote server: %s\n", remoteSSHCommand, nodeIP)
|
return "", nil
|
}
|
}
|
|
// 安装Docker
|
func installDocker(nodeIP, sshUsername, sshPassword string, sshPort int) error {
|
// 检查Docker是否已安装
|
checkCommand := "which docker"
|
_, err := sshExec(nodeIP, sshUsername, sshPassword, checkCommand, sshPort)
|
if err == nil {
|
fmt.Println("Docker is already installed on the remote server.")
|
return nil
|
}
|
|
// 安装Docker
|
installCommand := "sudo curl -fsSL https://get.docker.com -o get-docker.sh && sudo sh get-docker.sh"
|
_, err = sshExec(nodeIP, sshUsername, sshPassword, installCommand, sshPort)
|
if err != nil {
|
return fmt.Errorf("failed to install Docker on the remote server: %v", err)
|
}
|
|
fmt.Println("Docker has been installed on the remote server.")
|
return nil
|
}
|
|
// 安装kubectl
|
func installKubectl(nodeIP, sshUsername, sshPassword string, sshPort int) error {
|
// 检查kubectl是否已安装
|
checkCommand := "which kubectl"
|
_, err := sshExec(nodeIP, sshUsername, sshPassword, checkCommand, sshPort)
|
if err == nil {
|
fmt.Println("kubectl is already installed on the remote server.")
|
return nil
|
}
|
|
// 安装kubectl
|
installCommand := "sudo curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && sudo chmod +x kubectl && sudo mv kubectl /usr/local/bin/"
|
_, err = sshExec(nodeIP, sshUsername, sshPassword, installCommand, sshPort)
|
if err != nil {
|
return fmt.Errorf("failed to install kubectl on the remote server: %v", err)
|
}
|
|
fmt.Println("kubectl has been installed on the remote server.")
|
return nil
|
}
|
|
type Node struct {
|
ClusterName string `json:"clusterName"`
|
Roles []string `json:"roles"`
|
IP string `json:"ip"`
|
SSHUsername string `json:"sshUsername"`
|
SSHPassword string `json:"sshPassword"`
|
SSHPort int `json:"sshPort"`
|
}
|
|
type RancherConfig struct {
|
RancherURL string `json:"rancherURL"`
|
BearerToken string `json:"bearerToken"`
|
}
|
|
type ClusterCreateRequest struct {
|
Name string `json:"name"`
|
Driver string `json:"driver"`
|
ControlPlaneProvider string `json:"controlPlaneProvider"`
|
WorkerNodeProvider string `json:"workerNodeProvider"`
|
RancherKubernetesEngineConfig interface{} `json:"rancherKubernetesEngineConfig"`
|
// Add other cluster properties as needed
|
}
|
|
type ClusterCreateResponse struct {
|
ID string `json:"id"`
|
Name string `json:"name"`
|
// Add other response fields as needed
|
}
|
|
func createClusterData(clusterName string) []byte {
|
requestBody := ClusterCreateRequest{
|
Name: clusterName,
|
Driver: "custom",
|
ControlPlaneProvider: "external",
|
WorkerNodeProvider: "external",
|
RancherKubernetesEngineConfig: map[string]interface{}{
|
"ignoreDockerVersion": true,
|
"kubernetesVersion": "v1.20.15-rancher2-1",
|
// Add other RKE config properties as needed
|
},
|
// Add other cluster properties as needed
|
}
|
|
requestBodyBytes, err := json.Marshal(requestBody)
|
if err != nil {
|
log.Fatalf("Failed to marshal request body: %v", err)
|
}
|
|
return requestBodyBytes
|
}
|
|
// Deploy Kubernetes roles on a node using SSH
|
func deployk8sRolesOnNode(nodeIP, sshUsername, sshPassword, remoteSSHCommand string, sshPort int, roles []string) error {
|
rancherAgentInstalled, err := isRancherAgentInstalled(nodeIP, sshUsername, sshPassword, sshPort)
|
if err == nil {
|
return nil
|
}
|
|
if !rancherAgentInstalled {
|
// Add role parameters to nodeCommand
|
for _, role := range roles {
|
switch role {
|
case "etcd":
|
remoteSSHCommand += " --etcd"
|
case "controlplane":
|
remoteSSHCommand += " --controlplane"
|
case "worker":
|
remoteSSHCommand += " --worker"
|
default:
|
log.Fatalf("invalid role specified : %s", role)
|
}
|
}
|
|
_, err := sshExec(nodeIP, sshUsername, sshPassword, remoteSSHCommand, sshPort)
|
if err != nil {
|
return fmt.Errorf("failed to deploy Kubernetes roles on the remote server: %v", err)
|
}
|
return nil
|
}
|
|
func isRancherInstalled(ip, username, password string, sshPort int) (bool, error) {
|
// 检查Rancher容器是否已运行
|
checkRancherCommand := "sudo docker ps --format '{{.Image}}' | grep -q rancher/rancher:v2.5.17"
|
_, err := sshExec(ip, username, password, checkRancherCommand, sshPort)
|
if err != nil {
|
// 如果执行命令出错,则说明Rancher未安装或发生其他错误
|
return false, fmt.Errorf("failed to check Rancher installation: %v", err)
|
}
|
|
return true, nil
|
}
|
|
func isRancherAgentInstalled(ip, username, password string, sshPort int) (bool, error) {
|
// 检查Rancher容器是否已运行
|
checkRancherCommand := "sudo docker ps --format '{{.Image}}' | grep -q rancher/rancher-agent:v2.5."
|
_, err := sshExec(ip, username, password, checkRancherCommand, sshPort)
|
if err != nil {
|
// 如果执行命令出错,则说明Rancher未安装或发生其他错误
|
return false, fmt.Errorf("failed to check Rancher installation: %v", err)
|
}
|
|
return true, nil
|
}
|
|
func installRancher(ip, username, password string, sshPort int) error {
|
rancherInstalled, err := isRancherInstalled(ip, username, password, sshPort)
|
if err == nil {
|
return nil
|
}
|
|
if !rancherInstalled {
|
// 安装Rancher命令
|
rancherCommand := "sudo docker run --privileged -d --restart=unless-stopped -p 8081:80 -p 8443:443 -v /opt/rancher:/var/lib/rancher registry.cn-hangzhou.aliyuncs.com/rancher/rancher:v2.5.17"
|
_, err = sshExec(ip, username, password, rancherCommand, sshPort)
|
if err != nil {
|
return fmt.Errorf("failed to install Rancher: %v", err)
|
}
|
} else {
|
fmt.Println("Rancher is already installed on the remote server.")
|
}
|
|
return nil
|
}
|
|
func installDockerAndRancher(ip, username, password string, sshPort int) error {
|
// 安装Docker命令
|
err := installDocker(ip, username, password, sshPort)
|
if err != nil {
|
return err
|
}
|
|
// 安装Rancher命令
|
err = installRancher(ip, username, password, sshPort)
|
if err != nil {
|
return err
|
}
|
|
return nil
|
}
|
|
func createCluster(rancherConfig RancherConfig, clusterName string) error {
|
requestBody := createClusterData(clusterName)
|
|
url := fmt.Sprintf("%s/v3/clusters", rancherConfig.RancherURL)
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
|
if err != nil {
|
return fmt.Errorf("Failed to create HTTP request: %v", err)
|
}
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rancherConfig.BearerToken))
|
req.Header.Set("Content-Type", "application/json")
|
|
client := createHTTPClient()
|
|
resp, err := client.Do(req)
|
if err != nil {
|
return fmt.Errorf("Failed to send HTTP request: %v", err)
|
}
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusCreated {
|
return fmt.Errorf("Failed to create cluster, status code: %d", resp.StatusCode)
|
}
|
|
var responseBody ClusterCreateResponse
|
err = json.NewDecoder(resp.Body).Decode(&responseBody)
|
if err != nil {
|
return fmt.Errorf("Failed to decode response body: %v", err)
|
}
|
|
fmt.Printf("Cluster created: ID=%s, Name=%s\n", responseBody.ID, responseBody.Name)
|
|
return nil
|
}
|
|
func main() {
|
clusterName := "kubernetus"
|
nodes := []Node{
|
{
|
ClusterName: clusterName,
|
Roles: []string{"etcd", "controlplane", "worker"},
|
IP: "192.168.20.189",
|
SSHUsername: "basic",
|
SSHPassword: "123",
|
SSHPort: 22,
|
},
|
{
|
ClusterName: clusterName,
|
Roles: []string{"worker"},
|
IP: "192.168.20.10",
|
SSHUsername: "basic",
|
SSHPassword: "123",
|
SSHPort: 22,
|
},
|
{
|
ClusterName: clusterName,
|
Roles: []string{"worker"},
|
IP: "192.168.20.115",
|
SSHUsername: "basic",
|
SSHPassword: "alien123",
|
SSHPort: 22,
|
},
|
// Add more nodes here if needed
|
}
|
|
//install rancher on master node
|
err := installDockerAndRancher(nodes[0].IP, nodes[0].SSHUsername, nodes[0].SSHPassword, nodes[0].SSHPort)
|
if err != nil {
|
log.Fatalf("Failed to install Rancher: %v", err)
|
}
|
|
// Create the cluster
|
// Rancher configuration
|
/*rancherConfig := RancherConfig{
|
RancherURL: "https://192.168.20.119:8443",
|
BearerToken: "token-nnrsc:w68zdt8s47fnpjd5xqdl5hhzpz4j2d56kt5nx49nsswcbpdzc28kh5",
|
}*/
|
|
rancherConfig := RancherConfig{
|
RancherURL: "https://192.168.20.189:8443",
|
BearerToken: "token-t4cdf:h7zhmbvbzdvd9mmjw8zmt8rh4m7rl5gtqpqljlhl9tlr2z26j9lf4l",
|
}
|
|
// Deploy clusterId
|
clusterID, err := getClusterID(rancherConfig.RancherURL, rancherConfig.BearerToken, clusterName)
|
if err != nil {
|
log.Fatal(err)
|
err = createCluster(rancherConfig, clusterName)
|
if err != nil {
|
log.Fatalf("Failed to create cluster: %v", err)
|
}
|
fmt.Printf("Cluster created: %s\n", clusterName)
|
clusterID, err = getClusterID(rancherConfig.RancherURL, rancherConfig.BearerToken, clusterName)
|
if err != nil {
|
log.Fatal(err)
|
}
|
}
|
fmt.Println(clusterID)
|
|
// Deploy nodeCommand
|
nodeCommand, err := getNodeCommand(rancherConfig.RancherURL, rancherConfig.BearerToken, clusterID)
|
if err != nil {
|
log.Fatal(err)
|
}
|
fmt.Println(nodeCommand)
|
|
for _, node := range nodes {
|
//Deploy Docker on each node
|
err = installDocker(node.IP, node.SSHUsername, node.SSHPassword, node.SSHPort)
|
if err != nil {
|
log.Fatal(err)
|
}
|
|
// Deploy Kubectl on each node
|
err = installKubectl(node.IP, node.SSHUsername, node.SSHPassword, node.SSHPort)
|
if err != nil {
|
log.Fatal(err)
|
}
|
|
// Deploy Kubernetes roles on each node
|
err = deployk8sRolesOnNode(node.IP, node.SSHUsername, node.SSHPassword, nodeCommand, node.SSHPort, node.Roles)
|
if err != nil {
|
log.Fatal(err)
|
}
|
}
|
os.Exit(0)
|
}
|