工作,学习,生活,这里将会有一些记录. 备用域名:http://meisw.wdlinux.cn 注册 | 登陆
浏览模式: 标准 | 列表2016年12月的文章

理解 Go Context 机制

 

什么是Context

最近在公司分析gRPC源码,proto文件生成的代码,接口函数第一个参数统一是 ctx context.Context 接口,公司不少同事都不了解这样设计的出发点是什么,其实我也不了解其背后的原理。今天趁着 妮妲 台风妹子正面登陆深圳,全市停工、停课、停业,在家休息找了一些资料研究把玩一把。

Context 通常被译作 上下文 ,它是一个比较抽象的概念。在公司技术讨论时也经常会提到 上下文 。一般理解为程序单元的一个运行状态、现场、快照,而翻译中 上下又很好地诠释了其本质,上下上下则是存在上下层的传递,  会把内容传递给  。在Go语言中,程序单元也就指的是Goroutine。

每个Goroutine在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个 Context 变量中,传递给要执行的Goroutine中。上下文则几乎已经成为传递与请求同生存周期变量的标准方法。在网络编程下,当接收到一个网络请求Request,处理Request时,我们可能需要开启不同的Goroutine来获取数据与逻辑处理,即一个请求Request,会在多个Goroutine中处理。而这些Goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有Goroutine也应该被结束。

context包

Go的设计者早考虑多个Goroutine共享数据,以及多Goroutine管理机制。 Context介绍请参考 Go Concurrency Patterns: Context , golang.org/x/net/context 包就是这种机制的实现。

context 包不仅实现了在程序单元之间共享状态变量的方法,同时能通过简单的方法,使我们在被调用程序单元的外部,通过设置ctx变量值,将过期或撤销这些信号传递给被调用的程序单元。在网络编程中,若存在A调用B的API, B再调用C的API,若A调用B取消,那也要取消B调用C,通过在A,B,C的API调用之间传递 Context ,以及判断其状态,就能解决此问题,这是为什么gRPC的接口中带上 ctx context.Context 参数的原因之一。

Go1.7(当前是RC2版本)已将原来的 golang.org/x/net/context 包挪入了标准库中,放在$GOROOT/src/context下面。标准库中 net 、 net/http 、 os/exec 都用到了 context 。同时为了考虑兼容,在原 golang.org/x/net/context 包下存在两个文件, go17.go 是调用标准库的 context 包,而 pre_go17.go 则是之前的默认实现,其介绍请参考 go程序包源码解读 。

context 包的核心就是 Context 接口,其定义如下:

type Context interface {     Deadline() (deadline time.Time, ok bool)     Done() <-chan struct{}     Err() error     Value(key interface{}) interface{} }
  • Deadline 会返回一个超时时间,Goroutine获得了超时时间后,例如可以对某些io操作设定超时时间。

  • Done 方法返回一个信道(channel),当 Context 被撤销或过期时,该信道是关闭的,即它是一个表示Context是否已关闭的信号。

  • 当 Done 信道关闭后, Err 方法表明 Contex t被撤的原因。

  • Value 可以让Goroutine共享一些数据,当然获得数据是协程安全的。但使用这些数据的时候要注意同步,比如返回了一个map,而这个map的读写则要加锁。

Context 接口没有提供方法来设置其值和过期时间,也没有提供方法直接将其自身撤销。也就是说, Context 不能改变和撤销其自身。那么该怎么通过 Context 传递改变后的状态呢?

context使用

无论是Goroutine,他们的创建和调用关系总是像层层调用进行的,就像人的辈分一样,而更靠顶部的Goroutine应有办法主动关闭其下属的Goroutine的执行(不然程序可能就失控了)。为了实现这种关系,Context结构也应该像一棵树,叶子节点须总是由根节点衍生出来的。

要创建Context树,第一步就是要得到根节点, context.Background 函数的返回值就是根节点:

func Background() Context

该函数返回空的Context,该Context一般由接收请求的第一个Goroutine创建,是与进入请求对应的Context根节点,它不能被取消、没有值、也没有过期时间。它常常作为处理Request的顶层context存在。

有了根节点,又该怎么创建其它的子节点,孙节点呢?context包为我们提供了多个函数来创建他们:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key interface{}, val interface{}) Context

