Docker下Mysql .cnf文件修改小贴士

一朝选Docker,就要会填坑,今天因为一不小心,改坏了测试环境的mysql配置,导致container启动不起来,然后想方设法恢复容器中文件,费了一中午时间终于在不做新镜像,不起新容器的情况下修复了错误配置。今天就来说说几种修改MySQL配置*.cnf文件修改方法。

环境

  • docker 17.06.0-ce
  • CentOS Linux 7 (Core)
  • MySQL 5.7.18 (docker镜像)

1. 映射目录(未雨绸缪)

在启动映射 /etc/mysql/目录到本地没有亲测 这个初学docker的基本都会。最简单的,修改本地文件重启container 即可。当你对一个镜像结构非常熟悉的时候可以用。 mysql官方镜像上也有说怎么映射这个目录出来,只是当时用的太匆忙这些没有做。

未分类

2. 容器bash或sh(紧急处理)

这种方法相对比较容易,用docker exec处理进入bash 或sh 操作即可。

docker exec -it <containerid> bash

安装vim

apt-get update
apt-get install vim

MySQL配置文件所在目录/etc/mysql/

vi /etc/mysql/my.cnf

未分类

my.cnf文件内容 在这个环境下,我把my.cnf文件给写错了导致 容器一直启动不起来,然后就无法exec bash了,我只能另球方法把 my.cnf修改回来。

(PS:结束exec bash ctrl+p,ctrl+q 组合命令)

3. docker cp 命令(安慰剂)

https://stackoverflow.com/questions/24553790/how-to-edit-docker-container-files-from-the-host 博主参考这个文章才知道的,虽然最佳答案不是关于cp命令的。官方docker container cp (https://docs.docker.com/engine/reference/commandline/container_cp/) 操作说明

$ docker cp CONTAINER:FILEPATH LOCALFILEPATH
$ vi LOCALFILEPATH
$ docker cp LOCALFILEPATH CONTAINER:FILEPATH

可以将本地的my.cnf 文件拷入到容器中,可以将容器中的文件拷出,解决了用

  • docker exec:只能对运行中的容器操作文件
  • docker run: 加-v映射文件目录出来 参数只能创建新容器

这两个痛点,重新将修改正确的cnf文件拷贝回container,于是乎坏掉container的又可以奇迹般的使用了。

4. 绕圈子解决办法(待测试)

参考 https://github.com/moby/moby/issues/18078

Commit the stopped container:
docker commit $STOPPED_CONTAINER user/test_image
Start/run with a different entry point:
docker run -ti --entrypoint=sh user/test_image

意思就是把坏掉的容器提交成镜像,然后再镜像启动的时候用ti模式加入entrypoint=sh 去修复问题。但是修复完怎么处理,还不知道。

5. 进阶docker container inspect

这个方法不是解决突然改坏文件的,通过这个方法可以访问到容器的配置文件 containerid(系统生成的).json文件。应该是可以修改json文件调整容器启动属性的。这个操作比较高深,也比较危险,目前我还用不到,如果有正好需要的可以研究下,这里只做一个引子。过程可以参考 https://stackoverflow.com/questions/32750748/how-to-edit-files-in-stopped-not-starting-docker-container

未分类

窥探一下 我也偷偷的看了一下,看花眼的配置,大家要玩这个配置的时候一定要小心哦~

Docker镜像的存储机制

近几年 Docker 风靡技术圈,不少从业人员都或多或少使用过,也了解如何通过 Dockerfile 构建镜像,从远程镜像仓库拉取自己所需镜像,推送构建好的镜像至远程仓库,根据镜像运行容器等。这个过程十分简单,只需执行 docker build、docker pull、docker push、docker run 等操作即可。但大家是否想过镜像在本地到底是如何存储的?容器又是如何根据镜像启动的?推送镜像至远程镜像仓库时,服务器又是如何存储的呢?下面我们就来简单聊一聊。

未分类

  
Docker 镜像本地存储机制及容器启动原理

  Docker 镜像不是一个单一的文件,而是有多层构成。我们可通过 docker images 获取本地的镜像列表及对应的元信息, 接着可通过docker history 查看某个镜像各层内容及对应大小,每层对应着 Dockerfile 中的一条指令。Docker 镜像默认存储在 /var/lib/docker/中,可通过 DOCKER_OPTS 或者 docker daemon 运行时指定 –graph= 或 -g 指定。

  Docker 使用存储驱动来管理镜像每层内容及可读写的容器层,存储驱动有 DeviceMapper、AUFS、Overlay、Overlay2、Btrfs、ZFS 等,不同的存储驱动实现方式有差异,镜像组织形式可能也稍有不同,但都采用栈式存储,并采用 Copy-on-Write(CoW) 策略。且存储驱动采用热插拔架构,可动态调整。那么,存储驱动那么多,该如何选择合适的呢?大致可从以下几方面考虑:

  若内核支持多种存储驱动,且没有显式配置,Docker 会根据它内部设置的优先级来选择。优先级为 AUFS > Btrfs/ZFS > Overlay2 > Overlay > DeviceMapper。若使用 DeviceMapper 的话,在生产环境,一定要选择 direct-lvm, loopback-lvm 性能非常差。

  选择会受限于 Docker 版本、操作系统、系统版本等。例如,AUFS 只能用于 Ubuntu 或 Debian 系统,Btrfs 只能用于 SLES (SUSE Linux Enterprise Server, 仅 Docker EE 支持)。

  有些存储驱动依赖于后端的文件系统。例如,Btrfs 只能运行于后端文件系统 Btrfs 上。

  不同的存储驱动在不同的应用场景下性能不同。例如,AUFS、Overlay、Overlay2 操作在文件级别,内存使用相对更高效,但大文件读写时,容器层会变得很大;DeviceMapper、Btrfs、ZFS 操作在块级别,适合工作在写负载高的场景;容器层数多,且写小文件频繁时,Overlay 效率比 Overlay2 更高;Btrfs、ZFS 更耗内存。

  Docker 容器其实是在镜像的最上层加了一层读写层,通常也称为容器层。在运行中的容器里做的所有改动,如写新文件、修改已有文件、删除文件等操作其实都写到了容器层。容器层删除了,最上层的读写层跟着也删除了,改动自然也丢失了。若要持久化这些改动,须通过 docker commit [repository[:tag]] 将当前容器保存成为一个新镜像。若想将数据持久化,或是多个容器间共享数据,需将数据存储在 Docker volume 中,并将 volume 挂载到相应容器中。

  存储驱动决定了镜像及容器在文件系统中的存储方式及组织形式,下面分别对常见的 AUFS、Overlay 作一简单介绍。

  
