0%

Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。

主要指令

指令 行为
FROM 定制的镜像都是基于 FROM 的镜像,例如nginx
WORKDIR 指定下面的shell语句指定运行在哪个路径下
COPY 将宿主机下的目录文件复制到镜像中去
RUN 执行后面跟着的命令行命令
CMD 指定整个镜像运行起来后执行的脚本 (一般都是阻塞式的脚本)
1
2
3
4
5
FROM alpine
WORKDIR /app
COPY src/ /app
RUN echo 321 >> 1.txt
CMD tail -f 1.txt

COPY vs ADD

两者功能差不多,但是ADD的源文件除了是本地文件资源,还可以是一个url

CMD vs ENTRYPOINT

ENTRYPOINT

类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。

但是, 如果运行 docker run 时使用了 –entrypoint 选项,此选项的参数可当作要运行的程序覆盖 ENTRYPOINT 指令指定的程序。

优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。

注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。

在 Dockerfile 文件的存放目录下,通过docker build -t <镜像名>:<版本号>命令来执行。

当两者混用时

  • entrypoint非json,则以entrypoint为准。
  • 如果entrypoint和cmd都是json,则以entrypoint+cmd拼接成的shell为准。

expose

expose仅仅只是声明端口。

作用:

  • 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。
  • 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

VOLUME

定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。

作用:

  • 避免重要的数据,因容器重启而丢失,这是非常致命的。
  • 避免容器不断变大。

ENV

设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。

ARG

构建参数,与 ENV 作用一至。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。

label

标识(没实际作用)

onbuild

onbuild env C = 10

在本次构建镜像的过程中不会执行(假设镜像为 test-build)。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build

资料

Dockerfile
Dockerfile用法全解析

Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。

Mac 用户不需要单独安装 Compose,因为Docker 桌面版已经包含

1
2
➜  blogs docker-compose --version
docker-compose version 1.24.1, build 4667896b

Compose 使用的三个步骤:

1.通过Dockerfile定义容器环境,打包成镜像

2.通过docker-compose.yml定义各应用服务

3.通过docker-compose up命令来启动所有容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# yaml 配置实例
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
- logvolume01:/var/log
links:
- redis
redis:
image: redis
volumes:
logvolume01: {}

Docker Compose
实战~如何组织一个多容器项目docker-compose

RPC原理

RPC与Restful接口的调用

不关心调用方式,通过网络传输
RPC相对高效,http接口相对灵活

restful接口更适合终端与服务端之间的交互,rpc更适合微服务的应用间通讯

ProtoBuf

ProtoBuf是一种跨语言和跨平台的数据序列化协议;
与XML/JSON相比,序列化效率更快、体积更小、更安全;
与XML/JSON相比,可读性差、灵活性较低;
自带编译器,定义proto源文件,可编译成多种语言的代码;

gRPC框架

框架go-micro

专注于微服务的一种RPC框架;
提供分布式系统相关的接口集合;

服务发现:支持服务注册与发现

负载均衡:rpc服务间的请求调度均衡策略

同步通信:基于RPC通信,支持单项、双向流通信模式

异步通信:提供pub\sub通信模型的接口

高级接口:比如服务发现,提供调用的接口是一致的

学习资料:
gin项目地址
gin中文文档
Gin入门实战
文中demo

Gin运行流程

engine:实现了ServeHTTP接口的handler
methodTree:根据http请求方法分别维护的路由树
routerGroup:将路由表分组,方便中间件统一处理
Context:Gin的上下文,在handler之间传递参数

示例代码

Router :路由规则定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import (
hdl "filestore-server/handler"

"github.com/gin-gonic/gin"
)

// Router :路由规则定义
func Router() *gin.Engine {
// gin framework
router := gin.Default()

// 静态资源处理
router.Static("/static/", "./static")

// 定义接口
router.GET("/user/signup", hdl.SignupHandler)
router.POST("/user/signup", hdl.DoSignupHandler)

return router
}

handler

1
2
3
4
// SignupHandler : 处理用户注册请求
func SignupHandler(c *gin.Context) {
c.Redirect(http.StatusFound, "http://"+c.Request.Host+"/static/view/signup.html")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// DoSignupHandler : 处理用户注册请求
func DoSignupHandler(c *gin.Context) {
username := c.Request.FormValue("username")
passwd := c.Request.FormValue("password")

if len(username) < 3 || len(passwd) < 5 {
c.JSON(http.StatusOK,
gin.H{
"msg": "Invalid parameter",
})
return
}

// 对密码进行加盐及取Sha1值加密
encPasswd := util.Sha1([]byte(passwd + pwdSalt))
// 将用户信息注册到用户表中
suc := dblayer.UserSignup(username, encPasswd)
if suc {
c.JSON(http.StatusOK,
gin.H{
"code": 0,
"msg": "注册成功",
"data": nil,
"forward": "/user/signin",
})
} else {
c.JSON(http.StatusOK,
gin.H{
"code": 0,
"msg": "注册失败",
"data": nil,
})
}
}

最终main.go可简洁为

1
2
3
4
5
6
7
8
9
10
func main() {
// gin framework
router := route.Router()

// 启动服务并监听端口
err := router.Run(config.UploadServiceHost)
if err != nil {
fmt.Printf("Failed to start server, err:%s\n", err.Error())
}
}

快速开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
"fmt"

"github.com/gin-gonic/gin"
)

