前端服务化-docker

23,891 阅读12分钟

前端的传统部署方式,将静态资源打包放在nginx下面,由nginx返回文件。

但,面对日益复杂的前端应用,我们可能将面对:

  • 线上的前端资源如何做的快速回滚?
  • 传统的方式,如何更好的做到灰度发布?
  • 前端的资源,如何更好的接入 持续集成,以及持续部署?
  • 前端的服务,如何做到自动伸缩?
  • ssr场景,如何更好做到服务发现?
  • 如何快速的迁移不同的机器和环境?
  • ...

一. 什么是Docker

百度百科:

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器或Windows 机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。

简单理解:

Docker是一个使用Go语言开发的虚拟容器。容器内可以跑操作系统和应用程序,容器间彼此隔离。

二. Docker的组成

image.png

Container:  容器是镜像创建的实例,用来运行镜像。

Images: 镜像,是一套环境 + 程序的整合,只读。可以理解成一个模板。

Repository:仓库,存放制作好的容器镜像。可以简单理解成gitLab代码仓库存储。

Daemon: 守护进程

三. Docker的安装

docker支持跨平台,可以运行在windows, linux, mac上

其中安装也很简单:www.docker.com/products/do…

安装完成后,输入命令:

docker version

结果如下:

image.png

四. 镜像

镜像定义

镜像是一种文件系统,除了包含镜像运行时所需要的资源,库,配置文件,还包含了环境变量,卷。

镜像的存储

镜像由多个层组成,每层叠加之后,从外部看来就如一个独立的对象。镜像内部是一个精简的操作系统,同时还包含应用运行所必须的文件和依赖包。

镜像构建时,会一层层构建,每层的变动只会影响本层的变动。 得益于分层的存储,镜像的复用和定制变得容易。

查看本地镜像

docker images

image.png

搜索远程镜像

docker search nginx

image.png

制作一个镜像

制作镜像,需要创建一个dockerfile文件,docker会执行里面所有的命令。制作的demo将在后面的container说明中体现。

删除镜像

imageId 为镜像的id

docker rmi <imageId>

发布镜像

镜像是资源的总和包。镜像是可以发布到云端仓库,支持镜像拉取,发布。

docker push 镜像源地址

五. 容器

容器的本质就是进程。他是镜像的运行时,而镜像本身的构建时。但docker容器和宿主机进程不同,容器进程有自己的独立的namespace。需要注意的是,容器被删除后,容器内的数据将被全部删除。

所以,需要持久化存储的数据,需要使用容器挂载技术。

启动一个容器

docker run -it -p 80:80 --name nginx nginx

打开浏览器,访问: http://localhost 即可看到 welecome to nginx, 启动一个nginx服务如此简单~

常用参数说明:

参数说明
-i以交互模式创建容器,一般和-t一起使用
-t给容器分配一个伪终端
-d以后台运行的方式创建容器
-p端口号映射规则,宿主机端口:容器端口
--name容器分配的名字,可自定义
-e设置环境变量
-v容器挂载,宿主机路径:容器内路径
--link链接到另外一个容器,多容器依赖时可以使用

查看容器运行状态

docker ps -a

image.png

停止容器

docker stop <containerId>

删除容器

docker rm <containerId>

查看容器信息

docker inspect <containerId>

结果信息如下:

