以非root账号方式运行Docker

47 min read

Docker daemon 绑定到 Unix socket, 而不是 TCP Port. 默认情况下只有 root 账号可以访问 Unix socket, 所以一般需要操作Docker时需要使用 sudo docker 的方式.

下面介绍如果使用docker命令而不需要root账号:

# 创建 docker group
sudo groupadd docker # 如果是使用包管理器安装docker, 有可能已经创建了这个group

# 把当前用户添加到该 group
sudo usermod -aG docker $USER

# 使用以下命令激活该组, 或者退出重新登陆
newgrp docker

然后执行docker命令就不需要借助sudo了, Visual Studio Code 也可以正常使用容器开发了.

Unix socket 和 TCP Port 区别

Unix socket 和 TCP port 是两种不同的通信方式,适用于不同的场景。

  1. Unix socket:是一种在同一台机器上进行进程间通信的机制,通过文件系统上的一个 socket 文件来实现通信。它不需要网络协议栈,通信速度更快,更适合在同一台机器上进行进程间通信。
  2. TCP Port:是一种通过网络协议栈进行进程间通信的机制,通过 IP 地址和端口号来实现通信。它可以在不同台机器之间进行通信,适合在不同台机器之间进行进程间通信。

Unix socket 实现通信的原理

首先,进程需要创建一个 socket 文件,这个文件通常位于 /tmp 目录下。创建 socket 文件时,进程可以指定文件权限,以便其他进程可以读写该文件。

接下来,进程需要绑定 socket 文件,这样其他进程才能连接到该 socket。绑定 socket 文件后,进程需要调用 listen 函数来监听该 socket。

当其他进程需要与该 socket 连接时,需要先调用 connect 函数来连接该 socket。连接成功后,就可以在该 socket 上进行读写操作。

在两个进程连接成功后,就可以在该 socket 上进行读写操作来进行通信。读写操作可以使用 read 和 write 函数来实现。

如何创建创建一个 socket 文件

在 Linux 系统中,可以使用 socket 函数来创建一个 socket 文件。

下面是一个示例代码:

#include <sys/socket.h>

int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) {
    // handle error
}

其中, AF_UNIX 是地址族,表示使用 Unix socket; SOCK_STREAM 是套接字类型, 表示使用 TCP 协议.

如果成功,函数会返回一个 socket 文件描述符,该文件描述符可以用来进行后续的 socket 操作。

另外, 在使用时还需要注意权限的问题, 因为 socket文件是普通文件, 所以需要为其设置权限, 以便其他进程可以访问.

在 Go 语言中,使用 net 包来实现 socket 通讯

 Go 语言中可以使用 net 包来实现 socket 通讯

下面是一个简单的示例代码它展示了如何创建一个服务端 socket 和一个客户端 socket并在两者之间进行通信

package main

import (
        "fmt"
        "net"
)

func main() {
        // 创建服务端
        listener, err := net.Listen("tcp", ":8080")
        if err != nil {
                fmt.Println("Error listening:", err.Error())
                return
        }
        defer listener.Close()

        fmt.Println("Listening on :8080")

        for {
                conn, err := listener.Accept()
                if err != nil {
                        fmt.Println("Error accepting: ", err.Error())
                        return
                }
                go handleRequest(conn)
        }
}

func handleRequest(conn net.Conn) {
        defer conn.Close()

        buf := make([]byte, 1024)

        // 读取客户端数据
        _, err := conn.Read(buf)
        if err != nil {
                fmt.Println("Error reading:", err.Error())
        }

        // 发送数据到客户端
        conn.Write([]byte("Hello World!"))
}
import (
        "fmt"
        "net"
)

func main() {
        // 创建客户端
        conn, err := net.Dial("tcp", "localhost:8080")
      if err != nil {
      fmt.Println("Error dialing:", err.Error())
      return
      }
      defer conn.Close()
            // 发送数据到服务端
          conn.Write([]byte("Hello Server!"))

          buf := make([]byte, 1024)

          // 读取服务端数据
          _, err = conn.Read(buf)
          if err != nil {
                  fmt.Println("Error reading:", err.Error())
          }

          fmt.Println("Server says:", string(buf))

}