docker 进阶

目录

第一章、docker 仓库
第二章、docker 网络高级
第三章、Dockerfile
第四章、IDE快速整合使用
第五章、docker Compose
第六章、docker swarm

第一章、docker 仓库

#阿里云下载镜像,需要填写前缀
- 创建命名空间
- 创建镜像仓库

docker pull registry.cn-shenzhen.aliyuncs.com/ali/mysql:[镜像版本号]

ali:命名空间
mysql:镜像仓库

1.docker私有仓库harbor

#1.安装dockercompose
curl -SL https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose

sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

docker-compose version

#2.安装harbor
cd /opt
wget https://github.com/goharbor/harbor/releases/download/v2.8.4/harbor-offline-installer-v2.8.4.tgz
tar xf harbor-offline-installer-v2.8.4.tgz

#3.加载镜像
docker load -i harbor.v2.8.4.tar.gz 

#4.修改配置文件

#4.1.复制文件
cp harbor.yml.tmpl harbor.yml

#4.2.修改文件
vim harbor.yml
 
修改如下:
hostname: kubernetes-register.openlab.cn
#https:
#  port: 443
#  certificate: /your/certificate/path
#  private_key: /your/private/key/path
 
harbor_admin_password: 123456
data_volume: /opt/harbor/data
 
#4.3.运行
./prepare
./install.sh

#4.4.定制启动文件
cat > /etc/systemd/system/harbor.service <<-EOF
[Unit]
Description=Harbor
After=docker.service systemd-networkd.service systemd-resolved.service
Requires=docker.service
Documentation=http://github.com/vmware/harbor
[Service]
Type=simple
Restart=on-failure
RestartSec=5
#需要注意harbor的安装位置
ExecStart=/usr/local/bin/docker-compose --file /opt/harbor/docker-compose.yml up
ExecStop=/usr/local/bin/docker-compose --file /opt/harbor/docker-compose.yml down
[Install]
WantedBy=multi-user.target
EOF
 
#4.5.重启
systemctl daemon-reload
systemctl enable harbor.service
systemctl restart harbor

第二章、docker 网络

Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。

1586516724992.png

Linux虚拟网络技术

Docker容器网络就很好的利用了Linux虚拟网络技术,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫veth pair);

Docker中的网络接口默认都是虚拟的接口。虚拟接口的优势就是转发效率极高(因为Linux是在内核中进行数据的复制来实现虚拟接口之间的数据转发,无需通过外部的网络设备交换),对于本地系统和容器系统来说,虚拟接口跟一个正常的以太网卡相比并没有区别,只是他的速度快很多。

原理:
   1、每一个安装了Docker的linux主机都有一个docker0的虚拟网卡。桥接网卡

   2、每启动一个容器linux主机多了一个虚拟网卡。    

1.docker 网络创建方法

#1、docker0网络的特点
  默认
  域名访问不通
  --link 域名通了,但是删了又不行
  --link:(不推荐使用)
    默认在docker0网络下,用主机名ping不通; 
        docker exec -it tomcat1 ping tomcat2

    设置hostname也没用; 
        docker run -d -P --hostname tomcat1 --name tomcat1 tomcat:9
        docker run -d -P --hostname tomcat2 --name tomcat2 tomcat:9

    使用link方法,直接把需要link的主机的域名和IP绑定在容器hosts里
        docker run -d -P --name tomcat1 --link tomcat2 tomcat:9
     
    删除link容器,直接强制把link文件删除了

#2、可以让容器创建的时候使用自定义网络

  1、自定义创建的默认default "bridge"

  2、自定义创建一个网络网络
        docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
        --driver:指定网络方式
        --subnet:指定子网范围
        --gateway: 指定网关地址

    使用自定义网络
        docker run -d -P --name tomcat --ip 192.168.0.77  --net mynet tomcat:9
        --ip: 自定义ip地址
        --net:使用自定义网络

     跨网络连接语法
        docker network connect [OPTIONS] NETWORK CONTAINER
        docker network connect mynet tomcat
    效果:
        1、自定义网络,默认都可以用主机名访问通
        2、跨网络连接别人就用 docker network connect mynet tomcat

2.docker 网络创建过程

Docker创建一个容器的时候,会具体执行以下操作:

  1. 创建一对虚拟接口,分别放到本地主机和新容器的命名空间中
  2. 本地主机一段的虚拟接口连接到默认的docker0网桥或者指定网桥上,并具有一个以veth开头的唯一名字
  3. 容器一段的虚拟接口将放到新创建的容器中,并修改名字为eth0。这个接口只在容器的命名空间可见
  4. 从网桥的可用地址段中获取一个空闲的地址分配给容器的eth0(如:172.17.0.2/16),并配置默认路由网关为docker0网卡的内部接口docker0的IP地址(如:172.17.42.1/16)

完成以上,容器就可以使用它所能看到的eth0虚拟网卡来连接其他容器和访问外部网络。

3.docker 网络模式

网络模式 配置 说明
bridge模式 --net=bridge 默认值,在Docker网桥docker0上为容器创建新的网络栈
none模式 --net=none 不配置网络,用户可以稍后进入容器,自行配置
container模式 --net=container:name/id 容器和另外一个容器共享Network namespace。<br /> kubernetes中的pod就是多个容器共享一个Network namespace。
host模式 --net=host 容器和宿主机共享Network namespace
用户自定义 --net=自定义网络 用户自己使用network相关命令定义网络,<br />创建容器的时候可以指定为自己定义的网络