AUFS

AUFS 简介

  AUFS 是 Debian (Stretch 之前的版本,Stretch默认采用 Overlay2) 或 Ubuntu 系统上 Docker 的默认存储驱动,也是 Docker 所有存储驱动中最为成熟的。具有启动快,内存、存储使用高效等特点。如果使用的 Linux 内核版本为 4.0 或更高,且使用的是 Docker CE,可考虑使用Overlay2 (比 AUFS 性能更佳)。

配置 AUFS 存储驱动

① 验证内核是否支持 AUFS

$ grep aufs /proc/filesystems nodev aufs

  
② 若内核支持,可在 docker 启动时通过指定参数 –storage-driver=aufs 选择 AUFS

AUFS 存储驱动工作原理

采用 AUFS 存储驱动时,有关镜像和容器的所有层信息都存储在 /var/lib/docker/aufs/ 目录下,下面有三个子目录:

  • /diff:每个目录中存储着每层镜像包含的真实内容

  • /layers:存储有关镜像层组织的元信息,文件内容存储着该镜像的组建镜像列表

  • /mnt:挂载点信息存储,当创建容器后,mnt 目录下会多出容器对应的层及该容器的 init 层。目录名称与容器 ID 不一致。实际的读写层存储在 /var/lib/docker/aufs/diff,直到容器删除,此读写层才会被清除掉。

采用 AUFS 后容器如何读写文件?

读文件

容器进行读文件操作有以下三种场景:

  • 容器层不存在: 要读取的文件在容器层中不存在,存储驱动会从镜像层逐层向下找,多个镜像层中若存在同名文件,上层的有效。

  • 文件只存在容器层:读取容器层文件

  • 容器层与镜像层同时存在:读取容器层文件

修改文件或目录

容器中进行文件的修改同样存在三种场景:

  • 第一次写文件:若待修改的文件在某个镜像层中,AUFS 会先执行 copy_up 操作将文件从只读的镜像层拷贝到可读写的容器层,然后进行修改。在文件非常大的情况下效率比较低下。

  • 删除文件:删除文件时,若文件在镜像层,其实是在容器层创建一个特殊的 writeout 文件,容器层访问不到,并没有实际删掉。

  • 目录重命名:目前 AUFS 还不支持目录重命名。

  
OverlayFS

OverlayFS 简介

  OverlayFS 是一种类似 AUFS 的现代联合文件系统,但实现更简单,性能更优。OverlayFS 严格说来是 Linux 内核的一种文件系统,对应的 Docker 存储驱动为 Overlay 或者 Overlay2,Overlay2 需 Linux 内核 4.0 及以上,Overlay 需内核 3.18 及以上。且目前仅 Docker 社区版支持。条件许可的话,尽量使用 Overlay2,与 Overlay 相比,它的 inode 利用率更高。

容器如何使用 Overlay/Overlay2 读写文件

读文件

读文件存在以下三种场景:

  • 文件不存在容器层:若容器要读的文件不在容器层,会继续从底层的镜像层找

  • 文件仅在容器层:若容器要读的文件在容器层,直接读取,不用在底层的镜像层查找

  • 文件同时在容器层和镜像层:若容器要读的文件在容器层和镜像层中都存在,则从容器层读取

修改文件或目录

写文件存在以下三种场景:

  • 首次写文件:若要写的文件位于镜像层中,则执行 copy_up 将文件从镜像层拷贝至容器层,然后进行修改,并在容器层保存一份新的。若文件较大,效率较低。OverlayFS 工作在文件级别而不是块级别,这意味着即使对文件稍作修改且文件很大,也须将整个文件拷贝至容器层进行修改。但需注意的是,copy_up 操作仅发生在首次,后续对同一文件进行修改,操作容器层文件即可

  • 删除文件或目录:容器中删除文件或目录时,其实是在容器中创建了一个 writeout 文件,并没有真的删除文件,只是使其对用户不可见

  • 目录重命名:仅当源路径与目标路径都在容器层时,调用 rename(2) 函数才成功,否则返回 EXDEV

