前端的传统部署方式,将静态资源打包放在nginx下面,由nginx返回文件。
但,面对日益复杂的前端应用,我们可能将面对:
- 线上的前端资源如何做的快速回滚?
- 传统的方式,如何更好的做到灰度发布?
- 前端的资源,如何更好的接入 持续集成,以及持续部署?
- 前端的服务,如何做到自动伸缩?
- ssr场景,如何更好做到服务发现?
- 如何快速的迁移不同的机器和环境?
- ...
一. 什么是Docker
百度百科:
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器或Windows 机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。
简单理解:
Docker是一个使用Go语言开发的虚拟容器。容器内可以跑操作系统和应用程序,容器间彼此隔离。
二. Docker的组成
Container: 容器是镜像创建的实例,用来运行镜像。
Images: 镜像,是一套环境 + 程序的整合,只读。可以理解成一个模板。
Repository:仓库,存放制作好的容器镜像。可以简单理解成gitLab代码仓库存储。
Daemon: 守护进程
三. Docker的安装
docker支持跨平台,可以运行在windows, linux, mac上
其中安装也很简单:www.docker.com/products/do…
安装完成后,输入命令:
docker version
结果如下:
四. 镜像
镜像定义
镜像是一种文件系统,除了包含镜像运行时所需要的资源,库,配置文件,还包含了环境变量,卷。
镜像的存储
镜像由多个层组成,每层叠加之后,从外部看来就如一个独立的对象。镜像内部是一个精简的操作系统,同时还包含应用运行所必须的文件和依赖包。
镜像构建时,会一层层构建,每层的变动只会影响本层的变动。 得益于分层的存储,镜像的复用和定制变得容易。
查看本地镜像
docker images
搜索远程镜像
docker search nginx
制作一个镜像
制作镜像,需要创建一个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
停止容器
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种,一直是客户端发现,另一种是服务端发现。
客户端发现
服务端发现
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:
九. Kubernetes
Kubernetes简称k8s, 由google使用Go开发。k8s旨在解决在分布式系统中,部署,调度,伸缩问题。
k8s也是一个容器管理工具,不过他比docker compose更强大。
docker compose提供是单台主机上多个容器管理。
而k8s是可以跨主机多个容器管理。不仅如此,他还可以启动,监控容器。如果有容器服务不正常,k8s会检测到并启动一个新的服务,以及负载均衡策略等。
内部大概构造
构造说明
| 字段 | 解释 | 备注 |
|---|---|---|
| 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
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。
码字不易,请多多关注点赞~