第三章 Dockerfile

应用-->Dockerfile--->打包成镜像--->上传到仓库(公有仓库,私有仓库)--->下载镜像--->启动运行

1586440635817.png

1.构建自己dockerfile

1、创建项目dockerfile

2、上传项目到服务器。

3、进入项目,构建镜像到本地仓库;

  • docker build -t nginx:1.0 -f ./Dockerfile . 别忘了最后的小数点。
  • docker images 查看镜像
  • docker exec -it 容器id /bin/bash;进入容器,修改容器
  • docker commit -a “icoding” -m “nginxxx” 容器id mynginx:2.0
    • docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
    • OPTIONS说明:
      • -a :提交的镜像作者;
      • -c :使用Dockerfile指令来创建镜像;
      • -m :提交时的说明文字;
      • -p :在commit时,将容器暂停。
  • docker login : 登陆到一个Docker镜像仓库,如果未指定镜像仓库地址,默认为官方仓库 Docker Hub
    • docker login -u 用户名 -p 密码
  • docker logout : 登出一个Docker镜像仓库,如果未指定镜像仓库地址,默认为官方仓库 Docker Hub

4、推送镜像到docker hub

  • 标记镜像,docker tag local-image:tagname username/new-repo:tagname
  • 上传镜像,docker push username/new-repo:tagname

5、保存镜像,加载镜像

  • 可以保存镜像为tar,使用u盘等设备复制到任意docker主机,再次加载镜像
  • 保存:docker save spring-boot-docker -o /home/spring-boot-docker.tar
  • 加载:docker load -i spring-boot-docker.tar

6、阿里云操作

  • 登录阿里云,密码就是开通镜像仓库时 的密码
    • docker login --username=icoding registry.cn-hangzhou.aliyuncs.com
  • 拉取镜像
    • docker pull registry.cn-hangzhou.aliyuncs.com/icoding/i-nginx:v1.0
  • 推送镜像
    • docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/icoding/icoding-nginx:v2
    • docker push registry.cn-hangzhou.aliyuncs.com/icoding/icoding-nginx:v2

2、Dockerfile 指令

Dockerfile由一行行命令语句组成,并且支持以#开头的注释行。
一般而言,Dockerfile可以分为四部分 基础镜像信息 维护者信息 镜像操作指令 启动时执行指令

指令 说明
FROM 指定基础镜像
MAINTAINER 指定维护者信息,已经过时,可以使用LABEL maintainer=xxx 来替代
RUN 运行命令 v
CMD 指定启动容器时默认的命令 v
LABEL 指定生成镜像的元数据标签信息 v
EXPOSE 声明镜像内服务监听的端口 v
ENV 指定环境变量,可以在docker run的时候使用-e改变 v
ADD 复制指定的src路径下的内容到容器中的dest路径下,src可以为url会自动下载,可以为tar文件,会自动解压
COPY 复制本地主机的src路径下的内容到镜像中的dest路径下,但不会自动解压等
ENTRYPOINT 指定镜像的默认入口.运行命令 v
VOLUME 创建数据卷挂载点
USER 指定运行容器时的用户名或UID
WORKDIR 配置工作目录,为后续的RUN、CMD、ENTRYPOINT指令配置工作目录
ARG 指定镜像内使用的参数(如版本号信息等),可以在build的时候,使用--build-args改变 v
OBBUILD 配置当创建的镜像作为其他镜像的基础镜像是,所指定的创建操作指令
STOPSIGNAL 容器退出的信号值
HEALTHCHECK 健康检查
SHELL 指定使用shell时的默认shell类型

3、dockerfile实战

#1、ping命令小工具。
编写Dockerfile-->打包镜像--->根据这个镜像启动容器使用容器的功能
docker build -t hello .
-t:tag标签。镜像名字
.:在当前目录下工作
默认.这个目录下就得有一个 Dockerfile
FROM alpine
CMD ping baidu.com

#2、文件名不是Dockerfile。用-f 指定
docker build -f Dockerfile2 -t hello:v1.0 .
docker run -d --name hello hello;

#3、默认构建出来的镜像,放到了我们的本地镜像仓库。
    1)、登陆到dockerhub
    2)、dockehub国外,就算能Push,非常慢。阿里云加速的是下载。

#4、把这个镜像发布到Docker hub
    1)、docker login -u 用户名 -p 密码;登陆到docker hub
    2)、docker push nginx:v1.0;推送过去。太慢了

#5、搭建私有的镜像仓库。使用阿里云的镜像仓库(免费)。
    1)、docker login --username=艾涵曦儿 registry.cn-hangzhou.aliyuncs.com
    2)、docker tag hello:v1 registry.cn-hangzhou.aliyuncs.com/zkaima/hello:v2
    3)、docker push registry.cn-hangzhou.aliyuncs.com/zkaima/hello:[镜像版本号]

4、Dockerfile区分一些易混淆的指令

1.USER 执行cmd等之类命令的使用那个用户

alpine  sudo   gosu
FROM centos
RUN groupadd -r abc && useradd -r -g abc aaa
USER aaa
CMD whoami
# CMD 就是容器启动以后要执行的命令

2. ARG、ENV 定义环境变量

  • The ARG instruction defines a variable that users can pass at build-time to the builder with the docker build :arg在构建期间。docker build
  • The environment variables set using ENV will persist when a container is run from the resulting image. You can view the values using docker inspect, and change them using docker run --env =. env在运行是可以用到的