函数都接收一个 Context 类型的参数 parent ,并返回一个 Context 类型的值,这样就层层创建出不同的节点。子节点是从复制父节点得到的,并且根据接收参数设定子节点的一些状态值,接着就可以将子节点传递给下层的Goroutine了。

再回到之前的问题:该怎么通过 Context 传递改变后的状态呢?使用 Context 的Goroutine无法取消某个操作,其实这也是符合常理的,因为这些Goroutine是被某个父Goroutine创建的,而理应只有父Goroutine可以取消操作。在父Goroutine中可以通过WithCancel方法获得一个cancel方法,从而获得cancel的权利。

第一个 WithCancel 函数,它是将父节点复制到子节点,并且还返回一个额外的 CancelFunc 函数类型变量,该函数类型的定义为:

type CancelFunc func()

调用 CancelFunc 对象将撤销对应的 Context 对象,这就是主动撤销 Context 的方法。在父节点的 Context 所对应的环境中,通过 WithCancel 函数不仅可创建子节点的 Context ,同时也获得了该节点 Context 的控制权,一旦执行该函数,则该节点 Context 就结束了,则子节点需要类似如下代码来判断是否已结束,并退出该Goroutine:

select {     case <-cxt.Done():         // do some clean... }

WithDeadline 函数的作用也差不多,它返回的Context类型值同样是 parent 的副本,但其过期时间由 deadline 和 parent 的过期时间共同决定。当 parent 的过期时间早于传入的 deadline 时间时,返回的过期时间应与 parent 相同。父节点过期时,其所有的子孙节点必须同时关闭;反之,返回的父节点的过期时间则为 deadline 。

WithTimeout 函数与 WithDeadline 类似,只不过它传入的是从现在开始Context剩余的生命时长。他们都同样也都返回了所创建的子Context的控制权,一个 CancelFunc 类型的函数变量。

当顶层的Request请求函数结束后,我们就可以cancel掉某个context,从而层层Goroutine根据判断 cxt.Done() 来结束。

WithValue 函数,它返回 parent 的一个副本,调用该副本的Value(key)方法将得到val。这样我们不光将根节点原有的值保留了,还在子孙节点中加入了新的值,注意若存在Key相同,则会被覆盖。

小结

context 包通过构建树型关系的Context,来达到上一层Goroutine能对传递给下一层Goroutine的控制。对于处理一个Request请求操作,需要采用 context 来层层控制Goroutine,以及传递一些变量来共享。

  • Context对象的生存周期一般仅为一个请求的处理周期。即针对一个请求创建一个Context变量(它为Context树结构的根);在请求处理结束后,撤销此ctx变量,释放资源。

  • 每次创建一个Goroutine,要么将原有的Context传递给Goroutine,要么创建一个子Context并传递给Goroutine。

  • Context能灵活地存储不同类型、不同数目的值,并且使多个Goroutine安全地读写其中的值。

  • 当通过父Context对象创建子Context对象时,可同时获得子Context的一个撤销函数,这样父Context对象的创建环境就获得了对子Context将要被传递到的Goroutine的撤销权。

  • 在子Context被传递到的goroutine中,应该对该子Context的Done信道(channel)进行监控,一旦该信道被关闭(即上层运行环境撤销了本goroutine的执行),应主动终止对当前请求信息的处理,释放资源并返回。

使用原则

Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation:

使用Context的程序包需要遵循如下的原则来满足接口的一致性以及便于静态分析。

  • Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx;不要把Context存在一个结构体当中,显式地传入函数。Context变量需要作为第一个参数使用,一般命名为ctx;

  • Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use;即使方法允许,也不要传入一个nil的Context,如果你不确定你要用什么Context的时候传一个context.TODO;

  • Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions;使用context的Value相关方法只应该用于在程序和接口中传递的和请求相关的元数据,不要用它来传递一些可选的参数;

  • The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines;同样的Context可以用来传递到不同的goroutine中,Context在多个goroutine中是安全的;

参考:

[1] https://blog.golang.org/context

[2] http://blog.golang.org/pipelines

[3] http://studygolang.com/articles/5131

[4] http://blog.csdn.net/sryan/article/details/51969129

[5] https://peter.bourgon.org/blog/2016/07/11/context.html

[6] http://www.tuicool.com/articles/vaieAbQ

golang中context包解读

简介

golang 中的创建一个新的 goroutine , 并不会返回像c语言类似的pid,所有我们不能从外部杀死某个goroutine,所有我就得让它自己结束,之前我们用 channel + select 的方式,来解决这个问题,但是有些场景实现起来比较麻烦,例如由一个请求衍生出的各个 goroutine 之间需要满足一定的约束关系,以实现一些诸如有效期,中止routine树,传递请求全局变量之类的功能。于是google 就为我们提供一个解决方案,开源了 context 包。使用 context 实现上下文功能约定需要在你的方法的传入参数的第一个传入一个 context.Context 类型的变量。

源码剖析

context.Context 接口

context 包的核心

//  context 包里的方法是线程安全的,可以被多个 goroutine 使用    

type Context interface {               

    // 当Context 被 canceled 或是 times out 的时候,Done 返回一个被 closed 的channel      

    Done() <-chan struct{}        


    // 在 Done 的 channel被closed 后, Err 代表被关闭的原因   

    Err() error 


    // 如果存在,Deadline 返回Context将要关闭的时间  

    Deadline() (deadline time.Time, ok bool)


    // 如果存在,Value 返回与 key 相关了的值,不存在返回 nil  

    Value(key interface{}) interface{}

}

我们不需要手动实现这个接口,context 包已经给我们提供了两个,一个是 Background(),一个是 TODO(),这两个函数都会返回一个 Context 的实例。只是返回的这两个实例都是空 Context。

主要结构

cancelCtx 结构体继承了 Context ,实现了 canceler 方法:

//*cancelCtx 和 *timerCtx 都实现了canceler接口,实现该接口的类型都可以被直接canceled

type canceler interface {

    cancel(removeFromParent bool, err error)

    Done() <-chan struct{}

}        


type cancelCtx struct {

    Context

    done chan struct{} // closed by the first cancel call.

    mu       sync.Mutex

    children map[canceler]bool // set to nil by the first cancel call

    err      error             // 当其被cancel时将会把err设置为非nil

}


func (c *cancelCtx) Done() <-chan struct{} {

    return c.done

}


func (c *cancelCtx) Err() error {

    c.mu.Lock()

    defer c.mu.Unlock()

    return c.err

}


func (c *cancelCtx) String() string {

    return fmt.Sprintf("%v.WithCancel", c.Context)

}


//核心是关闭c.done

//同时会设置c.err = err, c.children = nil

//依次遍历c.children,每个child分别cancel

//如果设置了removeFromParent,则将c从其parent的children中删除

func (c *cancelCtx) cancel(removeFromParent bool, err error) {

    if err == nil {

        panic("context: internal error: missing cancel error")

    }

    c.mu.Lock()

    if c.err != nil {

        c.mu.Unlock()

        return // already canceled

    }

    c.err = err

    close(c.done)

    for child := range c.children {

        // NOTE: acquiring the child's lock while holding parent's lock.

        child.cancel(false, err)

    }

    c.children = nil

    c.mu.Unlock()


    if removeFromParent {

        removeChild(c.Context, c) // 从此处可以看到 cancelCtx的Context项是一个类似于parent的概念

    }

}

timerCtx 结构继承 cancelCtx

type timerCtx struct {

    cancelCtx //此处的封装为了继承来自于cancelCtx的方法,cancelCtx.Context才是父亲节点的指针

    timer *time.Timer // Under cancelCtx.mu. 是一个计时器

    deadline time.Time

}

valueCtx 结构继承 cancelCtx

type valueCtx struct {

    Context

    key, val interface{}

}

主要方法

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key interface{}, val interface{}) Context
 