func main() {
r := gin.New()
r.Use(gin.Logger())

r.Use(gin.Recovery())

r.GET("first", func(c *gin.Context) {
fmt.Println("first .........")
c.JSON(200, gin.H{
"message": "pong",
})
})

authorized := r.Group("/try")

authorized.POST("/second", second)
authorized.POST("/third", third)

// 嵌套路由组
testing := authorized.Group("testing")
testing.GET("/forth", fourth)

// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}

func second(c *gin.Context) {
fmt.Println("second .........")
}

func third(c *gin.Context) {
fmt.Println("third .........")
}

func fourth(c *gin.Context) {
fmt.Println("fourth .........")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
➜  gin-demo git:(master) ✗ lsof -i tcp:8080    
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
QQ 327 samtake 29u IPv4 0xfdcbb6b79716f31f 0t0 TCP huangloshansmbp:50202->157.255.13.190:http-alt (ESTABLISHED)
QQ 327 samtake 48u IPv4 0xfdcbb6b79716f31f 0t0 TCP huangloshansmbp:50202->157.255.13.190:http-alt (ESTABLISHED)
Google 420 samtake 26u IPv4 0xfdcbb6b793e58fb7 0t0 TCP localhost:51492->localhost:http-alt (ESTABLISHED)
Postman 66875 samtake 67u IPv4 0xfdcbb6b778668ca7 0t0 TCP localhost:51499->localhost:http-alt (ESTABLISHED)
main 67553 samtake 3u IPv6 0xfdcbb6b793ea9b8f 0t0 TCP *:http-alt (LISTEN)
main 67553 samtake 7u IPv6 0xfdcbb6b793eac3cf 0t0 TCP localhost:http-alt->localhost:51492 (ESTABLISHED)
main 67553 samtake 8u IPv6 0xfdcbb6b7912a55cf 0t0 TCP localhost:http-alt->localhost:51499 (ESTABLISHED)
➜ gin-demo git:(master) ✗ kill -9 67553
➜ gin-demo git:(master) ✗ go run start/main.go
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET /first --> main.main.func1 (3 handlers)
[GIN-debug] POST /try/second --> main.second (3 handlers)
[GIN-debug] POST /try/third --> main.third (3 handlers)
[GIN-debug] GET /try/testing/forth --> main.fourth (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080
first .........
[GIN] 2020/02/25 - 11:51:19 | 200 | 176.613µs | 127.0.0.1 | GET /first

1.请求路由

多种请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
r := gin.Default()

r.GET("get", func(c *gin.Context) {
c.String(200, "get")
})

r.POST("post", func(c *gin.Context) {
c.String(200, "post")
})

r.DELETE("delete", func(c *gin.Context) {
c.String(200, "delete")
})

r.Any("/any", func(c *gin.Context) {
c.String(200, "any")
})

r.Run()
}

给我们创建的any请求几乎覆盖了所有种类的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[GIN-debug] GET    /get                      --> main.main.func1 (3 handlers)
[GIN-debug] POST /post --> main.main.func2 (3 handlers)
[GIN-debug] DELETE /delete --> main.main.func3 (3 handlers)
[GIN-debug] GET /any --> main.main.func4 (3 handlers)
[GIN-debug] POST /any --> main.main.func4 (3 handlers)
[GIN-debug] PUT /any --> main.main.func4 (3 handlers)
[GIN-debug] PATCH /any --> main.main.func4 (3 handlers)
[GIN-debug] HEAD /any --> main.main.func4 (3 handlers)
[GIN-debug] OPTIONS /any --> main.main.func4 (3 handlers)
[GIN-debug] DELETE /any --> main.main.func4 (3 handlers)
[GIN-debug] CONNECT /any --> main.main.func4 (3 handlers)
[GIN-debug] TRACE /any --> main.main.func4 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

静态文件夹

1
2
3
4
5
6
7
8
9
func main() {
r := gin.Default()
// 静态文件夹绑定+路由,有两种写法:
r.Static("/assets", "./assets")
r.StaticFS("/static", http.Dir("static"))

//又或者路由+资源
r.StaticFile("/favicon.ico", "/favicon.ico")
}

go build -o router_static && ./router_static这需要在routerstatic文件夹下运行,不然找不到资源文件,最后访问http://localhost:8080/assets/a.html以及http://localhost:8080/static/b.html测试即可。

参数作为url

Get请求http://localhost:8080/Sam/520即可得到相应参数

1
2
3
4
{
"id": "520",
"name": "Sam"
}

2.获取请求参数

  • 获取get请求参数
  • 获取post请求参数
  • 获取body值
  • 获取参数绑定结构体

泛绑定

1
2
3
4
5
6
7
8
func main() {
r := gin.Default()
r.GET("/user/*action", func(c *gin.Context) { //设置获取name和id的参数
c.String(200, "泛绑定~")
})

r.Run()
}

所有user前缀的请求都能请求到:http://localhost:8080/user/<XXXX>

获取get参数

1
2
3
4
5
6
7
8
9
10
11
func main() {
r := gin.Default()
r.GET("/testGetParam", func(c *gin.Context) { //设置获取name和id的参数
param1 := c.Query("parm1")
param2 := c.Query("parm2")

c.String(http.StatusOK, "%s, %s", param1, param2)
})

r.Run()
}
1
2
3
4
➜  ~ curl -X GET 'http://127.0.0.1:8080/testGetParam?parm1=11111' 
11111, %
➜ ~ curl -X GET 'http://127.0.0.1:8080/testGetParam?parm1=11111&parm2=2222'
11111, 2222%

获取body内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
r := gin.Default()
r.POST("/parmBody", func(c *gin.Context) {
bodyBytes, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.String(http.StatusBadRequest, err.Error())
c.Abort()
}
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) //重新回写到缓存才能拿到parm1 parm2
parm1 := c.PostForm("parm1")
parm2 := c.PostForm("parm2")
c.String(http.StatusOK, "%s %s %s", parm1, parm2, string(bodyBytes))
})