FROM alpine

ARG bbb=haha
ENV abc 666

CMD echo $bbb
容器运行的时候arg的东西拿不到

CMD echo $abc
容器运行的时候env的能拿到

FROM alpine

ARG bbb=haha
ENV abc=$bbb

CMD echo $abc

ARG指定的值,在镜像构建的后面位置,构建期间都可以使用到。
docker build -f Dockerfile-arg-env -t file-arg --build-arg bbb=888 .
docker run -it --rm file-arg
结果888
docker build -f Dockerfile-arg-env -t file-arg --build-arg bbb=888 --build-arg abc=777 .
结果888 


# 构建时 bbb=888 abc=777 ,cmd打印888.原因 构建时不能改变env
# 运行时 evn可以改变
docker run -it --rm -e abc=helloword
结果helloword

 最佳实战:

ARG:定义一些版本信息

FROM alpine 

ARG version=1

RUN yum install nginx:$version

Env:运行时的环境变量。

ENV  env = --spring.profile.active=prod

    -e可以修改。
sb java -jar xxx.jar $env 
\ ARG ENV
build时 √。--build-arg改变 构建参数(ARG声明) 生效,能不能改(不能)
运行时 不生效 生效。能改 -e abc

3. ADD 、COPY

ADD ["<src1>","<src2>","<dest>"]  dest:容器里面的目录
可以指定很多种路径地址。自动下载,解压复制。

COPY ["<src1>","<src2>","<dest>"]  dest:
复制本地主机的src路径下的内容到镜像中的dest路径下,但不会自动解压


FROM alpine

ADD hello.tar /opt/hello

COPY hello.tar /opt/world/

CMD echo "1234"

这个东西构建的镜像为什么docker run -d 不行。因为容器运行的是ech 1234;
没有一个守护进程一直运行。
CMD ping baidu.com

4. VOLUME和WORKDIR

VOLUME:指定容器需要挂载的卷
WORKDIR:工作目录。
    1、以后的其他命令在这个目录里面运行
    2、exec进去都默认来到了 WORKDIR 指定的目录。sh
    docker run -it --rm file-volume sh
    
WORKDIR


WORKDIR /root  == RUN cd /root

挂载麻烦。自动挂载。
FROM alpine

WORKDIR /opt/a

VOLUME /opt/b

COPY hello.txt .

ADD hello.tar /opt/b

CMD whoami

volume声明的挂载目录,即使容器运行的时候,不用-v进行挂载。docker也会自动的进行匿名挂载。

5. RUN、CMD、ENTRYPOINT

相同点:运行命令

不同点:RUN:在构建镜像的时候运行的命令

CMD、ENTRYPOINT:在容器启动运行的命令

1.测试RUN

dcoker pull centos:7.9.2009

#想构建一个具有git功能的镜像。
FROM centos:7

RUN yum install -y git

WORKDIR /opt/data

RUN git clone  https://gitee.com/ma_zhen_kai/django.git

VOLUME /opt/data
CMD echo "git clone success"

#CMD 容器运行的时候CMD的命令才执行。
  • -RUN指令的所有命令都在镜像docker build期间就执行
  • CMD和ENTRYPOINT在容器启动时运行
    • CMD 容器运行的时候CMD的命令才执行。docker run -it --rm file-run bash 能进容器中
    • 替换为ENTRYPOINT。虽然指令运行了。但是 docker run -it --rm file-run bash 。最后的bash没有进去,失效了。

无论是CMD还是ENTRYPOINT还是RUN

  • RUN (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows) RUN yum install -y git;/bin/sh -c可以动态获取一些变量
  • RUN ["executable", "param1", "param2"] (exec form);无法动态获取一些变量
FROM centos:7

ARG soft=git
RUN ["yum install","-y","$soft"] #这是错误的。因为非`/bin/sh -c`方式,用不到前面声明的ARG,ENV

WORKDIR /opt/data

RUN git clone https://gitee.com/ma_zhen_kai/django.git

VOLUME /opt/data
ENTRYPOINT echo "git clone success"
# bash-c 和 数组方式的区别,  修改后的;安装正确。
FROM centos

ARG soft=git
RUN ["/bin/sh","-c","yum install -y $soft"]
#-c command:后免是完整命令
WORKDIR /opt/data

RUN git clone https://gitee.com/lanoox/luject.git

VOLUME /opt/data
ENTRYPOINT echo "git clone success"
2.RUN、CMD、ENTRYPOINT都支持一下两种方式

The exec form, which is the preferred form:

ENTRYPOINT ["executable", "param1", "param2"]

The shell form:

ENTRYPOINT command param1 param2
3.RUN、CMD、ENTRYPOINT区别
  1. RUN 执行命令并创建新的镜像层RUN 经常用于安装软件包(在构建镜像时运行的)。
  2. CMD 设置容器启动后默认执行的命令及其参数但 CMD 能够被 `docker run` 后面跟的命令行参数替换。
  3. ENTRYPOINT 配置容器启动时运行的命令。
  4. 以上命令都可以使用shell或者exec方式执行
  5. `CMD ["executable","param1","param2"]` (*exec* form, this is the preferred form)
  6. `CMD ["param1","param2"]` (as *default parameters to ENTRYPOINT*)
  7. `CMD command param1 param2` (*shell* form)
4.shell和exec方式
1.  shell 是  /bin/sh -c <command>的方式,

2.  exec ["/bin/sh","-c",command] 的方式== shell方式

eg:shell方式

