Conflicts:
	install.yaml
This commit is contained in:
w-mj 2023-03-11 16:23:01 +08:00
commit 93a7210db3
No known key found for this signature in database
GPG Key ID: 3A2CB5BE2F835897
8 changed files with 210 additions and 68 deletions

View File

@ -33,11 +33,15 @@ spec:
value: "!bd832W0@CG" value: "!bd832W0@CG"
- name: ASE_KEY - name: ASE_KEY
value: A5es$&!0GeoEast value: A5es$&!0GeoEast
- name: NFS_HOST
value: 192.168.252.250
- name: NFS_PATH
value: /mnt/CGdata/ms
volumeMounts: volumeMounts:
- name: persistent-storage - name: k8s-manager-nfs-volume
mountPath: /nfs mountPath: /mnt/CGdata/ms
volumes: volumes:
- name: persistent-storage - name: k8s-manager-nfs-volume
nfs: nfs:
server: 192.168.252.250 server: 192.168.252.250
path: /mnt/CGdata/ms path: /mnt/CGdata/ms

61
main.go
View File

@ -21,21 +21,10 @@ import (
//go:embed template/* static/* //go:embed template/* static/*
var f embed.FS var f embed.FS
var jwtKey string
var aseKey string
func main() { func main() {
m.InitConfig()
m.InitClient() m.InitClient()
jwtKey = os.Getenv("JWT_KEY")
aseKey = os.Getenv("ASE_KEY")
if jwtKey == "" {
panic("No jwt key")
}
if aseKey == "" {
panic("No ase key")
}
r := gin.Default() r := gin.Default()
fp, _ := fs.Sub(f, "static") fp, _ := fs.Sub(f, "static")
@ -52,7 +41,9 @@ func main() {
r.POST("/dep", m.JWTAuthMiddleware, m.CreateDeleteDeployment) r.POST("/dep", m.JWTAuthMiddleware, m.CreateDeleteDeployment)
r.DELETE("/dep", m.JWTAuthMiddleware, m.CreateDeleteDeployment) r.DELETE("/dep", m.JWTAuthMiddleware, m.CreateDeleteDeployment)
r.GET("/login", Test) r.GET("/pod", m.JWTAuthMiddleware, m.GetPodStatus)
r.GET("/login", LoginToken)
r.Any("/test_token", TestToken) r.Any("/test_token", TestToken)
r.GET("/username", m.JWTAuthMiddleware, m.GetUsername) r.GET("/username", m.JWTAuthMiddleware, m.GetUsername)
err := r.Run(":8000") err := r.Run(":8000")
@ -76,10 +67,10 @@ func ParseCGToken(token string) (string, error) {
return "", err return "", err
} }
jwtString := AesDecryptECB(aesEncrypt, []byte(aseKey)) jwtString := AesDecryptECB(aesEncrypt, []byte(m.GetConfig().AseKey))
jwtResult, err := jwt.ParseWithClaims(string(jwtString), &CGJWTClaim{}, func(token *jwt.Token) (i interface{}, err error) { jwtResult, err := jwt.ParseWithClaims(string(jwtString), &CGJWTClaim{}, func(token *jwt.Token) (i interface{}, err error) {
return []byte(jwtKey), nil return []byte(m.GetConfig().JwtKey), nil
// return hmac.New(sha256.New, []byte(jwtKey)), nil // return hmac.New(sha256.New, []byte(jwtKey)), nil
}) })
if err != nil { if err != nil {
@ -146,7 +137,7 @@ func AesDecrypt(data []byte, key []byte) ([]byte, error) {
return crypted, nil return crypted, nil
} }
func Test(c *gin.Context) { func LoginToken(c *gin.Context) {
username := c.Request.URL.Query().Get("username") username := c.Request.URL.Query().Get("username")
var err error var err error
if username == "" { if username == "" {
@ -158,26 +149,30 @@ func Test(c *gin.Context) {
"msg": "user name error", "msg": "user name error",
}) })
} else { } else {
token, err := m.GenerateToken(username) Login(c, username)
if err != nil {
log.Error(err)
c.Abort()
return
}
log.WithField("user name", username).Info("Login success.")
c.SetCookie("Authorization", "Bearer "+token, 0, "", "", false, false)
c.Header("Authorization", "Bearer "+token)
c.Header("Cache-Control", "no-store")
redirectUrl := ""
if os.Getenv("URL_PREFIX") == "" {
redirectUrl = "/index"
} else {
redirectUrl = fmt.Sprintf("/%s/index", os.Getenv("URL_PREFIX"))
}
c.Redirect(http.StatusTemporaryRedirect, redirectUrl)
} }
} }
func Login(c *gin.Context, username string) {
token, err := m.GenerateToken(username)
if err != nil {
log.Error(err)
c.Abort()
return
}
log.WithField("user name", username).Info("Login success.")
c.SetCookie("Authorization", "Bearer "+token, 0, "", "", false, false)
c.Header("Authorization", "Bearer "+token)
c.Header("Cache-Control", "no-store")
redirectUrl := ""
if os.Getenv("URL_PREFIX") == "" {
redirectUrl = "/index"
} else {
redirectUrl = fmt.Sprintf("/%s/index", os.Getenv("URL_PREFIX"))
}
c.Redirect(http.StatusTemporaryRedirect, redirectUrl)
}
func TestToken(c *gin.Context) { func TestToken(c *gin.Context) {
token := c.Request.URL.Query().Get("cgtoken") token := c.Request.URL.Query().Get("cgtoken")
if token == "" { if token == "" {

View File

@ -37,21 +37,25 @@ func (a *AppModel) Fill() error {
return res.Error return res.Error
} }
func (a *AppModel) Update() error { func (a *AppModel) fillDefaults() {
if a.SvcName == "" { if a.SvcName == "" {
a.SvcName = "svc" a.SvcName = "svc"
} }
a.SvcName = strings.TrimPrefix(a.SvcName, a.NamePrefix()+"-") a.SvcName = strings.TrimPrefix(a.SvcName, a.NamePrefix()+"-")
if a.PersistentPath == "" {
a.PersistentPath = "/data/"
}
}
func (a *AppModel) Update() error {
a.fillDefaults()
db := DB() db := DB()
db.Save(a) db.Save(a)
return nil return nil
} }
func (a *AppModel) Create() error { func (a *AppModel) Create() error {
if a.SvcName == "" { a.fillDefaults()
a.SvcName = "svc"
}
a.SvcName = strings.TrimPrefix(a.SvcName, a.NamePrefix()+"-")
db := DB() db := DB()
db.Create(a) db.Create(a)
return nil return nil
@ -156,7 +160,7 @@ func (a *AppModel) PersistentVolumeName() string {
} }
func (a *AppModel) NFSPath() string { func (a *AppModel) NFSPath() string {
return "/data/" + a.UserName return GetConfig().NFSPath + "/" + a.UserName
} }
func (a *AppModel) PersistentVolumeClaimName() string { func (a *AppModel) PersistentVolumeClaimName() string {
@ -166,3 +170,7 @@ func (a *AppModel) PersistentVolumeClaimName() string {
func (a *AppModel) StorageClassName() string { func (a *AppModel) StorageClassName() string {
return a.NamePrefix() + "-storage-class" return a.NamePrefix() + "-storage-class"
} }
func (a *AppModel) PersistentVolumeMountName() string {
return a.NamePrefix() + "-pvm"
}

60
src/config.go Normal file
View File

@ -0,0 +1,60 @@
package k8s_manager
import (
"fmt"
"os"
"reflect"
)
type Config struct {
JwtKey string
AseKey string
DBHost string
DBUser string
DBPassword string
NFSHost string
NFSPath string
}
var config *Config
func getField(v *Config, field string) string {
r := reflect.ValueOf(v)
f := reflect.Indirect(r).FieldByName(field)
return f.String()
}
func setField(v *Config, field string, value string) {
r := reflect.ValueOf(v)
f := reflect.Indirect(r).FieldByName(field)
f.SetString(value)
}
func InitConfig() {
config = &Config{}
config.JwtKey = os.Getenv("JWT_KEY")
config.AseKey = os.Getenv("ASE_KEY")
config.DBHost = os.Getenv("DB_HOST")
config.DBUser = os.Getenv("DB_USER")
config.DBPassword = os.Getenv("DB_PASS")
config.NFSHost = os.Getenv("NFS_HOST")
config.NFSPath = os.Getenv("NFS_PATH")
mustFields := []string{"JwtKey", "AseKey", "DBHost", "DBUser", "DBPassword", "NFSHost", "NFSPath"}
optionalFields := map[string]string{}
for _, f := range mustFields {
if getField(config, f) == "" {
panic(fmt.Sprintf("field %s must be set.", f))
}
}
for f, v := range optionalFields {
if getField(config, f) == "" {
setField(config, f, v)
}
}
}
func GetConfig() *Config {
return config
}

View File

@ -49,3 +49,20 @@ func CreateDeleteDeployment(c *gin.Context) {
} }
c.JSON(http.StatusOK, gin.H{"msg": "ok"}) c.JSON(http.StatusOK, gin.H{"msg": "ok"})
} }
func GetPodStatus(c *gin.Context) {
id := c.Query("id")
if strings.TrimSpace(id) == "" {
c.JSON(http.StatusBadRequest, gin.H{"msg": "no app id"})
return
}
appId, err := strconv.Atoi(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": "wrong id"})
return
}
app := AppModel{Id: appId}
app.Fill()
pod := K.GetPodFromDeployment(app.DeploymentName())
c.JSON(http.StatusOK, pod.Status)
}

View File

@ -72,20 +72,27 @@ func (c *K8sClient) DeploymentExists(deploymentName string) bool {
} }
func (c *K8sClient) UpdateDeployment(app *AppModel) { func (c *K8sClient) UpdateDeployment(app *AppModel) {
var rep int32 = 1
labelKey, labelValue := app.GetLabel() labelKey, labelValue := app.GetLabel()
l := map[string]string{labelKey: labelValue} l := map[string]string{labelKey: labelValue}
if _, err := os.Stat(app.NFSPath()); os.IsNotExist(err) {
os.MkdirAll(app.NFSPath(), os.ModeDir)
}
podLabels := map[string]string{ podLabels := map[string]string{
labelKey: labelValue, labelKey: labelValue,
"cg-deployment": app.DeploymentName(), "cg-deployment": app.DeploymentName(),
} }
containerPorts := funk.Map(app.GetPorts(), func(port int32) v1.ContainerPort {
return v1.ContainerPort{
ContainerPort: port,
}
}).([]v1.ContainerPort)
deployment := &appv1.Deployment{ deployment := &appv1.Deployment{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: app.DeploymentName(), Name: app.DeploymentName(),
Labels: l, Labels: l,
}, },
Spec: appv1.DeploymentSpec{ Spec: appv1.DeploymentSpec{
Replicas: &rep, Replicas: UnnamedPointer(int32(1)),
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: l, MatchLabels: l,
}, },
@ -102,14 +109,30 @@ func (c *K8sClient) UpdateDeployment(app *AppModel) {
Name: "URL_PREFIX", Name: "URL_PREFIX",
Value: app.ServiceName(), Value: app.ServiceName(),
}), }),
VolumeMounts: nil, //VolumeMounts: []v1.VolumeMount{
Ports: []v1.ContainerPort{ // {
{ContainerPort: 80}, // Name: app.PersistentVolumeMountName(),
}, // ReadOnly: false,
// MountPath: app.PersistentPath,
// },
//},
Ports: containerPorts,
SecurityContext: &v1.SecurityContext{ SecurityContext: &v1.SecurityContext{
RunAsUser: UnnamedPointer(int64(0)), RunAsUser: UnnamedPointer(int64(0)),
}, },
}}, }},
//Volumes: []v1.Volume{
// {
// Name: app.PersistentVolumeMountName(),
// VolumeSource: v1.VolumeSource{
// NFS: &v1.NFSVolumeSource{
// Server: config.NFSHost,
// Path: app.NFSPath(),
// ReadOnly: false,
// },
// },
// },
//},
}, },
}, },
}, },
@ -362,7 +385,7 @@ func (c *K8sClient) CreatePersistentVolume(app *AppModel) {
StorageClassName: app.StorageClassName(), StorageClassName: app.StorageClassName(),
PersistentVolumeSource: v1.PersistentVolumeSource{ PersistentVolumeSource: v1.PersistentVolumeSource{
NFS: &v1.NFSVolumeSource{ NFS: &v1.NFSVolumeSource{
Server: "192.168.1.1", Server: GetConfig().NFSHost,
Path: app.NFSPath(), Path: app.NFSPath(),
ReadOnly: false, ReadOnly: false,
}, },

View File

@ -5,7 +5,6 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/gorm" "gorm.io/gorm"
"os"
"sync" "sync"
) )
@ -16,18 +15,9 @@ func opendb() *gorm.DB {
if instance == nil { if instance == nil {
lock.Lock() lock.Lock()
if instance == nil { if instance == nil {
db_host, ok := os.LookupEnv("DB_HOST") db_host := GetConfig().DBHost
if !ok { db_user := GetConfig().DBUser
panic("DB_HOST NOT SET") db_pass := GetConfig().DBPassword
}
db_user, ok := os.LookupEnv("DB_USER")
if !ok {
panic("DB_USER NOT SET")
}
db_pass, ok := os.LookupEnv("DB_PASS")
if !ok {
panic("DB_PASS NOT SET")
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/k8s?charset=utf8mb4&parseTime=True&loc=Local", db_user, db_pass, db_host) dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/k8s?charset=utf8mb4&parseTime=True&loc=Local", db_user, db_pass, db_host)
var err error var err error
instance, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) instance, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
@ -43,6 +33,9 @@ func opendb() *gorm.DB {
func checkdb(cnt int) *gorm.DB { func checkdb(cnt int) *gorm.DB {
ans := opendb() ans := opendb()
if ans == nil {
return nil
}
db, err := ans.DB() db, err := ans.DB()
if err != nil { if err != nil {
logrus.WithError(err).Error("Cannot get *sql.DB.") logrus.WithError(err).Error("Cannot get *sql.DB.")

View File

@ -48,8 +48,15 @@
<td> {{ item.Image }}</td> <td> {{ item.Image }}</td>
<td> {{ item.Command }}</td> <td> {{ item.Command }}</td>
<td> {{ item.Env }}</td> <td> {{ item.Env }}</td>
<td> {{ item.Status }}</td> <td>
<td> <a :href="'/' + item.ServiceDisplayName + '/'" target="_blank">{{ item.ServiceDisplayName }}</a></td> <span v-if="item.Status == 'Halt'">
{{ item.Status }}
</span>
<span v-else>
<a href="javascript:void(0)" @click="showPodStatus(item)">{{ item.Status }}</a>
</span>
</td>
<td><a :href="'/' + item.ServiceDisplayName + '/'" target="_blank">{{ item.ServiceDisplayName }}</a></td>
<td> {{ item.Ports }}</td> <td> {{ item.Ports }}</td>
<td> <td>
<div class="btn-group"> <div class="btn-group">
@ -112,7 +119,14 @@
<div class="row"> <div class="row">
<label for="app-ports" class="form-label">端口号</label> <label for="app-ports" class="form-label">端口号</label>
<input type="text" id="app-ports" class="form-control" v-model="selectApp.Ports" <input type="text" id="app-ports" class="form-control" v-model="selectApp.Ports"
placeholder="端口号,多个端口以逗号分隔"/> placeholder="端口号"/>
</div>
<div class="row">
<label for="app-ports" class="form-label">持久化路径</label>
<input type="text" id="app-persistent-path" class="form-control"
v-model="selectApp.PersistentPath"
placeholder="持久化路径"/>
</div> </div>
<div class="row"> <div class="row">
@ -136,13 +150,27 @@
<strong class="me-auto">请求失败</strong> <strong class="me-auto">请求失败</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div> </div>
<div class="toast-body"> <div class="toast-body" v-html="toastText">
{{ toastText }}
</div> </div>
</div> </div>
</div> </div>
<div class="modal" tabindex="-1" id="pod-status-modal">
<div class="modal-dialog modal-dialog-scrollable">
<div class="modal-header">
Pod详情
</div>
<div class="modal-content">{{ podStatus }}</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div> </div>
</body> </body>
@ -165,8 +193,11 @@
Comment: "", Comment: "",
Env: "", Env: "",
SvcName: "", SvcName: "",
PersistentPath: "",
Ports: "", Ports: "",
} },
timer: null,
podStatus: ""
} }
}, },
methods: { methods: {
@ -216,11 +247,22 @@
this.username = r.data.Username this.username = r.data.Username
this.apps = r.data.Apps this.apps = r.data.Apps
}) })
},
showPodStatus(app) {
axios.get("pod?id=" + app.Id).then(r => {
this.podStatus = JSON.stringify(r.data, null, 2);
(new bootstrap.Modal("#pod-status-modal")).show();
});
} }
}, },
mounted() { mounted() {
this.blankApp = JSON.parse(JSON.stringify(this.selectApp)) this.blankApp = JSON.parse(JSON.stringify(this.selectApp))
this.refresh() this.refresh()
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
setInterval(this.timer, this.refresh, 5000)
} }
}).mount("#app") }).mount("#app")