远程镜像仓库如何存储镜像?

  不少人可能经常使用 Docker,那么有没有思考过镜像推送至远程镜像仓库,是如何保存的呢?Docker 客户端是如何与远程镜像仓库交互的呢?

  我们平时本地安装的 Docker 其实包含两部分:docker client 与 docker engine,docker client 与 docker engine 间通过 API 进行通信。Docker engine 提供的 API 大致有认证、容器、镜像、网络、卷、swarm 等,具体调用形式请参考:Docker Engine API(https://docs.docker.com/engine/api/v1.27/#)。

  Docker engine 与 registry (即:远程镜像仓库)的通信也有一套完整的 API,大致包含 pull、push 镜像所涉及的认证、授权、镜像存储等相关流程,具体请参考:Registry API(https://github.com/docker/distribution/blob/master/docs/spec/api.md)。目前常用 Registry 版本为 v2,Registry v2 拥有断点续传、并发拉取镜像多层等特点。能并发拉取多层是因为镜像的元信息与镜像层数据分开存储,当 pull 一个镜像时,先进行认证获取到 token 并授权通过,然后获取镜像的 manifest 文件,进行 signature 校验。校验完成后,依据 manifest 里的层信息并发拉取各层。其中 manifest 包含的信息有:仓库名称、tag、镜像层 digest 等, 更多,请参考:manifest 格式文档(https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md)。

  各层拉下来后,也会先在本地进行校验,校验算法采用 sha256。Push 过程则先将镜像各层并发推至 Registry,推送完成后,再将镜像的 manifest 推至 Registry。Registry 其实并不负责具体的存储工作,具体存储介质根据使用方来定,Registry 只是提供一套标准的存储驱动接口,具体存储驱动实现由使用方实现。

  目前官方 Registry 默认提供的存储驱动包括:微软 Azure、Google gcs、Amazon s3、OpenStack swift、阿里云 OSS、本地存储等。若需要使用自己的对象存储服务,则需要自行实现 registry 存储驱动。网易云目前将镜像存储在自己的对象存储服务 nos 上,故专门针对 nos 实现了一套存储驱动,另外认证服务也对接了网易云认证服务,并结合自身业务实现了一套认证、授权逻辑,并有效地限制了仓库配额。

  Registry 干的事情其实很简单,大致可分为:① 读配置 ;② 注册 handler ;③ 监听。本质上 Registry 是个 HTTP 服务,启动后,监听在配置文件设定的某端口上。当 http 请求过来后,便会触发之前注册过的 Handler。Handler 包含 manifest、tag、blob、blob-upload、blob-upload-chunk、catalog 等六类,具体请可参考 Registry 源码: /registry/handlers/app.go:92。配置文件包含监听端口、auth 地址、存储驱动信息、回调通知等。

从开发到部署会用到的 Docker 命令

本文的目的是理解容器开发在目标环境中部署的端到端流程,并列出这些操作所需的 Docker 命令。

一、介绍

整个流程包括使用代码、依赖软件和配置来开发容器映像,在开发环境中运行和测试容器,将容器映像发布到 Docker Hub,以及最后的部署和在目标环境中运行容器。

本文假设您已经在开发和目标环境中安装了 Docker 引擎。有关安装说明请参阅 6.3:https://docs.docker.com/engine/installation/。

二、开发容器映像

在构建容器映像之前,你需要创建一个 dockerfile,它包含了所需要的信息。请参考https://nodejs.org/en/docs/guides/nodejs-docker-webapp/来编制一个 dockerfile。

2.1 构建 Docker 容器

未分类

这个命令会使用当前目录下的 Dockerfile。如果 dockerfile 使用了其它文件名或者放在其它位置,可以使用 -f 参数来指定 dockerfile 的名称。“docker build” 命令会构建容器映像,这个容器映像的名称由 “-t” 参数指定。

未分类

未分类

2.2 Docker 映像命名规范

如果你只是在本地使用,那么你可以随意为 Docker 容器命名。它可以像上面那边简单的命名为“myApp”。但是如果你想将映像发布到 Docker Hub,就需要遵循特定的命名规范。这个规范有助于 Docker 工具将容器映像发布到正确的命名空间和仓库。

格式如下:

未分类

现在我们按上面的规范来构建 Docker 映像:

未分类

我们可以使用“docker tag”命令从已经存在的映像创建新的映像。“docker tag”命令会在下面说明。

2.3 列出 Docker 中所有映像

未分类

未分类

三、运行容器

3.1 启动 Docker 容器

使用“docker run”命令来启动 Docker 容器。

未分类

未分类

“-d” 参数会让容器以独立的模式来运行容器,这样即使终端关闭了容器仍然会保持运行。

“-p” 用于映射容器。比如,“-p 8080:8080” 的第一个端口号用在 Docker 主机上,第二个端口号是在 Docker 容器中使用的。根据这个参数的设置,所有对 Docker 主机端口的数据传输都会被转发到对应的 Docker 容器端口。

3.2 查看运行中的容器

未分类

未分类

从上面的输出我们可以看到 Docker 容器以 “trusting_snyder” 这个名字在运行。

如果要列出所有容器,而不管其状态如何,使用 “-a” 参数。

未分类

3.3 显示运行中容器的控制台日志

未分类

未分类

ContainerName(容器名称) 可以通过 “docker ps” 命令找到。

3.4 登入容器

未分类

上面的命令会用容器中的 “bash” shell 给出提示。

未分类

3.5 停止容器运行

未分类

未分类

3.6 从 Docker 中删除容器映像

未分类

使用 “docker images” 或 “docker images -a” 命令找到 imageId(映像 ID)。

未分类

未分类

上面的命令会强制删除指定的映像。

3.7 清理 Docker / 删除本地 Docker 中所有容器映像

未分类

四、发布容器映像

Docker 容器映像可以发布到本地库或公共的 Docker Hub。两种情况所使用的命令的操作过程一样。为了将你的 Docker 映像发布到 Docker Hub,你得先在 http://hub.docker.com 创建自己的命名空间和仓库。

我自己的命名空间是 “saravasu”,当前练习使用的仓库是 “techietweak”。

未分类

4.1 登录 Docker Hub

未分类

如果你想登录本地库,请先提供 URL。如果没有指定 URL,那么这个命令会登录 Docker Hub。

未分类

未分类

4.2 标记容器映像

在把容器映像推送到 Docker Hub 之前,必须按指定的格式对其进行标记:/:。如果你没有指定版本(version),它会使用默认的 “default”。下面的命令演示了标记映像:

未分类

4.3 将 Docker 映像推送到 Docker Hub

未分类

未分类

4.4 在 Docker Hub 中检查容器映像

现在使用你的账号登录 Docker Hub 并检查映像的仓库中进行检查。

docker+jenkins+seneca构建去集中化微服务架构

前言

在微服务架构中,服务发现一直是一件比较复杂的事。而且服务发现式的架构处理不好,容易产生集中化。同时,微服务的提供,不可避免的需要一些负载均衡方案,实现服务的高可用和可扩展,这无疑增加了很多复杂度。

笔者认为,使用异步、基于消息的方式,可能更适合微服务架构。

基于消息的微服务架构,对于所有微服务的部署条件非常简单,只需要能访问到消息服务即可。同时微服务节点的移除和增加不会影响到服务的提供。相比服务发现的架构,简单太多了,简单即是美。

在这次实践中,使用到了seneca,一个nodejs 微服务框架。seneca,使用seneca-amqp-transport插件,可以轻松构建基于消息的微服务。

下面是架构图:

未分类

https://www.processon.com/view/link/59dc2491e4b0ef561379bc25
在这个架构中,我们使用的是标准的seneca定义的命令规范,这可能是所有微服务都需要遵守的一个规范,至于说使用其他语言,也很简单。封装一个seneca命令规范的库即可。不知道官方有没开发,开发起来难度也不会太大。

接口层比较灵活,可以根据上层应用特性,来决定如何封装传输协议,最后将转化成标准命令发送到消息服务。不建议直接访问消息服务,上层应用应保持灵活。

完整的实践代码:https://github.com/luaxlou/micro-service-practice.git

一、前期准备

使用docker-machine创建虚拟机。

关于docker的一些基本用法,可以读上一篇文章:docker+consul基于服务发现的极简web架构实践,这里就不再赘述。

依次创建3台虚拟机:

$ dm create -d "virtualbox” node1
$ dm create -d "virtualbox” node2
$ dm create -d "virtualbox" node3

二、开始构建

1、搭建Rabbitmq消息服务

消息队列服务,已经成为高并发应用的必备基础服务。我们选用Rabbitmq,你可以换成任意的,遵循amqp协议即可。

使用docker安装很方便,但是生产环境不建议使用docker安装。更推荐的是使用云服务,这样能保证足够高的高可用和扩展性。虽然价格贵点,但是这是唯一的单点,花点钱还是值得的。

直接安装在宿主机上:

$ docker search rabbitmq

NAME                                       DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
rabbitmq                                   RabbitMQ is an open source multi-protocol ...   1466      [OK]
tutum/rabbitmq                             Base docker image to run a RabbitMQ server      11
frodenas/rabbitmq                          A Docker Image for RabbitMQ                     11                   [OK]
sysrun/rpi-rabbitmq                        RabbitMQ Container for the Raspberry Pi 2 ...   6
aweber/rabbitmq-autocluster                RabbitMQ with the Autocluster Plugin            5
gonkulatorlabs/rabbitmq                    DEPRECATED: See maryville/rabbitmq              5                    [OK]
letsxo/rabbitmq                            RabbitMQ with Management and MQTT plugins.      4                    [OK]
bitnami/rabbitmq                           Bitnami Docker Image for RabbitMQ               3                    [OK]
$ docker run -d --name rabbit -p   5672:5672  rabbitmq

这样就启动了一个消息队里服务,并且开放5672端口

2、安装jenkins

jenkins用于自动集成,不然每次构建是个很麻烦的事。

下面的实践是笔者掉了不少坑之后完成的,jenkins在安装过程中会有不少麻烦,而且在mac下安装也会遇到麻烦。

将jenkins 安装到 node1

$ dm ssh node1

$ mkdir /mnt/sda1/var/jenkins_home
$ sudo chown 1000 /mnt/sda1/var/jenkins_home
$ sudo chown 1000 /var/run/docker.sock

$ docker run -d -v /var/run/docker.sock:/var/run/docker.sock 
                -v /mnt/sda1/var/jenkins_home:/var/jenkins_home 
                -v $(which docker):/usr/bin/docker -p 8080:8080 jenkins

查看初始密码:

$ cat /mnt/sda1/var/jenkins_home/secrets/initialAdminPassword

3、安装私有的Registry

在mac上安装即可

$ docker run -d -p 5000:5000 registry

文档参考:https://docs.docker.com/registry/spec/api/

4、准备代码

代码使用的是seneca官方的例子,完整的Dockerfile也已经写好。

FROM node:alpine

RUN npm install pm2 -g
WORKDIR /usr/src/app

COPY package.json ./
RUN npm install
COPY . .

CMD ["pm2-docker","process.yml"]

为了让nodejs能使用到多核cpu,Dockerfile 集成了pm2,使用pm2来管理node进程。

完整代码:
https://github.com/luaxlou/micro-service-practice.git

5、配置自动集成

这里使用了最新版的jenkins,新版的jenkins使用了pipline。一种新的构建方式,使用groovy语法。

写起来是挺优雅的,但是学习成本颇高。因为文档不全及有些文档失效,笔者不得已反编译了pipeline插件,才得以调通。

使用pipeline script

node {
    stage('Preparation') {
        def r = git('https://github.com/luaxlou/micro-service-practice.git')
   }
   stage('Build') {
       dir('seneca-listener') {
          withEnv(["DOCKER_REGISTRY_URL=http://192.168.99.1:5000"]) {

              docker.build("seneca-listener").push("latest")

          }

       }

   }

}

开始构建,顺利的话,会看到如下的结果:

未分类

这是pipeline的特性,可以可视化看到各个阶段的执行情况,算是不小的进步吧。

访问私有Registy的API,就可以看到生成的tag。

curl http://192.168.99.1:5000/v2/seneca-listener/tags/list

6、最后一步,试试我们的程序

在宿主机发布消息:

$ git clone https://github.com/luaxlou/micro-service-practice.git

seneca-clinet 代码是接口层代码的示意,可以根据自己的喜好封装。
同时直接发送了命令代码用于测试。

进入seneca-clinet 目录

$  AMQP_URL=192.168.99.1:5672 node index.js

这个程序会每隔两秒发送一个命令:

#!/usr/bin/env node
'use strict';

const client = require('seneca')()
    .use('seneca-amqp-transport')
    .client({
        type: 'amqp',
        pin: 'cmd:salute',
        url: process.env.AMQP_URL
    });

setInterval(function() {
    client.act('cmd:salute', {
        name: 'World',
        max: 100,
        min: 25
    }, (err, res) => {
        if (err) {
            throw err;
        }
        console.log(res);
});
}, 2000);

虽然一直在发命令,你很快就会发现命令全部超时了。这是因为还没有消费者,当然这些命令也没有丢失,只不过接口层没有得到及时返回。如果应用层支持异步的模式,每个command都有独立的id,可以保留id后,以后再过来取。这就很灵活了,一切看需求去封装接口层即可。

进入node2

$ docker run 192.168.99.1:5000/seneca-listener:latest
0|seneca-l | {"kind":"notice","notice":"hello seneca fwunhukrcmzn/1507605332382/16/3.4.2/-","level":"info","seneca":"fwunhukrcmzn/1507605332382/16/3.4.2/-","when":1507605332661}

启动后,回到seneca-clinet,发现之前超时的命令,全部接收到了。

{ id: 86,
  message: 'Hello World!',
  from: { pid: 16, file: 'index.js' },
  now: 1507605332699 }
{ id: 44,
  message: 'Hello World!',
  from: { pid: 16, file: 'index.js' },
  now: 1507605332701 }
{ id: 56,
  message: 'Hello World!',
  from: { pid: 16, file: 'index.js' },
  now: 1507605332703 }
{ id: 57,
  message: 'Hello World!',
  from: { pid: 16, file: 'index.js' },
  now: 1507605332706 }
{ id: 58,
  message: 'Hello World!',
  from: { pid: 16, file: 'index.js' },
  now: 1507605332707 }

至此,完整架构已经构建完毕。

7、一些未完的事项

  1. 自动集成,只需要配置webhook即可。

  2. 自动部署,因为docker运转的方式,当服务升级时需要重启docker进程。方式有很多,比较粗暴的是直接控制宿主机,或者类似salt这样的工具。

目前来说,没有找到太好的开源方案。个人倾向于自己开发agent,发布有限的API,用于常规的部署或者其他任务,以及可以定时收集服务器的信息,用于监控。这可能会是笔者的下一个开源项目。

三、总结

这篇文章算是一个新的里程碑,实践的成果将用于后期的架构。docker让我从传统的架构模式中脱离出来,同时也让我吃了不少苦头。但这一切都是值得的。

同时也是一个新的开始,终于从之前的公司出来。未来何去何从,有很多的未知,但我相信都是美好的。

这也许就是人生的魅力。

Hello World!!

使用 Docker 搭建 Laravel 本地环境

Laravel 官方提供 Homestead 和 Valet 作为本地开发环境,Homestead 是一个官方预封装的 Vagrant Box,也就是一个虚拟机,但是跟 docker 比,它占用体积太大,启动速度慢,同时响应速度很慢,现在有了 docker 这种更好的方式,可以轻松方便的搭建整套 PHP 开发环境。

本文就介绍如何使用 docker 搭建 Laravel 本地环境。

安装 docker

首先安装 docker。

克隆 laradock

laradock 官方文档:http://laradock.io/

laradock github:https://github.com/laradock/laradock

laradock 是一个包含全功能用于 docker 的 PHP 运行环境,使用 docker-compose 方式部署。(特别说明:它不仅用于 Laravel 环境搭建,而且支持所有其他 PHP 框架,它就是一整套 PHP 的环境。)

部署 PHP 环境

1. 克隆 laradock

git clone https://github.com/Laradock/laradock.git

2. 创建环境变量文件

cp env-example .env

3. 直接用 docker-compose 运行需要启用的服务,如:

docker-compose up -d nginx mysql redis beanstalkd

这样就启动了所需的 PHP 运行环境,php-fpm 默认会运行,所以不需要指定。

未分类

Laravel 配置文件

Laravel 配置文件需要注意的问题是,在 .env 文件中,mysql 和 redis 的地址需填写成这样,而不是 ip 地址形式:

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=tanteng.me
DB_USERNAME=root
DB_PASSWORD=root

REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

注意代码中高亮部分。

Nginx 配置

在本地通过域名方式访问站点,要将 host 中域名绑定到本地,同时还需要增加 nginx 配置。

未分类

如图,在 laradock 项目的 nginx 文件夹下的 sites 目录下添加配置文件即可。

执行 composer

执行 composer 等操作,需要进入到 workspace 容器中进行,使用命令:

docker-compose exec workspace bash

进入到 workspace 容器,就可以进行 compose 命令等操作了。

具体使用上的问题请参加 laradock 官方文档,上面都有说明。

awk 获取指定列的内容进行判断输出

现在有一些日志,要获取倒数第二行的内容,并且判断其值是否为 0,如果为0则输出对应的行。

[root@centos ~]# awk -F ',' '{if($7==0) print $0}' test.txt
上海, 上海, 中国移动, 0.000000 0.000000, 2017-10-10 16:45:14, http://upod-dl.fm/m4a/57c555ed7cb8912ec41015b0_5820095_24.m4a?sign=376ed5bda7ca56c990aed71e107498c0&t=59dd3192, 0, 404

或者

awk -F ',' '{ if($(NF-1)==0) print $0}' test.txt

其中 NF 表示最后一行,NF-1 表示倒数第二行。

awk使用

awk操作两个文件

  • NR,表示awk开始执行程序后所读取的数据行数.

  • FNR,与NR功用类似,不同的是awk每打开一个新文件,FNR便从0重新累计.

下面看两个例子:

1、对于单个文件NR 和FNR 的 输出结果一样的 :

awk ‘{print NR,$0}’ file1

1 a b c d 2 a b d c 3 a c b d
awk ‘{print FNR,$0}’ file1

1 a b c d 2 a b d c 3 a c b d

2、但是对于多个文件 :

awk ‘{print NR,$0}’ file1 file2

1 a b c d 2 a b d c 3 a c b d 4 aa bb cc dd 5 aa bb dd cc 6 aa cc bb dd
awk ‘{print FNR,$0}’ file1 file2

1 a b c d 2 a b d c 3 a c b d 1 aa bb cc dd 2 aa bb dd cc 3 aa cc bb dd

在看一个例子关于NR和FNR的典型应用:

现在有两个文件格式如下:

cat account

张三|000001 李四|000002
cat cdr

000001|10 000001|20 000002|30 000002|15

想要得到的结果是将用户名,帐号和金额在同一行打印出来,如下:

张三  000001   10

张三  000001   20

李四  000002   30

李四  000002   15

执行如下代码

awk -F | ‘NR==FNR{a[$2]=$0;next}{print a[$1]”   “$2}’ account cdr

注释:

由NR=FNR为真时,判断当前读入的是第一个文件account,然后使用{a[$2]=$0;next}循环将account文件的每行记录都存入数组a,并使用$2第2个字段作为下标引用.

未分类

强大的grep,sed和awk–用案例来讲解

准备工作:

先简单了解grep,sed和awk功能  

1) grep 显示匹配特定模式的内容

  • grep -v ‘boy’ test.txt 过滤掉test.txt文件的boy,显示其余内容

  • grep ‘boy’ test.txt 显示test.txt文件中,和boy匹配的内容

  • -E 同时过滤多个”a|b”

  • -i 不区分大小写

  • –color=auto 设置颜色

2)sed 取各种内容,以行为单位取内容

  • -n取消默认输出

  • p=print

  • d=delete 