ENV name icoding  
ENTRYPOINT echo "Hello, $name" 
#输出 Hello icoding

ENTRYPOINT ["/bin/echo", "Hello, $name"]
#输出 Hello $name

ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]
#输出 Hello icoding

##建议:CMD 和 ENTRYPOINT 推荐使用 Exec 格式因为指令可读性更强更容易理解。RUN 则两种格式都可以。exec的时候要获取环境变量等值,就用/bin/sh -c 即可。
No ENTRYPOINT ENTRYPOINT exec_entry p1_entry ENTRYPOINT [“exec_entry”, “p1_entry”]
No CMD error, not allowed /bin/sh -c exec_entry p1_entry exec_entry p1_entry
CMD [“exec_cmd”, “p1_cmd”] exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry exec_cmd p1_cmd
CMD [“p1_cmd”, “p2_cmd”] p1_cmd p2_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry p1_cmd p2_cmd
CMD exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd
1683034287471.jpg

总结:

  • 如果运行命令是。[]方式,默认不是bash -c就无法用变化,普通的方式RUN yum -install -y $soft可以使用变量。

  • CMD、ENTRYPOINT的最佳实战

    • Dockerfile文件必须至少有一个 CMD 或者ENTRYPOINT 命令.
    • ENTRYPOINT 用来定义容器如何运行.
    • CMD 应该被用来作为给ENTRYPOINT 传递。默认参数的一种方式
    • CMD 将会被覆盖,容器运行时指定参数的时候. ENTRYPOINT 命令不会被覆盖。
    • CMD多个只会有一个生效。
    • ENTRYPOINT :不被传入的指令覆盖,但是多个也只有一个生效。

    为什么我的指令Dockerfile写CMD的时候,docker run -it --rm file-run bash可以进控制台,而ENTRYPOINT 不行?

    运行效果:

  • Dockerfile是CMD;没打印git clone success。但是bash生效。

  • Dockerfile 是 ENTRYPOINT;打印了git clone success但是没有进容器(bash没生效)

5.混写。CMD + ENTRYPOINT
FROM centos

ARG soft=git
RUN ["/bin/bash","-c","yum install -y $soft"]

WORKDIR /opt/data

RUN git clone https://gitee.com/lanoox/luject.git

VOLUME /opt/data


CMD ["nginx"] #CMD给entrypoint提供参数
ENTRYPOINT ["yum","install","-y"]


运行:
docker run -it --rm file-run maven
FROM centos

ARG soft=git
RUN ["/bin/bash","-c","yum install -y $soft"]

WORKDIR /opt/data

RUN git clone https://gitee.com/lanoox/luject.git

VOLUME /opt/data


CMD ["","","",""] #可以放空
ENTRYPOINT ["/bin/sh","-c","yum install -y"]

#容器启动就是yum install -y "";自己启动命令加上git
CMD 后面有N参数。传了参数替换N个还是最后一个?
docker run -it --rm file-run maven  CMD ["maven"]  对
docker run -it --rm file-run maven  CMD [“”,“”,"maven"] 错

第四章、IDE快速整合使用

1.打开远程访问

#修改该Docker服务文件
vi /lib/systemd/system/docker.service
#修改ExecStart这行,将原来注释,加上这个命令
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
#重新加载配置文件
systemctl daemon-reload
#重启服务
systemctl restart docker.service
#查看端口是否开启
netstat -nlpt #如果找不到netstat命令,可进行安装。yum install net-tools
#直接curl看是否生效
curl http://127.0.0.1:2375/info

2.IDEA安装Docker插件

1586754551705.png

3.IDEA配置docker

image.png

4.整合自己的私有仓库

image.png

1586755804914.png

第五章、docker Compose

https://docs.docker.com/compose/

Dockerfile。商城。拆分很多微服务。10微服务

购物车微服务+Redis+MySQL = 一套服务(自动化的全套部署,全套下线)。交给Docker Compose【编排】。一个整套的application会有非常多的service

一次性的将所有相关联的服务自动部署。

前面我们使用 Docker 的时候,定义 Dockerfile 文件,然后使用 docker build、docker run 等命令操作容器。然而微服务架构的应用系统一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启停,那么效率之低,维护量之大可想而知。批量的维护整套应用

使用 Docker Compose 可以轻松、高效的管理容器,它是一个用于定义和运行多容器 Docker 的应用程序工具

Compose的三步最佳实战:

  1. 定义自己应用的 Dockerfile 文件。为了在anywhere进行构建。

  2. 使用 docker-compose.yml定义我们服务。这一整套服务可以一起在一个隔离环境运行.

  3. 运行 docker-compose up 就可以启动整个app.

总结:批量服务编排。docker-compose.yml声明服务的规则。

1、安装

https://docs.docker.com/compose/install/other/

curl -SL https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose

sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

docker-compose version

2、体验

https://docs.docker.com/compose/gettingstarted/

#1.Create a directory for the project:
mkdir composetest
cd composetest

#2.Create a file called app.py in your project directory and paste the following code in:
import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            # 异常执行以下代码
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)

#3、Create another file called requirements.txt in your project directory and paste this in:
flask
redis

3、创建Dockerfile

#In your project directory, create a file named Dockerfile and paste the following code in:
# syntax=docker/dockerfile:1
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]

4、在Compose file里定义服务

#Create a file called docker-compose.yml in your project directory and paste the following:
version: '3'
services:
  web:
    build: .
    ports:
      - "5000:5000" 
  redis:
    image: "redis:alpine"