[

    {

        "Id": "63a6595fefea17d0d36a5dcd4e64404e8b5c65b5fff0d24361c47b145f4e735c",

        "Created": "2019-06-22T05:23:29.5080991Z",

        "Path": "docker-entrypoint.sh",

        "Args": [

            "mysqld"

        ],

        "State": {

            "Status": "running",

            "Running": true,

            "Paused": false,

            "Restarting": false,

            "OOMKilled": false,

            "Dead": false,

            "Pid": 2531,

            "ExitCode": 0,

            "Error": "",

            "StartedAt": "2021-11-03T01:31:07.493829056Z",

            "FinishedAt": "2021-11-03T00:31:07.548021281Z"

        },

        "Image": "sha256:3ed1080b793fc4a10cab741a04ce090caf1ad2932cbcc679b6587624af9f6157",

        "ResolvConfPath": "/var/lib/docker/containers/63a6595fefea17d0d36a5dcd4e64404e8b5c65b5fff0d24361c47b145f4e735c/resolv.conf",

        "HostnamePath": "/var/lib/docker/containers/63a6595fefea17d0d36a5dcd4e64404e8b5c65b5fff0d24361c47b145f4e735c/hostname",

        "HostsPath": "/var/lib/docker/containers/63a6595fefea17d0d36a5dcd4e64404e8b5c65b5fff0d24361c47b145f4e735c/hosts",

        "LogPath": "/var/lib/docker/containers/63a6595fefea17d0d36a5dcd4e64404e8b5c65b5fff0d24361c47b145f4e735c/63a6595fefea17d0d36a5dcd4e64404e8b5c65b5fff0d24361c47b145f4e735c-json.log",

        "Name": "/mysql",

        "RestartCount": 0,

        "Driver": "overlay2",

        "Platform": "linux",

        "MountLabel": "",

        "ProcessLabel": "",

        "AppArmorProfile": "",

        "ExecIDs": null,

        "HostConfig": {

            "Binds": null,

            "ContainerIDFile": "",

            "LogConfig": {

                "Type": "json-file",

                "Config": {}

            },

            "NetworkMode": "default",

            "PortBindings": {

                "3306/tcp": [

                    {

                        "HostIp": "",

                        "HostPort": "3306"

                    }

                ]

            },

            "RestartPolicy": {

                "Name": "no",

                "MaximumRetryCount": 0

            },

            "AutoRemove": false,

            "VolumeDriver": "",

            "VolumesFrom": null,

            "CapAdd": null,

            "CapDrop": null,

            "Dns": [],

            "DnsOptions": [],

            "DnsSearch": [],

            "ExtraHosts": null,

            "GroupAdd": null,

            "IpcMode": "shareable",

            "Cgroup": "",

            "Links": null,

            "OomScoreAdj": 0,

            "PidMode": "",

            "Privileged": false,

            "PublishAllPorts": false,

            "ReadonlyRootfs": false,

            "SecurityOpt": null,

            "UTSMode": "",

            "UsernsMode": "",

            "ShmSize": 67108864,

            "Runtime": "runc",

            "ConsoleSize": [

                0,

                0

            ],

            "Isolation": "",

            "CpuShares": 0,

            "Memory": 0,

            "NanoCpus": 0,

            "CgroupParent": "",

            "BlkioWeight": 0,

            "BlkioWeightDevice": [],

            "BlkioDeviceReadBps": null,

            "BlkioDeviceWriteBps": null,

            "BlkioDeviceReadIOps": null,

            "BlkioDeviceWriteIOps": null,

            "CpuPeriod": 0,

            "CpuQuota": 0,

            "CpuRealtimePeriod": 0,

            "CpuRealtimeRuntime": 0,

            "CpusetCpus": "",

            "CpusetMems": "",

            "Devices": [],

            "DeviceCgroupRules": null,

            "DiskQuota": 0,

            "KernelMemory": 0,

            "MemoryReservation": 0,

            "MemorySwap": 0,

            "MemorySwappiness": null,

            "OomKillDisable": false,

            "PidsLimit": 0,

            "Ulimits": null,

            "CpuCount": 0,

            "CpuPercent": 0,

            "IOMaximumIOps": 0,

            "IOMaximumBandwidth": 0,

            "MaskedPaths": [

                "/proc/asound",

                "/proc/acpi",

                "/proc/kcore",

                "/proc/keys",

                "/proc/latency_stats",

                "/proc/timer_list",

                "/proc/timer_stats",

                "/proc/sched_debug",

                "/proc/scsi",

                "/sys/firmware"

            ],

            "ReadonlyPaths": [

                "/proc/bus",

                "/proc/fs",

                "/proc/irq",

                "/proc/sys",

                "/proc/sysrq-trigger"

            ]

        },

        "GraphDriver": {

            "Data": {

                "LowerDir": "/var/lib/docker/overlay2/6dc3b5a4ce7b7eee3b2e7eed6fb8e532e92006b9c8740b29a01142fb221faf1e-init/diff:/var/lib/docker/overlay2/b1cbde66699dd957051aa772e88e50fdd6bad4d326b6fe4b54c4e536a2166dc9/diff:/var/lib/docker/overlay2/5f57f69dda5f8dd9d45f44c73af919ed084fb364c9f0da97c398f65cc6003490/diff:/var/lib/docker/overlay2/d721128f780e2c5a6d9a011499e38caf7f9f5b52e036bdd781034620c259a47b/diff:/var/lib/docker/overlay2/0b456e8086417170fe7ee5ae15b5581478a9915bf607c21505f3b48e6595f497/diff:/var/lib/docker/overlay2/0b944ce3e4123c5ab0defee5bde75f471330badf470a559dd642a35d2409eb57/diff:/var/lib/docker/overlay2/0e8402a11ea8a07a644c390dd385b5be69f0eb9ac3aea42aab79b690d43c8524/diff:/var/lib/docker/overlay2/f818b7965f49cfdf02cfebb7ef5d96411f9272ee7f7d00700765cf2d73b23932/diff:/var/lib/docker/overlay2/70e9d6b0236f59a903ce2557a1680e9edd2602addedb28a3e3a0129cb316e7ea/diff:/var/lib/docker/overlay2/b2829f4dababfa86d3ca9444b109dcba26a00b7dcdc426b7d72f7125836fbd42/diff:/var/lib/docker/overlay2/f0d772d9688123b101145eb0def37d748daf93f60af4ede762c86944a3fa15ae/diff:/var/lib/docker/overlay2/935b029dc3d0f698155eb500f6f9bbf72968b25edd4bd4d7d0a64dac01ea2b33/diff",

                "MergedDir": "/var/lib/docker/overlay2/6dc3b5a4ce7b7eee3b2e7eed6fb8e532e92006b9c8740b29a01142fb221faf1e/merged",

                "UpperDir": "/var/lib/docker/overlay2/6dc3b5a4ce7b7eee3b2e7eed6fb8e532e92006b9c8740b29a01142fb221faf1e/diff",

                "WorkDir": "/var/lib/docker/overlay2/6dc3b5a4ce7b7eee3b2e7eed6fb8e532e92006b9c8740b29a01142fb221faf1e/work"

            },

            "Name": "overlay2"

        },

        "Mounts": [

            {

                "Type": "volume",

                "Name": "cfa1769ef30034349aab12855852c4670558902fcbc6ab695df9a342974b43b4",

                "Source": "/var/lib/docker/volumes/cfa1769ef30034349aab12855852c4670558902fcbc6ab695df9a342974b43b4/_data",

                "Destination": "/var/lib/mysql",

                "Driver": "local",

                "Mode": "",

                "RW": true,

                "Propagation": ""

            }

        ],

        "Config": {

            "Hostname": "63a6595fefea",

            "Domainname": "",

            "User": "",

            "AttachStdin": false,

            "AttachStdout": false,

            "AttachStderr": false,

            "ExposedPorts": {

                "3306/tcp": {}

            },

            "Tty": true,

            "OpenStdin": true,

            "StdinOnce": false,

            "Env": [

                "MYSQL_ROOT_PASSWORD=tianyuantupo",

                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",

                "GOSU_VERSION=1.7",

                "MYSQL_MAJOR=5.6",

                "MYSQL_VERSION=5.6.44-1debian9"

            ],

            "Cmd": [

                "mysqld"

            ],

            "ArgsEscaped": true,

            "Image": "mysql:5.6",

            "Volumes": {

                "/var/lib/mysql": {}

            },

            "WorkingDir": "",

            "Entrypoint": [

                "docker-entrypoint.sh"

            ],

            "OnBuild": null,

            "Labels": {}

        },

        "NetworkSettings": {

            "Bridge": "",

            "SandboxID": "eedebae73fe31fa493a42ddc39f114fb17e20a4969eec6963c8825e5d188c0a5",

            "HairpinMode": false,

            "LinkLocalIPv6Address": "",

            "LinkLocalIPv6PrefixLen": 0,

            "Ports": {

                "3306/tcp": [

                    {

                        "HostIp": "0.0.0.0",

                        "HostPort": "3306"

                    }

                ]

            },

            "SandboxKey": "/var/run/docker/netns/eedebae73fe3",

            "SecondaryIPAddresses": null,

            "SecondaryIPv6Addresses": null,

            "EndpointID": "915c0601992380b4bfaa8c8d8091de1bf891367fb95c8a92a113c5f0ab7e67be",

            "Gateway": "172.17.0.1",

            "GlobalIPv6Address": "",

            "GlobalIPv6PrefixLen": 0,

            "IPAddress": "172.17.0.2",

            "IPPrefixLen": 16,

            "IPv6Gateway": "",

            "MacAddress": "02:42:ac:11:00:02",

            "Networks": {

                "bridge": {

                    "IPAMConfig": null,

                    "Links": null,

                    "Aliases": null,

                    "NetworkID": "d829afa6278f314b184889d585acf7a6b86bd26df8f71145489517fb10623eef",

                    "EndpointID": "915c0601992380b4bfaa8c8d8091de1bf891367fb95c8a92a113c5f0ab7e67be",

                    "Gateway": "172.17.0.1",

                    "IPAddress": "172.17.0.2",

                    "IPPrefixLen": 16,

                    "IPv6Gateway": "",

                    "GlobalIPv6Address": "",

                    "GlobalIPv6PrefixLen": 0,

                    "MacAddress": "02:42:ac:11:00:02",

                    "DriverOpts": null

                }

            }

        }

    }

]

