由于最新分支在命令方面做了改变,与我研究的docker engine分支(1.13.0)不匹配,决定下个老点的分支来研究.具体方法是直接git下源码,然后:
root@os:/opt/goHome/workspace/src/github.com/docker/containerd_bak# git tag
0.0.2
0.0.3
0.0.4
0.0.5
v0.1.0
v0.2.0
v0.2.1
v0.2.2
v0.2.3
v0.2.4
v0.2.5
root@os:/opt/goHome/workspace/src/github.com/docker/containerd_bak# git reset --hard v0.2.5
HEAD is now at 2a5e70c Use golang.org/x/net/context for now
好了回到我们的正题.docker engine是通过gRPC与containerd通信的.网上摘了段gRPC的定义:
gRPC是一个高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。
还有一个golang使用gRPC的例子(不管message,直接看服务端代码和客户端代码,因为我们是要研究结果代码):
服务端代码
package main
import (
"log"
"net"
pb "your_path_to_gen_pb_dir/helloworld"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
port = ":50051"
)
// server is used to implement helloworld.GreeterServer.
type server struct{}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
s.Serve(lis)
}
客户端代码
package main
import (
"log"
"os"
pb "your_path_to_gen_pb_dir/helloworld"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
address = "localhost:50051"
defaultName = "world"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
}
然后我们参照上面服务端代码例子的代码研究containerd.
入口函数在docker\containerd\containerd\main.go
func main() {
logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: time.RFC3339Nano})
app := cli.NewApp()
app.Name = "containerd"
if containerd.GitCommit != "" {
app.Version = fmt.Sprintf("%s commit: %s", containerd.Version, containerd.GitCommit)
} else {
app.Version = containerd.Version
}
app.Usage = usage
app.Flags = daemonFlags
app.Before = func(context *cli.Context) error {
setupDumpStacksTrap()
if context.GlobalBool("debug") {
logrus.SetLevel(logrus.DebugLevel)
if context.GlobalDuration("metrics-interval") > 0 {
if err := debugMetrics(context.GlobalDuration("metrics-interval"), context.GlobalString("graphite-address")); err != nil {
return err
}
}
}
if p := context.GlobalString("pprof-address"); len(p) > 0 {
pprof.Enable(p)
}
if err := checkLimits(); err != nil {
return err
}
return nil
}
//最终会用来执行命令
app.Action = func(context *cli.Context) {
if err := daemon(context); err != nil {
logrus.Fatal(err)
}
}
if err := app.Run(os.Args); err != nil {
logrus.Fatal(err)
}
}
可以看到最后执行Run函数,Run函数里面主要做的是执行Before,解析参数到context里面等工作,最后执行app.Action,进而执行daemon:
func daemon(context *cli.Context) error {
s := make(chan os.Signal, 2048)
signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
// Supervisor represents a container supervisor
//容器管理器
sv, err := supervisor.New(
context.String("state-dir"),
context.String("runtime"),
context.String("shim"),
context.StringSlice("runtime-args"),
context.Duration("start-timeout"),
context.Int("retain-count"))
if err != nil {
return err
}
wg := &sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
//专门用于启动容器的工作队列
w := supervisor.NewWorker(sv, wg)
go w.Start()
}
//开启内部消息处理loop
if err := sv.Start(); err != nil {
return err
}
// Split the listen string of the form proto://addr
listenSpec := context.String("listen")
listenParts := strings.SplitN(listenSpec, "://", 2)
if len(listenParts) != 2 {
return fmt.Errorf("bad listen address format %s, expected proto://address", listenSpec)
}
//包含启动GRPC服务
server, err := startServer(listenParts[0], listenParts[1], sv)
if err != nil {
return err
}
for ss := range s {
switch ss {
//收到Ctr+C等中断信息,其他都不反应
default:
logrus.Infof("stopping containerd after receiving %s", ss)
server.Stop()
os.Exit(0)
}
}
return nil
}
在deamon中主要干了三件事:
第一:开启容器启动工作队列
w := supervisor.NewWorker(sv, wg)
第二:开启内部消息loop
if err := sv.Start(); err != nil {
return err
}
第三:启用gRPC服务
server, err := startServer(listenParts[0], listenParts[1], sv)
我们先了解第三件事具体做了什么:
func startServer(protocol, address string, sv *supervisor.Supervisor) (*grpc.Server, error) {
// TODO: We should use TLS.
// TODO: Add an option for the SocketGroup.
sockets, err := listeners.Init(protocol, address, "", nil)
if err != nil {
return nil, err
}
if len(sockets) != 1 {
return nil, fmt.Errorf("incorrect number of listeners")
}
l := sockets[0]
s := grpc.NewServer()
//注册gRPC服务
types.RegisterAPIServer(s, server.NewServer(sv))
healthServer := health.NewHealthServer()
grpc_health_v1.RegisterHealthServer(s, healthServer)
go func() {
logrus.Debugf("containerd: grpc api on %s", address)
if err := s.Serve(l); err != nil {
logrus.WithField("error", err).Fatal("containerd: serve grpc")
}
}()
return s, nil
}
参照gRPC服务端的例子,我们需要三件东西:
1.监听对象lis:
lis, err := net.Listen("tcp", port)
2.gRPC服务对象s:
s := grpc.NewServer()
3.封装的服务函数对象&server{}:
pb.RegisterGreeterServer(s, &server{})
s.Serve(lis)
这三个我们在startServer函数中都能发现,分别是:
l := sockets[0]
s := grpc.NewServer()
//注册gRPC服务
types.RegisterAPIServer(s, server.NewServer(sv))
l和s都是一样的,不同只有封装的服务函数对象server.NewServer(sv):
// NewServer returns grpc server instance
func NewServer(sv *supervisor.Supervisor) types.APIServer {
return &apiServer{
//初始化在docker\containerd\containerd\main.go的main函数
sv: sv,
}
}
可以看到实际返回的是apiServer,是一些API的封装,docker engine可以方便得调用:
后面我们分析docker run启动容器命令的时候,将详细分析其中一些接口.