5、Compose构建和运行应用

docker-compose up -d
#访问 http://localhost:5000/ 测试效果

6、效果

自己打包---》dockerfile--》结合docker-compose

1、创建网络

version: '3'
services:
  web:
    build: .
    ports:
      - "5000:5000"
  redis:
    image: "redis:alpine"
#默认两个服务都用一个网络,是contextName_default;web里面可以使用redis的hostname访问
#cache = redis.Redis(host='redis', port=6379)
#默认每一个启动的容器 contextName_serviceName_1
docker network inspect composetest_default  
#效果:这个网络里面有。两个容器
#docker service ls #查看当前的服务。docker集群下,我们能看到所有的服务 容器的名_num。代表第几个副本。

集群状态下;compose更帅。我们可以直接定义服务在多个机器都有副本。

2、构建每个镜像

3、创建两个容器在同一网络,我们可以使用hostname访问

docker-compose帮我们自定的部署了pyweb,redis,并且pyweb能访问redis。pyweb的5000浏览器也能访问
整个docker:

1、dockerfile

2、docker-compose

3、docker二次开发,如何更换底层存储驱动,如何自定义docker的网络。

详情:https://docs.docker.com/compose/compose-file/#reference-and-guidelines

#语法
三层
version: ""
services:  #定义很多服务
    服务1:
      #当前服务配置
    服务2:
      #当前服务配置
#服务要用的网络、卷,等其他全局规则,定义
volumes:
networks:
configs:
secrets:



networks:
  outside:
    external: true #外面创建好了,docker-compose不自动创建
如果自定义用了网络。卷等,都必须声明
version: "3"
services:
  java-app: 
     build: .
     ports:
       - "9000:8080"
     depends_on:
       - mysql
     command: java -jar /app.jar --spring.datasource.url=jdbc:mysql://mysql:3306/wps?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
     networks:
       - app-network
  mysql:
    image: mysql:5.7
    environment:
       MYSQL_ROOT_PASSWORD: 123456
    networks:
       - app-network

networks:
  app-network: {}


docker-compose up -d
docker-compose up -d --build #再触发一下手动构建


随便网上一个项目。dockerfile+docker-compose 一键搞定
mysql dockerfile-->run 网上的项目拉下来,把的sql文件导入数据。


docker-compose替代了dockerfile。完全可以(镜像存在)自定义的镜像 dockerfile+docker-compose。

整个docker-compose up/down/rm 单机情况下的编排

第六章 docker swarm

架构

20190603170539763.png

swarm集群
https://docs.docker.com/engine/swarm/ swarm入门操作
在所有docker主机上,选定一个主节点。运行docker swarm init --advertise-addr ip地址,初始化一个manager

docker swarm init \
--advertise-addr 172.168.0.66
--listen-addr 172.168.0.66
#关于advertise-addr和listen-addr这两个参数,前者用来指定其他节点连接m0时的地址,后者指定承载swarm流量的IP和端口

剩下节点使用docker swarm join即可

docker swarm join-token manager
#可以生成Manager节点的join信息,再选中一个节点,运行命令就可以作为manager加入

docker node ls #查看节点信息

1.告别docker run

#1、docker-compose up 使用compose启动一个服务。docker-compose是单机下的玩具
#2、集群:docker service (使用docker管理服务。)容器升级为服务。
# docker service:管理集群里面的所有服务
# docker stack:管理集群的app stack;一个完整的应用有超多服务,就是一个服务栈

#扩容;
docker service create --name hello-nginx -p 8888:80 --replicas 3 nginx
docker service update --replicas 3 hello-nginx

#升级。滚动升级
docker service rollback hello-nginx; 当前+之前 来回回滚

#灰度发布,迭代。
docker service update --image nginx:1.18.0-alpine --update-parallelism 1 --update-delay 10s hello-nginx

2.测试在Swarm中部署服务(nginx为例)

#1、可以创建网络
# docker network create -d overlay nginx_net
#2、部署service
docker service create --replicas 1 --name my_nginx -p 88:80 nginx    
# 就创建了一个具有一个副本(--replicas 1 )的nginx服务,使用镜像nginx
docker service ls
docker service inspect --pretty my_nginx
docker service ps my_nginx
#3、扩容
docker service scale my_nginx=4
docker service ps my_nginx
#4、模拟宕机查看效果
systemctl stop docker
docker node ls
docker service ps my_nginx
#5、Swarm 动态缩容服务(scale)
docker service scale my_nginx=1
#6、update扩缩容
docker service update --replicas 3 my_nginx
#7、update镜像升级
docker service update --image nginx:latest my_nginx
#8、服务移除
docker service rm my_nginx
   #1、创建名为tomcat-net的覆盖网络(Overlay Netowork),这是个二层网络,处于该网络下的docker容器,即使宿主机不一样,也能相互访问:
    docker network create -d overlay tomcat-net
    #2、创建名为tomcat的服务,使用了刚才创建的覆盖网络:
    docker service create --name tomcat \
    --network tomcat-net \
    -p 8080:8080 \
    --replicas 2 \
    tomcat:7.0.96-jdk8-openjdk
    #3、执行命令docker service ls查看当前所有服务:
    #4、执行命令docker service ps tomcat查看名为tomcat的服务,可见三个容器分别部署在m0、m2、w1机器上:
    #5、执行命令docker service inspect --pretty tomcat查看名为tomcat的服务的详细信息(去掉--pretty可以看到更完整的):
    #6、访问三个节点的tomcat,都能访问
    #7、扩缩容
    docker service scale tomcat=5
    #8、滚动更新
    docker service update \
    --image tomcat:9.0.24-jdk11-openjdk \
    --update-parallelism 1 \
    --update-delay 10s tomcat
    #update-parallelism:每次更新的容器数量,这里设置为1,表示每一个容器升级成功后才去升级下一个;
    #update-delay:每一批升级成功后,升级下一批之前的等待时间,这里表示升级一个容器后等10秒再升级下一个;
    #在升级过程中执行命令docker service ps tomcat查看服务,可以看到新版本容器逐个启动的过程:
    #9、移除服务
    docker service rm tomcat