进入容器内部

docker exec -it <containerId> bash

进行容器内部后,将发现,这是一个新的世界,里面跑着一个linux操作系统,和宿主机一样~~

六. docker服务通信方式

通讯模式

模式是否异步场景备注
一对一同步B服务请求A服务鉴权等服务需求立刻响应
一对多同步--
一对一异步支付后信息同步服务不需要立刻响应
一对多异步发布订阅,如:饿了么点餐用户下单,把事件推送给骑手们

通讯协议

协议备注
http建议使用restful api,docker内服务通过容器ip,桥接模式,实现跨docker服务调用。
MQ使用消息队列,实现服务异步通信。服务间解耦。如:RabiitMQ, kafka等。
RPC远程过程调用。rpc分为2种,一种基于http实现,一种基于tcp实现。如:google的grpc。 在go中可以使用go-micro
Socket不建议使用

微服务通信:同步建议使用RPC(其次restful),异步建议使用MQ(Kafka, RabbitMQ等)。

注:一个完整的RPC框架,需要包括:服务发现,负载,容错,网络传输,序列化等。

七. 服务发现

服务越来越多时,每次通过inspect获取桥接ip地址会很麻烦,服务迁移ip地址都会发生变化。这时需要使用服务发现机制。

服务发现发2种,一直是客户端发现,另一种是服务端发现。

