├── Go-package使用 ├── Context用法.md ├── Reflect的使用.md ├── go-restful.md ├── net.http用法.md ├── sort package.md ├── 信号.md ├── 基于cobra构建命令行.md ├── 基础知识整理.md └── 并发Demo.md ├── Linux ├── (01)Unix-Linux编程实践之文件和信号.md ├── (02)Unix-Linux编程实践之进程和程序.md ├── (03)Unix-Linux编程实践之IO重定向和管道.md ├── (04)Unix-Linux编程实践之socket.md ├── (05)Unix-Linux编程实践之线程机制.md ├── (06)Unix-Linux编程实践之IPC.md ├── (07)cp、mv、rm的底层实现.md ├── (08)空洞文件.md └── (09)标准IO库.md ├── README.md ├── apiserver ├── (01) apiserver参数详解.md ├── (02) apiserver综述.md ├── (03) apiserver主体流程.md ├── (04) StorageVersions寻根.md ├── (05) project apimachinery.md ├── (06) 多版本资源注册-apimachinery.md ├── (07) 多版本资源注册-初始化流程.md ├── (08) 多版本资源注册-Scheme-1.md ├── (09) 多版本资源注册-Scheme-2.md ├── (10) 多版本资源注册-RESTMapper.md ├── (11) Apiserver从资源到Restful API的关键概念.md ├── (12) Restful API注册-1.md ├── (13) Restful API注册-2.md ├── (14) storage机制.md ├── (15) StorageWithCacher和UndecoratedStorage.md ├── (16) Apiserver端List-Watch机制-1.md ├── (17) EtcdWatcher.md ├── (18) Apiserver端List-Watch机制-2.md ├── (19) converter例子.md ├── (20) deep dive系列-1.md ├── (21) deep dive系列-2.md ├── (22) 一个Request的流程之Filters.md ├── (23)Authenticator机制.md ├── (24)Authorization机制.md ├── (25) Admission Control机制.md ├── (26) Admission Control机制之Resource Quota.md ├── (27)API Changes.md ├── (28)Adding an API Group.md ├── (29)resourceVersion机制.md └── 定制一个API之ApiServer篇 │ ├── pkg-apis-premierleague │ ├── doc.go │ ├── install │ │ └── install.go │ ├── registry.go │ ├── types.generated.go │ ├── types.go │ ├── v1 │ │ ├── conversion.go │ │ ├── defaults.go │ │ ├── doc.go │ │ ├── registry.go │ │ ├── types.generated.go │ │ └── types.go │ └── validation │ │ └── validation.go │ ├── pkg-registry-premierleague │ ├── match │ │ ├── doc.go │ │ ├── etcd │ │ │ └── etcd.go │ │ └── strategy.go │ └── rest │ │ └── storage_premierleague.go │ └── 定制一个API之ApiServer.md ├── cni plugin ├── calico.md └── flannel源码解读.md ├── controller-manager ├── (01)ControllerManager的List-Watch机制-1.md ├── (02)ControllerManager的List-Watch机制-2.md ├── (03)Reflector机制中的store.md ├── (04)控制器ReplicationManager分析.md ├── (05)ResourceQuota概念介绍.md ├── (06)ResourceQuota流程分析.md ├── (07)定制API之controller.md └── (08)qps限流.md ├── docker ├── (01)Cgroups用法.md ├── (02)docker搭建ceph环境.md ├── (03)docker镜像存储分析.md ├── (04)docker命令行.md ├── (05)Client端与Daemon端的通信.md ├── (06)daemon的创建过程.md ├── (07)layerStore初始化.md ├── (08)ImageStore初始化.md ├── (09)VolumeStore初始化.md ├── (10)docker container create命令.md ├── (11)docker start命令.md ├── (12)grpc.md ├── (13)标准化容器执行引擎-runc.md ├── (14)contianerd基本流程.md ├── (15)containerd之container和process.md ├── (16)containerd-shim分析.md ├── (17)Aufs.md ├── (18)FIFO管道.md ├── (19)ruc create命令.md ├── (20)runc init命令.md ├── (21)runc start命令.md ├── (22)shell命令创建一个简单的容器.md └── (23)containerd之monitor.md ├── etcd ├── cfssl证书.md ├── etcd v3.md └── openssl证书.md ├── images ├── API-server-flow.png ├── API-server-gvr.png ├── API-server-overview.png ├── API-server-serialization-overview.png ├── API-server-space.png ├── API-server-storage-flow.png ├── Calico.png ├── IO重定向.png ├── StorageVersions-00.jpeg ├── access-control-overview.jpg ├── apiserver-00.jpeg ├── containerd-architecture.png ├── dockerDaemon-containerd.png ├── dup重定向.png ├── etcd-101.png ├── execvp的原理.png ├── exec复制环境变量.png ├── flannel-1.png ├── flannel-2.png ├── flannel-3.png ├── fopen和popen.png ├── fork共享管道.png ├── go对string类型的定义.png ├── iptables-1.png ├── iptables.png ├── iptables路由次序图.png ├── kubernetes-controller.png ├── new和make的区别.png ├── pod-controller-panel.png ├── shell为子进程重定向其输出.png ├── shell模型.png ├── shell模型简化版.png ├── slice的定义.png ├── socket流程图.png ├── userspace.png ├── 一个shell的主循环.png ├── 一个进程创建pipe.png ├── 一个进程的三个计时器.png ├── 一个进程的虚拟地址空间.png ├── 三种传输数据的办法.png ├── 内核缓冲区和普通进程缓冲区.png ├── 函数和进程的相似性.png ├── 基础类型在内存中的存在形式.png ├── 多个文件系统的组合.png ├── 文件系统的不同视角-1.png ├── 文件系统的不同视角-2.png ├── 最低可用fd原则.png ├── 标准数据流.png ├── 标准数据流模型.png ├── 环境变量的存储.png ├── 管道.png ├── 系统信号.png ├── 系统调用fork.png ├── 系统调用wait.png ├── 网络传输切割数据包.png ├── 设备文件和普通数据文件的区别.png ├── 设备文件和普通磁盘文件的区别.png ├── 进程通过共享内存交换数据.png └── 重定向策略open-then-close.png ├── kubectl ├── (01) kubectl主体流程.md ├── (02) Factory.md ├── (03) Builder.md ├── (04) Visitor.md ├── (05) Printer.md ├── (06) genetator.md ├── (07) Event-1.md ├── (08) Event-2.md ├── (09) Event-3.md ├── (10) kubernetes里面各种Client.md ├── (11) kubectl总结.md ├── (12) kubeconfig.md ├── (13)Resource Helper分析.md ├── (14)Yaml、JSON文件读取.md └── (15)定制一个API之kubectl.md ├── kubelet ├── (01)Client端的List-Watch机制-kubelet.md ├── (02)Demo-合并三个来源的Pod.md ├── (03)Ceph Rbd使用.md ├── (04) kubernetes存储机制.md └── (05)kubelet资源上报&Evition机制.md ├── proxy ├── (01)Demo-Broadcaster的使用.md ├── (02)iptables.md ├── (03)kube-proxy流程分析.md ├── (04)iptables的DNAT和SNAT.md └── (05)Nginx Ingress的使用.md ├── scheduler ├── k8s调度之Affinity.md ├── scheduler流程分析.md └── 预选和优选策略.md ├── 常用的网站.md └── 深入go └── (01)基本类型.md /Go-package使用/Context用法.md: -------------------------------------------------------------------------------- 1 | # Context用法 2 | 3 | ## 简介 4 | 在 Go http包的Server中,每一个请求在都有一个对应的 goroutine 去处理。 请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和RPC服务。 用来处理一个请求的 goroutine 通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的token、请求的截止时间。 当一个请求被取消或超时时,所有用来处理该请求的 goroutine 都应该迅速退出,然后系统才能释放这些 goroutine 占用的资源。 那么该如何优雅地同时关闭多个goroutine呢? 这个时候就轮到context包上场了。 5 | 6 | ## Context的核心数据结构 7 | type Context interface 是 package context的核心数据结构: 8 | 9 | 1. Done 方法返回一个 channel,这个 channel 对于以 Context 方式运行的函数而言,是一个取消信号。 当这个 channel 关闭时,上面提到的这些函数应该终止手头的工作并立即返回。 之后,Err 方法会返回一个错误,告知为什么 Context 被取消。 10 | 11 | 2. Context 对象是线程安全的,你可以把一个 Context 对象传递给任意个数的 gorotuine,对它执行取消操作时,所有 goroutine 都会接收到取消信号。 12 | 13 | 3. Deadline() 方法允许函数确定它们是否应该开始工作。 如果剩下的时间太少,也许这些函数就不值得启动。在代码中,我们也可以使用 Deadline 对象为 I/O 操作设置截止时间。 14 | 15 | 4. Value 方法允许 Context 对象携带request作用域的数据,该数据必须是线程安全的。 16 | 17 | 5. 一个 Context 不能拥有 Cancel() 方法,同时我们也只能使用 Done channel 来接收数据。 原因是:接收取消信号的函数和发送取消信号的函数通常不是一个。 一个典型的场景是:父操作为子操作操作启动 goroutine,子操作也就不能取消父操作。 作为一个折中,WithCancel() 函数提供了一种取消新的 Context 的方法。 18 | 19 | ```go 20 | // A Context carries a deadline, a cancelation signal, and other values across 21 | // API boundaries. 22 | // 23 | // Context's methods may be called by multiple goroutines simultaneously. 24 | 25 | type Context interface { 26 | // Deadline returns the time when work done on behalf of this context 27 | // should be canceled. Deadline returns ok==false when no deadline is 28 | // set. Successive calls to Deadline return the same results. 29 | Deadline() (deadline time.Time, ok bool) 30 | 31 | // Done returns a channel that's closed when work done on behalf of this 32 | // context should be canceled. Done may return nil if this context can 33 | // never be canceled. Successive calls to Done return the same value. 34 | // 35 | // WithCancel arranges for Done to be closed when cancel is called; 36 | // WithDeadline arranges for Done to be closed when the deadline 37 | // expires; WithTimeout arranges for Done to be closed when the timeout 38 | // elapses. 39 | // 40 | // Done is provided for use in select statements: 41 | // 42 | // // Stream generates values with DoSomething and sends them to out 43 | // // until DoSomething returns an error or ctx.Done is closed. 44 | // func Stream(ctx context.Context, out chan<- Value) error { 45 | // for { 46 | // v, err := DoSomething(ctx) 47 | // if err != nil { 48 | // return err 49 | // } 50 | // select { 51 | // case <-ctx.Done(): 52 | // return ctx.Err() 53 | // case out <- v: 54 | // } 55 | // } 56 | // } 57 | // 58 | // See http://blog.golang.org/pipelines for more examples of how to use 59 | // a Done channel for cancelation. 60 | Done() <-chan struct{} 61 | 62 | // Err returns a non-nil error value after Done is closed. Err returns 63 | // Canceled if the context was canceled or DeadlineExceeded if the 64 | // context's deadline passed. No other values for Err are defined. 65 | // After Done is closed, successive calls to Err return the same value. 66 | Err() error 67 | 68 | // Value returns the value associated with this context for key, or nil 69 | // if no value is associated with key. Successive calls to Value with 70 | // the same key returns the same result. 71 | // 72 | // Use context values only for request-scoped data that transits 73 | // processes and API boundaries, not for passing optional parameters to 74 | // functions. 75 | // 76 | // A key identifies a specific value in a Context. Functions that wish 77 | // to store values in Context typically allocate a key in a global 78 | // variable then use that key as the argument to context.WithValue and 79 | // Context.Value. A key can be any type that supports equality; 80 | // packages should define keys as an unexported type to avoid 81 | // collisions. 82 | // 83 | // Packages that define a Context key should provide type-safe accessors 84 | // for the values stores using that key: 85 | // 86 | // // Package user defines a User type that's stored in Contexts. 87 | // package user 88 | // 89 | // import "golang.org/x/net/context" 90 | // 91 | // // User is the type of value stored in the Contexts. 92 | // type User struct {...} 93 | // 94 | // // key is an unexported type for keys defined in this package. 95 | // // This prevents collisions with keys defined in other packages. 96 | // type key int 97 | // 98 | // // userKey is the key for user.User values in Contexts. It is 99 | // // unexported; clients use user.NewContext and user.FromContext 100 | // // instead of using this key directly. 101 | // var userKey key = 0 102 | // 103 | // // NewContext returns a new Context that carries value u. 104 | // func NewContext(ctx context.Context, u *User) context.Context { 105 | // return context.WithValue(ctx, userKey, u) 106 | // } 107 | // 108 | // // FromContext returns the User value stored in ctx, if any. 109 | // func FromContext(ctx context.Context) (*User, bool) { 110 | // u, ok := ctx.Value(userKey).(*User) 111 | // return u, ok 112 | // } 113 | Value(key interface{}) interface{} 114 | } 115 | ``` 116 | 117 | ## 创建根context 118 | context 包提供了一些函数,协助用户从现有的 Context 对象创建新的 Context 对象。 这些 Context 对象形成一棵树:当一个 Context 对象被取消时,继承自它的所有 Context 都会被取消。 119 | 120 | Background()是所有 Context 对象树的根,它不能被取消。 121 | ```go 122 | func Background() Context { 123 | return background 124 | } 125 | ``` 126 | 127 | ## 创建子context 128 | WithCancel 和 WithTimeout 函数会返回继承的 Context 对象,这些对象可以比它们的父 Context 更早地取消。 当请求处理函数返回时,与该请求关联的 Context 会被取消。 129 | - 当使用多个副本发送请求时,可以使用 WithCancel 取消多余的请求。 130 | - WithTimeout 在设置对后端服务器请求截止时间时非常有用。 131 | ```go 132 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 133 | ctx, f := context.WithCancel(parent) 134 | return ctx, CancelFunc(f) 135 | } 136 | 137 | func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { 138 | return WithDeadline(parent, time.Now().Add(timeout)) 139 | } 140 | ``` 141 | 142 | ## WithTimeout例子 143 | 以下是官方提供的例子,子context ctx会在30Second后被关闭,从而触发了`ctx.Done()` 144 | ```go 145 | func main() { 146 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 147 | defer cancel() 148 | 149 | select { 150 | case <-time.After(2 * time.Minute): 151 | fmt.Println("overslept") 152 | case <-ctx.Done(): 153 | fmt.Println(ctx.Err()) // prints "context deadline exceeded" 154 | } 155 | } 156 | ``` 157 | 这里输出结果是 158 | ```shell 159 | context deadline exceeded 160 | ``` 161 | 162 | ## context的使用规范 163 | 1. context包里的方法是线程安全的,可以被多个线程使用 164 | 2. 就算是被多个不同的goroutine使用,context也是安全的 165 | 3. 把context作为第一个参数,并且一般都把变量命名为ctx 166 | 167 | ## 参考 168 | [Go语言并发模型:使用 context](https://segmentfault.com/a/1190000006744213) 169 | 170 | [Golang之Context的使用](http://www.nljb.net/default/Golang之Context的使用/) 171 | 172 | [Golang之Context](https://studygolang.com/articles/9485) 173 | 174 | [源码解读](http://blog.csdn.net/xiaohu50/article/details/49100433) -------------------------------------------------------------------------------- /Go-package使用/Reflect的使用.md: -------------------------------------------------------------------------------- 1 | # Reflect Package 2 | 3 | [Reflect Package](https://golang.org/pkg/reflect/#Kind) 4 | 5 | ## 基本用法 6 | Type()得到的是静态类型,Kind()得到的是相关类型 7 | ``` 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "reflect" 13 | ) 14 | 15 | type myInt int 16 | 17 | type T struct { 18 | A int 19 | B string 20 | } 21 | 22 | func main() { 23 | var i myInt 24 | 25 | i = 4 26 | a := reflect.ValueOf(i) 27 | /* 28 | Type()和Kind()的区别 29 | */ 30 | fmt.Println(a.Type()) // main.myInt, Type()得到的是静态类型 31 | fmt.Println(a.Kind()) // int, Kind()得到的是相关类型 32 | fmt.Println(a.Kind() == reflect.Int) // true 33 | 34 | fmt.Println(a.Interface()) // 4 35 | 36 | /* 37 | 通过反射来修改value的值 38 | */ 39 | fmt.Println(a.CanSet()) // false,表示a不可以被设置。因为reflect.ValueOf得到的是i的一个复制副本, 而不是i本身 40 | point := reflect.ValueOf(&i) 41 | fmt.Println(point.CanSet()) // false,因为b是一个指针,我们要修改的是i的值,而不是指针&i的值 42 | 43 | c := point.Elem() // 通过 Value 的 Elem() 方法来指针所指向内容的 Value 44 | fmt.Println(c.CanSet()) // true 45 | c.SetInt(99) 46 | fmt.Println(c.Interface()) // 99,修改成功 47 | fmt.Println(i) // 99 48 | 49 | /* 50 | 结构体 51 | */ 52 | t := T{33, "my"} 53 | point_e := reflect.ValueOf(&t).Elem() 54 | fmt.Println(point_e.Type()) // main.T 55 | point_e.Field(0).SetInt(11) //通过反射修改结构体中的某个成员的值 56 | fmt.Println(point_e.Interface()) // &{11 my} 57 | fmt.Println(point_e.Field(0).Type()) //int 58 | } 59 | ``` -------------------------------------------------------------------------------- /Go-package使用/go-restful.md: -------------------------------------------------------------------------------- 1 | # go-restful 2 | 3 | 简单介绍[package go-restful](https://godoc.org/github.com/emicklei/go-restful)的用法。 4 | 构建REST-style WebServices的一个第三方包 5 | 6 | 本文摘抄于[apiServer之go-restful的使用](http://dockone.io/article/2171) 7 | 8 | ## 关键组件 9 | ### Route 10 | 路由包含两种,一种是标准JSR311接口规范的实现RouterJSR311,一种是快速路由CurlyRouter。 11 | 12 | CurlyRouter支持正则表达式和动态参数,相比RouterJSR11更加轻量级,apiserver中使用的就是这种路由。 13 | 14 | A Route is defined by a HTTP method, an URL path and (optionally) the MIME types it consumes (Content-Type) and produces (Accept). 15 | Route binds a HTTP Method,Path,Consumes combination to a RouteFunction. 16 | 其中请求方法(http Method),请求路径(URL Path),输入输出类型(JSON/YAML),响应内容类型(Accept)。 17 | 18 | ### WebService 19 | WebService逻辑上是Route的集合,功能上主要是为一组Route统一设置包括root path,请求响应的数据类型等一些通用的属性。 20 | 21 | 需要注意的是,WebService必须加入到Container中才能生效。 22 | 23 | ### Container 24 | Container逻辑上是WebService的集合,功能上可以实现多终端的效果。 25 | 26 | 它包括一组restful.WebService和一个http.ServeMux对象,使用RouteSelector进行请求派发。 27 | 28 | 例如,下面代码中创建了两个Container,分别在不同的port上提供服务。 29 | ```go 30 | package main 31 | 32 | import ( 33 | "github.com/emicklei/go-restful" 34 | "io" 35 | "log" 36 | "net/http" 37 | ) 38 | 39 | func main() { 40 | ws := new(restful.WebService) 41 | ws.Route(ws.GET("/hello").To(hello)) 42 | /* 43 | ws被添加到默认的container restful.DefaultContainer中 44 | DefaultContainer is a restful.Container that uses http.DefaultServeMux 45 | */ 46 | restful.Add(ws) 47 | go func() { 48 | // restful.DefaultContainer监听在端口8080上 49 | http.ListenAndServe(":8080", nil) 50 | }() 51 | 52 | /* 53 | NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter) 54 | */ 55 | container2 := restful.NewContainer() 56 | ws2 := new(restful.WebService) 57 | ws2.Route(ws2.GET("/hello").To(hello2)) 58 | //WebService ws2被添加到container2中 59 | container2.Add(ws2) 60 | // container2中监听端口8081 61 | server := &http.Server{Addr: ":8081", Handler: container2} 62 | log.Fatal(server.ListenAndServe()) 63 | } 64 | 65 | func hello(req *restful.Request, resp *restful.Response) { 66 | io.WriteString(resp, "default world") 67 | } 68 | 69 | func hello2(req *restful.Request, resp *restful.Response) { 70 | io.WriteString(resp, "second world") 71 | } 72 | ``` 73 | 74 | ### Filter 75 | Filter用于动态的拦截请求和响应,类似于放置在相应组件前的钩子,在相应组件功能运行前捕获请求或者响应,主要用于记录log,验证,重定向等功能。 76 | go-restful中有三种类型的Filter: 77 | - Container Filter: 78 | 运行在Container中所有的WebService执行之前。 79 | ```go 80 | // install a (global) filter for the default container (processed before any webservice) 81 | restful.Filter(globalLogging) 82 | ``` 83 | - WebService Filter: 84 | 运行在WebService中所有的Route执行之前。 85 | ```go 86 | // install a webservice filter (processed before any route) 87 | ws.Filter(webserviceLogging).Filter(measureTime) 88 | ``` 89 | - Route Filter: 90 | 运行在调用Route绑定的方法之前。 91 | ```go 92 | // install 2 chained route filters (processed before calling findUser) 93 | ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter).To(findUser)) 94 | ``` 95 | 96 | 例子如下: 97 | ```go 98 | package main 99 | 100 | import ( 101 | "github.com/emicklei/go-restful" 102 | "log" 103 | "net/http" 104 | ) 105 | 106 | type User struct { 107 | Id, Name string 108 | } 109 | 110 | type UserResource struct { 111 | // normally one would use DAO (data access object) 112 | users map[string]User 113 | } 114 | 115 | func (u UserResource) Register(container *restful.Container) { 116 | // 创建新的WebService 117 | ws := new(restful.WebService) 118 | 119 | // 设定WebService对应的路径("/users")和支持的MIME类型(restful.MIME_XML/ restful.MIME_JSON) 120 | ws. 121 | Path("/users"). 122 | Consumes(restful.MIME_XML, restful.MIME_JSON). 123 | Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well 124 | 125 | // 添加路由: GET /{user-id} --> u.findUser 126 | ws.Route(ws.GET("/{user-id}").To(u.findUser)) 127 | 128 | // 添加路由: POST / --> u.updateUser 129 | ws.Route(ws.POST("").To(u.updateUser)) 130 | 131 | // 添加路由: PUT /{user-id} --> u.createUser 132 | ws.Route(ws.PUT("/{user-id}").To(u.createUser)) 133 | 134 | // 添加路由: DELETE /{user-id} --> u.removeUser 135 | ws.Route(ws.DELETE("/{user-id}").To(u.removeUser)) 136 | 137 | // 将初始化好的WebService添加到Container中 138 | container.Add(ws) 139 | } 140 | 141 | // GET http://localhost:8080/users/1 142 | // 143 | func (u UserResource) findUser(request *restful.Request, response *restful.Response) { 144 | id := request.PathParameter("user-id") 145 | usr := u.users[id] 146 | if len(usr.Id) == 0 { 147 | response.AddHeader("Content-Type", "text/plain") 148 | response.WriteErrorString(http.StatusNotFound, "User could not be found.") 149 | } else { 150 | response.WriteEntity(usr) 151 | } 152 | } 153 | 154 | // POST http://localhost:8080/users 155 | // 1Melissa Raspberry 156 | // 157 | func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) { 158 | usr := new(User) 159 | err := request.ReadEntity(&usr) 160 | if err == nil { 161 | u.users[usr.Id] = *usr 162 | response.WriteEntity(usr) 163 | } else { 164 | response.AddHeader("Content-Type", "text/plain") 165 | response.WriteErrorString(http.StatusInternalServerError, err.Error()) 166 | } 167 | } 168 | 169 | // PUT http://localhost:8080/users/1 170 | // 1Melissa 171 | // 172 | func (u *UserResource) createUser(request *restful.Request, response *restful.Response) { 173 | usr := User{Id: request.PathParameter("user-id")} 174 | err := request.ReadEntity(&usr) 175 | if err == nil { 176 | u.users[usr.Id] = usr 177 | response.WriteHeader(http.StatusCreated) 178 | response.WriteEntity(usr) 179 | } else { 180 | response.AddHeader("Content-Type", "text/plain") 181 | response.WriteErrorString(http.StatusInternalServerError, err.Error()) 182 | } 183 | } 184 | 185 | // DELETE http://localhost:8080/users/1 186 | // 187 | func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) { 188 | id := request.PathParameter("user-id") 189 | delete(u.users, id) 190 | } 191 | 192 | func main() { 193 | // 创建一个空的Container 194 | wsContainer := restful.NewContainer() 195 | 196 | // 设定路由为CurlyRouter(快速路由),这个也是默认值 197 | wsContainer.Router(restful.CurlyRouter{}) 198 | 199 | // 创建自定义的Resource Handle(此处为UserResource) 200 | u := UserResource{map[string]User{}} 201 | 202 | // 创建WebService,并将WebService加入到Container中 203 | u.Register(wsContainer) 204 | 205 | log.Printf("start listening on localhost:8080") 206 | server := &http.Server{Addr: ":8080", Handler: wsContainer} 207 | 208 | // 启动服务 209 | log.Fatal(server.ListenAndServe()) 210 | } 211 | ``` 212 | ## 总结 213 | 上面的示例代码构建RESTful服务,分为几个步骤: 214 | - 创建Container 215 | - 配置Container属性:ServeMux/Router type等 216 | - 创建自定义的Resource Handle,实现Resource相关的处理方式。 217 | - 创建对应Resource的WebService,在WebService中添加响应Route,并将WebService加入到Container中。 218 | - 启动监听服务。 219 | 220 | 通俗来说,三者的关系如下: 221 | - Container: 一个Container包含多个WebService 222 | - WebService: 一个WebService包含多条route 223 | - Route: 一条route包含一个method(GET、POST、DELETE等),一条具体的path(URL)以及一个响应的handler function。 224 | 225 | -------------------------------------------------------------------------------- /Go-package使用/net.http用法.md: -------------------------------------------------------------------------------- 1 | # net/http 用法 2 | 3 | ## 用法 4 | server.go代码如下 5 | ```go 6 | package main 7 | 8 | import ( 9 | "encoding/json" 10 | "fmt" 11 | "net/http" 12 | "time" 13 | ) 14 | 15 | type Book struct { 16 | Name string `json: "name"` 17 | Price float64 `json: "price"` 18 | } 19 | 20 | func getHandler(w http.ResponseWriter, req *http.Request) { 21 | fmt.Println("In func getHandler") 22 | 23 | b1 := Book{ 24 | Name: "book1", 25 | Price: 13.1, 26 | } 27 | 28 | //解析成json格式,对数据b1进行编码,写入 w http.ResponseWriter 29 | encoder := json.NewEncoder(w) 30 | // 间隔3s给client端发送一个信息 31 | for i := 0; i < 10; i++ { 32 | encoder.Encode(b1) 33 | w.(http.Flusher).Flush() //把缓存中的信息发送出去 34 | time.Sleep(3 * time.Second) 35 | } 36 | } 37 | 38 | func easyHandler(w http.ResponseWriter, req *http.Request) { 39 | fmt.Println("In func easyHandler") 40 | st := "I am easyHandler" 41 | res, err := json.Marshal(st) 42 | if err != nil { 43 | fmt.Println("occurs error") 44 | return 45 | } 46 | w.Write(res) 47 | } 48 | 49 | func main() { 50 | serverMux := http.NewServeMux() 51 | serverMux.HandleFunc("/easy", easyHandler) 52 | 53 | serverMux.HandleFunc("/getbook", getHandler) 54 | http.ListenAndServe("127.0.0.1:8080", serverMux) 55 | } 56 | ``` 57 | 58 | client端代码如下,间隔3s收到一条消息 59 | ```go 60 | package main 61 | 62 | import ( 63 | "encoding/json" 64 | "fmt" 65 | "net/http" 66 | ) 67 | 68 | func main() { 69 | url := "http://127.0.0.1:8080/getbook" 70 | resp, err := http.Get(url) 71 | if err != nil { 72 | fmt.Println(err) 73 | return 74 | } 75 | var v map[string]interface{} 76 | 77 | //从resp.Body中读取数据,解码成&v 78 | decoder := json.NewDecoder(resp.Body) 79 | // 在server端全部发送完消息后,会进行break 80 | for { 81 | err := decoder.Decode(&v) 82 | if err != nil { 83 | break 84 | } 85 | fmt.Println(v) 86 | } 87 | resp.Body.Close() 88 | 89 | } 90 | ``` -------------------------------------------------------------------------------- /Go-package使用/sort package.md: -------------------------------------------------------------------------------- 1 | # sort 2 | 3 | 简单介绍[package sort](https://godoc.org/sort)的用法。 4 | 5 | 对于sliece,目前sort包官方仅仅支持int float64 string三个类型的slice的排序。这对于期望对自定义的数据结构进行排序是不够的。 6 | 7 | 所以我们需要针对自定义的数据结构实现func Len() int 、Swap(i, j int)、Less(i, j int) bool函数,然后就可以直接用sort.sort()来对自定义的数据进行排序了。 8 | 9 | ## 例子1 10 | ```go 11 | package main 12 | 13 | import ( 14 | "fmt" 15 | "sort" 16 | ) 17 | 18 | type Person struct { 19 | Name string 20 | Age int 21 | High float64 22 | } 23 | 24 | type PersonSlice []Person 25 | 26 | func (ps PersonSlice) Len() int { return len(ps) } 27 | func (ps PersonSlice) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] } 28 | func (ps PersonSlice) Less(i, j int) bool { return ps[i].Age < ps[j].Age } 29 | 30 | func main() { 31 | var persons = PersonSlice{ 32 | {"a", 1, 1.1}, 33 | {"c", 5, 3.1}, 34 | {"b", 2, 2.1}, 35 | } 36 | fmt.Println(persons) 37 | sort.Sort(persons) 38 | fmt.Println(persons) 39 | 40 | } 41 | ``` 42 | 输出 43 | ```go 44 | [{a 1 1.1} {c 5 3.1} {b 2 2.1}] 45 | [{a 1 1.1} {b 2 2.1} {c 5 3.1}] 46 | ``` 47 | 这是一个比较简单的例子,容易上手。但如果我希望能按照`Name`属性或者`High`属性来排序呢?这就得重新构造一个slice,再重写三个函数......是不是觉得心累? 48 | 49 | 可以发现Len() int 和Swap(i, j int)应该是一样的,实现不变。那么我们应该把Less(i, j int) bool提取成一个通用的模型。 50 | 51 | type lessFunc func(p, q Person) bool 52 | 53 | ## 例子2 54 | ```go 55 | package main 56 | 57 | import ( 58 | "fmt" 59 | "sort" 60 | ) 61 | 62 | type Person struct { 63 | Name string 64 | Age int 65 | High float64 66 | } 67 | 68 | func main() { 69 | var persons = []Person{ 70 | {"a", 1, 1.1}, 71 | {"c", 5, 3.1}, 72 | {"b", 2, 5.1}, 73 | } 74 | fmt.Println(persons) 75 | var pm = PersonMutil{ 76 | p: persons, 77 | //从这里更改条件即可 78 | less: func(i, j Person) bool { return i.High < j.High }, 79 | } 80 | 81 | sort.Sort(pm) 82 | //注意此时仍是打印var persons,而不是pm 83 | fmt.Println(persons) 84 | 85 | } 86 | 87 | type lessFunc func(p, q Person) bool 88 | type PersonMutil struct { 89 | p []Person 90 | less lessFunc 91 | } 92 | 93 | //注意函数的操作对象是pm.p,而不是pm 94 | func (pm PersonMutil) Len() int { return len(pm.p) } 95 | func (pm PersonMutil) Swap(i, j int) { pm.p[i], pm.p[j] = pm.p[j], pm.p[i] } 96 | func (pm PersonMutil) Less(i, j int) bool { 97 | return pm.less(pm.p[i], pm.p[j]) 98 | } 99 | ``` 100 | 或者main函数写成 101 | ```go 102 | func main() { 103 | var persons = []Person{ 104 | {"a", 1, 1.1}, 105 | {"c", 5, 3.1}, 106 | {"b", 2, 5.1}, 107 | } 108 | fmt.Println(persons) 109 | high_attr := func(i, j Person) bool { return i.High < j.High } 110 | sort.Sort(PersonMutil{persons, high_attr}) 111 | fmt.Println(persons) 112 | 113 | } 114 | ``` 115 | -------------------------------------------------------------------------------- /Go-package使用/信号.md: -------------------------------------------------------------------------------- 1 | # 信号 2 | 3 | 学习下os.signal package的基本用法, 给某个进程发送一个系统信号 4 | 5 | ```go 6 | /* 7 | 用于k8s源码调试时kill掉5个相关进程 8 | 参考: https://github.com/hyper0x/goc2p/blob/master/src/multiproc/signal/mysignal.go 9 | */ 10 | package main 11 | 12 | import ( 13 | "bytes" 14 | "errors" 15 | "fmt" 16 | "os" 17 | "os/exec" 18 | "strconv" 19 | "strings" 20 | "syscall" 21 | ) 22 | 23 | const ( 24 | Apiserver = "kube-apiserver" 25 | Kubelet = "kubelet" 26 | Controller = "kube-controller-manager" 27 | Scheduler = "kube-scheduler" 28 | Proxy = "kube-proxy" 29 | Docker = "docker" 30 | ) 31 | 32 | func findPids(processkey []string) ([]int, error) { 33 | allPids := make([]int, 0) 34 | for _, v := range processkey { 35 | fmt.Println("search the process ", v) 36 | pids, err := findPidsByOneKey(v) 37 | if err == nil { 38 | allPids = append(allPids, pids...) 39 | } else { 40 | fmt.Println(err.Error()) 41 | } 42 | } 43 | return allPids, nil 44 | } 45 | 46 | /* 47 | ps -ef|grep docker|grep -v "grep"|awk '{print $2}' 48 | */ 49 | func findPidsByOneKey(key string) ([]int, error) { 50 | cmds := []*exec.Cmd{ 51 | //不能写成"ps -ef",前面的ps可以在系统环境变量$PATH中找得到 52 | exec.Command("ps", "-ef"), 53 | exec.Command("grep", key), 54 | exec.Command("grep", "-v", "grep"), 55 | exec.Command("awk", "{print $2}"), 56 | } 57 | cmdResult, err := runCmds(cmds) 58 | if err != nil { 59 | return nil, err 60 | } 61 | pids, err := split(cmdResult) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return pids, nil 66 | } 67 | 68 | func runCmds(cmds []*exec.Cmd) ([]byte, error) { 69 | if cmds == nil || len(cmds) == 0 { 70 | return nil, errors.New("The cmd slice is invalid") 71 | } 72 | 73 | var output []byte 74 | first := true 75 | for _, cmd := range cmds { 76 | if !first { 77 | var cmdIn bytes.Buffer 78 | //把数据写入buffer 79 | _, err := cmdIn.Write(output) 80 | if err != nil { 81 | return nil, err 82 | } 83 | cmd.Stdin = &cmdIn 84 | } 85 | var cmdOut bytes.Buffer 86 | cmd.Stdout = &cmdOut 87 | err := cmd.Start() 88 | if err != nil { 89 | return nil, err 90 | } 91 | err = cmd.Wait() 92 | if err != nil { 93 | return nil, err 94 | } 95 | //读取buffer cmdOut中的数据 96 | output = cmdOut.Bytes() 97 | if first { 98 | first = false 99 | } 100 | } 101 | return output, nil 102 | } 103 | 104 | func split(in []byte) ([]int, error) { 105 | in = bytes.TrimSpace(in) 106 | st := string(in) 107 | p := strings.Split(st, "\n") 108 | pids := make([]int, 0) 109 | for _, value := range p { 110 | id, err := strconv.Atoi(value) 111 | if err != nil { 112 | return nil, err 113 | } 114 | pids = append(pids, id) 115 | } 116 | return pids, nil 117 | } 118 | 119 | func sendSIGQuit(pids []int) error { 120 | for _, pid := range pids { 121 | fmt.Println("strat to quict process: ", pid) 122 | // 根据pid获取到对效应的process 123 | proc, err := os.FindProcess(pid) 124 | if err != nil { 125 | return err 126 | } 127 | // 给该process发送信号 128 | sig := syscall.SIGQUIT 129 | err = proc.Signal(sig) 130 | if err != nil { 131 | return err 132 | } 133 | } 134 | return nil 135 | } 136 | 137 | func main() { 138 | processkey := []string{ 139 | // Docker, 140 | Apiserver, 141 | Controller, 142 | Kubelet, 143 | Proxy, 144 | Scheduler, 145 | } 146 | 147 | pids, err := findPids(processkey) 148 | if err != nil { 149 | fmt.Println(err.Error()) 150 | } 151 | if len(pids) > 0 { 152 | err := sendSIGQuit(pids) 153 | if err != nil { 154 | fmt.Println(err.Error()) 155 | } 156 | } 157 | } 158 | ``` -------------------------------------------------------------------------------- /Go-package使用/基于cobra构建命令行.md: -------------------------------------------------------------------------------- 1 | # 基于cobra构建命令行 2 | 3 | **Table of Contents** 4 | 5 | - [环境准备](#环境准备) 6 | - [给root命令添加参数](#给root命令添加参数) 7 | - [添加子命令](#添加子命令) 8 | - [多级命令](#多级命令) 9 | - [参考](#参考) 10 | 11 | 12 | 13 | 从三个例子,来说明如何使用`github.com/spf13/cobra`,构造自己的命令行 14 | 15 | ## 环境准备 16 | ```shell 17 | go get github.com/spf13/cobra/cobra 18 | ``` 19 | 会发现$GOPATH/bin下生成了一个可执行文件cobra,利用可执行文件cobra来生成cobra_example工程。 20 | 21 | ```shell 22 | ./bin/cobra init cobra_example 23 | ``` 24 | 会发现/src目录下自动生成cobra_example工程,目录如下所示: 25 | ``` 26 | ▾ cobra_example/ 27 | ▾ cmd/ 28 | root.go 29 | main.go 30 | ``` 31 | 32 | ## 给root命令添加参数 33 | 此时的代码如下: 34 | - main.go 35 | ```go 36 | package main 37 | 38 | import "cobra_example/cmd" 39 | 40 | func main() { 41 | cmd.Execute() 42 | } 43 | ``` 44 | 45 | - cmd/root.go 46 | ```go 47 | package cmd 48 | 49 | import ( 50 | "fmt" 51 | "os" 52 | 53 | "github.com/spf13/cobra" 54 | // "github.com/spf13/viper" 55 | "cobra_example/add" 56 | ) 57 | 58 | //var cfgFile string 59 | var name string 60 | var age int 61 | 62 | // RootCmd represents the base command when called without any subcommands 63 | var RootCmd = &cobra.Command{ 64 | Use: "demo", 65 | Short: "A test demo", 66 | Long: `Demo is a test appcation for print things`, 67 | // Uncomment the following line if your bare application 68 | // has an action associated with it: 69 | Run: func(cmd *cobra.Command, args []string) { 70 | if len(name) == 0 { 71 | cmd.Help() 72 | return 73 | } 74 | add.Show(name, age) 75 | }, 76 | } 77 | 78 | // Execute adds all child commands to the root command sets flags appropriately. 79 | // This is called by main.main(). It only needs to happen once to the rootCmd. 80 | func Execute() { 81 | if err := RootCmd.Execute(); err != nil { 82 | fmt.Println(err) 83 | os.Exit(-1) 84 | } 85 | } 86 | 87 | func init() { 88 | RootCmd.Flags().StringVarP(&name, "name", "n", "", "person's name") 89 | RootCmd.Flags().IntVarP(&age, "age", "a", 0, "person's age") 90 | } 91 | ``` 92 | 93 | - 新建个add package 94 | ``` 95 | ▾ cobra_example/ 96 | ▾ add/ 97 | add.go 98 | ▾ cmd/ 99 | root.go 100 | main.go 101 | ``` 102 | ```go 103 | package add 104 | 105 | import( 106 | "fmt" 107 | ) 108 | 109 | func Show(name string, age int) { 110 | fmt.Printf("My Name is %s, My age is %d\n", name, age) 111 | } 112 | ``` 113 | 114 | 然后go build 生成可执行文件,最后执行效果如下 115 | ```shell 116 | # ./cobra_example -a 11 -n lining 117 | My Name is lining, My age is 11 118 | ``` 119 | 120 | ## 添加子命令 121 | ```shell 122 | # ./cobra_example --help 123 | Demo is a test appcation for print things 124 | 125 | Usage: 126 | demo [flags] 127 | demo [command] 128 | 129 | Available Commands: 130 | test A brief description of your command 131 | 132 | Flags: 133 | -a, --age int person's age 134 | -n, --name string person's name 135 | 136 | Use "demo [command] --help" for more information about a command. 137 | 138 | # ./cobra_example test 139 | test called 140 | My Name is , My age is 0 141 | ``` 142 | 我们在前面例子的基础上进行改进,目录结构如下 143 | ``` 144 | ▾ cobra_example/ 145 | ▾ add/ 146 | add.go 147 | ▾ cmd/ 148 | root.go 149 | test.go 150 | main.go 151 | ``` 152 | 153 | - test.go文件内容如下 154 | ```go 155 | package cmd 156 | 157 | import ( 158 | "fmt" 159 | 160 | "cobra_example/add" 161 | "github.com/spf13/cobra" 162 | ) 163 | 164 | // testCmd represents the test command 165 | var testCmd = &cobra.Command{ 166 | Use: "test", 167 | Short: "A brief description of your command", 168 | Long: `A longer description that spans multiple lines and likely contains examples 169 | and usage of using your command. For example: 170 | 171 | Cobra is a CLI library for Go that empowers applications. 172 | This application is a tool to generate the needed files 173 | to quickly create a Cobra application.`, 174 | Run: func(cmd *cobra.Command, args []string) { 175 | fmt.Println("test called") 176 | add.Show(name, age) 177 | }, 178 | } 179 | 180 | func init() { 181 | RootCmd.AddCommand(testCmd) 182 | 183 | testCmd.Flags().StringVarP(&name, "name", "n", "", "person's name") 184 | testCmd.Flags().IntVarP(&age, "age", "a", 0, "person's age") 185 | } 186 | ``` 187 | 188 | ## 多级命令 189 | ```go 190 | package main 191 | import ( 192 | "fmt" 193 | "strings" 194 | "github.com/spf13/cobra" 195 | ) 196 | 197 | func main() { 198 | var echoTimes int 199 | var cmdPrint = &cobra.Command{ 200 | Use: "print [string to print]", 201 | Short: "Print anything to the screen", 202 | Long: `print is for printing anything back to the screen. 203 | For many years people have printed back to the screen. 204 | `, 205 | Run: func(cmd *cobra.Command, args []string) { 206 | fmt.Println("Print: " + strings.Join(args, " ")) 207 | }, 208 | } 209 | var cmdEcho = &cobra.Command{ 210 | Use: "echo [string to echo]", 211 | Short: "Echo anything to the screen", 212 | Long: `echo is for echoing anything back. 213 | Echo works a lot like print, except it has a child command. 214 | `, 215 | Run: func(cmd *cobra.Command, args []string) { 216 | fmt.Println("Print: " + strings.Join(args, " ")) 217 | }, 218 | } 219 | var cmdTimes = &cobra.Command{ 220 | Use: "times [# times] [string to echo]", 221 | Short: "Echo anything to the screen more times", 222 | Long: `echo things multiple times back to the user by providing 223 | a count and a string.`, 224 | Run: func(cmd *cobra.Command, args []string) { 225 | for i := 0; i < echoTimes; i++ { 226 | fmt.Println("Echo: " + strings.Join(args, " ")) 227 | } 228 | }, 229 | } 230 | cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input") 231 | var rootCmd = &cobra.Command{Use: "app"} 232 | rootCmd.AddCommand(cmdPrint, cmdEcho) 233 | cmdEcho.AddCommand(cmdTimes) 234 | rootCmd.Execute() 235 | } 236 | ``` 237 | 238 | 239 | 240 | ## 参考 241 | [urfave/cli](https://github.com/urfave/cli),这是另外一个构造命令行工具cli 242 | 243 | [cobra](https://github.com/spf13/cobra) 244 | 245 | 246 | -------------------------------------------------------------------------------- /Go-package使用/并发Demo.md: -------------------------------------------------------------------------------- 1 | # 并发Demo 2 | 3 | ## 最简单的生产消费者模型 4 | 最后借用stopch chan bool来实现优雅 exit 5 | ```go 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | func produce(channel chan int, stopch chan bool, args ...int) { 14 | for _, arg := range args { 15 | channel <- arg 16 | fmt.Println(arg) 17 | time.Sleep(2 * time.Second) 18 | } 19 | defer close(channel) 20 | stopch <- true 21 | } 22 | 23 | func custom(channel chan int) { 24 | // for { 25 | // select { 26 | // case x, ok := <-channel: 27 | // fmt.Println(x, ok) 28 | // if !ok { 29 | // return 30 | // } 31 | // } 32 | // } 33 | for arg := range channel { 34 | fmt.Println("xx", arg) 35 | } 36 | } 37 | 38 | func main() { 39 | channel := make(chan int) 40 | stopch := make(chan bool) 41 | go produce(channel, stopch, []int{1, 3, 7}...) 42 | go custom(channel) 43 | select { 44 | case <-stopch: 45 | break 46 | } 47 | } 48 | ``` 49 | 50 | ## 一个泄露的缓冲区 51 | 52 | server端和client端共用一个容量为100的buffer `freeList`。 53 | 54 | client端从网络io读取信息存放到buffer `freeList`中。 55 | 在buffer `freeList`有空闲的时候,就使用已经分配好的空间,否则就`new`一个。 56 | 57 | 负责消费client存放到buffer `freeList`中的信息,处理完毕后,将`Buffer b`放回空闲列表`freeList`中直到列表已满, 此时缓冲区将被丢弃, 并被垃圾回收器回收。 58 | select 语句中的 default 子句在没有条件符合时执行,这也就意味着 select 永远不会被阻塞。 59 | 这意味着缓冲区发生了槽位泄露。 60 | 61 | ```go 62 | var freeList = make(chan *Buffer, 100) 63 | var serverChan = make(chan *Buffer) 64 | 65 | func client() { 66 | for { 67 | var b *Buffer 68 | select { // 若缓冲区可用就用它,不可用就分配个新的。 69 | case b = <-freeList: 70 | fmt.Println("got a free buffer") 71 | default: 72 | // 非空闲,因此分配一个新的。 73 | b = new(Buffer) 74 | } 75 | load(b) // 从网络中读取下一条消息。 76 | serverChan <- b // 发送至服务器。 77 | } 78 | } 79 | 80 | func server() { 81 | for { 82 | b := <-serverChan // 等待工作。 83 | process(b) 84 | select { // 若缓冲区有空间就重用它。 85 | case freeList <- b: 86 | // 将缓冲区放到空闲列表中,不做别的。 87 | /* 88 | 若freeList没有多余的空间了,那么此次执行过程中的b无法存放到freeList中。 89 | 那么将转而执行 default 语句。 90 | 但此次的 b 已经丢失。。。。。。 91 | */ 92 | default: 93 | // 空闲列表已满,保持就好。 94 | } 95 | } 96 | } 97 | ``` -------------------------------------------------------------------------------- /Linux/(03)Unix-Linux编程实践之IO重定向和管道.md: -------------------------------------------------------------------------------- 1 | # Unix/Linux编程实践之IO重定向和管道 2 | 3 | 个人读书笔记 4 | 5 | ## I/O重定向的原理模型 6 | `ls > test.file`是如何工作的? shell是如何告诉程序把结果输出到文件,而不是屏幕? 7 | 在`who | sort > user.file`中,shell是如何把一个进程的输出连接到另一个进程的输入的? 8 | 9 | ![IO重定向](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/IO重定向.png) 10 | 11 | 其实原理就是`>`操作符是把文件看成任意大小的和结构的变量。 12 | 下面两个实现是等价的,其中前者用`c`实现,后者用`shell`实现: 13 | ```c 14 | x = func_a(func_b(y)); //把func_b的结果作为func_a的输入 15 | ``` 16 | 等价于 17 | ```shell 18 | prog_b | prog_a > x 19 | ``` 20 | 21 | ### 标准I/O 22 | 标准I/O的定义是什么? 23 | 所有的Unix I/O重定向都是基于标准数据流的原理,所有的Unix工具默认都会携带三个数据流: 24 | 1. 标准输入,输出处理的数据流 25 | 2. 标准输出,结果数据流 26 | 3. 标准错误处理,错误消息流 27 | 28 | ![标准数据流](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/标准数据流.png) 29 | 30 | ![标准数据流模型](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/标准数据流模型.png) 31 | 32 | 从上面两个图可以看出来,所有的Unix工具都是使用这种数据流模型。 33 | 这三种流的每一种都是一种特别文件描述符fd。 34 | 35 | ### 重定向的是shell 36 | 一般情况下,通过shell来运行一个程序时,该进程的stdin、stdout和stderr都是默认被连接到当前终端上。 37 | 所以用户的输入,程序的输出或者错误消息都是显示到屏幕终端。 38 | 39 | **重定向的是shell,而不是程序本身。** 40 | 程序仅仅是持续不断地和`文件描述符0、1、2`打交道,把fd和指定文件联系起来的是`shell`。 41 | 也就是说shell本身并不会把`重定向这个动作和指定文件`传递给程序。 42 | 43 | ### 最低可用文件描述符 44 | 文件描述符是一个数组的索引号。 45 | 每个进程都有其打开的一组文件,这些打开的文件被保存在一个数组里面,fd就是某文件在此数组中的索引号。 46 | 47 | 当打开(open)一个文件的时候,内核为此文件安排的fd总是此数组中最低的可用位置的索引。 48 | 49 | ![最低可用fd原则](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/最低可用fd原则.png) 50 | 51 | 最后,库函数`isatty(fd)`,可以用来给一个进程判断一个fd的指向,和系统调用`fstat`有关。 52 | 53 | ## 重定向的实现 54 | 根据上面说明的模型,可以设想一下如何写一个程序来实现重定向的功能。 55 | 有很多种实现方法,下面以stdin定向到一个文件为例进行说明。 56 | 57 | ### close-then-open策略 58 | close-then-open策略,具体步骤如下: 59 | 60 | 1. close(0),把标准输入的连接挂断 61 | 2. `fd = open(file_name,O_RDONLY)`, 和指定的文件建立一个连接,此时最低可用的fd应该是0 62 | 63 | ![重定向策略open-then-close](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/重定向策略open-then-close.png) 64 | 65 | ### open-close-dup-close策略 66 | open-close-dup-close策略,具体步骤如下: 67 | 68 | 1. `fd = open(file_name,O_RDONLY)`, 和指定的文件建立一个连接 69 | 2. close(0) 70 | 3. `newfd = dup(fd)` ,copy open fd to 0,得到的newfd是0 71 | 4. close(fd) 72 | 73 | ![dup重定向](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/dup重定向.png) 74 | 75 | ### open-dup2-close策略 76 | 把`newfd = dup(fd)`和`close(0)`组成成一个单独的系统调用dup2,`newfd=dup2(oldfd,newfd)`,一般使用的newfd设置为0。 77 | 78 | 79 | ## shell为子进程重定向其输出 80 | 以`who>userlist.file`为例子,shell运行`who`程序,并将`who`的输出重定向到userlist.file。 其中的原理是什么? 81 | 82 | 关键之处在于fork和exec的时间间隙。 83 | fork之后,子进程准备执行exec。 84 | shell就利用这个时间间隙来完成子进程的输出重定向工作。 85 | 86 | 系统调用`exec`替换进程中运行的程序,但它不会改变进程的属性、和进程中所有的连接。 87 | 文件描述符fd(复制了一个连接)、进程的用户ID、进程的优先级都不会被系统调用`exec`改变。 88 | 这是因为打开的文件的并不是程序的代码,也不是数据。 fd是进程的一个属性,故exec不能改变它们。 89 | 90 | 也就是文件描述符fd是可以被`exec`传递的,也会从父进程传递到子进程。 91 | 92 | ![shell为子进程重定向其输出](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/shell为子进程重定向其输出.png) 93 | 94 | 具体流程如下: 95 | 1. 父进程fork() 96 | 2. 子进程继承了父进程的stdout fd=1 97 | 3. 子进程close(1) 98 | 4. 子进程fd=create(file,0644),此时最低可用fd=1 99 | 5. 子进程exec() 100 | 101 | 102 | *** 103 | 104 | ## 管道pipe 105 | 管道是内核中一个数据队列,其每一端都连接着一个文件描述符fd,管道有一个读取端和写入端。 106 | 107 | pipe又被称为无名管道,只有共同父进程的进程之间才可以用pipe连接。原因是它没有“名字”或者说是匿名的,另外的进程看不到它。 108 | 109 | 函数原型`resutl=pipe(int array[2])`,其中array[0]为读取端的fd,array[1]为写入端的fd。 110 | 管道的实现隐藏在内核中,进程只能看到两个文件描述符fd。 111 | 112 | ![管道](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/管道.png) 113 | 114 | pipe()和前面open()这些系统调用是类似的,都适用于最低可用fd原则。 115 | 那么一个进程创建一个pipe之后的效果图是怎样的? 116 | 117 | ![一个进程创建pipe](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/一个进程创建pipe.png) 118 | 119 | 可以发现一个进程刚创建一个管道成功的之后,一个pipe的两端都是连着自己的。 120 | 一般而言,很少有进程利用管道向自己发送数据。 121 | 都是把`pipe()`和`fork()`结合起来,连接两个不同的进程。 122 | 123 | ### 使用fork共享管道 124 | 一个进程刚创建一个管道成功的之后,调用fork()。那么子进程就拥有了两个fd,分别执行该管道的两端。 125 | 理论上,父子进程可以同时对该管道进行读写操作。 126 | 一般是一个读,一个写。 127 | 128 | ![fork共享管道](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/fork共享管道.png) 129 | 130 | docker就是把fork、pipe和exec结合起来使用。 131 | 132 | ### 管道并非文件 133 | 一方面,管道和文件一样,是不带任何结构的字节序列,都可以使用read、write等操作。 134 | 另一方面,两者还是存在不同之处的。 135 | 136 | 1. 管道的读取,必须要有进程往管道中写入数据,否则会产生阻塞 137 | 2. 多个读者可能会引起麻烦,因为管道是一个队列,读取之后,数据就不存在了 138 | 3. 写进程会产生阻塞,直到管道有足够的容量允许你写入 139 | 4. 若读者在读取数据,写操作会失败 140 | 141 | 如果两个进程没有关联,使用`FIFO`联系。 142 | 143 | 如果两个进程处于不同的主机上面呢?这个时候把管道的思路扩展到`套接字socket`上。 144 | 145 | ### pipe和fifo的本质区别 146 | 无名管道不属于任何文件系统,只存在于内存中,它是无名无形的,但是可以把它看作一种特殊的文件,通过使用普通文件的read(),write()函数对管道进行操作。 147 | 148 | 为了使用fifo,LINUX中设立了一个专门的特殊文件系统--管道文件,它存在于文件系统中,任何进程可以在任何时候通过有名管道的路径和文件名来访问管道。但是在磁盘上的只是一个节点,而文件的数据则只存在于内存缓冲页面中,与普通管道一样。 149 | 150 | fifo是基于VFS,对应的文件类型就是FIFO文件,可以通过`mknod`命令在磁盘上创建一个FIFO文件。 151 | 注意:这就是fifo与pipe的本质区别,pipe完全就是存在于内存中,在磁盘上毫无痕迹。 152 | 153 | 当进程想通过该FIFO来通信时就可以标准的API open打开该文件,然后开始读写操作。对于FIFO的读写实现,它与pipe是相同的。 154 | 区别在于,FIFO有open这一操作,而pipe是在调用pipe这个系统调用时直接创建了一对文件描述符用于通信。 155 | 并且,FIFO的open操作还有些细致的地方要考虑,例如如果写者先打开,尚无读者,那么肯定是不能通信了,所以就需要先去睡眠等待读者打开该FIFO,反之对读者亦然。 156 | 157 | ## 参考 158 | [Linux中的pipe与named pipe](http://www.linuxidc.com/Linux/2011-05/36155.htm) 159 | 160 | -------------------------------------------------------------------------------- /Linux/(04)Unix-Linux编程实践之socket.md: -------------------------------------------------------------------------------- 1 | # Unix-Linux编程实践之socket 2 | 3 | 个人读书笔记 4 | 5 | ## popen和fopen 6 | `fopen("file_name","r")`,以只读方式打开一个指向文件file_name的带缓存的连接。 7 | 8 | `popen()`是类似的,只不过打开的是一个指向进程的带缓冲的连接。 9 | popen通过封装pipe、fork、dup和exec等系统调用,使得对程序的操作变得和文件一样。 10 | 其实现是运行了一个程序(fork),并返回指向新进程的stdin和stdout的连接。 11 | ```c 12 | fp = popen("who|sort", "r") 13 | fgets(buf,len,fp) 14 | printf(buf) 15 | pclose(fp) //pclose会调用wait()来等待子进程退出,防止僵尸进程的产生 16 | ``` 17 | ![fopen和popen](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/fopen和popen.png) 18 | 19 | 唯一能够运行任意shell命令的程序是shell本身,即`/bin/sh`。 20 | sh支持`-c`选项,告诉shell执行完某命令之后退出。 21 | 22 | 管道使得一个进程向另外一个进程发送数据,就像向文件发送数据一样。 23 | 但管道只能用于有血缘关系的进程,也只能用于同一个host主机上的进程通信。 24 | 故Unix提供了另外一种通信机制---`socket` 25 | 26 | popen对于编写网络服务是非常危险的,因为它直接把一行字符串传递给shell。 27 | 28 | 在网络程序中,把字符串传递给shell是非常错误的想法。 29 | 因为不确定对方有足够的缓存来接受字符串。 30 | 31 | ## socket 32 | socket允许在不相关的进程之间创建类似于管道的连接,甚至可以通过socket连接其他host主机上的进程。 33 | 34 | 基于流的系统都需要建立连接。 35 | 36 | 服务器端: 37 | 1. `sockid=socket(int domain, int type, int protocol)`,系统调用socket向内核申请一个socket,得到一个通信端点。 38 | 2. `result=bind(int sockid, struct sockaddr *addrp, socklen_t addrlen)`,绑定地址到socket,包括主机、port。 39 | 3. `result=listen(int sockid, int qsize)`,服务端请求内核允许指定的sockid接受请求,qsize是接收队列的长度。 40 | 4. `fd=accept(int sockid, struct sockaddr *callerid, socklen_t *addrlen)`,服务端使用sockid接收请求。 41 | 5. accept会一直阻塞当前进程,一直到有连接被建立起来 42 | 7. close关闭连接 43 | 44 | client端: 45 | 1. `sockid=socket(int domain, int type, int protocol)`,系统调用socket向内核申请一个socket,得到一个通信端点。 46 | 2. `result=connect(int sockid, struct sockaddr *serv_addrp, socklen_t addrlen)`,建立连接 47 | 3. 传送数据和close 48 | ![socket流程图](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/socket流程图.png) 49 | 50 | 比较管道和socket: 51 | 1. 管道是一对相连接的文件描述符 52 | 2. socket是一个未连接的通信端点,也是一个潜在的文件描述符fd。客户进程通过把自己的socket和服务端的socket相连来创建一个通信连接。 53 | 3. 到管道和socket的连接使用文件描述符。文件描述符为程序提供了与文件、设备和其他的进程通信的统一编程接口。 54 | 55 | ## waitpid() 56 | 一个进程fork多个子进程的时候,怎么wait()所有子进程的退出信号? 57 | 答案是`while(waitpid(-1,NULL,WNOHANG)>0)`,waitpid()提供了wait函数超集的功能。其中: 58 | 59 | 1. 第一个参数表示它要等待的进程ID,-1表示等待所有的子进程 60 | 2. 第二个参数用来获取状态,服务端不关心子进程状态时,填NULL 61 | 3. 最后一个参数表示选项,`WNOHANG`表示如果没有僵尸进程,则不必等待 62 | 63 | ## 数据报socket 64 | 前面所介绍的是`流socket`,是基于TCP协议实现的,双方通信之前需要建立连接,然后使用该连接进行字节流传送。 65 | 66 | ![网络传输切割数据包](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/网络传输切割数据包.png) 67 | 68 | 而本小节将要介绍的是`数据报socket`,是基于UDP协议实现的,Client不必建立连接,只要向特定的地址发送嘻嘻即可,而服务器进程在该地址接收信息。 69 | 70 | TCP: 传输控制协议,Transmission Control Protocol 71 | UDP: 用户数据报协议,User Datagram Protocol 72 | 73 | TCP是流式的,具有分片/重组,排序的,可靠、连接的特性。 74 | 而UDP则是数据报式,内核不会给数据加编号标签,在目的地也不会重组,不保证一定能到达,可能会有多个发送者。 75 | 76 | UDP多适用于能容忍丢包的声音和视频流。 77 | 78 | 可以通过系统调用kill给一个进程发送编号为0的信号,用来判断该进程是否还存活。 如果进程不存在,内核将不会发送信号,而是返回一个error。 79 | 80 | ## Unix域socket 81 | 有两种连接,`流连接`和`数据报连接`。 82 | 83 | 也有两种socket地址: 84 | 1. `Internet地址`,主机ID+端口,可以接收本地、甚至更大网络上的Client的请求 85 | 2. `本地地址`,又称Unix域地址,是一个文件名(/dev/log,/dev/printer),没有主机号和端口号,仅用于一个host主机内部通信 86 | 87 | socket是进程间通信的万能工具 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /Linux/(05)Unix-Linux编程实践之线程机制.md: -------------------------------------------------------------------------------- 1 | # Unix/Linux编程实践之线程机制 2 | 3 | 个人读书笔记 4 | 5 | ## 线程 6 | fork和exec实现的是创建多个进程,那么如何要同时运行多个函数呢? 7 | 8 | 线程之于函数,就相当于进程之于程序。 很多函数可以同时运行,但它们都在相同的进程中。 9 | 10 | 其实在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone()。 11 | 该系统调用copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。 12 | 不过这个copy过程和fork不一样。 copy后的进程和原先的进程`共享了所有的变量,运行环境`。 13 | 这样,原先进程中的变量变动在copy后的进程中便能体现出来。 14 | 15 | ### 进程和线程关于共享资源的对比 16 | 理解清楚这些概念非常重要。 17 | 18 | Unix从其产生伊始就将线程作为它的重要组成部分,线程是后来才加进来的。 19 | 20 | 进程的概念是非常清晰和统一的,而线程的概念和属性则取决于你的OS和线程版本。 本文所说的是`POSIX线程`。 21 | 22 | 进程和线程有根本上的不同。 23 | * 进程有独立的数据空间、文件描述符、进程ID。 24 | * 而线程共享一个数据空间、文件描述符以及进程ID。 25 | 26 | 1. 共享数据空间 27 | 试想两个线程同时使用一块内存空间,通过系统调用malloc和free,如果不加限制的,将会造成可怕的后果。 28 | 29 | 2. 共享的文件描述符 30 | 在fork原语被调用之后,fd被自动地复制,从而子进程得到了一套新的fd。 在子进程关闭了某一个从父进程那里继承而来的fd之后,此fd对于父进程来说仍然是打开的。 31 | 32 | 然而,在多线程程序中,可能会把同一个fd传递给两个不同的线程。 即传递给两个线程的值指向同一个fd。 在这种情况下,如果一个线程关闭了这个文件,那么对于此进程中的所有线程来说,该fd都是已经关闭了的。 这会导致问题。。。。 33 | 34 | 3. fork、exec、exit、Signals 35 | 所有的线程都共享一个进程。 36 | 如果一个线程调用了`exec`,内核会用一个新的程序取代本进程当前的程序,从而所有正在运行的线程都会消失。 37 | 38 | 同理,一个线程执行了`exit`,那么整个进程都将退出。 所以,如果是线程的运行导致内存异常、系统错误或线程崩溃,都会造成整个进程瘫痪,而不仅仅是线程本身。 39 | 40 | fork创建了一个新的进程,并把`原调用进程`的数据和代码复制给这个新的进程。 41 | 如果一个线程中的某函数调用了`fork`,那么是不是把所有线程都复制给新的进程呢? 42 | 答案是否定的,只有调用了fork的线程在新的进程中运行。 43 | 这里面设计的更加复杂了,如果在fork的时候有数据修改什么的。。。。 44 | 45 | 信号量Signal要比线程更加复杂。进程可以接收任何种类的信号量。那么线程呢?。。 46 | 47 | ## pthread相关系统调用 48 | 49 | ### pthread_create 50 | 创建线程的系统调用`pthread_create`,若成功则返回0,否则返回出错编号。 51 | 1. 第一个参数为指向线程标识符的指针。 52 | 2. 第二个参数用来设置线程属性,如果设置为NULL,则主进程需调用`pthread_join`等待本进程。可以设置为`detached`,线程自己来清理退出的状态、回收资源,不需要主进程等待。 53 | 3. 第三个参数是线程运行函数的起始地址。 54 | 4. 最后一个参数是运行函数的参数。 55 | ```c 56 | int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, (void*)(*start_rtn)(void*), void *arg); 57 | ``` 58 | 59 | ### pthread_join 60 | 函数`pthread_join`用来等待一个线程的结束,是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回0,如果失败则返回一个错误号。 61 | ```c 62 | int pthread_join(pthread_t thread, void **retval) 63 | ``` 64 | 65 | 如果一个线程不设置为detached,主进程也不调用pthread_join,那么会产生`僵尸线程`,线程占用的资源无法得到回收。 66 | 可以通过设置为独立线程(detached thread)解决,detached thread会自动释放它所占用的所有资源,它们自身甚至不允许等待其他线程返回。 67 | 68 | ### 一个例子 69 | ```c 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | 76 | char st[20]="hello";//共享内存 77 | 78 | void print_fn(const char *s){ 79 | pid_t pid; 80 | pthread_t tid; 81 | pid = getpid();//进程id 82 | tid = pthread_self();//线程id 83 | printf("%s pid %u ,and tid %u\n", s, (unsigned int)pid, (unsigned int)tid); 84 | } 85 | 86 | void *new_pthread(void *arg){ 87 | print_fn("I am a new pthread: "); 88 | return NULL; 89 | } 90 | 91 | int main(void){ 92 | pthread_t tid; 93 | int err; 94 | err = pthread_create(&tid, NULL, new_pthread, NULL); 95 | if (err!=0) 96 | printf("occurs error while create new pthread: %s\n", strerror(err)); 97 | print_fn("Main thread: "); 98 | pthread_join(tid, NULL); 99 | return 0; 100 | } 101 | ``` 102 | 执行效果如下,其中编译的时候需要加上编译链接`-lpthread` 103 | ```shell 104 | [root@fqhnode01 c++]# cc ./pthread.c -o pthread -lpthread 105 | [root@fqhnode01 c++]# ./pthread 106 | Main thread: pid 1190 ,and tid 4010510144 107 | I am a new pthread: pid 1190 ,and tid 4002174720 108 | ``` 109 | 可以发现主函数的流程总是要快于新线程先打印,应该是创建线程需要耗费时间。这和Go的Goroutine等待调度应该是类似的。 110 | 111 | ## 线程的通信 112 | 进程间可以通过管道、socket、信号、退出/等待、env来进行通信。 113 | 114 | 线程间的通信要简单一点,多个线程在一个单独的进程中运行,它们共享全局变量。 因此,线程间可以通过设置和读取这些全局变量来进行通信。 其实这就是通过共享内存。 115 | 116 | 线程间共享内存,必然需要关注资源竞争问题,如互斥锁等。 117 | ```c 118 | int pthread_mutex_lock(pthread_mutex_t *mutex); 119 | int pthread_mutex_unlock(pthread_mutex_t *mutex); 120 | ``` 121 | 122 | 进程的数据空间包含了所有属于它的变量。 因此,此进程中运行的所有线程都拥有对这些变量访问的权限。 123 | 如果这些变量值不变的话,线程可以无误地读取并使用。 124 | 但如果有一个线程更改了一个变量值,就需要上锁了。 125 | 这是临界资源,在某一时刻,只能有唯一的线程对其进行访问。 126 | 127 | ### 和进程的差异 128 | 对于进程来说,父进程可以通过wait获取子进程的返回结果。 129 | 130 | 那么线程呢?线程不具有这种机制。 131 | 因为对于线程来说,没有父线程、子线程的概念。 132 | 所以不存在某一个明显的线程去通知。 133 | 134 | 一般来说,线程是通过`pthread_cond_wait()`把线程挂起,直到另外一个线程通过条件变量发出消息,常常和`互斥锁`一起使用。 135 | 136 | ## 线程的优点 137 | 现代线程库允许不同的线程运行在不同的处理器芯片上,从而实现真正意义上的并行。 138 | 139 | ## 总结 140 | 进程和线程的区别? 141 | - 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。 142 | - 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源(寄存器、堆栈、上下文)。 143 | - 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。 144 | - 私有属性:线程有自己的私有属性TCB,线程id,寄存器、硬件上下文,而进程也有自己的私有属性进程控制块PCB,这些私有属性是不被共享的, 用来标示一个进程或一个线程的标志。 145 | 146 | 线程是轻量级的进程,它的创建和销毁所需要的时间比进程小很多,所有操作系统中的执行功能都是创建线程去完成的。 147 | 148 | 没有说进程或者线程哪个更具具有绝对优势,各有优缺点! 149 | 150 | 在双核环境下,两个核可以分别单独运行两个进程(进程的并行,也是线程的并行,因为每个进程至少有一个线程);或者是运行一个进程里的两个线程(线程的并行) 151 | 152 | 在单核cpu上运行多线程程序时,每一时刻,cpu只能运行一个线程。但由于操作系统会根据调度策略切换cpu运行的线程,所以看起来像是多线程在同时运行。 153 | 154 | 并行的必要条件是多核cpu! 155 | 同时真正处于running的线程数,不能超过cpu核数目! 156 | 157 | 多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。缺点是创建进程的代价大。 158 | 159 | 多线程模式致命的缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。 160 | 161 | 在多核的环境下,多线程性能上可能还不如多进程! 162 | 163 | ## 参考 164 | [pthread系统调用](https://baike.baidu.com/item/pthread_join) 165 | 166 | [关于进程、线程的一个最好的例子](https://www.zhihu.com/question/19901763/answer/13299543) 167 | 168 | [processes_and_threads](http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html) -------------------------------------------------------------------------------- /Linux/(06)Unix-Linux编程实践之IPC.md: -------------------------------------------------------------------------------- 1 | # Unix/Linux编程实践之IPC 2 | 个人读书笔记 3 | 4 | 两个进程交换数据,选择合适的通信方式也是提高程序效率的一种方法。 5 | 6 | ## 概念 7 | 1. I/O多路复用,挂起并等待从多个源端输入:select、poll、epoll 8 | 2. 命名管道,mkfifo 9 | 3. 共享内存,shmget、shmat、shmctl、shmdt 10 | 4. 文件锁 11 | 5. 信号量,进程间的加锁机制 12 | 6. IPC,InterProcess Communication。包括文件、fifo、共享内存等方式 13 | 14 | ## I/O多路复用 15 | 情景分析: 一个进程如果需要从多个输入源获取信息,每次调用Read(),必然要从用户态`切换`到内核态进行操作。 16 | 假设cpu只有单核的情况下,也就是说在某一个时刻,内核仅仅只能和一个I/O源打交道。 17 | 18 | 这种时候,就要用到I/O多路复用机制。 19 | 20 | `流`的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象。不管是文件,还是套接字,还是管道,我们都可以把他们看作流。 21 | 22 | 关于这部分的详细内容可以参考文章[containerd之monitor](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/docker/(23)containerd之monitor.md),在分析containerd对容器中进程进行monitor的时候,用到的就是`epoll机制`。 23 | 24 | ## 进程间传输数据 25 | 一个进程如何从另外一个进程中获取数据? 26 | 27 | 有三种解决办法:文件、有名管道(FIFO)、共享内存。 28 | 这三种方法分别通过磁盘、内核、用户空间进行数据传输。 29 | 30 | ![三种传输数据的办法](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/三种传输数据的办法.png) 31 | 32 | 1. 文件 33 | 注意读写权限、一写多读、竞态条件 34 | 35 | 2. FIFO 36 | 无名管道pipe只能连接相关进程,常规管道由进程创建,并由最后一个进程关闭。 37 | 38 | 使用`命名管道FIFO`可以连接不相关的进程,并且`可以独立于进程存在`。相关调用如下: 39 | ```shell 40 | int mkfifo(const char * pathname,mode_t mode); 41 | unlink(fifoname) ,用于删除fifo 42 | open 43 | read 44 | write 45 | ``` 46 | FIFO不存在竞态条件问题,在信息长度不超过容量的前提下,read和write都是一个原子操作。 在读者和写者连通之前,内核会把进程都挂起来,所以这里不需要锁机制。 47 | 48 | 关于FIFO的更详细信息可以参考[FIFO管道](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/docker/(18)FIFO管道.md) 49 | 50 | 3. 共享内存段 51 | 可以发现,`文件`和`FIFO`两种方式都是通过write把字节流从用户空间中复制到`内核缓存`中,read把数据从内核缓存复制到用户空间中。都需要用户态和内核态的切换。 52 | 53 | 共享内存就不需要从用户态切换到内核态。同一个系统里面的两个进程使用共享内存的方式进行交换数据,两个进程都有一个指向该内存空间的指针,资源是共享的,不需要把数据进行复制来拷贝去的。 54 | 55 | 实际上,在存储器中存储数据比想象的中要复杂得多。 56 | 虚拟内存系统允许`用户空间中的段`交换到磁盘上, **也就是说其实`共享内存段`的方法也是有可能对磁盘进行读写操作的** 。 57 | 58 | 共享内存之于进程,就类似于共享变量之于线程。 59 | 60 | ![进程通过共享内存交换数据](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/进程通过共享内存交换数据.png) 61 | 62 | 使用共享内存有几个注意点: 63 | * 共享内存段在内存中的存在,是不依赖于进程的存在的 64 | * 共享内存段有自己的名字,称为关键字key 65 | * 关键字是一个整型数 66 | * 共享内存段有自己的拥有者以及权限位 67 | * 进程可以连接到某共享内存段,并且获得指向此段的指针 68 | 69 | 相关系统调用 70 | ```c 71 | int shmget(key_t key, size_t size, int shmflg); //得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符 72 | void *shmat(int shmid, const void *shmaddr, int shmflg); //连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问 73 | char *strcpy(char* dest, const char *src); //把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间 74 | ``` 75 | 76 | 和文件系统类似,有竞态条件,拥有者和权限位的概念。 77 | 78 | ## 选择哪一种通信方式 79 | 交换数据的通信方式有很多,应该如何选择呢? 80 | 这里不是说有个明显的标准,而是需要熟悉各种通信机制的特点,根据场景来选择最优的通信方式。 81 | 82 | 1. 速度 83 | 一般而言,通过文件或者是FIFO的方式来传输数据需要更多的操作,因为其过程都需要CPU从用户态切换到内核态,然后再切换回到用户态。 84 | 如果是文件的话,内核还需要把数据复制到磁盘上,然后再从磁盘中把数据拷走。。。。 85 | 86 | 前面也提到,`共享内存段`的方法也是有可能对磁盘进行读写操作。 87 | 88 | 2. 连接和无连接 89 | 文件和共享内存就像公告牌一样,数据生产者把信息贴在广告牌上,多个消费者可以同时从上面读取信息。 90 | 91 | FIFO则要求建立连接,因为在内核转换数据之前,读者和写者都必须等待FIFO被打开,并且只有一个读者可以读取此信息。 92 | 93 | 流socket是面向连接的,而数据报socket则不是。 94 | 95 | 3. 传输范围 96 | 共享内存和FIFO只允许本机上进程间的通信。 97 | 98 | 文件可以支持不同host主机上进程的通信。 99 | 100 | 使用IP地址的socket可以支持不同host主机上进程的通信,而使用Unix地址的socket则不能。 101 | 102 | 4. 访问限制 103 | 你是希望所有人都能与服务器进行通信还是只有特定权限的用户才行? 104 | 文件、FIFO、共享内存、Unix地址的socket都能提供标准的Unix文件系统权限,而Internet Socket则不行。 105 | 106 | 5. 竞态条件 107 | 使用共享内存和共享文件要比使用管道、socket麻烦得多。 108 | 109 | 管道和socket都是由内核来进行管理的队列。写者只需把数据放入一端,而读者则从另一段读出,进程不需关心其内部实现。 110 | 111 | 然而对于共享文件、共享内存来说,对它们的访问不是由内核来管理。需要进程来进行管理,比如文件锁和信号量。 112 | 113 | ## 文件锁 114 | 有三种锁:flock、lockf和fcntl。其中最灵活的和移植性最好的是`fcntl`锁。 115 | ```c 116 | int fcntl(int fd, int cmd); 117 | int fcntl(int fd, int cmd, long arg); 118 | int fcntl(int fd, int cmd, struct flock *lock); 119 | ``` 120 | fcntl()针对(文件)描述符提供控制。 121 | 参数fd 是被参数cmd操作的描述符。 122 | 针对cmd的值,fcntl能够接受第三个参数int arg 123 | 124 | ## 信号量 125 | 信号量是一个内核变量,可以被系统中的任意一个进程访问,是系统级的全局变量。 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /Linux/(08)空洞文件.md: -------------------------------------------------------------------------------- 1 | # 空洞文件 2 | 3 | ## lseek 4 | 不带缓冲I/O函数lseek()仅仅将当前的文件偏移量记录在内核中,它并`不会引起任何I/O操作`。然后,该偏移量用于下一个读或写操作。 5 | 6 | 文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的。 7 | 位于文件中没有写过的字节将会被read()读取为0。 8 | 9 | 文件中的空洞并不要求在磁盘上占用存储区。 10 | 具体处理方式与文件系统的实现有关,当定位到超出文件尾端之后写时,对于新写的数据需要分配分配磁盘块,但是对于原文件尾端和新开始写位置之间的的部分(即空洞部分)则不需要分配磁盘块。 11 | 12 | 空洞文件的存在意味着一个文件实际上占用的磁盘大小是可能会小于该文件名义上的大小的。 13 | 14 | 为了便于管理文件,文件系统都是按块大小来分配给文件的。 15 | 16 | 刚打开一个文件的时候,偏移量都是0。 17 | 18 | ```c 19 | #include 20 | #include 21 | 22 | off_t lseek(int fd, off_t offset, int whence); 23 | 24 | // whence的值有SEEK_SET,SEEK_CUR,SEEK_END 25 | // SEEK_SET, 将该文件的偏移量设置为距文件开始处offset个字节 26 | // SEEK_CUR, 将该文件的偏移量为其当前值加上offset,offset可正数可负数 27 | // SEEK_END, 将该文件的偏移量设置为文件长度加offset,offset可正数可负数 28 | ``` 29 | 返回值off_t,若成功,则返回新的偏移量;若出错,返回 -1。 30 | 31 | ## 判断一个设备或文件是否可以设置偏移量 32 | ```c 33 | off_t currpos; 34 | currpos = lseek(fd, 0, SEEK_CUR); 35 | ``` 36 | 常规文件都可以设置偏移量,而设备一般是不可以设置偏移量的。 37 | 38 | 如果设备不支持lseek,则lseek返回-1 39 | 40 | ```c 41 | #include 42 | #include 43 | #include 44 | 45 | main(void) 46 | { 47 | if(lseek(STDIN_FILENO, 0, SEEK_CUR) == -1) 48 | printf("can not seek\n"); 49 | else 50 | printf("seek ok\n"); 51 | return 0; 52 | } 53 | ``` 54 | 55 | 其中`STDIN_FILENO`属于系统API接口库,其声明为 int 型,是一个打开文件句柄,对应的函数主要包括 open/read/write/close 等系统级调用。 56 | 操作系统一级提供的文件API都是以文件描述符来表示文件。STDIN_FILENO就是标准输入设备的文件描述符。 57 | 58 | 效果如下: 59 | ```shell 60 | [root@fqhnode01 c++]# cat lseek_test_file 61 | hello 62 | [root@fqhnode01 c++]# cat lseek_test_file | ./mylseek 63 | can not seek 64 | [root@fqhnode01 c++]# ./mylseek < lseek_test_file 65 | seek ok 66 | ``` 67 | 68 | ## 生成一个空洞文件 69 | 首先新建一个文件,可以看出其字节数是12,os为其分配了8个数据块,其中文件系统的最佳缓冲大小为4096。 70 | 71 | 这里需要说明的是`stat`的输出,8个数据块表示的是占用了8个物理扇区。 72 | 扇区,是硬盘片上的最小存储单位,一个扇区一般是512字节。 73 | 74 | 逻辑块(block): 分区进行格式化时所指定的“最小存储单位”。即文件系统存储的最小单位,这里是4096。 75 | ```c 76 | // vim /usr/include/bits/stat.h 77 | __off64_t st_size; /* Size of file, in bytes. */ 78 | 79 | __blksize_t st_blksize; /* Optimal block size for I/O. */ 80 | __blkcnt64_t st_blocks; /* Nr. 512-byte blocks allocated. */ 81 | ``` 82 | 83 | 4096/512=8 84 | ```shell 85 | [root@fqhnode01 c++]# fdisk -l 86 | 87 | 磁盘 /dev/sda:32.2 GB, 32212254720 字节,62914560 个扇区 88 | Units = 扇区 of 1 * 512 = 512 bytes 89 | 扇区大小(逻辑/物理):512 字节 / 512 字节 90 | I/O 大小(最小/最佳):512 字节 / 512 字节 91 | 磁盘标签类型:dos 92 | 磁盘标识符:0x0006197f 93 | 94 | 设备 Boot Start End Blocks Id System 95 | /dev/sda1 * 2048 33548287 16773120 83 Linux 96 | /dev/sda2 33548288 41936895 4194304 82 Linux swap / Solaris 97 | /dev/sda3 41936896 62914559 10488832 83 Linux 98 | ``` 99 | 系统通常一次会读取一个Block size大小,而不是一个扇区大小。 100 | 即一个文件至少占用一个逻辑块block,即4.0K,包含8个扇区 101 | 102 | ```shell 103 | [root@fqhnode01 c++]# touch lseek_test_file 104 | [root@fqhnode01 c++]# echo "hello world">lseek_test_file 105 | [root@fqhnode01 c++]# du -h lseek_test_file 106 | 4.0K lseek_test_file 107 | [root@fqhnode01 c++]# stat lseek_test_file 108 | 文件:"lseek_test_file" 109 | 大小:12 块:8 IO 块:4096 普通文件 110 | 设备:801h/2049d Inode:36529634 硬链接:1 111 | 权限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root) 112 | 环境:unconfined_u:object_r:admin_home_t:s0 113 | 最近访问:2018-02-14 10:34:11.089426007 -0500 114 | 最近更改:2018-02-14 10:34:13.714738010 -0500 115 | 最近改动:2018-02-14 10:34:13.714738010 -0500 116 | 创建时间:- 117 | ``` 118 | 119 | 执行下面的程序 120 | ```c 121 | #include 122 | #include 123 | #include 124 | #include 125 | #include 126 | 127 | int main(int argc, char *argv[]) 128 | { 129 | int fd; 130 | fd = open(argv[1], O_RDWR); 131 | if (fd<0){ 132 | perror("fd<0"); 133 | return 0; 134 | } 135 | lseek(fd, 16384,SEEK_SET); 136 | write(fd, "abcde",5); 137 | close(fd); 138 | return 0; 139 | } 140 | ``` 141 | (16384+5)/512=32 142 | 143 | 效果如下: 144 | ```shell 145 | [root@fqhnode01 c++]# gcc -o mylseek mylseek.c 146 | [root@fqhnode01 c++]# ./mylseek lseek_test_file 147 | [root@fqhnode01 c++]# ll lseek_test_file 148 | -rw-r--r--. 1 root root 16389 2月 14 11:10 lseek_test_file 149 | [root@fqhnode01 c++]# stat lseek_test_file 150 | 文件:"lseek_test_file" 151 | 大小:16389 块:16 IO 块:4096 普通文件 152 | 设备:801h/2049d Inode:36284030 硬链接:1 153 | 权限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root) 154 | 环境:unconfined_u:object_r:admin_home_t:s0 155 | 最近访问:2018-02-14 11:08:53.743231691 -0500 156 | 最近更改:2018-02-14 11:10:05.093886607 -0500 157 | 最近改动:2018-02-14 11:10:05.093886607 -0500 158 | 创建时间:- 159 | [root@fqhnode01 c++]# du -h lseek_test_file 160 | 8.0K lseek_test_file 161 | 162 | [root@fqhnode01 c++]# od -c lseek_test_file 163 | 0000000 h e l l o w o r l d \n \0 \0 \0 \0 164 | 0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 165 | * 166 | 0040000 a b c d e 167 | 0040005 168 | ``` 169 | 可以发现文件lseek_test_file名义上的大小变为了16389个字节,但占用的扇区只有16个,是要小于32个的。 170 | 171 | 最后,来看看把空洞的地方填充为0,会如何?read()会把空洞的地方读取为0,然后写入到新的文件中。 172 | ```shell 173 | [root@fqhnode01 c++]# cat lseek_test_file >lseek_new_file 174 | [root@fqhnode01 c++]# du -c lseek_test_file lseek_new_file 175 | 8 lseek_test_file 176 | 20 lseek_new_file 177 | 28 总用量 178 | 179 | [root@fqhnode01 c++]# stat lseek_new_file 180 | 文件:"lseek_new_file" 181 | 大小:16389 块:40 IO 块:4096 普通文件 182 | 设备:801h/2049d Inode:36284001 硬链接:1 183 | 权限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root) 184 | 环境:unconfined_u:object_r:admin_home_t:s0 185 | 最近访问:2018-02-14 11:15:05.215808560 -0500 186 | 最近更改:2018-02-14 11:14:41.869808560 -0500 187 | 最近改动:2018-02-14 11:14:41.869808560 -0500 188 | 创建时间:- 189 | ``` 190 | 这里分配了40个扇区大小,不是上面算出来的32。。。。多了一个逻辑块,可能和文件系统有关系?? 191 | 192 | 需要说明的是 193 | 1. ls -l file 查看文件逻辑大小 194 | 2. du -c file 查看文件实际占用的存储块多少 195 | 3. od -c file 查看文件存储的内容 196 | 197 | ## 用途 198 | 实际中的空洞文件会在哪里用到呢?常见的场景有两个: 199 | 一是在下载电影的时候,发现刚开始下载,文件的大小就已经到几百M了。 200 | 201 | 二是在创建虚拟机的磁盘镜像的时候,你创建了一个100G的磁盘镜像,但是其实装起来系统之后,开始也不过只占用了3,4G的磁盘空间,如果一开始把100G都分配出去的话,无疑是很大的浪费. 202 | 203 | 204 | -------------------------------------------------------------------------------- /Linux/(09)标准IO库.md: -------------------------------------------------------------------------------- 1 | # 标准I/O库 2 | 3 | ## 标准I/O、不带缓冲的I/O 4 | open()、read()等不带缓冲的I/O函数都是基于一个文件描述符fd来进行操作的,而标准I/O库则是基于`流stream`的。 5 | 6 | 标准I/O库负责缓冲区分配、以优化的块长度来处理I/O等功能。 7 | 8 | 近30+年,标准I/O库基本上没怎么修改过,一如既往地稳定。 9 | 10 | 两者的关系:标准I/O为不带缓冲的I/O提供了一个带缓冲的接口。使用标准I/O无需担心如何选择最佳缓冲区大小。 11 | 12 | 当用标准I/O打开或创建一个文件时,已经使一个流与一个文件绑定。 13 | 14 | ## 流 15 | `流的定向`,分为`单字节定向`和`多字节定向`,决定了该流所读写的字符是单字节还是多字节,可以通过wchar.h中函数来进行设定。 16 | 17 | 一个流在最初被创建的时候,是没有设置定向的。 18 | 19 | 只有两个函数可以设置一个流的定向: 20 | 1. freopen(),用于清除一个流的定向 21 | 2. fwide(FILE *fp, int mode),用于设置一个流的定向。本函数并不会改变一个已定向流的定向。 22 | 23 | 当打开一个流的时候,标准I/O函数fopen()返回的是`一个指向FIFE对象的指针`。 24 | 该对象通常是一个结构,包含了标准I/O库为管理一个流所需的全部信息,包括用于实际I/O的文件描述符fd、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数与及出错标识等。 25 | 26 | 我们把指向该FIFE对象的指针,称为`文件指针`。 27 | 28 | 注意标准I/O函数fopen()和前面系统调用open()的`区别`,系统调用open()返回的仅仅是一个文件描述符fd,而这里返回的是一个包含fd的对象。 29 | 30 | 标准I/O库中和前面的类似,为一个进程定义了三个流,标准输入、标准输出、标准错误,定义在stdio.h中。 31 | 32 | ## 缓冲 33 | 标准I/O库提供缓冲的目的是尽可能地减少使用read和write的次数。它对每个I/O流自动地进行缓冲管理,从而避免在应用程序这一层来处理。 34 | 然而缓冲机制也正是标准I/O库中最令人困惑的地方。 35 | 36 | 标准I/O库有三种类型的缓冲: 37 | ### 全缓冲 38 | 只有填满标准I/O缓冲区之后才进行实际的I/O操作。对于驻留在磁盘上的文件通常是由标准I/O库实施全缓冲。 39 | 40 | 术语`冲洗flush`指的是标准I/O缓冲区的`写操作`。缓冲区可以由标准I/O例程自动地冲洗(在缓冲区被填满时),或者人工调用函数`fflush`来冲洗一个流。 41 | 42 | 在UNIX环境中,flush(冲洗)有两种意思: 43 | 1. 在标准I/O库中,flush意味着把缓冲区中的数据写入到磁盘上,该缓冲区可能仅仅是填充了部分数据 44 | 2. 在终端驱动程序方面,flush表示丢弃已经存储在缓冲区中的数据。 45 | 46 | ### 行缓冲 47 | 在输入输出中遇到换行符时,标准I/O库执行I/O操作。 48 | 49 | 当流涉及到一个终端时(如标准输入、标准输出),通常使用行缓冲。这样可以允许我们一次输入输出一个字符,但只有在写入了一行之后再进行实际的I/O操作。 50 | 51 | 行缓冲有两个限制: 52 | 1. 因为标准I/O库用来存储每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,即使还没有写入一个换行符,也进行I/O操作 53 | 2. 任何时候通过标准I/O库来从 a)一个不带缓冲的流;或 b)一个行缓冲的流 中得到输入数据,那么就会冲洗所有行缓冲输出流。 54 | 55 | 需要说明的是,当从 b)一个行缓冲的流中读取数据时,是向内核发送请求希望读取数据,但所需的数据可能已经在该缓冲区中了,所以不会要求是从内核中读取数据。 56 | 57 | 同理,如果是从 a)一个不带缓冲的流中读取数据,就需要从内核中获取数据了。 58 | ### 不带缓冲 59 | 标准I/O库不会对字符进行缓冲存储,通用用于希望输入的内容能够立刻显示出来的场景。 60 | 61 | 标准错误流stderr通常是不带缓冲的,为了使错误信息尽快地显示。 62 | 63 | ### 小结 64 | 很多系统默认会采用下面的`缓冲机制`:标准错误流stder常是不带缓冲的,指向终端设备的流是行缓冲,其他的流则是全缓冲! 65 | 66 | 可以通过`setbuf`和`setvbuf`来打开关闭缓冲,或者更改缓冲类型。 67 | 68 | 最后,`fflush(FILE *fp)`可以强制冲洗一个流,使得该流中所有未写的数据都会被传送到内核。一个特殊的情况,如果fp为`NULL`,那么所有的流都会被冲洗。。。。 69 | 70 | *** 71 | 72 | ## 打开流 73 | fopen(),基于一个指定路径来打开一个文件。 74 | 75 | freopen(),在一个指定的流上打开一个指定的文件。此函数常用于将一个指定的文件打开为一个预先定义的流:标准输入、标准输出、标准错误 76 | 77 | fdopen(),基于一个文件描述符fd来构建一个流。此函数常用于创建管道和网络通信管道函数返回的描述符,这类特殊的文件无法直接用fopen()打开,必须用fdopen()。 78 | 79 | 最后调用fclose(FILE *fp)来关闭一个已打开的流,在该文件关闭之前,冲洗缓冲区中的输出数据,而缓冲区中所有的`输入数据`将被`丢弃`。 80 | 81 | 可以发现这个和前面的系统调用提供的I/O是类似的,只是在其基础上增加了缓存。 82 | 83 | 当一个进程正常终止时(直接调用exit,或从main函数返回),则所有带未写缓冲数据的标准I/O流都会被冲洗,所有打开的标准I/O流都会被关闭。 84 | 85 | ## 非格式化I/O 86 | 一个流被打开了之后,如何进行读和写操作?有三种`非格式化`I/O方式: 87 | 1. 每次一个字符的I/O,一次仅读/写一个字符,使用fgetc、getc、getchar 88 | 2. 每次一行的I/O,使用fgets和fputs 89 | 3. 直接I/O,每次读/写一个对象,使用fread和fwrite。又被称为二进制I/O、一次一个对象I/O、面向记录的I/O、面向结构的I/O。 90 | 91 | 如果一个流是带缓冲的,读写操作之后,标准I/O库会处理缓冲区。 92 | 93 | 使用直接I/O的一个问题,就是只能用于读取在同一个系统上已写的数据。所以,在现在异构系统通过网络互联的复杂环境下,fread和fwrite可能不能正常工作。原因是: 94 | * 因为不同的对齐要求,在一个结构中,同一个成员的偏移量可能和编译程序、系统相关 95 | * 用来存储多子节整数和浮点数的二进制格式在不同系统结构间也可能不同 96 | 97 | 现在一般在不同系统之间交换二进制数据的办法是使用互认的规范格式! 98 | 99 | ## 对一个流进行定位 100 | 可以使用ftell、fseek、ftello、fseeko和fgetpos、fsetpos三种方法对一个标准I/O流进行定位。 101 | 102 | 需要移植到非UNIX系统上运行的应用程序应当使用fgetpos、fsetpos 103 | 104 | ## 格式化I/O 105 | 1. 格式化输出 106 | 107 | 格式化输出由5个printf()函数来处理,printf()将格式化数据写至标准输出,fprintf()写至指定的流,dprintf()写至指定的文件描述符fd,sprintf()则将格式化的字符送入数组buf中。 108 | 109 | sprintf()函数可能会导致buf指向的缓冲区溢出,需要由调用者来确保安全。 110 | 111 | 2. 格式化输入 112 | 113 | 三个scanf()执行格式化输入处理,分析输入的字符串,并将其转化为指定类型的变量。 114 | 115 | ## 内存流 116 | 标准I/O库把数据缓存在内存中,因此每次一个字符、或每次一行的I/O更有效。 117 | 118 | 我们可以通过setbuf、setvbuf函数让I/O库使用自己设置的缓冲区,即`内存流`,也是`标准I/O流`。虽然仍然使用FILE指针进行访问,但其实并没有底层文件。所有的I/O都是通过缓冲区与主存之间来回传送字节来完成的。 119 | 120 | 这些流看起来像文件流,但它们的一些特征使其更适合用在字符串操作。 121 | 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning k8s source code 2 | 记录源码学习和一些原理译文,个人随笔 3 | 力从应用出发,再去深究某个概念的原理 4 | 以apiserver controller-manager scheduler kubelet proxy 和 kubectl 6个命令为主线 5 | 6 | 同时记录一些平时用到的GO package的用法,方便查询 7 | ## 版本说明 8 | 如无特别说明,本project所涉及源码皆为 V1.5.2 9 | 编译V1.5.2版本需要go version go1.7.5 linux/amd64 10 | 11 | ## License 12 | 使用GPL License -------------------------------------------------------------------------------- /apiserver/(02) apiserver综述.md: -------------------------------------------------------------------------------- 1 | # Apiserver综述 2 | 3 | ## 综述 4 | - API Server作为整个Kubernetes集群的核心组件,让所有资源可被描述和配置;这里的资源包括了类似网络、存储、Pod这样的基础资源也包括了replication controller、deployment这样的管理对象。 5 | - API Server某种程度上来说更像是包含了一定逻辑的对象数据库;接口上更加丰富、自带GC、支持对象间的复杂逻辑;当然API Server本身是无状态的,数据都是存储在etcd当中。 6 | - API Server提供集群管理的REST API接口,支持增删改查和patch、监听的操作,其他组件通过和API Server的接口获取资源配置和状态,以实现各种资源处理逻辑。 7 | - 只有API Server才直接操作etcd 8 | 9 | ## 架构图 10 | ![APiserver架构图](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/apiserver-00.jpeg) 11 | 12 | - Scheme:定义了资源序列化和反序列化的方法以及资源类型和版本的对应关系;这里我们可以理解成一张映射表。 13 | - Storage:是对资源的完整封装,实现了资源创建、删除、watch等所有操作。 14 | - APIGroupInfo:是在同一个Group下的所有资源的集合。 15 | 16 | ## 资源版本 17 | 一个资源对应着两个版本: 一个版本是用户访问的接口对象(yaml或者json通过接口传递的格式),称之为external version; 18 | 另一个版本则是核心对象,实现了资源创建和删除等,并且直接参与持久化,对应了在etcd中存储,称之为internal version。 19 | 这两个版本的资源是需要相互转化的,并且转换方法需要事先注册在Scheme中。 20 | 21 | 版本化的API旨在保持稳定,而internal version是为了最好地反映Kubernetes代码本身的需要。 22 | 23 | 这也就是说如果要自己新定义一个资源,需要声明两个版本! 24 | 同时应该还要在Scheme中注册版本转换函数。 25 | 26 | etcd中存储的是带版本的,这也是Apiserver实现多版本转换的核心。 27 | 多个external version版本之间的资源进行相互转换,都是需要通过internal version进行中转。 28 | 29 | 对于core Group而言,internal version的对象定义在`/kubernetes-1.5.2/pkg/api/types.go`。 30 | v1是其中一个external version,其对象定义在`/kubernetes-1.5.2/pkg/api/v1/types.go`。 31 | 一个对象在internal version和external version中的定义可以一样,也可以不一样。 32 | 33 | ## Apiserver启动时初始化流程 34 | 1. initial.go中的初始化主要用internal version和external versions填充了Scheme,完成了 APIRegistrationManager中GroupMeta的初始化。GroupMeta的主要用于后面的初始化APIGroupVersion。 35 | 36 | 2. GroupMeta包含成员其中就有Groupversion、RESTMapper。初始化groupMeta的时候会根据Scheme和externalVersions新建一个RESTMapper。 37 | 38 | 3. /pkg/registry/core/rest/storage_core.go中的NewLegacyRESTStorage基于上面的Scheme和GroupMeta生成了一个APIGroupInfo。初始化时候的GroupMeta是通过type APIRegistrationManager struct的函数来获取的。 39 | 40 | 4. 然后基于APIGroupInfo生成一个APIGroupVersion。 41 | 42 | 5. 最后`apiGroupVersion.InstallREST(s.HandlerContainer.Container)`,完成从API资源到restful API的注册。 43 | 44 | 6. 在InstallREST的过程中会用到RESTMapper生成的RESTMapping 45 | 46 | ```go 47 | 重要结构体: 48 | 一: 49 | APIGroupVersion===>定义在pkg/apiserver/apiserver.go==>type APIGroupVersion struct 50 | 创建APIGroupVersion的地方在/pkg/genericapiserver/genericapiserver.go中的 51 | --->func (s *GenericAPIServer) newAPIGroupVersion 52 | ************************* 53 | ************************* 54 | -->可以从/pkg/master/master.go中的-->func (c completedConfig) New() (*Master, error)中的 55 | -->m.InstallLegacyAPI(c.Config, restOptionsFactory.NewFor, legacyRESTStorageProvider) 和 56 | m.InstallAPIs(c.Config.GenericConfig.APIResourceConfigSource, restOptionsFactory.NewFor, restStorageProviders...) 57 | 开始分析 58 | 59 | 二: 60 | APIRegistrationManager===>/pkg/apimachinery/registered/registered.go==>type APIRegistrationManager struct 61 | 创建APIRegistrationManager的地方在/pkg/apimachinery/registered/registered.go中 62 | 63 | 总结: 64 | 综合上面所有的初始化可以看到(APIGroupVersion、APIGroupInfo、Scheme、GroupMeta、RESTMapper、APIRegistrationManager), 65 | 其实主要用internal version和external versions填充Scheme, 66 | 用external versions去填充GroupMeta以及其成员RESTMapper。 67 | GroupMeta有啥作用呢?主要用于生成最后的APIGroupVersion。 68 | ``` 69 | 70 | ## API资源注册为Restful API 71 | 当API资源初始化完成以后,需要将这些API资源注册为restful api,用来接收用户的请求。 72 | kube-apiServer使用了go-restful这套框架,里面主要包括三种对象: 73 | - Container: 一个Container包含多个WebService 74 | - WebService: 一个WebService包含多条route 75 | - Route: 一条route包含一个method(GET、POST、DELETE等),一条具体的path(URL)以及一个响应的handler function。 76 | 77 | API注册的入口函数有两个: m.InstallAPIs 和 m.InstallLegacyAPI。 78 | 文件路径:pkg/master/master.go 79 | 这两个函数分别用于注册"/api"和"/apis"的API,这里先拿InstallLegacyAPI进行介绍。 80 | 这些接口都是在config.Complete().New()函数中被调用 81 | 82 | ## Storage机制 83 | Apiserver针对每一类资源(pod、service、replication controller),都会与etcd建立一个连接,获取该资源的opt。 84 | 所有资源都定义了restful实现。 85 | Apiserver正是通过这个opt去操作对应的资源。 86 | 87 | ## Apiserver端List-Watch机制 88 | 什么是watch?kubelet、kube-controller-manager、kube-scheduler需要监控各种资源(pod、service等)的变化, 89 | 当这些对象发生变化时(add、delete、update),kube-apiserver能够主动通知这些组件。这是Client端的Watch实现。 90 | 91 | Apiserver端的Watch机制是建立在etcd的Watch基础上的。 92 | etcd的watch是没有过滤功能的,而kube-apiserver增加了过滤功能。 93 | 94 | 什么是过滤功能?,比如说kubelet只对调度到本节点上的pod感兴趣,也就是pod.host=node1; 95 | 而kube-scheduler只对未被调度的pod感兴趣,也就是pod.host=”“。 96 | etcd只能watch到pod的add、delete、update。 97 | kube-apiserver则增加了过滤功能,将订阅方感兴趣的部分资源发给订阅方。 98 | 99 | ## 一个Restful请求需要经过的流程 100 | 101 | Authentication-->Authorization-->Admission Control 102 | 103 | ![一个请求需要经过的流程](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/access-control-overview.jpg) 104 | 105 | ## 参考 106 | [如何扩展Kubernetes管理的资源对象](http://dockone.io/article/2405) 107 | -------------------------------------------------------------------------------- /apiserver/(05) project apimachinery.md: -------------------------------------------------------------------------------- 1 | # project apimachinery 2 | 3 | **Table of Contents** 4 | 5 | - [project apimachinery](#project-apimachinery) 6 | - [k8s里面的apimachinery package](#k8s里面的apimachinery-package) 7 | - [project apimachinery中各个package的作用](#project-apimachinery中各个package的作用) 8 | - [总结](#总结) 9 | 10 | 11 | 12 | 首先弄清两个apimachinery的范畴,一个是project,一个是k8s里面的一个package。前者包含了后者, 13 | k8s是前者的消费者。 14 | 15 | ## project apimachinery 16 | 查看项目[kubernetes/apimachinery](https://github.com/kubernetes/apimachinery)的介绍, 17 | 18 | Scheme, typing, encoding, decoding, and conversion packages for Kubernetes and Kubernetes-like API objects。 19 | 这是一套用于kubernetes和类kubernetes API的Scheme, typing, encoding, decoding, and conversion packages。 20 | 21 | This library is a shared dependency for servers and clients to work with Kubernetes API infrastructure without direct type dependencies. It's first comsumers are k8s.io/kubernetes, k8s.io/client-go, and k8s.io/apiserver. 22 | 23 | 这里提到了kubernetes采用的API机制就是本项目,见`/kubernetes-1.5.2/pkg/apimachinery`,用的就是本项目的`pkg/apimachinery/` 24 | 25 | 从这里可以看出,kubernetes把其很多组件以开源项目的形式单独开源出来了,比如[k8s.io/apiserver](https://github.com/kubernetes/apiserver)。 26 | 27 | 需要注意的一点是,Do not add API types to this repo. This is for the machinery, not for the types. 28 | 也就是说apimachinery是一套通用的API机制,不要往里面添加一些具体的type,比如说k8s里面的'pod、service'这些就是一些具体的type。 29 | 30 | 这个项目基本是和kubernetes源码保持一致的,所以可以通过它来学习kubernetes的通用API机制。[godoc-apimachinery](https://godoc.org/k8s.io/apimachinery)中列出了本项目的情况,可以发现很多都是和kuebrnetes源码对应的。 31 | 32 | ## k8s里面的apimachinery package 33 | 我们来查看k8s源码里面的目录结构 34 | ``` 35 | -------announced 36 | | 37 | | 38 | pkg---apimachinery----------registered 39 | | 40 | | 41 | -----type.go 42 | ``` 43 | 三个package的作用 44 | ``` 45 | pkg/apimachinery Package apimachinery contains the generic API machinery code that is common to both server and clients. 46 | 47 | pkg/apimachinery/announced Package announced contains tools for announcing API group factories. 48 | 49 | pkg/apimachinery/registered Package to keep track of API Versions that can be registered and are enabled in api.Scheme. 50 | 51 | ``` 52 | 53 | ## project apimachinery中各个package的作用 54 | 先来个总体的概念,后面才好更好地深入了解。 55 | ``` 56 | pkg/api/errors 提供了api字段验证的详细错误类型。-Package errors provides detailed error types for api field validation. 57 | 58 | pkg/apimachinery Package apimachinery contains the generic API machinery code that is common to both server and clients. 59 | 60 | pkg/apimachinery/announced Package announced contains tools for announcing API group factories. 61 | 62 | pkg/apimachinery/registered Package to keep track of API Versions that can be registered and are enabled in api.Scheme. 63 | 64 | pkg/api/meta Package meta provides functions for retrieving API metadata from objects belonging to the Kubernetes API 65 | 66 | pkg/api/resource Package resource is a generated protocol buffer package. 67 | pkg/apis/meta/fuzzer 68 | pkg/apis/meta/internalversion 69 | pkg/apis/meta/v1 70 | pkg/apis/meta/v1alpha1 71 | pkg/apis/meta/v1/unstructured 72 | pkg/apis/meta/v1/validation 73 | pkg/apis/testapigroup 74 | pkg/apis/testapigroup/fuzzer 75 | 76 | pkg/apis/testapigroup/install Package install installs the certificates API group, making it available as an option to all of the API encoding/decoding machinery. 77 | 78 | pkg/apis/testapigroup/v1 79 | pkg/api/testing 80 | pkg/api/testing/fuzzer 81 | pkg/api/testing/roundtrip 82 | 83 | pkg/api/validation Package validation contains generic api type validation functions. 84 | pkg/api/validation/path 85 | 86 | pkg/conversion Package conversion provides go object versioning. 87 | 88 | pkg/conversion/queryparams Package queryparams provides conversion from versioned runtime objects to URL query values 89 | 90 | pkg/conversion/unstructured Package unstructured provides conversion from runtime objects to map[string]interface{} representation. 91 | 92 | pkg/fields Package fields implements a simple field system, parsing and matching selectors with sets of fields. 93 | 94 | pkg/labels Package labels implements a simple label system, parsing and matching selectors with sets of labels. 95 | 96 | pkg/runtime Defines conversions between generic types and structs to map query strings to struct objects. 97 | 98 | pkg/runtime/schema Package schema is a generated protocol buffer package. 99 | 100 | pkg/runtime/serializer 101 | 102 | pkg/runtime/serializer/json 103 | 104 | pkg/runtime/serializer/protobuf Package protobuf provides a Kubernetes serializer for the protobuf format. 105 | 106 | pkg/runtime/serializer/recognizer 107 | 108 | pkg/runtime/serializer/streaming Package streaming implements encoder and decoder for streams of runtime.Objects over io.Writer/Readers. 109 | 110 | pkg/runtime/serializer/testing 111 | pkg/runtime/serializer/versioning 112 | pkg/runtime/serializer/yaml 113 | pkg/runtime/testing 114 | pkg/selection 115 | pkg/test 116 | 117 | pkg/types Package types implements various generic types used throughout kubernetes. 118 | 119 | pkg/util/cache 120 | pkg/util/clock 121 | pkg/util/diff 122 | 123 | pkg/util/errors Package errors implements various utility functions and types around errors. 124 | 125 | pkg/util/framer Package framer implements simple frame decoding techniques for an io.ReadCloser 126 | 127 | pkg/util/httpstream Package httpstream adds multiplexed streaming support to HTTP requests and responses via connection upgrades. 128 | 129 | pkg/util/httpstream/spdy 130 | pkg/util/initialization 131 | pkg/util/intstr Package intstr is a generated protocol buffer package. 132 | pkg/util/json 133 | pkg/util/jsonmergepatch 134 | pkg/util/mergepatch 135 | pkg/util/net 136 | pkg/util/proxy Package proxy provides transport and upgrade support for proxies. 137 | pkg/util/rand Package rand provides utilities related to randomization. 138 | pkg/util/remotecommand 139 | pkg/util/runtime 140 | pkg/util/sets Package sets has auto-generated set types. 141 | pkg/util/sets/types Package types just provides input types to the set generator. 142 | pkg/util/strategicpatch 143 | pkg/util/uuid 144 | pkg/util/validation 145 | pkg/util/validation/field 146 | pkg/util/wait Package wait provides tools for polling or listening for changes to a condition. 147 | pkg/util/yaml 148 | pkg/version Package version supplies the type for version information collected at build time. 149 | 150 | pkg/watch Package watch contains a generic watchable interface, and a fake for testing code that uses the watch interface. 151 | 152 | third_party/forked/golang/json Package json is forked from the Go standard library to enable us to find the field of a struct that a given JSON key maps to. 153 | 154 | third_party/forked/golang/netutil 155 | 156 | third_party/forked/golang/reflect Package reflect is a fork of go's standard library reflection package, which allows for deep equal with equality functions defined. 157 | 158 | ``` 159 | 160 | ## 总结 161 | 本文主要对开源项目apimachinery来了个整体性的介绍,并没有深入讲解,但相信,了解这些项目的由来和关系对后面深入学习k8s的机制是很有帮助的。 162 | 163 | 下一篇将对[k8s里面的apimachinery package](#k8s里面的apimachinery-package)进行讲解。 164 | 165 | 166 | 167 | ## 参考 168 | [开源项目-apimachinery](https://github.com/kubernetes/apimachinery) 169 | 170 | [godoc-开源项目apimachinery](https://godoc.org/k8s.io/apimachinery) 171 | 172 | [开源项目-apiserver](https://github.com/kubernetes/apiserver) -------------------------------------------------------------------------------- /apiserver/(22) 一个Request的流程之Filters.md: -------------------------------------------------------------------------------- 1 | # 一个Request的流程之Filters 2 | 3 | **Table of Contents** 4 | 5 | - [引言](#引言) 6 | - [DefaultBuildHandlerChain](#defaultbuildhandlerchain) 7 | - [WithAuthentication](#withauthentication) 8 | - [参考](#参考) 9 | 10 | 11 | 12 | ## 引言 13 | 在[deep dive]()系列中提到过:当HTTP请求命中Kubernetes API时,HTTP请求首先由在`DefaultBuildHandlerChain()`中注册的过滤器链进行处理。 14 | 该过滤器对其执行一系列过滤操作。 15 | 过滤器通过并附加相应信息到`ctx.RequestInfo`,例如经过身份验证的user或返回适当的HTTP响应代码。 16 | 17 | ![API-server-flow](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/API-server-flow.png) 18 | 19 | ## DefaultBuildHandlerChain 20 | kube-apiserver使用go-restful框架来建立Web服务,分为http和https两个handler。见/pkg/genericapiserver/config.go的func (c completedConfig) New() 21 | 22 | ```go 23 | s.HandlerContainer = mux.NewAPIContainer(http.NewServeMux(), c.Serializer) 24 | /* 25 | 上面Container已创建并且也进行了初始化。该轮到WebService了 26 | */ 27 | s.installAPI(c.Config) 28 | 29 | s.Handler, s.InsecureHandler = c.BuildHandlerChainsFunc(s.HandlerContainer.ServeMux, c.Config) 30 | ``` 31 | 关于Handler和InsecureHandler的使用可以参考[Restful API注册]()系列文章。 32 | 33 | 而BuildHandlerChainsFunc的声明如下: 34 | ```go 35 | func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) (secure, insecure http.Handler) { 36 | attributeGetter := apiserverfilters.NewRequestAttributeGetter(c.RequestContextMapper) 37 | 38 | generic := func(handler http.Handler) http.Handler { 39 | handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true") 40 | handler = genericfilters.WithPanicRecovery(handler, c.RequestContextMapper) 41 | handler = apiserverfilters.WithRequestInfo(handler, NewRequestInfoResolver(c), c.RequestContextMapper) 42 | handler = api.WithRequestContext(handler, c.RequestContextMapper) 43 | handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc) 44 | handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.LongRunningFunc) 45 | return handler 46 | } 47 | audit := func(handler http.Handler) http.Handler { 48 | return apiserverfilters.WithAudit(handler, attributeGetter, c.AuditWriter) 49 | } 50 | protect := func(handler http.Handler) http.Handler { 51 | /* 52 | 封装了Authorization、Authentication的handler 53 | */ 54 | handler = apiserverfilters.WithAuthorization(handler, attributeGetter, c.Authorizer) 55 | handler = apiserverfilters.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer) 56 | handler = audit(handler) // before impersonation to read original user 57 | handler = authhandlers.WithAuthentication(handler, c.RequestContextMapper, c.Authenticator, authhandlers.Unauthorized(c.SupportsBasicAuth)) 58 | return handler 59 | } 60 | 61 | return generic(protect(apiHandler)), generic(audit(apiHandler)) 62 | } 63 | ``` 64 | 以Authentication为例,看看其handler定义。 65 | 66 | ## WithAuthentication 67 | 68 | WithAuthentication()会创建一个http handler,会对指定的request进行认证。 69 | 认证的返回信息的一个user,WithAuthentication()会把这些user信息附加到该request的context上。 70 | - 如果身份认证失败或返回错误,则使用失败的handler来进行处理。 71 | - 如果成功,会从request的header中删除"Authorization"信息,并调用handler来为该request提供服务。 72 | 73 | 见/pkg/auth/handlers/handlers.go 74 | 75 | ```go 76 | // WithAuthentication creates an http handler that tries to authenticate the given request as a user, and then 77 | // stores any such user found onto the provided context for the request. If authentication fails or returns an error 78 | // the failed handler is used. On success, "Authorization" header is removed from the request and handler 79 | // is invoked to serve the request. 80 | 81 | func WithAuthentication(handler http.Handler, mapper api.RequestContextMapper, auth authenticator.Request, failed http.Handler) http.Handler { 82 | if auth == nil { 83 | glog.Warningf("Authentication is disabled") 84 | return handler 85 | } 86 | return api.WithRequestContext( 87 | http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 88 | /* 89 | 认证 90 | ==>/plugin/pkg/auth/authenticator/request/union/union.go 91 | ==>func (authHandler *unionAuthRequestHandler) AuthenticateRequest 92 | */ 93 | user, ok, err := auth.AuthenticateRequest(req) 94 | if err != nil || !ok { 95 | if err != nil { 96 | glog.Errorf("Unable to authenticate the request due to an error: %v", err) 97 | } 98 | //认证失败时的处理 99 | failed.ServeHTTP(w, req) 100 | return 101 | } 102 | 103 | // authorization header is not required anymore in case of a successful authentication. 104 | //如果前面认证成功了,不再需要authorization header信息 105 | req.Header.Del("Authorization") 106 | 107 | //把认证返回的user信息附加到该request的context上,供后续决策流程使用 108 | if ctx, ok := mapper.Get(req); ok { 109 | mapper.Update(req, api.WithUser(ctx, user)) 110 | } 111 | 112 | authenticatedUserCounter.WithLabelValues(compressUsername(user.GetName())).Inc() 113 | 114 | handler.ServeHTTP(w, req) 115 | }), 116 | mapper, 117 | ) 118 | } 119 | ``` 120 | 121 | 这里的`user, ok, err := auth.AuthenticateRequest(req)`调用的正是定义在`/plugin/pkg/auth/authenticator/request/union/union.go`中的AuthenticateRequest()。 122 | 123 | 这就和`认证机制`一文中的流程对应上了,见[Authenticator机制]()。 124 | 125 | ## 参考 126 | [accessing the api](https://kubernetes.io/docs/admin/accessing-the-api/) 127 | 128 | [authentication](https://kubernetes.io/docs/admin/authentication/) 129 | 130 | [authorization](https://kubernetes.io/docs/admin/authorization/) 131 | 132 | [admission-controllers](https://kubernetes.io/docs/admin/admission-controllers/) 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /apiserver/(28)Adding an API Group.md: -------------------------------------------------------------------------------- 1 | # Adding an API Group 2 | =============== 3 | 4 | 本文译自[Adding an API Group V1.6](https://github.com/kubernetes/community/blob/release-1.6/contributors/devel/adding-an-APIGroup.md) 5 | 6 | ## 创建你的core group package 7 | 方法在未来会改进,演变的方向参考[16062](https://github.com/kubernetes/kubernetes/pull/16062)。 8 | 9 | 1. 在`pkg/apis`目录下创建一个文件夹,用于存放你准备新建的group。然后在types.go中定义你的API objects 10 | - 新建`pkg/apis//types.go` 11 | - 新建`pkg/apis///types.go` 12 | 13 | 2. 在registry.go中把本Group 的API Object注册到Scheme中,可以参考`pkg/apis/authentication/register.go 和 pkg/apis/authentication/v1beta1/register.go`。 registry文件必须有一个名为SchemeBuilder的变量,以供自动生成的代码引用。 必须有一个AddToScheme方法供安装程序引用。 可以参考pkg/apis/...下Group的register.go文件,但不要直接复制粘贴,它们不是通用的。 14 | - `pkg/apis//register.go` 15 | - `pkg/apis///register.go` 16 | 17 | 3. 新建install.go,可能仅仅需要更改pkg/apis/authentication/install.go中的Group Name和Version。 这个package必须要在k8s.io/kubernetes/pkg/api/install中被导入。 18 | - `pkg/apis//install/install.go` 19 | 20 | 第2步和第3步是机械性的,我们计划使用`cmd/libs/go2idl/`下的工具来自动生成。 21 | 22 | ## Type definitions in types.go 23 | 每一个type都应该是一个可导出的struct(大写name)。 24 | 这个struct应该内嵌`TypeMeta`和`ObjectMeta`,也应该含有属性`Spec`和`Status`。 25 | 如果该对象仅仅是用来进行存储数据的,并且不会被controller修改,那么其`Status`字段可以保持不变;然后`Spec`中的fileds可以直接内连到本struct中。 26 | 27 | 对于每个top-level type,还应该有一个`List`结构体。 List结构应该内嵌`TypeMeta`和`ListMeta`。 28 | 还应该有一个`Items`字段,是一个本type的切片。 29 | 30 | ## Scripts changes and auto-generated code 31 | 32 | 1. Generate conversions and deep-copies: 33 | 1. Add your "group/" or "group/version" into cmd/libs/go2idl/conversion-gen/main.go; 34 | 2. 确保你的`pkg/apis//`目录下的doc.go的文件中有一行注解`// +k8s:deepcopy-gen=package,register`, 35 | 以便引起自动生成代码工具的注意。 36 | 3. 确保你的`pkg/apis//`目录下的doc.go的文件中有一行注解`// +k8s:conversion-gen=`。 37 | 对于大部分API来说,一般都是填写其对应的internal Group目录,`k8s.io/kubernetes/pkg/apis/`。 38 | 4. 确保目录`pkg/apis/` 和 `pkg/apis//` 下的doc.go中有一行注解`+groupName=.k8s.io`, 39 | 以便生成正确的该Group的DNS后缀。 40 | 5. 运行 `hack/update-all.sh` 41 | 42 | 2. Generate files for Ugorji codec: 43 | 1. Touch types.generated.go in pkg/apis/{/, }; 44 | 2. 运行 `hack/update-codecgen.sh` 45 | 46 | 3. Generate protobuf objects: 47 | 1. 在`cmd/libs/go2idl/go-to-protobuf/protobuf/cmd.go`中的`func New()`中的`Packages`字段中新增你的Group 48 | 2. 运行 `hack/update-generated-protobuf.sh` 49 | 50 | 译者在V1.5.2试验的时候,改好了所有代码之后,直接执行`hack/update-all.sh`即可。 51 | 52 | 记得在文件types.generated.go中写上 `package premierleague`和`package {version}`,不能是空文件。 53 | 54 | ## Client (optional): 55 | 把你的Group添加到client package中,方法如下: 56 | 1. 新建`pkg/client/unversioned/.go`,定义一个group client interface,然后实现该client。 可以参考pkg/client/unversioned/extensions.go 57 | 58 | 2. 在`pkg/client/unversioned/client.go`中新增你的group client interface ,并提供方法来获取该interface。 可以参考如何添加Extensions group的。 59 | 60 | 3. 最后,如果需要kubectl支持你的group,需要修改`pkg/kubectl/cmd/util/factory.go` -------------------------------------------------------------------------------- /apiserver/(29)resourceVersion机制.md: -------------------------------------------------------------------------------- 1 | # resourceVersion机制 2 | 3 | ## 作用 4 | resourceVersion:标识一个object的internal version,Client端可以依据resourceVersion来判断该obj是否已经发生了变化。 5 | Client端不能忽视resourceVersion值,而且必须不加修改地回传给Server端。 6 | 客户端不应该假设resourceVersion在命名空间,不同类型的资源或不同的服务器上有意义。 7 | 有关更多详细信息,请参阅[并发控制](https://github.com/kubernetes/community/tree/master/contributors/devel#concurrency-control-and-consistency) 8 | 9 | Kubernetes利用resourceVersion的概念来实现乐观的并发。 10 | 所有Kubernetes资源都有一个“resourceVersion”字段作为其元数据的一部分。 11 | 当一个记录即将更新时,会根据预先保存的值检查版本,如果不匹配,则更新会失败,并显示StatusConflict(HTTP状态码409)。 12 | 13 | 14 | ## 来源 15 | resourceVersion的值是怎么来的? 我们从`kubectl create`命令开始研究。 16 | 17 | ### etcdhelper 18 | 可以知道`kubectl create`会调用etcdhelp的Create(),见/pkg/storage/etcd/etcd_helper.go,其流程分析如下: 19 | 1. 根据用户传入的信息调用etcdClient的Set()在etcd中创建对象,并得到返回信息response。 具体用法参考[etcdClient](https://github.com/coreos/etcd/tree/master/client) 20 | 2. response中信息会包含etcd数据库此时最新的index,此值就是该obj的resourceversion 21 | 3. 把response中信息解析绑定到obj中 22 | 23 | ```go 24 | // Implements storage.Interface. 25 | /*kubectl create -f xx.yaml触发此函数*/ 26 | func (h *etcdHelper) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error { 27 | trace := util.NewTrace("etcdHelper::Create " + getTypeName(obj)) 28 | defer trace.LogIfLong(250 * time.Millisecond) 29 | if ctx == nil { 30 | glog.Errorf("Context is nil") 31 | } 32 | key = h.prefixEtcdKey(key) 33 | data, err := runtime.Encode(h.codec, obj) 34 | trace.Step("Object encoded") 35 | if err != nil { 36 | return err 37 | } 38 | /* 39 | ==>/pkg/storage/etcd/api_object_versioner.go 40 | ==>func (a APIObjectVersioner) ObjectResourceVersion 41 | */ 42 | if version, err := h.versioner.ObjectResourceVersion(obj); err == nil && version != 0 { 43 | return errors.New("resourceVersion may not be set on objects to be created") 44 | } 45 | trace.Step("Version checked") 46 | 47 | startTime := time.Now() 48 | opts := etcd.SetOptions{ 49 | TTL: time.Duration(ttl) * time.Second, 50 | PrevExist: etcd.PrevNoExist, 51 | } 52 | response, err := h.etcdKeysAPI.Set(ctx, key, string(data), &opts) 53 | /* 54 | 创建成功之后, 55 | 返回的Response: &{create {Key: /registry/resourcequotas/default/quota, CreatedIndex: 30, ModifiedIndex: 30, TTL: 0} 30} 56 | 57 | 对应的etcd内容 58 | # etcdctl -o extended --endpoints 172.17.0.2:2379 get /registry/resourcequotas/default/quota 59 | Key: /registry/resourcequotas/default/quota 60 | Created-Index: 30 61 | Modified-Index: 30 62 | TTL: 0 63 | Index: 30 64 | 65 | {"kind":"ResourceQuota","apiVersion":"v1","metadata":{"name":"quota","namespace":"default","uid":"2b7ba689-eb27-11e7-b9d2-080027e58fc6","creationTimestamp":"2017-12-27T16:58:32Z"},"spec":{"hard":{"services":"5"}},"status":{}} 66 | */ 67 | trace.Step("Object created") 68 | metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime) 69 | if err != nil { 70 | return toStorageErr(err, key, 0) 71 | } 72 | if out != nil { 73 | if _, err := conversion.EnforcePtr(out); err != nil { 74 | panic("unable to convert output object to pointer") 75 | } 76 | /* 77 | 把response中信息解析绑定到obj中 78 | */ 79 | _, _, err = h.extractObj(response, err, out, false, false) 80 | } 81 | return err 82 | } 83 | ``` 84 | 85 | 以namespace default为例子 86 | ```shell 87 | [root@fqhnode01 ~]# kubectl get ns default -o yaml 88 | apiVersion: v1 89 | kind: Namespace 90 | metadata: 91 | creationTimestamp: 2017-12-27T16:15:08Z 92 | name: default 93 | resourceVersion: "13" 94 | selfLink: /api/v1/namespacesdefault 95 | uid: 1b723730-eb21-11e7-ba17-080027e58fc6 96 | spec: 97 | finalizers: 98 | - kubernetes 99 | status: 100 | phase: Active 101 | 102 | [root@fqhnode01 ~]# etcdctl -o extended --endpoints 172.17.0.2:2379 get /registry/namespaces/default 103 | Key: /registry/namespaces/default 104 | Created-Index: 13 105 | Modified-Index: 13 106 | TTL: 0 107 | Index: 30 108 | 109 | {"kind":"Namespace","apiVersion":"v1","metadata":{"name":"default","uid":"1b723730-eb21-11e7-ba17-080027e58fc6","creationTimestamp":"2017-12-27T16:15:08Z"},"spec":{"finalizers":["kubernetes"]},"status":{"phase":"Active"}} 110 | ``` 111 | 可以发现resourceVersion的值不是直接记录在etcd中该obj的信息上的,而是该obj在etcd中的`Modified-Index`。 112 | 113 | ### extractObj 114 | extractObj()会把response中信息解析绑定到obj中 115 | ```go 116 | func (h *etcdHelper) extractObj(response *etcd.Response, inErr error, objPtr runtime.Object, ignoreNotFound, prevNode bool) (body string, node *etcd.Node, err error) { 117 | if response != nil { 118 | if prevNode { 119 | node = response.PrevNode 120 | } else { 121 | //没有记录的前值,直接node = response.Node 122 | node = response.Node 123 | } 124 | } 125 | if inErr != nil || node == nil || len(node.Value) == 0 { 126 | if ignoreNotFound { 127 | v, err := conversion.EnforcePtr(objPtr) 128 | if err != nil { 129 | return "", nil, err 130 | } 131 | v.Set(reflect.Zero(v.Type())) 132 | return "", nil, nil 133 | } else if inErr != nil { 134 | return "", nil, inErr 135 | } 136 | return "", nil, fmt.Errorf("unable to locate a value on the response: %#v", response) 137 | } 138 | body = node.Value 139 | out, gvk, err := h.codec.Decode([]byte(body), nil, objPtr) 140 | if err != nil { 141 | return body, nil, err 142 | } 143 | if out != objPtr { 144 | return body, nil, fmt.Errorf("unable to decode object %s into %v", gvk.String(), reflect.TypeOf(objPtr)) 145 | } 146 | // being unable to set the version does not prevent the object from being extracted 147 | /* 148 | 无法通过设置version的方式,来阻止该object被提取 149 | ==>/kubernetes-1.5.2/pkg/storage/etcd/api_object_versioner.go 150 | ==>func (a APIObjectVersioner) UpdateObject(obj runtime.Object, resourceVersion uint64) 151 | 这里是把node.ModifiedIndex作为该obj的新的resourceVersion 152 | */ 153 | _ = h.versioner.UpdateObject(objPtr, node.ModifiedIndex) 154 | return body, node, err 155 | } 156 | ``` 157 | 158 | ### 设置metadata 159 | UpdateObject()会负责调用Accessor的SetResourceVersion() 160 | ```go 161 | // UpdateObject implements Versioner 162 | func (a APIObjectVersioner) UpdateObject(obj runtime.Object, resourceVersion uint64) error { 163 | accessor, err := meta.Accessor(obj) 164 | if err != nil { 165 | return err 166 | } 167 | versionString := "" 168 | if resourceVersion != 0 { 169 | versionString = strconv.FormatUint(resourceVersion, 10) 170 | } 171 | /* 172 | ==>/pkg/api/meta/meta.go 173 | ==>func (a genericAccessor) SetResourceVersion(version string) 174 | */ 175 | accessor.SetResourceVersion(versionString) 176 | return nil 177 | } 178 | 179 | func (a genericAccessor) SetResourceVersion(version string) { 180 | //设置一个obj中元数据的resourceVersion值 181 | *a.resourceVersion = version 182 | } 183 | ``` 184 | 185 | 最后,如果在yaml文件中指定了resourceVersion的值,是会被忽略的,在`func (h *etcdHelper) Create`中返回的response还是会包含etcd中的`Modified-Index` 186 | 187 | 188 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package,register 18 | // +groupName=premierleague.k8s.io 19 | // +k8s:openapi-gen=true 20 | package premierleague // import "k8s.io/kubernetes/pkg/apis/premierleague" 21 | 22 | /* 23 | curl http://192.168.56.101:8080/apis/premierleague.k8s.io/v1 24 | */ 25 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/install/install.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package install installs the experimental API group, making it available as 18 | // an option to all of the API encoding/decoding machinery. 19 | package install 20 | 21 | import ( 22 | "k8s.io/kubernetes/pkg/apimachinery/announced" 23 | "k8s.io/kubernetes/pkg/apis/premierleague" 24 | "k8s.io/kubernetes/pkg/apis/premierleague/v1" 25 | "k8s.io/kubernetes/pkg/util/sets" 26 | ) 27 | 28 | func init() { 29 | if err := announced.NewGroupMetaFactory( 30 | &announced.GroupMetaFactoryArgs{ 31 | GroupName: premierleague.GroupName, 32 | VersionPreferenceOrder: []string{v1.SchemeGroupVersion.Version}, 33 | ImportPrefix: "k8s.io/kubernetes/pkg/apis/premierleague", 34 | //RootScopedKinds are resources that are not namespaced. nil is allow 35 | RootScopedKinds: sets.NewString(""), 36 | AddInternalObjectsToScheme: premierleague.AddToScheme, 37 | }, 38 | announced.VersionToSchemeFunc{ 39 | v1.SchemeGroupVersion.Version: v1.AddToScheme, 40 | }, 41 | ).Announce().RegisterAndEnable(); err != nil { 42 | panic(err) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/registry.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package premierleague 18 | 19 | import ( 20 | "k8s.io/kubernetes/pkg/api" 21 | "k8s.io/kubernetes/pkg/api/unversioned" 22 | "k8s.io/kubernetes/pkg/runtime" 23 | ) 24 | 25 | // GroupName is the group name use in this package 26 | // 改1 27 | const GroupName = "premierleague.k8s.io" 28 | 29 | // SchemeGroupVersion is group version used to register these objects 30 | var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} 31 | 32 | // Kind takes an unqualified kind and returns a Group qualified GroupKind 33 | func Kind(kind string) unversioned.GroupKind { 34 | return SchemeGroupVersion.WithKind(kind).GroupKind() 35 | } 36 | 37 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 38 | func Resource(resource string) unversioned.GroupResource { 39 | return SchemeGroupVersion.WithResource(resource).GroupResource() 40 | } 41 | 42 | var ( 43 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 44 | AddToScheme = SchemeBuilder.AddToScheme 45 | ) 46 | 47 | // 改2 48 | func addKnownTypes(scheme *runtime.Scheme) error { 49 | scheme.AddKnownTypes(SchemeGroupVersion, 50 | &api.ListOptions{}, 51 | &api.DeleteOptions{}, 52 | &api.ExportOptions{}, 53 | 54 | &Match{}, 55 | &MatchList{}, 56 | ) 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/types.generated.go: -------------------------------------------------------------------------------- 1 | package premierleague 2 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package premierleague 18 | 19 | import ( 20 | "k8s.io/kubernetes/pkg/api" 21 | "k8s.io/kubernetes/pkg/api/unversioned" 22 | ) 23 | 24 | // +genclient=true 25 | 26 | // Match contains two teams 27 | // Status will add in the future 28 | type Match struct { 29 | unversioned.TypeMeta `json:",inline"` 30 | // +optional 31 | api.ObjectMeta `json:"metadata,omitempty"` 32 | 33 | // Specification of the desired two teams. 34 | // +optional 35 | Spec MatchSpec `json:"spec,omitempty"` 36 | } 37 | 38 | type MatchList struct { 39 | unversioned.TypeMeta `json:",inline"` 40 | // +optional 41 | unversioned.ListMeta `json:"metadata,omitempty"` 42 | 43 | //Items is the list of Match 44 | Items []Match `json:"items"` 45 | } 46 | 47 | type MatchSpec struct { 48 | // +optional 49 | Host string `json:"host,omitempty"` 50 | // +optional 51 | Guest string `json:"guest,omitempty"` 52 | // matchSelector unversioned.LabelSelector `json:"matchSelector"` 53 | } 54 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/v1/conversion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | "k8s.io/kubernetes/pkg/runtime" 21 | ) 22 | 23 | func addConversionFuncs(scheme *runtime.Scheme) error { 24 | // Add non-generated conversion functions 25 | return scheme.AddConversionFuncs() 26 | } 27 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/v1/defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | "k8s.io/kubernetes/pkg/runtime" 21 | ) 22 | 23 | func addDefaultingFuncs(scheme *runtime.Scheme) error { 24 | return scheme.AddDefaultingFuncs() 25 | } 26 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package,register 18 | // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/premierleague 19 | // +groupName=premierleague.k8s.io 20 | // +k8s:openapi-gen=true 21 | // +k8s:defaulter-gen=TypeMeta 22 | package v1 // import "k8s.io/kubernetes/pkg/apis/premierleague/v1" 23 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/v1/registry.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | "k8s.io/kubernetes/pkg/api/unversioned" 21 | "k8s.io/kubernetes/pkg/api/v1" 22 | "k8s.io/kubernetes/pkg/runtime" 23 | versionedwatch "k8s.io/kubernetes/pkg/watch/versioned" 24 | ) 25 | 26 | // GroupName is the group name use in this package 27 | const GroupName = "premierleague.k8s.io" 28 | 29 | // SchemeGroupVersion is group version used to register these objects 30 | var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: "v1"} 31 | 32 | var ( 33 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs, addConversionFuncs) 34 | AddToScheme = SchemeBuilder.AddToScheme 35 | ) 36 | 37 | // Adds the list of known types to api.Scheme. 38 | func addKnownTypes(scheme *runtime.Scheme) error { 39 | scheme.AddKnownTypes(SchemeGroupVersion, 40 | &v1.ListOptions{}, 41 | &v1.DeleteOptions{}, 42 | &v1.ExportOptions{}, 43 | 44 | &Match{}, 45 | &MatchList{}, 46 | ) 47 | //会注册watchevent,否则后面该资源无法提供watch接口,所有的Versiond的registry都会调用 versionedwatch.AddToGroupVersion 48 | versionedwatch.AddToGroupVersion(scheme, SchemeGroupVersion) 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/v1/types.generated.go: -------------------------------------------------------------------------------- 1 | package v1 2 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/v1/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | "k8s.io/kubernetes/pkg/api/unversioned" 21 | "k8s.io/kubernetes/pkg/api/v1" 22 | ) 23 | 24 | // +genclient=true 25 | 26 | // Match contains two teams 27 | // Status will add in the future 28 | type Match struct { 29 | unversioned.TypeMeta `json:",inline"` 30 | // opt 表示是可选类型(还有require类型) 31 | // +optional 32 | v1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 33 | 34 | // Specification of the desired two teams. 35 | // +optional 36 | Spec MatchSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` 37 | } 38 | 39 | type MatchList struct { 40 | unversioned.TypeMeta `json:",inline"` 41 | // +optional 42 | unversioned.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 43 | 44 | // req 表示数组、切片 45 | //Items is the list of Match 46 | Items []Match `json:"items" protobuf:"bytes,2,rep,name=items"` 47 | } 48 | 49 | type MatchSpec struct { 50 | // +optional 51 | Host string `json:"host,omitempty" protobuf:"bytes,1,opt,name=host"` 52 | // +optional 53 | Guest string `json:"guest,omitempty" protobuf:"bytes,2,opt,name=guest"` 54 | } 55 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague/validation/validation.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package validation 18 | 19 | import ( 20 | apivalidation "k8s.io/kubernetes/pkg/api/validation" 21 | "k8s.io/kubernetes/pkg/apis/premierleague" 22 | "k8s.io/kubernetes/pkg/util/validation/field" 23 | ) 24 | 25 | var ValidateMatchName = apivalidation.NameIsDNSSubdomain 26 | 27 | func ValidateMatch(obj *premierleague.Match) field.ErrorList { 28 | allErrs := apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateMatchName, field.NewPath("metadata")) 29 | allErrs = append(allErrs, ValidateMatchSpec(&obj.Spec, field.NewPath("spec"))...) 30 | return allErrs 31 | } 32 | 33 | func ValidateMatchUpdate(update, old *premierleague.Match) field.ErrorList { 34 | allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) 35 | allErrs = append(allErrs, ValidateMatchSpec(&update.Spec, field.NewPath("spec"))...) 36 | return allErrs 37 | } 38 | 39 | // Validates given match spec. 40 | func ValidateMatchSpec(spec *premierleague.MatchSpec, fldPath *field.Path) field.ErrorList { 41 | //根据前面Type的定义来判断Spec的有效性 42 | allErrs := field.ErrorList{} 43 | if spec.Host == "" { 44 | allErrs = append(allErrs, field.Required(fldPath.Child("Host"), "at least 1 Host is required")) 45 | } 46 | if spec.Guest == "" { 47 | allErrs = append(allErrs, field.Required(fldPath.Child("Guest"), "at least 1 Guest is required")) 48 | } 49 | return allErrs 50 | } 51 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-registry-premierleague/match/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package match provides Registry interface and it's RESTStorage 18 | // implementation for storing premierleague.Match api objects. 19 | package match // import "k8s.io/kubernetes/pkg/registry/premierleague/match" 20 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-registry-premierleague/match/etcd/etcd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package etcd 17 | 18 | import ( 19 | "k8s.io/kubernetes/pkg/api" 20 | premierleague "k8s.io/kubernetes/pkg/apis/premierleague" 21 | "k8s.io/kubernetes/pkg/registry/cachesize" 22 | "k8s.io/kubernetes/pkg/registry/generic" 23 | "k8s.io/kubernetes/pkg/registry/generic/registry" 24 | "k8s.io/kubernetes/pkg/registry/premierleague/match" 25 | "k8s.io/kubernetes/pkg/runtime" 26 | "k8s.io/kubernetes/pkg/storage" 27 | ) 28 | 29 | // MatchStorage includes storage for matchs and all sub resources 30 | type MatchStorage struct { 31 | Match *REST 32 | // add status in the future 33 | } 34 | 35 | type REST struct { 36 | *registry.Store 37 | } 38 | 39 | func NewStorage(opts generic.RESTOptions) MatchStorage { 40 | matchREST := NewREST(opts) 41 | 42 | return MatchStorage{ 43 | Match: matchREST, 44 | } 45 | } 46 | 47 | // NewREST returns a RESTStorage object that will work against matchs. 48 | func NewREST(opts generic.RESTOptions) *REST { 49 | prefix := "/" + opts.ResourcePrefix 50 | newListFunc := func() runtime.Object { 51 | return &premierleague.MatchList{} 52 | } 53 | 54 | storageInterface, dFunc := opts.Decorator( 55 | opts.StorageConfig, 56 | // implementate it 57 | cachesize.GetWatchCacheSizeByResource(cachesize.Matchs), 58 | &premierleague.Match{}, 59 | prefix, 60 | // implementate it 61 | match.Strategy, 62 | newListFunc, 63 | storage.NoTriggerPublisher, 64 | ) 65 | 66 | store := ®istry.Store{ 67 | NewFunc: func() runtime.Object { return &premierleague.Match{} }, 68 | NewListFunc: newListFunc, 69 | KeyRootFunc: func(ctx api.Context) string { 70 | return registry.NamespaceKeyRootFunc(ctx, prefix) 71 | }, 72 | KeyFunc: func(ctx api.Context, id string) (string, error) { 73 | return registry.NamespaceKeyFunc(ctx, prefix, id) 74 | }, 75 | ObjectNameFunc: func(obj runtime.Object) (string, error) { 76 | return obj.(*premierleague.Match).Name, nil 77 | }, 78 | //注意这里的MatchMatch,前面是固定格式,后面是资源Match 79 | PredicateFunc: match.MatchMatch, 80 | QualifiedResource: premierleague.Resource("matchs"), 81 | EnableGarbageCollection: opts.EnableGarbageCollection, 82 | DeleteCollectionWorkers: opts.DeleteCollectionWorkers, 83 | 84 | CreateStrategy: match.Strategy, 85 | UpdateStrategy: match.Strategy, 86 | DeleteStrategy: match.Strategy, 87 | 88 | Storage: storageInterface, 89 | DestroyFunc: dFunc, 90 | } 91 | 92 | return &REST{store} 93 | } 94 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-registry-premierleague/match/strategy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package match 18 | 19 | import ( 20 | "fmt" 21 | 22 | "k8s.io/kubernetes/pkg/api" 23 | "k8s.io/kubernetes/pkg/apis/premierleague" 24 | "k8s.io/kubernetes/pkg/apis/premierleague/validation" //待实现 25 | "k8s.io/kubernetes/pkg/fields" 26 | "k8s.io/kubernetes/pkg/labels" 27 | "k8s.io/kubernetes/pkg/registry/generic" 28 | "k8s.io/kubernetes/pkg/runtime" 29 | apistorage "k8s.io/kubernetes/pkg/storage" 30 | "k8s.io/kubernetes/pkg/util/validation/field" 31 | ) 32 | 33 | // matchStrategy implements behavior for Matchs. 34 | type matchStrategy struct { 35 | runtime.ObjectTyper 36 | api.NameGenerator 37 | } 38 | 39 | // Strategy is the default logic that applies when creating and updating Match 40 | // objects via the REST API. 41 | var Strategy = matchStrategy{api.Scheme, api.SimpleNameGenerator} 42 | 43 | // NamespaceScoped is true for match. 44 | func (matchStrategy) NamespaceScoped() bool { 45 | return true 46 | } 47 | 48 | func (matchStrategy) PrepareForCreate(ctx api.Context, obj runtime.Object) { 49 | match := obj.(*premierleague.Match) 50 | match.Generation = 1 51 | } 52 | 53 | // Validate validates a new match. 54 | func (matchStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { 55 | match := obj.(*premierleague.Match) 56 | return validation.ValidateMatch(match) 57 | } 58 | 59 | // Canonicalize normalizes the object after validation. 60 | func (matchStrategy) Canonicalize(obj runtime.Object) { 61 | } 62 | 63 | // AllowCreateOnUpdate is false for match. 64 | func (matchStrategy) AllowCreateOnUpdate() bool { 65 | return false 66 | } 67 | 68 | func (matchStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) { 69 | } 70 | 71 | // ValidateUpdate is the default update validation for an end user. 72 | func (matchStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { 73 | return validation.ValidateMatchUpdate(obj.(*premierleague.Match), old.(*premierleague.Match)) 74 | } 75 | 76 | func (matchStrategy) AllowUnconditionalUpdate() bool { 77 | return true 78 | } 79 | 80 | // MatchToSelectableFields returns a field set that represents the object. 81 | func MatchToSelectableFields(match *premierleague.Match) fields.Set { 82 | return generic.ObjectMetaFieldsSet(&match.ObjectMeta, true) 83 | } 84 | 85 | // MatchMatch is the filter used by the generic etcd backend to route 86 | // watch events from etcd to clients of the apiserver only interested in specific 87 | // labels/fields. 88 | func MatchMatch(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate { 89 | return apistorage.SelectionPredicate{ 90 | Label: label, 91 | Field: field, 92 | GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 93 | match, ok := obj.(*premierleague.Match) 94 | if !ok { 95 | return nil, nil, fmt.Errorf("given object is not a match.") 96 | } 97 | return labels.Set(match.ObjectMeta.Labels), MatchToSelectableFields(match), nil 98 | }, 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/pkg-registry-premierleague/rest/storage_premierleague.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rest 18 | 19 | import ( 20 | "k8s.io/kubernetes/pkg/api/rest" 21 | "k8s.io/kubernetes/pkg/apis/premierleague" 22 | premierleagueapiv1 "k8s.io/kubernetes/pkg/apis/premierleague/v1" 23 | "k8s.io/kubernetes/pkg/genericapiserver" 24 | matchetcd "k8s.io/kubernetes/pkg/registry/premierleague/match/etcd" 25 | ) 26 | 27 | type RESTStorageProvider struct{} 28 | 29 | var _ genericapiserver.RESTStorageProvider = &RESTStorageProvider{} 30 | 31 | func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource genericapiserver.APIResourceConfigSource, restOptionsGetter genericapiserver.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { 32 | apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(premierleague.GroupName) 33 | 34 | if apiResourceConfigSource.AnyResourcesForVersionEnabled(premierleagueapiv1.SchemeGroupVersion) { 35 | //k8s中所有的资源都会注册到 VersionedResourcesStorageMap 中,后面设计路由path时会用到VersionedResourcesStorageMap 36 | apiGroupInfo.VersionedResourcesStorageMap[premierleagueapiv1.SchemeGroupVersion.Version] = p.v1Storage(apiResourceConfigSource, restOptionsGetter) 37 | apiGroupInfo.GroupMeta.GroupVersion = premierleagueapiv1.SchemeGroupVersion 38 | } 39 | 40 | return apiGroupInfo, true 41 | } 42 | 43 | func (p RESTStorageProvider) v1Storage(apiResourceConfigSource genericapiserver.APIResourceConfigSource, restOptionsGetter genericapiserver.RESTOptionsGetter) map[string]rest.Storage { 44 | version := premierleagueapiv1.SchemeGroupVersion 45 | 46 | storage := map[string]rest.Storage{} 47 | //注册资源matchs 48 | if apiResourceConfigSource.ResourceEnabled(version.WithResource("matchs")) { 49 | matchStorage := matchetcd.NewStorage(restOptionsGetter(premierleague.Resource("matchs"))) 50 | storage["matchs"] = matchStorage.Match 51 | } 52 | return storage 53 | } 54 | 55 | func (p RESTStorageProvider) GroupName() string { 56 | return premierleague.GroupName 57 | } 58 | -------------------------------------------------------------------------------- /apiserver/定制一个API之ApiServer篇/定制一个API之ApiServer.md: -------------------------------------------------------------------------------- 1 | # 定制一个API之ApiServer 2 | 3 | **Table of Contents** 4 | 5 | - [创建你的Group Package](#创建你的group-package) 6 | - [实现你的Rest Storage](#实现你的rest-storage) 7 | - [Enabled你的Group和Resources](#enabled你的group和resources) 8 | - [自动生成代码工具](#自动生成代码工具) 9 | - [End](#end) 10 | 11 | 12 | 13 | 直奔主题,在ApiServer端添加一个自己的GVK,且被被外面访问到 14 | 15 | ## 环境 16 | 本文是基于kubernetes V1.5.2进行, go1.7.5 linux/amd64 17 | 18 | 19 | ## 创建你的Group Package 20 | 首先定义一个`type Match struct`对象,Group名为`premierleague.k8s.io`,具体代码见[pkg-apis-premierleague](https://github.com/Kevin-fqh/learning-k8s-source-code/tree/master/apiserver/定制一个API之ApiServer篇/pkg-apis-premierleague) 21 | 22 | 参考[Adding-an-API-Group](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/apiserver/(28)Adding%20an%20API%20Group.md) 23 | 24 | 在这里要同时定义internal和versiond两个版本的数据结构,完成资源的注册。 25 | 26 | 要注意的是两个`types.generated.go`文件要写上package,不能直接是空文件去运行脚本。 27 | 28 | 完成数据结构的定义之后,不要急着去运行脚本生成代码,我们完成ApiServer端所有的逻辑之后,再去执行生成代码脚本。 29 | 30 | ## 实现你的Rest Storage 31 | 目录位置是`pkg/registry/premierleague`,具体代码见[pkg-registry-premierleague](https://github.com/Kevin-fqh/learning-k8s-source-code/tree/master/apiserver/定制一个API之ApiServer篇/pkg-registry-premierleague) 32 | 33 | ## Enabled你的Group和Resources 34 | 修改[pkg/master/master.go](https://github.com/kubernetes/kubernetes/blob/v1.8.0-alpha.2/pkg/master/master.go#L381) 35 | ```go 36 | import ( 37 | premierleagueapiv1 "k8s.io/kubernetes/pkg/apis/premierleague/v1" 38 | premierleaguerest "k8s.io/kubernetes/pkg/registry/premierleague/rest" 39 | ) 40 | 41 | restStorageProviders := []genericapiserver.RESTStorageProvider{ 42 | ...... 43 | ...... 44 | premierleaguerest.RESTStorageProvider{}, 45 | } 46 | 47 | 48 | func DefaultAPIResourceConfigSource() *genericapiserver.ResourceConfig { 49 | ret := genericapiserver.NewResourceConfig() 50 | ret.EnableVersions( 51 | ... 52 | ... 53 | premierleagueapiv1.SchemeGroupVersion, 54 | ) 55 | 56 | ret.EnableResources( 57 | ... 58 | ... 59 | premierleagueapiv1.SchemeGroupVersion.WithResource("matchs"), 60 | ) 61 | 62 | return ret 63 | } 64 | ``` 65 | 66 | 最后别忘了修改`pkg/master/import_known_versions.go`,初始化你的Group 67 | ```go 68 | // These imports are the API groups the API server will support. 69 | import ( 70 | "fmt" 71 | 72 | _ "k8s.io/kubernetes/pkg/api/install" 73 | ... 74 | ... 75 | _ "k8s.io/kubernetes/pkg/apis/premierleague/install" 76 | ) 77 | ``` 78 | 好了,至此Apiserver端的代码已经完成了。 79 | 80 | ## 自动生成代码工具 81 | 1. `/kubernetes-1.5.2/hack/lib/init.sh`中增加你的GV 82 | ```shell 83 | # 这地方注意格式,前面都是有一个空格的,只有最后一个没空格!!!否则会出问题!!! 84 | KUBE_AVAILABLE_GROUP_VERSIONS="${KUBE_AVAILABLE_GROUP_VERSIONS:-\ 85 | v1 \ 86 | apps/v1beta1 \ 87 | authentication.k8s.io/v1beta1 \ 88 | authorization.k8s.io/v1beta1 \ 89 | autoscaling/v1 \ 90 | batch/v1 \ 91 | batch/v2alpha1 \ 92 | certificates.k8s.io/v1alpha1 \ 93 | extensions/v1beta1 \ 94 | imagepolicy.k8s.io/v1alpha1 \ 95 | policy/v1beta1 \ 96 | rbac.authorization.k8s.io/v1alpha1 \ 97 | storage.k8s.io/v1beta1 \ 98 | premierleague.k8s.io/v1\ 99 | }" 100 | ``` 101 | 102 | 2. `/kubernetes-1.5.2/cmd/libs/go2idl/go-to-protobuf/protobuf/cmd.go`中增加你的GV 103 | ```go 104 | func New() *Generator { 105 | ... 106 | ... 107 | Packages: strings.Join([]string{ 108 | ... 109 | ... 110 | `k8s.io/kubernetes/pkg/apis/storage/v1beta1`, 111 | `k8s.io/kubernetes/pkg/apis/premierleague/v1`, 112 | }, ","), 113 | ... 114 | ... 115 | } 116 | ``` 117 | 118 | 3. `/kubernetes-1.5.2/cmd/libs/go2idl/conversion-gen/main.go`中增加Group和GV 119 | ```go 120 | // Custom args. 121 | customArgs := &generators.CustomArgs{ 122 | ExtraPeerDirs: []string{ 123 | ... 124 | ... 125 | "k8s.io/kubernetes/pkg/apis/premierleague", 126 | "k8s.io/kubernetes/pkg/apis/premierleague/v1", 127 | }, 128 | SkipUnsafe: false, 129 | } 130 | ``` 131 | 132 | 4. `/kubernetes-1.5.2/cmd/libs/go2idl/client-gen/main.go`中增加Group 133 | ```go 134 | var ( 135 | 136 | inputVersions = flag.StringSlice("input", []string{ 137 | "api/", 138 | ... 139 | ... 140 | "premierleague/", 141 | }, 142 | ... 143 | ... 144 | ) 145 | ``` 146 | 147 | 自动生成代码工具会基于`+genclient=true` `+k8s:deepcopy-gen=package,register`这些注释来进行工作。 148 | 149 | ## End 150 | 执行脚本来生成转换函数和DeepCopy等代码,其中在`hack/update-all.sh`这一步,如果不是kubernetes源码不是用git clone下载的话,在生成doc文档的时候会报错退出,不过前面的步骤已经生成我们需要的代码。 151 | ```shell 152 | $ make clean && make generated_files 153 | 154 | $ hack/update-all.sh 155 | 156 | $ make 157 | ``` 158 | 159 | 效果如下,注意的是,这里的路由路径是`premierleague.k8s.io`,如果希望不带`k8s.io`,需要另作处理 160 | ```shell 161 | [root@fqhnode01 premierleagueClient]# curl http://192.168.56.101:8080/apis/premierleague.k8s.io 162 | { 163 | "kind": "APIGroup", 164 | "apiVersion": "v1", 165 | "name": "premierleague.k8s.io", 166 | "versions": [ 167 | { 168 | "groupVersion": "premierleague.k8s.io/v1", 169 | "version": "v1" 170 | } 171 | ], 172 | "preferredVersion": { 173 | "groupVersion": "premierleague.k8s.io/v1", 174 | "version": "v1" 175 | }, 176 | "serverAddressByClientCIDRs": null 177 | } 178 | 179 | [root@fqhnode01 premierleagueClient]# curl http://192.168.56.101:8080/apis/premierleague.k8s.io/v1 180 | { 181 | "kind": "APIResourceList", 182 | "apiVersion": "v1", 183 | "groupVersion": "premierleague.k8s.io/v1", 184 | "resources": [ 185 | { 186 | "name": "matchs", 187 | "namespaced": true, 188 | "kind": "Match" 189 | } 190 | ] 191 | } 192 | 193 | [root@fqhnode01 premierleagueClient]# curl http://192.168.56.101:8080/apis/premierleague.k8s.io/v1/matchs 194 | { 195 | "kind": "MatchList", 196 | "apiVersion": "premierleague.k8s.io/v1", 197 | "metadata": { 198 | "selfLink": "/apis/premierleague.k8s.io/v1/matchs", 199 | "resourceVersion": "29" 200 | }, 201 | "items": null 202 | } 203 | ``` 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /docker/(01)Cgroups用法.md: -------------------------------------------------------------------------------- 1 | # Cgroups用法 2 | 3 | **Table of Contents** 4 | 5 | - [创建并挂载一个hierarchy](#创建并挂载一个hierarchy) 6 | - [在新建的hierarchy上的cgroup的根节点中扩展出两个子cgroup](#在新建的hierarchy上的cgroup的根节点中扩展出两个子cgroup) 7 | - [通过subsystem来限制cgroup中进程的资源](#通过subsystem来限制cgroup中进程的资源) 8 | - [Docker中的Cgroups](#docker中的cgroups) 9 | 10 | 11 | 12 | 环境说明: 13 | ```shell 14 | [root@fqhnode01 ~]# uname -a 15 | Linux fqhnode01 3.10.0-514.6.1.el7.x86_64 #1 SMP Wed Jan 18 13:06:36 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 16 | [root@fqhnode01 ~]# docker version 17 | Client: 18 | Version: 1.13.1 19 | ``` 20 | 21 | ## 创建并挂载一个hierarchy 22 | ```shell 23 | [root@fqhnode01 home]# mkdir cgroups-test 24 | [root@fqhnode01 home]# mount -t cgroup -o none,name=cgroups-test cgroups-test ./cgroups-test 25 | [root@fqhnode01 home]# ll cgroups-test/ 26 | 总用量 0 27 | -rw-r--r--. 1 root root 0 11月 1 12:31 cgroup.clone_children 28 | --w--w--w-. 1 root root 0 11月 1 12:31 cgroup.event_control 29 | -rw-r--r--. 1 root root 0 11月 1 12:31 cgroup.procs 30 | -r--r--r--. 1 root root 0 11月 1 12:31 cgroup.sane_behavior 31 | -rw-r--r--. 1 root root 0 11月 1 12:31 notify_on_release 32 | -rw-r--r--. 1 root root 0 11月 1 12:31 release_agent 33 | -rw-r--r--. 1 root root 0 11月 1 12:31 tasks 34 | ``` 35 | 36 | ## 在新建的hierarchy上的cgroup的根节点中扩展出两个子cgroup 37 | ```shell 38 | [root@fqhnode01 cgroups-test]# cd cgroups-test/ 39 | [root@fqhnode01 cgroups-test]# mkdir cgroup-1 cgroup-2 40 | [root@fqhnode01 cgroups-test]# ls 41 | cgroup-1 cgroup.clone_children cgroup.procs notify_on_release tasks 42 | cgroup-2 cgroup.event_control cgroup.sane_behavior release_agent 43 | [root@fqhnode01 cgroups-test]# 44 | [root@fqhnode01 cgroups-test]# tree 45 | . 46 | ├── cgroup-1 47 | │ ├── cgroup.clone_children 48 | │ ├── cgroup.event_control 49 | │ ├── cgroup.procs 50 | │ ├── notify_on_release 51 | │ └── tasks 52 | ├── cgroup-2 53 | │ ├── cgroup.clone_children 54 | │ ├── cgroup.event_control 55 | │ ├── cgroup.procs 56 | │ ├── notify_on_release 57 | │ └── tasks 58 | ├── cgroup.clone_children 59 | ├── cgroup.event_control 60 | ├── cgroup.procs 61 | ├── cgroup.sane_behavior 62 | ├── notify_on_release 63 | ├── release_agent 64 | └── tasks 65 | 66 | 2 directories, 17 files 67 | ``` 68 | 可以看出,在一个cgroup的目录下创建文件夹时,系统Kernel会自动把该文件夹标记为这个cgroup的子cgroup,它们会继承父cgroup的属性。 69 | 70 | 几个文件功能说明: 71 | - tasks,标识该cgroup下面的进程ID。如果把一个进程ID写入了tasks文件,就是把该进程加入了这个cgroup中。 72 | - cgroup.procs 是树中当前cgroup中的进程组ID。如果是根节点,会包含所有的进程组ID。 73 | - cgroup.clone_children,默认值为0。如果设置为1,子cgroup会继承父cgroup的cpuset配置。 74 | 75 | ## 往一个cgroup中添加和移动进程 76 | 1. 首先,查看一个进程目前所处的cgroup 77 | ```shell 78 | [root@fqhnode01 cgroup-1]# echo $$ 79 | 1019 80 | [root@fqhnode01 cgroup-1]# cat /proc/1019/cgroup 81 | 12:name=cgroups-test:/ 82 | 11:devices:/ 83 | 10:memory:/ 84 | 9:hugetlb:/ 85 | 8:cpuset:/ 86 | 7:blkio:/ 87 | 6:cpuacct,cpu:/ 88 | 5:freezer:/ 89 | 4:net_prio,net_cls:/ 90 | 3:perf_event:/ 91 | 2:pids:/ 92 | 1:name=systemd:/user.slice/user-0.slice/session-1.scope 93 | ``` 94 | 从`name=cgroups-test:/`可以看到当前进程($$)位于hierarchy cgroups-test:/的根节点上。 95 | 96 | 2. 把进程移动到节点cgroup-1/中 97 | ```shell 98 | [root@fqhnode01 cgroups-test]# cd cgroup-1/ 99 | [root@fqhnode01 cgroup-1]# cat tasks 100 | [root@fqhnode01 cgroup-1]# 101 | [root@fqhnode01 cgroup-1]# cat cgroup.procs 102 | [root@fqhnode01 cgroup-1]# 103 | ``` 104 | 可以看到目前节点cgroup-1的tasks文件是空的。 105 | ```shell 106 | [root@fqhnode01 cgroup-1]# echo $$ >> tasks 107 | [root@fqhnode01 cgroup-1]# cat /proc/1019/cgroup 108 | 12:name=cgroups-test:/cgroup-1 109 | 11:devices:/ 110 | 10:memory:/ 111 | 9:hugetlb:/ 112 | 8:cpuset:/ 113 | 7:blkio:/ 114 | 6:cpuacct,cpu:/ 115 | 5:freezer:/ 116 | 4:net_prio,net_cls:/ 117 | 3:perf_event:/ 118 | 2:pids:/ 119 | 1:name=systemd:/user.slice/user-0.slice/session-1.scope 120 | 121 | [root@fqhnode01 cgroup-1]# cat cgroup.procs 122 | 1019 123 | 1436 124 | [root@fqhnode01 cgroup-1]# cat tasks 125 | 1019 126 | 1437 127 | ``` 128 | 可以看到,当前进程1019已经加入到cgroups-test:/cgroup-1中了。 129 | 130 | 需要注意的是,到目前为止,上面创建的hierarchy并没有关联到任何的subsystem,所以还是没有办法通过上面的cgroup节点来限制一个进程的资源占用。 131 | 132 | ## 通过subsystem来限制cgroup中进程的资源 133 | 系统已经默认为每一个subsystem创建了一个hierarchy,以memory的hierarchy为例子 134 | ```shell 135 | # mount |grep memory 136 | cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) 137 | ``` 138 | 可以看到memory subsystem的hierarchy的目录是`/sys/fs/cgroup/memory`。 139 | 这个目录就跟上面我们创建的目录类似,只不过它是系统默认创建的,已经和memory subsystem关联起来,可以限制其名下进程占用的内存。 140 | 141 | 1. 安装stress工具 142 | ```shell 143 | yum install stress.x86_64 144 | ``` 145 | 146 | 2. 不使用Cgroups限制,启动一个占用200M内存的stress进程 147 | ```shell 148 | [root@fqhnode01 memory]# stress --vm-bytes 200m --vm-keep -m 1 149 | ``` 150 | 测试结果如下,本机内存为2G,所以2G*10%=200M: 151 | ```shell 152 | # top 153 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 154 | 1851 root 20 0 212060 204796 132 R 96.3 10.0 0:18.27 stress 155 | ``` 156 | 157 | 3. 使用Cgroups进行限制,仅允许使用100M内存 158 | 159 | 首先参照前面步骤,在memory 的根节点下新建一个cgroup节点test-limit-memory 160 | ```shell 161 | [root@fqhnode01 memory]# cd /sys/fs/cgroup/memory 162 | [root@fqhnode01 memory]# mkdir test-limit-memory 163 | ``` 164 | 然后,设置该cgroup节点的最大内存占用为100M 165 | ```shell 166 | [root@fqhnode01 memory]# cd test-limit-memory/ 167 | [root@fqhnode01 test-limit-memory]# cat memory.limit_in_bytes 168 | 9223372036854771712 169 | [root@fqhnode01 test-limit-memory]# echo "100m" >> ./memory.limit_in_bytes 170 | [root@fqhnode01 test-limit-memory]# 171 | [root@fqhnode01 test-limit-memory]# cat memory.limit_in_bytes 172 | 104857600 173 | ``` 174 | 然后,把当前进程移动到这个cgroup节点test-limit-memory中 175 | ```shell 176 | [root@fqhnode01 test-limit-memory]# cat tasks 177 | [root@fqhnode01 test-limit-memory]# 178 | [root@fqhnode01 test-limit-memory]# echo $$ >> tasks 179 | [root@fqhnode01 test-limit-memory]# cat tasks 180 | 1019 181 | 1878 182 | ``` 183 | 最后,再次运行`stress --vm-bytes 200m --vm-keep -m 1`,测试结果如下: 184 | ```shell 185 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 186 | 1881 root 20 0 212060 87924 132 R 37.0 4.3 0:12.05 stress 187 | ``` 188 | 观察显示内存占用最大值只能达到5%,也就是100m。 成功地使用Cgroups把stree进程的内存占用限制到了100m的范围之内。 189 | 190 | 最后说一下上面创建的`/sys/fs/cgroup/memory/test-limit-memory`文件会在重启之后被系统自动删除。 191 | 如果要删除一个cgroup,用umount命令。 192 | 193 | ## Docker中的Cgroups 194 | ```shell 195 | [root@fqhnode01 memory]# docker run -itd -e is_leader=true -e node_name=aaa -m 128m 98c2ef0aa9bb 196 | e19acd747074941e9062f2beb5163927b097296f31b7a0317ecb93387cc16466 197 | ``` 198 | docker 会自动在memory hierarchy的目录下新建一个cgroup节点 199 | ```shell 200 | [root@fqhnode01 e19acd747074941e9062f2beb5163927b097296f31b7a0317ecb93387cc16466]# pwd 201 | /sys/fs/cgroup/memory/docker/e19acd747074941e9062f2beb5163927b097296f31b7a0317ecb93387cc16466 202 | [root@fqhnode01 e19acd747074941e9062f2beb5163927b097296f31b7a0317ecb93387cc16466]# cat memory.limit_in_bytes 203 | 134217728 204 | [root@fqhnode01 e19acd747074941e9062f2beb5163927b097296f31b7a0317ecb93387cc16466]# cat memory.usage_in_bytes 205 | 66355200 206 | ``` 207 | 208 | - memory.limit_in_bytes 内存限制 209 | - memory.usage_in_bytes 该cgroup中的进程已经使用的memory 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /docker/(02)docker搭建ceph环境.md: -------------------------------------------------------------------------------- 1 | # Docker搭建ceph环境 2 | 3 | ## 基础环境配置 4 | ``` 5 | # ifconfig 6 | 7 | enp0s8: flags=4163 mtu 1500 8 | inet 192.168.56.101 netmask 255.255.255.0 broadcast 192.168.56.255 9 | inet6 fe80::2407:e89c:1adc:1272 prefixlen 64 scopeid 0x20 10 | ether 08:00:27:4f:f7:1d txqueuelen 1000 (Ethernet) 11 | RX packets 6053 bytes 482631 (471.3 KiB) 12 | RX errors 0 dropped 0 overruns 0 frame 0 13 | TX packets 5583 bytes 5674853 (5.4 MiB) 14 | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 15 | ``` 16 | 下载镜像 17 | ``` 18 | docker pull ceph/daemon:tag-build-master-jewel-ubuntu-16.04 19 | yum install ceph-common 20 | ``` 21 | 22 | ## 运行monitor 23 | ``` 24 | docker run -d --privileged --net=host -v /etc/ceph:/etc/ceph -v /var/lib/ceph/:/var/lib/ceph/ -e MON_IP=192.168.56.101 -e CEPH_PUBLIC_NETWORK=192.168.56.0/24 ceph/daemon:tag-build-master-jewel-ubuntu-16.04 mon 25 | ``` 26 | 27 | ## 修改/etc/ceph/ceph.conf 28 | 新增最后两个属性 29 | ``` 30 | [global] 31 | fsid = 7d885ac4-b748-40d0-992b-394b9f0322b8 32 | mon initial members = fqhnode01 33 | mon host = 192.168.56.101 34 | auth cluster required = cephx 35 | auth service required = cephx 36 | auth client required = cephx 37 | public network = 192.168.56.0/24 38 | cluster network = 192.168.56.0/24 39 | osd journal size = 100 40 | osd max object name len = 256 41 | osd max object namespace len = 64 42 | ``` 43 | ## 新建OSD目录 44 | ``` 45 | cd /var/lib/osd 46 | [root@fqhnode01 osd]# mkdir ceph-0 47 | [root@fqhnode01 osd]# mkdir ceph-1 48 | [root@fqhnode01 osd]# mkdir ceph-2 49 | ``` 50 | 执行三次docker exec ceph osd create 51 | ``` 52 | [root@fqhnode01 osd]# docker exec 4489d6bc0cc7 ceph osd create 53 | 0 54 | [root@fqhnode01 osd]# docker exec 4489d6bc0cc7 ceph osd create 55 | 1 56 | [root@fqhnode01 osd]# docker exec 4489d6bc0cc7 ceph osd create 57 | 2 58 | ``` 59 | ## 起osd 60 | ``` 61 | docker run -d --privileged --net=host -v /etc/ceph:/etc/ceph -v /var/lib/ceph/:/var/lib/ceph/ -e MON_IP=192.168.56.101 -e CEPH_PUBLIC_NETWORK=192.168.56.0/24 ceph/daemon:tag-build-master-jewel-ubuntu-16.04 osd_directory 62 | ``` 63 | ``` 64 | [root@fqhnode01 osd]# ceph -s 65 | cluster 7d885ac4-b748-40d0-992b-394b9f0322b8 66 | health HEALTH_ERR 67 | 64 pgs are stuck inactive for more than 300 seconds 68 | 64 pgs stuck inactive 69 | 64 pgs stuck unclean 70 | too few PGs per OSD (21 < min 30) 71 | monmap e1: 1 mons at {fqhnode01=192.168.56.101:6789/0} 72 | election epoch 3, quorum 0 fqhnode01 73 | osdmap e8: 3 osds: 3 up, 3 in 74 | flags sortbitwise,require_jewel_osds 75 | pgmap v9: 64 pgs, 1 pools, 0 bytes data, 0 objects 76 | 0 kB used, 0 kB / 0 kB avail 77 | 64 creating 78 | ``` 79 | ## 消除错误 80 | 由于默认只有一个rbd pool,所以我们需要调整rbd pool的pg数。由于我们有3个OSD,而副本数目前为3,所以3*100/3=100,取2的次方为128。 81 | ``` 82 | [root@fqhnode01 osd]# ceph osd pool set rbd pg_num 128 83 | set pool 0 pg_num to 128 84 | [root@fqhnode01 osd]# ceph osd pool set rbd pgp_num 128 85 | set pool 0 pgp_num to 128 86 | [root@fqhnode01 osd]# ceph -s 87 | cluster 7d885ac4-b748-40d0-992b-394b9f0322b8 88 | health HEALTH_ERR 89 | 128 pgs are stuck inactive for more than 300 seconds 90 | 128 pgs degraded 91 | 128 pgs stuck inactive 92 | 128 pgs stuck unclean 93 | 128 pgs undersized 94 | monmap e1: 1 mons at {fqhnode01=192.168.56.101:6789/0} 95 | election epoch 3, quorum 0 fqhnode01 96 | osdmap e14: 3 osds: 3 up, 3 in 97 | flags sortbitwise,require_jewel_osds 98 | pgmap v23: 128 pgs, 1 pools, 0 bytes data, 0 objects 99 | 30439 MB used, 18670 MB / 49110 MB avail 100 | 128 undersized+degraded+peered 101 | ``` 102 | 系统默认按主机来对副本进行存储的,而我们的系统为单机环境,所以把rbd pool的副本数调整为1。 103 | ``` 104 | ceph osd pool set rbd size 1 105 | set pool 0 size to 1 106 | [root@fqhnode01 osd]# ceph -s 107 | cluster 7d885ac4-b748-40d0-992b-394b9f0322b8 108 | health HEALTH_OK 109 | monmap e1: 1 mons at {fqhnode01=192.168.56.101:6789/0} 110 | election epoch 3, quorum 0 fqhnode01 111 | osdmap e16: 3 osds: 3 up, 3 in 112 | flags sortbitwise,require_jewel_osds 113 | pgmap v28: 128 pgs, 1 pools, 0 bytes data, 0 objects 114 | 30440 MB used, 18669 MB / 49110 MB avail 115 | 128 active+clean 116 | 117 | [root@fqhnode01 osd]# rados lspools 118 | rbd 119 | ``` -------------------------------------------------------------------------------- /docker/(04)docker命令行.md: -------------------------------------------------------------------------------- 1 | # docker命令行 2 | 3 | **Table of Contents** 4 | 5 | - [命令行分类](#命令行分类) 6 | - [docker常用命令](#docker常用命令) 7 | - [参考](#参考) 8 | 9 | 10 | 11 | ## 版本说明 12 | v1.13.1 13 | 14 | ## 命令行分类 15 | docker现在采用也是"github.com/spf13/cobra"来管理其命令结构,和k8s是一样的。 16 | 17 | 来看看docker的命令行分类,见/moby-1.13.1/cli/command/commands/commands.go 18 | ```go 19 | // AddCommands adds all the commands from cli/command to the root command 20 | func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) { 21 | cmd.AddCommand( 22 | /* 23 | 根据资源划分,添加各种子命令到root command下 24 | */ 25 | node.NewNodeCommand(dockerCli), 26 | service.NewServiceCommand(dockerCli), 27 | swarm.NewSwarmCommand(dockerCli), 28 | secret.NewSecretCommand(dockerCli), 29 | container.NewContainerCommand(dockerCli), 30 | image.NewImageCommand(dockerCli), 31 | system.NewSystemCommand(dockerCli), 32 | container.NewRunCommand(dockerCli), 33 | image.NewBuildCommand(dockerCli), 34 | network.NewNetworkCommand(dockerCli), 35 | hide(system.NewEventsCommand(dockerCli)), 36 | registry.NewLoginCommand(dockerCli), 37 | registry.NewLogoutCommand(dockerCli), 38 | registry.NewSearchCommand(dockerCli), 39 | system.NewVersionCommand(dockerCli), 40 | volume.NewVolumeCommand(dockerCli), 41 | hide(system.NewInfoCommand(dockerCli)), 42 | hide(container.NewAttachCommand(dockerCli)), 43 | hide(container.NewCommitCommand(dockerCli)), 44 | hide(container.NewCopyCommand(dockerCli)), 45 | hide(container.NewCreateCommand(dockerCli)), 46 | hide(container.NewDiffCommand(dockerCli)), 47 | hide(container.NewExecCommand(dockerCli)), 48 | hide(container.NewExportCommand(dockerCli)), 49 | hide(container.NewKillCommand(dockerCli)), 50 | hide(container.NewLogsCommand(dockerCli)), 51 | hide(container.NewPauseCommand(dockerCli)), 52 | hide(container.NewPortCommand(dockerCli)), 53 | hide(container.NewPsCommand(dockerCli)), 54 | hide(container.NewRenameCommand(dockerCli)), 55 | hide(container.NewRestartCommand(dockerCli)), 56 | hide(container.NewRmCommand(dockerCli)), 57 | hide(container.NewStartCommand(dockerCli)), 58 | hide(container.NewStatsCommand(dockerCli)), 59 | hide(container.NewStopCommand(dockerCli)), 60 | hide(container.NewTopCommand(dockerCli)), 61 | hide(container.NewUnpauseCommand(dockerCli)), 62 | hide(container.NewUpdateCommand(dockerCli)), 63 | hide(container.NewWaitCommand(dockerCli)), 64 | hide(image.NewHistoryCommand(dockerCli)), 65 | hide(image.NewImagesCommand(dockerCli)), 66 | hide(image.NewImportCommand(dockerCli)), 67 | hide(image.NewLoadCommand(dockerCli)), 68 | hide(image.NewPullCommand(dockerCli)), 69 | hide(image.NewPushCommand(dockerCli)), 70 | hide(image.NewRemoveCommand(dockerCli)), 71 | hide(image.NewSaveCommand(dockerCli)), 72 | hide(image.NewTagCommand(dockerCli)), 73 | hide(system.NewInspectCommand(dockerCli)), 74 | stack.NewStackCommand(dockerCli), 75 | stack.NewTopLevelDeployCommand(dockerCli), 76 | checkpoint.NewCheckpointCommand(dockerCli), 77 | plugin.NewPluginCommand(dockerCli), 78 | ) 79 | 80 | } 81 | 82 | /* 83 | 在设置了 环境变量“DOCKER_HIDE_LEGACY_COMMANDS”的情况下 84 | func hide的功能就是给入参 cmd 增加两个属性:Hidden和Aliases 85 | */ 86 | func hide(cmd *cobra.Command) *cobra.Command { 87 | if os.Getenv("DOCKER_HIDE_LEGACY_COMMANDS") == "" { 88 | return cmd 89 | } 90 | cmdCopy := *cmd 91 | cmdCopy.Hidden = true 92 | cmdCopy.Aliases = []string{} 93 | return &cmdCopy 94 | } 95 | ``` 96 | 97 | docker 之前版本的命令管理比较复杂的,容易混淆。 98 | 在v1.13.1中进行了重构,旧的语法仍然支持。 99 | 100 | func AddCommands将命令按照逻辑分组到`management commands`中,以下就是docker中的顶级命令(注意这里都是`单数`的): 101 | ``` 102 | Management Commands: 103 | container Manage containers 104 | image Manage images 105 | network Manage networks 106 | node Manage Swarm nodes 107 | plugin Manage plugins 108 | secret Manage Docker secrets 109 | service Manage services 110 | stack Manage Docker stacks 111 | swarm Manage Swarm 112 | system Manage Docker 113 | volume Manage volumes 114 | ``` 115 | 116 | 每一个管理命令有一套类似的`子命令`,他们负责执行操作: 117 | ``` 118 | 子命令 用途 119 | ls 获取的列表 120 | rm 移除 121 | inspect 检阅 122 | ``` 123 | 这也就是说:`docker image ls` 效果等同于 `docker images`。 124 | 125 | 重构之后,一些平时使用频繁的`子命令`仍然留在顶层。 126 | 默认情况下,所有的顶级命令也会显示出来。 127 | 可以设置`DOCKER_HIDE_LEGACY_COMMANDS环境变量`来只显示顶级命令,这就是`func hide`函数的作用。 128 | 但即便如此`docker --help`依然会显示所有的顶级命令和管理命令。 129 | 130 | 最后可以通过下述命令来强制只显示管理命令 131 | ```shell 132 | DOCKER_HIDE_LEGACY_COMMANDS=true docker --help 133 | ``` 134 | 可以和`docker --help`命令的效果进行对比下 135 | 136 | ## docker常用命令 137 | 最后列举一下docker常用命令 138 | ``` 139 | 1.12 1.13 用途 140 | 141 | attach container attach 附加到一个运行的容器 142 | build image build 从一个Dockerfile构建镜像 143 | commit container commit 从一个容器的修改创建一个新的镜像 144 | cp container cp 在容器与本地文件系统之间复制文件/文件夹 145 | create container create 创建新的容器 146 | diff container diff 检阅一个容器文件系统的修改 147 | events system events 获取服务器的实时时间 148 | exec container exec 在运行的容器内执行命令 149 | export container export 打包一个容器文件系统到tar文件 150 | history image history 展示镜像历史信息 151 | images image ls 展示镜像列表 152 | import image import 用tar文件导入并创建镜像文件 153 | info system info 展示整个系统信息 154 | inspect container inspect 展示一个容器/镜像或者任务的底层信息 155 | kill container kill 终止一个或者多个运行中的容器 156 | load image load 从tar文件或者标准输入载入镜像 157 | login login 登录Docker registry 158 | logout logout 从Docker registry登出 159 | logs container logs 获取容器的日志 160 | network network 管理Docker网络 161 | node node 管理Docker Swarm节点 162 | pause container pause 暂停一个或者多个容器的所有进程 163 | port container port 展示容器的端口映射 164 | ps container ls 展示容器列表 165 | pull image pull 从某个registry拉取镜像或者仓库 166 | push image push 推送镜像或者仓库到某个registry 167 | rename container rename 重命名容器 168 | restart container restart 重启容器 169 | rm container rm 移除一个或多个容器 170 | rmi image rm 移除一个或多个镜像 171 | run container run 运行一个新的容器 172 | save image save 打包一个或多个镜像到tar文件(默认是到标准输出) 173 | search search 在Docker Hub搜索镜像 174 | service service 管理Docker services 175 | start container start 启动一个或者多个容器 176 | stats container stats 获取容器的实时资源使用统计 177 | stop container stop 停止一个或多个运行容器 178 | swarm swarm 管理Docker Swarm 179 | tag image tag 标记一个镜像到仓库 180 | top container top 展示容器运行进程 181 | unpause container unpause 解除暂停一个或多个容器的所有进程 182 | update container update 更新一个或多个容器的配置 183 | version version 显示Docker版本信息 184 | volume volume 管理Docker volumes 185 | wait container wait 阻塞直到容器停止,然后打印退出代码 186 | ``` 187 | 188 | ## 参考 189 | [Docker 1.13 管理命令](http://dockone.io/article/2059) 190 | 191 | [Docker 1.13 Management Commands](https://blog.couchbase.com/docker-1-13-management-commands/) -------------------------------------------------------------------------------- /docker/(13)标准化容器执行引擎-runc.md: -------------------------------------------------------------------------------- 1 | ## 标准化容器执行引擎-runc 2 | 3 | **Table of Contents** 4 | 5 | - [容器的标准OCF](#容器的标准OCF) 6 | - [容器标准包(bundle)](#容器标准包-bundle) 7 | - [运行时状态](#运行时状态) 8 | - [runc启动一个容器](#runc启动一个容器) 9 | - [使用docker来生成一个容器的rootfs](#使用docker来生成一个容器的rootfs) 10 | - [spec配置](#spec配置) 11 | - [参考](#参考) 12 | 13 | 14 | 15 | ## 版本说明 16 | * runc version 1.0.0-rc2 17 | * containerd version 0.2.4 18 | 19 | runc和containerd的版本也要匹配,否则在解析config.json的时候也容易出问题 20 | 编译都使用go-1.8.4,好像低于1.8的容易出问题,比如grpc连接不上 21 | 22 | ## 容器的标准OCF 23 | 容器标准化的目标:操作标准化、内容无关、基础设施无关。 runc是OCF的其中一个具体实现,下面从runc的角度来介绍OCF,这并不是OCF的全部标准 24 | 25 | ### 容器标准包(bundle) 26 | 现在的runc下的bundle包含了两个部分,rootfs目录必须与config.json文件同时存在容器目录最顶层。 27 | * /rootfs目录,根文件系统目录,包含了容器执行所需的必要环境依赖,如/bin、/var、/lib、/dev、/usr等目录及相应文件。 28 | 29 | * 配置文件config.json,包含了基本配置和容器运行时的相关配置,主要包括 30 | * ociVersion, runc的版本 31 | * platform,宿主机信息 32 | * process,容器启动时的初始化进程,通过args传递命令,用户uid/gid绑定,rlimit配置、capabilities设置 33 | * root,rootfs目录的路径,读写权限设置 34 | * hostname 35 | * mounts,挂载信息设置 36 | * hooks,钩子,设置容器运行前和停止后执行的自定义脚本 37 | * linux,针对Linux的特性支持的诸如namespace设置、cgroups资源限额、设备权限配置、apparmor配置项目录、selinux标记以及seccomp配置。 38 | ```json 39 | { 40 | "ociVersion": "1.0.0-rc2-dev", 41 | "platform": { 42 | "os": "linux", 43 | "arch": "amd64" 44 | }, 45 | "process": { 46 | "terminal": false, //false表示以后台模式运行 47 | "user": { 48 | "uid": 0, //都是0,表示进入容器后是root用户 49 | "gid": 0 50 | }, 51 | "args": [ 52 | "sleep", "10" 53 | ], 54 | "env": [ 55 | "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 56 | "TERM=xterm" 57 | ], 58 | "cwd": "/", //工作目录为/ 59 | "capabilities": [ //使用白名单制度 60 | "CAP_AUDIT_WRITE", //允许写入审计日志 61 | "CAP_KILL", //允许发送信号 62 | "CAP_NET_BIND_SERVICE" //允许绑定socket到网络端口 63 | ], 64 | "rlimits": [ 65 | { 66 | "type": "RLIMIT_NOFILE", 67 | "hard": 1024, 68 | "soft": 1024 69 | } 70 | ], 71 | "noNewPrivileges": true 72 | }, 73 | "root": { 74 | "path": "rootfs", 75 | "readonly": true 76 | }, 77 | "hostname": "runc", 78 | "mounts": [ 79 | { 80 | "destination": "/proc", 81 | "type": "proc", 82 | "source": "proc" 83 | }, 84 | ... 85 | ... 86 | ], 87 | "hooks": {}, 88 | "linux": { 89 | "resources": { 90 | "devices": [ 91 | { 92 | "allow": false, 93 | "access": "rwm" 94 | } 95 | ] 96 | }, 97 | "namespaces": [ 98 | { 99 | "type": "pid" 100 | }, 101 | { 102 | "type": "network" 103 | }, 104 | { 105 | "type": "ipc" 106 | }, 107 | { 108 | "type": "uts" 109 | }, 110 | { 111 | "type": "mount" 112 | } 113 | ], 114 | "maskedPaths": [ 115 | "/proc/kcore", 116 | "/proc/latency_stats", 117 | "/proc/timer_list", 118 | "/proc/timer_stats", 119 | "/proc/sched_debug", 120 | "/sys/firmware" 121 | ], 122 | "readonlyPaths": [ 123 | "/proc/asound", 124 | "/proc/bus", 125 | "/proc/fs", 126 | "/proc/irq", 127 | "/proc/sys", 128 | "/proc/sysrq-trigger" 129 | ] 130 | } 131 | } 132 | ``` 133 | 134 | ### 运行时状态 135 | OCF要求容器把自身运行时的状态持久化到磁盘中,这样便于外部的其他工具对此信息使用。 136 | 该运行时状态以JSON格式编码存储。 137 | 推荐把运行时状态的json文件存储在临时文件系统中以便系统重启后会自动移除。 138 | 139 | runc以state.json文件保存一个容器运行时的状态信息,默认存放在/run/runc/{containerID}/state.json。 140 | 141 | ## runc启动一个容器 142 | runc是基于容器标准包(bundle)来启动一个容器,官方步骤如下: 143 | 144 | ### 使用docker来生成一个容器的rootfs 145 | ```shell 146 | # create the top most bundle directory 147 | mkdir /mycontainer 148 | cd /mycontainer 149 | 150 | # create the rootfs directory 151 | mkdir rootfs 152 | 153 | # export busybox via Docker into the rootfs directory 154 | docker export $(docker create busybox:latest) | tar -C rootfs -xvf - 155 | 156 | # 现在可以从docker中删除该容器,我们的目的仅是创建一个容器的rootfs 157 | docker rm {containerID} 158 | ``` 159 | 至此,OCF标准的rootfs目录创建好了。 使用Docker只是为了获取rootfs目录的方便,runc的运行本身不依赖Docker。 160 | 161 | ### spec配置 162 | 关于spec详细信息,可以查看[runtime-spec](https://github.com/opencontainers/runtime-spec)。 163 | 164 | containerd中也有一个具体的例子,见[containerd-bundle](https://github.com/containerd/containerd/blob/v0.2.3/docs/bundle.md) 165 | ```shell 166 | docker-runc spec 167 | ``` 168 | runc spec 命令会自动生成一个config.json文件,可以自行设置。 169 | 如果不设置,直接start一个容器,那么效果就是`docker exec -it {containerID} sh`。 170 | 171 | 修改config.json中的process部分,以sleep命令启动一个容器 172 | ```json 173 | "process": { 174 | "terminal": false, 175 | "user": { 176 | "uid": 0, 177 | "gid": 0 178 | }, 179 | "args": [ 180 | "sleep", "5" 181 | ], 182 | "env": [ 183 | "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 184 | "TERM=xterm" 185 | ], 186 | "cwd": "/", 187 | "capabilities": [ 188 | "CAP_AUDIT_WRITE", 189 | "CAP_KILL", 190 | "CAP_NET_BIND_SERVICE" 191 | ], 192 | "rlimits": [ 193 | { 194 | "type": "RLIMIT_NOFILE", 195 | "hard": 1024, 196 | "soft": 1024 197 | } 198 | ], 199 | "noNewPrivileges": true 200 | }, 201 | ``` 202 | 现在,可以启动容器了 203 | ```shell 204 | runc create mycontainerid 205 | 206 | # view the container is created and in the "created" state 207 | runc list 208 | 209 | # start the process inside the container 210 | runc start mycontainerid 211 | 212 | # after 5 seconds view that the container has exited and is now in the stopped state 213 | runc list 214 | 215 | # now delete the container 216 | runc delete mycontainerid 217 | ``` 218 | 219 | 最后可以用systemd来管理一个runc容器,实现在其退出后自动重启。 220 | 221 | ## 参考 222 | [runc官网](https://github.com/opencontainers/runc/tree/v1.0.0-rc2) 223 | 224 | [runC介绍](http://www.infoq.com/cn/articles/docker-standard-container-execution-engine-runc) 225 | 226 | [opencontainers/runtime-spec](https://github.com/opencontainers/specs) -------------------------------------------------------------------------------- /docker/(17)Aufs.md: -------------------------------------------------------------------------------- 1 | # Aufs 2 | 3 | 例子参考于[自己动手写docker](https://github.com/xianlubird/mydocker),演示aufu+Cow技术 4 | ## 环境 5 | ```shell 6 | root@fqhnode:~/aufs# uname -a 7 | Linux fqhnode 4.4.0-87-generic #110-Ubuntu SMP Tue Jul 18 12:55:35 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 8 | ``` 9 | 10 | ## 创建好各个layer 11 | 目录结构如下,其中mnt是挂载点 12 | ```shell 13 | [root@fqhnode01 aufs]# tree ./ 14 | ./ 15 | ├── mnt 16 | ├── ro-layer-1 17 | │ └── ro-1.txt 18 | ├── ro-layer-2 19 | │ └── ro-2.txt 20 | └── rw-layer 21 | └── rw.txt 22 | ``` 23 | 24 | ## 联合挂载 25 | ```shell 26 | mount -t aufs -o dirs=./rw-layer/:./ro-layer-2:./ro-layer-1 none ./mnt/ 27 | ``` 28 | 效果如下: 29 | 1. 从用户的视角来看,文件系统将从./mnt开始,其实./mnt只是一个虚拟挂载点,里面并没有任何实质性数据。 30 | ```shell 31 | root@fqhnode:~/aufs# tree ./mnt/ 32 | ./mnt/ 33 | |-- ro-1.txt 34 | |-- ro-2.txt 35 | `-- rw.txt 36 | ``` 37 | 38 | 2. 在./mnt目录下新建一个文件new-file.txt 39 | ```shell 40 | root@fqhnode:~/aufs# tree ./ 41 | ./ 42 | |-- mnt 43 | | |-- new-file.txt 44 | | |-- ro-1.txt 45 | | |-- ro-2.txt 46 | | `-- rw.txt 47 | |-- ro-layer-1 48 | | `-- ro-1.txt 49 | |-- ro-layer-2 50 | | `-- ro-2.txt 51 | `-- rw-layer 52 | |-- new-file.txt 53 | `-- rw.txt 54 | ``` 55 | 56 | 3. 对./mnt目录下任何内容进行更改,系统都会在第一次更改的时候copy一份对应的ro-{n}.txt到目录rw-layer下,在副本下进行修改。 而不是直接修改ro-layer的数据。 57 | ```shell 58 | root@fqhnode:~/aufs# cat mnt/ro-1.txt 59 | I am ro-1 layer! 60 | new write! 61 | root@fqhnode:~/aufs# cat rw-layer/ro-1.txt 62 | I am ro-1 layer! 63 | new write! 64 | root@fqhnode:~/aufs# cat ro-layer-1/ro-1.txt 65 | I am ro-1 layer! 66 | ``` 67 | 68 | 69 | -------------------------------------------------------------------------------- /docker/(21)runc start命令.md: -------------------------------------------------------------------------------- 1 | # runc start命令 2 | 3 | **Table of Contents** 4 | 5 | - [startCommand](#startcommand) 6 | - [获取指定container](#获取指定container) 7 | - [container.Exec()](#containerexec) 8 | 9 | 10 | 11 | `runc start`只能启动状态处于`created`的容器。 12 | ## startCommand 13 | 1. 从context中获取id,再获取指定的容器 14 | 2. 启动处于Created状态的容器 15 | 16 | 见/runc-1.0.0-rc2/start.go 17 | ```go 18 | var startCommand = cli.Command{ 19 | Name: "start", 20 | Usage: "executes the user defined process in a created container", 21 | ArgsUsage: ` 22 | 23 | Where "" is your name for the instance of the container that you 24 | are starting. The name you provide for the container instance must be unique on 25 | your host.`, 26 | Description: `The start command executes the user defined process in a created container.`, 27 | Action: func(context *cli.Context) error { 28 | /* 29 | 获取指定的容器 30 | ==>/utils_linux.go 31 | ==>func getContainer(context *cli.Context) (libcontainer.Container, error) 32 | */ 33 | container, err := getContainer(context) 34 | if err != nil { 35 | return err 36 | } 37 | status, err := container.Status() 38 | if err != nil { 39 | return err 40 | } 41 | switch status { 42 | /* 43 | 仅仅会启动处于Created状态的容器 44 | */ 45 | case libcontainer.Created: 46 | /* 47 | ==>/libcontainer/container_linux.go 48 | ==>func (c *linuxContainer) Exec() 49 | */ 50 | return container.Exec() 51 | case libcontainer.Stopped: 52 | return fmt.Errorf("cannot start a container that has run and stopped") 53 | case libcontainer.Running: 54 | return fmt.Errorf("cannot start an already running container") 55 | default: 56 | return fmt.Errorf("cannot start a container in the %s state", status) 57 | } 58 | }, 59 | } 60 | ``` 61 | 62 | ## 获取指定container 63 | ```go 64 | // getContainer returns the specified container instance by loading it from state 65 | // with the default factory. 66 | /* 67 | 从容器文件夹中载入该容器的配置,生成libcontainer.Container对象 68 | */ 69 | func getContainer(context *cli.Context) (libcontainer.Container, error) { 70 | id := context.Args().First() 71 | if id == "" { 72 | return nil, errEmptyID 73 | } 74 | factory, err := loadFactory(context) 75 | if err != nil { 76 | return nil, err 77 | } 78 | return factory.Load(id) 79 | } 80 | ``` 81 | 82 | ## container.Exec() 83 | 以“只读”的方式打开FIFO管道,读取内容。 84 | 这同时也恢复之前处于阻塞状态的`runc Init`进程,Init进程会执行最后调用用户期待的cmd部分。 85 | ```go 86 | func (c *linuxContainer) Exec() error { 87 | c.m.Lock() 88 | defer c.m.Unlock() 89 | return c.exec() 90 | } 91 | 92 | func (c *linuxContainer) exec() error { 93 | path := filepath.Join(c.root, execFifoFilename) 94 | /* 95 | 以“只读”的方式打开FIFO管道,读取内容。 96 | 这同时也恢复之前处于阻塞状态的`runc Init`进程,Init进程会执行最后调用用户期待的cmd部分。 97 | 98 | func OpenFile(name string, flag int, perm FileMode) (file *File, err error) 99 | 以“指定文件权限和打开方式”来打开name文件或者create文件 100 | flag标识: 101 | O_RDONLY:只读模式(read-only) 102 | O_WRONLY:只写模式(write-only) 103 | O_RDWR:读写模式(read-write) 104 | 操作权限perm,除非创建文件时才需要指定,不需要创建新文件时可以将其设定为0 105 | 106 | os package的详细用法 107 | http://blog.csdn.net/chenbaoke/article/details/42494851 108 | */ 109 | f, err := os.OpenFile(path, os.O_RDONLY, 0) 110 | if err != nil { 111 | return newSystemErrorWithCause(err, "open exec fifo for reading") 112 | } 113 | defer f.Close() 114 | data, err := ioutil.ReadAll(f) 115 | if err != nil { 116 | return err 117 | } 118 | if len(data) > 0 { 119 | /* 120 | 如果读取到的data长度大于0,则读取到Create流程中最后写入的“0”,则删除FIFO管道文件 121 | */ 122 | os.Remove(path) 123 | return nil 124 | } 125 | return fmt.Errorf("cannot start an already running container") 126 | } 127 | ``` -------------------------------------------------------------------------------- /docker/(22)shell命令创建一个简单的容器.md: -------------------------------------------------------------------------------- 1 | # shell命令创建一个简单的容器 2 | 3 | ## 准备rootfs 4 | 有两个方法来准备rootfs,一个是利用docker命令,另外一个是自己下载安装一个文件系统编译安装。以busybox为例,我们用docker export提供的rootfs作为试验对象 5 | 6 | ## 验证rootfs的有效性 7 | ```shell 8 | root@fqhnode:~/fqhcontainer# ll 9 | drwxr-xr-x 13 root root 4096 Jan 9 11:39 buxybox/ 10 | 11 | root@fqhnode:~/fqhcontainer# chroot buxybox/ sh 12 | / # which date 13 | /bin/date 14 | / # date 15 | Wed Jan 10 08:31:18 UTC 2018 16 | / # which sh 17 | /bin/sh 18 | ``` 19 | 此时可以另起一个终端,用`pstree`命令来查看`sh进程`的根路径,对比在内部和外部看到的区别。 20 | 21 | ## 创建容器 22 | 1. 目录`outdir`用于容器内部和host主机的数据共享 23 | 2. pivot_root命令要求原来根目录的挂载点为private 24 | 3. --propagation private 是为了容器内部的挂载点都是私有,private 表示既不继承主挂载点中挂载和卸载操作,自身的挂载和卸载操作也不会反向传播到主挂载点中。 25 | 4. 查看`unshare`进程,可以发现其6个namesoace都已经和其父进程不一样了。所以下面再执行的命令其实已经处于另外一个namespace了 26 | 5. 如果在另外一个终端上查看hostname,会发现还是`fqhnode`,而不是刚刚设置的`aaa` 27 | 6. exec bash ,通过exec系统调用,执行容器内部的bash命令 28 | 7. `mount --bind ./buxybox/ ./buxybox/`的作用是在原地创建一个新的挂载点,这是pivot_root的要求 29 | 8. 把outdir和interdir挂载起来,实现容器内外的通信 30 | 9. mount -t proc none /proc之后需要umout掉老挂载点的信息 31 | ```shell 32 | root@fqhnode:~/fqhcontainer# mkdir outdir 33 | root@fqhnode:~/fqhcontainer# unshare --user --mount --ipc --pid --net --uts -r --fork --propagation private bash 34 | 35 | root@fqhnode:~/fqhcontainer# hostname aaa 36 | root@fqhnode:~/fqhcontainer# hostname 37 | aaa 38 | 39 | root@fqhnode:~/fqhcontainer# exec bash 40 | root@aaa:~/fqhcontainer# 41 | 42 | root@aaa:~/fqhcontainer# mkdir -p buxybox/old_root buxybox/interdir 43 | root@aaa:~/fqhcontainer# mount --bind ./buxybox/ ./buxybox/ 44 | 45 | root@aaa:~/fqhcontainer# mount --bind outdir/ ./buxybox/interdir/ 46 | 47 | root@aaa:~/fqhcontainer/buxybox# pivot_root ./ ./old_root/ 48 | root@aaa:~/fqhcontainer/buxybox# exec sh 49 | / # export PS1='root@$(hostname):$(pwd)# ' 50 | root@aaa:/# mount 51 | mount: no /proc/mounts 52 | 53 | root@aaa:/# mount -t proc none /proc 54 | root@aaa:/# mount 55 | /dev/sda1 on /old_root type ext4 (rw,relatime,errors=remount-ro,data=ordered) 56 | udev on /old_root/dev type devtmpfs (rw,nosuid,relatime,size=487892k,nr_inodes=121973,mode=755) 57 | devpts on /old_root/dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000) 58 | tmpfs on /old_root/dev/shm type tmpfs (rw,nosuid,nodev) 59 | mqueue on /old_root/dev/mqueue type mqueue (rw,relatime) 60 | hugetlbfs on /old_root/dev/hugepages type hugetlbfs (rw,relatime) 61 | ... 62 | ... 63 | none on /proc type proc (rw,nodev,relatime) 64 | 65 | root@aaa:/# umount -l /old_root 66 | root@aaa:/# mount 67 | /dev/sda1 on / type ext4 (rw,relatime,errors=remount-ro,data=ordered) 68 | /dev/sda1 on /interdir type ext4 (rw,relatime,errors=remount-ro,data=ordered) 69 | none on /proc type proc (rw,nodev,relatime) 70 | 71 | root@aaa:/# ps -ef 72 | PID USER TIME COMMAND 73 | 1 root 0:00 sh 74 | 65 root 0:00 ps -ef 75 | 76 | root@aaa:/# cd interdir/ 77 | root@aaa:/interdir# ls 78 | root@aaa:/interdir# 79 | root@aaa:/interdir# touch inter.txt 80 | ``` 81 | 此时可以在host主机外部的outdir看到对应的文件 82 | 83 | ## chroot和pivot_root的区别 84 | chroot - run command or interactive shell with special root directory 85 | 86 | pivot_root - change the root filesystem 87 | 88 | 看起来挺迷糊的。。。从效果上看,pivot_root会修改一个进程的`根目录、工作目录`;而chroot只会修改进程的工作目录,不会修改其根目录 89 | 90 | ### chroot 91 | 在一个终端运行`chroot`命令,查看该进程的根目录,可以发现是会输出host主机上的路径的`/root/fqhcontainer/buxybox` 92 | ```shell 93 | root@fqhnode:~/fqhcontainer# chroot buxybox/ sh 94 | / # 95 | ``` 96 | 在另一个终端进行查看 97 | ```shell 98 | root@fqhnode:~# pstree -l -p 99 | root@fqhnode:~# readlink /proc/16154/exe 100 | /root/fqhcontainer/buxybox/bin/sh 101 | ``` 102 | 103 | ## Namespace的系统调用 104 | clone: 创建一个新的进程并把他放到新的namespace中 105 | 106 | sents: 将当前进程加入到已有的namespace中 107 | 108 | unshare: 使当前进程退出指定类型的namespace,并加入到新创建的namespace(相当于创建并加入新的namespace) 109 | 110 | ## 参考 111 | [Cgroups, namespaces, and beyond: what are containers made from?](https://www.youtube.com/watch?v=sK5i-N34im8) 112 | 113 | [https://segmentfault.com/a/1190000006913509](https://segmentfault.com/a/1190000006913509) 114 | 115 | [Linux Namespace分析——mnt namespace的实现与应用](http://hustcat.github.io/namespace-implement-1/) 116 | 117 | [mount详解](http://www.jinbuguo.com/man/mount.html) 118 | 119 | 120 | -------------------------------------------------------------------------------- /etcd/cfssl证书.md: -------------------------------------------------------------------------------- 1 | # etcd使用cfssl证书 2 | 3 | **Table of Contents** 4 | 5 | - [cfssl安装](#cfssl安装) 6 | - [生成ca证书](#生成ca证书) 7 | - [server证书](#server证书) 8 | - [client证书](#client证书) 9 | - [etcd使用cfssl](#etcd使用cfssl) 10 | - [客户端到服务器通信](#客户端到服务器通信) 11 | - [对等通信](#对等通信) 12 | - [两个例子](#两个例子) 13 | 14 | 15 | 16 | 17 | etcd推荐使用cfssl工具来生成证书进行验证 18 | 19 | ## cfssl安装 20 | 21 | 以x86_64 Linux为例 22 | ``` 23 | mkdir ~/bin 24 | curl -s -L -o ~/bin/cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 25 | curl -s -L -o ~/bin/cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 26 | chmod +x ~/bin/{cfssl,cfssljson} 27 | export PATH=$PATH:~/bin 28 | ``` 29 | 离线安装的情况,直接把两个文件下载下来重命名即可 30 | 31 | ## 生成ca证书 32 | 33 | 生成ca的配置文件 34 | ``` 35 | mkdir ~/cfssl 36 | cd ~/cfssl 37 | cfssl print-defaults config > ca-config.json 38 | cfssl print-defaults csr > ca-csr.json 39 | ``` 40 | 三大证书类型介绍: 41 | ``` 42 | client certificate: is used to authenticate client by server. For example etcdctl, etcd proxy, fleetctl or docker clients. 43 | server certificate: is used by server and verified by client for server identity. For example docker server or kube-apiserver. 44 | peer certificate: is used by etcd cluster members as they communicate with each other in both ways. 45 | ``` 46 | 三大证书都由CA证书进行签发 47 | 48 | 其中ca-config.json中的expiry: 这个属性是指定证书的有效时间 49 | 50 | 然后执行命令生成CA证书 51 | ``` 52 | cfssl gencert -initca ca-csr.json | cfssljson -bare ca - 53 | ``` 54 | 会得到ca-key.pem、ca.csr、ca.ppem。 55 | 56 | 其中 Please keep ca-key.pem file in safe. 57 | This key allows to create any kind of certificates within your CA. 58 | 59 | csr证书在这里面用不到。至此,CA证书生成完毕,后面利用CA证书来生成server证书和client端的证书。 60 | 61 | ## server证书 62 | 生成server证书的配置文件 63 | ``` 64 | cfssl print-defaults csr > server.json 65 | ``` 66 | server.json的重要属性有: 67 | 68 | ``` 69 | ... 70 | "CN": "coreos1", 71 | "hosts": [ //只能通过在hosts声明的方式来进行访问 72 | "172.17.0.2", 73 | "c2ccacf259ed", 74 | "coreos1.local", 75 | "coreos1" 76 | ], 77 | ... 78 | ``` 79 | 以etcd为例子,后面可以通过 80 | `curl -L --cacert /root/openssl/dd_ca.crt https://c2ccacf259ed:4001/version`进行访问 81 | 82 | 执行命令生成server端证书 83 | 84 | ``` 85 | cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server.json | cfssljson -bare server 86 | ``` 87 | 得到server-key.pem 、server.csr 、server.pem三个文件 88 | 89 | ## client证书 90 | 91 | 生成client配置文件 92 | ``` 93 | cfssl print-defaults csr > client.json 94 | ``` 95 | client证书可以忽略hosts属性的设置,设置CN即可 96 | 97 | ``` 98 | ... 99 | "CN": "client", 100 | "hosts": [""], 101 | ... 102 | ``` 103 | 执行 104 | ``` 105 | cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client 106 | ``` 107 | 得到client-key.pem、client.csr、client.pem 108 | 109 | 至此所有证书生成完毕 110 | 111 | ## etcd使用cfssl 112 | 113 | etcd通过命令行标志或环境变量获取几个证书相关的配置选项: 114 | 115 | ### 客户端到服务器通信 116 | ``` 117 | --cert-file = :用于与etcd的SSL/TLS连接的证书。设置此选项时,advertise-client-urls可以使用HTTPS模式。 118 | --key-file = :证书的密钥。必须未加密。 119 | --client-cert-auth:设置此选项后,etcd将检查所有来自受信任CA签署的客户端证书的传入HTTPS请求,因此不提供有效客户端证书的请求将失败。 120 | --trusted-ca-file = :受信任的证书颁发机构。 121 | --auto-tls:对客户端的TLS连接使用自动生成的自签名证书。 122 | ``` 123 | 124 | ### 对等通信 125 | 对等通信--服务器到服务器/集群 126 | 对等选项的工作方式与客户端到服务器选项的工作方式相同: 127 | ``` 128 | --peer-cert-file = :用于对等体之间SSL / TLS连接的证书。这将用于侦听对等体地址以及向其他对等体发送请求。 129 | --peer-key-file = :证书的密钥。必须未加密。 130 | --peer-client-cert-auth:设置时,etcd将检查来自集群的所有传入对等请求,以获得由提供的CA签名的有效客户端证书。 131 | --peer-trusted-ca-file = :受信任的证书颁发机构。 132 | --peer-auto-tls:对对等体之间的TLS连接使用自动生成的自签名证书。 133 | ``` 134 | 如果提供客户端到服务器或对等体证书,则还必须设置密钥。所有这些配置选项也可通过环境变量ETCD_CA_FILE,ETCD_PEER_CA_FILE等获得。 135 | 136 | 137 | ### 两个例子 138 | (1)带client证书 139 | 140 | ``` 141 | 启动方式 142 | etcd -name etcd -data-dir /var/lib/etcd -advertise-client-urls=https://172.17.0.2:4001 -listen-client-urls=https://172.17.0.2:4001 -cert-file=/root/cfssl/server.pem -key-file=/root/cfssl/server-key.pem -client-cert-auth -trusted-ca-file=/root/cfssl/ca.pem & 143 | 144 | 访问方式 145 | curl --cacert /root/cfssl/ca.pem --cert /root/cfssl/client.pem --key /root/cfssl/client-key.pem https://c2ccacf259ed:4001/version 146 | ``` 147 | 148 | 这里面c2ccacf259ed在前面的server.json文件hosts中进行了声明,同时需要在client端的/etc/hosts文件中声明c2ccacf259ed对应的ip地址。 149 | 150 | 151 | (2)不带client证书 152 | 153 | ``` 154 | 启动方式 155 | etcd --name infra0 --data-dir infra0 \ 156 | --cert-file=/path/to/server.crt --key-file=/path/to/server.key \ 157 | --advertise-client-urls=https://127.0.0.1:2379 --listen-client-urls=https://127.0.0.1:2379 158 | 159 | 访问方式 160 | curl --cacert /path/to/ca.crt https://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -v 161 | ``` 162 | 163 | ## 参考 164 | 165 | cfssl: 166 | 167 | https://coreos.com/etcd/docs/latest/op-guide/security.html#example-3-transport-security--client-certificates-in-a-cluster 168 | 169 | https://coreos.com/os/docs/latest/generate-self-signed-certificates.html 170 | 171 | openssl: 172 | 173 | http://www.cnblogs.com/breg/p/5923604.html 174 | 175 | etcd的api: 176 | 177 | https://coreos.com/etcd/docs/latest/v2/api.html 178 | 179 | -------------------------------------------------------------------------------- /etcd/etcd v3.md: -------------------------------------------------------------------------------- 1 | # etcd v3 2 | 3 | https://coreos.com/blog/etcd3-a-new-etcd.html 4 | 5 | 6 | 1. 这是一个简易版的client工具 7 | 2. 利用v3的get命令实现ls的效果,手动更改endpoints和etcdctl的绝对路径即可 8 | 3. 如果没有证书,设置为""即可 9 | 10 | ```bash 11 | #! /bin/bash 12 | # Already test in etcd V3.0.4 and V3.1.7 13 | # Deal with ls command 14 | 15 | ENDPOINTS="http://192.168.56.101:2379" 16 | ETCDCTL_ABSPATH="/home/expend-disk/etcd/bin/etcdctl" 17 | CERT_ARGS="" 18 | 19 | export ETCDCTL_API=3 20 | 21 | if [ $1 == "ls" ] 22 | then 23 | keys=$2 24 | if [ -z $keys ] 25 | then 26 | keys="/" 27 | fi 28 | if [ ${keys: -1} != "/" ] 29 | then 30 | keys=$keys"/" 31 | fi 32 | num=`echo $keys | grep -o "/" | wc -l` 33 | (( num=$num+1 )) 34 | #/home/expend-disk/etcd/bin/etcdctl --endpoints="$ENDPOINTS" get $keys --prefix=true --keys-only=true | sed '/^\s*$/d'|sort 35 | $ETCDCTL_ABSPATH --endpoints="$ENDPOINTS" get $keys --prefix=true --keys-only=true $CERT_ARGS | cut -d '/' -f 1-$num | grep -v "^$" | grep -v "compact_rev_key" | uniq | sort 36 | exit 0 37 | fi 38 | # Deal with get command 39 | if [ $1 == "get" ] 40 | then 41 | $ETCDCTL_ABSPATH --endpoints="$ENDPOINTS" $* $CERT_ARGS 42 | #--print-value-only=true 43 | exit 0 44 | fi 45 | # Deal with other command 46 | $ETCDCTL_ABSPATH --endpoints="$ENDPOINTS" $* $CERT_ARGS 47 | exit 0 48 | ``` -------------------------------------------------------------------------------- /etcd/openssl证书.md: -------------------------------------------------------------------------------- 1 | # K8S添加openssl证书并授权多使用者 2 | 3 | ## 生成证书文件 4 | 5 | ### 配置ssl证书"使用者备用名称(DNS)" 6 | 修改openssl配置文件 7 | ``` 8 | vim /etc/pki/tls/openssl.cfg 9 | ``` 10 | 确保`[ req ]`下存在以下两行(第一行是默认有的,第二行被注释掉了) 11 | ``` 12 | [ req ] 13 | distinguished_name = req_distinguished_name 14 | req_extensions = v3_req 15 | ``` 16 | 确保`[ req_distinguished_name ]`下没有0.xxx的标,有的话把0.去掉 17 | ``` 18 | [ req_distinguished_name ] 19 | countryName = Country Name (2 letter code) 20 | countryName_default = XX 21 | countryName_min = 2 22 | countryName_max = 2 23 | stateOrProvinceName = State or Province Name (full name) 24 | localityName = Locality Name (eg, city) 25 | localityName_default = Default City 26 | organizationName = Organization Name (eg, company) 27 | organizationName_default = Default Company Ltd 28 | organizationalUnitName = Organizational Unit Name (eg, section) 29 | commonName = Common Name (eg, your name or your server\'s hostname) 30 | commonName_max 64 31 | emailAddress = Email Address 32 | emailAddress_max = 64 33 | ``` 34 | 新增`[ v3_req ]`最后一行内容 35 | ``` 36 | [ v3_req ] 37 | basicConstraints = CA:FALSE 38 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 39 | subjectAltName = @alt_names 40 | ``` 41 | 新增`[ alt_names ]`内容,其中DNS的名称就是多使用者的名字 42 | ``` 43 | [ alt_names] 44 | DNS.1 = kubernetes 45 | DNS.2 = master1 46 | DNS.3 = master2 47 | DNS.4 = master3 48 | IP.1 = 10.100.0.1 49 | ``` 50 | ### 根据openssl.cfg生成ssl证书 51 | openssl.cfg要求部分文件及目录存在,在生成证书目录执行以下操作 52 | ``` 53 | mkdir -p CA/{certs,cri,newcerts,private} 54 | touch CA/index.txt 55 | echo 00 > CA/serial 56 | ``` 57 | 如果最后一步提示找不到目录,改用 58 | ``` 59 | mkdir -p ./{certs,cri,newcerts,private} 60 | touch ./index.txt 61 | echo 00 > ./serial 62 | ``` 63 | 64 | 然后开始进行ca证书签名以及签署操作 65 | ``` 66 | openssl req -new -x509 -days 3650 -keyout ca.key -out ca.crt -config ./openssl.cnf 67 | openssl genrsa -out server.key 2048 68 | openssl req -new -key server.key -out server.csr -config ./openssl.cnf 69 | openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key -passin pass:secret -batch -extensions v3_req -config ./openssl.cnf 70 | ``` 71 | 注意第三条命令执行后,Common Name要写主域名(注意:这个域名也要在openssl.cnf的DNS.x里) 72 | 73 | ## K8S添加证书认证 74 | 采取kubeconfig模式添加证书 75 | Kubernetes各个组件启用https添加证书参数如下 76 | ``` 77 | kube-apiserver --logtostderr=false --v=0 --etcd-servers=http://192.169.39.209:4001 --insecure-bind-address=0.0.0.0 --allow-privileged=false --service-cluster-ip-range=10.100.0.0/16 --admission-control=AlwaysAdmit,NamespaceLifecycle,NamespaceExists,LimitRanger,ResourceQuota --token_auth_file=/home/container/keystone/token --audit-log-path=/var/log/kubernetes/audit --log-dir=/var/log/kubernetes/kube-apiserver --experimental-keystone-url=http://192.169.39.211:35357 --experimental-keystone-auth-file=/etc/kubernetes/security/keystone_auth_file --authorization-mode=ABAC --authorization-policy-file=/etc/kubernetes/security/policy_file.json --client-ca-file=/root/.kube/keys/ca.crt --tls-private-key-file=/root/.kube/keys/server.key --tls-cert-file=/root/.kube/keys/server.crt 78 | 79 | kube-controller-manager --logtostderr=false --v=0 --log-dir=/var/log/kubernetes/kube-controller-manager --kubeconfig=/root/.kube/config 80 | 81 | kube-scheduler --logtostderr=false --v=0 --log-dir=/var/log/kubernetes/kube-scheduler --kubeconfig=/root/.kube/config 82 | 83 | kube-proxy --logtostderr=false --v=0 --log-dir=/var/log/kubernetes/kube-proxy --kubeconfig=/root/.kube/config 84 | 85 | kubelet --logtostderr=false --v=0 --address=0.0.0.0 --hostname-override=192.169.39.209 --allow-privileged=false --cpu-cfs-quota=true --kubeconfig=/root/.kube/config --require-kubeconfig=True --pod-infra-container-image=pause:0.8.0 --log-dir=/var/log/kubernetes/kubelet 86 | ``` 87 | 位于/root/.kube/目录下的config文件,是Kubernetes的默认配置文件本地kubectl默认也会根据该文件执行操作,该文件一般内容如下 88 | ``` 89 | apiVersion: v1 90 | kind: Config 91 | users: 92 | - name: visitor 93 | user: 94 | client-certificate: /root/.kube/keys/server.crt 95 | client-key: /root/.kube/keys/server.key 96 | clusters: 97 | - name: kubernetes 98 | cluster: 99 | certificate-authority: /root/.kube/keys/ca.crt 100 | server: https://kubernetes:6444 101 | contexts: 102 | - context: 103 | cluster: kubernetes 104 | user: visitor 105 | name: kubernetes-context 106 | current-context: kubernetes-context 107 | ``` 108 | 其中server字段填写的是api-server的ip地址和端口。 109 | 其中/root/.kube/下需要手打创建keys文件夹,下面存放openssl生成的证书文件。 110 | -------------------------------------------------------------------------------- /images/API-server-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/API-server-flow.png -------------------------------------------------------------------------------- /images/API-server-gvr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/API-server-gvr.png -------------------------------------------------------------------------------- /images/API-server-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/API-server-overview.png -------------------------------------------------------------------------------- /images/API-server-serialization-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/API-server-serialization-overview.png -------------------------------------------------------------------------------- /images/API-server-space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/API-server-space.png -------------------------------------------------------------------------------- /images/API-server-storage-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/API-server-storage-flow.png -------------------------------------------------------------------------------- /images/Calico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/Calico.png -------------------------------------------------------------------------------- /images/IO重定向.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/IO重定向.png -------------------------------------------------------------------------------- /images/StorageVersions-00.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/StorageVersions-00.jpeg -------------------------------------------------------------------------------- /images/access-control-overview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/access-control-overview.jpg -------------------------------------------------------------------------------- /images/apiserver-00.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/apiserver-00.jpeg -------------------------------------------------------------------------------- /images/containerd-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/containerd-architecture.png -------------------------------------------------------------------------------- /images/dockerDaemon-containerd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/dockerDaemon-containerd.png -------------------------------------------------------------------------------- /images/dup重定向.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/dup重定向.png -------------------------------------------------------------------------------- /images/etcd-101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/etcd-101.png -------------------------------------------------------------------------------- /images/execvp的原理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/execvp的原理.png -------------------------------------------------------------------------------- /images/exec复制环境变量.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/exec复制环境变量.png -------------------------------------------------------------------------------- /images/flannel-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/flannel-1.png -------------------------------------------------------------------------------- /images/flannel-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/flannel-2.png -------------------------------------------------------------------------------- /images/flannel-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/flannel-3.png -------------------------------------------------------------------------------- /images/fopen和popen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/fopen和popen.png -------------------------------------------------------------------------------- /images/fork共享管道.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/fork共享管道.png -------------------------------------------------------------------------------- /images/go对string类型的定义.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/go对string类型的定义.png -------------------------------------------------------------------------------- /images/iptables-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/iptables-1.png -------------------------------------------------------------------------------- /images/iptables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/iptables.png -------------------------------------------------------------------------------- /images/iptables路由次序图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/iptables路由次序图.png -------------------------------------------------------------------------------- /images/kubernetes-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/kubernetes-controller.png -------------------------------------------------------------------------------- /images/new和make的区别.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/new和make的区别.png -------------------------------------------------------------------------------- /images/pod-controller-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/pod-controller-panel.png -------------------------------------------------------------------------------- /images/shell为子进程重定向其输出.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/shell为子进程重定向其输出.png -------------------------------------------------------------------------------- /images/shell模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/shell模型.png -------------------------------------------------------------------------------- /images/shell模型简化版.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/shell模型简化版.png -------------------------------------------------------------------------------- /images/slice的定义.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/slice的定义.png -------------------------------------------------------------------------------- /images/socket流程图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/socket流程图.png -------------------------------------------------------------------------------- /images/userspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/userspace.png -------------------------------------------------------------------------------- /images/一个shell的主循环.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/一个shell的主循环.png -------------------------------------------------------------------------------- /images/一个进程创建pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/一个进程创建pipe.png -------------------------------------------------------------------------------- /images/一个进程的三个计时器.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/一个进程的三个计时器.png -------------------------------------------------------------------------------- /images/一个进程的虚拟地址空间.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/一个进程的虚拟地址空间.png -------------------------------------------------------------------------------- /images/三种传输数据的办法.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/三种传输数据的办法.png -------------------------------------------------------------------------------- /images/内核缓冲区和普通进程缓冲区.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/内核缓冲区和普通进程缓冲区.png -------------------------------------------------------------------------------- /images/函数和进程的相似性.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/函数和进程的相似性.png -------------------------------------------------------------------------------- /images/基础类型在内存中的存在形式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/基础类型在内存中的存在形式.png -------------------------------------------------------------------------------- /images/多个文件系统的组合.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/多个文件系统的组合.png -------------------------------------------------------------------------------- /images/文件系统的不同视角-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/文件系统的不同视角-1.png -------------------------------------------------------------------------------- /images/文件系统的不同视角-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/文件系统的不同视角-2.png -------------------------------------------------------------------------------- /images/最低可用fd原则.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/最低可用fd原则.png -------------------------------------------------------------------------------- /images/标准数据流.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/标准数据流.png -------------------------------------------------------------------------------- /images/标准数据流模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/标准数据流模型.png -------------------------------------------------------------------------------- /images/环境变量的存储.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/环境变量的存储.png -------------------------------------------------------------------------------- /images/管道.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/管道.png -------------------------------------------------------------------------------- /images/系统信号.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/系统信号.png -------------------------------------------------------------------------------- /images/系统调用fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/系统调用fork.png -------------------------------------------------------------------------------- /images/系统调用wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/系统调用wait.png -------------------------------------------------------------------------------- /images/网络传输切割数据包.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/网络传输切割数据包.png -------------------------------------------------------------------------------- /images/设备文件和普通数据文件的区别.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/设备文件和普通数据文件的区别.png -------------------------------------------------------------------------------- /images/设备文件和普通磁盘文件的区别.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/设备文件和普通磁盘文件的区别.png -------------------------------------------------------------------------------- /images/进程通过共享内存交换数据.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/进程通过共享内存交换数据.png -------------------------------------------------------------------------------- /images/重定向策略open-then-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevin-fqh/learning-k8s-source-code/4369312ec7e8cd7a3861d735651397fea2437823/images/重定向策略open-then-close.png -------------------------------------------------------------------------------- /kubectl/(06) genetator.md: -------------------------------------------------------------------------------- 1 | # Generator 2 | kubectl run/expose中会用到Generator 3 | 4 | generation: a sequence number representing a specific generation of the desired state. Set by the system and monotonically increasing, per-resource. May be compared, such as for RAW and WAW consistency. 5 | 6 | file:///Users/fanqihong/Desktop/Kubernetes文档/需要翻译的文章/community:api-conventions.md%20at%20master%20·%20kubernetes:community.webarchive -------------------------------------------------------------------------------- /kubectl/(11) kubectl总结.md: -------------------------------------------------------------------------------- 1 | # The summary of kubectl 2 | 3 | ## part 1 4 | kubectl是基于package cobra来进行命令的构造。 5 | 每次执行kubectl命令时候,都会通过Factory 来重新获取Apiserver的gvk、restmapper这些信息,存储到cache目录/root/.kube/cache/discovery/localhost_8080下。 6 | 7 | 根据cmd的参数来生成一个Builder,type Builder struct提供了从命令行获取arguments和参数的函数接口, 8 | 并将其转换为一系列的resources,以便可以迭代使用Visitor interface。 9 | 通过多个Visitor的嵌套来进info信息进行过滤。 10 | 11 | Builder的Do()函数最后会返回一个type Result struct,这里面存储的就是执行kubectl命令的返回结果。 12 | 最后会通过获取特定的Printer(describer)来对信息进行格式化输出。 13 | 14 | ## part 2 15 | 通过对event的定义和输出的了解,可以知道kubernetes中定义一个资源的通用必备属性:unversioned.TypeMeta和ObjectMeta。 16 | 17 | 然后研究event的流向,可以发现kubernetes通过list-watch机制进行消息的传递。 18 | Broadcaster广播机制也是list-watch机制的一种实现方式。 19 | 各个组件都会向Apiserver发送自身产生的event。 20 | kubernetes会通过EventCorrelator对event进行聚合和去重的处理,以避免由于event的数据量过大而造成系统的不稳定。 21 | 22 | kubernetes是一个`level driven(state)`的系统,并非一个`edge driven(event)`系统。 23 | 也就是会说,k8s不是一接收到信号,就会立马触发某个事件。 24 | 而是,系统声明了这么一个信息,然后在未来一段时间里面,系统会根据这个信息做出相应的处理,这是一种`声明式`的处理方式。 25 | 26 | ![kubernetes-controller](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/kubernetes-controller.png) 27 | 28 | ## part 3 29 | 最后通过的client-go的使用,可以更加简易地了解到kubernetes的内部核心机制。 30 | RESTClient是Kubernetes最基础的Client,封装了一个http client。 restclient 是dynamic client和clientset的基础。 31 | 重点学习和掌握clientset的用法,根据其衍生出各种实体资源的Client(PodClient...)。 32 | 33 | -------------------------------------------------------------------------------- /kubectl/(12) kubeconfig.md: -------------------------------------------------------------------------------- 1 | # k8s里面的kubeconfig 2 | 3 | **Table of Contents** 4 | 5 | - [模板](#模板) 6 | - [clusters模块](#clusters模块) 7 | - [users模块](#users模块) 8 | - [contexts模块](#contexts模块) 9 | - [current-context](#current-context) 10 | - [加载和合并kubeconfig规则](#加载和合并kubeconfig规则) 11 | - [kubectl config命令](#kubectl-config命令) 12 | - [模板2](#模板2) 13 | 14 | 15 | 16 | 17 | 在kubectl系列文章里面的对Factory进行介绍时,提到过kubeconfig文件。 18 | 19 | `kubectl config view` 命令可以展示当前的 kubeconfig 设置。 20 | 21 | ## 模板 22 | `vim /root/.kube/config`,默认路径 23 | 24 | ``` 25 | apiVersion: v1 26 | kind: Config 27 | preferences: {} 28 | users: 29 | - name: visitor 30 | user: 31 | client-certificate: /root/.kube/server.crt 32 | client-key: /root/.kube/server.key 33 | clusters: 34 | - name: kubernetes 35 | cluster: 36 | server: https://kubernetes:6443 37 | certificate-authority: /root/.kube/ca.crt 38 | contexts: 39 | - context: 40 | cluster: kubernetes 41 | user: visitor 42 | name: default-context 43 | current-context: default-context 44 | ``` 45 | apiVersion 和 kind 标识客户端解析器的版本和模式 46 | 47 | ## clusters模块 48 | cluster中包含kubernetes集群的端点数据,包括 kubernetes apiserver 的完整 url 以及集群的证书颁发机构。 49 | 50 | 可以使用 `kubectl config set-cluster` 添加或修改 cluster 条目。 51 | 52 | ## users模块 53 | user定义用于向kubernetes集群进行身份验证的客户端凭据。 54 | 在加载/合并kubeconfig之后,user将有一个名称作为用户条目列表中的key。 55 | 56 | 可用凭证有 `client-certificate、client-key、token 和 username/password`。 57 | `username/password` 和 `token` 是二者只能选择一个,但 `client-certificate` 和 `client-key` 可以分别与它们组合。 58 | 59 | 您可以使用 `kubectl config set-credentials` 添加或者修改 user 条目。 60 | 61 | ## contexts模块 62 | context定义了一个命名的`cluster、user、namespace`元组,用于使用提供的认证信息和命名空间将请求发送到指定的集群。 63 | 64 | 三个都是可选的; 65 | 仅使用 cluster、user、namespace 之一指定上下文,或指定`none`。 66 | 67 | 未指定的值或在加载的 kubeconfig 中没有相应条目的命名值将被替换为默认值。 68 | 加载和合并 kubeconfig 文件的规则很简单,但有很多,具体可以查看[加载和合并kubeconfig规则](#加载和合并kubeconfig规则)。 69 | 70 | 可以使用`kubectl config set-context`添加或修改上下文条目。 71 | 72 | ## current-context 73 | current-context 是作为`cluster、user、namespace`元组的 ”key“, 74 | 当kubectl从该文件中加载配置的时候会被默认使用。 75 | 76 | 可以在kubectl命令行里覆盖这些值,通过分别传入`—context=CONTEXT、 —cluster=CLUSTER、--user=USER 和 --namespace=NAMESPACE`。 77 | 78 | 可以使用`kubectl config use-context`更改 current-context。 79 | 80 | ## 加载和合并kubeconfig规则 81 | 根据下面的规则来生成一个clientcmd.ClientConfig。规则呈现如下层次结构 82 | 83 | 一: 使用kubeconfig builder。这里的合并和覆盖次数有点多。 84 | 85 | 1. 合并kubeconfig本身。 这是通过以下层次结构规则完成的 86 | (1)CommandLineLocation - 这是从命令行解析的,so it must be late bound。 87 | 如果指定了这一点,则不会合并其他kubeconfig文件。 此文件必须存在。 88 | (2)如果设置了$KUBECONFIG,那么它被视为应该被合并的文件之一。 89 | (3)主目录位置 HomeDirectoryLocation ,即${HOME}/.kube/config==>/root/.kube/config 90 | 91 | 2. 根据此规则链中的第一个命中确定要使用的上下文---context 92 | (1)命令行参数 - 再次从命令行解析,so it must be late bound 93 | (2)CurrentContext from the merged kubeconfig file 94 | (3)Empty is allowed at this stage 95 | 3. 确定要使用的群集信息和身份验证信息。---cluster info and auth info 96 | 在这里,我们可能有也可能没有上下文。 97 | 他们是建立在这个规则链中的第一个命中。(运行两次,一次为auth,一次为集群) 98 | (1)命令行参数 99 | (2)If context is present, then use the context value 100 | (3)Empty is allowed 101 | 4. 确定要使用的实际群集信息。---actual cluster info 102 | 在这一点上,我们可能有也可能没有集群信息。基于下述规则链构建集群信息: 103 | (1)命令行参数 104 | (2)If cluster info is present and a value for the attribute is present, use it. 105 | (3)If you don't have a server location, bail. 106 | 5. cluster info and auth info是使用同样的规则来进行创建的。 107 | 除非你在auth info仅仅使用了一种认证方式。 108 | 下述情况将会导致ERROR: 109 | (1)如果从命令行指定了两个冲突的认证方式,则失败。 110 | (2)如果命令行未指定,并且auth info具有冲突的认证方式,则失败。 111 | (3)如果命令行指定一个,并且auth info指定另一个,则遵守命令行指定的认证方式。 112 | 113 | 二: 对于任何仍然缺少的信息,使用默认值,并可能提示验证信息 114 | 115 | Kubeconfig 文件中的任何路径都相对于 kubeconfig 文件本身的位置进行解析。 116 | 117 | ## kubectl config命令 118 | ```shell 119 | $ kubectl config set-credentials myself --username=admin --password=secret 120 | $ kubectl config set-cluster local-server --server=http://localhost:8080 121 | $ kubectl config set-context default-context --cluster=local-server --user=myself 122 | $ kubectl config use-context default-context 123 | $ kubectl config set contexts.default-context.namespace the-right-prefix 124 | $ kubectl config view 125 | ``` 126 | 产生对应的kubeconfig文件如下所示: 127 | ``` 128 | apiVersion: v1 129 | clusters: 130 | - cluster: 131 | server: http://localhost:8080 132 | name: local-server 133 | contexts: 134 | - context: 135 | cluster: local-server 136 | namespace: the-right-prefix 137 | user: myself 138 | name: default-context 139 | current-context: default-context 140 | kind: Config 141 | preferences: {} 142 | users: 143 | - name: myself 144 | user: 145 | password: secret 146 | username: admin 147 | ``` 148 | 149 | ## 模版2 150 | 最后,来看一个官方的详细一点模版 151 | ``` 152 | current-context: federal-context 153 | apiVersion: v1 154 | clusters: 155 | - cluster: 156 | api-version: v1 157 | server: http://cow.org:8080 158 | name: cow-cluster 159 | - cluster: 160 | certificate-authority: path/to/my/cafile 161 | server: https://horse.org:4443 162 | name: horse-cluster 163 | - cluster: 164 | insecure-skip-tls-verify: true 165 | server: https://pig.org:443 166 | name: pig-cluster 167 | contexts: 168 | - context: 169 | cluster: horse-cluster 170 | namespace: chisel-ns 171 | user: green-user 172 | name: federal-context 173 | - context: 174 | cluster: pig-cluster 175 | namespace: saw-ns 176 | user: black-user 177 | name: queen-anne-context 178 | kind: Config 179 | preferences: 180 | colors: true 181 | users: 182 | - name: blue-user 183 | user: 184 | token: blue-token 185 | - name: green-user 186 | user: 187 | client-certificate: path/to/my/client/cert 188 | client-key: path/to/my/client/key 189 | ``` 190 | -------------------------------------------------------------------------------- /kubectl/(15)定制一个API之kubectl.md: -------------------------------------------------------------------------------- 1 | # 定制API之kubectl 2 | 3 | 承接前面[定制一个API之ApiServer](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/apiserver/定制一个API之ApiServer篇/定制一个API之ApiServer.md),现在来完善kubectl 4 | 5 | ## 初始化 6 | `/pkg/client/clientset_generated/internalclientset/import_known_versions.go` 和 `/pkg/client/clientset_generated/release_1_5/import_known_versions.go`中完成Group的初始化安装,这个地方自动化生成代码工具居然没有更新。。。 7 | ``` 8 | _ "k8s.io/kubernetes/pkg/apis/premierleague/install" 9 | ``` 10 | 11 | ## Create 12 | 现在我们可以创建前面定义好的premierleague.Match资源了,premierleague-match.yaml文件如下: 13 | ```yaml 14 | apiVersion: premierleague.k8s.io/v1 15 | kind: Match 16 | metadata: 17 | name: fqhmatch 18 | namespace: default 19 | spec: 20 | host: A 21 | guest: B 22 | ``` 23 | 24 | 效果如下 25 | ```shell 26 | [root@fqhnode01 yaml]# kubectl create -f premierleague-match.yaml 27 | [root@fqhnode01 ~]# etcdctl --endpoints 172.17.0.2:2379 get /registry/matchs/default/fqhmatch 28 | {"kind":"Match","apiVersion":"premierleague.k8s.io/v1","metadata":{"name":"fqhmatch","namespace":"default","uid":"546fc0f9-e74e-11e7-8c9c-080027e58fc6","generation":1,"creationTimestamp":"2017-12-22T19:28:46Z"},"spec":{"host":"A","guest":"B"}} 29 | ``` 30 | 31 | ## Get 32 | 指定json或者yaml格式执行get操作,`kubectl get match -o json` 33 | ```json 34 | enforceNamespace is: false 35 | args is: [match] 36 | len(args) is: 1 37 | generic is: true 38 | { 39 | "apiVersion": "v1", 40 | "items": [ 41 | { 42 | "apiVersion": "premierleague.k8s.io/v1", 43 | "kind": "Match", 44 | "metadata": { 45 | "creationTimestamp": "2017-12-22T19:28:46Z", 46 | "generation": 1, 47 | "name": "fqhmatch", 48 | "namespace": "default", 49 | "resourceVersion": "31", 50 | "selfLink": "/apis/premierleague.k8s.io/v1/namespaces/default/matchs/fqhmatch", 51 | "uid": "546fc0f9-e74e-11e7-8c9c-080027e58fc6" 52 | }, 53 | "spec": { 54 | "guest": "B", 55 | "host": "A" 56 | } 57 | } 58 | ], 59 | "kind": "List", 60 | "metadata": {}, 61 | "resourceVersion": "", 62 | "selfLink": "" 63 | } 64 | ``` 65 | 其实,此时指定`/pkg/kubectl/resource_printer.go`中的`func GetPrinter`的大部分Printer都能得到输出,除了`-o wide`和`不指定格式`的情况。 66 | 67 | 68 | ### 代码 69 | 修改`pkg/kubectl/resource_printer.go` 70 | 71 | 1. import 72 | ```go 73 | "k8s.io/kubernetes/pkg/apis/premierleague" 74 | ``` 75 | 76 | 2. 定义输出item 77 | ```go 78 | // NOTE: When adding a new resource type here, please update the list 79 | // pkg/kubectl/cmd/get.go to reflect the new resource type. 80 | var ( 81 | matchColumns = []string{"NAME", "HOST", "GUEST", "AGE"} 82 | podColumns = []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"} 83 | ... 84 | ... 85 | ) 86 | ``` 87 | 88 | 3. addDefaultHandlers中增加Match对象的print函数 89 | ```go 90 | // addDefaultHandlers adds print handlers for default Kubernetes types. 91 | func (h *HumanReadablePrinter) addDefaultHandlers() { 92 | h.Handler(matchColumns, printMatch) 93 | h.Handler(matchColumns, printMatchList) 94 | h.Handler(podColumns, h.printPodList) 95 | ... 96 | ... 97 | } 98 | ``` 99 | 4. 两个print函数如下 100 | ``` 101 | func printMatch(match *premierleague.Match, w io.Writer, options PrintOptions) error { 102 | name := formatResourceName(options.Kind, match.Name, options.WithKind) 103 | 104 | namespace := match.Namespace 105 | 106 | if options.WithNamespace { 107 | if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil { 108 | return err 109 | } 110 | } 111 | if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s", name, match.Spec.Host, match.Spec.Guest, translateTimestamp(match.CreationTimestamp)); err != nil { 112 | return err 113 | } 114 | 115 | if _, err := fmt.Fprint(w, AppendLabels(match.Labels, options.ColumnLabels)); err != nil { 116 | return err 117 | } 118 | _, err := fmt.Fprint(w, AppendAllLabels(options.ShowLabels, match.Labels)) 119 | return err 120 | } 121 | 122 | func printMatchList(matchList *premierleague.MatchList, w io.Writer, options PrintOptions) error { 123 | for _, match := range matchList.Items { 124 | if err := printMatch(&match, w, options); err != nil { 125 | return err 126 | } 127 | } 128 | return nil 129 | } 130 | ``` 131 | 132 | ### 效果 133 | ```shell 134 | [root@fqhnode01 yaml]# kubectl get match 135 | NAME HOST GUEST AGE 136 | fqhmatch A B 1h 137 | ``` 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /kubelet/(02)Demo-合并三个来源的Pod.md: -------------------------------------------------------------------------------- 1 | # kubelet合并三个来源的pod 2 | 3 | ## 简介 4 | kubelet可以从Apiserver、http 和 file 三个途径获取pod,然后会把所有的pod都汇总到一起,再按照其动作(update、add)来进行具体的操作。 下面我们仿照其实现写个Demo 5 | 6 | ## util 7 | package util主要提供Until()函数,间隔固定时间来运行一个方法。 8 | 如果输入NeverStop,Until()函数将永远不会退出。 9 | 10 | ```go 11 | package util 12 | 13 | import ( 14 | "fmt" 15 | "runtime" 16 | "time" 17 | 18 | "github.com/golang/glog" 19 | ) 20 | 21 | var ReallyCrash bool 22 | 23 | // PanicHandlers is a list of functions which will be invoked when a panic happens. 24 | var PanicHandlers = []func(interface{}){logPanic} 25 | 26 | // logPanic logs the caller tree when a panic occurs. 27 | func logPanic(r interface{}) { 28 | callers := "" 29 | for i := 0; true; i++ { 30 | _, file, line, ok := runtime.Caller(i) 31 | if !ok { 32 | break 33 | } 34 | callers = callers + fmt.Sprintf("%v:%v\n", file, line) 35 | } 36 | glog.Errorf("Recovered from panic: %#v (%v)\n%v", r, r, callers) 37 | } 38 | 39 | // HandleCrash simply catches a crash and logs an error. Meant to be called via defer. 40 | // Additional context-specific handlers can be provided, and will be called in case of panic 41 | func HandleCrash(additionalHandlers ...func(interface{})) { 42 | if ReallyCrash { 43 | return 44 | } 45 | if r := recover(); r != nil { 46 | for _, fn := range PanicHandlers { 47 | fn(r) 48 | } 49 | for _, fn := range additionalHandlers { 50 | fn(r) 51 | } 52 | } 53 | } 54 | 55 | // Until loops until stop channel is closed, running f every period. 56 | // Catches any panics, and keeps going. f may not be invoked if 57 | // stop channel is already closed. Pass NeverStop to Until if you 58 | // don't want it stop. 59 | func Until(f func(), period time.Duration, stopCh <-chan struct{}) { 60 | for { 61 | select { 62 | case <-stopCh: 63 | return 64 | default: 65 | } 66 | func() { 67 | defer HandleCrash() 68 | f() 69 | }() 70 | time.Sleep(period) 71 | } 72 | } 73 | 74 | // NeverStop may be passed to Until to make it never stop. 75 | var NeverStop <-chan struct{} = make(chan struct{}) 76 | ``` 77 | 78 | ## merge 79 | package merge 负责根据一个string生成一个channel,形成一个一一对应关系。 80 | 如果提供了多个string,将被视为联合。 81 | 其实并没有真正的合并,多少个channel,就有多少个groutine去调用func (m *Mux) listen 82 | 83 | ```go 84 | package merge 85 | 86 | import ( 87 | "kubelet-collect-pod-demo/src/util" 88 | "sync" 89 | ) 90 | 91 | type Merger interface { 92 | // Invoked when a change from a source is received. May also function as an incremental 93 | // merger if you wish to consume changes incrementally. Must be reentrant when more than 94 | // one source is defined. 95 | Merge(source string, update interface{}) error 96 | } 97 | 98 | // MergeFunc implements the Merger interface 99 | type MergeFunc func(source string, update interface{}) error 100 | 101 | func (f MergeFunc) Merge(source string, update interface{}) error { 102 | return f(source, update) 103 | } 104 | 105 | //Mux就两个东西,一个chan map还有一个Merger的interface。从用法上来看,其实Mux就是一个多生产者的合并 106 | type Mux struct { 107 | // Invoked when an update is sent to a source. 108 | merger Merger 109 | 110 | // Sources and their lock. 111 | sourceLock sync.RWMutex 112 | // Maps source names to channels 113 | sources map[string]chan interface{} 114 | } 115 | 116 | // NewMux creates a new mux that can merge changes from multiple sources. 117 | func NewMux(merger Merger) *Mux { 118 | mux := &Mux{ 119 | sources: make(map[string]chan interface{}), 120 | merger: merger, 121 | } 122 | return mux 123 | } 124 | 125 | // Channel returns a channel where a configuration source 126 | // can send updates of new configurations. Multiple calls with the same 127 | // source will return the same channel. This allows change and state based sources 128 | // to use the same channel. Different source names however will be treated as a 129 | // union. 130 | /* 131 | 译:func (m *Mux) Channel将返回一个channel,可以用来更新配置。 132 | Multiple calls with the same source will return the same channel(根据传进来的参数source string判定); 133 | 这允许sources的change 和 state 使用相同的channel。 134 | 135 | 不同的source names将被视为联合。 136 | 其实并没有真正的合并,多少个channel,就有多少个groutine去调用func (m *Mux) listen 137 | 138 | */ 139 | func (m *Mux) Channel(source string) chan interface{} { 140 | //Channel(source string)方法 就是生产者的注册,并返回生产者传送obj的channel。 141 | if len(source) == 0 { 142 | panic("Channel given an empty name") 143 | } 144 | m.sourceLock.Lock() 145 | defer m.sourceLock.Unlock() 146 | channel, exists := m.sources[source] 147 | if exists { 148 | //channel已经存在,直接return 149 | return channel 150 | } 151 | newChannel := make(chan interface{}) 152 | m.sources[source] = newChannel 153 | /* 154 | 如果channel是新的,起一个groutine 155 | */ 156 | go util.Until(func() { m.listen(source, newChannel) }, 0, util.NeverStop) 157 | return newChannel 158 | } 159 | 160 | func (m *Mux) listen(source string, listenChannel <-chan interface{}) { 161 | /* 162 | 负责调用obj的Merge()进行输出处理 163 | */ 164 | for update := range listenChannel { 165 | m.merger.Merge(source, update) 166 | } 167 | } 168 | ``` 169 | 170 | ## main() 171 | 模仿两个生产者file和http, 假设要处理的数据类型是 string 172 | ```go 173 | package main 174 | 175 | import ( 176 | "fmt" 177 | "kubelet-collect-pod-demo/src/merge" 178 | "sync" 179 | ) 180 | 181 | type testStorage struct { 182 | update chan string //要处理的数据类型是string 183 | updatLock sync.Mutex 184 | } 185 | 186 | func (s testStorage) Merge(source string, update interface{}) error { 187 | s.updatLock.Lock() 188 | defer s.updatLock.Unlock() 189 | // if source == "http" { 190 | // } 191 | st := fmt.Sprintf("source is %s, Got value %s", source, update.(string)) 192 | fmt.Println(st) 193 | obj, ok := update.(string) 194 | if !ok { 195 | fmt.Println("not a string") 196 | return nil 197 | } 198 | s.update <- obj 199 | return nil 200 | } 201 | 202 | func (s testStorage) Customer() { 203 | for { 204 | select { 205 | case st := <-s.update: 206 | fmt.Println("消费", st) 207 | } 208 | } 209 | } 210 | 211 | func HttpInput(chHttp chan<- interface{}) { 212 | //往channel chHttp 输入数据 213 | chHttp <- "hello" 214 | chHttp <- "world" 215 | close(chHttp) 216 | } 217 | 218 | func FileInput(chFile chan<- interface{}) { 219 | //往channel chFile 输入数据 220 | chFile <- "I" 221 | chFile <- "am" 222 | chFile <- "file" 223 | close(chFile) 224 | } 225 | 226 | func main() { 227 | chupdate := make(chan string, 50) 228 | storage := testStorage{ 229 | update: chupdate, 230 | } 231 | mux := merge.NewMux(storage) 232 | go HttpInput(mux.Channel("http")) 233 | go FileInput(mux.Channel("file")) 234 | go storage.Customer() 235 | select {} 236 | } 237 | ``` 238 | 239 | 输出如下 240 | ``` 241 | source is file, Got value I 242 | source is file, Got value am 243 | source is file, Got value file 244 | source is http, Got value hello 245 | 消费 I 246 | 消费 am 247 | 消费 file 248 | 消费 hello 249 | source is http, Got value world 250 | 消费 world 251 | ``` -------------------------------------------------------------------------------- /kubelet/(03)Ceph Rbd使用.md: -------------------------------------------------------------------------------- 1 | # kubelet对rbd的使用 2 | 3 | ## rbd基本命令 4 | 1. rbd的创建过程一般如下所示 5 | ```shell 6 | # ceph osd pool create rbdpool 64 7 | # rbd crearte rbdpool/rbdimage --size 10G --image-feature layering 8 | # rbd map rbdpool/rbdimage 9 | # mkfs.ext4 /dev/rbd0 10 | # mount 11 | ``` 12 | 在v1.5.2版本中,pool和rbd image对于kubelet来说,是已经创建好了的。 13 | 也就是说kubelet要完成`map、mkfs和mount`动作。 14 | 15 | 2. rbd的删除过程一般如下所示 16 | ```shell 17 | # unmount /dev/rbd0 18 | # rbd unmap /dev/rbd0 19 | # rbd rm rbdimage –p rbdpool 20 | ``` 21 | 22 | 3. 如果要删除一个pool 23 | ```shell 24 | ceph osd pool delete {pool-name} {pool-name} --yes-i-really-really-mean-it 25 | ``` 26 | 27 | ## 挂载过程 28 | 在storageClass+pvc的模型中,创建pvc的时候并不会进行mount动作。 29 | 30 | 把"ceph rbd image /dev/rbd0" 挂载到 "主机节点的目录globalPDPath"会出现下述现象: 31 | 1. 同一台物理机上,把RBD进行只读挂载后,再进行写挂载,由于会回改RBD挂载点的权限,所以RBD挂载点的权限会从ro变成rw。 32 | 2. 同样的,在同一台物理机上,先把RBD进行写挂载,再进行读挂载,RBD挂载点的权限会从rw变成ro,从而写挂载不能写入数据。 33 | 3. 如果只读挂载和写挂载不在同一台物理主机上,则问题不存在。 34 | 35 | ```go 36 | // utility to mount a disk based filesystem 37 | func diskSetUp(manager diskManager, b rbdMounter, volPath string, mounter mount.Interface, fsGroup *int64) error { 38 | globalPDPath := manager.MakeGlobalPDName(*b.rbd) 39 | // TODO: handle failed mounts here. 40 | notMnt, err := mounter.IsLikelyNotMountPoint(volPath) 41 | 42 | if err != nil && !os.IsNotExist(err) { 43 | glog.Errorf("cannot validate mountpoint: %s", volPath) 44 | return err 45 | } 46 | if !notMnt { 47 | return nil 48 | } 49 | /* 50 | AttachDisk()把"ceph rbd image /dev/rbd0" 挂载到 "主机节点的目录globalPDPath" 51 | ==>/pkg/volume/rbd/rbd_util.go 52 | ==>func (util *RBDUtil) AttachDisk(b rbdMounter) error 53 | 54 | globalPDPath是主机节点上代表了persistentvolume的目录,形如 "rbd"+{pool}+"-image-"+{image} 55 | 56 | 在storageClass+pvc的模型中,这里的读写权限由 spec.volumes.persistentVolumeClaim.readOnly 控制 57 | 当spec.volumes.persistentVolumeClaim.readOnly为false时, 58 | K8s在挂载RBD的过程中会给该RBD image加入锁,所以集群中写方式的RBD image只能挂载一次 59 | */ 60 | if err := manager.AttachDisk(b); err != nil { 61 | glog.Errorf("failed to attach disk") 62 | return err 63 | } 64 | 65 | /* 66 | 这里是把pv和容器中的目录进行了挂载,对应docker的操作是 docker -v 指定读写模式。 67 | 68 | 把主机host目录globalPDPath 和 volume的挂载点volPath(pv) 进行mount操作 69 | */ 70 | if err := os.MkdirAll(volPath, 0750); err != nil { 71 | glog.Errorf("failed to mkdir:%s", volPath) 72 | return err 73 | } 74 | // Perform a bind mount to the full path to allow duplicate mounts of the same disk. 75 | options := []string{"bind"} 76 | if (&b).GetAttributes().ReadOnly { 77 | /* 78 | 如果设置了只读模式,以read-only模式进行挂载 79 | 在storageClass+pvc的模型中, 80 | 读写权限由 spec.containers.volumeMounts.readOnly中的readOnly控制 81 | */ 82 | options = append(options, "ro") 83 | } 84 | /* 85 | ==>/pkg/util/mount/mount_linux.go 86 | ==>func (mounter *Mounter) Mount 87 | 把pv和容器中的目录进行了挂载 88 | */ 89 | err = mounter.Mount(globalPDPath, volPath, "", options) 90 | if err != nil { 91 | glog.Errorf("failed to bind mount:%s", globalPDPath) 92 | return err 93 | } 94 | 95 | if !b.ReadOnly { 96 | volume.SetVolumeOwnership(&b, fsGroup) 97 | } 98 | 99 | return nil 100 | } 101 | ``` 102 | 103 | ### AttachDisk() 104 | AttachDisk()把"ceph rbd image /dev/rbd0" 挂载到 "主机节点的目录globalPDPath"。 105 | 106 | 在storageClass+pvc的模型中,这里的读写权限由 spec.volumes.persistentVolumeClaim.readOnly 控制。 107 | ```go 108 | func (util *RBDUtil) AttachDisk(b rbdMounter) error { 109 | /* 110 | 完成rbd image map、格式化和mount三个动作 111 | */ 112 | var err error 113 | var output []byte 114 | 115 | devicePath, found := waitForPath(b.Pool, b.Image, 1) 116 | if !found { 117 | // modprobe 118 | _, err = b.plugin.execCommand("modprobe", []string{"rbd"}) 119 | if err != nil { 120 | return fmt.Errorf("rbd: failed to modprobe rbd error:%v", err) 121 | } 122 | // rbd map 123 | l := len(b.Mon) 124 | // avoid mount storm, pick a host randomly 125 | start := rand.Int() % l 126 | // iterate all hosts until mount succeeds. 127 | for i := start; i < start+l; i++ { 128 | mon := b.Mon[i%l] 129 | glog.V(1).Infof("rbd: map mon %s", mon) 130 | if b.Secret != "" { 131 | output, err = b.plugin.execCommand("rbd", 132 | []string{"map", b.Image, "--pool", b.Pool, "--id", b.Id, "-m", mon, "--key=" + b.Secret}) 133 | } else { 134 | output, err = b.plugin.execCommand("rbd", 135 | []string{"map", b.Image, "--pool", b.Pool, "--id", b.Id, "-m", mon, "-k", b.Keyring}) 136 | } 137 | if err == nil { 138 | break 139 | } 140 | glog.V(1).Infof("rbd: map error %v %s", err, string(output)) 141 | } 142 | if err != nil { 143 | return fmt.Errorf("rbd: map failed %v %s", err, string(output)) 144 | } 145 | devicePath, found = waitForPath(b.Pool, b.Image, 10) 146 | if !found { 147 | return errors.New("Could not map image: Timeout after 10s") 148 | } 149 | } 150 | // mount it 151 | globalPDPath := b.manager.MakeGlobalPDName(*b.rbd) 152 | notMnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath) 153 | // in the first time, the path shouldn't exist and IsLikelyNotMountPoint is expected to get NotExist 154 | if err != nil && !os.IsNotExist(err) { 155 | return fmt.Errorf("rbd: %s failed to check mountpoint", globalPDPath) 156 | } 157 | if !notMnt { 158 | return nil 159 | } 160 | 161 | /* 162 | globalPDPath是主机节点上代表了persistentvolume的目录,形如 "rbd"+{pool}+"-image-"+{image} 163 | devicePath则是形如 /dev/rbd0 164 | */ 165 | if err := os.MkdirAll(globalPDPath, 0750); err != nil { 166 | return fmt.Errorf("rbd: failed to mkdir %s, error", globalPDPath) 167 | } 168 | 169 | // fence off other mappers 170 | if err := util.fencing(b); err != nil { 171 | // rbd unmap before exit 172 | b.plugin.execCommand("rbd", []string{"unmap", devicePath}) 173 | return fmt.Errorf("rbd: image %s is locked by other nodes", b.Image) 174 | } 175 | // rbd lock remove needs ceph and image config 176 | // but kubelet doesn't get them from apiserver during teardown 177 | // so persit rbd config so upon disk detach, rbd lock can be removed 178 | // since rbd json is persisted in the same local directory that is used as rbd mountpoint later, 179 | // the json file remains invisible during rbd mount and thus won't be removed accidentally. 180 | util.persistRBD(b, globalPDPath) 181 | 182 | /* 183 | 格式化和mount 184 | ==>/pkg/util/mount/mount.go 185 | ==>func (mounter *SafeFormatAndMount) FormatAndMount 186 | */ 187 | if err = b.mounter.FormatAndMount(devicePath, globalPDPath, b.fsType, nil); err != nil { 188 | err = fmt.Errorf("rbd: failed to mount rbd volume %s [%s] to %s, error %v", devicePath, b.fsType, globalPDPath, err) 189 | } 190 | return err 191 | } 192 | ``` 193 | 194 | 195 | -------------------------------------------------------------------------------- /proxy/(01)Demo-Broadcaster的使用.md: -------------------------------------------------------------------------------- 1 | # kube-proxy对广播机制Broadcaster的使用 2 | 3 | 和介绍Event时候的Broadcaster不是同一个结构体,但功能是类似的,都是把消息进行广播,发送给所有的订阅者。 4 | 5 | ## broadcaster 6 | 7 | ```go 8 | package broadcaster 9 | 10 | import ( 11 | "sync" 12 | ) 13 | 14 | type Listener interface { 15 | // OnUpdate is invoked when a change is made to an object. 16 | //listener其实就是一个带有OnUpdate的interface 17 | OnUpdate(instance interface{}) 18 | } 19 | 20 | // ListenerFunc receives a representation of the change or object. 21 | type ListenerFunc func(instance interface{}) 22 | 23 | func (f ListenerFunc) OnUpdate(instance interface{}) { 24 | f(instance) 25 | } 26 | 27 | type Broadcaster struct { 28 | // Listeners for changes and their lock. 29 | //Broadcaster(广播事件)就是一个listener的集合 30 | listenerLock sync.RWMutex 31 | listeners []Listener 32 | } 33 | 34 | // NewBroadcaster registers a set of listeners that support the Listener interface 35 | // and notifies them all on changes. 36 | /* 37 | 这里的type Broadcaster struct 会被kube-proxy用到 38 | ==>/pkg/proxy/config/config.go 39 | ==>bcaster := config.NewBroadcaster() 40 | */ 41 | func NewBroadcaster() *Broadcaster { 42 | 43 | return &Broadcaster{} 44 | } 45 | 46 | //如何使用广播事件Broadcaster? 看add方法和notify方法,注册和通知 47 | // Add registers listener to receive updates of changes. 48 | func (b *Broadcaster) Add(listener Listener) { 49 | //首先当然是注册listener 50 | b.listenerLock.Lock() 51 | defer b.listenerLock.Unlock() 52 | b.listeners = append(b.listeners, listener) 53 | } 54 | 55 | // Notify notifies all listeners. 56 | func (b *Broadcaster) Notify(instance interface{}) { 57 | //然后就是事件通知了。 58 | b.listenerLock.RLock() 59 | listeners := b.listeners 60 | b.listenerLock.RUnlock() 61 | for _, listener := range listeners { 62 | listener.OnUpdate(instance) 63 | } 64 | } 65 | ``` 66 | 67 | ## main() 68 | ```go 69 | package main 70 | 71 | import ( 72 | "fmt" 73 | "proxy-broadcaster-demo/broadcaster" 74 | ) 75 | 76 | func main() { 77 | b := broadcaster.NewBroadcaster() 78 | // b.Notify(struct{}{}) 79 | 80 | ch := make(chan bool, 2) 81 | b.Add(broadcaster.ListenerFunc(func(object interface{}) { 82 | fmt.Println("I am listener one, get value: ", object) 83 | ch <- true 84 | })) 85 | b.Add(broadcaster.ListenerFunc(func(object interface{}) { 86 | fmt.Println("I am listener two, get value: ", object) 87 | ch <- true 88 | })) 89 | b.Notify("hello") 90 | <-ch 91 | <-ch 92 | } 93 | ``` 94 | 95 | 输出如下 96 | ``` 97 | I am listener one, get value: hello 98 | I am listener two, get value: hello 99 | ``` -------------------------------------------------------------------------------- /proxy/(04)iptables的DNAT和SNAT.md: -------------------------------------------------------------------------------- 1 | # iptables的DNAT和SNAT 2 | 3 | **Table of Contents** 4 | 5 | - [userspace](#userspace) 6 | - [iptables](#iptables) 7 | - [yaml文件](#yaml文件) 8 | - [DNAT](#dnat) 9 | - [SNAT](#snat) 10 | 11 | 12 | 13 | ## userspace 14 | kube-proxy会为每个service随机监听一个端口(proxy port ),并增加一条iptables规则:所以到clusterIP:Port 的报文都redirect到proxy port;kube-proxy从它监听的proxy port收到报文后,走round robin(默认)或者session affinity(会话亲和力,即同一client IP都走同一链路给同一pod服务),分发给对应的pod。 15 | 16 | 显然userspace会造成所有报文都走一遍用户态,性能不高,现在k8s已经不再使用了。 17 | 18 | ![userspace](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/userspace.png) 19 | 20 | ## iptables 21 | 既然用户态会增加性能损耗,那么有没有办法不走呢?实际上用户态也只是一个报文LB,通过iptables完全可以搞定。k8s下面这张图很清晰的说明了iptables方式与userspace方式的不同:`kube-proxy只是作为controller,而不是server`,真正服务的是内核的netfilter,体现在用户态则是iptables。 22 | 23 | kube-proxy的iptables方式也支持round robin(默认)和session affinity。 24 | 25 | ![iptables](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/iptables.png) 26 | 27 | iptables可以使用扩展模块来进行数据包的匹配,语法就是 `-m module_name`, 所以`-m tcp `的意思是使用`tcp`扩展模块的功能 (tcp扩展模块提供了 --dport, --tcp-flags, --sync等功能)。 28 | 29 | 其实只用 -p tcp 了话, iptables也会默认的使用 -m tcp 来调用 tcp模块提供的功能。 30 | 31 | 但是 `-p tcp` 和 `-m tcp` 是两个不同层面的东西,一个是说当前规则作用于 tcp 协议包,而后一是说明要使用iptables的tcp模块的功能 (--dport 等) 32 | 33 | 区分DNAT和SNAT,可以简单的由连接发起者是谁来区分。 34 | 35 | ## yaml文件 36 | rc文件 37 | ```yaml 38 | apiVersion: v1 39 | kind: ReplicationController 40 | metadata: 41 | name: registry 42 | namespace: default 43 | spec: 44 | replicas: 1 45 | selector: 46 | name: registry 47 | template: 48 | metadata: 49 | labels: 50 | name: registry 51 | spec: 52 | containers: 53 | - name: registry 54 | image: etcdimage:0.3 55 | ports: 56 | - containerPort: 22 57 | name: ssh 58 | ``` 59 | 60 | svc文件 61 | ```yaml 62 | apiVersion: v1 63 | kind: Service 64 | metadata: 65 | name: registry 66 | namespace: default 67 | spec: 68 | clusterIP: 10.10.10.2 69 | ports: 70 | - port: 22 71 | targetPort: 22 72 | name: ssh 73 | selector: 74 | name: registry 75 | ``` 76 | 77 | 创建出来的pod Ip地址是`172.17.0.5` 78 | 79 | ## DNAT 80 | 效果如下,分析其iptables规则 81 | ```shell 82 | [root@fqhnode01 proxy]# iptables-save |grep registry 83 | -A KUBE-SEP-JJDDRQDQBMLIVXO7 -s 172.17.0.5/32 -m comment --comment "default/registry:ssh" -j KUBE-MARK-MASQ 84 | 85 | -A KUBE-SEP-JJDDRQDQBMLIVXO7 -p tcp -m comment --comment "default/registry:ssh" -m tcp -j DNAT --to-destination 172.17.0.5:22 86 | 87 | -A KUBE-SERVICES -d 10.10.10.2/32 -p tcp -m comment --comment "default/registry:ssh cluster IP" -m tcp --dport 22 -j KUBE-SVC-ZMVASYV5VVSOQD2Z 88 | 89 | -A KUBE-SVC-ZMVASYV5VVSOQD2Z -m comment --comment "default/registry:ssh" -j KUBE-SEP-JJDDRQDQBMLIVXO7 90 | ``` 91 | 92 | 分析如下: 93 | 1. 第三条,从`KUBE-SERVICES`规则链开始,目的地址是`10.10.10.2/32`,使用tcp协议,`-m comment --comment`表示注释。符合规则的转发到规则链`KUBE-SVC-ZMVASYV5VVSOQD2Z` 94 | 95 | 2. 第四条,从KUBE-SVC-ZMVASYV5VVSOQD2Z转发到KUBE-SEP-JJDDRQDQBMLIVXO7 96 | 97 | 3. 第二条,当接收到tcp请求之后,进行DNAT操作,转发到地址 172.17.0.5:22 98 | 99 | 4. 至此,流量已经从svc转发到pod了 100 | 101 | 5. 第一条规则,应该是标记从pod往外发送的流量,打上标记`0x4000/0x4000`,后面会利用这个标记进行一些工作。 102 | 103 | 6. 如果一个svc后端有多个pod的话,默认情况下是会用round-robin算法随机其中一个后端。 104 | 105 | 106 | kube-proxy的iptables有个缺陷,即当pod故障时无法自动更新iptables规则,需要依赖readiness probes。 主要思想就是创建一个探测容器,当检测到后端pod挂了的时候,更新对应的iptables规则。 107 | 108 | 109 | ## SNAT 110 | 我们从上面看到的第一条规则,看看从pod往外发送的流量是如何进行SNAT操作的。 111 | ```shell 112 | [root@fqhnode01 proxy]# iptables-save |grep 0x4000/0x4000 113 | -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000 114 | 115 | -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE 116 | ``` 117 | 从第二条规则,可以看到,在POSTROUTING阶段,接受到的流量如果带有标签`0x4000/0x4000`,则进行`MASQUERADE`操作,即SNAT,源地址自动选择。 -------------------------------------------------------------------------------- /scheduler/预选和优选策略.md: -------------------------------------------------------------------------------- 1 | # 调度策略 2 | 3 | ## 版本说明 4 | v1.3.6 5 | 6 | ## 策略函数接口 7 | 所有的策略函数接口都必须满足下面两个接口要求,用户可以根据需求自行实现调度算法。见 plugin/pkg/scheduler/algorithm/types.go 8 | * FitPredicate,预选函数的接口,返回值为bool 9 | * PriorityFunction:优选函数的接口,前面可能会过滤多个node,这个函数给这些节点打分,返回各个节点的加权值。 10 | ```go 11 | // FitPredicate is a function that indicates if a pod fits into an existing node. 12 | // The failure information is given by the error. 13 | 14 | type FitPredicate func(pod *api.Pod, nodeInfo *schedulercache.NodeInfo) (bool, error) 15 | 16 | type PriorityFunction func(pod *api.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo, nodeLister NodeLister) (schedulerapi.HostPriorityList, error) 17 | 18 | type PriorityConfig struct { 19 | Function PriorityFunction 20 | Weight int 21 | } 22 | ``` 23 | 24 | ## 策略组合 25 | 之前提到在main()函数之前,init()函数会提前将DefaultProvider、FitPredicate、Priority注册到factory里面, 而DefaultProvider有默认的defaultPredicates()、defaultPriorities()。这是系统默认的策略组合。 26 | 27 | 如果用户需要自行设置策略组合,可以通过在kube-scheduler启动参数中添加`--policy-config-file`来指定要运用的Policies集合。 28 | ```yaml 29 | { 30 | "kind" : "Policy", 31 | "apiVersion" : "v1", 32 | "predicates" : [ 33 | {"name" : "PodFitsPorts"}, 34 | {"name" : "PodFitsResources"}, 35 | {"name" : "NoDiskConflict"}, 36 | {"name" : "NoVolumeZoneConflict"}, 37 | {"name" : "MatchNodeSelector"}, 38 | {"name" : "HostName"} 39 | ], 40 | "priorities" : [ 41 | {"name" : "LeastRequestedPriority", "weight" : 1}, 42 | {"name" : "BalancedResourceAllocation", "weight" : 1}, 43 | {"name" : "ServiceSpreadingPriority", "weight" : 1}, 44 | {"name" : "EqualPriority", "weight" : 1} 45 | ] 46 | } 47 | ``` 48 | 49 | ## 预选策略 50 | - PodFitsHostPorts 过滤端口冲突的机器 51 | - PodFitsResources 判断是否有足够的资源 52 | - NoDiskConflict 没有挂载点冲突 53 | - MatchNodeSelector 指定相同标签的node调度 54 | - HostName 指定机器调度 55 | - NoVolumeZoneConflict:和云提供商相关。 56 | - PodFitsResources:检查主机的资源是否满足Pod的需求。根据实际已经分配的资源量做调度,而不是使用已实际使用的资源量做调度。 57 | - PodFitsHostPorts:检查Pod内每一个容器所需的HostPort是否已被其它容器占用。如果有所需的HostPort不满足需求,那么Pod不能调度到这个主机上。 58 | - HostName:检查主机名称是不是Pod指定的HostName。 59 | - MatchNodeSelector:检查主机的标签是否满足Pod的nodeSelector属性需求。 60 | - NoDiskConflict:检查在此主机上是否存在卷冲突。如果这个主机已经挂载了卷,其它同样使用这个卷的Pod不能调度到这个主机上。 Ceph RBD不允许任何两个pods分享相同的monitor,match pool和 image。 61 | 62 | 其中选中默认的预选策略如下 63 | ```go 64 | func defaultPredicates() sets.String { 65 | return sets.NewString( 66 | // Fit is determined by non-conflicting disk volumes. 67 | factory.RegisterFitPredicate("NoDiskConflict", predicates.NoDiskConflict), 68 | // Fit is determined by volume zone requirements. 69 | factory.RegisterFitPredicateFactory( 70 | "NoVolumeZoneConflict", 71 | func(args factory.PluginFactoryArgs) algorithm.FitPredicate { 72 | return predicates.NewVolumeZonePredicate(args.PVInfo, args.PVCInfo) 73 | }, 74 | ), 75 | // Fit is determined by whether or not there would be too many AWS EBS volumes attached to the node 76 | factory.RegisterFitPredicateFactory( 77 | "MaxEBSVolumeCount", 78 | func(args factory.PluginFactoryArgs) algorithm.FitPredicate { 79 | // TODO: allow for generically parameterized scheduler predicates, because this is a bit ugly 80 | maxVols := getMaxVols(aws.DefaultMaxEBSVolumes) 81 | return predicates.NewMaxPDVolumeCountPredicate(predicates.EBSVolumeFilter, maxVols, args.PVInfo, args.PVCInfo) 82 | }, 83 | ), 84 | // Fit is determined by whether or not there would be too many GCE PD volumes attached to the node 85 | factory.RegisterFitPredicateFactory( 86 | "MaxGCEPDVolumeCount", 87 | func(args factory.PluginFactoryArgs) algorithm.FitPredicate { 88 | // TODO: allow for generically parameterized scheduler predicates, because this is a bit ugly 89 | maxVols := getMaxVols(DefaultMaxGCEPDVolumes) 90 | return predicates.NewMaxPDVolumeCountPredicate(predicates.GCEPDVolumeFilter, maxVols, args.PVInfo, args.PVCInfo) 91 | }, 92 | ), 93 | // GeneralPredicates are the predicates that are enforced by all Kubernetes components 94 | // (e.g. kubelet and all schedulers) 95 | factory.RegisterFitPredicate("GeneralPredicates", predicates.GeneralPredicates), 96 | 97 | // Fit is determined based on whether a pod can tolerate all of the node's taints 98 | factory.RegisterFitPredicateFactory( 99 | "PodToleratesNodeTaints", 100 | func(args factory.PluginFactoryArgs) algorithm.FitPredicate { 101 | return predicates.NewTolerationMatchPredicate(args.NodeInfo) 102 | }, 103 | ), 104 | 105 | // Fit is determined by node memory pressure condition. 106 | factory.RegisterFitPredicate("CheckNodeMemoryPressure", predicates.CheckNodeMemoryPressurePredicate), 107 | ) 108 | } 109 | ``` 110 | 111 | ## 优选策略 112 | - LeastRequestedPriority:如果新的pod要分配给一个节点,这个节点的优先级就由节点空闲的那部分与总容量的比值(即(总容量-节点上pod的容量总和-新pod的容量)/总容量)来决定。CPU和memory权重相当,比值最大的节点的得分最高。需要注意的是,这个优先级函数起到了按照资源消耗来跨节点分配pods的作用。 113 | - BalancedResourceAllocation:尽量选择在部署Pod后各项资源更均衡的机器。BalancedResourceAllocation不能单独使用,而且必须和LeastRequestedPriority同时使用,它分别计算主机上的cpu和memory的比重,主机的分值由cpu比重和memory比重的“距离”决定。 114 | - SelectorSpreadPriority:对于属于同一个service、replication controller的Pod,尽量分散在不同的主机上。如果指定了区域,则会尽量把Pod分散在不同区域的不同主机上。调度一个Pod的时候,先查找Pod对于的service或者replication controller,然后查找service或replication controller中已存在的Pod,主机上运行的已存在的Pod越少,主机的打分越高。 115 | - CalculateAntiAffinityPriority:对于属于同一个service的Pod,尽量分散在不同的具有指定标签的主机上。 116 | - ImageLocalityPriority:根据主机上是否已具备Pod运行的环境来打分。ImageLocalityPriority会判断主机上是否已存在Pod运行所需的镜像,根据已有镜像的大小返回一个0-10的打分。如果主机上不存在Pod所需的镜像,返回0;如果主机上存在部分所需镜像,则根据这些镜像的大小来决定分值,镜像越大,打分就越高。 117 | 118 | DefaultProvider配置的默认Priorities Policies 119 | ```go 120 | func defaultPriorities() sets.String { 121 | return sets.NewString( 122 | // Prioritize nodes by least requested utilization. 123 | factory.RegisterPriorityFunction("LeastRequestedPriority", priorities.LeastRequestedPriority, 1), 124 | // Prioritizes nodes to help achieve balanced resource usage 125 | factory.RegisterPriorityFunction("BalancedResourceAllocation", priorities.BalancedResourceAllocation, 1), 126 | // spreads pods by minimizing the number of pods (belonging to the same service or replication controller) on the same node. 127 | factory.RegisterPriorityConfigFactory( 128 | "SelectorSpreadPriority", 129 | factory.PriorityConfigFactory{ 130 | Function: func(args factory.PluginFactoryArgs) algorithm.PriorityFunction { 131 | return priorities.NewSelectorSpreadPriority(args.PodLister, args.ServiceLister, args.ControllerLister, args.ReplicaSetLister) 132 | }, 133 | Weight: 1, 134 | }, 135 | ), 136 | factory.RegisterPriorityConfigFactory( 137 | "NodeAffinityPriority", 138 | factory.PriorityConfigFactory{ 139 | Function: func(args factory.PluginFactoryArgs) algorithm.PriorityFunction { 140 | return priorities.NewNodeAffinityPriority(args.NodeLister) 141 | }, 142 | Weight: 1, 143 | }, 144 | ), 145 | factory.RegisterPriorityConfigFactory( 146 | "TaintTolerationPriority", 147 | factory.PriorityConfigFactory{ 148 | Function: func(args factory.PluginFactoryArgs) algorithm.PriorityFunction { 149 | return priorities.NewTaintTolerationPriority(args.NodeLister) 150 | }, 151 | Weight: 1, 152 | }, 153 | ), 154 | ) 155 | } 156 | ``` 157 | 158 | ## 参考 159 | [Kubernetes调度详解](http://dockone.io/article/2885) 160 | 161 | [Kubernetes Scheduler原理解析](http://blog.csdn.net/waltonwang/article/details/54409917) -------------------------------------------------------------------------------- /常用的网站.md: -------------------------------------------------------------------------------- 1 | # 常用的网站 2 | 3 | 4 | [iptables系列文章](http://www.zsythink.net/archives/tag/iptables/page/2/) 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /深入go/(01)基本类型.md: -------------------------------------------------------------------------------- 1 | # 基本类型 2 | 3 | 参考[深入解析go语言](https://www.w3cschool.cn/go_internals/go_internals-tdys282g.html) 4 | 5 | 以go-1.8.3进行研究 6 | 7 | ## 在内存中的形式 8 | 首先看一下在go中,一些基础类型在内存中是以什么形态存在的,如下图所示: 9 | 10 | ![基础类型在内存中的存在形式](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/基础类型在内存中的存在形式.png) 11 | 12 | 变量j的类型是int32, 而变量i的类型是int,两者不是同一个类型,所以赋值操作`i=j`是一种类型错误`cannot use j (type int32) as type int in assignment`。 13 | 14 | 正确的方式应该是 15 | ```go 16 | i := int(7) 17 | j := int32(7) 18 | i = int(j) 19 | ``` 20 | 21 | 结构体的域在内存中是紧密排列的。 22 | 23 | ## 静态类型和底层类型 24 | byte是Go的静态类型,uint8是Go的底层类型 25 | 26 | rune是int32的别名,用于表示unicode字符。通常在处理中文的时候需要用到它 27 | 28 | ## string类型 29 | 定义在`go-1.8.3/go/src/runtime/string.go` 30 | ```go 31 | type stringStruct struct { 32 | str unsafe.Pointer 33 | len int 34 | } 35 | ``` 36 | 两个属性:一个指针,一个int型的长度,都是私有成员! 37 | 38 | ![go对string类型的定义](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/go对string类型的定义.png) 39 | 40 | string类型类型在Go语言的内存模型中用一个2字长的数据结构表示。 41 | 从上图可以看出,其实多个string是共享一个存储的。 42 | 43 | `str[i:j]`进行字符串切片操作,会得到一个新的`type stringStruct struct`对象,该对象的指针依然还是指向str的底层存储,长度为`j-i`。 44 | 这说明字符串切分不涉及内存分配或复制操作,其效率等同于传递下标。 45 | 46 | 内建函数len()对string类型的操作是直接从底层结构中取出len值,而不需要额外的操作 47 | 48 | ## slice类型 49 | 定义在`/go-1.8.3/src/runtime/slice.go` 50 | ```go 51 | type slice struct { 52 | array unsafe.Pointer 53 | len int 54 | cap int 55 | } 56 | ``` 57 | 显然,type slice struct和上面的type stringStruct struct很类似,只是多了一个cap属性。 58 | 59 | ![slice的定义](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/slice的定义.png) 60 | 61 | 一个slice是一个底层数组的部分引用。同理,对底层数据进行切片操作也不会涉及到内存分配或复制操作,仅仅是新建了一个`type slice struct`对象而已! 62 | 63 | 需要注意的是,在上图中,y[0:4]是有效的,打印出来的结果会是`[3,5,7,11]` 64 | 65 | 由于slice是不同于指针的多字长结构,分割操作并不需要分配内存,甚至没有通常被保存在堆中的slice头部。这种表示方法使slice操作和在C中传递指针、长度对一样廉价。 66 | 67 | slice相关的函数有如下几个,是不是感觉很熟悉。 68 | ```go 69 | func makeslice(et *_type, len64, cap64 int64) slice 70 | func growslice(et *_type, old slice, cap int) slice 71 | func slicecopy(to, fm slice, width uintptr) int 72 | func slicestringcopy(to []byte, fm string) int 73 | ``` 74 | 75 | ### slice的扩容 76 | 对slice进行append操作时,可能会造成扩容操作。扩容规则如下: 77 | - 如果新的大小是当前大小2倍以上,则大小增长为新大小 78 | - 否则循环以下操作:如果当前长度len小于1024,按每次2倍增长,否则每次按当前cap的1/4增长。直到增长的大小超过或等于新大小。 79 | 80 | ```go 81 | newcap := old.cap 82 | doublecap := newcap + newcap 83 | //和old.cap的double进行比较 84 | if cap > doublecap { 85 | newcap = cap 86 | } else { 87 | if old.len < 1024 { 88 | newcap = doublecap 89 | } else { 90 | for newcap < cap { 91 | newcap += newcap / 4 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | ### slice与unsafe.Pointer相互转换 98 | 1. 利用make+slice弄块内存出来自己管理。。。。这个比较牛逼了 99 | ```go 100 | s := make([]byte, 200) 101 | ptr := unsafe.Pointer(&s[0]) 102 | ``` 103 | 104 | 2. 基于内存指针ptr构造出一个slice 105 | ```go 106 | var o []byte 107 | sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&o))) 108 | sliceHeader.Cap = length 109 | sliceHeader.Len = length 110 | sliceHeader.Data = uintptr(ptr) 111 | ``` 112 | 113 | ## new和make 114 | 了解完Go对`type slice struct`的定义之后,再来理解new和make的差异就简单得多了。 115 | 116 | - new(T),仅分配内存,不进行初始化。返回的`*T`指向一个类型T的零值。 117 | - make(T, args),分配内存,且进行初始化。返回是`T`本身。因为T本身就是一个引用类型。 118 | 119 | 以下属声明的类型为例子,分别用new和make的效果如下图: 120 | ```go 121 | type Point struct { X, Y int } 122 | type Rect1 struct { Min, Max Point } 123 | type Rect2 struct { Min, Max *Point } 124 | ``` 125 | 126 | ![new和make的区别](https://github.com/Kevin-fqh/learning-k8s-source-code/blob/master/images/new和make的区别.png) 127 | 128 | ## 参考 129 | 本系列参考[深入解析go语言](https://www.w3cschool.cn/go_internals/go_internals-tdys282g.html) 130 | --------------------------------------------------------------------------------