WithCancel 对应的是 cancelCtx ,其中,返回一个 cancelCtx ,同时返回一个 CancelFunc,CancelFunc 是 context 包中定义的一个函数类型:type CancelFunc func()。调用这个 CancelFunc 时,关闭对应的c.done,也就是让他的后代goroutine退出。

WithDeadline 和 WithTimeout 对应的是 timerCtx ,WithDeadline 和 WithTimeout 是相似的,WithDeadline 是设置具体的 deadline 时间,到达 deadline 的时候,后代 goroutine 退出,而 WithTimeout 简单粗暴,直接 return WithDeadline(parent, time.Now().Add(timeout))。

WithValue 对应 valueCtx ,WithValue 是在 Context 中设置一个 map,拿到这个 Context 以及它的后代的 goroutine 都可以拿到 map 里的值。

详细 context 包源码解读:go源码解读

使用原则

使用 Context 的程序包需要遵循如下的原则来满足接口的一致性以及便于静态分析

不要把 Context 存在一个结构体当中,显式地传入函数。Context 变量需要作为第一个参数使用,一般命名为ctx

即使方法允许,也不要传入一个 nil 的 Context ,如果你不确定你要用什么 Context 的时候传一个 context.TODO

使用 context 的 Value 相关方法只应该用于在程序和接口中传递的和请求相关的元数据,不要用它来传递一些可选的参数