客户端发现

image.png

服务端发现

image.png

eureka

eureka 是 netflix 开发的服务发现组件,本身是一个基于 rest 的服务,我们可以通过 http 实现我们想要的功能。

eureka支持的注册列表: github.com/Netflix/eur…

八. docker compose

定义

docker compose 实现对容器的快速编排。

docker compose yaml字段说明

字段名说明
version指定 docker-compose.yml 文件的写法格式
services多个容器集合
image容器镜像
container_name容器名称
environment环境变量
mem_limit内存限制
cap_add指定容器内核能力
volumes挂载
ports对外暴露端口
networks网络,bridge 桥接模式

docker compose基本操作

  • docker-compose stop 服务名   (停止服务)

  • docker-compose rm 服务名      (删除服务)

  • docker-compose start 服务名    (启动服务)

  • docker-compose restart 服务名  (重启服务)

  • docker-compose up                  (服务更新)

  • docker-compose logs             (查看容器日志)

示例

使用docker compose实现一个ruby服务,连接elasticsearch:

image.png

九. Kubernetes

Kubernetes简称k8s, 由google使用Go开发。k8s旨在解决在分布式系统中,部署,调度,伸缩问题。

k8s也是一个容器管理工具,不过他比docker compose更强大。

docker compose提供是单台主机上多个容器管理。

而k8s是可以跨主机多个容器管理。不仅如此,他还可以启动,监控容器。如果有容器服务不正常,k8s会检测到并启动一个新的服务,以及负载均衡策略等。

内部大概构造

image.png

构造说明

字段解释备注
master主节点主要负责整个系统对外接口,和内部node集群调度,以及管理容器
node子节点
docker容器环境,创建容器使用
kubelet主要负责对pod,创建,修改,删除和监控
kube-proxy代理,为pod提供对象代理pod的ip地址经常会变,监听对象变化,通过管理iptables实现网络通信转发。
fluentd日志收集,查询,存储

常用命令

  • 获取所有的pods
kubectl get pods -n <namespaces>
  • 查看具体容器描述
kubectl describe 类型/具体名 -n namespace
  • 通过k8s进入容器节点
kubectl exec -it 容器id -n odcp bash
  • k8s编辑Ingress
kubectl edit vs -n odcp ingress名称
  • k8s查看容器日志
kubectl logs 容器id
  • 查看node使用情况
kubectl top node

更多见:www.kubernetes.org.cn/k8s

Ingress

k8s的ingress可以理解成入口网关,使用过nginx的同学,可以理解成nginx的配置文件。

通常一个ingress大概结构如下:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: 'false'
  name: frontend-ingress
  namespace: odcp
spec:
  rules:
  - host: frontend.internal
    http:
      paths:
      - backend:
          serviceName: 服务名称1
          servicePort: 80
        path: /具体路径/index.html

      - backend:
          serviceName: 服务名称2
          servicePort: 80
        path: /具体路径/index.html

      ...

开发同学,更多的需要关注 path以及 backend。这里对应着 nginx中的location 以及proxy_pass。

码字不易,请多多关注点赞~