├── docs ├── CNAME ├── cmds │ ├── bash.md │ ├── k8s │ │ ├── cni.md │ │ └── index.md │ ├── ingress.md │ ├── sidecar │ │ └── dashboard.md │ ├── etcd │ │ └── index.md │ └── hosts │ │ └── index.md ├── reduce │ ├── plugins.md │ └── index.md ├── terraform.md ├── logo.png ├── logo_txt.png ├── releases │ ├── v0.5.2.md │ ├── v0.6.2.md │ ├── v0.5.0.md │ └── index.md ├── extra.css ├── index.md └── quickstart.md ├── config ├── _testdata │ ├── containerd.conf │ └── docker.conf ├── containerd.go ├── config_test.go ├── config.go ├── etcd.go ├── docker.go └── k8s.go ├── scripts ├── mkdocs.Dockerfile ├── clean_tag.sh ├── release.sh ├── build.sh └── cicd.sh ├── .github ├── ISSUE_TEMPLATE │ ├── enhancement_zh.md │ ├── enhancement_en.md │ ├── bug_report_zh.md │ └── bug_report_en.md └── workflows │ ├── pages.yaml │ ├── release.yaml │ └── test.yaml ├── yaml ├── vik8s-api-server.conf ├── sidecars │ └── dashboard │ │ ├── user.conf │ │ └── ingress.conf ├── cni │ └── calico │ │ └── custom-resources.yaml ├── kubeadm-config.yaml └── ingress │ └── traefik.conf ├── install ├── cri │ ├── containerd │ │ └── config.go │ ├── install.go │ └── docker │ │ ├── config_test.go │ │ └── config.go ├── bases │ ├── selinux.go │ ├── firewalld.go │ ├── check.go │ ├── service.go │ ├── timestamp.go │ ├── install.go │ └── repos.go ├── k8s │ ├── taint.go │ ├── reset.go │ ├── clean.go │ └── install.go ├── paths │ └── consts.go ├── tools │ ├── vip.go │ ├── nodes.go │ └── template.go ├── cni │ ├── plugins.go │ ├── flannel.go │ ├── calico.go │ └── customer.go ├── hosts │ ├── manager_test.go │ ├── parse_test.go │ ├── parse.go │ └── managers.go ├── etcd │ ├── join.go │ └── reset.go └── repo │ ├── secret.go │ └── main.go ├── main.go ├── reduce ├── asserts │ ├── labels.go │ ├── selector.go │ ├── asserts.go │ └── meta.go ├── kube │ ├── pod │ │ ├── volumes │ │ │ ├── persistentVolumeClaim.go │ │ │ ├── hostPath.go │ │ │ ├── others.go │ │ │ ├── emptyDir.go │ │ │ ├── secret.go │ │ │ ├── configmap.go │ │ │ └── parse.go │ │ ├── hostAliases.go │ │ └── container │ │ │ ├── ports.go │ │ │ ├── envs.go │ │ │ ├── mountVolume.go │ │ │ └── parse.go │ ├── node.go │ ├── namespace.go │ ├── reduceies.go │ ├── deployment │ │ └── main.go │ ├── daemonset │ │ └── main.go │ ├── replace.go │ ├── secret.go │ ├── configmap.go │ ├── service │ │ └── service.go │ └── ingress │ │ └── ingress.go ├── apply.go ├── refs │ ├── type-handler.go │ └── type-manager.go ├── config │ ├── filters.go │ ├── token_iterator.go │ ├── chars_iterator.go │ ├── parse.go │ ├── writer.go │ └── types.go └── plugins │ └── plugin.go ├── libs ├── ssh │ ├── shell.go │ ├── make.go │ ├── cmd.go │ └── scp.go ├── utils │ ├── async.go │ ├── logger.go │ ├── io.go │ ├── ip.go │ └── errors.go └── logs │ ├── root.go │ └── logger_test.go ├── .gitignore ├── Makefile ├── cmd ├── cni.go ├── config.go ├── ingress.go ├── cri.go ├── hosts.go ├── clean.go ├── reduce.go ├── bash.go ├── root.go └── k8s.go ├── ingress ├── main.go ├── nginx.go └── traefik.go ├── certs ├── docker │ ├── cert_test.go │ └── generate.go └── kubernetes │ └── kube_test.go ├── go.mod ├── mkdocs.yml ├── Vagrantfile ├── README.MD ├── .goreleaser.yml └── playbook.yml /docs/CNAME: -------------------------------------------------------------------------------- 1 | vik8s.renzhen.la 2 | -------------------------------------------------------------------------------- /docs/cmds/bash.md: -------------------------------------------------------------------------------- 1 | # 集群统一维护 2 | -------------------------------------------------------------------------------- /docs/reduce/plugins.md: -------------------------------------------------------------------------------- 1 | # 自定义扩展简化 2 | -------------------------------------------------------------------------------- /docs/terraform.md: -------------------------------------------------------------------------------- 1 | # Terraform 支持 2 | 3 | 开发中,敬请期待。 4 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaiker/vik8s/HEAD/docs/logo.png -------------------------------------------------------------------------------- /docs/logo_txt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaiker/vik8s/HEAD/docs/logo_txt.png -------------------------------------------------------------------------------- /config/_testdata/containerd.conf: -------------------------------------------------------------------------------- 1 | containerd { 2 | version: v1.21.2; 3 | data-root: /data; 4 | strait-version: true; 5 | } 6 | -------------------------------------------------------------------------------- /docs/releases/v0.5.2.md: -------------------------------------------------------------------------------- 1 | # v0.5.2 Changed 2 | 3 | - [x] 修复hosts命令解析错误和不可覆盖问题 4 | - [x] 修复calico网络插件异常 5 | - [x] 更新文档内容 6 | 7 | -------------------------------------------------------------------------------- /config/_testdata/docker.conf: -------------------------------------------------------------------------------- 1 | docker { 2 | version: v20.1.10; 3 | data-root: /data; 4 | daemon-json: "{}"; 5 | strait-version: true; 6 | } 7 | -------------------------------------------------------------------------------- /scripts/mkdocs.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | RUN pip install mkdocs && pip install mkdocs-ivory 3 | 4 | WORKDIR /build 5 | 6 | ENTRYPOINT ["mkdocs"] 7 | CMD ["build"] 8 | -------------------------------------------------------------------------------- /docs/releases/v0.6.2.md: -------------------------------------------------------------------------------- 1 | # v0.6.1 2 | - [x] 修改部分结构支持编写terraform插件 3 | - [x] terraform plugin v0.1.0 release [ihaiker/vik8s](https://registry.terraform.io/providers/ihaiker/vik8s/latest) 4 | - [x] 部分依赖包升级 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能改进 3 | about: 提交一个功能改进方案 4 | labels: kind/feature 5 | 6 | --- 7 | 8 | 9 | 10 | #### 您希望添加什么: 11 | 12 | #### 为什么需要这个: 13 | -------------------------------------------------------------------------------- /yaml/vik8s-api-server.conf: -------------------------------------------------------------------------------- 1 | service {{.Kubeadm.ApiServer}} { 2 | selector { 3 | component: kube-apiserver; 4 | } 5 | namespace: kube-system; 6 | clusterIP: {{.Kubeadm.ApiServerVIP}}; 7 | port: 6443:6443/TCP; 8 | } -------------------------------------------------------------------------------- /docs/releases/v0.5.0.md: -------------------------------------------------------------------------------- 1 | ## v0.5.0 Change 2 | 3 | - [X] 更新docker、k8s版本支持。 4 | - [X] Docker私有仓库问题 5 | - [X] 支持非root用户安装 6 | - [X] 完整的CICD 7 | 8 | [NGX_DOCS]: https://ihaiker.github.io/ngx 9 | [NGX_REPO]: https://github.com/ihaiker/ngx 10 | -------------------------------------------------------------------------------- /install/cri/containerd/config.go: -------------------------------------------------------------------------------- 1 | package containerd 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/config" 5 | "github.com/ihaiker/vik8s/libs/ssh" 6 | ) 7 | 8 | func Install(config *config.DockerConfiguration, node *ssh.Node, china bool) { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /docs/cmds/k8s/cni.md: -------------------------------------------------------------------------------- 1 | # Kubernetes网络插件 2 | 3 | ## 支持类型 4 | 1、calico 5 | ```shell 6 | vik8s cni calico 7 | ``` 8 | 2、flannel 9 | ```shell 10 | vik8s cni flannel 11 | ``` 12 | 13 | 有关更多信息,请查阅帮助文档 14 | ```shell 15 | $ vik8s cni calico --help 16 | $ vik8s cni flannel --help 17 | ``` 18 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/cmd" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | var ( 10 | version = "v0.0.0" 11 | date = "2012-12-12 12:12:12" 12 | commit = "0000" 13 | ) 14 | 15 | func main() { 16 | rand.Seed(time.Now().UnixNano()) 17 | cmd.Execute(version, date, commit) 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement Request 3 | about: Suggest an enhancement to the ngx project 4 | labels: kind/feature 5 | 6 | --- 7 | 8 | 9 | 10 | #### What would you like to be added: 11 | 12 | #### Why is this needed: 13 | -------------------------------------------------------------------------------- /docs/releases/index.md: -------------------------------------------------------------------------------- 1 | # 版本历史 2 | 3 | ## 下一步开发计划 4 | 5 | - [ ] ngx工具类库抽离,单独成立项目。 6 | - [ ] 添加对containerd支持。 7 | - [x] [添加对Terraform工具的支持](https://terraform.io/) 8 | 9 | ## 更新历史 10 | 11 | - [v0.6.2 发布于:2021-12-23](./v0.6.2.md) 12 | - [v0.5.2 发布于:2021-11-21](./v0.5.2.md) 13 | - [v0.5.0 发布于:2021-07-01](./v0.5.0.md) 14 | - v0.4.1 15 | 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 报告BUG 3 | about: 提交使用ngx时遇到的bug. 4 | labels: kind/bug 5 | 6 | --- 7 | 8 | 9 | 10 | **发生了什么?**: 11 | 12 | **你希望什么样的结果?**: 13 | 14 | **如何重现? (尽可能最少和精确方式描述,也可以在这里放置代码和配置文件内容)**: 15 | 16 | **还有什么我们需要知道的吗?(例如:错误堆栈)**: 17 | 18 | **环境**: 19 | - vik8s Tag 版本号: 20 | -------------------------------------------------------------------------------- /install/bases/selinux.go: -------------------------------------------------------------------------------- 1 | package bases 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/ssh" 5 | ) 6 | 7 | func disableSELinuxAndSwap(node *ssh.Node) { 8 | node.Logger("disable SELinux and swap") 9 | _ = node.Sudo().Cmd("setenforce 0") 10 | _ = node.Sudo().Cmd("swapoff -a") 11 | _ = node.Sudo().Cmd(`sed -i 's/.*swap.*//' /etc/fstab`) 12 | } 13 | -------------------------------------------------------------------------------- /install/bases/firewalld.go: -------------------------------------------------------------------------------- 1 | package bases 2 | 3 | import "github.com/ihaiker/vik8s/libs/ssh" 4 | 5 | func disableFirewalld(node *ssh.Node) { 6 | _ = node.Sudo().Cmd("systemctl stop firewalld") 7 | _ = node.Sudo().Cmd("systemctl disable firewalld") 8 | _ = node.Sudo().CmdStdout("systemctl stop iptables") 9 | _ = node.Sudo().CmdStdout("systemctl disable iptables") 10 | } 11 | -------------------------------------------------------------------------------- /docs/extra.css: -------------------------------------------------------------------------------- 1 | .wy-nav-content { 2 | min-width: 100% !important; 3 | } 4 | 5 | .wy-menu-vertical header, .wy-menu-vertical p.caption { 6 | color: #55a5d9; 7 | height: 32px; 8 | line-height: 32px; 9 | padding: 0 1.618em; 10 | margin: 12px 0 0; 11 | display: block; 12 | font-weight: 700; 13 | font-size: 16px; 14 | text-transform: uppercase; 15 | white-space: nowrap; 16 | } 17 | -------------------------------------------------------------------------------- /docs/cmds/ingress.md: -------------------------------------------------------------------------------- 1 | # Ingress 安装 2 | 3 | ## Nginx/Treafik Ingress 4 | 5 | nginx: 6 | ```shell script 7 | vik8s ingress nginx --hostnetwork --node.selector kubernetes.io/hostname=node1 8 | ``` 9 | traefik: 10 | ```shell script 11 | vik8s ingress traefik --hostnetwork --node.selector kubernetes.io/hostname=node1 12 | ``` 13 | 执行命令后ingress会使用 hostNetwork方式部署在node1上。其他部署方式可以查阅帮助 `vik8s ingress nginx|traefik --help` 14 | -------------------------------------------------------------------------------- /reduce/asserts/labels.go: -------------------------------------------------------------------------------- 1 | package asserts 2 | 3 | import ( 4 | "fmt" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | func AutoLabels(meta metav1.Object, prefix string) { 9 | if len(meta.GetLabels()) > 0 { 10 | return 11 | } 12 | meta.SetLabels(map[string]string{ 13 | fmt.Sprintf("%s/name", prefix): meta.GetName(), 14 | //fmt.Sprintf("%s/kind", prefix): strings.ToLower(filepath.Ext(reflect.TypeOf(meta).String())[1:]), 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /yaml/sidecars/dashboard/user.conf: -------------------------------------------------------------------------------- 1 | namespace kubernetes-dashboard; 2 | 3 | ServiceAccount:v1 admin-user; 4 | 5 | ClusterRoleBinding:rbac.authorization.k8s.io/v1 admin-user { 6 | roleRef { 7 | apiGroup: rbac.authorization.k8s.io; 8 | kind: ClusterRole; 9 | name: cluster-admin; 10 | } 11 | subjects { 12 | kind: ServiceAccount; 13 | name: admin-user; 14 | namespace: kubernetes-dashboard; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/clean_tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tag=$1 4 | echo "delete origin branch release-$tag" 5 | git push origin --delete release-$tag 6 | if [ $? -gt 0 ]; then 7 | echo "branch not found" 8 | fi 9 | 10 | echo "delete origin tag $tag" 11 | git push --delete origin $tag 12 | if [ $? -gt 0 ]; then 13 | echo "origin tag not found" 14 | fi 15 | 16 | echo "delete local tag $tag" 17 | git tag -d $tag 18 | if [ $? -gt 0 ]; then 19 | echo "origin tag not found" 20 | fi 21 | -------------------------------------------------------------------------------- /config/containerd.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type ContainerdConfiguration struct { 4 | Version string `ngx:"version"` 5 | DataRoot string `ngx:"data-root"` 6 | RegistryMirrors []string `ngx:"registry-mirrors"` 7 | StraitVersion bool `ngx:"strait-version"` 8 | } 9 | 10 | func DefaultContainerdConfiguration() *ContainerdConfiguration { 11 | return &ContainerdConfiguration{ 12 | Version: "v1.21.2", 13 | DataRoot: "/var/lib/containerd", 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /install/k8s/taint.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/config" 6 | "github.com/ihaiker/vik8s/libs/ssh" 7 | "github.com/ihaiker/vik8s/libs/utils" 8 | ) 9 | 10 | func RemoveTaint(configure *config.Configuration, node *ssh.Node) { 11 | master := configure.Hosts.MustGet(configure.K8S.Masters[0]) 12 | err := master.CmdStdout(fmt.Sprintf("kubectl taint node %s node-role.kubernetes.io/master-", node.Hostname)) 13 | utils.Panic(err, "remove taint") 14 | } 15 | -------------------------------------------------------------------------------- /reduce/asserts/selector.go: -------------------------------------------------------------------------------- 1 | package asserts 2 | 3 | import "github.com/ihaiker/vik8s/reduce/config" 4 | 5 | func Selector(d *config.Directive, comsumer func(*config.Directive)) { 6 | if len(d.Args) == 0 { 7 | for _, body := range d.Body { 8 | comsumer(&config.Directive{ 9 | Name: body.Name, Args: body.Args, 10 | Body: body.Body, 11 | }) 12 | } 13 | } else { 14 | comsumer(&config.Directive{ 15 | Name: d.Args[0], Args: d.Args[1:], Body: d.Body, 16 | }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /reduce/kube/pod/volumes/persistentVolumeClaim.go: -------------------------------------------------------------------------------- 1 | package volumes 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/reduce/config" 5 | v1 "k8s.io/api/core/v1" 6 | ) 7 | 8 | func pvcParse(v *v1.Volume, 9 | source string, args []string, bodys config.Directives) { 10 | v.PersistentVolumeClaim = &v1.PersistentVolumeClaimVolumeSource{ 11 | ClaimName: source, ReadOnly: false, 12 | } 13 | if d := bodys.Remove("readOnly"); d != nil { 14 | v.PersistentVolumeClaim.ReadOnly = d.Args[0] == "true" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /reduce/kube/pod/volumes/hostPath.go: -------------------------------------------------------------------------------- 1 | package volumes 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "github.com/ihaiker/vik8s/reduce/config" 6 | v1 "k8s.io/api/core/v1" 7 | ) 8 | 9 | func hostPathParse(volume *v1.Volume, 10 | source string, args []string, body config.Directives) { 11 | volume.HostPath = &v1.HostPathVolumeSource{ 12 | Path: args[0], 13 | } 14 | 15 | if hp := utils.Index(args, 1); hp != "" { 16 | t := v1.HostPathType(hp) 17 | volume.HostPath.Type = &t 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libs/ssh/shell.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func (node *Node) Shell(shell string, watch StreamWatcher) error { 10 | sudo := node.isSudo() 11 | file := fmt.Sprintf("/tmp/vik8s-%s-%d.sh", time.Now().Format("2006.01.02"), rand.Int63()) 12 | if err := node.ScpContent([]byte(shell), file); err != nil { 13 | return err 14 | } 15 | if sudo { 16 | node = node.Sudo() 17 | } 18 | return node.CmdWatcher(fmt.Sprintf("sh -c 'chmod +x %s && sh -c %s'", file, file), watch) 19 | } 20 | -------------------------------------------------------------------------------- /reduce/kube/pod/hostAliases.go: -------------------------------------------------------------------------------- 1 | package pod 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/reduce/config" 5 | v1 "k8s.io/api/core/v1" 6 | ) 7 | 8 | func HostAliasesParse(d *config.Directive, spec *v1.PodSpec) { 9 | if len(d.Args) > 0 { 10 | spec.HostAliases = append(spec.HostAliases, v1.HostAlias{ 11 | IP: d.Args[0], Hostnames: d.Args[1:], 12 | }) 13 | } 14 | for _, host := range d.Body { 15 | spec.HostAliases = append(spec.HostAliases, v1.HostAlias{ 16 | IP: host.Name, Hostnames: host.Args, 17 | }) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug encountered while use ngx. 4 | labels: kind/bug 5 | 6 | --- 7 | 8 | 9 | 10 | **What happened**: 11 | 12 | **What you expected to happen**: 13 | 14 | **How to reproduce it (as minimally and precisely as possible, You can place the code and configuration file content here)**: 15 | 16 | **Anything else we need to know? (E.g. error stack)**: 17 | 18 | **Environment**: 19 | - NGX Tag Version: 20 | -------------------------------------------------------------------------------- /libs/ssh/make.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | "sync" 7 | ) 8 | 9 | func Sync(nodes []*Node, run func(i int, node *Node)) { 10 | hasError := "" 11 | gw := new(sync.WaitGroup) 12 | for i, node := range nodes { 13 | gw.Add(1) 14 | go func(i int, node *Node) { 15 | defer gw.Done() 16 | defer utils.Catch(func(err error) { 17 | hasError += fmt.Sprintf("%s %s\n", node.Host, err.Error()) 18 | }) 19 | run(i, node) 20 | }(i, node) 21 | } 22 | gw.Wait() 23 | utils.Assert(hasError == "", hasError) 24 | } 25 | -------------------------------------------------------------------------------- /reduce/kube/pod/volumes/others.go: -------------------------------------------------------------------------------- 1 | package volumes 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/reduce/config" 5 | "github.com/ihaiker/vik8s/reduce/refs" 6 | v1 "k8s.io/api/core/v1" 7 | ) 8 | 9 | func othersParse(volume *v1.Volume, 10 | volumeType, source string, args []string, body config.Directives) { 11 | 12 | switch volumeType { 13 | case "gluster": 14 | volumeType = "glusterfs" 15 | case "ceph": 16 | volumeType = "cephfs" 17 | } 18 | 19 | refs.UnmarshalItem(&volume.VolumeSource, &config.Directive{ 20 | Name: volumeType, Args: args, Body: body, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export BASE_PATH="$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )" 4 | 5 | tag=$1 6 | 7 | if [ "$tag" == "" ]; then 8 | echo "tag not found !" 9 | exit 1 10 | fi 11 | 12 | tag_commit=$(ls $BASE_PATH/../docs/releases/$tag.md) 13 | if [ "$tag_commit" == "" ]; then 14 | echo "The tag $tag.md not found" 15 | exit 1 16 | fi 17 | 18 | tag_list=$(git tag|grep $tag) 19 | if [ ! "$tag_list" == "" ]; then 20 | echo "Tag has created." 21 | exit 1 22 | fi 23 | 24 | cd $BASE_PATH/.. 25 | 26 | git tag $tag 27 | git push --progress origin master --tags 28 | -------------------------------------------------------------------------------- /docs/cmds/sidecar/dashboard.md: -------------------------------------------------------------------------------- 1 | # dashboard 安装 2 | 3 | 简单安装: 4 | ```shell script 5 | vik8s sidecars dashboard 6 | ``` 7 | 安装完成后会打印登录方式。 8 | 9 | 如果您再次安装完成后,没有记住token您可以通过命令 `vik8s sidecars dashboard` 获取。 10 | 11 | ## 参数说明 12 | 13 | | 参数 | 说明 | 14 | | --- | --- | 15 | | --enable-insecure-login| 是否启用不安全登录方式,启用之后dashboard将不再使用https方式提供服务 | 16 | | --expose | 通过nodePort方式对外提供服务,-1: 禁用, 0: 系统自动分配, >0: 指定端口方式 | 17 | | --ingress | 给dashboard添加入口,默认不添加 | 18 | | --insecure-header | 是否给入口直接添加认证token header这样,进入dashboard就不需要再次输入token了. | 19 | | --tlskey | dashboard.key 证书 | 20 | | --tlscert | dashboard.crt 证书 | -------------------------------------------------------------------------------- /reduce/kube/pod/volumes/emptyDir.go: -------------------------------------------------------------------------------- 1 | package volumes 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/reduce/config" 5 | v1 "k8s.io/api/core/v1" 6 | "k8s.io/apimachinery/pkg/api/resource" 7 | ) 8 | 9 | func emptyDirParse(volume *v1.Volume, 10 | source string, args []string, body config.Directives) { 11 | volume.EmptyDir = &v1.EmptyDirVolumeSource{} 12 | 13 | if d := body.Get("medium"); d != nil { 14 | volume.EmptyDir.Medium = v1.StorageMedium(d.Args[0]) 15 | } 16 | if d := body.Get("sizeLimit"); d != nil { 17 | q := resource.MustParse(d.Args[0]) 18 | volume.EmptyDir.SizeLimit = &q 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /reduce/apply.go: -------------------------------------------------------------------------------- 1 | package reduce 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/install/tools" 5 | "github.com/ihaiker/vik8s/libs/ssh" 6 | "github.com/ihaiker/vik8s/reduce/kube" 7 | ) 8 | 9 | func ApplyAssert(node *ssh.Node, name string, data interface{}) error { 10 | pods, err := tools.Assert(name, data) 11 | if err != nil { 12 | return err 13 | } 14 | 15 | remote := node.Vik8s("apply", name[5:len(name)-5]+".yaml") 16 | pods = kube.ParseWith(pods).Bytes() 17 | if err = node.HideLog().ScpContent(pods, remote); err != nil { 18 | return err 19 | } 20 | err = node.CmdStdout("kubectl apply -f " + remote) 21 | return err 22 | } 23 | -------------------------------------------------------------------------------- /docs/cmds/etcd/index.md: -------------------------------------------------------------------------------- 1 | # 使用外部ETCD集群 2 | 3 | ## 独立安装 4 | 5 | ### 准备主机 6 | 7 | | 主机名称 | IP地址 | 8 | | -------- | ------------ | 9 | | etcd1 | 172.16.100.11 | 10 | | etcd2 | 172.16.100.12 | 11 | | etcd3 | 172.16.100.13 | 12 | 13 | ### 初始化 14 | 15 | ```shell 16 | vik8s etcd init 172.16.100.11 172.16.100.12 172.16.100.13 17 | ``` 18 | 或者使用连续IP方式 19 | 20 | ```shell 21 | vik8s etcd init 172.16.100.11-172.16.100.13 22 | ``` 23 | 24 | 注意:关于修改节点ssh连接地址,查阅[主机访问方式管理](../hosts/index.md) 25 | 26 | ### 新节点加入 27 | 28 | ```shell 29 | vik8s etcd join 172.16.100.14 172.16.100.15 30 | ``` 31 | 32 | 如果您想独立安装ETCD集群,直接按照上面教程命令安装即可,安装后,程序会自动配置etcd到下一步kubernetes集群安装中。 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-build-*/ 3 | *.iws 4 | out/ 5 | .idea_modules/ 6 | atlassian-ide-plugin.xml 7 | com_crashlytics_export_strings.xml 8 | crashlytics.properties 9 | crashlytics-build.properties 10 | fabric.properties 11 | 12 | # Binaries for programs and plugins 13 | *.exe 14 | *.exe~ 15 | *.dll 16 | *.so 17 | *.dylib 18 | 19 | # Test binary, built with `go test -c` 20 | *.test 21 | 22 | # Output of the go coverage tool, specifically when used with LiteIDE 23 | *.out 24 | 25 | bin 26 | .vagrant 27 | site 28 | .env 29 | scripts/.vagrant.env 30 | dist 31 | 32 | terraform.tfstate 33 | terraform.tfstate.backup 34 | .terraform.lock.hcl 35 | .terraform 36 | -------------------------------------------------------------------------------- /install/paths/consts.go: -------------------------------------------------------------------------------- 1 | package paths 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | type Json map[string]interface{} 9 | 10 | var ConfigDir = os.ExpandEnv("$HOME/.vik8s") 11 | var Cloud = "default" 12 | var China = true 13 | var IsTerraform = false 14 | 15 | func Join(path ...string) string { 16 | paths := append([]string{os.ExpandEnv(ConfigDir), Cloud}, path...) 17 | outpath := filepath.Join(paths...) 18 | outpath, _ = filepath.Abs(outpath) 19 | return outpath 20 | } 21 | 22 | func Vik8sConfiguration() string { 23 | return Join("vik8s.conf") 24 | } 25 | 26 | func HostsConfiguration() string { 27 | return Join("hosts.conf") 28 | } 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help chmod build vagrant cicd mkdocs clean test 2 | 3 | help: ## 帮助信息 4 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) 5 | 6 | chmod: ## 脚本赋权 7 | chmod +x ./scripts/*.sh 8 | 9 | test: ## 测试GO代码 10 | go test ./... -failfast -cover 11 | 12 | build: chmod ## 编译程序 13 | ./scripts/build.sh 14 | 15 | vagrant: ## 启动虚拟机 16 | vagrant up 17 | 18 | cicd: ## 运行CI/CD测试¸ 19 | ./scripts/cicd.sh 20 | 21 | mkdocs: ## 构建文档 22 | docker run --rm -v `pwd`/:/build xhaiker/mkdocs:latest build -c 23 | 24 | clean: ## 清理 25 | vagrant destroy -f 26 | rm -rf ./bin .vagrant 27 | -------------------------------------------------------------------------------- /libs/utils/async.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | ) 7 | 8 | type async struct { 9 | runs []func() 10 | gw *sync.WaitGroup 11 | } 12 | 13 | func Async() *async { 14 | return &async{ 15 | runs: make([]func(), 0), 16 | gw: new(sync.WaitGroup), 17 | } 18 | } 19 | 20 | func (as *async) Add(fn interface{}, params ...interface{}) { 21 | as.gw.Add(1) 22 | go func() { 23 | defer as.gw.Done() 24 | 25 | in := make([]reflect.Value, 0) 26 | for _, param := range params { 27 | in = append(in, reflect.ValueOf(param)) 28 | } 29 | 30 | reflect.ValueOf(fn).Call(in) 31 | }() 32 | } 33 | 34 | func (as *async) Wait() { 35 | as.gw.Wait() 36 | } 37 | -------------------------------------------------------------------------------- /install/tools/vip.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "net" 6 | ) 7 | 8 | type VipFor int 9 | 10 | const ( 11 | Vik8sApiServer = iota + 1 12 | Vik8sCalicoETCD 13 | ) 14 | 15 | func GetVip(cidr string, vipFor VipFor) string { 16 | _, ipnet, err := net.ParseCIDR(cidr) 17 | utils.Panic(err, "invalid %s cidr", cidr) 18 | 19 | _, max := utils.AddressRange(ipnet) 20 | for i := 0; i < int(vipFor); i++ { 21 | max = utils.PrevIP(max) 22 | } 23 | return max.String() 24 | } 25 | 26 | func AddressRange(cidr string) (net.IP, net.IP) { 27 | _, ipnet, err := net.ParseCIDR(cidr) 28 | utils.Panic(err, "invalid %s cidr", cidr) 29 | return utils.AddressRange(ipnet) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/cni.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/install/cni" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var cniCmd = &cobra.Command{ 9 | Use: "cni", Short: "define kubernetes network interface", 10 | } 11 | 12 | func init() { 13 | for _, plugin := range cni.Plugins { 14 | name := plugin.Name() 15 | cmd := &cobra.Command{Use: name} 16 | plugin.Flags(cmd) 17 | cmd.PersistentPreRunE = configLoad(none) 18 | cmd.PersistentPostRunE = configDown(none) 19 | cmd.Run = func(cmd *cobra.Command, args []string) { 20 | master := configure.Hosts.MustGet(configure.K8S.Masters[0]) 21 | cni.Plugins.Apply(cmd, configure, master) 22 | } 23 | cniCmd.AddCommand(cmd) 24 | } 25 | rootCmd.AddCommand(cniCmd) 26 | } 27 | -------------------------------------------------------------------------------- /reduce/kube/pod/container/ports.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | var ( 11 | //ip:hostPort:containerPort/protocol 12 | portPattern = regexp.MustCompile(`(((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):)?(\d{1,5}):)?(\d{1,5})(\/(\S+))?`) 13 | ) 14 | 15 | func portParse(name, portString string) v1.ContainerPort { 16 | groups := portPattern.FindStringSubmatch(portString) 17 | containerPort, _ := strconv.Atoi(groups[5]) 18 | hostPort, _ := strconv.Atoi(groups[4]) 19 | return v1.ContainerPort{ 20 | Name: name, Protocol: v1.Protocol(strings.ToUpper(groups[7])), 21 | ContainerPort: int32(containerPort), 22 | HostPort: int32(hostPort), HostIP: groups[3], 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /install/cri/install.go: -------------------------------------------------------------------------------- 1 | package cri 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/config" 5 | "github.com/ihaiker/vik8s/install/cri/containerd" 6 | "github.com/ihaiker/vik8s/install/cri/docker" 7 | "github.com/ihaiker/vik8s/install/paths" 8 | "github.com/ihaiker/vik8s/libs/ssh" 9 | ) 10 | 11 | func Install(configure *config.Configuration, node *ssh.Node) { 12 | if configure.Docker == nil && configure.Containerd == nil { 13 | node.Logger("the runtime container interface not found, config it use docker container .") 14 | configure.Docker = config.DefaultDockerConfiguration() 15 | } 16 | 17 | if configure.IsDockerCri() { 18 | docker.Install(configure.Docker, node, paths.China) 19 | } else { 20 | containerd.Install(configure.Docker, node, paths.China) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ingress/main.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/ssh" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type Ingress interface { 9 | Name() string 10 | Description() string 11 | Flags(cmd *cobra.Command) 12 | Apply(master *ssh.Node) 13 | Delete(master *ssh.Node) 14 | } 15 | 16 | type manager []Ingress 17 | 18 | var Manager = manager{ 19 | Treafik(), Nginx(), 20 | } 21 | 22 | func (p *manager) Apply(name string, master *ssh.Node) { 23 | for _, plugin := range *p { 24 | if plugin.Name() == name { 25 | plugin.Apply(master) 26 | return 27 | } 28 | } 29 | } 30 | 31 | func (p *manager) Delete(name string, master *ssh.Node) { 32 | for _, plugin := range *p { 33 | if plugin.Name() == name { 34 | plugin.Delete(master) 35 | return 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /certs/docker/cert_test.go: -------------------------------------------------------------------------------- 1 | package dockercert 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/config" 5 | "github.com/ihaiker/vik8s/libs/logs" 6 | "github.com/ihaiker/vik8s/libs/ssh" 7 | "github.com/sirupsen/logrus" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func init() { 13 | logs.SetLevel(logrus.DebugLevel) 14 | } 15 | 16 | func TestGenerateCertificate(t *testing.T) { 17 | tmpDir := "_testdata" 18 | err := os.MkdirAll(tmpDir, os.ModePerm) 19 | defer func() { _ = os.RemoveAll(tmpDir) }() 20 | 21 | var cfg *config.DockerCertsConfiguration 22 | if cfg, err = GenerateBootstrapCertificates(tmpDir); err != nil { 23 | t.Fatal(err) 24 | } 25 | node := &ssh.Node{ 26 | Host: "10.24.0.10", 27 | } 28 | if _, _, err := GenerateServerCertificates(node, cfg); err != nil { 29 | t.Fatal(err) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ihaiker/vik8s 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/cheggaaa/pb/v3 v3.0.8 7 | github.com/fatih/color v1.13.0 8 | github.com/hashicorp/go-version v1.3.0 9 | github.com/ihaiker/cobrax v1.2.5 10 | github.com/ihaiker/ngx/v2 v2.0.6 11 | github.com/kvz/logstreamer v0.0.0-20150507115422-a635b98146f0 12 | github.com/peterh/liner v1.2.0 13 | github.com/pkg/sftp v1.11.0 14 | github.com/sirupsen/logrus v1.8.1 15 | github.com/spf13/cobra v1.3.0 16 | github.com/stretchr/testify v1.7.0 17 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 18 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect 19 | k8s.io/api v0.23.1 20 | k8s.io/apiextensions-apiserver v0.23.1 21 | k8s.io/apimachinery v0.23.1 22 | k8s.io/client-go v0.23.1 23 | sigs.k8s.io/yaml v1.2.0 24 | ) 25 | -------------------------------------------------------------------------------- /cmd/config.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | yamls "github.com/ihaiker/vik8s/yaml" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var configCmd = &cobra.Command{ 10 | Use: "config", Short: "Show yaml file used by vik8s deployment cluster", 11 | Args: cobra.ExactValidArgs(1), ValidArgs: yamls.AssetNames(), 12 | Example: "vik8s config all", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | fmt.Println(string(yamls.MustAsset(args[0]))) 15 | }, 16 | } 17 | var configNamesCmd = &cobra.Command{ 18 | Use: "names", Short: "show file names", 19 | Run: func(cmd *cobra.Command, args []string) { 20 | for _, name := range yamls.AssetNames() { 21 | fmt.Println(name) 22 | } 23 | }, 24 | } 25 | 26 | func init() { 27 | configCmd.AddCommand(configNamesCmd) 28 | rootCmd.AddCommand(configCmd) 29 | } 30 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: vik8s 2 | site_description: very easy install kubernetes 3 | site_author: Haiker 4 | site_url: https://vik8s.renzhen.la 5 | repo_url: https://github.com/ihaiker/vik8s 6 | copyright: Copyright @2021 Haiker 7 | 8 | nav: 9 | - 简介: index.md 10 | - 快速开始: quickstart.md 11 | - 命令详解: 12 | - 集群安装命令: 13 | - cmds/k8s/index.md 14 | - cmds/k8s/cni.md 15 | - cmds/hosts/index.md 16 | - cmds/etcd/index.md 17 | - cmds/bash.md 18 | - Reduce配置简化: 19 | - 简单使用: reduce/index.md 20 | - 自定义扩展简化: reduce/plugins.md 21 | - Terraform支持: terraform.md 22 | - 版本历史: releases/index.md 23 | theme: 24 | name: readthedocs 25 | highlightjs: true 26 | hljs_languages: 27 | - yaml 28 | - rust 29 | - shell 30 | - json 31 | extra_css: 32 | - extra.css 33 | -------------------------------------------------------------------------------- /reduce/refs/type-handler.go: -------------------------------------------------------------------------------- 1 | package refs 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/reduce/config" 5 | "reflect" 6 | ) 7 | 8 | type ( 9 | TypeHandler func(fieldType reflect.Type, item *config.Directive) interface{} 10 | TypeManager map[reflect.Type]TypeHandler 11 | ) 12 | 13 | func (h *TypeManager) DealWith(fieldType reflect.Type, item *config.Directive) (reflect.Value, bool) { 14 | if handler, has := (*h)[fieldType]; has { 15 | v := handler(fieldType, item) 16 | return reflect.ValueOf(v), true 17 | } 18 | return reflect.Value{}, false 19 | } 20 | 21 | func (h *TypeManager) With(fieldType reflect.Type, handler TypeHandler) *TypeManager { 22 | (*h)[fieldType] = handler 23 | if fieldType.Kind() == reflect.Ptr { 24 | (*h)[fieldType.Elem()] = handler 25 | } else { 26 | (*h)[reflect.PtrTo(fieldType)] = handler 27 | } 28 | return h 29 | } 30 | -------------------------------------------------------------------------------- /install/bases/check.go: -------------------------------------------------------------------------------- 1 | package bases 2 | 3 | import ( 4 | "github.com/hashicorp/go-version" 5 | "github.com/ihaiker/vik8s/libs/ssh" 6 | "github.com/ihaiker/vik8s/libs/utils" 7 | ) 8 | 9 | func Check(node *ssh.Node) { 10 | checkDistribution(node) 11 | setAliRepo(node) 12 | disableSELinuxAndSwap(node) 13 | disableFirewalld(node) 14 | } 15 | 16 | func checkDistribution(node *ssh.Node) { 17 | node.Logger("check distribution") 18 | v1, _ := version.NewVersion("4.1") 19 | v2, _ := version.NewVersion(node.Facts.KernelVersion) 20 | utils.Assert(v1.LessThanOrEqual(v2), "%s The kernel version is too low, please upgrade the kernel first, "+ 21 | "your current version is: %s, the minimum requirement is %s", node.Prefix(), v2.String(), v1.String()) 22 | } 23 | 24 | func InstallJQTools(node *ssh.Node) { 25 | Install("jq", "", node) 26 | Install("wget", "", node) 27 | } 28 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config_test 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/config" 5 | "github.com/stretchr/testify/suite" 6 | "testing" 7 | ) 8 | 9 | type TestConfigSuite struct { 10 | suite.Suite 11 | } 12 | 13 | func (p *TestConfigSuite) TestDocker() { 14 | cfg, err := config.Load("./_testdata/docker.conf") 15 | p.Nil(err) 16 | p.Equal("v20.1.10", cfg.Docker.Version) 17 | p.Equal("/data", cfg.Docker.DataRoot) 18 | p.Equal("{}", cfg.Docker.DaemonJson) 19 | p.True(cfg.Docker.StraitVersion) 20 | } 21 | 22 | func (p *TestConfigSuite) TestContainerd() { 23 | cfg, err := config.Load("./_testdata/containerd.conf") 24 | p.Nil(err) 25 | p.Equal("v1.21.2", cfg.Containerd.Version) 26 | p.Equal("/data", cfg.Containerd.DataRoot) 27 | p.True(cfg.Containerd.StraitVersion) 28 | } 29 | 30 | func TestConfig(t *testing.T) { 31 | suite.Run(t, new(TestConfigSuite)) 32 | } 33 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export BASE_PATH="$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )" 4 | cd $BASE_PATH/.. 5 | 6 | Version=$(git describe --tags `git rev-list --tags --max-count=1`) 7 | BuildDate=$(date +"%F %T") 8 | GitCommit=$(git rev-parse HEAD) 9 | param="-X main.version=${Version} -X main.commit=${GitCommit} -X 'main.date=${BuildDate}'" 10 | 11 | go_bindata_bin=$(which go-bindata) 12 | if [ "$go_bindata_bin" == "" ]; then 13 | echo "install go-bindata" 14 | go get -u github.com/shuLhan/go-bindata/cmd/go-bindata 15 | else 16 | echo "go-bindata installed $go_bindata_bin" 17 | fi 18 | 19 | echo "generator go bin data" 20 | go-bindata -modtime 1590460659 -pkg yamls -o yaml/assets.go -ignore .*\.go -ignore .*\.part yaml/... 21 | 22 | echo "format yaml/assets.go" 23 | go fmt yaml/assets.go 24 | 25 | go build -trimpath -ldflags "$param" -o ./bin/vik8s main.go 26 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | ![](./logo.png) ![](./logo_txt.png) 3 | 4 | 一个非常简单kubernetes高可用集群安装部署工具,支持 v1.19.+ 5 | 6 | 程序尽可能采用原生kubernetes特性不对kubernetes进行修改和面向过程模式编写,把安装过程清晰化。 7 | 8 | ## 特性 9 | 10 | - 简单快捷方便的安装方式。所有安装基本上就是一条命令 11 | - 多集群管理,方便的管理不同集群。 12 | - 统一命令管理程序,可以方便的在客户端使用一条命令在所有管理主机上运行。 13 | - 独立应用不依赖任何第三方 14 | - 可控的证书时间(默认:44年,本人的幸运数字就是4,我的地盘我任性) 15 | - 可选择性的镜像地址。默认提供国内/外**可信&安全**的镜像地址。不使用离线包和私有镜像(为啥不提供离线包?您是否还记得IOS环境侵入问题,Goolge一下吧,当然这样的话你的所有安装节点必须可以联网去下载镜像。) 16 | - 通过使用service特性和IPVS实现HA高可用,不依赖于任何第三实现。 17 | - 轻松的增加集群节点 `vik8s join -m ` 18 | - ETCD节点可单独安装和节点添加。`vik8s etcd init ...` 和 `vik8s etcd join ...` 19 | - 提供周边 安装,同样简单方便。 20 | - dashboard (1.0 后废弃) 21 | - ingress (nginx/traefik) 22 | - 【重磅推出】kubernetes reduce 命令,简化yaml配置文件。 [查看教程和实例](./reduce/index.md)。 23 | 24 | 25 | 26 | ### 快速开始: 27 | 28 | 查看文档:[Quickstart](./quickstart.md) 29 | -------------------------------------------------------------------------------- /reduce/asserts/asserts.go: -------------------------------------------------------------------------------- 1 | package asserts 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | "github.com/ihaiker/vik8s/reduce/config" 7 | "strings" 8 | ) 9 | 10 | func Assert(d *config.Directive, at bool, format string, params ...interface{}) { 11 | utils.Assert(at, "line %d, %s", d.Line, fmt.Sprintf(format, params...)) 12 | } 13 | 14 | func ArgsLen(d *config.Directive, size int) { 15 | Assert(d, len(d.Args) == size, "[%s] args len must is %d: %s", d.Name, size, strings.Join(d.Args, " ")) 16 | } 17 | 18 | func ArgsMin(d *config.Directive, size int) { 19 | Assert(d, len(d.Args) >= size, "[%s] args len must is %d: %s", d.Name, size, strings.Join(d.Args, " ")) 20 | } 21 | 22 | func ArgsRange(d *config.Directive, min, max int) { 23 | Assert(d, len(d.Args) >= min && len(d.Args) <= max, "[%s] args len must in %d,%d : %s", d.Name, min, max, strings.Join(d.Args, " ")) 24 | } 25 | -------------------------------------------------------------------------------- /cmd/ingress.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/ingress" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ingressRootCmd = &cobra.Command{ 10 | Use: "ingress", Short: "install kubernetes ingress controller", 11 | Example: "vik8s ingress nginx", 12 | } 13 | 14 | func ingressRun(cmd *cobra.Command, args []string) { 15 | master := configure.Hosts.MustGet(configure.K8S.Masters[0]) 16 | name := cmd.Name() 17 | ingress.Manager.Apply(name, master) 18 | } 19 | 20 | func init() { 21 | for _, plugin := range ingress.Manager { 22 | //install 23 | cmd := &cobra.Command{ 24 | Use: plugin.Name(), Short: utils.FirstLine(plugin.Description()), 25 | Long: plugin.Description(), Run: ingressRun, 26 | PreRunE: configLoad(none), PostRunE: configDown(none), 27 | } 28 | plugin.Flags(cmd) 29 | cmd.Flags().SortFlags = false 30 | ingressRootCmd.AddCommand(cmd) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /libs/logs/root.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "io" 6 | "os" 7 | ) 8 | 9 | var ( 10 | Debug = logrus.Debug 11 | Debugf = logrus.Debugf 12 | 13 | Info = logrus.Info 14 | Infof = logrus.Infof 15 | 16 | Warn = logrus.Warn 17 | Warnf = logrus.Warnf 18 | 19 | Error = logrus.Error 20 | Errorf = logrus.Errorf 21 | 22 | Print = logrus.Print 23 | Printf = logrus.Printf 24 | ) 25 | 26 | type messageFormatter struct { 27 | } 28 | 29 | func (m messageFormatter) Format(entry *logrus.Entry) ([]byte, error) { 30 | return []byte(entry.Message + "\n"), nil 31 | } 32 | 33 | func init() { 34 | logrus.SetReportCaller(true) 35 | logrus.SetLevel(logrus.DebugLevel) 36 | logrus.SetFormatter(&messageFormatter{}) 37 | logrus.SetOutput(os.Stdout) 38 | } 39 | 40 | func SetOutput(out io.Writer) { 41 | logrus.SetOutput(out) 42 | } 43 | func SetLevel(level logrus.Level) { 44 | logrus.SetLevel(level) 45 | } 46 | -------------------------------------------------------------------------------- /yaml/cni/calico/custom-resources.yaml: -------------------------------------------------------------------------------- 1 | # This section includes base Calico installation configuration. 2 | # For more information, see: https://docs.projectcalico.org/v3.21/reference/installation/api#operator.tigera.io/v1.Installation 3 | apiVersion: operator.tigera.io/v1 4 | kind: Installation 5 | metadata: 6 | name: default 7 | spec: 8 | # Configures Calico networking. 9 | calicoNetwork: 10 | # Note: The ipPools section cannot be modified post-install. 11 | ipPools: 12 | - blockSize: 26 13 | cidr: {{.NetworkCidr}} 14 | encapsulation: VXLANCrossSubnet 15 | natOutgoing: Enabled 16 | nodeSelector: all() 17 | 18 | --- 19 | 20 | # This section configures the Calico API server. 21 | # For more information, see: https://docs.projectcalico.org/v3.21/reference/installation/api#operator.tigera.io/v1.APIServer 22 | apiVersion: operator.tigera.io/v1 23 | kind: APIServer 24 | metadata: 25 | name: default 26 | spec: {} 27 | 28 | -------------------------------------------------------------------------------- /install/bases/service.go: -------------------------------------------------------------------------------- 1 | package bases 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/ssh" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | ) 7 | 8 | func EnableAndStartService(name string, mustRestart bool, node *ssh.Node) { 9 | node.Logger("start service %s", name) 10 | status, _ := node.Sudo().HideLog().CmdString("systemctl status " + name + " | grep 'Active:' | awk '{printf $2}'") 11 | node.Logger("the service %s status is: %s", name, status) 12 | if status == "inactive" { 13 | _ = node.Sudo().Cmd("systemctl enable " + name) 14 | } 15 | if status == "active" && mustRestart { 16 | err := node.Sudo().Cmd("systemctl stop " + name) 17 | utils.Panic(err, "stop service %s", name) 18 | } 19 | status, _ = node.Sudo().HideLog().CmdString("systemctl status " + name + " | grep 'Active:' | awk '{printf $2}'") 20 | if status != "active" { 21 | err := node.Sudo().Cmd("systemctl start " + name) 22 | utils.Panic(err, "start service %s at node(%s)", name, node.Hostname) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/utils/logger.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | func Logs(format string, args ...interface{}) { 13 | fmt.Printf(format, args...) 14 | fmt.Println() 15 | } 16 | 17 | func Line(format string, params ...interface{}) { 18 | out := bytes.NewBuffer([]byte{}) 19 | _, _ = fmt.Fprint(out, strings.Repeat("- ", 15)) 20 | _, _ = fmt.Fprintf(out, format, params...) 21 | _, _ = fmt.Fprint(out, strings.Repeat(" -", 15)) 22 | out.WriteByte('\n') 23 | _, _ = os.Stdout.Write(out.Bytes()) 24 | } 25 | 26 | func Stdout(name string) func(reader io.Reader) error { 27 | return func(reader io.Reader) error { 28 | r := bufio.NewReader(reader) 29 | for { 30 | line, isPrefix, err := r.ReadLine() 31 | if err == io.EOF { 32 | return nil 33 | } 34 | if err != nil { 35 | return err 36 | } 37 | if isPrefix { 38 | fmt.Print(string(line)) 39 | } else { 40 | fmt.Println(name, string(line)) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /reduce/kube/node.go: -------------------------------------------------------------------------------- 1 | package kube 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/reduce/asserts" 5 | "github.com/ihaiker/vik8s/reduce/config" 6 | "github.com/ihaiker/vik8s/reduce/plugins" 7 | v1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | type node struct { 12 | metav1.TypeMeta `json:",inline"` 13 | metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 14 | } 15 | 16 | var Node = plugins.ReduceHandler{ 17 | Names: []string{"node", "Node"}, Handler: func(version, prefix string, directive *config.Directive) metav1.Object { 18 | node := &node{ 19 | TypeMeta: metav1.TypeMeta{ 20 | Kind: "Node", 21 | APIVersion: v1.SchemeGroupVersion.String(), 22 | }, 23 | } 24 | asserts.Metadata(node, directive) 25 | for _, d := range directive.Body { 26 | node.Labels[d.Name] = d.Args[0] 27 | } 28 | return node 29 | }, 30 | Demo: ` 31 | node nodeName [label1=value1 ...]{ 32 | lable-l1 value-v1; 33 | lable-l2 value-v2; 34 | } 35 | `, 36 | } 37 | -------------------------------------------------------------------------------- /reduce/kube/namespace.go: -------------------------------------------------------------------------------- 1 | package kube 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/reduce/asserts" 5 | "github.com/ihaiker/vik8s/reduce/config" 6 | "github.com/ihaiker/vik8s/reduce/plugins" 7 | v1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | var Namespace = plugins.ReduceHandler{ 12 | Names: []string{"namespace", "Namespace"}, 13 | Handler: func(version, prefix string, directive *config.Directive) metav1.Object { 14 | asserts.ArgsMin(directive, 1) 15 | namespace := &v1.Namespace{ 16 | TypeMeta: metav1.TypeMeta{ 17 | Kind: "Namespace", 18 | APIVersion: v1.SchemeGroupVersion.String(), 19 | }, 20 | } 21 | asserts.Metadata(namespace.GetObjectMeta(), directive) 22 | 23 | labels := namespace.GetLabels() 24 | for _, d := range directive.Body { 25 | labels[d.Name] = d.Args[0] 26 | } 27 | namespace.SetLabels(labels) 28 | return namespace 29 | }, 30 | Demo: ` 31 | namespace name; 32 | namespace name [label1=value1 label2=label3 ...] { 33 | label-sub-1 value-sub-1; 34 | label-sub-2 value-sub-2; 35 | ... 36 | } 37 | `, 38 | } 39 | -------------------------------------------------------------------------------- /cmd/cri.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/ihaiker/cobrax" 5 | "github.com/ihaiker/vik8s/config" 6 | "github.com/ihaiker/vik8s/install/cri/docker" 7 | "github.com/ihaiker/vik8s/libs/utils" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var criCmd = &cobra.Command{ 12 | Use: "cri", Short: "defined kubernetes container runtime interface", 13 | } 14 | 15 | var dockerFlag = config.DefaultDockerConfiguration() 16 | var dockerCmd = &cobra.Command{ 17 | Use: "docker", Short: "defined kubernetes cni configure for docker", 18 | Example: "vik8s docker --tls.enable", 19 | PreRunE: configLoad(none), 20 | PostRunE: configDown(none), 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | if err := docker.Config(dockerFlag); err == nil { 23 | configure.Docker = dockerFlag 24 | } 25 | return nil 26 | }, 27 | } 28 | 29 | func init() { 30 | err := cobrax.FlagsWith(dockerCmd, cobrax.GetFlags, dockerFlag, "", "VIK8S_DOCKER") 31 | utils.Panic(err, "setting flag error") 32 | dockerCmd.Flags().SortFlags = false 33 | 34 | criCmd.AddCommand(dockerCmd) 35 | rootCmd.AddCommand(criCmd) 36 | } 37 | -------------------------------------------------------------------------------- /reduce/kube/pod/volumes/secret.go: -------------------------------------------------------------------------------- 1 | package volumes 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "github.com/ihaiker/vik8s/reduce/config" 6 | v1 "k8s.io/api/core/v1" 7 | ) 8 | 9 | func secretParse(v *v1.Volume, source string, args []string, body config.Directives) { 10 | v.Secret = &v1.SecretVolumeSource{ 11 | SecretName: source, 12 | } 13 | 14 | if d := utils.Index(args, 0); d != "" { 15 | v.Secret.DefaultMode = utils.Int32(d, 8) 16 | } 17 | if d := body.Remove("defaultModule"); d != nil { 18 | v.Secret.DefaultMode = utils.Int32(d.Args[0], 8) 19 | } 20 | 21 | setBody := func(items config.Directives) { 22 | if len(items) > 0 { 23 | for _, body := range items { 24 | name, mode := utils.Split2(body.Name, ":") 25 | path := utils.Default(body.Args, 1, name) 26 | kp := v1.KeyToPath{Key: name, Path: path} 27 | if mode != "" { 28 | kp.Mode = utils.Int32(mode, 8) 29 | } 30 | v.Secret.Items = append(v.Secret.Items, kp) 31 | } 32 | } 33 | } 34 | 35 | if items := body.Remove("items"); items != nil { 36 | setBody(items.Body) 37 | } 38 | setBody(body) 39 | } 40 | -------------------------------------------------------------------------------- /cmd/hosts.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/cobrax" 6 | hs "github.com/ihaiker/vik8s/install/hosts" 7 | "github.com/ihaiker/vik8s/libs/utils" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var _hosts_config = new(hs.Option) 12 | var hostsCmd = &cobra.Command{ 13 | Use: "hosts", Short: "Add Management Host", 14 | Long: `vik8s hosts 172.16.100.4 172.16.100.10-172.16.100.15`, 15 | Args: cobra.MinimumNArgs(1), 16 | PreRunE: configLoad(none), 17 | Run: func(cmd *cobra.Command, args []string) { 18 | _, err := configure.Hosts.Fetch(true, args...) 19 | utils.Panic(err, "add hosts") 20 | }, 21 | } 22 | 23 | var hostsListCmd = &cobra.Command{ 24 | Use: "list", Aliases: []string{"ls"}, 25 | PreRunE: configLoad(none), 26 | Run: func(cmd *cobra.Command, args []string) { 27 | for _, node := range configure.Hosts.All() { 28 | fmt.Println(node.Hostname, " ", node.Host) 29 | } 30 | }, 31 | } 32 | 33 | func init() { 34 | _ = cobrax.Flags(hostsCmd, _hosts_config, "", "VIK8S_SSH") 35 | //utils.Panic(err, "set hosts command flag error") 36 | hostsCmd.AddCommand(hostsListCmd) 37 | } 38 | -------------------------------------------------------------------------------- /certs/kubernetes/kube_test.go: -------------------------------------------------------------------------------- 1 | package kubecerts 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/config" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | const testdata = "_testdata" 13 | 14 | func TestKubernetesCerts(t *testing.T) { 15 | config := config.DefaultK8SConfiguration() 16 | defer os.RemoveAll(testdata) 17 | 18 | node := Node{ 19 | Name: "vm11", 20 | Host: "10.24.1.11", 21 | ApiServer: "vik8s-api-server", 22 | SvcCIDR: "10.96.0.0/12", 23 | CertificateValidity: time.Hour * 24 * 365 * 10, 24 | } 25 | CreatePKIAssets(config.ApiServer, filepath.Join(testdata, "pki"), node) 26 | 27 | endpoint := fmt.Sprintf("https://%s:6443", "vik8s-api-server") 28 | files := CreateWorkerKubeConfigFile(testdata, "vm09", endpoint, time.Hour) 29 | 30 | t.Log("------------ worker -------------") 31 | for k, v := range files { 32 | t.Log(k, "=", v) 33 | } 34 | 35 | t.Log("------------ nodes -------------") 36 | files = CreateJoinControlPlaneKubeConfigFiles(testdata, "vm10", endpoint, time.Hour) 37 | for k, v := range files { 38 | t.Log(k, "=", v) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /install/cni/plugins.go: -------------------------------------------------------------------------------- 1 | package cni 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/config" 5 | "github.com/ihaiker/vik8s/libs/ssh" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type Plugin interface { 10 | Name() string 11 | 12 | //Flags 为初始化添加命令行参数 13 | Flags(cmd *cobra.Command) 14 | 15 | //Apply 生成插件 16 | Apply(configure *config.Configuration, node *ssh.Node) 17 | 18 | //Clean 清楚插件内容 19 | Clean(node *ssh.Node) 20 | } 21 | 22 | type plugins []Plugin 23 | 24 | var Plugins = plugins{ 25 | NewFlannelCni(), NewCalico(), 26 | new(Customer), 27 | } 28 | 29 | func (p *plugins) Apply(cmd *cobra.Command, configure *config.Configuration, node *ssh.Node) { 30 | for _, plugin := range *p { 31 | if plugin.Name() == cmd.Use { 32 | plugin.Apply(configure, node) 33 | } 34 | } 35 | } 36 | 37 | func (p *plugins) Clean(node *ssh.Node) { 38 | _ = node.Sudo().Cmd("ifconfig | grep cni0 > /dev/null && ifconfig cni0 down") 39 | _ = node.Sudo().Cmd("ip link show | grep kube-ipvs0 && ip link delete kube-ipvs0 ") 40 | _ = node.Sudo().Cmd("ip link show | grep dummy0 && ip link delete dummy0 ") 41 | for _, plugin := range *p { 42 | plugin.Clean(node) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /reduce/kube/pod/volumes/configmap.go: -------------------------------------------------------------------------------- 1 | package volumes 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "github.com/ihaiker/vik8s/reduce/config" 6 | v1 "k8s.io/api/core/v1" 7 | ) 8 | 9 | func configMapParse(v *v1.Volume, source string, args []string, body config.Directives) { 10 | cm := &v1.ConfigMapVolumeSource{ 11 | LocalObjectReference: v1.LocalObjectReference{ 12 | Name: source, 13 | }, 14 | } 15 | 16 | if d := utils.Index(args, 0); d != "" { 17 | cm.DefaultMode = utils.Int32(d, 8) 18 | } 19 | if d := body.Remove("defaultModule"); d != nil { 20 | cm.DefaultMode = utils.Int32(d.Args[0], 8) 21 | } 22 | 23 | setBody := func(items config.Directives) { 24 | if len(items) > 0 { 25 | for _, body := range items { 26 | name, mode := utils.Split2(body.Name, ":") 27 | path := utils.Default(body.Args, 1, name) 28 | kp := v1.KeyToPath{Key: name, Path: path} 29 | if mode != "" { 30 | kp.Mode = utils.Int32(mode, 8) 31 | } 32 | cm.Items = append(cm.Items, kp) 33 | } 34 | } 35 | } 36 | 37 | if items := body.Remove("items"); items != nil { 38 | setBody(items.Body) 39 | } 40 | setBody(body) 41 | 42 | v.ConfigMap = cm 43 | } 44 | -------------------------------------------------------------------------------- /reduce/kube/reduceies.go: -------------------------------------------------------------------------------- 1 | package kube 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/reduce/config" 5 | "github.com/ihaiker/vik8s/reduce/kube/daemonset" 6 | "github.com/ihaiker/vik8s/reduce/kube/deployment" 7 | "github.com/ihaiker/vik8s/reduce/kube/ingress" 8 | "github.com/ihaiker/vik8s/reduce/kube/pod" 9 | "github.com/ihaiker/vik8s/reduce/kube/service" 10 | "github.com/ihaiker/vik8s/reduce/plugins" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | ) 13 | 14 | type sourceYaml struct { 15 | metav1.TypeMeta `json:",inline"` 16 | metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 17 | Data string 18 | } 19 | 20 | func yamlParse(version, prefix string, directive *config.Directive) metav1.Object { 21 | v := &sourceYaml{} 22 | v.Data = directive.Args[0] 23 | return v 24 | } 25 | 26 | var Yaml = plugins.ReduceHandler{Names: []string{"yaml"}, Demo: ` 27 | yaml ' 28 | --- 29 | apiVersion: v1 30 | kind: Pod 31 | .... 32 | '; 33 | `, Handler: yamlParse} 34 | 35 | var ReduceKinds = plugins.ReduceHandlers{ 36 | Namespace, Node, ConfigMap, Secret, 37 | pod.Pod, deployment.Deployment, daemonset.DaemonSet, 38 | service.Service, ingress.Ingress, Yaml, 39 | } 40 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | # 4 | 5 | boxes = { 6 | "centos7" => "centos/7", 7 | "centos8" => "roboxes/centos8", 8 | "ubuntu" => "generic/ubuntu1804" 9 | } 10 | box = ENV["box"] ? ENV["box"] : "centos8" 11 | box_name = boxes[box] 12 | 13 | nodes = { 14 | "master01" => [2, 4096, 40], 15 | "slave20" => [2, 4096, 40], 16 | "slave21" => [2, 4096, 40], 17 | } 18 | 19 | Vagrant.configure("2") do |config| 20 | nodes.each do |(name, cfg)| 21 | vcpus, memory, storage = cfg 22 | 23 | config.vm.define name do |machine| 24 | machine.vm.hostname = name 25 | machine.vm.box = box_name 26 | 27 | machine.vm.provider :vmware_esxi do |esxi| 28 | esxi.esxi_hostname = 'api.esxi.do' 29 | esxi.esxi_username = 'root' 30 | esxi.esxi_password = 'env:API_EXSI_DO' 31 | esxi.guest_numvcpus = vcpus 32 | esxi.guest_memsize = memory 33 | esxi.guest_boot_disk_size = storage 34 | esxi.local_allow_overwrite = 'True' 35 | end 36 | end 37 | end 38 | if box != "ubuntu" 39 | config.vm.provision "ansible" do |ansible| 40 | ansible.playbook = "playbook.yml" 41 | end 42 | end 43 | end 44 | 45 | # https://github.com/josenk/vagrant-vmware-esxi 46 | -------------------------------------------------------------------------------- /reduce/kube/pod/container/envs.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | v1 "k8s.io/api/core/v1" 6 | "k8s.io/apimachinery/pkg/api/resource" 7 | ) 8 | 9 | func envParse(name string, args []string) v1.EnvVar { 10 | env := v1.EnvVar{Name: name} 11 | 12 | if len(args) == 1 { 13 | env.Value = args[0] 14 | } else { 15 | env.ValueFrom = &v1.EnvVarSource{} 16 | 17 | switch args[0] { 18 | case "field": 19 | env.ValueFrom.FieldRef = &v1.ObjectFieldSelector{ 20 | FieldPath: args[1], 21 | } 22 | case "configMap", "config", "configmap": 23 | env.ValueFrom.ConfigMapKeyRef = &v1.ConfigMapKeySelector{ 24 | LocalObjectReference: v1.LocalObjectReference{ 25 | Name: args[1], 26 | }, 27 | Key: args[2], 28 | } 29 | case "secret": 30 | env.ValueFrom.SecretKeyRef = &v1.SecretKeySelector{ 31 | LocalObjectReference: v1.LocalObjectReference{ 32 | Name: args[1], 33 | }, 34 | Key: args[2], 35 | } 36 | case "resource", "res": 37 | env.ValueFrom.ResourceFieldRef = &v1.ResourceFieldSelector{ 38 | ContainerName: args[1], 39 | Resource: args[2], 40 | Divisor: resource.MustParse(utils.Default(args, 3, "1")), 41 | } 42 | } 43 | } 44 | return env 45 | } 46 | -------------------------------------------------------------------------------- /reduce/config/filters.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "regexp" 4 | 5 | type CharFilter func(current, previous string) bool 6 | 7 | func (self CharFilter) And(cf ...CharFilter) CharFilter { 8 | return func(current, previous string) bool { 9 | if !self(current, previous) { 10 | return false 11 | } 12 | for _, filter := range cf { 13 | if !filter(current, previous) { 14 | return false 15 | } 16 | } 17 | return true 18 | } 19 | } 20 | 21 | func (self CharFilter) Or(cf ...CharFilter) CharFilter { 22 | return func(current, previous string) bool { 23 | out := self(current, previous) 24 | for _, filter := range cf { 25 | out = out || filter(current, previous) 26 | } 27 | return out 28 | } 29 | } 30 | 31 | var ( 32 | vailCharRegexp = regexp.MustCompile("\\S") 33 | ValidChars CharFilter = func(current, previous string) bool { 34 | return vailCharRegexp.MatchString(current) 35 | } 36 | 37 | In = func(chars ...string) CharFilter { 38 | return func(current, previous string) bool { 39 | for _, char := range chars { 40 | if char == current { 41 | return true 42 | } 43 | } 44 | return false 45 | } 46 | } 47 | 48 | Not = func(cf CharFilter) CharFilter { 49 | return func(current, previous string) bool { 50 | return !cf(current, previous) 51 | } 52 | } 53 | ) 54 | -------------------------------------------------------------------------------- /libs/logs/logger_test.go: -------------------------------------------------------------------------------- 1 | package logs_test 2 | 3 | import ( 4 | "bytes" 5 | "github.com/ihaiker/vik8s/libs/logs" 6 | "github.com/sirupsen/logrus" 7 | "github.com/stretchr/testify/suite" 8 | "math/rand" 9 | "os" 10 | "strconv" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | type testLogsSuite struct { 16 | suite.Suite 17 | } 18 | 19 | func TestLogs(t *testing.T) { 20 | rand.Seed(time.Now().Unix()) 21 | suite.Run(t, new(testLogsSuite)) 22 | } 23 | 24 | func randomString() string { 25 | return strconv.FormatFloat(rand.Float64(), 10, 1, 64) 26 | } 27 | 28 | func (t *testLogsSuite) TestRoot() { 29 | out := bytes.NewBufferString("") 30 | logs.SetOutput(out) 31 | logs.SetLevel(logrus.DebugLevel) 32 | 33 | rs := randomString() 34 | logs.Debug(rs) 35 | t.Contains(out.String(), rs) 36 | out.Reset() 37 | 38 | logs.SetLevel(logrus.InfoLevel) 39 | rs = randomString() 40 | logs.Debug(rs) 41 | t.NotContains(out.String(), rs) 42 | out.Reset() 43 | 44 | logs.SetOutput(os.Stdout) 45 | logs.Info("test") 46 | logs.Warn("test") 47 | logs.Error("test") 48 | } 49 | 50 | func (t *testLogsSuite) TestOut() { 51 | out := bytes.NewBuffer([]byte{}) 52 | logs.SetOutput(out) 53 | logs.SetLevel(logrus.DebugLevel) 54 | 55 | logs.Debug("test") 56 | logs.Info("test") 57 | logs.Warn("test") 58 | logs.Error("test") 59 | 60 | t.T().Log(out.String()) 61 | } 62 | -------------------------------------------------------------------------------- /scripts/cicd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | export SCRIPTS_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd -P)" 5 | cd $SCRIPTS_PATH/.. 6 | 7 | vik8s() { 8 | ./bin/vik8s $@ 9 | } 10 | 11 | machine_install(){ 12 | box=$1 vagrant up 13 | } 14 | machine_destroy(){ 15 | vagrant destroy -f 16 | } 17 | 18 | cluster_install() { 19 | echo "start install kubernetes clusters." 20 | vik8s init master01 21 | vik8s join slave20 slave21 22 | # vik8s cni calico 23 | } 24 | 25 | k8s_clean() { 26 | vik8s reset all 27 | vik8s clean all --force 28 | } 29 | 30 | add_host() { 31 | echo "add hosts config: run user $1" 32 | } 33 | 34 | test_plan() { 35 | box_name=$1 36 | run_user=$2 37 | echo " -------------- remove config root directory -------------------" 38 | rm -rf ~/.vik8s/default 39 | 40 | echo "-------- $run_user test in $box_name start --------" 41 | echo "vagrant setup" 42 | time machine_install $box_name 43 | time add_host $run_user 44 | time cluster_install 45 | # time k8s_clean 46 | # time machine_destroy 47 | echo "-------- $run_user test in $box_name end --------" 48 | } 49 | 50 | echo " ----------- build ------------- " 51 | . $SCRIPTS_PATH/build.sh 52 | 53 | test_plan "centos8" "root" 54 | #test_plan "centos7" "root" 55 | #test_plan "centos8" "vagrant" 56 | #test_plan "centos7" "vagrant" 57 | #test_plain "ubuntu" "vagrant" 58 | -------------------------------------------------------------------------------- /install/bases/timestamp.go: -------------------------------------------------------------------------------- 1 | package bases 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/libs/ssh" 6 | "github.com/ihaiker/vik8s/libs/utils" 7 | "path/filepath" 8 | ) 9 | 10 | func InstallTimeServices(node *ssh.Node, timezone string, timeServices ...string) { 11 | err := node.Sudo().Cmd("rm -f /etc/localtime") 12 | utils.Panic(err, "set timezone") 13 | 14 | err = node.Sudo().Cmd(fmt.Sprintf("cp -f %s /etc/localtime", 15 | filepath.Join("/usr/share/zoneinfo", timezone))) 16 | utils.Panic(err, "set timezone") 17 | 18 | if node.Facts.MajorVersion == "7" { 19 | //fixbug 必须指定版本号,不然如何用户含有自己的repo会导致安装低版本出现问题 20 | Install("chrony", "3.4", node) 21 | } else { 22 | Install("chrony", "", node) 23 | } 24 | 25 | config := "allow all\n" 26 | for _, service := range timeServices { 27 | config += fmt.Sprintf("server %s iburst\n", service) 28 | } 29 | config += "\nlocal stratum 10\n" 30 | 31 | err = node.Sudo().ScpContent([]byte(config), "/etc/chrony.conf") 32 | utils.Panic(err, "send ntp config") 33 | 34 | err = node.Sudo().Cmd(fmt.Sprintf("timedatectl set-timezone %s", timezone)) 35 | utils.Panic(err, "set timezone") 36 | 37 | err = node.Sudo().Cmd("timedatectl set-ntp true") 38 | utils.Panic(err, "set timezone") 39 | 40 | err = node.Sudo().Cmd("chronyc -a makestep") 41 | utils.Panic(err, "set timezone") 42 | 43 | EnableAndStartService("chronyd", true, node) 44 | } 45 | -------------------------------------------------------------------------------- /install/hosts/manager_test.go: -------------------------------------------------------------------------------- 1 | package hosts_test 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/install/hosts" 5 | "github.com/ihaiker/vik8s/install/paths" 6 | "github.com/ihaiker/vik8s/libs/ssh" 7 | "github.com/stretchr/testify/suite" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | type TestHostsSuite struct { 13 | suite.Suite 14 | cfg *hosts.Option 15 | } 16 | 17 | func (t *TestHostsSuite) SetupTest() { 18 | paths.ConfigDir = "./_testdata" 19 | t.TearDownTest() //remove config folder 20 | } 21 | 22 | func (t TestHostsSuite) TearDownTest() { 23 | _ = os.RemoveAll(paths.ConfigDir) 24 | } 25 | 26 | func (t TestHostsSuite) TestGetOrAdd() { 27 | cfg := paths.HostsConfiguration() 28 | manager, err := hosts.New(cfg, hosts.Option{ 29 | Port: 22, 30 | User: "root", 31 | PrivateKey: "$HOME/.ssh/id_rsa", 32 | }) 33 | t.Nil(err, "初始化 hosts.conf错误") 34 | 35 | node := &ssh.Node{ 36 | Host: "10.24.0.10", 37 | Port: 22, 38 | User: "root", 39 | Password: "", 40 | PrivateKey: "", 41 | Passphrase: "", 42 | Hostname: "master", 43 | Proxy: "", 44 | ProxyNode: nil, 45 | Facts: ssh.Facts{}, 46 | } 47 | _ = manager.Add(node) 48 | 49 | node = manager.Get("10.24.0.10") 50 | t.NotNil(node) 51 | 52 | node = manager.Get("master") 53 | t.NotNil(node) 54 | } 55 | 56 | func TestHosts(t *testing.T) { 57 | suite.Run(t, new(TestHostsSuite)) 58 | } 59 | -------------------------------------------------------------------------------- /install/etcd/join.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/config" 5 | "github.com/ihaiker/vik8s/install/bases" 6 | "github.com/ihaiker/vik8s/install/cri" 7 | "github.com/ihaiker/vik8s/libs/ssh" 8 | "github.com/ihaiker/vik8s/libs/utils" 9 | ) 10 | 11 | func JoinCluster(configure *config.Configuration, node *ssh.Node) { 12 | bases.Check(node) 13 | cri.Install(configure, node) 14 | image := pullContainerImage(configure, node) 15 | 16 | removeEtcdMember(configure, node) 17 | cleanEtcdData(configure, node) 18 | 19 | makeAndPushCerts(configure, node) 20 | addEtcdMember(configure, node) 21 | joinEtcd(configure, node, image) 22 | waitEtcdReady(node) 23 | showClusterStatus(node) 24 | } 25 | 26 | func joinEtcd(configure *config.Configuration, node *ssh.Node, image string) { 27 | if configure.IsDockerCri() { 28 | initEtcdDocker(configure, node, image, "existing") 29 | } 30 | } 31 | 32 | func addEtcdMember(configure *config.Configuration, node *ssh.Node) { 33 | node.Logger("add etcd node") 34 | master := configure.Hosts.MustGet(configure.ETCD.Nodes[0]) 35 | num, err := master.Sudo().CmdString(Etcdctl("member list | grep " + node.Host + ":2380 | wc -l")) 36 | utils.Panic(err, "etcd list member") 37 | if num == "0" { 38 | err = master.Sudo().CmdPrefixStdout(Etcdctl("member add " + node.Hostname + 39 | " --peer-urls https://" + node.Host + ":2380")) 40 | utils.Panic(err, "etcd add member") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /libs/utils/io.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | func NotExists(path string) bool { 11 | _, err := os.Stat(path) 12 | return err != nil && os.IsNotExist(err) 13 | } 14 | 15 | func Exists(path string) bool { 16 | return !NotExists(path) 17 | } 18 | 19 | func FileBytes(path string) []byte { 20 | bs, err := ioutil.ReadFile(path) 21 | Panic(err, "read file %s", path) 22 | return bs 23 | } 24 | 25 | func Mkdir(dest string) error { 26 | return os.MkdirAll(dest, 0755) 27 | } 28 | 29 | func Copy(src, dest string) (err error) { 30 | if NotExists(src) { 31 | return Error("file not found: %s", src) 32 | } 33 | if err = os.MkdirAll(filepath.Dir(dest), 0666); err != nil { 34 | return 35 | } 36 | 37 | var fs os.FileInfo 38 | if fs, err = os.Stat(dest); err != nil { 39 | return 40 | } 41 | 42 | var input io.ReadCloser 43 | var output io.WriteCloser 44 | if output, err = os.OpenFile(dest, os.O_RDWR|os.O_CREATE, fs.Mode()); err != nil { 45 | return Error("open file %s", dest) 46 | } 47 | 48 | if input, err = os.Open(src); err != nil { 49 | return Error("open file %s", src) 50 | } 51 | 52 | if _, err = io.Copy(output, input); err != nil { 53 | return Error("copy file, from %s to %s", src, dest) 54 | } 55 | return 56 | } 57 | 58 | func Copyto(src, destPath string) (string, error) { 59 | name := filepath.Base(src) 60 | dest := filepath.Join(destPath, name) 61 | return dest, Copy(src, dest) 62 | } 63 | -------------------------------------------------------------------------------- /reduce/kube/deployment/main.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "github.com/ihaiker/vik8s/reduce/asserts" 6 | "github.com/ihaiker/vik8s/reduce/config" 7 | "github.com/ihaiker/vik8s/reduce/kube/pod" 8 | "github.com/ihaiker/vik8s/reduce/plugins" 9 | "github.com/ihaiker/vik8s/reduce/refs" 10 | appsv1 "k8s.io/api/apps/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "strings" 13 | ) 14 | 15 | func Parse(version, prefix string, directive *config.Directive) metav1.Object { 16 | dep := &appsv1.Deployment{ 17 | TypeMeta: metav1.TypeMeta{ 18 | Kind: "Deployment", 19 | APIVersion: appsv1.SchemeGroupVersion.String(), 20 | }, 21 | } 22 | dep.Spec.Replicas = utils.Int32("1", 10) 23 | asserts.Metadata(dep, directive) 24 | asserts.AutoLabels(dep, prefix) 25 | 26 | for it := directive.Body.Iterator(); it.HasNext(); { 27 | d := it.Next() 28 | if err := utils.Safe(func() { refs.UnmarshalItem(&dep.Spec, d) }); err == nil { 29 | it.Remove() 30 | } 31 | } 32 | pod.PodSpecParse(directive, &dep.Spec.Template.Spec) 33 | 34 | dep.Spec.Template.Labels = dep.Labels 35 | dep.Spec.Template.Name = dep.Name 36 | dep.Spec.Selector = &metav1.LabelSelector{ 37 | MatchLabels: dep.Labels, 38 | } 39 | return dep 40 | } 41 | 42 | var Deployment = plugins.ReduceHandler{ 43 | Names: []string{"dep", "deployment", "Dep", "Deployment"}, Handler: Parse, 44 | Demo: strings.Replace(pod.Pod.Demo, "pod test", "deployment test", 1), 45 | } 46 | -------------------------------------------------------------------------------- /install/k8s/reset.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/config" 5 | "github.com/ihaiker/vik8s/install/etcd" 6 | "github.com/ihaiker/vik8s/install/paths" 7 | "github.com/ihaiker/vik8s/libs/logs" 8 | "github.com/ihaiker/vik8s/libs/ssh" 9 | "github.com/ihaiker/vik8s/libs/utils" 10 | "os" 11 | ) 12 | 13 | func ResetNode(configure *config.Configuration, node *ssh.Node) { 14 | err := node.Sudo().CmdStdout("kubeadm reset -f") 15 | if err != nil { 16 | node.Logger("reset %s", err.Error()) 17 | } 18 | 19 | configure.K8S.RemoveNode(node.Host) 20 | 21 | if len(configure.K8S.Masters) == 0 && len(configure.K8S.Nodes) == 0 { 22 | dataDir := paths.Join("kube") 23 | logs.Infof("remove data folder %s", dataDir) 24 | _ = os.RemoveAll(dataDir) 25 | if configure.IsExternalETCD() { 26 | logs.Infof("remove all cluster data in etcd") 27 | etcdNode := configure.Hosts.MustGet(configure.ETCD.Nodes[0]) 28 | err = etcdNode.Sudo().CmdPrefixStdout(etcd.Etcdctl("del /registry --prefix")) 29 | utils.Panic(err, "delete etcd cluster data /registry") 30 | err = etcdNode.Sudo().CmdPrefixStdout(etcd.Etcdctl("del /calico --prefix")) 31 | utils.Panic(err, "delete etcd cluster data /calico") 32 | } 33 | } 34 | 35 | logs.Infof("ipvsadm clear") 36 | if err = node.Sudo().Cmd("ipvsadm --clear"); err != nil { 37 | node.Logger("remove ipvsadm all role: %s", err.Error()) 38 | } 39 | 40 | logs.Infof("clean CNI configuration") 41 | _ = node.Sudo().Cmd("rm -rf /etc/cni/net.d") 42 | } 43 | -------------------------------------------------------------------------------- /reduce/refs/type-manager.go: -------------------------------------------------------------------------------- 1 | package refs 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "github.com/ihaiker/vik8s/reduce/asserts" 6 | "github.com/ihaiker/vik8s/reduce/config" 7 | v1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/api/resource" 9 | "k8s.io/apimachinery/pkg/util/intstr" 10 | "reflect" 11 | ) 12 | 13 | func intOrString(fieldType reflect.Type, item *config.Directive) interface{} { 14 | s := intstr.Parse(item.Args[0]) 15 | if fieldType.Kind() == reflect.Ptr { 16 | return &s 17 | } else { 18 | return s 19 | } 20 | } 21 | 22 | func resourceListParse(fieldType reflect.Type, item *config.Directive) interface{} { 23 | res := v1.ResourceList{} 24 | for _, arg := range item.Args { 25 | k, v := utils.CompileSplit2(arg, ":|=") 26 | res[v1.ResourceName(k)] = resource.MustParse(v) 27 | } 28 | for _, directive := range item.Body { 29 | asserts.ArgsLen(directive, 1) 30 | res[v1.ResourceName(directive.Name)] = resource.MustParse(directive.Args[0]) 31 | } 32 | if fieldType.Kind() == reflect.Ptr { 33 | return &res 34 | } else { 35 | return res 36 | } 37 | } 38 | 39 | var Defaults = TypeManager{ 40 | reflect.TypeOf(intstr.IntOrString{}): intOrString, reflect.TypeOf(&intstr.IntOrString{}): intOrString, 41 | //reflect.TypeOf(v1.ResourceRequirements{}): resourceRequirementsParse, reflect.TypeOf(&v1.ResourceRequirements{}): resourceRequirementsParse, 42 | reflect.TypeOf(v1.ResourceList{}): resourceListParse, reflect.TypeOf(&v1.ResourceList{}): resourceListParse, 43 | } 44 | -------------------------------------------------------------------------------- /reduce/kube/daemonset/main.go: -------------------------------------------------------------------------------- 1 | package daemonset 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "github.com/ihaiker/vik8s/reduce/asserts" 6 | "github.com/ihaiker/vik8s/reduce/config" 7 | "github.com/ihaiker/vik8s/reduce/kube/pod" 8 | "github.com/ihaiker/vik8s/reduce/plugins" 9 | "github.com/ihaiker/vik8s/reduce/refs" 10 | appsv1 "k8s.io/api/apps/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "strings" 13 | ) 14 | 15 | func Parse(version, prefix string, directive *config.Directive) metav1.Object { 16 | daemonset := &appsv1.DaemonSet{ 17 | TypeMeta: metav1.TypeMeta{ 18 | Kind: "DaemonSet", 19 | APIVersion: appsv1.SchemeGroupVersion.String(), 20 | }, 21 | } 22 | asserts.Metadata(daemonset, directive) 23 | asserts.AutoLabels(daemonset, prefix) 24 | 25 | for it := directive.Body.Iterator(); it.HasNext(); { 26 | item := it.Next() 27 | if err := utils.Safe(func() { refs.UnmarshalItem(&daemonset.Spec, item) }); err == nil { 28 | it.Remove() 29 | } 30 | } 31 | pod.PodSpecParse(directive, &daemonset.Spec.Template.Spec) 32 | 33 | daemonset.Spec.Template.Labels = daemonset.Labels 34 | daemonset.Spec.Template.Name = daemonset.Name 35 | daemonset.Spec.Selector = &metav1.LabelSelector{ 36 | MatchLabels: daemonset.Labels, 37 | } 38 | return daemonset 39 | } 40 | 41 | var DaemonSet = plugins.ReduceHandler{ 42 | Names: []string{"daemon", "daemonset", "DaemonSet"}, Handler: Parse, 43 | Demo: strings.Replace(pod.Pod.Demo, "pod test", "daemonset test", 1), 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/pages.yaml: -------------------------------------------------------------------------------- 1 | name: gh-pages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | workflow_dispatch: 10 | 11 | jobs: 12 | check: 13 | name: Check docs files changed 14 | outputs: 15 | run_job: ${{ steps.check_files.outputs.run_job }} 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 2 22 | 23 | - name: check modified files 24 | id: check_files 25 | run: | 26 | echo "=============== list modified files ===============" 27 | git diff --name-only HEAD^ HEAD 28 | 29 | num=`git diff --name-only HEAD^ HEAD | grep "docs/" | wc -l | tr -d '\n '` 30 | if [ "$num" == "0" ] ; then 31 | echo "::set-output name=run_job::false" 32 | else 33 | echo "::set-output name=run_job::true" 34 | fi 35 | 36 | build: 37 | needs: check 38 | if: needs.check.outputs.run_job == 'true' 39 | name: Deploy docs 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Checkout main 43 | uses: actions/checkout@v2 44 | 45 | - name: build docs 46 | run: make mkdocs 47 | 48 | - name: deploy docs 49 | uses: crazy-max/ghaction-github-pages@v2 50 | with: 51 | target_branch: gh-pages 52 | build_dir: site 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | -------------------------------------------------------------------------------- /reduce/config/token_iterator.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "fmt" 4 | 5 | type tokenIterator struct { 6 | it *charIterator 7 | } 8 | 9 | func newTokenIterator(filename string) *tokenIterator { 10 | return &tokenIterator{it: newCharIterator(filename)} 11 | } 12 | 13 | func newTokenIteratorWithBytes(bs []byte) *tokenIterator { 14 | return &tokenIterator{it: newCharIteratorWithBytes(bs)} 15 | } 16 | 17 | func (self *tokenIterator) next() (token string, tokenLine int, tokenHas bool) { 18 | for { 19 | char, line, has := self.it.nextFilter(ValidChars) 20 | if !has { 21 | return 22 | } 23 | switch char { 24 | case ";", "{", "}": 25 | { 26 | token = char 27 | tokenLine = line 28 | tokenHas = true 29 | return 30 | } 31 | case "#": 32 | { 33 | word, _, _ := self.it.nextTo(In("\n"), false) 34 | token = char + word 35 | tokenLine = line 36 | tokenHas = true 37 | return 38 | } 39 | case "'", `"`, "`": 40 | { 41 | word, _, wordHas := self.it.nextTo(In(char), true) 42 | if !wordHas { 43 | panic(fmt.Errorf("error at line : %d", line)) 44 | } 45 | //token = char + word 46 | token = word[0 : len(word)-1] 47 | tokenLine = line 48 | tokenHas = true 49 | return 50 | } 51 | default: 52 | word, _, wordHas := self.it.nextTo(Not(ValidChars).Or(In(";", "{")), false) 53 | if !wordHas { 54 | panic(fmt.Errorf("error at line : %d", line)) 55 | } 56 | token = char + word 57 | tokenLine = line 58 | tokenHas = true 59 | return 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/cmds/k8s/index.md: -------------------------------------------------------------------------------- 1 | # Kubernetes 集群初始化 2 | 3 | ## 前言: 4 | 5 | 开始本章节之前,请确定您已经查看了 [快速开始章节](../../quickstart.md) 6 | 7 | ## 命令详解 8 | 9 | 由于本程序设计构建的,你只需要以下命令 10 | 11 | ```shell 12 | $ vik8s init 172.16.100.10 # 初始化集群 13 | $ vik8s join --master 172.16.100.11 172.16.100.12 #添加两个控制节点 14 | # slave节点安装 15 | $ vik8s join 172.16.100.13-172.16.100.15 16 | $ vik8s cni flannel #网络插件安装 17 | ``` 18 | 19 | 即可简单初始化一个kubernetes集群。那么下面我们就介绍一下出初始化集群的一些参数内容: 20 | 21 | | 参数 | 默认值 | 描述 | 22 | | ---- | ------ | ---- | 23 | |--version string |v1.21.3| 指定Kubernetes集群版本号 | 24 | | --kubeadm-config || kubeadm 配置文件路径,如果您对集群有锁扩展可以设置此文件. 有关此配置文件更多信息查看`kubeadm --config` | 25 | | --api-server || Specify a stable IP address or DNS name for the control plane. see kubeadm --control-plane-endpoint (env: VIK8S_K8S_API_SERVER) (default "api-vik8s-io") | 26 | | --api-server-cert-extra-sans || kubernetes服务API证书附加sans | 27 | | --repo string || image地址获取的地址,默认从 k8s.gcr.io(国外) 和 registry.aliyuncs.com/google_containers(国内) | 28 | | --interface |`eth*`,`en*`,`em*`| 指定集群使用的网卡 | 29 | | --pod-cidr |100.64.0.0/24| 指定pod地址范围 | 30 | | --svc-cidr |10.96.0.0/12| 指定service地址范围 | 31 | | --certs-validity |44y| 指定证书有效时间 | 32 | | --timezone |Asia/Shanghai| | 33 | | --ntp-services strings || time server,默认:ntp1.aliyun.com,ntp2.aliyun.com,ntp3.aliyun.com | 34 | | --taint || Update the taints on the nodes | 35 | 36 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ![](./docs/logo.png) ![](docs/logo_txt.png) 2 | 3 | 一个非常简单kubernetes高可用集群安装部署工具,支持 v1.19.+ 4 | 5 | 程序尽可能采用原生kubernetes特性不对kubernetes进行修改和面向过程模式编写,把安装过程清晰化。 6 | 7 | 8 | 9 | ## 特性 10 | 11 | - 简单快捷方便的安装方式。所有安装基本上就是一条命令 12 | - 多集群管理,方便的管理不同集群。 13 | - 统一命令管理程序,可以方便的在客户端使用一条命令在所有管理主机上运行。 14 | - 独立应用不依赖任何第三方 15 | - 可控的证书时间(默认:44年,本人的幸运数字就是4,我的地盘我任性) 16 | - 可选择性的镜像地址。默认提供国内/外**可信&安全**的镜像地址。不使用离线包和私有镜像(为啥不提供离线包?您是否还记得IOS环境侵入问题,Goolge一下吧,当然这样的话你的所有安装节点必须可以联网去下载镜像。) 17 | - 通过使用service特性和IPVS实现HA高可用,不依赖于任何第三实现。 18 | - 轻松的增加集群节点 `vik8s join -m ` 19 | - ETCD节点可单独安装和节点添加。`vik8s etcd init ...` 和 `vik8s etcd join ...` 20 | - 提供周边 安装,同样简单方便。 21 | - dashboard (1.0 后废弃) 22 | - ingress (nginx/traefik) 23 | - 【重磅推出】kubernetes reduce 命令,简化yaml配置文件。 [查看教程和实例](./reduce/index.md)。 24 | 25 | 26 | 27 | ## 快速开始: 28 | 29 | 查看文档:[Quickstart][quickstart] 30 | 31 | 32 | 33 | ## 技术支持群 34 | 35 | | 钉钉群:34673135 | QQ群:715096758 | 36 | | ------------------------ | ------------------ | 37 | | ![][dd] | ![][qq] | 38 | 39 | [dd]: https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=https%3A%2F%2Fqr.dingtalk.com%2Faction%2Fjoingroup%3Fcode%3Dv1%2Ck1%2CZiQs4kjvfFMm5EDwWHPZSGGZRCHeW%2BZUwqlW73xXrO0%3D "钉钉群地址" 40 | [qq]: https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=https%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Fk%3DRif3DnLnNWbZh3BTQod_vYcCpYRSpLkk%26authKey%3DIng0sqF0Wj6o22WMjXBDiu38V9arojuEcy5iGLR%2BfHMNOoYoAkg1tpcK9B3mkL4b%26noverify%3D0 "QQ" 41 | [quickstart]: http://vik8s.renzhen.la/#quick-start 42 | -------------------------------------------------------------------------------- /install/k8s/clean.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/install/paths" 5 | "github.com/ihaiker/vik8s/libs/ssh" 6 | "github.com/ihaiker/vik8s/libs/utils" 7 | "os" 8 | ) 9 | 10 | func Clean(nodes []*ssh.Node, expFn ...func(node *ssh.Node)) { 11 | ssh.Sync(nodes, func(i int, node *ssh.Node) { 12 | for _, fn := range expFn { 13 | fn(node) 14 | } 15 | 16 | _ = node.Sudo().CmdStdout("kubeadm reset -f") 17 | _ = node.Sudo().CmdStdout("ipvsadm -C") 18 | _ = node.Sudo().CmdStdout("ifconfig cni0 down") 19 | _ = node.Sudo().CmdStdout("ip link delete cni0") 20 | _ = node.Sudo().CmdStdout("ip link delete kube-ipvs0") 21 | _ = node.Sudo().CmdStdout("ip link delete dummy0") 22 | 23 | _ = node.Sudo().CmdStdout("rm -rf /etc/cni/net.d/* ~/.kube /etc/kubernetes/*") 24 | _ = node.Sudo().CmdStdout("rm -rf /var/lib/etcd") 25 | _ = node.Sudo().CmdStdout("rm -rf /var/lib/ceph") 26 | 27 | _ = node.Shell(` 28 | iptables -P INPUT ACCEPT 29 | iptables -P FORWARD ACCEPT 30 | iptables -P OUTPUT ACCEPT 31 | iptables -t nat -F 32 | iptables -t mangle -F 33 | iptables -F 34 | iptables -X 35 | 36 | ip6tables -P INPUT ACCEPT 37 | ip6tables -P FORWARD ACCEPT 38 | ip6tables -P OUTPUT ACCEPT 39 | ip6tables -t nat -F 40 | ip6tables -t mangle -F 41 | ip6tables -F 42 | ip6tables -X 43 | `, utils.Stdout("")) 44 | }) 45 | config := paths.Join("vik8s.conf") 46 | utils.Panic(os.RemoveAll(config), "remove %s", config) 47 | 48 | kube := paths.Join("kube") 49 | utils.Panic(os.RemoveAll(kube), "remove %s", kube) 50 | } 51 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: vik8s 2 | builds: 3 | - env: [CGO_ENABLED=0] 4 | goos: 5 | - linux 6 | - windows 7 | - darwin 8 | - freebsd 9 | - netbsd 10 | goarch: 11 | - amd64 12 | - arm64 13 | - arm 14 | - 386 15 | - ppc64 16 | - ppc64le 17 | - s390x 18 | archives: 19 | - replacements: 20 | darwin: Darwin 21 | linux: Linux 22 | windows: Windows 23 | 386: i386 24 | amd64: x86_64 25 | checksum: 26 | name_template: 'checksums.txt' 27 | 28 | #dockers: 29 | # - image_templates: ["ghcr.io/goreleaser/example:{{ .Version }}"] 30 | # dockerfile: Dockerfile 31 | # build_flag_templates: 32 | # - --label=org.opencontainers.image.title={{ .ProjectName }} 33 | # - --label=org.opencontainers.image.description={{ .ProjectName }} 34 | # - --label=org.opencontainers.image.url=https://github.com/goreleaser/example 35 | # - --label=org.opencontainers.image.source=https://github.com/goreleaser/example 36 | # - --label=org.opencontainers.image.version={{ .Version }} 37 | # - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }} 38 | # - --label=org.opencontainers.image.revision={{ .FullCommit }} 39 | # - --label=org.opencontainers.image.licenses=MIT 40 | 41 | 42 | #nfpms: 43 | # - maintainer: haiker 44 | # description: very easy install kubernetes cluster 45 | # homepage: https://github.com/ihaiker/vik8s 46 | # license: MIT 47 | # formats: 48 | # - deb 49 | # - rpm 50 | # - apk 51 | -------------------------------------------------------------------------------- /yaml/sidecars/dashboard/ingress.conf: -------------------------------------------------------------------------------- 1 | namespace kubernetes-dashboard; 2 | 3 | {{ if not .EnableInsecureLogin }} 4 | secret kubernetes-dashboard-tls "kubernetes.io/tls" { 5 | tls.crt: "{{.TLSCert}}"; 6 | tls.key: "{{.TLSKey}}"; 7 | } 8 | {{ end }} 9 | 10 | ingress dashboard-ingress { 11 | annotations { 12 | nginx.ingress.kubernetes.io/ingress.class: nginx; 13 | 14 | {{ if not .EnableInsecureLogin }} 15 | nginx.ingress.kubernetes.io/proxy-ssl-protocols: "https"; 16 | nginx.ingress.kubernetes.io/proxy-ssl-verify: "false"; 17 | nginx.ingress.kubernetes.io/backend-protocol: "https"; 18 | nginx.ingress.kubernetes.io/ssl-redirect: "true"; 19 | nginx.ingress.kubernetes.io/rewrite-target: /; 20 | nginx.ingress.kubernetes.io/secure-backends: "true"; 21 | 22 | ingress.kubernetes.io/protocol: "https"; 23 | traefik.ingress.kubernetes.io/redirect-entry-point: https; 24 | traefik.ingress.kubernetes.io/frontend-entry-points: https; 25 | {{ end }} 26 | 27 | {{ if .InsecureHeader }} 28 | ingress.kubernetes.io/custom-request-headers: "Authorization: Bearer {{.Token}}"; 29 | nginx.ingress.kubernetes.io/configuration-snippet: 'proxy_set_header Authorization "Bearer {{.Token}}"'; 30 | {{ end }} 31 | } 32 | 33 | {{ if not .EnableInsecureLogin }} 34 | tls kubernetes-dashboard-tls; 35 | {{end}} 36 | 37 | rules "{{.Ingress}}" { 38 | http paths { 39 | serviceName: kubernetes-dashboard; 40 | servicePort: "{{if not .EnableInsecureLogin}}8443{{else}}9090{{end}}"; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/ihaiker/ngx/v2" 5 | "github.com/ihaiker/vik8s/install/hosts" 6 | "github.com/ihaiker/vik8s/libs/utils" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | ) 11 | 12 | type Configuration struct { 13 | filename string 14 | Docker *DockerConfiguration `ngx:"docker"` 15 | Containerd *ContainerdConfiguration `ngx:"containerd"` 16 | K8S *K8SConfiguration `ngx:"k8s"` 17 | ETCD *ETCDConfiguration `ngx:"etcd"` 18 | ExternalETCD *ExternalETCDConfiguration `ngx:"external_etcd"` 19 | Hosts *hosts.Manager `ngx:"-"` 20 | } 21 | 22 | //Load 加载vik8s.conf配置,如果配置文件不存在,直接返回空配置 23 | func Load(filename string) (cfg *Configuration, err error) { 24 | cfg = new(Configuration) 25 | cfg.filename = filename 26 | if !utils.Exists(filename) { 27 | cfg.K8S = DefaultK8SConfiguration() 28 | cfg.Docker = DefaultDockerConfiguration() 29 | return 30 | } 31 | 32 | var data []byte 33 | if data, err = ioutil.ReadFile(filename); err != nil { 34 | return 35 | } 36 | if err = ngx.Unmarshal(data, cfg); err != nil { 37 | return 38 | } 39 | return 40 | } 41 | 42 | func (cfg *Configuration) Write() error { 43 | data, _ := ngx.Marshal(cfg) 44 | if err := os.MkdirAll(filepath.Dir(cfg.filename), 0755); err != nil { 45 | return err 46 | } 47 | return ioutil.WriteFile(cfg.filename, data, 0666) 48 | } 49 | 50 | func (cfg *Configuration) IsDockerCri() bool { 51 | return cfg.Docker != nil 52 | } 53 | 54 | func (cfg *Configuration) IsExternalETCD() bool { 55 | return (cfg.ETCD != nil && len(cfg.ETCD.Nodes) > 0) || cfg.ExternalETCD != nil 56 | } 57 | -------------------------------------------------------------------------------- /reduce/asserts/meta.go: -------------------------------------------------------------------------------- 1 | package asserts 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "github.com/ihaiker/vik8s/reduce/config" 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | ) 8 | 9 | func Metadata(metaObject metav1.Object, directive *config.Directive) { 10 | MetadataIndex(metaObject, directive, 1) 11 | } 12 | 13 | func MetadataIndex(meta metav1.Object, directive *config.Directive, argsLabel int) { 14 | ArgsMin(directive, 1) 15 | meta.SetName(directive.Args[0]) 16 | 17 | { 18 | labels := make(map[string]string) 19 | if len(directive.Args) >= argsLabel { 20 | for _, arg := range directive.Args[argsLabel:] { 21 | label, value := utils.Split2(arg, "=") 22 | labels[label] = value 23 | } 24 | } 25 | //label 26 | for { 27 | if d := directive.Body.Remove("label"); d == nil { 28 | break 29 | } else { 30 | labels[d.Args[0]] = d.Args[1] 31 | } 32 | } 33 | //labels{} 34 | if d := directive.Body.Remove("labels"); d != nil { 35 | for _, ld := range d.Body { 36 | labels[ld.Name] = ld.Args[0] 37 | } 38 | } 39 | meta.SetLabels(labels) 40 | } 41 | 42 | if ns := directive.Body.Remove("namespace"); ns != nil { 43 | meta.SetNamespace(ns.Args[0]) 44 | } 45 | 46 | { 47 | annotations := make(map[string]string) 48 | for { 49 | if d := directive.Body.Remove("annotation"); d == nil { 50 | break 51 | } else { 52 | annotations[d.Args[0]] = d.Args[1] 53 | } 54 | } 55 | if d := directive.Body.Remove("annotations"); d != nil { 56 | for _, ld := range d.Body { 57 | annotations[ld.Name] = ld.Args[0] 58 | } 59 | } 60 | meta.SetAnnotations(annotations) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | 7 | jobs: 8 | check: 9 | name: Check whether to publish release 10 | outputs: 11 | enable: ${{ steps.check_release.outputs.enable }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 2 18 | - uses: olegtarasov/get-tag@v2.1 19 | id: tagName 20 | with: 21 | tagRegex: "(.*)" 22 | - name: check is release 23 | id: check_release 24 | run: | 25 | num=`ls ./docs/releases/*.md | grep ${{ steps.tagName.outputs.tag }} || echo ''` 26 | if [ "$num" == "" ] ; then 27 | echo "::set-output name=enable::false" 28 | else 29 | echo "::set-output name=enable::true" 30 | fi 31 | 32 | build: 33 | name: auto-release 34 | runs-on: ubuntu-latest 35 | needs: check 36 | if: needs.check.outputs.enable == 'true' 37 | steps: 38 | - uses: actions/setup-go@v2.1.3 39 | id: go 40 | with: 41 | stable: false 42 | go-version: 1.17.5 43 | 44 | - name: Checkout code 45 | uses: actions/checkout@v2 46 | 47 | - uses: olegtarasov/get-tag@v2.1 48 | id: tagName 49 | with: 50 | tagRegex: "(.*)" 51 | 52 | - name: Run GoReleaser 53 | uses: goreleaser/goreleaser-action@v2.8.0 54 | with: 55 | args: release --release-header-tmpl=./docs/releases/${{ steps.tagName.outputs.tag }}.md --rm-dist 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | -------------------------------------------------------------------------------- /install/cni/flannel.go: -------------------------------------------------------------------------------- 1 | package cni 2 | 3 | import ( 4 | "github.com/ihaiker/cobrax" 5 | "github.com/ihaiker/vik8s/config" 6 | "github.com/ihaiker/vik8s/install/paths" 7 | "github.com/ihaiker/vik8s/install/repo" 8 | "github.com/ihaiker/vik8s/libs/ssh" 9 | "github.com/ihaiker/vik8s/libs/utils" 10 | "github.com/ihaiker/vik8s/reduce" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type Flannel struct { 15 | Version string `flag:"version" help:"the flannel version"` 16 | Repo string `flag:"repo" help:"docker image pull from."` 17 | LimitCPU string `flag:"limits-cpu" help:"Container Cup Limit"` 18 | LimitMemory string `flag:"limits-memory" help:"Container Memory Limit"` 19 | } 20 | 21 | func NewFlannelCni() *Flannel { 22 | return &Flannel{ 23 | Version: "v0.14.0", 24 | LimitCPU: "100m", 25 | LimitMemory: "50Mi", 26 | } 27 | } 28 | func (f *Flannel) Name() string { 29 | return "flannel" 30 | } 31 | 32 | func (f *Flannel) Flags(cmd *cobra.Command) { 33 | err := cobrax.Flags(cmd, f, "", "") 34 | utils.Panic(err, "set flannel flag error") 35 | } 36 | 37 | func (f *Flannel) Apply(configure *config.Configuration, node *ssh.Node) { 38 | data := paths.Json{ 39 | "Version": f.Version, "Repo": repo.QuayIO(f.Repo), 40 | "CIDR": configure.K8S.PodCIDR, "Interface": configure.K8S.Interface, 41 | "LimitCPU": f.LimitCPU, "LimitMemory": f.LimitMemory, 42 | } 43 | name := "yaml/cni/flannel.conf" 44 | err := reduce.ApplyAssert(node, name, data) 45 | utils.Panic(err, "apply flannel network") 46 | } 47 | 48 | func (f *Flannel) Clean(node *ssh.Node) { 49 | _ = node.Sudo().CmdStdout("ifconfig flannel.1 down") 50 | _ = node.Sudo().CmdStdout("ip link delete flannel.1") 51 | _ = node.Sudo().CmdStdout("rm -rf /var/lib/cni/ /etc/cni/net.d/*") 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | check: 12 | name: Check code files changed 13 | outputs: 14 | run_job: ${{ steps.check_files.outputs.run_job }} 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | with: 20 | fetch-depth: 2 21 | 22 | - name: check modified files 23 | id: check_files 24 | run: | 25 | echo "=============== list modified files ===============" 26 | git diff --name-only HEAD^ HEAD 27 | 28 | num=`git diff --name-only HEAD^ HEAD | grep -c ".go" | tr -d '\n '` 29 | if [ "$num" == "0" ] ; then 30 | echo "::set-output name=run_job::false" 31 | else 32 | echo "::set-output name=run_job::true" 33 | fi 34 | 35 | test: 36 | needs: check 37 | if: needs.check.outputs.run_job == 'true' 38 | strategy: 39 | matrix: 40 | go_version: 41 | - 1.17.5 42 | os: 43 | - macos 44 | - ubuntu 45 | - windows 46 | goarch: 47 | - amd64 48 | 49 | name: test (${{ matrix.os }}/go-${{ matrix.go_version }}/${{ matrix.goarch }}) 50 | runs-on: ${{ matrix.os }}-latest 51 | steps: 52 | - uses: actions/setup-go@v2.1.3 53 | id: go 54 | with: 55 | stable: false 56 | go-version: ${{ matrix.go_version }} 57 | 58 | - uses: actions/checkout@v2.3.4 59 | 60 | - run: go mod download 61 | 62 | - run: go test ./... 63 | env: 64 | GOARCH: ${{ matrix.goarch }} 65 | GOPROXY: off 66 | -------------------------------------------------------------------------------- /docs/cmds/hosts/index.md: -------------------------------------------------------------------------------- 1 | # 主机访问方式管理 2 | 3 | 4 | 5 | 在某些情况下,会出现每个主机的密码或秘钥不一致,或者集群访问需要通过代理,为了让安装更方便,我们添加了 `hosts`命令。此命令可以先行把集群ip添加的到管理之中,在之后指定主机的时候,我们只需要指定主机的`IP`或者`hostname`即可。 6 | 7 | ## 简单实例 8 | 9 | ```shell 10 | $ vik8s hosts -u root -P passwd 10.24.0.10 11 | $ vik8s hosts -u admin -i ~/.ssh/id_rsa 10.24.0.11 12 | $ vik8s hosts -u addmin -P passwd --port 22 13 | ``` 14 | 15 | 命令基本上符合`ssh`命令的参数,就不多余赘述。有关参数可以使用 `vik8s hosts --help`方式查看。 16 | 17 | 18 | 19 | ## 参数详解 20 | 21 | | 参数 | 默认值 | 环境变量获取 | 描述 | 22 | | ----------------- | ----------------- | --------------------- | ----------------------------------- | 23 | | -u, –user | root | VIK8S_SSH_USER | 设置登录账户 | 24 | | -P, --password | | VIK8S_SSH_PASSWORD | ssh账户密码 | 25 | | --port | 22 | VIK8S_SSH_PORT | ssh端口号 | 26 | | -i, --private-key | $HOME/.ssh/id_rsa | VIK8S_SSH_PRIVATE_KEY | ssh private key | 27 | | --proxy | | VIK8S_SSH_PROXY | 使用的ssh代理,更多查看代理使用章节 | 28 | | --passphras | | VIK8S_SSH_PASSPHRASE | private key passphrase | 29 | 30 | 31 | 32 | ## 使用代理 33 | 34 | 为了保证系统安全有些时候我们无法直接访问控制节点,需要使用代理去访问此时我们就需要使用需要使用ssh代理访问我们的主机。 35 | 36 | 添加代理和添加主机一样,我们主要先把代理ssh主机10.24.加入到管理列表, 37 | 38 | ```shell 39 | vik8s hosts -u root -P password 40 | ``` 41 | 42 | 然后添加我们需要访问的主机 43 | 44 | ```shell 45 | vik8s hosts --proxy= 46 | ``` 47 | 48 | 49 | 50 | ## 使用技巧 51 | 52 | 为了让管理变得更通用,我们进一步支持了`ssh`命令的一致性 53 | 54 | ```shell 55 | vik8s hosts admin@10.24.0.10:12232 56 | ``` 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /cmd/clean.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "github.com/ihaiker/vik8s/install/cni" 7 | "github.com/ihaiker/vik8s/install/k8s" 8 | "github.com/ihaiker/vik8s/libs/ssh" 9 | "github.com/peterh/liner" 10 | "github.com/spf13/cobra" 11 | "io" 12 | "math/rand" 13 | "strings" 14 | ) 15 | 16 | var cleanCmd = &cobra.Command{ 17 | Use: "clean", Hidden: true, Args: cobra.MinimumNArgs(1), 18 | Short: color.New(color.FgHiRed).Sprintf("This command is used to deeply clean up the environment. %s", strings.Repeat("Use very carefully", 3)), 19 | Example: `vik8s clean or vik8s clean 10.24.0.1`, 20 | PreRunE: configLoad(none), 21 | Run: func(cmd *cobra.Command, args []string) { 22 | force, _ := cmd.Flags().GetBool("force") 23 | if !force { 24 | if !importantConfirmation() { 25 | fmt.Println("Verification code error") 26 | return 27 | } 28 | } 29 | var nodes []*ssh.Node 30 | if len(args) == 1 && args[0] == "all" { 31 | nodes = configure.Hosts.All() 32 | } else { 33 | nodes = configure.Hosts.MustGets(args) 34 | } 35 | k8s.Clean(nodes, cni.Plugins.Clean) 36 | }, 37 | } 38 | 39 | func importantConfirmation() bool { 40 | term := liner.NewLiner() 41 | defer func() { _ = term.Close() }() 42 | term.SetCtrlCAborts(true) 43 | 44 | code := fmt.Sprintf("%04d", rand.Intn(10000)) 45 | for i := 0; i < 3; i++ { 46 | line, err := term.Prompt(fmt.Sprintf("Enter confirmation code [%s]> ", code)) 47 | if err == io.EOF { 48 | break 49 | } else if err != nil && strings.Contains(err.Error(), "control-c break") { 50 | break 51 | } 52 | if code == line { 53 | return true 54 | } 55 | } 56 | return false 57 | } 58 | 59 | func init() { 60 | cleanCmd.Flags().Bool("force", false, "Clean the node without prompting for confirmation") 61 | } 62 | -------------------------------------------------------------------------------- /reduce/kube/pod/container/mountVolume.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | "github.com/ihaiker/vik8s/reduce/config" 7 | "github.com/ihaiker/vik8s/reduce/kube/pod/volumes" 8 | v1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | func mountParse(args []string, body *config.Directive, spec *v1.PodSpec, c *v1.Container) { 12 | vt, name, _ := volumes.VolumeTypeAndNameAndSource(args[0], args[1:]) 13 | 14 | vm := v1.VolumeMount{Name: name} 15 | switch vt { 16 | case "from": 17 | vm.MountPath, vm.SubPath = utils.Split2(args[1], ":") 18 | c.VolumeMounts = append(c.VolumeMounts, vm) 19 | return 20 | case "empty", "emptyDir", "emptydir": 21 | vm.MountPath = args[1] 22 | case "hostpath", "hostPath": 23 | sourcePath := "" 24 | sourcePath, vm.MountPath = utils.Split2(args[1], ":") 25 | if vm.MountPath == "" { 26 | vm.MountPath = sourcePath 27 | } 28 | args[1] = sourcePath 29 | case "secret", "configmap", "config", "configMap": 30 | vm.MountPath, vm.SubPath = utils.Split2(args[1], ":") 31 | args = append(args[0:1], args[2:]...) 32 | default: 33 | vm.MountPath, vm.SubPath = utils.Split2(args[1], ":") 34 | args[0] = fmt.Sprintf("%s:%s", vt, name) 35 | } 36 | 37 | if d := body.Body.Remove("mountPropagation"); d != nil { 38 | mp := v1.MountPropagationMode(d.Args[0]) 39 | vm.MountPropagation = &mp 40 | } 41 | if d := body.Body.Remove("subPath"); d != nil { 42 | vm.SubPath = d.Args[0] 43 | } 44 | if d := body.Body.Remove("subPathExpr"); d != nil { 45 | vm.SubPathExpr = d.Args[0] 46 | } 47 | if d := body.Body.Remove("readOnly"); d != nil { 48 | vm.ReadOnly = d.Args[0] == "true" 49 | } 50 | 51 | c.VolumeMounts = append(c.VolumeMounts, vm) 52 | volumes.VolumeParse(&config.Directive{Name: "volume", Args: args, Body: body.Body}, spec) 53 | } 54 | -------------------------------------------------------------------------------- /reduce/plugins/plugin.go: -------------------------------------------------------------------------------- 1 | package plugins 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/install/paths" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | "github.com/ihaiker/vik8s/reduce/config" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "os" 9 | "path/filepath" 10 | "plugin" 11 | ) 12 | 13 | type ( 14 | ReduceHandler struct { 15 | Names []string 16 | Demo string 17 | Handler func(version, prefix string, item *config.Directive) (obj metav1.Object) 18 | } 19 | 20 | PluginLoad func() ReduceHandler 21 | 22 | ReduceHandlers []ReduceHandler 23 | ) 24 | 25 | var Manager = ReduceHandlers{} 26 | 27 | func (m *ReduceHandler) Has(name string) bool { 28 | for _, n := range m.Names { 29 | if n == name { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | 36 | func (m *ReduceHandlers) Handler(version, prefix string, item *config.Directive) (metav1.Object, bool) { 37 | name, _ := utils.Split2(item.Name, ":") 38 | for _, reduce := range *m { 39 | if reduce.Has(name) { 40 | obj := reduce.Handler(version, prefix, item) 41 | return obj, true 42 | } 43 | } 44 | return nil, false 45 | } 46 | 47 | func plugins() []string { 48 | files := make([]string, 0) 49 | pluginDir := paths.Join("plugins", "reduce") 50 | filepath.Walk(pluginDir, func(path string, info os.FileInfo, err error) error { 51 | if err != nil { 52 | return err 53 | } 54 | if !info.IsDir() { 55 | files = append(files, path) 56 | } 57 | return nil 58 | }) 59 | return files 60 | } 61 | 62 | func Load() { 63 | pluginFiles := plugins() 64 | for _, file := range pluginFiles { 65 | p, err := plugin.Open(file) 66 | utils.Panic(err, "load reduce plugins %s", file) 67 | sl, err := p.Lookup("Reduce") 68 | utils.Assert(err == nil, "load reduce plugins %s", file) 69 | Manager = append(Manager, sl.(PluginLoad)()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /install/cni/calico.go: -------------------------------------------------------------------------------- 1 | package cni 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/cobrax" 6 | "github.com/ihaiker/vik8s/config" 7 | "github.com/ihaiker/vik8s/install/paths" 8 | "github.com/ihaiker/vik8s/install/repo" 9 | "github.com/ihaiker/vik8s/install/tools" 10 | "github.com/ihaiker/vik8s/libs/ssh" 11 | "github.com/ihaiker/vik8s/libs/utils" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | const ( 16 | tigera_operator = "apply/cni/calico/tigera-operator.yaml" 17 | custom_resources = "apply/cni/calico/custom-resources.yaml" 18 | ) 19 | 20 | type Calico struct { 21 | Version string `flag:"version" help:"calico server"` 22 | Repo string `flag:"repo" help:"tigera/operator image repository. default: from quay.io or quay.mirrors.ustc.edu.cn in china"` 23 | } 24 | 25 | func NewCalico() *Calico { 26 | return &Calico{ 27 | Version: "v1.23.1", 28 | } 29 | } 30 | 31 | func (f *Calico) Name() string { 32 | return "calico" 33 | } 34 | 35 | func (f *Calico) Flags(cmd *cobra.Command) { 36 | _ = cobrax.Flags(cmd, f, "", "") 37 | } 38 | 39 | func (f *Calico) Apply(configure *config.Configuration, node *ssh.Node) { 40 | image := fmt.Sprintf("%s/tigera/operator", repo.QuayIO(f.Repo)) 41 | err := tools.ScpAndApplyAssert(node, "yaml/cni/calico/tigera-operator.yaml", paths.Json{ 42 | "Image": image, "Version": f.Version, 43 | }) 44 | utils.Panic(err, "apply calico error") 45 | 46 | err = tools.ScpAndApplyAssert(node, "yaml/cni/calico/custom-resources.yaml", paths.Json{ 47 | "NetworkCidr": configure.K8S.PodCIDR, 48 | }) 49 | utils.Panic(err, "apply calico custom resources error") 50 | } 51 | 52 | func (f *Calico) Clean(node *ssh.Node) { 53 | remote := node.Vik8s(tigera_operator) 54 | _ = node.CmdStdout("kubectl delete -f " + remote) 55 | 56 | remote = node.Vik8s(custom_resources) 57 | _ = node.CmdStdout("kubectl delete -f " + remote) 58 | } 59 | -------------------------------------------------------------------------------- /playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | become: yes 3 | gather_facts: true 4 | 5 | tasks: 6 | - name: centos 7 | block: 8 | - name: make root ssh directory 9 | file: 10 | path: "${HOME}/.ssh" 11 | state: directory 12 | - name: copy authorized_keys 13 | copy: 14 | remote_src: true 15 | src: "/home/vagrant/.ssh/authorized_keys" 16 | dest: "/root/.ssh/authorized_keys" 17 | mode: 0600 18 | when: ansible_distribution == "CentOS" 19 | - name: yum install jq/vim/net-tools 20 | yum: 21 | name: "{{packages}}" 22 | state: present 23 | vars: 24 | packages: 25 | - jq 26 | - vim 27 | - net-tools 28 | - tree 29 | retries: 5 30 | ignore_errors: true 31 | 32 | - when: ansible_distribution_major_version == "7" 33 | block: 34 | - name: add key 35 | rpm_key: 36 | state: present 37 | key: https://www.elrepo.org/RPM-GPG-KEY-elrepo.org 38 | - name: install elrepo7 39 | yum: 40 | name: https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm 41 | state: present 42 | - name: install kernel-ml 43 | yum: 44 | name: kernel-ml 45 | enablerepo: elrepo-kernel 46 | state: present 47 | - name: grub2-set-default 48 | command: grub2-set-default 0 49 | - name: grub2-mkconfig 50 | command: grub2-mkconfig -o /boot/grub2/grub.cfg 51 | - name: reboot 52 | reboot: 53 | - name: list kernel 54 | shell: | 55 | awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg 56 | register: kernel_stdout 57 | - name: show kernel 58 | debug: 59 | msg: "{{kernel_stdout.stdout}}" 60 | -------------------------------------------------------------------------------- /reduce/kube/replace.go: -------------------------------------------------------------------------------- 1 | package kube 2 | 3 | import ( 4 | "bytes" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | "github.com/ihaiker/vik8s/reduce/config" 7 | "text/template" 8 | ) 9 | 10 | func replace(cfg *config.Directive) { 11 | for itemReplace := cfg.Body.Remove("replace"); itemReplace != nil; itemReplace = cfg.Body.Remove("replace") { 12 | 13 | left, right := "<<", ">>" 14 | if delims := itemReplace.Body.Remove("@delims"); delims != nil { 15 | left, right = utils.Index(delims.Args, 0), utils.Index(delims.Args, 1) 16 | if right == "" { 17 | right = left 18 | } 19 | } 20 | 21 | args := config.Directives{} 22 | for { 23 | if r := itemReplace.Body.Remove("@args"); r != nil { 24 | if len(r.Args) > 0 { 25 | r.Name = r.Args[0] 26 | r.Args = r.Args[1:] 27 | } 28 | args = append(args, r) 29 | } else { 30 | break 31 | } 32 | } 33 | 34 | for _, arg := range args { 35 | out := bytes.NewBufferString("") 36 | templateString := itemReplace.String() 37 | tmpl, err := template.New("").Delims(left, right).Funcs(funcs()).Parse(templateString) 38 | utils.Panic(err, "template error") 39 | 40 | err = tmpl.Execute(out, arg) 41 | utils.Panic(err, "template error") 42 | 43 | newConfig, err := config.ParseWith("", out.Bytes()) 44 | utils.Panic(err, "template error") 45 | 46 | for _, directive := range newConfig.Body { 47 | cfg.Body = append(cfg.Body, directive.Body...) 48 | } 49 | } 50 | } 51 | } 52 | 53 | func funcs() template.FuncMap { 54 | return template.FuncMap{ 55 | "body": func(d *config.Directive, name string) *config.Directive { 56 | for _, directive := range d.Body { 57 | if directive.Name == name { 58 | return directive 59 | } 60 | } 61 | return nil 62 | }, 63 | "remove": func(d *config.Directive, name string) *config.Directive { 64 | return d.Body.Remove(name) 65 | }, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /reduce/kube/pod/volumes/parse.go: -------------------------------------------------------------------------------- 1 | package volumes 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "github.com/ihaiker/vik8s/reduce/config" 6 | v1 "k8s.io/api/core/v1" 7 | ) 8 | 9 | func VolumeTypeAndNameAndSource(name string, args []string) (string, string, string) { 10 | vt, volumeName, sourceName := utils.Split3(name, ":") 11 | if volumeName == "" { 12 | volumeName = vt 13 | vt = utils.Switch(len(args) == 1, "emptyDir", "hostPath") 14 | } 15 | if sourceName == "" { 16 | sourceName = volumeName 17 | } 18 | return vt, volumeName, sourceName 19 | } 20 | 21 | func volumeParse(name string, args []string, body config.Directives) v1.Volume { 22 | vt, volumeName, sourceName := VolumeTypeAndNameAndSource(name, args) 23 | volume := v1.Volume{Name: volumeName} 24 | switch vt { 25 | case "emptyDir", "emptydir", "empty": 26 | emptyDirParse(&volume, sourceName, args, body) 27 | case "hostpath", "hostPath": 28 | hostPathParse(&volume, sourceName, args, body) 29 | case "config", "configmap", "configMap": 30 | configMapParse(&volume, sourceName, args, body) 31 | case "secret": 32 | secretParse(&volume, sourceName, args, body) 33 | case "pvc": 34 | pvcParse(&volume, sourceName, args, body) 35 | default: 36 | othersParse(&volume, vt, sourceName, []string{}, body) 37 | } 38 | return volume 39 | } 40 | 41 | func VolumesParse(d *config.Directive, spec *v1.PodSpec) { 42 | for _, body := range d.Body { 43 | volume := volumeParse(body.Name, body.Args, body.Body) 44 | addVolume(spec, volume) 45 | } 46 | } 47 | 48 | func addVolume(spec *v1.PodSpec, volume v1.Volume) { 49 | for _, v := range spec.Volumes { 50 | if v.Name == volume.Name { 51 | return 52 | } 53 | } 54 | spec.Volumes = append(spec.Volumes, volume) 55 | } 56 | 57 | func VolumeParse(d *config.Directive, spec *v1.PodSpec) { 58 | volume := volumeParse(d.Args[0], d.Args[1:], d.Body) 59 | addVolume(spec, volume) 60 | } 61 | -------------------------------------------------------------------------------- /install/cni/customer.go: -------------------------------------------------------------------------------- 1 | package cni 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/cobrax" 6 | "github.com/ihaiker/vik8s/config" 7 | "github.com/ihaiker/vik8s/libs/ssh" 8 | "github.com/ihaiker/vik8s/libs/utils" 9 | "github.com/spf13/cobra" 10 | "net/url" 11 | "path/filepath" 12 | ) 13 | 14 | type Customer struct { 15 | Urls []string `flag:"url" help:"User-defined network plugin URL, if used for kubectl apply -f "` 16 | Files []string `flag:"file" help:"User-defined network plugin file location, if used for kubectl apply -f "` 17 | } 18 | 19 | func (f *Customer) Name() string { 20 | return "customer" 21 | } 22 | 23 | func (f *Customer) Flags(cmd *cobra.Command) { 24 | err := cobrax.Flags(cmd, f, "", "") 25 | utils.Panic(err, "set customer flag") 26 | } 27 | 28 | func (f *Customer) Apply(configure *config.Configuration, node *ssh.Node) { 29 | utils.Assert(len(f.Files) != 0 || len(f.Urls) != 0, 30 | "No custom network plugin found,see: --url or --file ") 31 | 32 | remote := node.Vik8s("yaml/cni/customer") 33 | err := node.HideLog().Cmd(fmt.Sprintf("rm -rf %s | mkdir -p %s", remote, remote)) 34 | utils.Panic(err, "mkdir customer network config file directory") 35 | 36 | for _, file := range f.Files { 37 | name := filepath.Base(file) 38 | err := node.Scp(file, filepath.Join(remote, name)) 39 | utils.Panic(err, "apply customer network") 40 | } 41 | 42 | for _, urlString := range f.Urls { 43 | u, err := url.Parse(urlString) 44 | utils.Panic(err, "--url %s", urlString) 45 | name := filepath.Base(u.Path) 46 | err = node.Cmd(fmt.Sprintf("curl -o %s %s", filepath.Join(remote, name), urlString)) 47 | } 48 | 49 | err = node.CmdStdout(fmt.Sprintf("kubectl apply -k %s", remote)) 50 | utils.Panic(err, "apply customer network") 51 | } 52 | 53 | func (f *Customer) Clean(node *ssh.Node) { 54 | remote := node.Vik8s("yaml/cni/customer") 55 | _ = node.CmdStdout(fmt.Sprintf("kubectl delete -k %s", remote)) 56 | } 57 | -------------------------------------------------------------------------------- /install/etcd/reset.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/config" 5 | "github.com/ihaiker/vik8s/libs/ssh" 6 | "github.com/ihaiker/vik8s/libs/utils" 7 | ) 8 | 9 | func ResetCluster(configure *config.Configuration, node *ssh.Node) { 10 | 11 | if utils.Search(configure.ETCD.Nodes, node.Host) != -1 { 12 | removeEtcdMember(configure, node) 13 | configure.ETCD.RemoveNode(node.Host) 14 | } 15 | 16 | cleanEtcdData(configure, node) 17 | 18 | if len(configure.ETCD.Nodes) > 0 { 19 | otherNode := configure.Hosts.MustGet(configure.ETCD.Nodes[0]) 20 | showClusterStatus(otherNode) 21 | } else { 22 | utils.Line("all etcd node remove") 23 | } 24 | } 25 | 26 | func removeEtcdMember(configure *config.Configuration, node *ssh.Node) { 27 | if len(configure.ETCD.Nodes) == 1 { 28 | return 29 | } 30 | master := configure.Hosts.MustGet(utils.Any(configure.ETCD.Nodes, node.Host)) 31 | 32 | id, err := master.Sudo().CmdString(Etcdctl("member list | grep " + node.Host + ":2380 | awk -F',' '{print $1}'")) 33 | utils.Panic(err, "etcd list member") 34 | 35 | if id != "" { 36 | node.Logger("remove etcd node %s", node.Host) 37 | if len(configure.ETCD.Nodes) != 1 { 38 | node.Logger("remove etcd member %s", id) 39 | err = master.Sudo().CmdPrefixStdout(Etcdctl("member remove " + id)) 40 | utils.Panic(err, "etcd remove member") 41 | } 42 | } else { 43 | node.Logger("this etcd node not found: %s", node.Host) 44 | } 45 | } 46 | 47 | func cleanEtcdData(configure *config.Configuration, node *ssh.Node) { 48 | node.Logger("remove docker container vik8s-etcd") 49 | _ = node.Sudo().CmdPrefixStdout("docker rm -vf vik8s-etcd") 50 | 51 | node.Logger("remove etcd member data %s", configure.ETCD.DataRoot) 52 | _ = node.Sudo().CmdPrefixStdout("rm -rf " + configure.ETCD.DataRoot) 53 | 54 | node.Logger("remove etcd config data %s", configure.ETCD.CertsDir) 55 | _ = node.Sudo().CmdPrefixStdout("rm -rf " + configure.ETCD.CertsDir) 56 | } 57 | -------------------------------------------------------------------------------- /reduce/kube/secret.go: -------------------------------------------------------------------------------- 1 | package kube 2 | 3 | import ( 4 | "bytes" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | "github.com/ihaiker/vik8s/reduce/asserts" 7 | "github.com/ihaiker/vik8s/reduce/config" 8 | "github.com/ihaiker/vik8s/reduce/plugins" 9 | v1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | func secretToString(secret *v1.Secret) string { 14 | w := config.Writer(0) 15 | w.Writer(out(secret.TypeMeta, secret.ObjectMeta)) 16 | if secret.Type != "" { 17 | w.Line("type:", string(secret.Type)) 18 | } 19 | if len(secret.Data) > 0 { 20 | w.Line("data:") 21 | for label, value := range secret.Data { 22 | if bytes.IndexByte(value, '\n') == -1 { 23 | w.Indent(1).Writer(label, ": ", string(value)).Enter() 24 | } else { 25 | w.Indent(1).Writer(label, ": |-").Enter() 26 | w.Writer(config.ToString(value, 4)) 27 | } 28 | } 29 | } 30 | return w.String() 31 | } 32 | 33 | func secretParse(version, prefix string, directive *config.Directive) metav1.Object { 34 | asserts.ArgsMin(directive, 1) 35 | 36 | secret := &v1.Secret{ 37 | TypeMeta: metav1.TypeMeta{ 38 | Kind: "Secret", 39 | APIVersion: v1.SchemeGroupVersion.String(), 40 | }, 41 | } 42 | asserts.MetadataIndex(secret.GetObjectMeta(), directive, 2) 43 | 44 | if st := utils.Index(directive.Args, 1); st != "" { 45 | secret.Type = v1.SecretType(st) 46 | } 47 | secret.Data = make(map[string][]byte) 48 | for _, d := range directive.Body { 49 | secret.Data[d.Name] = []byte(d.Args[0]) 50 | } 51 | 52 | return secret 53 | } 54 | 55 | var Secret = plugins.ReduceHandler{ 56 | Names: []string{"secret", "Secret"}, Handler: secretParse, 57 | Demo: ` 58 | secret data-config-1 [secretType] { 59 | datakey ZGF0YXZhbHVlCg==; 60 | password aGFpa2VyOmFiZDEyMzEyMzEyMwo=; 61 | } 62 | secret data-config-2 { 63 | labels { 64 | label1 value1; 65 | } 66 | label label2 value2; 67 | 68 | data-key-1 dmFsdWUtMQo=; 69 | data-key-2 dmFsdWUtMgo=; 70 | } 71 | `, 72 | } 73 | -------------------------------------------------------------------------------- /cmd/reduce.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | "github.com/ihaiker/vik8s/reduce/kube" 7 | "github.com/ihaiker/vik8s/reduce/plugins" 8 | "github.com/spf13/cobra" 9 | "io/ioutil" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | var reduceCmd = &cobra.Command{ 15 | Use: "reduce", Short: "Simplify kubernetes configure file", 16 | Args: cobra.ExactArgs(1), 17 | PreRunE: configLoad(none), 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | if utils.NotExists(args[0]) { 20 | return fmt.Errorf("file not found: %s", args[0]) 21 | } 22 | kube := kube.Reduce(args[0]).String() 23 | if output, _ := cmd.Flags().GetString("output"); output != "" { 24 | outputFile, _ := filepath.Abs(output) 25 | return ioutil.WriteFile(outputFile, []byte(kube), 0600) 26 | } else { 27 | fmt.Println(kube) 28 | return nil 29 | } 30 | }, 31 | } 32 | 33 | var reduceDemoCmd = &cobra.Command{ 34 | Use: "demo", Short: "show config demo", 35 | Args: cobra.ExactValidArgs(1), ValidArgs: []string{}, 36 | PreRunE: configLoad(none), 37 | Run: func(cmd *cobra.Command, args []string) { 38 | for _, m := range plugins.Manager { 39 | for _, name := range m.Names { 40 | if name == args[0] { 41 | fmt.Println(m.Demo) 42 | } 43 | } 44 | } 45 | for _, kind := range kube.ReduceKinds { 46 | for _, name := range kind.Names { 47 | if name == args[0] { 48 | fmt.Println(kind.Demo) 49 | } 50 | } 51 | } 52 | }, 53 | } 54 | 55 | func init() { 56 | reduceCmd.Flags().StringP("output", "o", "", "Output content to file") 57 | plugins.Load() 58 | for _, m := range plugins.Manager { 59 | reduceDemoCmd.ValidArgs = append(reduceDemoCmd.ValidArgs, m.Names...) 60 | } 61 | for _, kind := range kube.ReduceKinds { 62 | reduceDemoCmd.ValidArgs = append(reduceDemoCmd.ValidArgs, kind.Names...) 63 | } 64 | reduceDemoCmd.Long = "Args: \n " + strings.Join(reduceDemoCmd.ValidArgs, ", ") 65 | reduceCmd.AddCommand(reduceDemoCmd) 66 | rootCmd.AddCommand(reduceCmd) 67 | } 68 | -------------------------------------------------------------------------------- /ingress/nginx.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/cobrax" 6 | "github.com/ihaiker/vik8s/install/repo" 7 | "github.com/ihaiker/vik8s/libs/ssh" 8 | "github.com/ihaiker/vik8s/libs/utils" 9 | "github.com/ihaiker/vik8s/reduce" 10 | "github.com/spf13/cobra" 11 | "os" 12 | ) 13 | 14 | type nginx struct { 15 | Repo repo.Repo 16 | Version string `help:""` 17 | 18 | HostNetwork bool `flag:"host-network" help:"deploy pod use hostNetwork"` 19 | NodePortHttp int `flag:"nodeport" help:"the ingress-nginx http 80 service nodeport, 0: automatic allocation, -1: disable" def:"-1"` 20 | NodePortHttps int `flag:"nodeport-https" help:"the ingress-nginx https 443 service nodeport, 0: automatic allocation, -1: disable" def:"-1"` 21 | 22 | Replicas int `help:"ingress-nginx pod replicas number" def:"1"` 23 | NodeSelectors map[string]string `flag:"node.selector" help:"Deployment.nodeSelector"` 24 | } 25 | 26 | func Nginx() Ingress { 27 | return &nginx{ 28 | Version: "0.30.0", 29 | HostNetwork: false, 30 | NodePortHttp: -1, NodePortHttps: -1, Replicas: 1, 31 | } 32 | } 33 | 34 | func (n *nginx) Name() string { 35 | return "nginx" 36 | } 37 | 38 | func (n *nginx) Description() string { 39 | return fmt.Sprintf("install kubernetes/ingress-nginx ( 0.33.0 ), more info see https://github.com/kubernetes/ingress-nginx") 40 | } 41 | 42 | func (n *nginx) Flags(cmd *cobra.Command) { 43 | err := cobrax.Flags(cmd, n, "", "VIK8S_INGRESS_NGINX") 44 | utils.Panic(err, "set nginx ingress flags") 45 | } 46 | 47 | func (n *nginx) Apply(master *ssh.Node) { 48 | n.Repo.QuayIO("ingress-nginx") 49 | //name := "yaml/ingress/nginx.yaml" 50 | //tools.MustScpAndApplyAssert(master, name, n) 51 | name := "yaml/ingress/nginx.conf" 52 | err := reduce.ApplyAssert(master, name, n) 53 | utils.Panic(err, "apply nginx ingress") 54 | } 55 | 56 | func (n *nginx) Delete(master *ssh.Node) { 57 | err := master.CmdOutput("kubectl delete namespaces ingress-nginx ", os.Stdout) 58 | utils.Panic(err, "delete nginx ingress") 59 | } 60 | -------------------------------------------------------------------------------- /reduce/config/chars_iterator.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "github.com/ihaiker/vik8s/libs/utils" 7 | "os" 8 | ) 9 | 10 | type charIterator struct { 11 | scanner *bufio.Scanner 12 | currentLine int 13 | lastCatchChar string 14 | } 15 | 16 | func newCharIterator(filename string) *charIterator { 17 | f, err := os.Open(filename) 18 | utils.Panic(err, "open file %s", filename) 19 | s := bufio.NewScanner(f) 20 | s.Split(bufio.ScanRunes) 21 | return &charIterator{scanner: s, currentLine: 1} 22 | } 23 | 24 | func newCharIteratorWithBytes(bs []byte) *charIterator { 25 | s := bufio.NewScanner(bytes.NewBuffer(bs)) 26 | s.Split(bufio.ScanRunes) 27 | return &charIterator{scanner: s, currentLine: 1} 28 | } 29 | 30 | func (self *charIterator) nextFilter(filter CharFilter) (word string, line int, has bool) { 31 | previous := "" 32 | for { 33 | if word, line, has = self.next(); !has { 34 | return 35 | } else if filter(word, previous) { 36 | return 37 | } else { 38 | previous = word 39 | } 40 | } 41 | } 42 | 43 | //不包括最后一个 44 | func (self *charIterator) nextTo(filter CharFilter, includeLast bool) (word string, line int, has bool) { 45 | lastChar := "" 46 | for { 47 | if lastChar, line, has = self.next(); !has { 48 | return 49 | } else if filter(lastChar, word) { 50 | if includeLast { 51 | word += lastChar 52 | } else { 53 | self.lastCatchChar = lastChar 54 | } 55 | return 56 | } else { 57 | word += lastChar 58 | } 59 | } 60 | } 61 | 62 | func (it *charIterator) next() (char string, line int, has bool) { 63 | if it.lastCatchChar != "" { 64 | char = it.lastCatchChar 65 | line = it.currentLine 66 | has = true 67 | it.lastCatchChar = "" 68 | return 69 | } 70 | 71 | if has = it.scanner.Scan(); !has { 72 | return 73 | } 74 | char = it.scanner.Text() 75 | line = it.currentLine 76 | if char == "\n" { 77 | it.currentLine += 1 78 | } 79 | if char == "\\" { 80 | nextChar := "" 81 | nextChar, line, has = it.next() 82 | char = char + nextChar 83 | } 84 | return 85 | } 86 | -------------------------------------------------------------------------------- /install/cri/docker/config_test.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/config" 5 | "github.com/ihaiker/vik8s/install/paths" 6 | "github.com/stretchr/testify/suite" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | ) 12 | 13 | type TestDockerSuite struct { 14 | suite.Suite 15 | } 16 | 17 | func (p TestDockerSuite) SetupTest() { 18 | paths.ConfigDir = "./_testdata" 19 | paths.Cloud = "test" 20 | err := os.MkdirAll(paths.ConfigDir, os.ModePerm) 21 | p.Nil(err) 22 | } 23 | 24 | func (p TestDockerSuite) TearDownTest() { 25 | _ = os.RemoveAll(filepath.Join(paths.ConfigDir)) 26 | } 27 | 28 | func (t *TestDockerSuite) TestEnableTLS() { 29 | cfg := config.DefaultDockerConfiguration() 30 | cfg.TLS = new(config.DockerCertsConfiguration) 31 | cfg.TLS.Enable = true 32 | err := Config(cfg) 33 | t.Nil(err) 34 | t.False(cfg.TLS.Custom) 35 | t.FileExists(cfg.TLS.CaPrivateKeyPath, "ca key not gen") 36 | t.FileExists(cfg.TLS.CaCertPath, "ca key not gen") 37 | t.FileExists(cfg.TLS.ClientKeyPath, "client key not gen") 38 | t.FileExists(cfg.TLS.ClientCertPath, "client cert not gen") 39 | } 40 | 41 | func (t *TestDockerSuite) TestEnableCustomTLS() { 42 | cfg := config.DefaultDockerConfiguration() 43 | cfg.TLS = new(config.DockerCertsConfiguration) 44 | cfg.TLS.Enable = true 45 | cfg.TLS.CaCertPath = paths.Join(DockerCertsPath, "ca.pem") 46 | 47 | err := Config(cfg) 48 | t.Nil(err) 49 | t.True(cfg.TLS.Custom, "custom is not true") 50 | } 51 | 52 | func (t *TestDockerSuite) TestDaemonJson() { 53 | path := paths.Join(DockerConfigPath, "daemon.json") 54 | err := os.MkdirAll(filepath.Dir(path), os.ModePerm) 55 | t.Nil(err, "mkdir dir error ", err) 56 | 57 | err = ioutil.WriteFile(path, []byte(`{}`), 0655) 58 | t.Nil(err, "write test deamon.json") 59 | 60 | cfg := config.DefaultDockerConfiguration() 61 | cfg.DaemonJson = path 62 | err = Config(cfg) 63 | t.Nil(err, "config daemon.json error") 64 | t.Nil(cfg.TLS) 65 | //t.False(cfg.TLS.Enable) 66 | } 67 | func TestDockerConfig(t *testing.T) { 68 | suite.Run(t, new(TestDockerSuite)) 69 | } 70 | -------------------------------------------------------------------------------- /yaml/kubeadm-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubeadm.k8s.io/v1beta2 2 | kind: ClusterConfiguration 3 | kubernetesVersion: {{.Kubeadm.Version}} 4 | controlPlaneEndpoint: "{{.Kubeadm.ApiServer}}:6443" 5 | imageRepository: {{.Kubeadm.Repo}} 6 | networking: 7 | dnsDomain: cluster.local 8 | podSubnet: {{.Kubeadm.PodCIDR}} 9 | serviceSubnet: {{.Kubeadm.SvcCIDR}} 10 | apiServer: 11 | certSANs: 12 | - 127.0.0.1 13 | - {{.Kubeadm.ApiServer}} 14 | - {{.Kubeadm.ApiServerVIP}}{{ range .Masters }} 15 | - {{.Hostname}} 16 | - {{.Host}}{{ end }} 17 | extraArgs: 18 | feature-gates: TTLAfterFinished=true 19 | extraVolumes: 20 | - name: localtime 21 | hostPath: /etc/localtime 22 | mountPath: /etc/localtime 23 | readOnly: true 24 | pathType: File 25 | controllerManager: 26 | extraArgs: 27 | feature-gates: TTLAfterFinished=true 28 | extraVolumes: 29 | - hostPath: /etc/localtime 30 | mountPath: /etc/localtime 31 | name: localtime 32 | readOnly: true 33 | pathType: File 34 | scheduler: 35 | extraArgs: 36 | feature-gates: TTLAfterFinished=true 37 | extraVolumes: 38 | - hostPath: /etc/localtime 39 | mountPath: /etc/localtime 40 | name: localtime 41 | readOnly: true 42 | pathType: File 43 | 44 | dns: 45 | type: CoreDNS 46 | 47 | {{ if .Etcd.External }} 48 | etcd: 49 | external: 50 | endpoints: {{ range .Etcd.Endpoints }} 51 | - {{.}}{{ end }} 52 | caFile: "/etc/kubernetes/pki/etcd/ca.crt" 53 | certFile: "/etc/kubernetes/pki/etcd/apiserver-etcd-client.crt" 54 | keyFile: "/etc/kubernetes/pki/etcd/apiserver-etcd-client.key" 55 | {{ end }} 56 | 57 | --- 58 | apiVersion: kubeproxy.config.k8s.io/v1alpha1 59 | kind: KubeProxyConfiguration 60 | mode: "ipvs" 61 | #ipvs: 62 | # excludeCIDRs: 63 | # - "excludeCIDR" 64 | --- 65 | apiVersion: kubeadm.k8s.io/v1beta2 66 | kind: InitConfiguration 67 | localAPIEndpoint: 68 | advertiseAddress: "{{(index .Masters 0).Host }}" 69 | 70 | --- 71 | apiVersion: kubelet.config.k8s.io/v1beta1 72 | kind: KubeletConfiguration 73 | cgroupDriver: systemd 74 | -------------------------------------------------------------------------------- /config/etcd.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "time" 6 | ) 7 | 8 | type ETCDConfiguration struct { 9 | Token string `ngx:"token" flag:"token" help:"cluster token"` 10 | Nodes []string `ngx:"nodes" flag:"-"` 11 | Version string `ngx:"version" help:"etcd version"` 12 | ServerCertExtraSans []string `ngx:"server-cert-extra-sans" help:"optional extra Subject Alternative Names for the etcd server signing cert, can be multiple comma separated DNS names or IPs"` 13 | 14 | CertsValidity time.Duration `ngx:"certs-validity" help:"Certificate validity time"` 15 | CertsDir string `ngx:"certs-dir" help:"certificates directory"` 16 | DataRoot string `ngx:"data" help:"etcd data dir"` 17 | 18 | Snapshot string `ngx:"snapshot" help:"Etcd v3 snapshot (local disk) file used to initialize member"` 19 | RemoteSnapshot string `ngx:"remote-snapshot" help:"Etcd v3 snapshot (remote disk at first node) file used to initialize member"` 20 | 21 | Repo string `ngx:"repo" flag:"repo" help:"the repo url"` 22 | } 23 | 24 | func (this *ETCDConfiguration) RemoveNode(node string) bool { 25 | idx := utils.Search(this.Nodes, node) 26 | if idx != -1 { 27 | this.Nodes = append(this.Nodes[0:idx], this.Nodes[idx+1:]...) 28 | } 29 | return idx != -1 30 | } 31 | 32 | type ExternalETCDConfiguration struct { 33 | Endpoints []string `ngx:"endpoints" flag:"endpoint"` 34 | CaFile string `ngx:"ca" flag:"ca"` 35 | Cert string `ngx:"cert" flag:"cert"` 36 | Key string `ngx:"key" flag:"key"` 37 | } 38 | 39 | func (this *ExternalETCDConfiguration) Set(name, value string) { 40 | switch name { 41 | case "ca": 42 | this.CaFile = value 43 | case "cert": 44 | this.Cert = value 45 | case "key": 46 | this.Key = value 47 | } 48 | } 49 | 50 | func DefaultETCDConfiguration() *ETCDConfiguration { 51 | d, _ := time.ParseDuration("876000h") 52 | return &ETCDConfiguration{ 53 | Version: "v3.4.13", 54 | CertsValidity: d, 55 | CertsDir: "/etc/etcd/pki", 56 | DataRoot: "/var/lib/etcd", 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /install/cri/docker/config.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | dockercerts "github.com/ihaiker/vik8s/certs/docker" 5 | "github.com/ihaiker/vik8s/config" 6 | "github.com/ihaiker/vik8s/install/paths" 7 | "github.com/ihaiker/vik8s/libs/utils" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | const DockerConfigPath = "docker" 14 | const DockerCertsPath = DockerConfigPath + "/certs.d" 15 | 16 | func dockerGenCert(this *config.DockerConfiguration) (err error) { 17 | if this.TLS != nil && this.TLS.Enable { 18 | destDir := paths.Join(DockerCertsPath) 19 | 20 | //用户使用自己定义的证书文件 21 | this.TLS.Custom = this.TLS.CaCertPath != "" 22 | 23 | if !this.TLS.Custom { 24 | utils.Logs("generator docker certs") 25 | if this.TLS, err = dockercerts.GenerateBootstrapCertificates(destDir); err != nil { 26 | return err 27 | } 28 | this.TLS.Enable = true 29 | } 30 | 31 | if !strings.HasPrefix(this.TLS.CaCertPath, destDir) { 32 | utils.Logs("copy certificates files to .vik8s") 33 | if this.TLS.CaCertPath, err = utils.Copyto(this.TLS.CaCertPath, destDir); err != nil { 34 | return 35 | } 36 | if this.TLS.ClientKeyPath, err = utils.Copyto(this.TLS.ClientKeyPath, destDir); err != nil { 37 | return 38 | } 39 | if this.TLS.ClientCertPath, err = utils.Copyto(this.TLS.ClientCertPath, destDir); err != nil { 40 | return 41 | } 42 | } 43 | _ = os.Symlink(this.TLS.CaCertPath, filepath.Join(destDir, "ca.pem")) 44 | _ = os.Symlink(this.TLS.ClientKeyPath, filepath.Join(destDir, "key.pem")) 45 | _ = os.Symlink(this.TLS.ClientCertPath, filepath.Join(destDir, "cert.pem")) 46 | } 47 | return 48 | } 49 | 50 | func Config(this *config.DockerConfiguration) error { 51 | //copy the daemon.json to local storage. 52 | if this.DaemonJson != "" { 53 | destDaemonJson := paths.Join(DockerConfigPath, "daemon.json") 54 | if err := os.MkdirAll(filepath.Dir(destDaemonJson), os.ModePerm); err != nil { 55 | return err 56 | } 57 | if destDaemonJson != this.DaemonJson { 58 | utils.Panic(utils.Copy(this.DaemonJson, destDaemonJson), "copy file") 59 | this.DaemonJson = destDaemonJson 60 | } 61 | return nil 62 | } 63 | return dockerGenCert(this) 64 | } 65 | -------------------------------------------------------------------------------- /config/docker.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type DockerCertsConfiguration struct { 4 | Enable bool `ngx:"enable" help:"Use TLS"` 5 | Custom bool `flag:"-"` 6 | 7 | CaCertPath string `ngx:"ca" flag:"ca" help:"Trust certs signed only by this CA"` 8 | CaPrivateKeyPath string `ngx:"ca-key" flag:"-"` 9 | 10 | ServerCertPath string `ngx:"server" flag:"cert" help:"Path to TLS certificate file"` 11 | ServerKeyPath string `ngx:"server-key" flag:"key" help:"Path to TLS key file"` 12 | 13 | ClientCertPath string `ngx:"cert" flag:"-"` 14 | ClientKeyPath string `ngx:"key" flag:"-"` 15 | } 16 | 17 | type DockerStorageConfiguration struct { 18 | Driver string `ngx:"driver" help:"storage driver to use"` 19 | Opt []string `ngx:"opt" help:"Storage driver options"` 20 | } 21 | 22 | type DockerDNSConfiguration struct { 23 | List []string `ngx:"list" help:"DNS server to use"` 24 | Opt []string `ngx:"opt" help:"DNS options to use"` 25 | Search []string `ngx:"search" help:"DNS search domains to use"` 26 | } 27 | 28 | type DockerConfiguration struct { 29 | Version string `ngx:"version" help:"docker version"` 30 | StraitVersion bool `ngx:"strait-version" help:"Strict check DOCKER version if inconsistent will upgrade"` 31 | SkipIfExist bool `ngx:"skip-if-exists" help:"skip install and change anything if exists docker."` 32 | DataRoot string `ngx:"data-root" help:"docker data root"` 33 | Hosts []string `ngx:"hosts" help:"Daemon socket(s) to connect to" def:"fd://"` 34 | DaemonJson string `ngx:"daemon-json" help:"docker cfg file, if set this option, other option will ignore."` 35 | 36 | InsecureRegistries []string `help:"it replaces the daemon insecure registries with a new set of insecure registries."` 37 | RegistryMirrors []string `ngx:"registry-mirrors" help:"preferred DockerConfiguration registry mirror"` 38 | 39 | Storage *DockerStorageConfiguration `ngx:"storage" flag:"storage"` 40 | DNS *DockerDNSConfiguration `flag:"dns" ngx:"dns" ` 41 | TLS *DockerCertsConfiguration `ngx:"tls" flag:"tls"` 42 | } 43 | 44 | func DefaultDockerConfiguration() *DockerConfiguration { 45 | return &DockerConfiguration{ 46 | Version: "v19.03.15", 47 | DataRoot: "/var/lib/docker", 48 | Hosts: []string{"fd://"}, 49 | StraitVersion: false, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /certs/docker/generate.go: -------------------------------------------------------------------------------- 1 | package dockercert 2 | 3 | import ( 4 | "crypto/x509" 5 | "github.com/ihaiker/vik8s/certs" 6 | "github.com/ihaiker/vik8s/config" 7 | "github.com/ihaiker/vik8s/libs/ssh" 8 | "github.com/ihaiker/vik8s/libs/utils" 9 | "path/filepath" 10 | ) 11 | 12 | func GenerateBootstrapCertificates(dir string) (cfg *config.DockerCertsConfiguration, err error) { 13 | cfg = &config.DockerCertsConfiguration{Custom: false, Enable: true} 14 | 15 | if cfg.CaCertPath, cfg.CaPrivateKeyPath = certs.PathsForCertAndKey(dir, "ca"); utils.NotExists(cfg.CaCertPath) || utils.NotExists(cfg.CaPrivateKeyPath) { 16 | config := certs.NewConfig("ca") 17 | cert, key := certs.NewCertificateAuthority(config) 18 | certs.WriteCertAndKey(dir, "ca", cert, key) 19 | } 20 | caCert, caKey := certs.TryLoadCertAndKeyFromDisk(dir, "ca") 21 | 22 | if cfg.ClientCertPath, cfg.ClientKeyPath = certs.PathsForCertAndKey(dir, "client"); utils.NotExists(cfg.ClientCertPath) || utils.NotExists(cfg.ClientKeyPath) { 23 | config := certs.NewConfig("client") 24 | { 25 | config.Organization = []string{"system:client"} 26 | config.Usages = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} 27 | config.AltNames = *certs.GetAltNames([]string{}, "client") 28 | } 29 | clientCert, clientKey := certs.NewCertAndKey(caCert, caKey, config) 30 | certs.WriteCertAndKey(dir, "client", clientCert, clientKey) 31 | } 32 | return 33 | } 34 | 35 | func GenerateServerCertificates(node *ssh.Node, options *config.DockerCertsConfiguration) (serverCertPath, serverKeyPath string, err error) { 36 | dir := filepath.Dir(options.CaCertPath) 37 | caCert, caKey := certs.TryLoadCertAndKeyFromDisk(dir, "ca") 38 | 39 | if serverCertPath, serverKeyPath = certs.PathsForCertAndKey(dir, "server-"+node.Host); utils.NotExists(serverCertPath) || utils.NotExists(serverKeyPath) { 40 | config := certs.NewConfig("server") 41 | { 42 | config.Organization = []string{"system:server"} 43 | config.Usages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} 44 | config.AltNames = *certs.GetAltNames([]string{node.Hostname, node.Host, "127.0.0.1", "localhost"}, "server") 45 | } 46 | serverCert, serverKey := certs.NewCertAndKey(caCert, caKey, config) 47 | certs.WriteCertAndKey(dir, "server-"+node.Host, serverCert, serverKey) 48 | } 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /install/bases/install.go: -------------------------------------------------------------------------------- 1 | package bases 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/libs/ssh" 6 | "github.com/ihaiker/vik8s/libs/utils" 7 | "strings" 8 | ) 9 | 10 | func Install(mod, version string, node *ssh.Node) { 11 | node.Logger("install %s %s", mod, version) 12 | if node.IsCentOS() { 13 | installCentOS(mod, version, node) 14 | } else { 15 | installUbuntu(mod, version, node) 16 | } 17 | } 18 | 19 | func installUbuntu(mod, version string, node *ssh.Node) { 20 | installVersion, err := GetPackageVersion(node, mod) 21 | utils.Panic(err, "search package") 22 | 23 | if installVersion != "" { 24 | node.Logger("%s installed %s", mod, installVersion) 25 | } 26 | if (version != "" && installVersion == version) || (version == "" && installVersion != "") { 27 | return 28 | } 29 | 30 | if version == "" { 31 | err = node.Sudo().CmdWatcher(fmt.Sprintf("apt-get install %s", mod), utils.Stdout(node.Prefix())) 32 | } else { 33 | err = node.Sudo().CmdWatcher(fmt.Sprintf("apt-get install %s %s", mod, version), utils.Stdout(node.Prefix())) 34 | } 35 | utils.Panic(err, "install %s %s", mod, version) 36 | } 37 | 38 | func installCentOS(mod, version string, node *ssh.Node) { 39 | installVersion, err := GetPackageVersion(node, mod) 40 | utils.Panic(err, "search rpm version") 41 | 42 | if installVersion != "" { 43 | node.Logger("%s installed version: %s", mod, installVersion) 44 | } 45 | if (version != "" && installVersion == version) || (version == "" && installVersion != "") { 46 | return 47 | } 48 | 49 | if version == "" { 50 | err = node.Sudo().Retries(3).CmdWatcher(fmt.Sprintf("yum install -y %s", mod), utils.Stdout(node.Prefix())) 51 | } else { 52 | err = node.Sudo().Retries(3).CmdWatcher(fmt.Sprintf("yum install -y %s-%s", mod, version), utils.Stdout(node.Prefix())) 53 | } 54 | utils.Panic(err, "install package %s %s", mod, version) 55 | } 56 | 57 | func GetPackageVersion(node *ssh.Node, mod string) (version string, err error) { 58 | if node.IsCentOS() { 59 | version, err = node.Sudo().HideLog().CmdString(fmt.Sprintf("rpm -qi %s | grep Version | awk '{printf $3}'", mod)) 60 | } else { 61 | version, err = node.Sudo().HideLog().CmdString(fmt.Sprintf("dpkg-query -s %s | grep Version | awk '{print $2}'", mod)) 62 | } 63 | if err != nil && !strings.Contains(err.Error(), "not installed") { 64 | return 65 | } 66 | return 67 | } 68 | 69 | func Installs(node *ssh.Node, mods ...string) { 70 | for _, mod := range mods { 71 | Install(mod, "", node) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /libs/ssh/cmd.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "bytes" 5 | "github.com/fatih/color" 6 | "github.com/ihaiker/vik8s/libs/utils" 7 | "io" 8 | "os" 9 | ) 10 | 11 | func (node *Node) Cmd(command string) error { 12 | return node.CmdWatcher(command, func(stdout io.Reader) error { return nil }) 13 | } 14 | 15 | func (node *Node) CmdWatcher(command string, watcher StreamWatcher) (err error) { 16 | defer node.reset() 17 | if node.isSudo() { 18 | command = "sudo " + command 19 | } 20 | if node.isShowLogger() { 21 | node.Logger("run command: %s", command) 22 | } 23 | retries := node.retries 24 | if retries == 0 { 25 | retries = 1 26 | } 27 | for i := 0; i < retries; i++ { 28 | if err = node.stream(command, watcher); err == nil { 29 | return 30 | } else if retries != 1 { 31 | node.Logger(color.New(color.FgHiRed).Sprintf("executor: [%s], error: %s", command, err.Error())) 32 | } 33 | } 34 | return 35 | } 36 | func (node *Node) CmdOutput(command string, output io.Writer) error { 37 | return node.CmdWatcher(command, func(stdout io.Reader) error { 38 | _, err := io.Copy(output, stdout) 39 | return err 40 | }) 41 | } 42 | 43 | func (node *Node) CmdStdout(command string) error { 44 | return node.CmdOutput(command, os.Stdout) 45 | } 46 | 47 | func (node *Node) CmdPrefixStdout(command string) error { 48 | return node.CmdWatcher(command, utils.Stdout(node.Prefix())) 49 | } 50 | 51 | func (node *Node) CmdBytes(command string) (*bytes.Buffer, error) { 52 | output := bytes.NewBufferString("") 53 | err := node.CmdOutput(command, output) 54 | return output, err 55 | } 56 | 57 | func (node *Node) CmdString(command string) (string, error) { 58 | if outBytes, err := node.CmdBytes(command); err == nil { 59 | out := utils.Trdn(outBytes.Bytes()) 60 | return string(out), nil 61 | } else { 62 | return "", err 63 | } 64 | } 65 | 66 | const ( 67 | Sudo = 0b10 68 | Log = 0b01 69 | None = 0b00 70 | ) 71 | 72 | func (node *Node) Sudo() *Node { 73 | node.flag = node.flag | Sudo 74 | return node 75 | } 76 | func (node *Node) HideLog() *Node { 77 | node.flag = node.flag | Log 78 | return node 79 | } 80 | 81 | func (node *Node) Retries(retries int) *Node { 82 | node.retries = retries 83 | return node 84 | } 85 | 86 | func (node *Node) isSudo() bool { 87 | return node.flag&Sudo == Sudo && node.User != "root" 88 | } 89 | 90 | func (node *Node) isShowLogger() bool { 91 | return node.flag&Log != Log 92 | } 93 | func (node *Node) reset() *Node { 94 | node.flag = None 95 | node.retries = 1 96 | return node 97 | } 98 | -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | # QuickStart 2 | 3 | ## 安装 4 | 5 | ### 1、下载二进制文件 6 | 7 | [Github https://github.com/ihaiker/vik8s](https://github.com/ihaiker/ngx/releases/latest) 8 | 9 | 下载和平台相应的二进制包 10 | 11 | 12 | ### 2、编译安装 13 | 14 | ```shell 15 | git clone https://github.com/ihaiker/vik8s 16 | ./scripts/build.sh 17 | cp bin/vik8s /usr/local/bin 18 | ``` 19 | 20 | 21 | 22 | ### 检验版本信息 23 | 24 | 执行 vik8s 将看到一下信息。 25 | 26 | ```shell 27 | very easy install k8s。Build: 2021-11-20 13:39:10, Go: go1.15.6, GitLog: 0d88c1b2296c9cd79d19d0108e99063d68217c65 28 | 29 | Usage: 30 | vik8s [command] 31 | 32 | Available Commands: 33 | bash Run commands uniformly in the cluster 34 | cni define kubernetes network interface 35 | completion generates completion scripts 36 | config Show yaml file used by vik8s deployment cluster 37 | cri defined kubernetes container runtime interface 38 | etcd Install ETCD cluster 39 | help Help about any command 40 | hosts Add Management Host 41 | ingress install kubernetes ingress controller 42 | init Initialize the kubernetes cluster 43 | join join to k8s 44 | reduce Simplify kubernetes configuration file 45 | reset reset kubernetes cluster node 46 | 47 | Flags: 48 | --china Whether domestic network (default true) 49 | -c, --cloud string Multi-kubernetes cluster selection (default "default") 50 | -f, --config string The folder where the configuration file is located (default "~/.vik8s") 51 | -h, --help help for vik8s 52 | -v, --version version for vik8s 53 | 54 | Use "vik8s [command] --help" for more information about a command. 55 | ``` 56 | 57 | ## 主机准备 58 | 59 | | 主机名称 | IP地址 | 60 | | -------- | ------------- | 61 | | master1 | 172.16.100.10 | 62 | | master2 | 172.16.100.11 | 63 | | master3 | 172.16.100.12 | 64 | | node1 | 172.16.100.13 | 65 | | node2 | 172.16.100.14 | 66 | | node3 | 172.16.100.15 | 67 | 68 | 注意:所有主机采用配置root免密登录,并且端口为22。 如果您的主机访问方式存在差异,那您可以查看[主机访问方式管理](./cmds/hosts/index.md) 69 | 70 | 71 | 72 | ## 安装 73 | 74 | 1、master节点安装 75 | 76 | ```shell 77 | $ vik8s init 172.16.100.10 # 初始化集群 78 | $ vik8s join --master 172.16.100.11 172.16.100.12 #添加两个控制节点 79 | ``` 80 | 81 | 2、slave节点安装 82 | 83 | ```shell 84 | vik8s join 172.16.100.13-172.16.100.15 85 | ``` 86 | 87 | 3、网络插件安装 88 | 89 | ```shell 90 | vik8s cni flannel 91 | ``` 92 | 93 | 更多详细信息查看[Kubernetes集群初始化教程](./cmds/k8s/index.md)和[网络插件安装](./cmds/k8s/cni.md) 94 | 95 | -------------------------------------------------------------------------------- /install/tools/nodes.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "github.com/ihaiker/vik8s/libs/ssh" 7 | "github.com/ihaiker/vik8s/libs/utils" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | func SearchLabelNode(master *ssh.Node, labels map[string]string) []string { 13 | utils.Assert(len(labels) > 0, "label is empty") 14 | str := utils.Join(labels, ",", "=") 15 | 16 | out, err := master.CmdString(fmt.Sprintf(`kubectl get nodes -l '%s' -o=jsonpath="{.items[*].status.addresses[1].address}"`, str)) 17 | utils.Panic(err, "get nodes error") 18 | 19 | hasname := strings.TrimSpace(out) 20 | if hasname == "" { 21 | return []string{} 22 | } 23 | return strings.Split(hasname, " ") 24 | } 25 | 26 | func AddNodeLabel(master *ssh.Node, labels map[string]string, nodes ...string) { 27 | for _, node := range nodes { 28 | for label, value := range labels { 29 | err := master.CmdStdout(fmt.Sprintf("kubectl label nodes %s %s=%s", node, label, value)) 30 | utils.Panic(err, "add node %s label %s=%s", node, label, value) 31 | } 32 | } 33 | } 34 | 35 | func RemoveNodeLabel(master *ssh.Node, label string, nodes ...string) { 36 | for _, node := range nodes { 37 | err := master.CmdStdout(fmt.Sprintf("kubectl label nodes %s %s-", node, label)) 38 | utils.Panic(err, "remove node %s label %s", node, label) 39 | } 40 | } 41 | 42 | //根据提供的IP或者hostname选择节点的hostname 43 | func SelectNodeNames(nodes []*ssh.Node, selectNodes []string) []string { 44 | selectNodes = utils.ParseIPS(selectNodes) 45 | selectNodeNames := make([]string, 0) 46 | NEXT: 47 | for _, selectNode := range selectNodes { 48 | for _, node := range nodes { 49 | if node.Hostname == selectNode || node.Host == selectNode { 50 | if utils.Search(selectNodeNames, node.Hostname) == -1 { 51 | selectNodeNames = append(selectNodeNames, node.Hostname) 52 | } 53 | continue NEXT 54 | } 55 | } 56 | utils.Panic(os.ErrNotExist, "node %s", selectNode) 57 | } 58 | return selectNodeNames 59 | } 60 | 61 | func AutoLabelNodes(master *ssh.Node, labels map[string]string, nodeNames ...string) { 62 | labeledNodes := SearchLabelNode(master, labels) 63 | 64 | //check label 65 | for _, labeledNode := range labeledNodes { 66 | utils.Assert(utils.Search(nodeNames, labeledNode) >= 0, 67 | color.RedString("node %s include label %s "), labeledNode, utils.Join(labels, ",", "=")) 68 | } 69 | 70 | //add label 71 | for _, selectNode := range nodeNames { 72 | if utils.Search(labeledNodes, selectNode) == -1 { 73 | AddNodeLabel(master, labels, selectNode) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /reduce/kube/configmap.go: -------------------------------------------------------------------------------- 1 | package kube 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/reduce/asserts" 5 | "github.com/ihaiker/vik8s/reduce/config" 6 | "github.com/ihaiker/vik8s/reduce/plugins" 7 | v1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "strings" 10 | ) 11 | 12 | func out(meta metav1.TypeMeta, om metav1.ObjectMeta) string { 13 | w := config.Writer(0) 14 | w.Line("apiVersion:", meta.APIVersion) 15 | w.Line("kind:", meta.Kind) 16 | w.Line("metadata:") 17 | w.Indent(1).Line("name:", om.Name) 18 | if om.Namespace != "" { 19 | w.Indent(1).Line("namespace:", om.Namespace) 20 | } 21 | if len(om.Labels) > 0 { 22 | w.Indent(1).Line("labels:") 23 | for label, value := range om.Labels { 24 | w.Indent(2).Writer(label, ": ") 25 | w.Writer(value) 26 | w.Enter() 27 | } 28 | } 29 | if len(om.Annotations) > 0 { 30 | w.Indent(1).Line("annotations:") 31 | for label, value := range om.Annotations { 32 | w.Indent(2).Writer(label, ": ", value).Enter() 33 | } 34 | } 35 | return w.String() 36 | } 37 | 38 | func configMapToString(configMap *v1.ConfigMap) string { 39 | w := config.Writer(0) 40 | w.Writer(out(configMap.TypeMeta, configMap.ObjectMeta)) 41 | 42 | if len(configMap.Data) > 0 { 43 | w.Line("data:") 44 | for label, value := range configMap.Data { 45 | if strings.Index(value, "\n") == -1 { 46 | w.Indent(1).Writer(label, ": ", value).Enter() 47 | } else { 48 | w.Indent(1).Writer(label, ": |-").Enter() 49 | w.Writer(config.ToString([]byte(value), 2)) 50 | } 51 | } 52 | } 53 | return w.String() 54 | } 55 | 56 | func configMapParse(version, prefix string, directive *config.Directive) metav1.Object { 57 | asserts.ArgsMin(directive, 1) 58 | configMap := &v1.ConfigMap{ 59 | TypeMeta: metav1.TypeMeta{ 60 | Kind: "ConfigMap", 61 | APIVersion: v1.SchemeGroupVersion.String(), 62 | }, 63 | } 64 | asserts.Metadata(configMap.GetObjectMeta(), directive) 65 | configMap.Data = make(map[string]string) 66 | for _, d := range directive.Body { 67 | configMap.Data[d.Name] = d.Args[0] 68 | } 69 | return configMap 70 | } 71 | 72 | var ConfigMap = plugins.ReduceHandler{ 73 | Names: []string{"configmap", "config", "ConfigMap"}, Handler: configMapParse, 74 | Demo: ` 75 | configmap data-config [label1=value1 ...] { 76 | datakey datavalue; 77 | nginx.conf ' 78 | http { 79 | server { 80 | 81 | } 82 | } 83 | '; 84 | password haiker:abd123123123; 85 | } 86 | 87 | configmap data-config-2 { 88 | labels { 89 | label1 value1; 90 | } 91 | data-key-1 value-1; 92 | data-key-2 value-2; 93 | } 94 | `, 95 | } 96 | -------------------------------------------------------------------------------- /reduce/config/parse.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func expectNextToken(it *tokenIterator, filter CharFilter) ([]string, string, error) { 11 | tokens := make([]string, 0) 12 | for { 13 | if token, _, has := it.next(); has { 14 | if filter(token, "") { 15 | return tokens, token, nil 16 | } 17 | tokens = append(tokens, token) 18 | } else { 19 | return nil, "", os.ErrNotExist 20 | } 21 | } 22 | } 23 | 24 | func subDirectives(it *tokenIterator) ([]*Directive, error) { 25 | directives := make([]*Directive, 0) 26 | for { 27 | token, line, has := it.next() 28 | if !has { 29 | break 30 | } 31 | if token == ";" || token == "}" { 32 | break 33 | } else if token[0] == '#' { //注释 34 | /* 35 | directives = append(directives, &Directive{ 36 | Line: line, Name: "#", Args: []string{token}, 37 | }) 38 | */ 39 | } else { 40 | if args, lastToken, err := expectNextToken(it, In(";", "{")); err != nil { 41 | return nil, err 42 | } else if lastToken == ";" { 43 | if strings.HasSuffix(token, ":") { 44 | token = token[0 : len(token)-1] 45 | } 46 | directives = append(directives, &Directive{ 47 | Line: line, Name: token, Args: args, 48 | }) 49 | } else { 50 | if strings.HasSuffix(token, ":") { 51 | token = token[0 : len(token)-1] 52 | } 53 | directive := &Directive{ 54 | Line: line, Name: token, Args: args, 55 | } 56 | if subDirs, err := subDirectives(it); err != nil { 57 | return nil, fmt.Errorf("line %d, %s ", line, token) 58 | } else { 59 | directive.Body = subDirs 60 | } 61 | directives = append(directives, directive) 62 | } 63 | } 64 | } 65 | return directives, nil 66 | } 67 | 68 | func MustParse(filename string) *Configuration { 69 | cfg, err := Parse(filename) 70 | utils.Panic(err, "parse %s", filename) 71 | return cfg 72 | } 73 | 74 | func Parse(filename string) (cfg *Configuration, err error) { 75 | defer utils.Catch(func(re error) { 76 | err = re 77 | }) 78 | cfg = &Configuration{Name: filename} 79 | it := newTokenIterator(filename) 80 | cfg.Body, err = subDirectives(it) 81 | return 82 | } 83 | 84 | func MustParseWith(filename string, bs []byte) *Configuration { 85 | cfg, err := ParseWith(filename, bs) 86 | utils.Panic(err, "parse config") 87 | return cfg 88 | } 89 | 90 | func ParseWith(filename string, bs []byte) (cfg *Configuration, err error) { 91 | defer utils.Catch(func(re error) { 92 | err = re 93 | }) 94 | cfg = &Configuration{Name: filename} 95 | it := newTokenIteratorWithBytes(bs) 96 | cfg.Body, err = subDirectives(it) 97 | return 98 | } 99 | -------------------------------------------------------------------------------- /install/bases/repos.go: -------------------------------------------------------------------------------- 1 | package bases 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/install/paths" 6 | "github.com/ihaiker/vik8s/libs/ssh" 7 | "github.com/ihaiker/vik8s/libs/utils" 8 | ) 9 | 10 | func setAliRepo(node *ssh.Node) { 11 | if node.IsUbuntu() { 12 | setAliRepoUbuntu(node) 13 | } else { 14 | setAliRepoCentOS(node) 15 | } 16 | } 17 | 18 | //添加一个Repo文件 19 | func AddRepoFile(node *ssh.Node, name string, content []byte) { 20 | var remoteRepoPath string 21 | if node.IsCentOS() { 22 | remoteRepoPath = fmt.Sprintf("/etc/yum.repos.d/%s.repo", name) 23 | } else { 24 | remoteRepoPath = fmt.Sprintf("/etc/apt/sources.list.d/%s.list", name) 25 | } 26 | utils.Panic(node.Sudo().ScpContent(content, remoteRepoPath), "scp repo content") 27 | } 28 | 29 | func setAliRepoUbuntu(node *ssh.Node) { 30 | sourceList := []byte(` 31 | deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse 32 | deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse 33 | deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse 34 | deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse 35 | deb http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse 36 | deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse 37 | deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse 38 | deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse 39 | deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse 40 | deb-src http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse 41 | `) 42 | err := node.Sudo().ScpContent(sourceList, "/etc/apt/sources.list.d/vik8s.list") 43 | utils.Panic(err, "add sources list") 44 | 45 | err = node.Sudo().CmdWatcher("apt-get update", utils.Stdout(node.Prefix())) 46 | utils.Panic(err, "update source") 47 | } 48 | 49 | func setAliRepoCentOS(node *ssh.Node) { 50 | Installs(node, "epel-release") 51 | if paths.China { 52 | repoUrl := fmt.Sprintf("http://mirrors.aliyun.com/repo/Centos-%s.repo", node.Facts.MajorVersion) 53 | err := node.Sudo().Cmd("curl --silent -o /etc/yum.repos.d/vik8s.repo " + repoUrl) 54 | utils.Panic(err, "download AliCloud repos") 55 | 56 | if node.Facts.MajorVersion == "7" { 57 | err = node.Sudo().Cmd("curl --silent -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo") 58 | utils.Panic(err, "download AliCloud repos") 59 | } 60 | } 61 | Installs(node, "yum-utils", "lvm2", "device-mapper-persistent-data") 62 | _ = node.Sudo().Cmd("yum makecache") 63 | } 64 | -------------------------------------------------------------------------------- /install/repo/secret.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/ihaiker/vik8s/install/paths" 8 | "strings" 9 | ) 10 | 11 | type Repo struct { 12 | Url string `flag:"" help:"Choose a container registry to pull images."` 13 | User string `flag:"user" help:"docker registry user"` 14 | Password string `flag:"password" help:"docker registry password"` 15 | ImagePullSecrets []string `flag:"image-pull-secrets"` 16 | 17 | //输出 Secret kubernetes yaml 定义 18 | Secret string `flag:"-"` 19 | ReduceSecret string `flag:"-"` 20 | 21 | //输出imagePullSecrets定义 22 | PullSecrets string `flag:"-"` 23 | ReducePullSecrets string `flag:"-"` 24 | } 25 | 26 | func (repo *Repo) String() string { 27 | return repo.Url 28 | } 29 | 30 | func (repo *Repo) Set(namespace string) { 31 | repo.Url = Suffix(repo.Url) 32 | if len(repo.ImagePullSecrets) > 0 { 33 | repo.PullSecrets = "imagePullSecrets:\n" 34 | for _, secret := range repo.ImagePullSecrets { 35 | repo.PullSecrets += fmt.Sprintf(" - name: %s\n", secret) 36 | } 37 | repo.ReducePullSecrets = "" 38 | for _, secret := range repo.ImagePullSecrets { 39 | repo.ReducePullSecrets = fmt.Sprintf("imagePullSecrets name=%s;\n", secret) 40 | } 41 | } else if repo.User != "" { 42 | auth := paths.Json{ 43 | "auths": paths.Json{ 44 | "http://" + strings.TrimRight(repo.Url, "/"): paths.Json{ 45 | "username": repo.User, 46 | "password": repo.Password, 47 | "auth": base64.StdEncoding.EncodeToString([]byte(repo.User + ":" + repo.Password)), 48 | }, 49 | //两个都加上,这样就不用判断了 50 | "https://" + strings.TrimRight(repo.Url, "/"): paths.Json{ 51 | "username": repo.User, 52 | "password": repo.Password, 53 | "auth": base64.StdEncoding.EncodeToString([]byte(repo.User + ":" + repo.Password)), 54 | }, 55 | }, 56 | } 57 | base, _ := json.Marshal(auth) 58 | repo.Secret = fmt.Sprintf(`--- 59 | apiVersion: v1 60 | kind: Secret 61 | type: kubernetes.io/dockerconfigjson 62 | metadata: 63 | name: docker-auth 64 | namespace: %s 65 | data: 66 | .dockerconfigjson: %s 67 | `, namespace, base64.StdEncoding.EncodeToString(base)) 68 | repo.PullSecrets = "imagePullSecrets:\n - name: docker-auth" 69 | 70 | repo.ReduceSecret = fmt.Sprintf(` 71 | secret docker-auth kubernetes.io/dockerconfigjson { 72 | .dockerconfigjson: "%s" 73 | }`, base64.StdEncoding.EncodeToString(base)) 74 | repo.ReducePullSecrets = "imagePullSecrets name=docker-auth;" 75 | } 76 | } 77 | 78 | func (repo *Repo) Default(namespace, def string) { 79 | if repo.Url == "" { 80 | repo.Url = def 81 | } 82 | repo.Set(namespace) 83 | } 84 | 85 | func (repo *Repo) QuayIO(namespace string) { 86 | repo.Default(namespace, QuayIO(repo.Url)) 87 | } 88 | -------------------------------------------------------------------------------- /install/hosts/parse_test.go: -------------------------------------------------------------------------------- 1 | package hosts 2 | 3 | import ( 4 | "github.com/stretchr/testify/suite" 5 | "testing" 6 | ) 7 | 8 | type ParseAddressSuite struct { 9 | suite.Suite 10 | opt *Option 11 | } 12 | 13 | func (t *ParseAddressSuite) SetupTest() { 14 | t.opt = &Option{ 15 | User: "root", Password: "test", Port: 22, 16 | } 17 | } 18 | 19 | func (t ParseAddressSuite) TestMerge() { 20 | ip, err := merge_end_ip("10.24.0.10", "20") 21 | t.Nil(err) 22 | t.Equal("10.24.0.20", ip) 23 | 24 | ip, err = merge_end_ip("10.24.0.10", "1.20") 25 | t.Nil(err) 26 | t.Equal("10.24.1.20", ip) 27 | 28 | ip, err = merge_end_ip("10.24.0.10", "25.1.20") 29 | t.Nil(err) 30 | t.Equal("10.25.1.20", ip) 31 | } 32 | 33 | func (t ParseAddressSuite) TestBase() { 34 | nodes, err := ParseAddr(*t.opt, "10.24.0.10") 35 | t.Nil(err, "parse error") 36 | t.Equal(1, len(nodes), "node size not equal 1") 37 | 38 | nodes, err = ParseAddr(*t.opt, "10.24.0.10-10.24.0.11") 39 | t.Nil(err, "parse error") 40 | t.Equal(2, len(nodes), "node size not equal 1") 41 | t.Equal("10.24.0.10", nodes[0].Host) 42 | t.Equal("10.24.0.11", nodes[1].Host) 43 | 44 | nodes, err = ParseAddr(*t.opt, "test@10.24.0.10-10.24.0.11") 45 | t.Nil(err, "parse error") 46 | t.Equal(2, len(nodes), "node size not equal 2") 47 | t.Equal("10.24.0.10", nodes[0].Host) 48 | t.Equal("test", nodes[1].User) 49 | 50 | nodes, err = ParseAddr(*t.opt, "test@10.24.0.10-10.24.0.11:234") 51 | t.Nil(err, "parse error") 52 | t.Equal(2, len(nodes), "node size not equal 2") 53 | t.Equal("10.24.0.10", nodes[0].Host) 54 | t.Equal("test", nodes[1].User) 55 | t.Equal(234, nodes[1].Port) 56 | 57 | nodes, err = ParseAddr(*t.opt, "test:123@10.24.0.10-11:234") 58 | t.Nil(err, "parse error") 59 | t.Equal(2, len(nodes), "node size not equal 2") 60 | t.Equal("10.24.0.10", nodes[0].Host) 61 | t.Equal("test", nodes[1].User) 62 | t.Equal(234, nodes[1].Port) 63 | t.Equal("123", nodes[1].Password) 64 | 65 | nodes, err = ParseAddr(*t.opt, "test:123@10.24.0.10-15:234") 66 | t.Nil(err, "parse error") 67 | t.Equal(6, len(nodes), "node size not equal 26") 68 | t.Equal("10.24.0.10", nodes[0].Host) 69 | } 70 | 71 | func (t ParseAddressSuite) TestArgs() { 72 | nodes, err := ParseAddrs(*t.opt, "10.24.0.10") 73 | t.Nil(err, "parse error") 74 | t.Equal(1, len(nodes), "node size not equal 1") 75 | 76 | nodes, err = ParseAddrs(*t.opt, "10.24.0.10", "10.24.0.11") 77 | t.Nil(err, "parse error") 78 | t.Equal(2, len(nodes), "node size not equal 1") 79 | t.Equal("10.24.0.11", nodes[1].Host) 80 | 81 | nodes, err = ParseAddrs(*t.opt, "10.24.0.10-11", "10.24.0.20-23") 82 | t.Nil(err, "parse error") 83 | t.Equal(6, len(nodes), "node size not equal 1") 84 | t.Equal("10.24.0.21", nodes[3].Host) 85 | } 86 | 87 | func TestParseAddress(t *testing.T) { 88 | suite.Run(t, new(ParseAddressSuite)) 89 | } 90 | -------------------------------------------------------------------------------- /reduce/config/writer.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "math" 9 | "strings" 10 | ) 11 | 12 | type writer struct { 13 | outs *bytes.Buffer 14 | prefix string 15 | } 16 | 17 | func Writer(indent int) *writer { 18 | return &writer{ 19 | prefix: strings.Repeat(" ", indent*2), 20 | outs: bytes.NewBufferString(""), 21 | } 22 | } 23 | 24 | func (w *writer) Tab() *writer { 25 | return w.Indent(1) 26 | } 27 | 28 | func (w *writer) Indent(indent int) *writer { 29 | w.outs.WriteString(strings.Repeat(" ", indent*2)) 30 | return w 31 | } 32 | 33 | func (w *writer) Format(format string, param ...interface{}) *writer { 34 | w.outs.WriteString(w.prefix) 35 | w.outs.WriteString(fmt.Sprintf(format, param...)) 36 | return w 37 | } 38 | 39 | func (w *writer) Line(args ...string) *writer { 40 | w.outs.WriteString(w.prefix) 41 | for i, arg := range args { 42 | w.outs.WriteString(arg) 43 | if i < len(args)-1 { 44 | w.outs.WriteString(" ") 45 | } 46 | } 47 | return w.Enter() 48 | } 49 | 50 | func (w *writer) Writer(args ...string) *writer { 51 | for _, arg := range args { 52 | w.outs.WriteString(arg) 53 | } 54 | return w 55 | } 56 | 57 | func (w *writer) Enter() *writer { 58 | w.outs.WriteRune('\n') 59 | return w 60 | } 61 | 62 | func (s *writer) String() string { 63 | return s.outs.String() 64 | } 65 | 66 | func (s *writer) Bytes() []byte { 67 | return s.outs.Bytes() 68 | } 69 | 70 | func ToString(bs []byte, indent int) string { 71 | reader := bufio.NewReader(bytes.NewBuffer(bs)) 72 | 73 | readLine := func(r *bufio.Reader) (string, error) { 74 | outs := bytes.NewBufferString("") 75 | for { 76 | if line, prefix, err := r.ReadLine(); err != nil { 77 | return "", err 78 | } else if prefix { 79 | outs.Write(line) 80 | } else { 81 | outs.Write(line) 82 | return outs.String(), err 83 | } 84 | } 85 | } 86 | 87 | min := math.MaxInt8 88 | for { 89 | if line, err := readLine(reader); err == nil { 90 | newline := strings.TrimLeft(line, " ") 91 | if newline == "" { 92 | continue 93 | } 94 | space := len(line) - len(newline) 95 | if space < min { 96 | min = space 97 | } 98 | } else if err == io.EOF { 99 | break 100 | } 101 | } 102 | 103 | first := true 104 | w := Writer(indent) 105 | reader = bufio.NewReader(bytes.NewBuffer(bs)) 106 | for { 107 | if line, err := readLine(reader); err == nil { 108 | if strings.Trim(line, " ") == "" { 109 | if !first { 110 | w.Enter() 111 | } 112 | } else { 113 | w.Indent(indent).Writer(line[min:]).Enter() 114 | } 115 | first = false 116 | } else if err == io.EOF { 117 | break 118 | } 119 | } 120 | 121 | return w.String() 122 | } 123 | -------------------------------------------------------------------------------- /reduce/kube/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/libs/utils" 6 | "github.com/ihaiker/vik8s/reduce/asserts" 7 | "github.com/ihaiker/vik8s/reduce/config" 8 | "github.com/ihaiker/vik8s/reduce/plugins" 9 | "github.com/ihaiker/vik8s/reduce/refs" 10 | v1 "k8s.io/api/core/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/util/intstr" 13 | "math" 14 | "strings" 15 | ) 16 | 17 | func ServiceParse(version, prefix string, dir *config.Directive) metav1.Object { 18 | service := &v1.Service{ 19 | TypeMeta: metav1.TypeMeta{ 20 | Kind: "Service", 21 | APIVersion: v1.SchemeGroupVersion.String(), 22 | }, 23 | } 24 | 25 | from := "" 26 | if strings.Contains(dir.Args[0], ":") { 27 | from = dir.Args[0] 28 | dir.Args = dir.Args[1:] 29 | } 30 | 31 | asserts.MetadataIndex(service, dir, math.MaxInt8) 32 | asserts.AutoLabels(service, prefix) 33 | 34 | if serviceType := utils.Index(dir.Args, 1); serviceType != "" { 35 | service.Spec.Type = v1.ServiceType(serviceType) 36 | } 37 | 38 | for _, item := range dir.Body { 39 | switch item.Name { 40 | default: 41 | refs.UnmarshalItem(&service.Spec, item) 42 | case "port": 43 | asserts.ArgsMin(item, 1) 44 | service.Spec.Ports = append(service.Spec.Ports, servicePortParse(item.Args)) 45 | case "ports": 46 | for _, i := range item.Body { 47 | asserts.ArgsMin(i, 1) 48 | service.Spec.Ports = append(service.Spec.Ports, 49 | servicePortParse(append([]string{i.Name}, i.Args...))) 50 | } 51 | } 52 | } 53 | if len(service.Spec.Selector) == 0 && from != "" { 54 | _, name := utils.Split2(from, ":") 55 | service.Spec.Selector = map[string]string{ 56 | fmt.Sprintf("%s/name", prefix): name, 57 | } 58 | } 59 | return service 60 | } 61 | 62 | func servicePortParse(args []string) v1.ServicePort { 63 | name := args[0] 64 | portExpr := utils.Index(args, 1) 65 | nodePort := utils.Index(args, 2) 66 | if strings.Contains(name, ":") { 67 | name = "" 68 | portExpr = args[0] 69 | nodePort = utils.Index(args, 1) 70 | } 71 | 72 | targetPort, portAndProtocol := utils.Split2(portExpr, ":") 73 | port, protocol := utils.Split2(portAndProtocol, "/") 74 | sp := v1.ServicePort{ 75 | Name: name, Protocol: v1.Protocol(strings.ToUpper(protocol)), 76 | Port: *utils.Int32(port, 10), 77 | TargetPort: intstr.Parse(targetPort), 78 | NodePort: *utils.Int32(nodePort, 10), 79 | } 80 | return sp 81 | } 82 | 83 | var Service = plugins.ReduceHandler{ 84 | Names: []string{"service", "Service"}, Handler: ServiceParse, 85 | Demo: ` 86 | service [deployment:mysql] server-name [serviceType] { 87 | port name targetPort:port/protocol [nodePort]; 88 | ports { 89 | name1 targetPort:port[/protocol] [nodePort]; 90 | name2 targetPort:port[/protocol] [nodePort]; 91 | } 92 | } 93 | `, 94 | } 95 | -------------------------------------------------------------------------------- /libs/utils/ip.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "net" 7 | "strings" 8 | ) 9 | 10 | func RangeIP(start, end string) []string { 11 | from := net.ParseIP(start).To4() 12 | to := net.ParseIP(end).To4() 13 | Assert(from != nil, "Invalid IP %s", start) 14 | Assert(to != nil, "Invalid IP %s", end) 15 | 16 | ips := make([]string, 0) 17 | for ComposeIP(from, to) <= 0 { 18 | ips = append(ips, from.String()) 19 | from = NextIP(from) 20 | } 21 | return ips 22 | } 23 | 24 | func ParseIPS(nodes []string) []string { 25 | outs := make([]string, 0) 26 | for _, node := range nodes { 27 | if strings.Contains(node, "-") { // ip-ip 28 | startAndEnd := strings.SplitN(node, "-", 2) 29 | outs = append(outs, RangeIP(startAndEnd[0], startAndEnd[1])...) 30 | } else { 31 | outs = append(outs, node) 32 | } 33 | } 34 | return outs 35 | } 36 | 37 | func ComposeIP(a, b net.IP) int { 38 | aa, _ := ipToInt(a) 39 | bb, _ := ipToInt(b) 40 | return aa.Cmp(bb) 41 | } 42 | 43 | func NextIP(ip net.IP) net.IP { 44 | a := big.NewInt(0).SetBytes(ip) 45 | b := a.Add(a, big.NewInt(1)) 46 | return net.IP(b.Bytes()) 47 | } 48 | 49 | func PrevIP(ip net.IP) net.IP { 50 | a := big.NewInt(0).SetBytes(ip) 51 | b := a.Sub(a, big.NewInt(1)) 52 | return net.IP(b.Bytes()) 53 | } 54 | 55 | // AddressRange returns the first and last addresses in the given CIDR range. 56 | func AddressRange(network *net.IPNet) (net.IP, net.IP) { 57 | // the first IP is easy 58 | firstIP := network.IP 59 | 60 | // the last IP is the network address OR NOT the mask address 61 | prefixLen, bits := network.Mask.Size() 62 | if prefixLen == bits { 63 | // Easy! 64 | // But make sure that our two slices are distinct, since they 65 | // would be in all other cases. 66 | lastIP := make([]byte, len(firstIP)) 67 | copy(lastIP, firstIP) 68 | return firstIP, lastIP 69 | } 70 | 71 | firstIPInt, bits := ipToInt(firstIP) 72 | hostLen := uint(bits) - uint(prefixLen) 73 | lastIPInt := big.NewInt(1) 74 | lastIPInt.Lsh(lastIPInt, hostLen) 75 | lastIPInt.Sub(lastIPInt, big.NewInt(1)) 76 | lastIPInt.Or(lastIPInt, firstIPInt) 77 | 78 | return firstIP, intToIP(lastIPInt, bits) 79 | } 80 | 81 | func ipToInt(ip net.IP) (*big.Int, int) { 82 | val := &big.Int{} 83 | val.SetBytes([]byte(ip)) 84 | if len(ip) == net.IPv4len { 85 | return val, 32 86 | } else if len(ip) == net.IPv6len { 87 | return val, 128 88 | } else { 89 | panic(fmt.Errorf("Unsupported address length %d", len(ip))) 90 | } 91 | } 92 | 93 | func intToIP(ipInt *big.Int, bits int) net.IP { 94 | ipBytes := ipInt.Bytes() 95 | ret := make([]byte, bits/8) 96 | // Pack our IP bytes into the end of the return array, 97 | // since big.Int.Bytes() removes front zero padding. 98 | for i := 1; i <= len(ipBytes); i++ { 99 | ret[len(ret)-i] = ipBytes[len(ipBytes)-i] 100 | } 101 | return net.IP(ret) 102 | } 103 | -------------------------------------------------------------------------------- /libs/utils/errors.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | func Safe(fn func()) (err error) { 11 | Try(fn, func(e error) { 12 | err = e 13 | }) 14 | return 15 | } 16 | 17 | //Try handler(err) 18 | func Try(fun func(), handler ...func(error)) { 19 | defer Catch(handler...) 20 | fun() 21 | } 22 | 23 | //Try handler(err) and finally 24 | func TryFinally(fun func(), handler func(error), finallyFn func()) { 25 | defer func() { 26 | if finallyFn != nil { 27 | finallyFn() 28 | } 29 | }() 30 | Try(fun, handler) 31 | } 32 | 33 | type WrapError struct { 34 | Err error 35 | Message string 36 | } 37 | 38 | func Error(format string, args ...interface{}) *WrapError { 39 | return &WrapError{ 40 | Err: nil, Message: fmt.Sprintf(format, args...), 41 | } 42 | } 43 | 44 | func (w WrapError) Error() string { 45 | if w.Err == nil { 46 | return w.Message 47 | } 48 | if w.Message == "" { 49 | return w.Err.Error() 50 | } 51 | return fmt.Sprintf("%s: %s", w.Message, w.Err) 52 | } 53 | 54 | func Wrap(err error, format string, param ...interface{}) error { 55 | if we, match := err.(*WrapError); match { 56 | return &WrapError{ 57 | Err: we.Err, Message: fmt.Sprintf(format, param...) + "\n" + we.Message, 58 | } 59 | } else { 60 | return &WrapError{ 61 | Err: err, Message: fmt.Sprintf(format, param...), 62 | } 63 | } 64 | } 65 | 66 | func Catch(fns ...func(error)) { 67 | if r := recover(); r != nil && len(fns) > 0 { 68 | if err, match := r.(error); match { 69 | for _, fn := range fns { 70 | fn(err) 71 | } 72 | } else { 73 | err := fmt.Errorf("%v", r) 74 | for _, fn := range fns { 75 | fn(err) 76 | } 77 | } 78 | } 79 | } 80 | 81 | func Assert(check bool, msg string, params ...interface{}) { 82 | if !check { 83 | panic(&WrapError{Err: fmt.Errorf(msg, params...), Message: "Assert"}) 84 | } 85 | } 86 | 87 | //如果不为空,使用msg panic错误, 88 | func Panic(err error, msg string, params ...interface{}) { 89 | if err != nil { 90 | panic(Wrap(err, fmt.Sprintf(msg, params...))) 91 | } 92 | } 93 | 94 | func Stack() string { 95 | stackBuf := make([]uintptr, 50) 96 | length := runtime.Callers(3, stackBuf[:]) 97 | stack := stackBuf[:length] 98 | trace := "" 99 | frames := runtime.CallersFrames(stack) 100 | for { 101 | frame, more := frames.Next() 102 | if strings.HasSuffix(frame.File, "/vik8s/libs/utils/errors.go") || 103 | strings.HasSuffix(frame.File, "/src/runtime/panic.go") || 104 | strings.HasSuffix(frame.File, "/testing/testing.go") || 105 | frame.Function == "runtime.goexit" || frame.Function == "" { 106 | 107 | } else if strings.HasPrefix(frame.Function, "github.com/ihaiker/vik8s") { 108 | trace = trace + fmt.Sprintf("\t%s:%d %s\n", frame.File, frame.Line, filepath.Base(frame.Function)) 109 | } 110 | 111 | if !more { 112 | break 113 | } 114 | } 115 | return trace 116 | } 117 | -------------------------------------------------------------------------------- /install/k8s/install.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/config" 5 | "github.com/ihaiker/vik8s/install/bases" 6 | "github.com/ihaiker/vik8s/install/repo" 7 | "github.com/ihaiker/vik8s/libs/ssh" 8 | "github.com/ihaiker/vik8s/libs/utils" 9 | ) 10 | 11 | func installKubernetesSoftware(configure *config.Configuration, node *ssh.Node) { 12 | setRepo(node) 13 | sysctl(node) 14 | installKubeletAndKubeadm(configure.K8S, node) 15 | modprobe(node) 16 | } 17 | 18 | func setRepo(node *ssh.Node) { 19 | node.Logger("set kubernetes repo") 20 | err := node.Sudo().ScpContent([]byte(repo.Kubernetes()), "/etc/yum.repos.d/kubernetes.repo") 21 | utils.Panic(err, "send /etc/yum.repos.d/kubernetes.repo") 22 | } 23 | func sysctl(node *ssh.Node) { 24 | //sysctl -w "net.netfilter.nf_conntrack_tcp_be_liberal=1" 25 | //https://juejin.cn/post/6976101827179708453 26 | err := node.Sudo().ScpContent([]byte(` 27 | net.netfilter.nf_conntrack_tcp_be_liberal=1 28 | net.bridge.bridge-nf-call-ip6tables=1 29 | net.bridge.bridge-nf-call-iptables=1 30 | net.ipv4.ip_forward=1 31 | `), "/etc/sysctl.d/k8s.conf") 32 | utils.Panic(err, "send /etc/sysctl.d/k8s.conf") 33 | _ = node.Sudo().Cmd("sh -c 'echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables'") 34 | _ = node.Sudo().Cmd("sh -c 'echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables'") 35 | _ = node.Sudo().Cmd("sysctl -p") 36 | _ = node.Sudo().Cmd("update-alternatives --set iptables /usr/sbin/iptables-legacy") 37 | _ = node.Sudo().Cmd("update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy") 38 | _ = node.Sudo().Cmd("update-alternatives --set arptables /usr/sbin/arptables-legacy") 39 | _ = node.Sudo().Cmd("update-alternatives --set ebtables /usr/sbin/ebtables-legacy") 40 | } 41 | 42 | func installKubeletAndKubeadm(configure *config.K8SConfiguration, node *ssh.Node) { 43 | version := configure.Version[1:] 44 | node.Logger("Install kubelet & kubeadm v%s", version) 45 | 46 | bases.Install("ethtool", "", node) 47 | 48 | switch node.Facts.MajorVersion { 49 | case "7": 50 | bases.Install("ebtables", "", node) 51 | case "8": 52 | bases.Install("iptables-ebtables", "", node) 53 | bases.Install("iproute-tc", "", node) 54 | } 55 | 56 | bases.Install("bash-completion", "", node) 57 | bases.Install("ipvsadm", "", node) 58 | bases.Install("ipset", "", node) 59 | bases.Install("kubelet", version, node) 60 | bases.Install("kubeadm", version, node) 61 | 62 | _ = node.Sudo().Cmd("systemctl enable ipvsadm") 63 | _ = node.Sudo().Cmd("systemctl start ipvsadm") 64 | _ = node.Sudo().Cmd("systemctl enable kubelet") 65 | _ = node.Sudo().Cmd("sh -c 'kubeadm completion bash > /etc/bash_completion.d/kubeadm'") 66 | _ = node.Sudo().Cmd("sh -c 'kubectl completion bash > /etc/bash_completion.d/kubectl'") 67 | } 68 | 69 | func modprobe(node *ssh.Node) { 70 | for _, mod := range []string{ 71 | "ip_vs", "ip_vs_rr", "ip_vs_wrr", "ip_vs_sh", "ip_tables", 72 | "nf_conntrack", "br_netfilter", "dm_thin_pool", 73 | } { 74 | _ = node.Sudo().Cmd("modprobe " + mod) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ingress/traefik.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/cobrax" 6 | "github.com/ihaiker/vik8s/install/repo" 7 | "github.com/ihaiker/vik8s/libs/ssh" 8 | "github.com/ihaiker/vik8s/libs/utils" 9 | "github.com/ihaiker/vik8s/reduce" 10 | "github.com/spf13/cobra" 11 | "os" 12 | "strings" 13 | ) 14 | 15 | type traefik struct { 16 | Repo repo.Repo 17 | Version string `help:"traefik ingress version"` 18 | HostNetwork bool `flag:"host-network" help:"Whether to enable the host network method"` 19 | NodePortHttp int `flag:"nodeport" help:"the ingress-traefik http service nodeport, 0: automatic allocatiot, -1: disable" def:"-1"` 20 | NodePortHttps int `flag:"nodeport-https" help:"the ingress-traefik https 443 service nodeport, 0: automatic allocation, -1: disable" def:"-1"` 21 | Replicas int `help:"ingress-traefik pod replicas number" def:"1"` 22 | NodeSelectors map[string]string `flag:"node-selector" help:"select what node to deploy"` 23 | 24 | IngressUI string `flag:"ui-ingress" help:"Creating ingress that will expose the Traefik Web UI."` 25 | AuthUI bool `flag:"ui-auth" help:"Whether to enable 'basic authentication' in traefik web ui ingress"` 26 | AuthUser string `flag:"ui-user" help:"web ui 'basic authentication' user "` 27 | AuthPassword string `flag:"ui-passwd" help:"web ui 'basic authentication' password (default: randomly generated and pint to console)"` 28 | } 29 | 30 | func Treafik() Ingress { 31 | return &traefik{ 32 | Version: "v1.7", 33 | HostNetwork: false, NodePortHttp: -1, NodePortHttps: -1, 34 | Replicas: 1, NodeSelectors: map[string]string{}, 35 | AuthUI: true, IngressUI: "traefik.vik8s.io", AuthUser: "admin", 36 | } 37 | } 38 | 39 | func (t *traefik) Name() string { 40 | return "traefik" 41 | } 42 | 43 | func (t *traefik) Description() string { 44 | return "https://docs.traefik.io/v1.7/user-guide/kubernetes/" 45 | } 46 | 47 | func (t *traefik) Flags(cmd *cobra.Command) { 48 | err := cobrax.Flags(cmd, t, "", "VIK8S_INGRESS_TREAFIK") 49 | utils.Panic(err, "set treafik ingress error") 50 | } 51 | 52 | func (t *traefik) Apply(master *ssh.Node) { 53 | t.Repo.Set("ingress-traefik") 54 | 55 | if t.AuthUI { 56 | if t.AuthPassword == "" { 57 | t.AuthPassword = utils.Random(8) 58 | fmt.Println(strings.Repeat("=", 40)) 59 | fmt.Printf("the web ui ingress default password for `%s` is : %s\n", t.AuthUser, t.AuthPassword) 60 | fmt.Println(strings.Repeat("=", 40)) 61 | } 62 | t.AuthPassword, _ = utils.HashApr1(t.AuthPassword) 63 | } 64 | 65 | //name := "yaml/ingress/traefik.yaml" 66 | //tools.MustScpAndApplyAssert(master, name, t) 67 | name := "yaml/ingress/traefik.conf" 68 | err := reduce.ApplyAssert(master, name, t) 69 | utils.Panic(err, "apply traefik ingress") 70 | } 71 | 72 | func (n *traefik) Delete(master *ssh.Node) { 73 | err := master.CmdOutput("kubectl delete namespaces ingress-traefik ", os.Stdout) 74 | utils.Panic(err, "delete traefik ingress") 75 | } 76 | -------------------------------------------------------------------------------- /install/hosts/parse.go: -------------------------------------------------------------------------------- 1 | package hosts 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/vik8s/libs/ssh" 6 | "github.com/ihaiker/vik8s/libs/utils" 7 | "net" 8 | "os" 9 | "path/filepath" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | //解析用户输入地址 root:[paswword|privatekey]@ip-ip:port 16 | var ( 17 | userAndPwd = `([^:]+)(:(\S+))?` 18 | ip = `(\d{1,3}\.\d{1,3}\.\d{1,3}\.)?\d{1,3}` 19 | port = `\d{2,5}` 20 | PATTERN = regexp.MustCompile(fmt.Sprintf(`(%s@)?((%s)-)?(%s)(:(%s))?`, userAndPwd, ip, ip, port)) 21 | ) 22 | 23 | //ParseAddrs 解析地址 24 | func ParseAddrs(opt Option, args ...string) (ssh.Nodes, error) { 25 | nodes := ssh.Nodes{} 26 | for _, arg := range args { 27 | if ns, err := ParseAddr(opt, arg); err != nil { 28 | return nil, err 29 | } else { 30 | nodes = append(nodes, ns...) 31 | } 32 | } 33 | return nodes, nil 34 | } 35 | 36 | //ParseAddr 解析地址 37 | func ParseAddr(opt Option, arg string) (nodes ssh.Nodes, err error) { 38 | defer utils.Catch(func(e error) { 39 | err = e 40 | }) 41 | groups := PATTERN.FindStringSubmatch(arg) 42 | 43 | user := groups[2] 44 | if user == "" { 45 | user = opt.User 46 | } 47 | 48 | //1、使用用户提供的密码\秘钥 49 | password := groups[4] 50 | privateKey := "" 51 | //2、如果用户秘钥未提供,使用全局密码, 52 | if password == "" { 53 | password = opt.Password 54 | } 55 | //3、如果全局密码未设置、使用全局秘钥 56 | if password == "" { 57 | password = opt.PrivateKey 58 | } 59 | //判断是不是秘钥文件,如果是秘钥文件就替换 60 | if utils.Exists(os.ExpandEnv(password)) { //秘钥文件 61 | privateKey, _ = filepath.Abs(os.ExpandEnv(password)) 62 | password = "" 63 | } 64 | endIp := groups[8] 65 | startIp := groups[6] 66 | if startIp == "" { //单独一个IP 67 | startIp = endIp 68 | } 69 | //处理 分段式标识形式 70 | if endIp, err = merge_end_ip(startIp, endIp); err != nil { 71 | return nil, utils.Error("invalid address: %s", arg) 72 | } 73 | 74 | from := net.ParseIP(startIp).To4() 75 | to := net.ParseIP(endIp).To4() 76 | 77 | port := 22 78 | if groups[11] != "" { 79 | if port, err = strconv.Atoi(groups[11]); err != nil { 80 | err = utils.Wrap(err, "Wrong port number:%v", groups[11]) 81 | return 82 | } 83 | } 84 | proxy := opt.Proxy 85 | 86 | nodes = ssh.Nodes{} 87 | for utils.ComposeIP(from, to) <= 0 { 88 | node := &ssh.Node{} 89 | node.User = user 90 | node.Password = password 91 | node.PrivateKey = privateKey 92 | node.Host = from.String() 93 | node.Port = port 94 | node.Passphrase = opt.Passphrase 95 | node.Proxy = proxy 96 | nodes = append(nodes, node) 97 | from = utils.NextIP(from) 98 | } 99 | return 100 | } 101 | 102 | func merge_end_ip(start, end string) (string, error) { 103 | segments := strings.Split(end, ".") 104 | num := len(segments) 105 | if num > 4 { 106 | return "", utils.Error("invalid address: %s", end) 107 | } 108 | startSegments := strings.Split(start, ".") 109 | segments = append(startSegments[0:4-num], segments...) 110 | return strings.Join(segments, "."), nil 111 | } 112 | -------------------------------------------------------------------------------- /config/k8s.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "time" 4 | 5 | type K8SConfiguration struct { 6 | Masters []string `ngx:"masters" flag:"-"` 7 | Nodes []string `ngx:"nodes" flag:"-"` 8 | Version string `ngx:"version" help:"Specify k8s version"` 9 | KubeadmConfig string `ngx:"kubeadm-config" help:"Path to a kubeadm configuration file. see kubeadm --config"` 10 | 11 | ApiServer string `ngx:"api-server" def:"api-vik8s-io" help:"Specify a stable IP address or DNS name for the control plane. see kubeadm --control-plane-endpoint"` 12 | ApiServerVIP string `flag:"-" ngx:"api-server-vip"` 13 | ApiServerCertExtraSans []string `ngx:"api-server-cert-extra-sans" help:"see kubeadm init --apiserver-cert-extra-sans"` 14 | 15 | Repo string `ngx:"repo" help:"Choose a container registry to pull control plane images from. \n(default: Best choice from k8s.gcr.io and registry.aliyuncs.com/google_containers.)"` 16 | 17 | Interface string `ngx:"network-interface" def:"eth.*|en.*|em.*" help:"name of network interface"` 18 | PodCIDR string `ngx:"pod-cidr" flag:"pod-cidr" def:"100.64.0.0/24" help:"Specify range of IP addresses for the pod network"` 19 | SvcCIDR string `ngx:"svc-cidr" flag:"svc-cidr" def:"10.96.0.0/12" help:"Use alternative range of IP address for service VIPs"` 20 | 21 | CertsValidity time.Duration `ngx:"certs-validity" def:"876000h" help:"Certificate validity time"` 22 | Timezone string `ngx:"timezone" def:"Asia/Shanghai"` 23 | NTPServices []string `ngx:"ntp-services" flag:"ntp-services" def:"ntp1.aliyun.com,ntp2.aliyun.com,ntp3.aliyun.com" help:"time server\n"` 24 | } 25 | 26 | func DefaultK8SConfiguration() *K8SConfiguration { 27 | return &K8SConfiguration{ 28 | Version: "v1.21.3", 29 | ApiServer: "api-vik8s-io", 30 | Interface: "eth.*|en.*|em.*", 31 | PodCIDR: "100.64.0.0/24", 32 | SvcCIDR: "10.96.0.0/12", 33 | CertsValidity: 100 * 365 * 24 * time.Hour, 34 | Timezone: "Asia/Shanghai", 35 | NTPServices: []string{"ntp1.aliyun.com", "ntp2.aliyun.com", "ntp3.aliyun.com"}, 36 | } 37 | } 38 | 39 | func (cfg *K8SConfiguration) ExistsNode(ip string) (exists bool, master bool) { 40 | for _, node := range cfg.Masters { 41 | if node == ip { 42 | exists = true 43 | master = true 44 | return 45 | } 46 | } 47 | for _, node := range cfg.Nodes { 48 | if node == ip { 49 | exists = true 50 | master = false 51 | return 52 | } 53 | } 54 | return 55 | } 56 | 57 | func (cfg *K8SConfiguration) JoinNode(master bool, ip string) { 58 | if master { 59 | cfg.Masters = append(cfg.Masters, ip) 60 | } else { 61 | cfg.Nodes = append(cfg.Nodes, ip) 62 | } 63 | } 64 | 65 | func (cfg *K8SConfiguration) RemoveNode(ip string) { 66 | for i, node := range cfg.Nodes { 67 | if node == ip { 68 | cfg.Nodes = append(cfg.Nodes[0:i], cfg.Nodes[i+1:]...) 69 | break 70 | } 71 | } 72 | for i, node := range cfg.Masters { 73 | if node == ip { 74 | cfg.Masters = append(cfg.Masters[0:i], cfg.Masters[i+1:]...) 75 | break 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /reduce/kube/pod/container/parse.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "github.com/ihaiker/vik8s/reduce/asserts" 6 | "github.com/ihaiker/vik8s/reduce/config" 7 | "github.com/ihaiker/vik8s/reduce/refs" 8 | v1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | func container(d *config.Directive, pod *v1.PodSpec) v1.Container { 12 | asserts.ArgsRange(d, 2, 3) 13 | 14 | c := v1.Container{ 15 | Name: d.Args[0], Image: d.Args[1], 16 | } 17 | 18 | if ipp := utils.Index(d.Args, 2); ipp != "" { 19 | c.ImagePullPolicy = v1.PullPolicy(ipp) 20 | } 21 | 22 | for _, body := range d.Body { 23 | switch body.Name { 24 | default: 25 | refs.UnmarshalItem(&c, body) 26 | 27 | case "command": 28 | c.Command = body.Args 29 | case "args": 30 | c.Args = body.Args 31 | 32 | case "port": 33 | asserts.ArgsMin(body, 1) 34 | if len(body.Args) == 1 { 35 | c.Ports = append(c.Ports, portParse("", body.Args[0])) 36 | } else { 37 | c.Ports = append(c.Ports, portParse(body.Args[0], body.Args[1])) 38 | } 39 | case "ports": 40 | for _, port := range body.Body { 41 | if len(port.Args) == 0 { 42 | c.Ports = append(c.Ports, portParse("", port.Name)) 43 | } else { 44 | c.Ports = append(c.Ports, portParse(port.Name, port.Args[0])) 45 | } 46 | } 47 | case "env": 48 | asserts.ArgsMin(body, 2) 49 | c.Env = append(c.Env, envParse(body.Args[0], body.Args[1:])) 50 | case "envs": 51 | asserts.ArgsLen(body, 0) 52 | for _, env := range body.Body { 53 | c.Env = append(c.Env, envParse(env.Name, env.Args)) 54 | } 55 | case "envFrom": 56 | asserts.ArgsMin(body, 1) 57 | fromType, name := utils.Split2(body.Args[0], ":") 58 | env := v1.EnvFromSource{ 59 | Prefix: utils.Index(body.Args, 1), 60 | } 61 | if fromType == "secret" { 62 | env.ConfigMapRef = &v1.ConfigMapEnvSource{ 63 | LocalObjectReference: v1.LocalObjectReference{Name: name}, 64 | } 65 | } else { 66 | env.SecretRef = &v1.SecretEnvSource{ 67 | LocalObjectReference: v1.LocalObjectReference{Name: name}, 68 | } 69 | } 70 | c.EnvFrom = append(c.EnvFrom, env) 71 | 72 | case "device": 73 | asserts.ArgsLen(body, 2) 74 | c.VolumeDevices = append(c.VolumeDevices, v1.VolumeDevice{ 75 | Name: body.Args[0], DevicePath: body.Args[1], 76 | }) 77 | 78 | case "mount", "volumeMount": 79 | mountParse(body.Args, body, pod, &c) 80 | case "mounts", "volumeMounts": 81 | for _, directive := range body.Body { 82 | args := append([]string{directive.Name}, directive.Args...) 83 | mountParse(args, directive, pod, &c) 84 | } 85 | } 86 | } 87 | 88 | return c 89 | } 90 | 91 | func ContainerParse(d *config.Directive, spec *v1.PodSpec) { 92 | c := container(d, spec) 93 | spec.Containers = append(spec.Containers, c) 94 | } 95 | 96 | func InitContainerParse(d *config.Directive, spec *v1.PodSpec) { 97 | c := container(d, spec) 98 | spec.InitContainers = append(spec.InitContainers, c) 99 | } 100 | -------------------------------------------------------------------------------- /yaml/ingress/traefik.conf: -------------------------------------------------------------------------------- 1 | namespace ingress-traefik; 2 | 3 | {{.Repo.ReduceSecret}} 4 | 5 | ClusterRole:rbac.authorization.k8s.io/v1beta1 traefik-ingress-controller { 6 | rules { 7 | apiGroups: ""; 8 | resources: services endpoints secrets; 9 | verbs: get list watch; 10 | } 11 | rules { 12 | apiGroups: extensions; 13 | resources: ingresses; 14 | verbs: get list watch; 15 | } 16 | rules { 17 | apiGroups: extensions; 18 | resources: "ingresses/status"; 19 | verbs: update; 20 | } 21 | } 22 | 23 | ClusterRoleBinding:rbac.authorization.k8s.io/v1beta1 traefik-ingress-controller { 24 | roleRef { 25 | apiGroup: rbac.authorization.k8s.io; 26 | kind: ClusterRole; 27 | name: traefik-ingress-controller; 28 | } 29 | subjects { 30 | kind: ServiceAccount; 31 | name: traefik-ingress-controller; 32 | namespace: ingress-traefik; 33 | } 34 | } 35 | 36 | ServiceAccount:v1 traefik-ingress-controller; 37 | 38 | deployment traefik-ingress-controller { 39 | replicas: "{{.Replicas}}"; 40 | hostNetwork: "{{.HostNetwork}}"; 41 | nodeSelector { 42 | {{ range $key, $value := .NodeSelectors }} 43 | {{ $key }}: {{ $value }}; 44 | {{ end }} 45 | } 46 | serviceAccountName: traefik-ingress-controller; 47 | terminationGracePeriodSeconds: 60; 48 | #{{.Repo.ReducePullSecrets}} 49 | containers traefik-ingress-lb "{{.Repo}}traefik:{{.Version}}" { 50 | args: "--api" "--kubernetes" "--logLevel=WARN" 51 | "--entrypoints=Name:https Address::443 TLS" 52 | "--entrypoints=Name:http Address::80" 53 | "--insecureskipverify"; 54 | #--defaultentrypoints=http,https 55 | ports { 56 | http 80; 57 | https 443; 58 | admin 8080; 59 | } 60 | } 61 | } 62 | 63 | service deployment:traefik-ingress-controller traefik-ingress-service 64 | "{{if or (ge .NodePortHttp 0) (ge .NodePortHttps 0)}}NodePort{{end}}" { 65 | ports { 66 | http 80:80 "{{if gt .NodePortHttp 0}}{{.NodePortHttp}}{{end}}"; 67 | https 443:443 "{{if gt .NodePortHttps 0}}{{.NodePortHttps}}{{end}}"; 68 | } 69 | } 70 | 71 | 72 | {{if .IngressUI}} 73 | {{if .AuthUI}} 74 | secret ingress-traefik-webui-auth Opaque { 75 | auth: '{{ base64 .AuthUser ":" .AuthPassword }}'; 76 | } 77 | {{end}} 78 | 79 | service deployment:traefik-ingress-controller traefik-ingress-ui { 80 | ports { 81 | adminui 8080:8080/TCP; 82 | } 83 | } 84 | 85 | ingress traefik-web-ui { 86 | annotations { 87 | kubernetes.io/ingress.class: traefik; 88 | {{if .AuthUI}} 89 | traefik.ingress.kubernetes.io/auth-type: "basic"; 90 | traefik.ingress.kubernetes.io/auth-secret: "ingress-traefik-webui-auth"; 91 | {{end}} 92 | } 93 | rules "{{ .IngressUI }}" { 94 | http paths { 95 | serviceName: traefik-ingress-ui; 96 | servicePort: 8080; 97 | } 98 | } 99 | } 100 | {{end}} 101 | 102 | -------------------------------------------------------------------------------- /install/repo/main.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/install/paths" 5 | "strings" 6 | ) 7 | 8 | func Containerd() string { 9 | if paths.China { 10 | return "https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.10-3.2.el7.`uname -p`.rpm" 11 | } else { 12 | return "https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.10-3.2.el7.`uname -p`.rpm" 13 | } 14 | } 15 | 16 | func Docker() string { 17 | /*if tools.China { 18 | return "https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo" 19 | } else { 20 | return "https://download.docker.com/linux/centos/docker-ce.repo" 21 | }*/ 22 | content := `[docker-ce-stable] 23 | name=Docker CE Stable - $basearch 24 | baseurl=https://download.docker.com/linux/centos/$releasever/$basearch/stable 25 | enabled=1 26 | gpgcheck=1 27 | gpgkey=https://download.docker.com/linux/centos/gpg 28 | 29 | [docker-ce-stable-debuginfo] 30 | name=Docker CE Stable - Debuginfo $basearch 31 | baseurl=https://download.docker.com/linux/centos/$releasever/debug-$basearch/stable 32 | enabled=0 33 | gpgcheck=1 34 | gpgkey=https://download.docker.com/linux/centos/gpg 35 | 36 | [docker-ce-stable-source] 37 | name=Docker CE Stable - Sources 38 | baseurl=https://download.docker.com/linux/centos/$releasever/source/stable 39 | enabled=0 40 | gpgcheck=1 41 | gpgkey=https://download.docker.com/linux/centos/gpg 42 | ` 43 | if paths.China { 44 | content = strings.ReplaceAll(content, "download.docker.com", "mirrors.aliyun.com/docker-ce") 45 | } 46 | return content 47 | } 48 | 49 | func Kubernetes() string { 50 | if paths.China { 51 | return `[kubernetes] 52 | baseurl = https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ 53 | enabled = 1 54 | gpgcheck = 1 55 | gpgkey = https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg 56 | https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg 57 | name = Ali Kubernetes Repo 58 | repo_gpgcheck = 1 59 | ` 60 | } else { 61 | return `[kubernetes] 62 | baseurl = https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64 63 | enabled = 1 64 | gpgcheck = 1 65 | gpgkey = https://packages.cloud.google.com/yum/doc/yum-key.gpg 66 | https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg 67 | name = Ali Kubernetes Repo 68 | repo_gpgcheck = 1 69 | ` 70 | } 71 | } 72 | 73 | func KubeletImage(repo string) string { 74 | if repo != "" { 75 | return repo 76 | } 77 | if paths.China { 78 | return "registry.aliyuncs.com/google_containers" 79 | } else { 80 | return "k8s.gcr.io" 81 | } 82 | } 83 | 84 | func QuayIO(repo string) string { 85 | if repo != "" { 86 | return repo 87 | } 88 | 89 | if paths.China { 90 | return "quay.mirrors.ustc.edu.cn" 91 | } else { 92 | return "quay.io" 93 | } 94 | } 95 | 96 | func Suffix(repo string) string { 97 | if repo != "" && !strings.HasSuffix(repo, "/") { 98 | repo = repo + "/" 99 | } 100 | if strings.HasPrefix(repo, "http://") { 101 | repo = repo[7:] 102 | } 103 | if strings.HasPrefix(repo, "https://") { 104 | repo = repo[8:] 105 | } 106 | return repo 107 | } 108 | -------------------------------------------------------------------------------- /reduce/kube/ingress/ingress.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "github.com/ihaiker/vik8s/libs/utils" 5 | "github.com/ihaiker/vik8s/reduce/asserts" 6 | "github.com/ihaiker/vik8s/reduce/config" 7 | "github.com/ihaiker/vik8s/reduce/plugins" 8 | "github.com/ihaiker/vik8s/reduce/refs" 9 | networkingv1beta1 "k8s.io/api/networking/v1beta1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "strings" 12 | ) 13 | 14 | func Parse(version, prefix string, directive *config.Directive) metav1.Object { 15 | ig := &networkingv1beta1.Ingress{ 16 | TypeMeta: metav1.TypeMeta{ 17 | Kind: "Ingress", 18 | APIVersion: networkingv1beta1.SchemeGroupVersion.String(), 19 | }, 20 | } 21 | asserts.Metadata(ig, directive) 22 | asserts.AutoLabels(ig, prefix) 23 | 24 | for _, d := range directive.Body { 25 | switch d.Name { 26 | case "tls": 27 | if d.HasArgs() { 28 | ig.Spec.TLS = append(ig.Spec.TLS, networkingv1beta1.IngressTLS{ 29 | Hosts: d.Args[1:], SecretName: d.Args[0], 30 | }) 31 | } else { 32 | for _, sub := range d.Body { 33 | ig.Spec.TLS = append(ig.Spec.TLS, networkingv1beta1.IngressTLS{ 34 | SecretName: sub.Name, Hosts: sub.Args, 35 | }) 36 | } 37 | } 38 | case "rules": 39 | asserts.ArgsLen(d, 1) 40 | rule := networkingv1beta1.IngressRule{ 41 | Host: d.Args[0], IngressRuleValue: networkingv1beta1.IngressRuleValue{ 42 | HTTP: &networkingv1beta1.HTTPIngressRuleValue{}, 43 | }, 44 | } 45 | for _, path := range d.Body { 46 | utils.Assert(path.Name == "http" && len(path.Args) >= 1 && path.Args[0] == "paths", 47 | "Invalid parameter: %s %s , line %d", 48 | path.Name, strings.Join(path.Args, " "), path.Line) 49 | ingressPath := networkingv1beta1.HTTPIngressPath{Path: utils.Index(path.Args, 1)} 50 | refs.Unmarshal(&ingressPath.Backend, path) 51 | rule.IngressRuleValue.HTTP.Paths = append(rule.IngressRuleValue.HTTP.Paths, ingressPath) 52 | } 53 | ig.Spec.Rules = append(ig.Spec.Rules, rule) 54 | case "backend": 55 | ig.Spec.Backend = &networkingv1beta1.IngressBackend{} 56 | refs.Unmarshal(ig.Spec.Backend, d) 57 | } 58 | } 59 | return ig 60 | } 61 | 62 | var Ingress = plugins.ReduceHandler{ 63 | Names: []string{"ingress", "Ingress"}, Handler: Parse, 64 | Demo: ` 65 | 66 | ingress mysql { 67 | tls secretName1 hosts1 hosts2; 68 | tls { 69 | secretName2 hosts3 hosts4; 70 | secretNameN hostsN; 71 | } 72 | 73 | rules host1.vik8s.io { 74 | http paths { 75 | serviceName service-name1; 76 | servicePort 1024; 77 | } 78 | } 79 | rules host3.vik8s.io { 80 | http paths { 81 | serviceName service-name3; 82 | servicePort 1024; 83 | } 84 | } 85 | rules host.vik8s.io { 86 | http paths /path2 { 87 | serviceName service-path2; 88 | servicePort 1024; 89 | } 90 | http paths /path1 { 91 | serviceName service-path1; 92 | servicePort 1024; 93 | } 94 | } 95 | 96 | backend { 97 | serviceName service-path2; 98 | servicePort 1024; 99 | } 100 | } 101 | `, 102 | } 103 | -------------------------------------------------------------------------------- /install/tools/template.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/base64" 7 | "github.com/ihaiker/vik8s/install/paths" 8 | "github.com/ihaiker/vik8s/libs/ssh" 9 | "github.com/ihaiker/vik8s/libs/utils" 10 | yamls "github.com/ihaiker/vik8s/yaml" 11 | "io" 12 | "os" 13 | "regexp" 14 | "strings" 15 | "text/template" 16 | ) 17 | 18 | var systemFuncs = template.FuncMap{ 19 | "strjoin": strings.Join, 20 | "base64": func(str ...string) string { 21 | outs := bytes.NewBufferString("") 22 | for _, s := range str { 23 | outs.WriteString(s) 24 | } 25 | return base64.StdEncoding.EncodeToString(outs.Bytes()) 26 | }, 27 | "environ": func(env, def string) string { 28 | if v := os.Getenv(env); v != "" { 29 | return v 30 | } 31 | return def 32 | }, 33 | "indent": func(n int, text string) string { 34 | startOfLine := regexp.MustCompile(`(?m)^`) 35 | indentation := strings.Repeat(" ", n) 36 | return startOfLine.ReplaceAllLiteralString(text, indentation) 37 | }, 38 | } 39 | 40 | func Template(content string, data interface{}, funcs ...template.FuncMap) ([]byte, error) { 41 | tempFuns := template.FuncMap{} 42 | for k, f := range systemFuncs { 43 | tempFuns[k] = f 44 | } 45 | for _, funcMap := range funcs { 46 | for k, f := range funcMap { 47 | tempFuns[k] = f 48 | } 49 | } 50 | 51 | out := bytes.NewBufferString("") 52 | t, err := template.New("").Funcs(tempFuns).Parse(content) 53 | if err != nil { 54 | return nil, utils.Wrap(err, "template error") 55 | } 56 | 57 | if err = t.Execute(out, data); err != nil { 58 | return nil, err 59 | } 60 | return out.Bytes(), nil 61 | } 62 | 63 | //Assert Search assert file. First, it will search user local home path $HOME/.vik8s/$cloud/$name. 64 | //if not found, then the default name file will return 65 | func Assert(name string, data interface{}, funcs ...template.FuncMap) ([]byte, error) { 66 | var tmpCtx []byte 67 | var err error 68 | 69 | localFile := paths.Join(name) 70 | if utils.Exists(localFile) { 71 | tmpCtx = utils.FileBytes(localFile) 72 | } else if tmpCtx, err = yamls.Asset(name); err != nil { 73 | return nil, err 74 | } 75 | 76 | var outs []byte 77 | if outs, err = Template(string(tmpCtx), data, funcs...); err != nil { 78 | return nil, err 79 | } 80 | 81 | //去除空行,使得的文件好看一下吧 82 | pretty := bytes.NewBuffer([]byte{}) 83 | reader := bufio.NewReader(bytes.NewReader(outs)) 84 | 85 | var line []byte 86 | var isPrefix bool 87 | for { 88 | line, isPrefix, err = reader.ReadLine() 89 | if err == io.EOF { 90 | err = nil 91 | break 92 | } 93 | if !isPrefix && strings.TrimSpace(string(line)) == "" { 94 | continue 95 | } 96 | pretty.Write(line) 97 | if !isPrefix { 98 | pretty.WriteRune('\n') 99 | } 100 | } 101 | return pretty.Bytes(), err 102 | } 103 | 104 | func ScpAndApplyAssert(node *ssh.Node, name string, data interface{}, funcs ...template.FuncMap) error { 105 | pods, err := Assert(name, data, funcs...) 106 | if err != nil { 107 | return err 108 | } 109 | remote := node.Vik8s("apply", strings.TrimPrefix(name, "yaml/")) 110 | 111 | if err = node.HideLog().ScpContent(pods, remote); err != nil { 112 | return err 113 | } 114 | return node.CmdStdout("kubectl apply -f " + remote) 115 | } 116 | -------------------------------------------------------------------------------- /cmd/bash.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "github.com/ihaiker/vik8s/install/paths" 7 | "github.com/ihaiker/vik8s/libs/ssh" 8 | "github.com/ihaiker/vik8s/libs/utils" 9 | "github.com/kvz/logstreamer" 10 | "github.com/peterh/liner" 11 | "github.com/spf13/cobra" 12 | "io" 13 | "log" 14 | "os" 15 | "strings" 16 | ) 17 | 18 | var colors = []color.Attribute{ 19 | color.FgRed, color.FgGreen, color.FgYellow, 20 | color.FgBlue, color.FgMagenta, color.FgCyan, 21 | } 22 | var colorsSize = len(colors) 23 | 24 | func filterNode(node *ssh.Node, filters []string) bool { 25 | if len(filters) == 0 { 26 | return true 27 | } 28 | for _, filter := range filters { 29 | if filter == node.Hostname { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | 36 | func runCmd(sync bool, cmd string, nodes []*ssh.Node, filters ...string) { 37 | run := func(i int, node *ssh.Node) { 38 | if !filterNode(node, filters) { 39 | return 40 | } 41 | prefix := color.New(colors[i%colorsSize]).Sprintf("[%s] ", node.Hostname) 42 | out := logstreamer.NewLogstreamerForStdout(prefix) 43 | if err := node.HideLog().CmdOutput(cmd, out); err != nil { 44 | fmt.Println(err) 45 | _, _ = out.Write([]byte(fmt.Sprint(err))) 46 | } 47 | } 48 | if sync { 49 | ssh.Sync(nodes, run) 50 | } else { 51 | for i, node := range nodes { 52 | run(i, node) 53 | } 54 | } 55 | } 56 | 57 | var bashCmd = &cobra.Command{ 58 | Use: "bash", Short: "Run commands uniformly in the cluster", 59 | //SilenceErrors: true, SilenceUsage: true, 60 | PersistentPreRunE: configLoad(none), 61 | Run: func(cmd *cobra.Command, args []string) { 62 | nodes := configure.Hosts.All() 63 | utils.Assert(len(nodes) > 0, "not found any host, use `vik8s host ` to add.") 64 | 65 | if len(args) > 0 { 66 | runCmd(false, strings.Join(args, " "), nodes) 67 | return 68 | } 69 | 70 | sync := false 71 | filters := make([]string, 0) 72 | 73 | term := liner.NewLiner() 74 | defer func() { _ = term.Close() }() 75 | term.SetCtrlCAborts(true) 76 | 77 | historyFile := paths.Join("history") 78 | if f, err := os.Open(historyFile); err == nil { 79 | _, _ = term.ReadHistory(f) 80 | _ = f.Close() 81 | } 82 | defer func() { 83 | if f, err := os.Create(historyFile); err != nil { 84 | log.Print("Error writing history file: ", err) 85 | } else { 86 | _, _ = term.WriteHistory(f) 87 | _ = f.Close() 88 | } 89 | }() 90 | 91 | for { 92 | line, err := term.Prompt("vik8s> ") 93 | if err == io.EOF { 94 | break 95 | } else if err == liner.ErrPromptAborted || len(line) == 0 { 96 | continue 97 | } 98 | 99 | line = strings.TrimSpace(line) 100 | if line[0] == '@' { 101 | filters = strings.Split(strings.TrimSpace(line[1:]), " ") 102 | continue 103 | } else if line == "&" { 104 | sync = true 105 | continue 106 | } else if line == "-" { 107 | sync = false 108 | filters = filters[0:0] 109 | continue 110 | } 111 | 112 | switch line { 113 | case "": 114 | case "clear": 115 | _, _ = os.Stdout.Write([]byte("\x1b[2J\x1b[0;0H")) 116 | case "exit", "quit": 117 | return 118 | default: 119 | runCmd(sync, line, nodes, filters...) 120 | term.AppendHistory(line) 121 | } 122 | } 123 | }, 124 | } 125 | -------------------------------------------------------------------------------- /reduce/config/types.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type ( 10 | Virtual string 11 | 12 | Directive struct { 13 | Line int `json:"line"` 14 | Virtual Virtual `json:"virtual,omitempty"` 15 | Name string `json:"name"` 16 | Args []string `json:"args,omitempty"` 17 | Body Directives `json:"body,omitempty"` 18 | } 19 | Directives []*Directive 20 | DirectiveIterator struct { 21 | items *Directives 22 | current int 23 | } 24 | Configuration = Directive 25 | ) 26 | 27 | func NewDirective(name string, args ...string) *Directive { 28 | return &Directive{Name: name, Args: args} 29 | } 30 | 31 | func (d *Directive) String() string { 32 | return d.Pretty(0) 33 | } 34 | 35 | func (d *Directive) HasArgs() bool { 36 | return len(d.Args) > 0 37 | } 38 | 39 | func (d *Directive) BodyBytes() []byte { 40 | out := bytes.NewBufferString("") 41 | for _, body := range d.Body { 42 | out.WriteString(body.Pretty(0)) 43 | out.WriteString("\n") 44 | } 45 | return out.Bytes() 46 | } 47 | 48 | func (d *Directive) noBody() bool { 49 | if len(d.Body) == 0 { 50 | return true 51 | } else { 52 | for _, body := range d.Body { 53 | if body.Virtual == "" { 54 | return false 55 | } 56 | } 57 | return true 58 | } 59 | } 60 | 61 | func (d *Directive) AddBody(name string, args ...string) *Directive { 62 | body := NewDirective(name, args...) 63 | d.AddBodyDirective(body) 64 | return body 65 | } 66 | 67 | func (d *Directive) AddBodyDirective(directive ...*Directive) { 68 | if d.Body == nil { 69 | d.Body = make([]*Directive, 0) 70 | } 71 | d.Body = append(d.Body, directive...) 72 | } 73 | 74 | func (d *Directive) Pretty(prefix int) string { 75 | prefixString := strings.Repeat(" ", prefix*4) 76 | if d.Name == "#" { 77 | return fmt.Sprintf("%s%s", prefixString, d.Args[0]) 78 | } else if d.Virtual != "" { 79 | return "" 80 | } else { 81 | out := bytes.NewBufferString(prefixString) 82 | out.WriteString(d.Name) 83 | out.WriteString(" ") 84 | if len(d.Args) > 0 { 85 | out.WriteString(strings.Join(d.Args, " ")) 86 | } 87 | 88 | if d.noBody() { 89 | out.WriteString(";") 90 | } else { 91 | out.WriteString(" {") 92 | for _, body := range d.Body { 93 | out.WriteString("\n") 94 | out.WriteString(body.Pretty(prefix + 1)) 95 | } 96 | out.WriteString(fmt.Sprintf("\n%s}", prefixString)) 97 | } 98 | return out.String() 99 | } 100 | } 101 | 102 | func (ds *Directives) Get(name string) *Directive { 103 | for _, d := range *ds { 104 | if d.Name == name { 105 | return d 106 | } 107 | } 108 | return nil 109 | } 110 | 111 | func (ds *Directives) Remove(name string) *Directive { 112 | for i, d := range *ds { 113 | if d.Name == name { 114 | *ds = append((*ds)[0:i], (*ds)[i+1:]...) 115 | return d 116 | } 117 | } 118 | return nil 119 | } 120 | 121 | func (ds *Directives) Iterator() *DirectiveIterator { 122 | return &DirectiveIterator{ 123 | current: -1, items: ds, 124 | } 125 | } 126 | 127 | func (ds *DirectiveIterator) Next() *Directive { 128 | ds.current++ 129 | return (*ds.items)[ds.current] 130 | } 131 | 132 | func (ds *DirectiveIterator) HasNext() bool { 133 | return len(*ds.items) > ds.current+1 134 | } 135 | 136 | func (ds *DirectiveIterator) Remove() { 137 | *ds.items = append((*ds.items)[0:ds.current], (*ds.items)[ds.current+1:]...) 138 | ds.current-- 139 | } 140 | -------------------------------------------------------------------------------- /libs/ssh/scp.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "github.com/cheggaaa/pb/v3" 8 | "github.com/ihaiker/vik8s/install/paths" 9 | "io/ioutil" 10 | "log" 11 | "path/filepath" 12 | "strconv" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | func (node *Node) Equal(local interface{}, remote string) bool { 18 | localMd5code := "true" 19 | //local 20 | if bs, match := local.([]byte); match { 21 | bbb := md5.Sum(bs) 22 | localMd5code = hex.EncodeToString(bbb[:]) 23 | } else { 24 | if bs, err := ioutil.ReadFile(local.(string)); err != nil { 25 | return false 26 | } else { 27 | bbb := md5.Sum(bs) 28 | localMd5code = hex.EncodeToString(bbb[:]) 29 | } 30 | } 31 | 32 | //remote 33 | remoteMd5Code, _ := node.Sudo().HideLog().CmdString(fmt.Sprintf("md5sum %s | awk '{printf $1}'", remote)) 34 | return strings.EqualFold(localMd5code, remoteMd5Code) 35 | } 36 | 37 | func (node *Node) Scp(localPath, remotePath string) error { 38 | if node.IsRoot() || !node.isSudo() { 39 | return node.scp(localPath, remotePath, remotePath, node.isShowLogger()) 40 | } else { 41 | path := fmt.Sprintf("/tmp/vik8s/%s", filepath.Base(localPath)) 42 | if err := node.scp(localPath, path, remotePath, node.isShowLogger()); err != nil { 43 | return err 44 | } 45 | dir := filepath.Dir(remotePath) 46 | if err := node.Sudo().HideLog().Cmd("mkdir -p " + dir); err != nil { 47 | return err 48 | } 49 | return node.Sudo().HideLog().Cmd(fmt.Sprintf("mv -f %s %s", strconv.Quote(path), strconv.Quote(remotePath))) 50 | } 51 | } 52 | 53 | func (node *Node) ScpContent(content []byte, remotePath string) error { 54 | if node.isShowLogger() { 55 | line := strings.Repeat("-", 30) 56 | node.Logger("push bytes to %s\n%s\n%s\n%s", remotePath, line, string(content), line) 57 | } 58 | defer node.reset() 59 | 60 | if node.IsRoot() || !node.isSudo() { 61 | return node._scpContent(content, remotePath) 62 | } else { 63 | path := fmt.Sprintf("/tmp/vik8s/%s", filepath.Base(remotePath)) 64 | if err := node._scpContent(content, path); err != nil { 65 | return err 66 | } 67 | dir := filepath.Dir(remotePath) 68 | if err := node.Sudo().HideLog().Cmd("mkdir -p " + dir); err != nil { 69 | return err 70 | } 71 | return node.Sudo().HideLog(). 72 | Cmd(fmt.Sprintf("mv -f %s %s", strconv.Quote(path), strconv.Quote(remotePath))) 73 | } 74 | } 75 | 76 | func (node *Node) scp(localPath, temporaryRemotePath, remotePath string, showProgressBar bool) error { 77 | defer node.reset() 78 | 79 | showProgressBar = showProgressBar && !paths.IsTerraform 80 | 81 | var bar *pb.ProgressBar 82 | if showProgressBar { 83 | bar = pb.New64(100) 84 | defer bar.Finish() 85 | bar.SetWriter(log.Writer()) 86 | bar.SetRefreshRate(time.Millisecond * 300) 87 | bar.Set("prefix", 88 | fmt.Sprintf("%s scp %s %s ", node.Prefix(), localPath, remotePath)) 89 | bar.Start() 90 | } 91 | 92 | return node._scp(localPath, temporaryRemotePath, func(step, all int64) { 93 | if showProgressBar { 94 | bar.SetTotal(all) 95 | bar.SetCurrent(step) 96 | } 97 | }) 98 | } 99 | 100 | func (node *Node) Pull(remotePath, localPath string) error { 101 | node.Logger("pull %s %s", remotePath, localPath) 102 | 103 | var bar *pb.ProgressBar 104 | if !paths.IsTerraform { 105 | bar = pb.New64(100) 106 | defer bar.Finish() 107 | bar.SetWriter(log.Writer()) 108 | bar.SetRefreshRate(time.Millisecond * 300) 109 | bar.Set(pb.Terminal, true) 110 | bar.Start() 111 | } 112 | 113 | return node._pull(remotePath, localPath, func(step, total int64) { 114 | if bar != nil { 115 | bar.SetTotal(total) 116 | bar.SetCurrent(step) 117 | } 118 | }) 119 | } 120 | -------------------------------------------------------------------------------- /install/hosts/managers.go: -------------------------------------------------------------------------------- 1 | package hosts 2 | 3 | import ( 4 | "github.com/ihaiker/ngx/v2" 5 | "github.com/ihaiker/vik8s/libs/ssh" 6 | "github.com/ihaiker/vik8s/libs/utils" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | type Option = ssh.Node 14 | 15 | type Manager struct { 16 | path string 17 | opt Option 18 | Nodes ssh.Nodes `ngx:"node"` 19 | } 20 | 21 | func New(path string, opt Option) (*Manager, error) { 22 | manager := &Manager{ 23 | path: path, opt: opt, 24 | Nodes: ssh.Nodes{}, 25 | } 26 | err := manager.load() 27 | return manager, err 28 | } 29 | 30 | func (this *Manager) load() error { 31 | if utils.NotExists(this.path) { 32 | return nil 33 | } 34 | 35 | if context, err := ioutil.ReadFile(this.path); err != nil { 36 | return err 37 | } else if err = ngx.Unmarshal(context, this); err != nil { 38 | return err 39 | } 40 | 41 | for _, node := range this.Nodes { 42 | _ = this.getProxy(node) 43 | } 44 | return nil 45 | } 46 | func (this *Manager) down() error { 47 | dir := filepath.Dir(this.path) 48 | if err := os.MkdirAll(dir, os.ModePerm); err != nil { 49 | return err 50 | } 51 | if out, err := ngx.Marshal(this); err != nil { 52 | return err 53 | } else { 54 | return ioutil.WriteFile(this.path, out, 0644) 55 | } 56 | } 57 | 58 | func (this *Manager) All() ssh.Nodes { 59 | return this.Nodes 60 | } 61 | 62 | func (this *Manager) getProxy(node *ssh.Node) error { 63 | if node.Proxy != "" { 64 | if proxyNode := this.Get(node.Proxy); proxyNode != nil { 65 | node.ProxyNode = proxyNode 66 | } else { 67 | return utils.Error("the ssh proxy %s not found", node.Proxy) 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | func (this *Manager) Add(node *ssh.Node) error { 74 | defer this.down() 75 | for i, n := range this.Nodes { 76 | if n.Host == node.Host { 77 | this.Nodes[i] = node 78 | return nil 79 | } 80 | } 81 | this.Nodes = append(this.Nodes, node) 82 | return nil 83 | } 84 | 85 | func (this *Manager) Get(args string) *ssh.Node { 86 | return this.Nodes.Get(args) 87 | } 88 | 89 | func (this *Manager) FetchNode(overwrite bool, nodes ...*ssh.Node) (ssh.Nodes, error) { 90 | for i, node := range nodes { 91 | oldNode := this.Get(node.Host) 92 | if oldNode == nil || overwrite { //当前节点不在列表中,或者需要覆盖 93 | if err := node.Sudo().HideLog().GatheringFacts(); err != nil { 94 | return nil, utils.Wrap(err, "gathering facts") 95 | } 96 | _ = this.Add(node) 97 | } else { 98 | nodes[i] = oldNode 99 | } 100 | } 101 | return nodes, nil 102 | } 103 | 104 | // Fetch 配置 105 | func (this *Manager) Fetch(overwrite bool, args ...string) (ssh.Nodes, error) { 106 | nodes := ssh.Nodes{} 107 | for _, arg := range args { 108 | if node := this.Get(arg); node != nil { 109 | nodes = append(nodes, node) 110 | } else if ns, err := ParseAddr(this.opt, arg); err == nil { 111 | for _, node = range ns { 112 | if err = this.getProxy(node); err != nil { 113 | return nil, err 114 | } 115 | } 116 | nodes = append(nodes, ns...) 117 | } else { 118 | return nil, utils.Error("not found node: %s", arg) 119 | } 120 | } 121 | return this.FetchNode(overwrite, nodes...) 122 | } 123 | 124 | func (this *Manager) MustGet(arg string) *ssh.Node { 125 | node := this.Get(arg) 126 | utils.Assert(node != nil, "not found %s", arg) 127 | err := node.GatheringFacts() 128 | utils.Panic(err, "gathering facts: %s", node.Host) 129 | return node 130 | } 131 | 132 | func (this *Manager) MustGets(args []string) ssh.Nodes { 133 | nodes, err := this.Fetch(false, args...) 134 | utils.Panic(err, "fetch nodes: %s", strings.Join(args, ", ")) 135 | for _, node := range nodes { 136 | err = node.GatheringFacts() 137 | utils.Panic(err, "gathering facts: %s", node.Host) 138 | } 139 | return nodes 140 | } 141 | 142 | func (this *Manager) Gets(args []string) (ssh.Nodes, error) { 143 | return this.Fetch(false, args...) 144 | } 145 | -------------------------------------------------------------------------------- /docs/reduce/index.md: -------------------------------------------------------------------------------- 1 | # Reduce kubernetes 简化配置 2 | 3 | 无论是在学习或者工作中都要编写yaml文件,我不知道其他人对yaml配置是否存在这样困惑:配置是否有些复杂了(当然了解了细节也明白是为啥)。 4 | 本程序reduce命令将尽可能的简化配置。 5 | 6 | reduce命令采用类似`nginx.conf`的配置方式配置kubernetes。 7 | 8 | 先给一个简单的配置:mysql.conf 9 | ```nginx 10 | kubernetes v1.18.2; 11 | prefix vik8s.io; 12 | namespace vik8s; 13 | 14 | configmap mysql-config { 15 | mysql.root.password haiker; 16 | mysql.vik8s.password vik8s; 17 | } 18 | 19 | deployment mysql { 20 | restartPolicy Always; 21 | container mysql mysql:5.7.29 IfNotPresent { 22 | port 3306; 23 | env MYSQL_ROOT_PASSWORD configmap mysql-config mysql.root.password; 24 | envs { 25 | MYSQL_DATABASE vik8s; 26 | MYSQL_USER vik8s; 27 | MYSQL_PASSWORD configmap mysql-config mysql.vik8s.password; 28 | } 29 | mount hostPath:mysql-data /data/mysql:/var/lib/mysql; 30 | } 31 | 32 | container php-my-admin phpmyadmin/phpmyadmin { 33 | envs { 34 | PMA_HOST 127.0.0.1; 35 | PMA_USER root; 36 | PMA_PASSWORD configmap mysql-config mysql.root.password; 37 | } 38 | port http 80; 39 | } 40 | } 41 | 42 | service deployment:mysql mysql-admin { 43 | port admin 80:80; 44 | port mysql 3306:3306; 45 | } 46 | 47 | ingress mysql-admin { 48 | rules myadmin.vik8s.io { 49 | http paths { 50 | serviceName mysql-admin; 51 | servicePort admin; 52 | } 53 | } 54 | } 55 | ``` 56 | 使用 `vik8s reduce mysql.conf` 编译后输出yaml内容。不知道您是否觉得会感觉配置清爽了许多呢(只要您不回答:呵呵,就好)。 57 | 58 | ## Reduce 原理 59 | 60 | 配置分为四种配置方式: 61 | 62 | - [用户自定配置插件](./plugins.md) 63 | - Reduce 系统定义配置方式 64 | - kubernetes配置转换方式 65 | - yaml标签方式。 66 | 67 | 优先顺序为从前向后。 68 | 69 | ### Reduce 系统定义配置方式 70 | 71 | 关于此处就不使用长篇论述了,您可以自己使用 `vik8s reduce demo ` 查看demo实例。 72 | 73 | ### kubernetes配置转换方式 74 | 由于kubernetes kind比较多,所有并非所有配置都做了简化处理,当然如果依然要使用此方式配置的话,就需要提供一个配置方式。此方式应用而生。 75 | 此方式配置有个统一的前缀 `kind:version name`。下面我们就一个简单的实例来说明。 76 | 77 | ```nginx 78 | CustomResourceDefinition:apiextensions.k8s.io/v1beta1 bgpconfigurations.crd.projectcalico.org { 79 | scope Cluster; 80 | group crd.projectcalico.org; 81 | version v1; 82 | names { 83 | kind BGPConfiguration; 84 | plural bgpconfigurations; 85 | singular bgpconfiguration; 86 | } 87 | } 88 | 89 | 90 | PodSecurityPolicy:policy/v1beta1 psp.flannel.unprivileged { 91 | annotations { 92 | seccomp.security.alpha.kubernetes.io/allowedProfileNames docker/default; 93 | seccomp.security.alpha.kubernetes.io/defaultProfileName docker/default; 94 | apparmor.security.beta.kubernetes.io/allowedProfileNames runtime/default; 95 | apparmor.security.beta.kubernetes.io/defaultProfileName runtime/default; 96 | } 97 | privileged false; 98 | volumes configMap secret emptyDir hostPath; 99 | 100 | allowedHostPaths { 101 | pathPrefix "/etc/cni/net.d"; 102 | } 103 | allowedHostPaths pathPrefix=/etc/kube-flannel; 104 | allowedHostPaths pathPrefix=/run/flannel; 105 | 106 | readOnlyRootFilesystem false; 107 | runAsUser { 108 | rule RunAsAny; 109 | } 110 | supplementalGroups { 111 | rule RunAsAny; 112 | } 113 | fsGroup rule=RunAsAny; 114 | # Privilege Escalation 115 | allowPrivilegeEscalation false; 116 | defaultAllowPrivilegeEscalation false; 117 | # Capabilities 118 | allowedCapabilities 'NET_ADMIN'; 119 | # Host namespaces 120 | hostPID false; 121 | hostIPC false; 122 | hostNetwork true; 123 | hostPorts { 124 | min 0; 125 | max 65535; 126 | } 127 | # SELinux 128 | seLinux rule=RunAsAny; 129 | } 130 | ``` 131 | 132 | ### yaml标签方式。 133 | 此方式就是直接使用yaml配置文件,做配置。 134 | ```nginx 135 | yaml ' 136 | yamlSource 137 | '; 138 | ``` 139 | 140 | ## 更多Demo查看 141 | ```shell 142 | vik8s reduce demo 143 | ``` 144 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/fatih/color" 7 | "github.com/ihaiker/vik8s/config" 8 | hs "github.com/ihaiker/vik8s/install/hosts" 9 | "github.com/ihaiker/vik8s/install/paths" 10 | "github.com/ihaiker/vik8s/libs/utils" 11 | "github.com/spf13/cobra" 12 | "os" 13 | "runtime" 14 | "strings" 15 | ) 16 | 17 | var configure *config.Configuration 18 | 19 | //configLoad load configure 20 | func configLoad(fn func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) error { 21 | return func(cmd *cobra.Command, args []string) (err error) { 22 | if configure, err = config.Load(paths.Vik8sConfiguration()); err != nil { 23 | return 24 | } 25 | if configure.Hosts, err = hs.New(paths.HostsConfiguration(), hs.Option{ 26 | Port: 22, 27 | User: "root", 28 | PrivateKey: "$HOME/.ssh/id_rsa", 29 | }); err != nil { 30 | return 31 | } 32 | return fn(cmd, args) 33 | } 34 | } 35 | 36 | //configDown the configure save it. 37 | func configDown(fn func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) error { 38 | return func(cmd *cobra.Command, args []string) error { 39 | if err := configure.Write(); err != nil { 40 | return utils.Wrap(err, "write configuration error") 41 | } 42 | return fn(cmd, args) 43 | } 44 | } 45 | 46 | func none(cmd *cobra.Command, args []string) error { 47 | return nil 48 | } 49 | 50 | var rootCmd = &cobra.Command{ 51 | Use: "vik8s", Short: "very easy install HA k8s", 52 | Long: "very easy install k8s。Build: %s, Go: %s, GitLog: %s", 53 | } 54 | 55 | var completionCmd = &cobra.Command{ 56 | Use: "completion", Short: "generates completion scripts", 57 | Args: cobra.ExactArgs(1), ValidArgs: []string{"bash", "zsh", "powershell", "fish"}, 58 | RunE: func(cmd *cobra.Command, args []string) error { 59 | switch args[0] { 60 | default: 61 | return errors.New("not support") 62 | case "bash": 63 | return rootCmd.GenBashCompletion(os.Stdout) 64 | case "zsh": 65 | return rootCmd.GenZshCompletion(os.Stdout) 66 | case "powershell": 67 | return rootCmd.GenPowerShellCompletionWithDesc(os.Stdout) 68 | case "fish": 69 | return rootCmd.GenFishCompletion(os.Stdout, true) 70 | } 71 | }, 72 | } 73 | 74 | func init() { 75 | rootCmd.PersistentFlags().StringVarP(&paths.ConfigDir, "config", "f", 76 | paths.ConfigDir, "The folder where the configure file is located") 77 | rootCmd.PersistentFlags().StringVarP(&paths.Cloud, "cloud", "c", paths.Cloud, 78 | "Multi-kubernetes cluster selection") 79 | rootCmd.PersistentFlags().BoolVar(&paths.China, "china", true, "Whether domestic network") 80 | 81 | rootCmd.AddCommand(hostsCmd, etcdCmd) 82 | rootCmd.AddCommand(initCmd, joinCmd, resetCmd, cleanCmd) 83 | rootCmd.AddCommand(ingressRootCmd) 84 | rootCmd.AddCommand(completionCmd) 85 | rootCmd.AddCommand(bashCmd) 86 | rootCmd.Flags().SortFlags = false 87 | } 88 | 89 | func Execute(version, buildTime, gitTag string) { 90 | rootCmd.Version = version 91 | rootCmd.Long = fmt.Sprintf(rootCmd.Long, buildTime, runtime.Version(), gitTag) 92 | rootCmd.SilenceUsage = true 93 | rootCmd.SilenceErrors = true 94 | 95 | defer utils.Catch(func(err error) { 96 | if serr, match := err.(*utils.WrapError); match { 97 | _, _ = color.New(color.FgRed).Println(serr.Error()) 98 | } else { 99 | color.Red(err.Error()) 100 | color.Red(utils.Stack()) 101 | } 102 | os.Exit(1) 103 | }) 104 | 105 | if runCommand, args, err := rootCmd.Find(os.Args[1:]); err == nil { 106 | if runCommand.Name() == rootCmd.Name() { 107 | for i, arg := range args { 108 | if arg == "--" { 109 | param := strings.Join(args[i+1:], " ") 110 | os.Args = append(os.Args[0:i+1], "bash", param) 111 | break 112 | } 113 | } 114 | } 115 | } 116 | 117 | if err := rootCmd.Execute(); err != nil { 118 | fmt.Println(color.HiRedString(err.Error())) 119 | os.Exit(1) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /cmd/k8s.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ihaiker/cobrax" 6 | "github.com/ihaiker/vik8s/config" 7 | "github.com/ihaiker/vik8s/install/k8s" 8 | "github.com/ihaiker/vik8s/libs/logs" 9 | "github.com/ihaiker/vik8s/libs/ssh" 10 | "github.com/ihaiker/vik8s/libs/utils" 11 | "github.com/spf13/cobra" 12 | "strings" 13 | ) 14 | 15 | var k8sConfig = config.DefaultK8SConfiguration() 16 | var initCmd = &cobra.Command{ 17 | Use: "init", Short: "Initialize the kubernetes cluster", 18 | Example: `vik8s init 10.24.1.10 172.10.0.2-172.10.0.5`, 19 | PreRunE: configLoad(none), PostRunE: configDown(none), 20 | Args: cobra.MinimumNArgs(1), 21 | Run: func(cmd *cobra.Command, args []string) { 22 | masters := configure.Hosts.MustGets(args) 23 | 24 | utils.Assert(len(masters) != 0, "master node is empty") 25 | configure.K8S = k8sConfig 26 | 27 | k8s.InitCluster(configure, masters[0]) 28 | for _, ctl := range masters[1:] { 29 | k8s.JoinControl(configure, ctl) 30 | } 31 | 32 | taint, _ := cmd.Flags().GetBool("taint") 33 | if taint { 34 | for _, master := range masters { 35 | k8s.RemoveTaint(configure, master) 36 | } 37 | } 38 | fmt.Println("-=-=-=- SUCCESS -=-=-=-") 39 | }, 40 | } 41 | 42 | func init() { 43 | err := cobrax.FlagsWith(initCmd, cobrax.GetFlags, k8sConfig, "", "VIK8S_K8S") 44 | utils.Panic(err, "setting `init` flag error") 45 | initCmd.Flags().Bool("taint", false, "Update the taints on the nodes") 46 | initCmd.Flags().SortFlags = false 47 | } 48 | 49 | var joinCmd = &cobra.Command{ 50 | Use: "join", Short: "join to k8s", 51 | Example: `vik8s join --master 172.10.0.2-172.10.0.4 52 | vik8s join 172.10.0.2 172.10.0.3 172.10.0.4 172.10.0.5`, 53 | PreRunE: configLoad(none), PostRunE: configDown(none), 54 | Args: cobra.MinimumNArgs(1), 55 | Run: func(cmd *cobra.Command, args []string) { 56 | nodes := configure.Hosts.MustGets(args) 57 | if len(nodes) == 0 { 58 | fmt.Println(cmd.UseLine()) 59 | return 60 | } 61 | master, _ := cmd.Flags().GetBool("master") 62 | for _, node := range nodes { 63 | utils.Assert(utils.Search(configure.K8S.Masters, node.Host) == -1 && 64 | utils.Search(configure.K8S.Nodes, node.Host) == -1, 65 | "the host is cluster node yet. %s", node.Hostname) 66 | 67 | if master { 68 | k8s.JoinControl(configure, node) 69 | } else { 70 | k8s.JoinWorker(configure, node) 71 | } 72 | taint, _ := cmd.Flags().GetBool("taint") 73 | if taint { 74 | k8s.RemoveTaint(configure, node) 75 | } 76 | } 77 | fmt.Println("-=-=-=- SUCCESS -=-=-=-") 78 | }, 79 | } 80 | 81 | func init() { 82 | joinCmd.Flags().BoolP("master", "m", false, "Whether it is a control plane") 83 | joinCmd.Flags().Bool("taint", false, "Update the taints on the nodes") 84 | } 85 | 86 | var resetCmd = &cobra.Command{ 87 | Use: "reset", Short: "reset kubernetes cluster node", 88 | Example: "vik8s reset ", 89 | Args: cobra.MinimumNArgs(1), 90 | PreRunE: configLoad(none), PostRunE: configDown(none), 91 | Run: func(cmd *cobra.Command, args []string) { 92 | nodes := args 93 | if configure.K8S == nil { 94 | configure.K8S = config.DefaultK8SConfiguration() 95 | } 96 | if args[0] == "all" { 97 | nodes = append(configure.K8S.Nodes, utils.Reverse(configure.K8S.Masters)...) 98 | } 99 | var master *ssh.Node 100 | if len(configure.K8S.Masters) > 0 { 101 | master = configure.Hosts.MustGet(configure.K8S.Masters[0]) 102 | } 103 | for _, nodeName := range nodes { 104 | node := configure.Hosts.MustGet(nodeName) 105 | utils.Assert(node != nil, "not found kubernetes %s", node.Host) 106 | logs.Infof("remove cluster node %s", node.Prefix()) 107 | 108 | if master != nil { 109 | err := master.Cmd(fmt.Sprintf("kubectl delete nodes %s", node.Hostname)) 110 | utils.Assert(err == nil || strings.Contains(err.Error(), "not found"), 111 | "reset kubernetes node: %v", err) 112 | } 113 | k8s.ResetNode(configure, node) 114 | } 115 | fmt.Println("-=-=-=- SUCCESS -=-=-=-") 116 | }, 117 | } 118 | 119 | func init() { 120 | resetCmd.Flags().Bool("force", false, "") 121 | } 122 | --------------------------------------------------------------------------------