同样的 Context 可以用来传递到不同的 goroutine 中,Context 在多个goroutine 中是安全的

使用示例

例子copy自: 关于 Golang 中的 context 包的介绍

package main


import (

    "fmt"

    "time"

    "golang.org/x/net/context"

)


// 模拟一个最小执行时间的阻塞函数

func inc(a int) int {

    res := a + 1                // 虽然我只做了一次简单的 +1 的运算,

    time.Sleep(1 * time.Second) // 但是由于我的机器指令集中没有这条指令,

    // 所以在我执行了 1000000000 条机器指令, 续了 1s 之后, 我才终于得到结果。B)

    return res

}


// 向外部提供的阻塞接口

// 计算 a + b, 注意 a, b 均不能为负

// 如果计算被中断, 则返回 -1

func Add(ctx context.Context, a, b int) int {

    res := 0

    for i := 0; i < a; i++ {

        res = inc(res)

        select {

        case <-ctx.Done():

            return -1

        default:

        }

    }

    for i := 0; i < b; i++ {

        res = inc(res)

        select {

        case <-ctx.Done():

            return -1

        default:

        }

    }

    return res

}


func main() {

    {

        // 使用开放的 API 计算 a+b

        a := 1

        b := 2

        timeout := 2 * time.Second

        ctx, _ := context.WithTimeout(context.Background(), timeout)

        res := Add(ctx, 1, 2)

        fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)

    }

    {

        // 手动取消

        a := 1

        b := 2

        ctx, cancel := context.WithCancel(context.Background())

        go func() {

            time.Sleep(2 * time.Second)

            cancel() // 在调用处主动取消

        }()

        res := Add(ctx, 1, 2)

        fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)

    }

}

Docker 清理命令集锦

 杀死所有正在运行的容器

复制代码代码如下:
docker kill $(docker ps -a -q)

 

 删除所有已经停止的容器

复制代码代码如下:
docker rm $(docker ps -a -q)

 

 删除所有未打 dangling 标签的镜像

复制代码代码如下:
docker rmi $(docker images -q -f dangling=true)

 

 删除所有镜像

复制代码代码如下:
docker rmi $(docker images -q)

 

 为这些命令创建别名

复制代码代码如下:
# ~/.bash_aliases

 

# 杀死所有正在运行的容器.
alias dockerkill='docker kill $(docker ps -a -q)'

# 删除所有已经停止的容器.
alias dockercleanc='docker rm $(docker ps -a -q)'

# 删除所有未打标签的镜像.
alias dockercleani='docker rmi $(docker images -q -f dangling=true)'

# 删除所有已经停止的容器和未打标签的镜像.
alias dockerclean='dockercleanc || true && dockercleani'

另附上docker常用命令

docker version #查看版本

 docker search tutorial#搜索可用docker镜像

 docker pull learn/tutorial #下载镜像

 docker run learn/tutorial echo "hello word"#在docker容器中运行hello world!

 docker run learn/tutorial apt-get install -y ping#在容器中安装新的程序

保存镜像

首先使用docker ps -l命令获得安装完ping命令之后容器的id。然后把这个镜像保存为learn/ping。
 提示:
 1.运行docker commit,可以查看该命令的参数列表。
 2.你需要指定要提交保存容器的ID。(译者按:通过docker ps -l 命令获得)
 3.无需拷贝完整的id,通常来讲最开始的三至四个字母即可区分。(译者按:非常类似git里面的版本号)
 正确的命令:
docker commit 698 learn/ping

运行新的镜像

docker run lean/ping ping www.google.com

检查运行中的镜像

