blog icon indicating copy to clipboard operation
blog copied to clipboard

使用 Docker 打造超溜的前端环境

Open axetroy opened this issue 6 years ago • 14 comments

前言:

没有什么是非得用 xx 技术才行的,只是 能用好用 的区别 有些习惯了老旧的工作方式,不愿去学习,不愿意去改变,追求 “能用就行” 有些不单追求能用,还有提升的空间,追去好用 It's up to you!

为什么需要 Docker?

在上一家公司,操作系统是自由的,你想用 Windows/Linux/Mac, 只要你用的习惯,能够高效率工作,无所谓什么系统。

然而问题就来了,大家的环境不一样。Unix 系系统基本无太大差别,Windows 就不一样了。

环境的差异会导致踩很多很多坑, 印象比较深刻的就是 sass 编译,在哪个还需要依赖 ruby 的年代,安装 gem 是不顺畅的,windows 格式编码格式不是 utf-8, 导致 sass 文件中有中文编译报错等等。

还有一些诸如文件监听,在 Windows 下需要 fallback,靠轮循监听变化。

在经过一系列的折腾之后,我们就尝试使用 Docker 来解决开发环境统一的问题。

构建一个通用的 Linux 镜像,里面包含了前端开发的基本环境,包括 python,nodejs 等等... 前端只要给定源码文件就行了。就这样暂时统一了开发环境,虽然最后我们统一使用 Unix 系统。

Docker 还有哪些场景

前后端不分离的项目

你作为一个前端,你是如何与 PHP 后端参与开发的?你需要搭建 PHP 环境吗?

我不搭建 PHP 开发环境,FTP 直接上传,就是这么简单粗暴,Docker 什么的,能吃吗 ∂?

如果你是这种开发模式,不是说 low,而是说效率低下。每次修改都要上传,盲人改代码。

所以你需要 Docker,配置好环境,一键启动,一键关闭,各开发人员相互独立,代码通过 git 同步,指向同一个数据库,修改代码立即见效,F*CK FTP

单页面应用

前后端分离的情况下,单页面渲染一般使用nginx, Docker 同样配置 nginx,做静态页渲染和去除难看的#号(通过 404 重定向只 index.html, 把 404 的控制权,交还给前端)

这样你的路由http://example.com/#/home变成了http://example.com/home

甚至你可以作反向代理,消除跨域

http://example.com/api > http://api.server.com

服务端渲染

那么你更需要 Docker, 因为你需要 Node 环境,你需要负载均衡,你需要反向代理等等...

Node 后端

既然是后端,数据库你要吧?nginx 你要吧,Node 你要安装吧?

A 同事项目依赖了 [email protected], 结果你项目依赖 [email protected]. 难道你叫他卸载了重新装一个?

如何应用 Docker

首先你得安装DockerDocker Compose, 使用 Docker Compose来配置镜像.

配置最简单的 Node 环境

一个简单的项目结构如下

├── docker-compose.yml
└── index.js
// index.js
const http = require('http');

const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'text/html');
  res.setHeader('X-Foo', 'bar');
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('ok');
});

server.listen(3000);
# docker-compose.yml

version: "3"
services:
  web:
    image: node:8-alpine
    user: node
    working_dir: /home/node/app
    environment:
      - PORT=3000
    volumes:
      - ./:/home/node/app # 将本地目录映射到容器内
    command: ["node", "index.js"] # 运行命令
    ports:
      - 3000:3000 # 本地端口:容器端口

运行命令启动

$ docker-compose up
Starting example_web_1 ... done
Attaching to example_web_1

现在 NodeJs 应用已经跑起来了,试着访问 http://localhost:3000

Node 的依赖包怎么办?

现在我们给项目添加依赖Koa,用 Koa 来搭建服务器.

这我们需要把node_modulespackage.json打包进镜像

而官方的 node 镜像node:8-alpine是不安装依赖的,需要我们自定义一个镜像

创建一个 Dockerfile

# Dockerfile
FROM node:8-alpine

# 设置工作目录
WORKDIR /home/node/app

# 把package.json复制进镜像中
COPY ./package.json /home/node/app/package.json

# 在镜像中安装依赖
RUN yarn --production

修改 docker-compose.yml

# docker-compose.yml

version: "3"
services:
  web:
    build:
      context: .
      dockerfile: ./Dockerfile
    user: node
    working_dir: /home/node/app
    environment:
      - PORT=3000
    volumes:
      - ./index.js:/home/node/app/index.js # 将本地目录映射到容器内
    command: ["node", "index.js"] # 运行命令
    ports:
      - 3000:3000 # 本地端口:容器端口

此时项目目录