3.服务模式

  • 服务模式一共有两种:Ingress和Host,如果不指定,则默认的是Ingress;
    • Ingress模式下,到达Swarm任何节点的8080端口的流量,都会映射到任何服务副本的内部80端口,就算该节点上没有tomcat服务副本也会映射;
    • Host模式下,仅在运行有容器副本的机器上开放端口,使用Host模式的命令如下:
docker service create --name tomcat \
--network tomcat-net \
--publish published=8080,target=8080,mode=host \
--replicas 3 \
tomcat:7.0.96-jdk8-openjdk

默认swarm集群的所有部署,(3副本)。

manager节点负责调度,真正会在其他节点执行(创建副本)【默认worker优先,基本随机】。我们不知道我们的容器最终会发配给那个服务器。指定服务器,给他部署上去(label)。

1、ssd硬盘 ,数据存储,mysql。合适作为数据节点,存储节点

2、cpu厉害,适合作为计算,大数据,数据分析框架,就应该优先调度到cpu厉害的。hadoop存储之类的,就应该调度到ssd磁盘。

Drain a node:排空节点。把节点里面的容器驱逐出去。
In earlier steps of the tutorial, all the nodes have been running with ACTIVE availability. The swarm manager can assign tasks to any ACTIVE node, so up to now all nodes have been available to receive tasks. ACTIVE的节点,manager就可以分配任务进行部署。

docker node update --availability drain worker1
#这台机器用service部署产生的所有容器,都被驱逐出去了。当前docker就没有service的容器。但是集群为了保证副本数量会在其他机器拉起。

docker node update --availability pause worker1 #暂停接受给我的任务。
 --availability active
#100 最好的状态,20台计算型的 。40台存储型的。30台冗余备份的
docker service hadoop --->调度到 40台存储型的;Label选择


# routing mesh;路由网
docker service create --name nginx -p 8080:80 --replicas 3 nginx
只要service是暴露端口的方式,全服务器都能访问。
#真正的nginx容器的ip
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
32: eth0@if33: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP 
    link/ether 02:42:0a:00:00:12 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.18/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
34: eth1@if35: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.3/16 brd 172.18.255.255 scope global eth1
       valid_lft forever preferred_lft forever

#集群状态下,每个主机都会多两个网络
43c75b51441f        docker_gwbridge     bridge              local
o7b9pfkdt2ti        ingress             overlay             swarm

ingress: "Subnet": "10.0.0.0/24", "Gateway": "10.0.0.1"
docker_gwbridge:"Subnet": "172.18.0.0/16"
集群状态下,service创建的容器默认加入两个网络。 docker_gwbridge 、 ingress

4.Docker Stack和Docker Compose区别

  • Docker stack会忽略了“构建”指令,无法使用stack命令构建新镜像,它是需要镜像是预先已经构建好的。 所以docker-compose更适合于开发场景;
  • Docker Compose是一个Python项目,在内部,它使用Docker API规范来操作容器。所以需要安装Docker -compose,以便与Docker一起在您的计算机上使用;
  • Docker Stack功能包含在Docker引擎中。你不需要安装额外的包来使用它,docker stacks 只是swarm mode的一部分。
  • Docker stack不支持基于第2版写的docker-compose.yml ,也就是version版本至少为3。然而Docker Compose对版本为2和3的 文件仍然可以处理;
  • docker stack把docker compose的所有工作都做完了,因此docker stack将占主导地位。同时,对于大多数用户来说,切换到使用docker stack既不困难,也不需要太多的开销。如果您是Docker新手,或正在选择用于新项目的技术,请使用docker stack。

5、Docker Stack常用命令

命令 描述
docker stack deploy 部署新的堆栈或更新现有堆栈
docker stack ls 列出现有堆栈
docker stack ps 列出堆栈中的任务
docker stack rm 删除一个或多个堆栈
docker stack services 列出堆栈中的服务

推荐实验: https://blog.csdn.net/huangjun0210/article/details/86502021

6.swarm网络细节

在 Swarm Service 中有三个重要的网络概念:

  • Overlay networks 管理 Swarm 中 Docker 守护进程间的通信。你可以将服务附加到一个或多个已存在的 overlay 网络上,使得服务与服务之间能够通信。
  • ingress network 是一个特殊的 overlay 网络,用于服务节点间的负载均衡。当任何 Swarm 节点在发布的端口上接收到请求时,它将该请求交给一个名为 IPVS 的模块。IPVS 跟踪参与该服务的所有IP地址,选择其中的一个,并通过 ingress 网络将请求路由到它。
    初始化或加入 Swarm 集群时会自动创建 ingress 网络,大多数情况下,用户不需要自定义配置,但是 docker 17.05 和更高版本允许你自定义。
  • docker_gwbridge是一种桥接网络,将 overlay 网络(包括 ingress 网络)连接到一个单独的 Docker 守护进程的物理网络。默认情况下,服务正在运行的每个容器都连接到本地 Docker 守护进程主机的 docker_gwbridge 网络。
    docker_gwbridge 网络在初始化或加入 Swarm 时自动创建。大多数情况下,用户不需要自定义配置,但是 Docker 允许自定义。