3)awk 取列

  • -F 指定分割符 如对“I am a student” 以空格为分割符,其将被分为4列,awk里有参数可以去任意列

  • NF 表示当前行记录域或列的个数

  • NR 显示当前记录号或行号

  • $1第一列 $2第二列 $0整行 $NF 最后一列        

案例一:如何过滤出em1的ip地址

[zhaohuizhen@localhost Test]$ ifconfig em1
em1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.8 netmask 255.255.255.0 broadcast 10.0.0.254
inet6 fe80::b283:feff:fed9:6a9a prefixlen 64 scopeid 0x20<link>
ether b0:83:fe:d9:6a:9a txqueuelen 1000 (Ethernet)
RX packets 13908772 bytes 4072069839 (3.7 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 982482 bytes 86260856 (82.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 40

步骤一

首先应该过滤出第二行inet 10.0.0.8 netmask 255.255.255.0 broadcast 10.0.0.254内容

方法一:grep命令

[zhaohuizhen@localhost Test]$ ifconfig em1 | grep 'inet '
 inet 10.0.0.8 netmask 255.255.255.0 broadcast 10.0.0.254

  
方法二:用sed命令

[zhaohuizhen@localhost Test]$ ifconfig em1 | sed -n '2p'
  inet 10.0.0.8 netmask 255.255.255.0 broadcast 10.0.0.254

方法三:用awk命令

[zhaohuizhen@localhost Test]$ ifconfig em1 | awk NR==2

  inet 10.0.0.8 netmask 255.255.255.0 broadcast 10.0.0.254

  
方法四:用head,tail命令

[zhaohuizhen@localhost Test]$ ifconfig em1 | head -2 | tail -1
  inet 10.0.0.8 netmask 255.255.255.0 broadcast 10.0.0.254

步骤二

过滤出第二行后,在过滤出ip地址

方法一:用cut命令

[zhaohuizhen@localhost Test]$ ifconfig em1 | sed -n '2p' | cut -c 14-25
   10.0.0.8

[zhaohuizhen@localhost Test]$ ifconfig em1 | grep 'inet ' | cut -d" " -f10
  10.0.0.8

方法二:用awk命令

[zhaohuizhen@localhost Test]$ ifconfig em1 | grep 'inet ' | awk -F '[ ]+' '{print $3}'
  10.0.0.8

用awk命令可以直接处理第二行,不用先将其过滤出来    

[zhaohuizhen@localhost Test]$ ifconfig em1 | awk -F '[ ]+' 'NR==2 {print $3}' 
  10.0.0.8

  
方法三:用sed命令

[zhaohuizhen@localhost Test]$ ifconfig em1 | sed -n '/inet /p' | sed 's#^.*et ##g' | sed 's# net.*$##g'
  10.0.0.8

此处用到了正则表达式(见http://www.cnblogs.com/ZGreMount/p/7656365.html),匹配的目标前面的字符串一般以^.开头,代表以任意字符开头,结尾写上要匹配的字符前面的几个字符,     如”^.addr “就匹配” inet addr “,而处理的目标后的内容则是开头写上要匹配字符后几个字符,加上以.$。如,“ Bcast:.$”就匹配“ Bcast:10.0.0.254 Mask:255.255.255.”

注:sed小括号分组功能

sed ‘s/********/……./标签’ #斜线可以被其它字符替换

前两条斜线中间部分内容********,可以使用正则表达式,后两条斜线中间内容…….不能使用正则表达式。

()是分组,在前面部分使用()括起来的内容,在后面部分可以使用1调用前面括号内内容。

如果有多个括号,那么依次是1,2,3,以此类推。

例如,直接取em1ip地址,不先过滤出第二行

[zhaohuizhen@localhost Test]$ ifconfig em1 | sed -n 's#^.*inet (.*) net.*$#1#gp'
  10.0.0.8

    
直接取出ip地址和子网掩码

[zhaohuizhen@localhost Test]$ ifconfig em1 | sed -n 's#^.*inet (.*) n.*k (.*) bro.*$#1 2#gp'
  10.0.0.8 255.255.255.0

案例二:输出文件a对应权限664

[zhaohuizhen@localhost Test]$ ll a 
   -rw-rw-r--. 1 zhaohuizhen zhaohuizhen 98 Oct 12 20:24 a

    
方法一:用awk命令

[zhaohuizhen@localhost Test]$ ll a | awk '{print $1}'|tr rwx- 4210|awk -F "" '{print $2+$3+$4 $5+$6+$7 $8+$9+$10}'
  664

    
解析:

1)ll a 长格式显示文件a    

[zhaohuizhen@localhost Test]$ ll a
  -rw-rw-r--. 1 zhaohuizhen zhaohuizhen 98 Oct 12 20:24 a

      
2)用awk命令,以空格为分隔符,取出第一列

[zhaohuizhen@localhost Test]$ ll a | awk '{print $1}'
  -rw-rw-r--.

      
3)用tr命令将rwx- 替换为4210