现在你已经运行了一个docker容器,让我们来看下正在运行的容器。
 使用docker ps命令可以查看所有正在运行中的容器列表,使用docker inspect命令我们可以查看更详细的关于某一个容器的信息。
 目标:

查找某一个运行中容器的id,然后使用docker inspect命令查看容器的信息。
 提示:

可以使用镜像id的前面部分,不需要完整的id。
 正确的命令:
docker inspect efe

Docker 技巧:删除 Docker 容器和镜像

 

删除所有未运行 Docker 容器

docker rm $(docker ps -a -q)

删除所有 Docker 镜像

删除所有未打 tag 的镜像

docker rmi $(docker images -q | awk '/^<none>/ { print $3 }')

删除所有镜像

docker rmi $(docker images -q)

根据格式删除所有镜像

docker rm $(docker ps -qf status=exited)
docker rmi $(docker images --filter dangling=true -q
执行这条命令删除pull过程或者create过程产生的临时镜像,剩下的都是有用的镜像。镜像是一层层叠加起来的,你使用的是带名字和tag的发布镜像,那些中间镜像没有名字,只有哈希,所以叫none。。
这个是列出所有的镜像的。因为某些镜像间存在依赖关系。你要知道docker跟git一样,有“层”的概念。你用docker pull ubuntu的时候就会发现,会pull下来一堆镜像,然后最后一个镜像tag为ubuntu:latest。

Docker 之Dockerfile 快速学习(二)

 Dockerfile文件的说明:

格式:

INSTRUCTION arguments

虽然指令忽略大小写,但建议用大写

# 是注释

MAINTAINER命令:

MAINTAINER命令用来指定维护者的姓名和联系方式

 

FROM命令:

FROM <image>/<image>:<tag>

这个是设置基本的镜像,为后续的命令使用,所以应该作为Dockerfile的第一条指令

比如:

FROM centos6-base

 

RUN 命令:

会上面FROM 指定的镜像里执行任何命令,然后提交(commit)结果,提交的镜像会在后面继续用到

格式:

RUN <command> (这个命令运行一个shell中 - '/bin/sh -c')

或:

RUN ["executable", "param1", "param2"]

RUN 命令等价于:

docker run image_name command

 

CMD  and  ENTRYPOINT

 

命令设置在容器启动时执行命令

 

ENTRYPOINT,表示镜像在初始化时需要执行的命令,不可被重写覆盖,需谨记

CMD,表示镜像运行默认参数,可被重写覆盖

ENTRYPOINT/CMD都只能在文件中存在一次,并且最后一个生效 多个存在,只有最后一个生效,其它无效!

需要初始化运行多个命令,彼此之间可以使用 && 隔开,但最后一个须要为无限运行的命令,需切记!

 

ENTRYPOINT/CMD,一般两者可以配合使用,比如:

ENTRYPOINT ["/usr/sbin/sshd"]

CMD ["-D"]

 

** 在Docker daemon模式下,无论你是使用ENTRYPOINT,还是CMD,最后的命令,一定要是当前进程需要一直运行的,才能够防容器退出。***

 

以下无效方式:

ENTRYPOINT service tomcat7 start #运行几秒钟之后,容器就会退出

CMD service tomcat7 start #运行几秒钟之后,容器就会退出

 

这样有效:

ENTRYPOINT service tomcat7 start && tail -f /var/lib/tomcat7/logs/catalina.out

# 或者

CMD service tomcat7 start && tail -f /var/lib/tomcat7/logs/catalina.out

这样也有效:

ENTRYPOINT ["/usr/sbin/sshd"]

CMD ["-D"]

 

 

USER 命令

比如指定 memcached 的运行用户,可以使用上面的 ENTRYPOINT or CMD来实现:

ENTRYPOINT ["memcached", "-u", "daemon"]

更好的方式:

ENTRYPOINT ["memcached"]

USER daemon

 

EXPOSE 命令

 

EXPOSE 命令可以设置一个端口在运行的镜像中暴露在外

在docker使用--link来链接两容器时会用到相关端口

EXPOSEd <port>

 

ENV命令:

用于设置环境变更

使用此dockerfile生成的image新建container,可以通过 docker inspect CONTAINER ID  看到这个环境变量

也可以通过在docker run时设置或修改环境变量

 

ADD 命令:

从src复制文件到container的dest路径:

ADD <src> <dest>

 

<src> 是相对被构建的源目录的相对路径,可以是文件或目录的路径,也可以是一个远程的文件url

<dest> 是container中的绝对路径

 

VOLUME 命令

VOLUME ["<mountpoint>"]

如:

VOLUME ["/data"]

创建一个挂载点用于共享目录

 

WORKDIR 命令

WORKDIR /path/to/workdir

配置RUN, CMD, ENTRYPOINT 命令设置当前工作路径

可以设置多次,如果是相对路径,则相对前一个 WORKDIR 命令

比如:

WORKDIR /a WORKDIR b WORKDIR c RUN pwd

其实是在 /a/b/c 下执行 pwd

 

Dockerfile文件到此介绍完毕。

下面是一个完整的Dockerfile文件

#Dockerfile

FROM centos6-base

MAINTAINER zhou_mfk <zhou_mfk@163.com>

RUN ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key

RUN ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key

RUN sed -ri 's/session    required     pam_loginuid.so/#session    required     pam_loginuid.so/g' /etc/pam.d/sshd

RUN mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh

EXPOSE 22

RUN echo 'root:redhat' | chpasswd

RUN yum install -y yum-priorities && rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6

RUN yum install tar gzip gcc vim wget -y

ENV LANG en_US.UTF-8

ENV LC_ALL en_US.UTF-8

CMD /usr/sbin/sshd -D

#End

docker_practice

centos下安装docker最新版教程

 1、通过yum安装

需要root或者能sudo的权限


yum包更新到最新
$ sudo yum update


添加Docker yum源
$ sudo tee /etc/yum.repos.d/docker.repo <<-'EOF'
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF


安装Docker包
$ sudo yum install docker-engine


启动Docker守护进程
$ sudo service docker start


通过测试镜像运行一个容器来验证Docker是否安装正确
$ sudo docker run hello-world


2、通过脚本安装
需要root或者能sudo的权限


yum包更新到最新
$ sudo yum update


运行Docker安装脚本
$ curl -sSL https://get.docker.com/ | sh
这个脚本会添加docker.repo仓库并且安装Docker


启动Docker守护进程
$ sudo service docker start


通过测试镜像运行一个容器来验证Docker是否安装正确
$ sudo docker run hello-world


创建一个Docker用户组
docker守护进程绑定在Unix socket 而不是 TCP 端口。默认情况下Unix socket属于root用户和具有sudo权限用户的使用范畴。出于这个原因,docker守护进程需要一直在root用户下面运行。
To avoid having to use sudo when you use the docker command, create a Unix group called docker and add users to it. When the docker daemon starts, it makes the ownership of the Unix socket read/writable by the docker group.
为了避免运行docker命令的时候需要使用sudo命令,创建一个叫docker的Unix用户组并将需要运行docker的用户添加进去。当开始运行docker守护进程,会令docker用户组的成员拥有Unix socket的读写权限。
创建docker用户组并添加用户:
1.需要root或者能sudo的权限


2.创建docker用户组并添加用户
sudo usermod -aG docker your_username


3.退出并且登录到your_username,如果在your_username下也建议退出再重新登录一次,这确保您的用户正在运行正确的权限。


4.验证运行docker已不再需要sudo命令

$ docker run hello-world
开机运行docker
$ sudo chkconfig docker on

博客恢复正常了

 之前的一段时间,突然这个后台的编辑器用不了了,发的文章也没有格式了,很不好看,慢慢,也就没有更新了

 
那时,也有找过原因,还以为是服务器的原因,但没找到也没解决
 
时间好快,又一年了
 
 
最近,开始学习,发现,没个博客记录些东西,还真麻烦
 
所以,再次偿试解决
 
在本地安装了个新的,也是一样的问题
 
所以,觉得,应该是服务器的问题了,而程序或是兼容性的问题
 
好吧,看代码
 
没多久,还真找到原因了
 
fckeditor_php5.php
这个文件里,有一个判断浏览器信息的,默认找不到匹配信息,返回假,所以没有编辑了
只需改为真,就OK了
 
又开始更新吧
 
一个小插曲
在刚看代码时,想百度搜索下,看有没人遇到这个问题或有没解决办法,毕竟觉得,用这个博客程序的人,还是有一些
但没找到,却无意间发现,这个程序的作者入狱了,还判了5年,挺震惊,也挺悲剧的
Records:72123456789