├── Dockerfile
├── docker-compose.yml
├── index.js
├── package.json
└── yarn.lock

运行命令

$ docker-compose up
Building web
Step 1/5 : FROM node:8-alpine
 ---> 86b71ddd55fa
Step 2/5 : WORKDIR /home/node/app
 ---> Using cache
 ---> 2347fe6d8f3c
Step 3/5 : COPY ./package.json /home/node/app/package.json
 ---> Using cache
 ---> 1e0026f493f5
Step 4/5 : RUN yarn --production
 ---> Using cache
 ---> 549284f78c1c
Step 5/5 : RUN ls ./node_modules
 ---> Using cache
 ---> 350adf90cfa8
Successfully built 350adf90cfa8
Successfully tagged example_web:latest
Recreating example_web_1 ... done
Attaching to example_web_1

服务已运行起来了,访问 3000 试试看吧

如何用 Nginx 反向代理,消除跨域问题

首先得有一个 nginx.conf 配置

# nginx.conf
user nginx;
worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
	worker_connections 1024;
}


http {

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	log_format main '$remote_addr - $remote_user [$time_local] "$request" '
	'$status $body_bytes_sent "$http_referer" '
	'"$http_user_agent" "$http_x_forwarded_for"';

	access_log /var/log/nginx/access.log main;

	sendfile on;
	tcp_nopush on;

	gzip on;

	# 定义上游服务器
	upstream api {

		ip_hash;

                # 这里服务器使用 koaserver
                # 其实原理是Docker改写了host, koaserver指向nodejs那个容器的IP
		server koaserver:3000 weight=1;

		keepalive 300;
	}

	server {

		listen 80;
		server_name localhost;

		charset utf-8;

		root   /usr/share/nginx/html;

		# 代理以 api 为前缀的请求
		location ~^/api {
			proxy_pass http://api;
			proxy_set_header X-Forwarded-Proto $scheme;
			proxy_set_header Host $http_host;
			proxy_set_header X-Real-IP $remote_addr;
		}

		error_page 500 502 503 504 /50x.html;

		location = /50x.html {
			root /usr/share/nginx/html;
		}
	}

}
version: "3"
services:
  nginx_proxy:
    image: nginx:1.13.8-alpine
    restart: always
    working_dir: /home/static
    volumes:
       - ./index.html:/usr/share/nginx/html/index.html
       - ./nginx.conf:/etc/nginx/nginx.conf # 映射 ginx 配置文件
    ports:
      - 3000:80 # 绑定容器的80端口到本的1080端口
    links:
      - web:koaserver # 给它取个别名,叫做 koaserver
  web:
    build:
      context: .
      dockerfile: ./Dockerfile
    user: node
    working_dir: /home/node/app
    environment:
      - PORT=3000
    volumes:
      - ./index.js:/home/node/app/index.js # 将本地目录映射到容器内
    command: ["node", "index.js"] # 运行命令
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  hello html
</body>
</html>

此时项目目录

├── Dockerfile
├── docker-compose.yml
├── index.html
├── index.js
├── nginx.conf
├── package.json
└── yarn.lock

运行命令

$ docker-compose up
Creating network "example_default" with the default driver
Pulling nginx_proxy (nginx:1.13.8-alpine)...
1.13.8-alpine: Pulling from library/nginx
550fe1bea624: Already exists
d421ba34525b: Already exists
fdcbcb327323: Already exists
bfbcec2fc4d5: Already exists
Digest: sha256:c8ff0187cc75e1f5002c7ca9841cb191d33c4080f38140b9d6f07902ababbe66
Status: Downloaded newer image for nginx:1.13.8-alpine
Creating example_web_1 ... done
Creating example_nginx_proxy_1 ... done
Attaching to example_web_1, example_nginx_proxy_1

访问 localhost:3000, 返回 hello html 访问 localhost:3000/api, 返回 hello koa

解决了跨域,但同时也存在问题,代理之后,应用程序无法获取 IP,需要从 header 的X-Real-IP 字段获取

最佳部署姿势是怎么样的?

其他还有很多案例需要使用 Docker,特别是开发后端的应用。后端应用依赖 数据库(MySQL, Postgres, Redis...)、服务器(Nginx/Apache),开发语言(如 PHP)。

如果我是一名开发者,接手别人的项目,项目需要安装 PHP, 需要安装 MySQL, Apache, redis, mongo... 最好能提供一个docker-compose.yml,否则我不保证我打不死你

回到 Node 部署,最佳的姿势:

把编译过后的代码(例如 Typescript>JS)+依赖+配置打包进镜像

然后 push 镜像,最后在服务端部署。其余的配置,尽量都通过docker-compose.yml的环境变量去设置