[zhaohuizhen@localhost Test]$ ll a | awk '{print $1}'|tr rwx- 4210
  0420420400.

      
4)用awk将上面的结果分割,然后相加得出结果

[zhaohuizhen@localhost Test]$ ll a | awk '{print $1}'|tr rwx- 4210|awk -F "" '{print $2+$3+$4 $5+$6+$7 $8+$9+$10}'
  664

方法二:用stat命令

[zhaohuizhen@localhost Test]$ stat a
File: ‘a’
Size: 98 Blocks: 8 IO Block: 4096 regular file
Device: fd02h/64770d Inode: 203491 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1002/zhaohuizhen) Gid: ( 1002/zhaohuizhen)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2017-10-14 09:20:34.337529787 +0800
Modify: 2017-10-12 20:24:27.512609708 +0800
Change: 2017-10-12 20:24:27.536609708 +0800
Birth: -

    
1)命令stat a结果包含文件a对应权限644,可以用前面的方法直接过滤出来

[zhaohuizhen@localhost Test]$ stat a | awk -F '[(/]' 'NR==4 {print $2}' 
  0664

    
2)stat命令包含需要结果,考虑stat命令是否有参数可以直接获得我们需要的结果

[zhaohuizhen@localhost Test]$ stat -c %a a
  664