名称 类型 注释
docker_gwbridge bridge none
ingress overlay none
custom-network overlay none
  • docker_gwbridge和ingress是swarm自动创建的,当用户执行了docker swarm init/connect之后。
  • docker_gwbridge是bridge类型的负责本机container和主机直接的连接
  • ingress负责service在多个主机container之间的路由。
  • custom-network是用户自己创建的overlay网络,通常我们都需要创建自己的network并把service挂在上面。

ingress网络。vip(虚拟ip模式)

ingress-routing-mesh.png

https://docs.docker.com/engine/swarm/ingress/

查看网络的基本环境

yum install bridge-utils -y    ##以后就可以brctl show 查看网关
iptables -nL -t nat   ##查看转发规则

ln -s /var/run/docker/netns /var/run/netns ## 看容器创建的两个Net Namespace

ip netns ##查看Net Namespace
ip netns exec xxx ip add  ##查看mynet网络命名空间下的网卡情况。
ip netns exec xxx brctl show ##查看mynet网络空间下网桥挂载情况可以看出veth0挂到了br0网桥上。

Docker Compose安装ES集群实战:
https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docker.html
Docker swarm 中的LB分为两种情况:

  1. Ingress Load Balancing:暴露端口方式的负载均衡。产生虚拟ip。转发链。
  2. Internal Load Balancing:内部的负载均衡。通过service的名字也可以访问吗?

自定义Service

  • the port where the swarm makes the service available outside the swarm

    • docker service -p 8080:80
  • an overlay network for the service to connect to other services in the swarm

    • docker service --network ?;同一个网络的跨机通讯
  • CPU and memory limits and reservations

    • docker service create
--limit-cpu decimal                  Limit CPUs
--limit-memory bytes                 Limit Memory
  • a rolling update policy

    • docker service create --update-delay 10s --update-parllelism 2
  • the number of replicas of the image to run in the swarm

    • docker service create --replicas 3 --name nginx nginx:1.19
      Replicated and global services
      replicated-vs-global.png

      调整service以什么方式运行
--mode string                        
Service mode (replicated or global) (default "replicated")

docker service create --mode replicated --name mytom tomcat:7 默认的


docker service create --mode global --name haha alpine ping baidu.com
#场景?日志收集
每一个节点有自己的日志收集器,过滤。把所有日志最终再传给日志中心
服务监控,状态性能。
docker service create --name myredis --network myswarm-net redis
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
93: eth0@if94: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP 
    link/ether 02:42:0a:00:01:03 brd ff:ff:ff:ff:ff:ff
    inet 10.0.1.3/24 brd 10.0.1.255 scope global eth0
       valid_lft forever preferred_lft forever
95: eth1@if96: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.4/16 brd 172.18.255.255 scope global eth1;都是本机的
       valid_lft forever preferred_lft forever
 "Endpoint": {
            "Spec": {
                "Mode": "vip"
            },
            "VirtualIPs": [
                {
                    "NetworkID": "xm5bv9m1yb5q5kfwl5sgbed0j",
                    "Addr": "10.0.1.2/24"  #集群访问redis拿这个可以用
                }
            ]
        }
ping 容器的集群ip(10.0.1.3),service的集群vip(10.0.1.2),serviceName都通
3redis副本以后;容器ip  10.0.1.8、10.0.1.3、10.0.1.9。
无论多少个副本。一个service虚拟ip是固定的。负载均衡的方式。
建立redis集群。一个虚拟ip。sb应用和redis集群只要在同一个网络。直接写10.0.1.2,serviceName




docker service create --name mynettomcat --network myswarm-net tomcat
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
93: eth0@if94: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 02:42:0a:00:01:06 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.0.1.6/24 brd 10.0.1.255 scope global eth0
       valid_lft forever preferred_lft forever
95: eth1@if96: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet 172.18.0.4/16 brd 172.18.255.255 scope global eth1
       valid_lft forever preferred_lft forever
   docker service  inspect  mynettomcat
    "Endpoint": {
            "Spec": {
                "Mode": "vip"
            },
            "VirtualIPs": [
                {
                    "NetworkID": "xm5bv9m1yb5q5kfwl5sgbed0j",
                    "Addr": "10.0.1.5/24"
                }
            ]
        }

     10.0.1.0/24
#1、他们两个能互相访问吗?有哪些方式。ip?serviceName?

#2、vip,dnsrr
 docker service update --endpoint-mode dnsrr myredis
 # docker inspect service就没vip了想要负载均衡。只能serviceName;
 外部端口暴露负载均衡ingress。内部集群负载。vip。dnsrr

1、给docker集群部署服务的两种方式

docker service create xxxxx

2、使用compose文件

docker service create --name myredis --network myswarm-net redis
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
93: eth0@if94: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP 
    link/ether 02:42:0a:00:01:03 brd ff:ff:ff:ff:ff:ff
    inet 10.0.1.3/24 brd 10.0.1.255 scope global eth0
       valid_lft forever preferred_lft forever