r.Run()
}
1
2
3
4
➜  ~ curl -X POST 'http://127.0.0.1:8080/parmBody' -d 'parm1=value1&parm2=value2' 
value1 value2 parm1=value1&parm2=value2%
➜ ~ curl -X POST 'http://127.0.0.1:8080/parmBody' -d '{"parm1":"value1","parm2":"value2"}'
{"parm1":"value1","parm2":"value2"}

获取bind参数

同时响应post和get,同时访问到同一个回调方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Person struct {
Name string `form:"name"` //设置tag `form`可以由参数转变成结构体
Address string `form:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-03"`
}

func main() {
r := gin.Default()
r.GET("/test", testHandler)
r.POST("/test", testHandler)
r.Run()
}

func testHandler(c *gin.Context) {
var person Person
//根据请求content-type来作不同的binding操作
if err := c.ShouldBind(&person); err == nil {
c.String(200, "%v", person)
} else {
c.String(200, "person bind error", err)
}

}
1
2
3
4
5
6
7
8
9
10
➜  ~ curl -X GET  'http://127.0.0.1:8080/test?name=samtake&adress=gd'
{samtake 0001-01-01 00:00:00 +0000 UTC}
➜ ~ curl -X GET 'http://127.0.0.1:8080/test?name=samtake&address=gd'
{samtake gd 0001-01-01 00:00:00 +0000 UTC}%
➜ ~ curl -X POST 'http://127.0.0.1:8080/test?name=samtake&address=gd&birthday=2008-09-09'
{samtake gd 2008-09-01 09:00:00 +0800 CST}%
➜ ~ curl -X POST 'http://127.0.0.1:8080/test' -d 'name=samtake&address=gd&birthday=2008-09-09'
{samtake gd 2008-09-01 09:00:00 +0800 CST}%
➜ ~ curl -H "Content-Type:application/json" -X POST "http://127.0.0.1:8080/test" -d '{"name":"wang"}'
{wang 0001-01-01 00:00:00 +0000 UTC}%

这里有个坑:如果时间格式写成像time_format:"2006-01-03"这种会报错,解析不了。

1
person bind error%!(EXTRA *time.ParseError=parsing time "2008-00-08" as "2011-01-03": cannot parse "-00-08" as "1")

验证请求参数

  • 结构体验证
  • 自定义验证
  • 支持多语言错误信息

结构体验证

validate规则
binding条件满足

坑的记录:

1
2
3
4
5
type Person struct {
Age int `form:"age" binding:"required,gt=10"`
Name string `form:"name" binding:"required"`
Address string `form:"address" binding:"required"`
}

正确的完整源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Person struct {
Age int `form:"Age" binding:"required,gt=10"`
Name string `form:"Name" binding:"required"`
Address string `form:"Address" binding:"required"`
}

func main() {
r := gin.Default()
r.GET("/test", testHandler)
r.Run()
}

func testHandler(c *gin.Context) {
var person Person
//根据请求content-type来作不同的binding操作
if err := c.ShouldBind(&person); err != nil {
c.String(500, "%v", err)
c.Abort()
return
}
c.String(200, "%v", person)

}
1
2
3
4
5
6
➜  ~ curl -X GET  "http://127.0.0.1:8080/test?age=19&name=samtake&address=gd"
Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'required' tag
Key: 'Person.Name' Error:Field validation for 'Name' failed on the 'required' tag
Key: 'Person.Address' Error:Field validation for 'Address' failed on the 'required' tag%
➜ ~ curl -X GET "http://127.0.0.1:8080/test?Age=19&Name=samtake&Address=gd"
{19 samtake gd}%

自定义验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"reflect"
"time"

"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v9"
)

type Booking struct {
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-01"`
CheckOut time.Time `form:"check_out" binding:"required,gtfield=checkIn" time_format:"2006-01-01"`
}

func customFunc(v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
if date, ok := field.Interface().(time.Time); ok {
today := time.Now()
if date.Unix() > today.Unix() {
return true
}
}
return false
}

func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("bookabledate", customFunc)
}
r.GET("/bookable", testHandler)
r.Run()
}

func testHandler(c *gin.Context) {
var b Booking
if err := c.ShouldBind(&b); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"message": "ok!", "booking": b})
}

这里有个报错,我自己也还没弄明白,然而我看了下源码,上面的使用方法是正确的:

1
2
3
4
# command-line-arguments
validCustom/validCustom.go:30:24: cannot use customFunc (type func(*validator.Validate, reflect.Value, reflect.Value, reflect.Value, reflect.Typ
e, reflect.Kind, string) bool) as type validator.Func in argument to v.RegisterValidation
`
1
2
3
4
5
6
7
8
// RegisterValidation adds a validation with the given tag
//
// NOTES:
// - if the key already exists, the previous validation function will be replaced.
// - this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterValidation(tag string, fn Func) error {
return v.RegisterValidationCtx(tag, wrapFunc(fn))
}

支持多语言错误信息

中间件

  • 使用Gin中间件
  • 自定义ip白名单中间件

Gin中间件

Logger日志
Recovery捕获panic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
gin.DefaultErrorWriter = io.MultiWriter(f)

r := gin.New()
r.Use(gin.Logger(),gin.Recovery())
r.GET("/test", func(c *gin.Context) {
name := c.DefaultQuery("name", "default_name")
c.String(200, "%s", name)
})

r.Run()
}

白名单

r.User(IPAuthMiddleware())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func IPAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
ipList := []string{
"127.0.0.1",
}
flag := false
clientIP := c.ClientIP()
for _, host := range ipList {
if clientIP == host {
flag = true
break
}
}

if !flag {
c.String(401, "%s,not in iplist", clientIP)
c.Abort()
}
}
}

func main() {
r := gin.New()
r.Use(IPAuthMiddleware())
r.Use(gin.Logger(), gin.Recovery())
r.GET("/test", func(c *gin.Context) {
c.String(200, "%s", "hello test")
})

r.Run()
}

Gin延展

  • 服务器优雅关停
  • 模版渲染
  • 自动证书配置

服务器关停

模版渲染

1
2
3
4
5
6
7
8
9
10
func main() {
r := gin.Default()
r.LoadHTMLGlob("template/*")
r.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{
"title": "index.html",
})
})
r.Run()
}
1
2
3
4
5
6
7
➜  ~ curl -X GET  "http://127.0.0.1:8080/index"
<html>
<h1>
index.html
</h1>
</html>%
➜ ~

自动证书配置

脚手架

项目地址:
gin_scaffold
golang_common
vue-admin

轻量级Golang类库

GORM:https://gorm.io/zh_CN/
redigo:https://godoc.org/github.com/gomodule/redigo/redis

├── README.md
├── conf 配置文件夹
│ └── dev
│ ├── base.toml
│ ├── mysql_map.toml
│ └── redis_map.toml
├── controller 控制器
│ └── demo.go
├── dao DB数据访问层
│ └── demo.go
├── dto Bind结构体层
│ └── demo.go
├── gin_scaffold.inf.log info日志
├── gin_scaffold.wf.log warning日志
├── go.mod go module管理文件
├── go.sum
├── main.go
├── middleware 中间件层
│ ├── panic.go
│ ├── response.go
│ ├── token_auth.go
│ └── translation.go
├── public 公共文件
│ ├── log.go
│ ├── mysql.go
│ └── validate.go
├── router 路由层
│ ├── httpserver.go
│ └── route.go
└── tmpl

输出格式统一封装

自定义中间件日志打印

请求数据绑定结构体与校验

RabbitMQ安装

1
2
3
4
5
6
# /Users/samtake/Documents/GitHub/www/rabbitmq目录可自定义,主要用于目录挂载
➜ ~ mkdir -p /Users/samtake/Documents/GitHub/www/rabbitmq
➜ ~ docker run -d --hostname rabbit-node1 --name rabbit-node1 -p 5672:5672 -p15672:15672 -v /Users/samtake/Documents/GitHub/www/rabbitmq:/var/lib/rabbitmq rabbitmq:management
Unable to find image 'rabbitmq:management' locally
management: Pulling from library/rabbitmq
...

报错提示,手动添加一下docker分享目录即可。

1
2
3
4
The path /www/rabbitmq
is not shared from OS X and is not known to Docker.
You can configure shared paths from Docker -> Preferences... -> File Sharing.
See https://docs.docker.com/docker-for-mac/osxfs/#namespaces for more info.
1
2
➜  ~ docker ps | grep rabbit
75d0ec6821fc rabbitmq:management "docker-entrypoint.s…" About a minute ago Up About a minute 4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp rabbit-node1

浏览器打开登录rabbitmq, 入口:http://localhost:15672
默认用户名: guest 密码: guest

RabbitMQ的UI界面使用测试

RabbitMQ的工作原理和转发模式

名词解析:
exchange:消息交换机,决定消息按什么规则,路由到哪个队列。
queue:消息载体,每个消息都会被投到一个或多个队列。
binding:绑定,把exchange和queue按照路由规则绑定起来。
routing key:路由关键字,exchange根据这关键字来投递消息。
channel:消息通道,客户端的每个连接建立多个channnel。
producer:消息生产者,用于投递消息的程序。
consumer:消息消费者,用于接收消息的程序。

exchange的工作模式
fanout:类似广播,转发到所有绑定交换机的queue。
direct:类似单播,routing key 和bingding key完美匹配。
topic:类似组播,转发到符合通配符的queue。
headers:请求头与消息匹配,才能接收消息。

实战

1.配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const (
// AsyncTransferEnable : 是否开启文件异步转移(默认同步)
AsyncTransferEnable = true
// RabbitURL : rabbitmq服务的入口url
RabbitURL = "amqp://guest:guest@127.0.0.1:5672/"
// TransExchangeName : 用于文件transfer的交换机
TransExchangeName = "uploadserver.trans"
// TransOSSQueueName : oss转移队列名
TransOSSQueueName = "uploadserver.trans.oss"
// TransOSSErrQueueName : oss转移失败后写入另一个队列的队列名
TransOSSErrQueueName = "uploadserver.trans.oss.err"
// TransOSSRoutingKey : routingkey
TransOSSRoutingKey = "oss"
)

2.生产者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
func init() {
// 是否开启异步转移功能,开启时才初始化rabbitMQ连接
if !config.AsyncTransferEnable {
return
}
if initChannel() {
channel.NotifyClose(notifyClose)
}
// 断线自动重连
go func() {
for {
select {
case msg := <-notifyClose:
conn = nil
channel = nil
log.Printf("onNotifyChannelClosed: %+v\n", msg)
initChannel()
}
}
}()
}

func initChannel() bool {
//1.判断channel是否已经创建
if channel != nil {
return true
}

//2.获得rabbitMQ的一个连接
conn, err := amqp.Dial(config.RabbitURL)
if err != nil {
log.Println(err.Error())
return false
}

//3.打开一个channel,用于消息的发布与接收等
channel, err = conn.Channel()
if err != nil {
log.Println(err.Error())
return false
}
return true
}

// Publish : 发布消息
func Publish(exchange, routingKey string, msg []byte) bool {
//1.判断channel是否正常
if !initChannel() {
return false
}

//2.执行消息发布
err := channel.Publish(
exchange,
routingKey,
false, // 如果没有对应的queue, 就会丢弃这条消息
false, //
amqp.Publishing{
ContentType: "text/plain", //明文编码
Body: msg,
},
)

if err != nil {
log.Println(err.Error())
return false
}
return true
}

3.消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var done chan bool

// StartConsume : 开始监听队列,获取消息
func StartConsume(qName, cName string, callback func(msg []byte) bool) {
//1.通过channel.Consume获得消息信道
msgs, err := channel.Consume(
qName,
cName,
true, //自动应答
false, // 非唯一的消费者
false, // rabbitMQ只能设置为false
false, // noWait, false表示会阻塞直到有消息过来
nil)
if err != nil {
log.Fatal(err)
return
}

done = make(chan bool)

go func() {
// 2.循环读取channel的数据
for d := range msgs {
//3.调用callback方法来处理新的消息
processErr := callback(d.Body)
if processErr {
// TODO: 将任务写入另一个队列,用于异常情况的重试
}
}
}()

// 接收done的信号, 没有信息过来则会一直阻塞,避免该函数退出
<-done

// 关闭通道
channel.Close()
}

// StopConsume : 停止监听队列
func StopConsume() {
done <- true
}

demo

Docker 安装 Redis

docker pull redis:latest
取最新版的 Redis 镜像

docker images
查看是否已安装了 redis

运行 redis 容器
docker run -itd --name redis-test -p 6379:6379 redis
或者运行redis并设置密码
docker run -d --name myredis -p 6379:6379 redis --requirepass "mypassword"

其中-p 6379:6379:映射容器服务的 6379 端口到宿主机的 6379 端口。外部可以直接通过宿主机ip:6379 访问到 Redis 的服务。

docker ps 查看容器的运行信息

1
2
3
4
➜  Desktop docker ps 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d1f67d1e5fda redis "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:6379->6379/tcp redis-test
0eee2c1708be mysql:5.7 "docker-entrypoint.s…" 8 hours ago Up 8 hours 0.0.0.0:3306->3306/tcp, 33060/tcp happy_chandrasekhar

最后,通过过 redis-cli 连接测试使用 redis 服务

1
2
3
4
5
➜  Desktop docker exec -it redis-test /bin/bash
root@d1f67d1e5fda:/data# redis-cli
127.0.0.1:6379> set test 1
OK
127.0.0.1:6379>

数据类型

数据类型 可以存储的值 操作
STRING 字符串、整数或者浮点数 对整个字符串或者字符串的其中一部分执行操作;对整数和浮点数执行自增或者自减操作;
LIST 列表 从两端压入或者弹出元素;读取单个或者多个元素;进行修剪,只保留一个范围内的元素;
SET 无序集合 添加、获取、移除单个元素;检查一个元素是否存在于集合中;计算交集、并集、差集;从集合里面随机获取元素;
HASH 包含键值对的无序散列表 添加、获取、删除单个键值对;获取所有键值对;检查某个健是否存在;
ZSET 有序集合 添加、获取、删除元素;根据分值范围或者成员来获取元素;计算一个键的排名;

STRING

命令 行为
GET 获取存储在给定健中的值
SET 设置存储在给定健中的值
DEL 删除存储在给定健中的值(这个命令可以用于所有类型)
1
2
3
4
5
6
7
8
9
127.0.0.1:6379> set name longshan
OK
127.0.0.1:6379> get name
"longshan"
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>

LIST

命令 行为
rpush 获取存储在给定键中的值
lrange 设置存储在给定键中的值
lindex 删除存储在给定键中的值(这个命令可以用于所有类型)
lpop 删除存储在给定键中的值(这个命令可以用于所有类型)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> lrange list 0-1
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange list 0 -1
1) "item1"
2) "item2"
3) "item3"
127.0.0.1:6379> lrange list 1
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lindex list 1
"item2"
127.0.0.1:6379> lpop list
"item1"
127.0.0.1:6379> lrange list 0 -1
1) "item2"
2) "item3"
127.0.0.1:6379>

SET

命令 | 行为
-|-|-
sadd | 添加一个或多个元素到集合里
smembers | 获取集合里面的所有元素
sismember | 确定一个给定的值是一个集合的成员
srem | 从集合里删除一个或多个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
127.0.0.1:6379> sadd set item1
(integer) 1
127.0.0.1:6379> sadd set item2
(integer) 1
127.0.0.1:6379> sadd set item3
(integer) 1
127.0.0.1:6379> sadd set item3
(integer) 0
127.0.0.1:6379> smember set
(error) ERR unknown command `smember`, with args beginning with: `set`,
127.0.0.1:6379> smembers set
1) "item3"
2) "item1"
3) "item2"
127.0.0.1:6379> sismember set item5
(integer) 0
127.0.0.1:6379> srem set item2
(integer) 1
127.0.0.1:6379> srem set item2
(integer) 0
127.0.0.1:6379> smenbers set
(error) ERR unknown command `smenbers`, with args beginning with: `set`,
127.0.0.1:6379> smembers set
1) "item3"
2) "item1"
127.0.0.1:6379>

HASH

命令 | 行为
-|-|-
hset | 设置 hash 里面一个字段的值
hget | 获取 hash 中域的值
hgetall | 从 hash 中读取全部的域和值
hdel | 删除一个或多个域

ZSET

命令 | 行为
-|-|-
ZADD | 添加到有序 set 的一个或多个成员,或更新的分数,如果它已经存在
ZRANGE | 根据指定的 index 返回,返回 sorted set 的成员列表
ZRANGEBYSCORE | 返回有序集合中指定分数区间内的成员,分数由低到高排序。
ZREM | 从排序的集合中删除一个或多个成员

使用场景

  • 缓存 - 将热点数据放到内存中,设置内存的最大使用量以及过期淘汰策略来保证缓存的命中率。

  • 计数器 - Redis 这种内存数据库能支持计数器频繁的读写操作。

  • 应用限流 - 限制一个网站访问流量。

  • 消息队列 - 使用 List 数据类型,它是双向链表。

  • 查找表 - 使用 HASH 数据类型。

  • 交集运算 - 使用 SET 类型,例如求两个用户的共同好友。

  • 排行榜 - 使用 ZSET 数据类型。

  • 分布式 Session - 多个应用服务器的 Session 都存储到 Redis 中来保证 Session 的一致性。

  • 分布式锁 - 除了可以使用 SETNX 实现分布式锁之外,还可以使用官方提供的 RedLock 分布式锁实现。

通过go语言使用Redis

1.设置一下Redis密码然后测试连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  egoshop docker rm -f cd93e4361dff                                                 
cd93e4361dff

➜ egoshop docker run -d --name myredis -p 6379:6379 redis --requirepass "mypassword"
f21ed92618bc5a48a95a05fcf73d1fb911b3020616dfd4950b4827dc9559814e
➜ egoshop docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f21ed92618bc redis "docker-entrypoint.s…" 3 seconds ago Up 1 second 0.0.0.0:6379->6379/tcp myredis
0eee2c1708be mysql:5.7 "docker-entrypoint.s…" 10 hours ago Up 10 hours 0.0.0.0:3306->3306/tcp, 33060/tcp happy_chandrasekhar
➜ egoshop docker exec -it myredis /bin/bash
root@f21ed92618bc:/data# redis-cli
127.0.0.1:6379> auth "mypassword"
OK
127.0.0.1:6379>

2.创建连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func newRedisPool() *redis.Pool {
return &redis.Pool{
MaxIdle: 50,
MaxActive: 30,
IdleTimeout: 300 * time.Second,
Dial: func() (redis.Conn, error) {
// 1. 打开连接
c, err := redis.Dial("tcp", redisHost)
if err != nil {
fmt.Println(err)
return nil, err
}

// 2. 访问认证
if _, err = c.Do("AUTH", redisPass); err != nil {
c.Close()
return nil, err
}
return c, nil
},
TestOnBorrow: func(conn redis.Conn, t time.Time) error {
if time.Since(t) < time.Minute {
return nil
}
_, err := conn.Do("PING")
return err
},
}
}

func init() {
pool = newRedisPool()
}

func RedisPool() *redis.Pool {
return pool
}

3.添加数据

1
2
3
4
5
6
7
8
func UploadHandler() {
// 获得redis的一个连接
rConn := rPool.RedisPool().Get()
defer rConn.Close()

// 将初信息写入到redis缓存
rConn.Do("sadd", "set", "uploadHandler")
}

4.查看是否有添加数据

1
2
3
127.0.0.1:6379> smembers set
1) "upload1"
2) "uploadHandler"

具体demo源码

参考资料
Redis 简介
Docker 安装 Redis
Redis 入门指南

实操一下

通过docker来启动一个mysql容器应用
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7

通过docker exec -it 容器id bash 来操作mysql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
➜  blogs docker ps                      
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0eee2c1708be mysql:5.7 "docker-entrypoint.s…" 41 minutes ago Up 41 minutes 0.0.0.0:3306->3306/tcp, 33060/tcp happy_chandrasekhar
➜ blogs docker exec -it 0eee2c1708be bash
root@0eee2c1708be:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 765627
Server version: 5.7.29 MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


mysql> status;
--------------
mysql Ver 14.14 Distrib 5.7.29, for Linux (x86_64) using EditLine wrapper

Connection id: 765627
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.7.29 MySQL Community Server (GPL)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: latin1
Db characterset: latin1
Client characterset: latin1
Conn. characterset: latin1
UNIX socket: /var/run/mysqld/mysqld.sock
Uptime: 1 hour 2 min 0 sec

Threads: 2 Questions: 6 Slow queries: 0 Opens: 105 Flush tables: 1 Open tables: 98 Queries per second avg: 0.001
--------------
mysql>

在navicat中使用sql语句创建表

1.进行命令行操作

2.输入mysql

3.右键refresh一下即可见到刚才新建的表tbl_user

通过navicat连接MySQL

创建数据库时,一般字符集选择utf8,排序规则选择utf8_general_ci

在Navicat中如何新建数据库和表并做查询
Navicat使用手册

1.拉取MySQL5.7镜像到本地

1
2
3
4
docker pull mysql:5.7

# 如果你只需要跑一个mysql实例,不做主从,那么执行以下命令即可,不用再做后面的参考步骤: docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
# 然后用shell或客户端软件通过配置( 用户名:root 密码:132456 IP:你的本机ip 端口:3306)来登录即可

2.准备MySQL的配置文件

MySQL5.7安装之后的默认配置文件在/etc/mysql/my.cnf,而自定义的配置文件一般放在/etc/mysql/config.d这个路径下。
在本地host主机上自定义的某个目录下(如/data/mysql/conf/),先创建两个文件master.conf和slave.conf,分别用于配置主从两个节点。

  • /data/mysql/conf/master.conf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [client] 
    default-character-set=utf8
    [mysql]
    default-character-set=utf8
    [mysqld]
    log_bin = log #开启二进制日志,用于从节点的历史复制回放
    collation-server = utf8_unicode_ci
    init-connect='SET NAMES utf8'
    character-set-server = utf8
    server_id = 1 #需保证主库和从库的server_id不同, 假设主库设为1
    replicate-do-db=fileserver #需要复制的数据库名,需复制多个数据库的话则重复设置这个选项
  • /data/mysql/conf/slave.conf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [client] 
    default-character-set=utf8
    [mysql]
    default-character-set=utf8
    [mysqld]
    log_bin = log #开启二进制日志,用于从节点的历史复制回放
    collation-server = utf8_unicode_ci
    init-connect='SET NAMES utf8'
    character-set-server = utf8
    server_id = 2 #需保证主库和从库的server_id不同, 假设从库设为2
    replicate-do-db=fileserver #需要复制的数据库名,需复制多个数据库的话则重复设置这个选项

3. Docker分别运行MySQL主/从两个容器

将mysql主节点运行起来

1
2
mkdir -p /data/mysql/datam 
docker run -d --name mysql-master -p 13306:3306 -v /data/mysql/conf/master.conf:/etc/mysql/mysql.conf.d/mysqld.cnf -v /data/mysql/datam:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7

运行参数说明:

1
2
3
4
5
–name mysql-master: 容器的名称设为mysql-master 
-p 13306:3306: 将host的13306端口映射到容器的3306端口
-v /data/mysql/conf/master.conf:/etc/mysql/mysql.conf.d/mysqld.cnf : master.conf配置文件挂载
-v /data/mysql/datam:/var/lib/mysql : mysql容器内数据挂载到host的/data/mysql/datam, 用于持久化
-e MYSQL_ROOT_PASSWORD=123456 : mysql的root登录密码为123456

将mysql从节点运行起来

1
2
mkdir -p /data/mysql/datas 
docker run -d --name mysql-slave -p 13307:3306 -v /data/mysql/conf/slave.conf:/etc/mysql/mysql.conf.d/mysqld.cnf -v /data/mysql/datas:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7

运行参数说明:

1
2
3
4
5
–name mysql-slave: 容器的名称设为mysql-slave 
-p 13307:3306: 将host的13307端口映射到容器的3306端口
-v /data/mysql/conf/master.conf:/etc/mysql/mysql.conf.d/mysqld.cnf : master.conf配置文件挂载
-v /data/mysql/datam:/var/lib/mysql : mysql容器内数据挂载到host的/data/mysql/datam, 用于持久化
-e MYSQL_ROOT_PASSWORD=123456 : mysql的root登录密码为123456

4.登录MySQL主节点配置同步信息

宿主机安装mysql客户端

1
sudo apt-get install -y mysql-client

登录mysql

1
2
# 192.168.1.xx 是你本机的内网ip 
mysql -u root -h 192.168.1.xx -P13306 -p123456

在mysql client中执行 (创建用于访问主节点来同步数据的帐号)

1
2
3
4
mysql> create user slave identified by 'slave'; 
mysql> GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' IDENTIFIED BY 'slave';
mysql> flush privileges;
mysql> create database fileserver default character set utf8mb4;

再获取status, 得到类似如下的输出:

1
2
3
4
5
6
mysql> show master status\G; 
*************************** 1. row ***************************
File: log.000025
Position: 155 Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: 1 row in set (0.00 sec)

5.登录MySQL从节点配置同步信息

  • 查看mysql master的容器独立ip地址

    1
    2
    docker inspect --format='{{.NetworkSettings.IPAddress}}' mysql-master 
    # 比如输出得到: 172.17.0.2
  • 另开一个tab登录mysql

    1
    2
    # 192.168.1.xx 是你本机的内网ip 
    mysql -u root -h 192.168.1.xx -P13307 -p123456
  • 在mysql client中操作:

1
2
3
4
5
mysql> stop slave; 
mysql> create database fileserver default character set utf8mb4;
#注意其中的日志文件和数值要和上面show master status的值对应
mysql> CHANGE MASTER TO MASTER_HOST='前两个步骤中获得的mysql master ip',MASTER_PORT=3306,MASTER_USER='slave',MASTER_PASSWORD='slave',MASTER_LOG_FILE='log.000025',MASTER_LOG_POS=155;
mysql> start slave;

再获取status, 正常应该得到类似如下的输出:

1
2
3
4
5
mysql> show slave status \G; 
// ...
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
// ...

到这时说明主从配置已经完成,可以尝试在主mysql的fileserver数据库里建表操作下,然后在从节点上检查数据是否已经同步过来。

几个指令

sudo netstat -antup -antup | grep docker 查看docker占用端口
ifconfig 查看本机ip

通过docker compose方式实现

https://github.com/mayinghan/mysql-master-slave-service

拉取mysql

1
docker pull mysql

在后台启动mysql容器(–name指定了容器的名称,方便之后进入容器的命令行,MYSQL_ROOT_PASSWORD=hls123指定了mysql的root密码,-d表示在后台运行)

1
sudo docker run --name=mysql -it -p 3306:3306 -e MYSQL_ROOT_PASSWORD=hls123 -d mysql

进入容器bash并进入mysql命令行

1
docker exec -it mysql bash

验证容器状态

1
docker ps

创建mysql容器 1master+1个slave

1
docker run --name=mysql-master -it -p 3306:3306 -e MYSQL_ROOT_PASSWORD=hls123 -d mysql

–name 为容器指定名称,这里是mysql-master

-p 将容器的指定端口映射到主机的指定端口,这里是将容器的3306端口映射到主机的3306端口

-e 设置环境变量,这里是指定root账号的密码为hls123

-d 后台运行容器,并返回容器ID

docker run –name=mysql-master -it -p 3306:3306 -e MYSQL_ROOT_PASSWORD=hls123 -d mysql:[版本号]
mysql:版本号 指定运行的mysql版本号

1
docker run --name=mysql-slave1 -it -p 3307:3306 -e MYSQL_ROOT_PASSWORD=hls123 -d mysql

基础

安装
1.通过wwww.docker.com下载桌面版docker
2.配置Docker镜像站
3.执行docker run hello-world,看输出完美运行~

基本概念

  • 镜像(Image)
  • 容器(Container)
  • 仓库(Repository)

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

常用操作

拉取镜像

1
2
3
4
5
6
7
//下载官方nginx镜像源,等同于docker pull docker.io/nginx
//也等同于docker pull docker.io/nginx.io/nginx:latest
docker pull nginx


//下载国内镜像源的ubuntu镜像,并制定版本
docker pull registry.docker-cn.com/library/ubuntu:18.04

推送镜像

1
2
3
4
5
//推送镜像到docker hub,需要先注册账号
docker push <你的用户名>/<你打包时定义的镜像名>:<标签,版本号>

//推送镜像到私有镜像库,需要提前搭建好仓库服务(比如用harbor来搭建)
docker push <私有镜像库域名,如a.b.com>/<项目名称>/镜像名:<标签>

打包镜像

1
2
3
//提前准备好一个Dockerfile,在Dockerfile相同路径下执行:
docker build -t <指定一个完整的镜像名,比如testdemo:v1.0>
//即可打包出一个本地镜像,然后再通过docker push 就可以推送到远端镜像仓库

启动容器

1
2
3
docker run -d //-d 表示通过daemon方式来启动
-p 13306:3306 //端口映射,将host主机的13306端口和docker容器的3306端口映射起来
-v /etc/mysql:/var/mysql //目录挂载,将容器内的/var/mysql目录挂载到host主机的/etc/mysql目录,可以实现容器内这个目录下的数据持久化,mysql为镜像名(指定加载哪个镜像)

重启或停止或删除容器应用

1
2
3
4
5
6
7
docker ps  //列出目前正在运行的容器列表
docker ps -a //列出所有的容器列表
docker start <容器id> //通过容器id来重启某个容器,批量操作的话,直接在参数后面再跟对应容器id即可
docker stop <容器id> //通过容器id来关闭某个容器,批量操作的话,直接在参数后面再跟对应容器id即可
docker rm <容器id> //通过容器id来删除掉某个已经停止的容器
docker rm -f <容器id> //通过容器id来删除掉某个正在运行的容器
docker container rm [trusting_newton]

删除本地镜像

1
2
docker rmo <镜像id>
docker rmi -f <镜像id> //强制删除

查看容器日志

1
2
docker logs -f <容器id>
docker inspect <容器id> //从返回结果中找到LogPath,运行的历史日志会在这个文件里找到

进入容器内

1
2
3
4
docker  exec  -it <容器id>  /bin/bash  //进入容器内并进入它的shell终端
docker exec -it <容器id> <shell命令> //在容器内执行shell命令
docker exec -it <容器id> ls -l //查看容器内系统跟目录下所有文件或文件夹
# 进入容器后,可以直接通过exit命令推出容器

服务器Linux下安装Docker社区版(Centos7为例)

  • yum源使用阿里云的源
1
2
3
4
5
6
cd /etc/yum.repos.d/
# 下载阿里云的yum源
wget http"mirrors.aliyun.com/repo/Centos-7.repo
mv CentOS-Base.repo CentOS-Base.repo.back

mv Centos-7.repo CentOS-Base.repo
  • 重置yum源

    1
    2
    3
    4
    yum install -y yum-utils
    yum-config-manager --add-repo http://mirros.aliyun.com/docker-ce/linux/centos/docker-ce.repo
    yum clean all
    yum makecache
  • 安装docker

1
2
3
4
5
6
7
8
9
10
#查看阿里云上docker 源信息
yum list docker-ce
#安装docker最新社区版
yum -y install docker-ce
#查看docker信息
docker -v
#启动docker
systemctl start docker
#查看docker详细状态信息
docker info

参考资料
Docker — 从入门到实践