案例三:输出文件a内容,不带空行,文件a内容如下:

[zhaohuizhen@localhost Test]$ cat a
"hello,this is a test"
I am a studeng My QQ is 1534612574

computer

book

river
tree

man
computer

book
river
tree
man

  
方法一:grep命令

[zhaohuizhen@localhost Test]$ grep -v '^$' a
"hello,this is a test"
I am a studeng My QQ is 1534612574
computer
book
river
tree
man
computer
book
river
tree
man

    
注释:-v 即排除;^$,开头和结尾间没有任何东西,即空行

方法二:用sed命令

[zhaohuizhen@localhost Test]$ sed '/^$/d' a
"hello,this is a test"
I am a studeng My QQ is 1534612574
computer
book
river
tree
man
computer
book
river
tree
man

注释:^$代表空行,d即delete

方法三:用awk命令

[zhaohuizhen@localhost Test]$ awk /[^$]/ a
"hello,this is a test"
I am a studeng My QQ is 1534612574
computer
book
river
tree
man
computer
book
river
tree
man

    
注释:^$代表空行,放在[]中代表非,即不匹配空行。

ubuntu LTS 16.04 编译安装配置Apache

操作系统:ubuntu LTS 16.04
apache版本:2.4.27

一、PRE准备工作

Apache编译安装指南 (http://httpd.apache.org/docs/2.4/install.html) 中给出了编译安装的详细过程,以下是注意事项:

1、安装C编译器

安装C语言编译器gcc-5

sudo apt-get install gcc-5

将gcc符号链接到gcc-5,使gcc命令等同于gcc-5

sudo ln -s /usr/bin/gcc-5 /usr/bin/gcc

2、安装C++编译器

安装C语言编译器g++-5

sudo apt-get install g++-5

将g++符号链接到g++-5,使gcc命令等同于g++-5

sudo ln -s /usr/bin/g++-5 /usr/bin/g++

3、安装make

–fix-missing是修复选项

sudo apt-get install make --fix-missing

4、安装依赖包

sudo apt-get install libexpat1-dev

二、编译安装详细过程

将下载的源码文件都放在/usr/local/src目录下

切换到/usr/local/src目录

cd /usr/local/src

1、安装APR

下载安装APR-1.6.2

去APR官网 (http://apr.apache.org/) 下载对应版本的tar.gz源码压缩包,放到/usr/local/src目录下,并解压

# 解压命令
sudo tar zxvpf apr-1.6.2.tar.gz

进到apr-1.6.2源码目录下

cd apr-1.6.2

编译安装

# 设置输出目录
./configure --prefix=/usr/local/apache2/apr-1.6.2

# 根据自己电脑核数×2来设定并行编译参数,提高编译速度
make -j1 

sudo make install

下载安装APR-util-1.6.0

去APR官网 (http://apr.apache.org/) 下载对应版本的tar.gz源码压缩包,放到/usr/local/src目录下,并解压

# 解压命令
sudo tar jxvpf apr-util-1.6.0.tar.bz2

进到apr-1.6.2源码目录下

cd apr-util-1.6.0

编译安装,注意apr路径参数要与之前apr安装路径参数相同

./configure --prefix=/usr/local/apache2/apr-util-1.6.0 --with-apr=/usr/local/apache2/apr-1.6.2

make -j1

make install

下载安装PCRE

去PCRE官网 (https://sourceforge.net/projects/pcre/files/pcre/) 下载pcre-8.41源码压缩包,放到/usr/local/src`目录下,并解压

# 解压命令
sudo tar zxvpf pcre-8.41.tar.gz

进到pcre-8.41源码目录下

cd pcre-8.41

编译安装

./configure --prefix=/usr/local/pcre-8.41

make -j1

make install

下载安装apache2.4.27

去Apache官网 (http://httpd.apache.org/download.cgi#apache24) 下载apache2.4.27源码压缩包,放到/usr/local/src目录下,并解压

# 解压命令
sudo tar zxvpf httpd-2.4.27.tar.gz

进到httpd-2.4.27.tar.gz目录下

cd httpd-2.4.27

编译安装

sudo ./configure --prefix=/usr/local/apache2 --with-apr=/usr/local/apache2/apr-1.6.2 --with-apr-util=/usr/local/apache2/apr-util-1.6.0/ --with-pcre=/usr/local/pcre-8.41 --with-expat=builtin --enable-so --enable-rewrite --enable-ssl

sudo make -j1

sudo make install
  • –prefix : 目标路径

  • –with: 依赖的库文件的路径

  • enable-ssl : 支持SSL加密

  • enable-so : 支持动态加载模块

添加启动脚本apache2到service

sudo cp /usr/local/apache2/bin/apachectl /etc/init.d/apache2

添加apache2到环境变量

# 输出启动脚本至 /home/phdchorus/apache2.sh
sudo echo 'export PATH=$PATH:/usr/local/apache2/bin' > /home/phdchorus/apache2.sh

cd /home/phdchorus

# 修改启动脚本的读写权限
sudo chmod a+x apache2.sh

# 拷贝脚本至目录
sudo cp apache2.sh /etc/profile.d

# 更新脚本状态
source /etc/profile.d/apache2.sh

添加apache2到开机启动项

sudo vim /etc/rc.local

将/etc/profile.d/httpd.sh添加到exit 0之前

/etc/profile.d/apache2.sh

exit 0

启动apache2

sudo service apache2 start

三、Apache配置

转到apache目录下,可以看到以下目录结构

phdchorus@phdchorus:/usr/local/apache2$ ls
bin  conf  error  htdocs  logs  modules
  • bin是apache启动目录

  • conf是apache配置文件目录

  • htdocs是默认的DocumentRoot

  • logs是默认的日志目录

  • modules是apache的扩展链接(PHP扩展、SSL扩展…)所在的目录

转到conf目录下,可以看到以下目录结构

phdchorus@phdchorus:/usr/local/apache2/conf$ ls
extra  httpd.conf  magic  mime.types  original
  • httpd.conf是apache的主配置文件

  • extra中存放了httpd.conf之外的配置文件

安装配置Apache中记录了通过apt-get安装Apache后,对apache的各种配置。apt-get安装apache后,apache配置按照种类分散到多个目录文件下面,主配置文件import这些配置文件,从而实现了配置模块化的效果。而编译安装的apache将几乎所有的配置都放到了主配置文件中,是非常不利于维护的。接下来我们先配置Apache,再按照模块化配置的思路,重构apache的主配置文件。

1、配置Apache

基本配置

因为装机时,我们的计划是将网络服务资源放在/var下,所以要修改DocumentRoot及DocumentRoot对应的Directory项

找到DocumentRoot "/usr/local/apache2/htdocs"
修改为DocumentRoot "/var/www/html" --该目录为自己创建的目录

找到:<Directory "/usr/local/apache2/htdocs"> 
修改为:<Directory "/var/www/html">

配置Apache解析PHP

在主配置文件中写入

LoadModule php7_module modules/libphp7.so

AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps

修复安全漏洞

在主配置文件中写入

TraceEnable off
ServerSignature off

Linux(Ubuntu16.04)apt-get install安装Nginx + PHP7+Mysql5.7

切换到root帐号,安装软件包源: apt-get install software-properties-common

1、安装PHP7.1

add-apt-repository ppa:ondrej/php

apt-get update

apt-get install php7.1-cli php7.1-fpm php7.1-common php7.1-curl  php7.1-xml php7.1-gd php7.1-mysql php7.1-mbstring php7.1-bcmath php7.1-dev php7.1-zip

相关服务命令:

service php7.1-fpm start/stop/restart

2、安装Nginx

add-apt-repository ppa:ondrej/nginx

apt-get update

apt-get install nginx-full

配置站点php-fpm

vim /etc/nginx/sites-available/default
fastcgi_pass  unix:/run/php/php7.1-fpm.sock;

相关服务命令:

service nginx start/stop/restart

3、安装Mysql5.7

add-apt-repository ppa:ondrej/mysql

apt-get update

apt-get install mysql-server-5.7

配置远程链接

创建远程链接帐号

mysql -u root -h localhost -p

mysql> GRANT ALL PRIVILEGES ON *.* TO 'remote_root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;

mysql> FLUSH PRIVILEGES;

修改cnf配置

vim  /etc/mysql/mysql.conf.d/mysqld.cnf
将bind-address    = 127.0.0.1

设置成bind-address    = 0.0.0.0(设备地址)

重启mysql

service mysql restart