95: eth1@if96: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.4/16 brd 172.18.255.255 scope global eth1;都是本机的
       valid_lft forever preferred_lft forever
 "Endpoint": {
            "Spec": {
                "Mode": "vip"
            },
            "VirtualIPs": [
                {
                    "NetworkID": "xm5bv9m1yb5q5kfwl5sgbed0j",
                    "Addr": "10.0.1.2/24"  #集群访问redis拿这个可以用
                }
            ]
        }
ping 容器的集群ip(10.0.1.3),service的集群vip(10.0.1.2),serviceName都通
3redis副本以后;容器ip  10.0.1.8、10.0.1.3、10.0.1.9。
无论多少个副本。一个service虚拟ip是固定的。负载均衡的方式。
建立redis集群。一个虚拟ip。sb应用和redis集群只要在同一个网络。直接写10.0.1.2,serviceName




docker service create --name mynettomcat --network myswarm-net tomcat
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
93: eth0@if94: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 02:42:0a:00:01:06 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.0.1.6/24 brd 10.0.1.255 scope global eth0
       valid_lft forever preferred_lft forever
95: eth1@if96: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet 172.18.0.4/16 brd 172.18.255.255 scope global eth1
       valid_lft forever preferred_lft forever
   docker service  inspect  mynettomcat
    "Endpoint": {
            "Spec": {
                "Mode": "vip"
            },
            "VirtualIPs": [
                {
                    "NetworkID": "xm5bv9m1yb5q5kfwl5sgbed0j",
                    "Addr": "10.0.1.5/24"
                }
            ]
        }

     10.0.1.0/24
#1、他们两个能互相访问吗?有哪些方式。ip?serviceName?

#2、vip,dnsrr
 docker service update --endpoint-mode dnsrr myredis
 # docker inspect service就没vip了想要负载均衡。只能serviceName;
 外部端口暴露负载均衡ingress。内部集群负载。vip。dnsrr
container-layers.jpg

sharing-layers.jpg

7.Docker Secret与Config

1、Secret

生产环境下,为了安全,我们不能把各项目的配置密码写入到配置文件
我们可以引入docker的secret方式保护密码。

场景

  • 用户名密码
  • SSH Key
  • TLS认证
  • 任何别人不想看到的数据

1、创建一个密码secret

2、用完就删

3、哪个服务想要使用只要暴露给他即可。

1、如何声明

#文本模式
printf 123456 | docker secret create my_secret -
echo "adminadmin" | docker secret create my-pwd -
#文件模式
docker secret create my_secret ./pwd
#Secret会基于raft在master主机之间同步。

2、如何使用
secret可以分配给一个service,就可以使用,secret在容器内部看起来是个文件,实际上在内存中

#1、普通情况
docker service create --name mysql --secret mysql_pwd -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_pwd -p 3306:3306 mysql:5.7

#2、compose文件
version: "3"
services:
  mysql:
    image: mysql:5.7
    ports:
      - 3306:3306
    secrets:
      - mysql_pwd
    enviroment:
      - MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_pwd

原理:我们使用--secret mysql_pwd以后,secret密码文件就会被解码保存到容器内部的/run/secrets/secretname中。这样我们就可以在容器中任意使用。但是外部无感知。

不管怎么使用,secret最好提前创建好。到时候声明使用就行。其实是在内存中的。整个集群manager是利用raft同步的。

2、config

1、如何声明

docker config create [OPTIONS] CONFIG file|-
docker config create redis.conf redis.conf
# 使用文件方式创建
docker service create --config redis.conf --name redis redis
#最终会被映射到容器的根目录。以后修改容器的启动命令即可


#配置中心
#部署100个redis副本。到100个服务器。他们配置文件都一样
#config内容base64编码,是可以inspect出来的。
secret文件是在容器中 /run/secrets/xxxx 暴露的
config默认是在根目录暴露的。

2、如何使用

#指定位置
docker service create --name redis \
  --config source=redis-conf,target=/etc/redis/redis.conf,mode=0400 redis:3.0.6
  
#2、compose文件
version: "3"
services:
  mysql:
    image: redis
    ports:
      - 6379:6379
    config:
      - mysql_pwd
    enviroment:
      - MYSQL_ROOT_PASSWORD_FILE: /

https://docs.docker.com/compose/compose-file/#configs
3、补充Label的使用
我们讨论了 Service 部署的两种模式:global mode 和 replicated mode。无论采用global mode 还是 replicated mode,副本运行在哪些节点都是由 Swarm 决定的,作为用户我们有没有可能精细控制 Service 的运行位置呢?
答:能,使用 label
逻辑分两步:
1、为每个 node 定义 label。
2、设置 service 运行在指定 label 的 node 上。

docker node update --label-add env=test 节点1
docker node update --label-add env=prod swarm-worker2
docker service create \
      --constraint node.labels.env==test \
      --replicas 3 \
      --name my_web \
      --publish 8080:80 \
      httpd
      
#更新 service,将其迁移到生产环境:
docker service update --constraint-rm node.labels.env==test my_web  
docker service update --constraint-add node.labels.env==prod my_web

8、扩展阅读

1、vxlan

https://blog.csdn.net/tony_vip/article/details/100097245

2、lvs

https://blog.csdn.net/tony_vip/article/details/104224374

image-20200506201415912.png

Service Mode:服务模式。 Endpoint Mode:端点模式

1、集群的所有操作必须在manager节点执行

2、机器数量决定的业务体量

3、service就是一个业务,真正是以容器的方式运行在docker机器。服务副本指容器数量

打日志的时候,加上机器名就行。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容