做到只需要一个配置文件,就能把完整的项目搭建起来

总结

Docker 到底解决了我们哪些问题?

  • [x] 容器化作隔离.。无论你怎么作,都是和宿主环境隔离的
  • [x] 统一环境。不会再出现 "为什么我的电脑上就可以,在你的电脑上就不行" 的玄学 BUG
  • [x] 易部署。一键安装是种怎样的体验?
  • [x] 集群分布式解决方案。前端页面仔需要知道这个?

axetroy avatar Jun 21 '18 15:06 axetroy

强行用 docker ...

jianglin-wu avatar Jun 26 '18 02:06 jianglin-wu

@jianglin-wu

何来强行一说,简直不要太好用。

对于我来说,维护几个 NodeJS 项目,分别依赖 MySQL,Postgres,Redis,Mongo,Nginx...

用 Docker 很好的解决了很多问题

axetroy avatar Jun 26 '18 02:06 axetroy

路过...我有一个疑问...比如说..我司有一台服务器,上面要部署很多很多很多的服务....这些服务有各自的依赖...这个时候假如我们用Docker来部署... 一个服务就是一个Docker镜像...那资源占用岂不是爆炸..毕竟有很多依赖可能是共通的..? 这种情况下,有什么方便部署的好方法么? 打包成Tar这一类的东西?用的时候安装...?

Zephylaci avatar Jun 28 '18 05:06 Zephylaci

@Tarhyru

确实如你所说,有很多很多服务的情况下,很有可能是有重复的服务,例如数据库,多个服务都用MySQL。如果各自都搭建自己的数据库,那么很浪费性能。

这时候共通的依赖应该抽离出来作为公共服务,单独一个 docker-compose.yml

axetroy avatar Jun 28 '18 05:06 axetroy

@axetroy 我是否可以这么理解,我可以先建一个没有任何服务的空镜像专门保存各种公用服务....比如数据库.. 后续的镜像..引用第一个镜像提供的服务...这样数据库的确可以通过让后续镜像连接指定的ip来避免重复安装.. 那么..运行环境怎么处理?比如JAVA JDK这种的..也能通过配置一个专门存环境的镜像,让后续的镜像不用安装么?如果不能,有什么好的解决方案么?

Zephylaci avatar Jun 28 '18 05:06 Zephylaci

正好要找 Docker 相关的资料, 先 star 一波.

ifyour avatar Jun 28 '18 05:06 ifyour

@Tarhyru

比如你服务器,要部署5个java服务,你是想不安装5次 JAVA JDK,是这个意思吗?

打包成镜像就好了呀,在JAVA+JDK镜像包的基础上,再把5个服务的代码,依赖,分别打包5个镜像。

再服务端部署5个就行了。JAVA+JDK这个基础镜像被多次引用,但是不会重复下载的。

axetroy avatar Jun 28 '18 05:06 axetroy

@axetroy 大致明白了..也就是说,可以让后续的镜像来引用只装环境的那个镜像来避免重复安装运行环境的问题... 如果环境有扩展..可以先组新的环境镜像... emmmm,晚些时候研究研究,总之谢解答

Zephylaci avatar Jun 28 '18 08:06 Zephylaci

虽说我们项目也是前后端不分离(php),但也没你说的那么麻烦啊,sftp idea 自动上传,后端接口 nginx 转发到开发机,静态资源 nginx 转发 webpack,整个开发体验跟前后端分离没什么两样。

huangw1 avatar Jul 02 '18 15:07 huangw1

@huangw1 你这个前端单独调试体验不会好吧...如果你们前端是php顺手做了,自然两说... 而docker是为了解决重复多次大量部署有一万个依赖的项目.... 你这个模式很明显不是为了解决这个场景的...

Zephylaci avatar Jul 04 '18 03:07 Zephylaci

哇塞…… 刚才发现这个博客,真的是厉害…… 完全基于 GH API

chuang02 avatar Jul 20 '18 15:07 chuang02

如果我没猜错,楼主应该是咪付的前端...

iamplex avatar Aug 09 '18 13:08 iamplex

6

achuanya avatar Oct 16 '18 03:10 achuanya

问个问题,统一前端开发环境,如何做到 node_modules 不重复下载和新增依赖后 多人同步? 比如,A 和 B 两个开发都在本机下载了 前端环境(包含 nodejs、python、@vue/cli等)镜像 FrontImage1,后面 A 需要引入一个 新的 npm 包, 这个怎么同步到 B 开发人员那边的镜像去?然后还有就是如果新的镜像有问题,怎么回滚

Mini-Web avatar Apr 18 '21 15:04 Mini-Web