戳蓝字“CSDN云计算”关注我们哦!
作者 | 王洪鹏
出品 | CSDN云计算(ID:CSDNcloud)
作为一枚程序员,大家几乎基本都有自己的个人网站,这些网站有的可能是自己开发的有的可能是用一些工具自动生成的,不管我们是用什么样的方式搭建的网站,如果想对自己的网站进行容器化,可以参考下当前这篇文章。
前两天刚将自己的网站做了下容器化,所以本文也算是笔者自己的一个个人笔记。接下来的内容里我会给大家讲下网站容器化的过程中需要用到的大部分的技术栈,所涉及的每一部分都会详细写明具体的实施过程,绝不会出现类似“详细过程可参考: https://xxx.xxx”这种动辄甩链接的情况。
容器化之前的痛点
以我自己为例,我自己目前维护的网站主要有我自己的个人简历网站、个人博客网站、个人常用工具集网站以及自己的开源项目网站。这些网站不是同时搭建起来的,有的是上学的时候搭建的,有的是工作之后搭建的,由于历史和网站用途的原因,这些网站在搭建时所采用的方式和技术都不太一样,差别挺大的。比如我的个人简历网站是直接使用vue.js构建的SPA单页应用,纯静态的网站,相对比较简单;我的博客网站和个人工具集网站使用的是当时比较流行的WordPress工具进行配置和搭建的,架构比较简单:PHP + Apache Web Server + MySQL数据库;我的个人开源项目网站采用的是Node.js + Express SSR进行搭建的。
从周围的同事来看,像笔者一样因为个人需要维护着几个自己网站的并不在少数。 每次修改完网站的代码接下来都需要进行一系列繁琐的操作,在此我们以个人简历网站这种算是最简单的纯静态的个人网站为例,看下修改一次网站我们需要进行的操作事项:
1. 下载网站当前的源代码
对网站进行修改,首先我们需要先将网站当前的代码clone下来,不论是从公共的Github、Gitlab还是自己搭建的代码托管平台。代码下载完成后本地部署下我们的网站程序,方便接下来的代码修改、测试。
2. 开发新的功能
这一步就是对已经下载下来的代码进行功能的修改,修改过程中需要对功能进行不断的测试,修改完成后一般还要对新增的功能进行一个大概的整体的测试,以确保新加的功能是正常的,比较有心的同学一般还会对先前已经有的功能在大概测试下,确保加入的新功能没有影响到已有的功能。
3. 代码编译
这一步的目的也很简单,就是对前端的项目进行编译,得到我们需要的静态资源文件。
4. 网站新代码提交
本地开发、测试好后,在部署新的代码前我们需要将最新的代码push到我们的代码托管平台,方便后续的代码维护。
当然如果代码的修改量较大的话我们一般还会在代码的书写过程中对代码进行提交,而不是等到代码全部更新完成后再进行最终的代码提交。
5. 代码更新
接下来我们需要将我们的代码文件打包下,然后借助一些工具将我们首先打包好的包含新加入功能的包传到我们的网站服务器。
考虑到网站的可用性,在我们上传新的代码包到网站的服务器后,我们一般不会直接对网站的文件进行替换,更常见的做法是先将已经在跑的代码备份下,然后再用新的代码进行替换,这样当新的代码出现问题时我们可以及时的进行代码的回滚,将代码再替换回原先的老代码。
6. 网站测试
上一步我们完成了新代码的部署操作,虽然在本地我们已经做过了对代码的测试,保险起见,我们还要对网站的功能进行一个大概的功能层面的测试,来确保新功能的添加没有引入太明显的bug。
以上过程放在工作中可能并不复杂,但是工作中一个人一般不会去负责这么多的事情:开发、测试、部署、验证。一般只是做这里面的某一块,各个模块的工作做的也相对比较细致,再加上工作中的项目迭代频率一般不会很高,所以还是可以接受的。但是如果是放在自己维护的个人网站的场景,里就会变的很麻烦,所有的操作都需要我们自己一个人来进行。
网站更新完成后,新增的内容没有问题可能还好,一旦出现问题前面的这些步骤就得再来一遍,且这类的情况几乎是不可避免的,比如更新的内容中有错别字。每次更新基本都等花掉我们少则半个小时,多则数小时的时间。
再加上由于用途多样,我们一般还会维护多个网站,这样的花在网站发布的时间又得加倍,由于改动一次流程比较多,所以每次不管我们进行多小的改动都很麻烦,几个错别字下来都能让我们感觉身体被掏空,花在这上面的精力不亚于维护一个大的网站,徒劳、无聊且得不偿失。
以上仅仅是针对诸如个人简历这种最简单的静态网站进行的说明,个人网站从功能层面上来讲可能并不复杂,但其更新过程中需要做的操作还是蛮费心力的。简单静态网站的维护尚且如此,一个常规的个人博客网站呢?
针对个人博客网站这种情况,一般需要有个web server、数据库,以最常见的lnmp这种架构为例,除了前面静态网站的更新中我们已经描述过的步骤之外,我们可能还要对mysql数据库的结构进行变更,甚至还要mysql的配置进行修改,对数据库服务进行维护。这些更新都完成之后,过几天你可能会发现网站出现问题,访问不了了,上去看下可能是mysql异常了或者web server 本身挂了。这种情况下,如果对linux、mysql、web server比较了解的话还好,如果不了解的话那接下来就需要一次次焦头烂额的排查。
对于大部分程序员来说,最好是只专注于代码,但现在面临的问题是,我们既要维护代码还是维护代码的运行环境。代码写的没问题,环境却老是出问题,这对于不太了解运维的同学来说的确是一件很难受的事情。更加难受的是当前使用的服务器,随着时间的推移,功能已经变的越来越复杂,这个趋势是必然的,作为小小程序员的我们根本无力阻拦。今天MySQL数据库挂了,明天web server 不能正常运行了,接下来呢?真的是个无底洞。对于很多程序员来说,既要维护代码又要负责网站的日常运维工作,简直不要太难受!
Docker 基础概念
我们总结一下容器化前我们面对的问题:
1. 手动部署成本代价大,成本高,上文中我们提到过仅仅是一个错别字的问题,我们就得将整个一系列的操作从头再操作一遍,很麻烦。
2. 随着时间的拉长,由于发布次数比较多,因此服务器上的环境会变的越来越脏、乱、差:这个路径放了这个组件的日志、那个路径备份了数据库的数据等等,每次一想到升级时需要面对这些蜘蛛网一般的文件分布,能硬生生的将自己的开发冲动给憋回去。
3. 如果服务器操作系统版本太低,或者因为其他的原因需要更换其他的操作,这个时候简直就是一个噩梦。新的服务器上,我们需要将我们的web server、mysql、网站本身,等逐一迁移过去,如果老服务器上的文件分布比较合理那还好,如果分布的比较杂乱的话,这个时候你会发现将将前面的这些文件迁移到新的服务器上后网站并不能正常运行起来,或者运行起来后网站不能正常的进行访问,这个时候可能是因为你从老的服务器上拷贝错了配置文件,或者少迁移了一些文件等,此时我们就需要逐步的进行尝试,如果不想进行尝试,直接将老的服务器上的所有文件都拷贝过去,网站可能会正常的运行起来,但是之前老服务器上的混乱状态也会被你带到新的服务器中。
再者即使我们将老服务器上的文件进行整体的拷贝,我们可能会发现网站还是没办法正常的运行起来,这个情况的原因很多,比如使用的mysql或者web server或者语言的解释器本身和当前的操作系统版本不兼容,巴拉、巴拉……总之就是一大堆的环境问题。
好在我们有容器,容器基本可以说是我们解决上面一系列环境问题的灵丹妙药,即使不能轻松的解决所有的问题,也可以解决掉大部分的问题(剩下的一小部分可以通过一些其他的折中方式进行解决)。了解容器云的同学应该知道,容器话的方案其实是有很多的,比如DockerOne 的Docker产品,CoreOS公司推出的Rocket,已经传统的LXC等等,在此就不一一列举了,但这些容器化的产品中在企业中使用最广泛的是Docker,所以接下来的内容中我们也是以Docker为例进行描述。
既然说容器可以帮我们解决前面说过的环境不一致所带来的的问题,那么问题来了,容器是如何帮我们解决这些问题的呢?接下来我们简单的介绍一下。
简单说来,Docker 由镜像和容器两部分所组成。
1. 镜像(Image)
镜像简单说来就是一个文件,并且是一个用来创建容器的文件,如果大家装过windows 系列的操作系统的话,那么Docker就特别像windows 里面的”windowsx 纯净版.rar”文件。
对于我们来说,最重要的是一个镜像中不但可以包含我们的应用进程同时也包含了我们的应用进程所需要的运行环境,用Docker 官方的说法就是build once,run anywhere。
2. 容器(Container)
容器就是一个加了壳的进程,容器在使用方面特别像我们常用的操作系统,有点类似我们常用的虚拟机,但是和虚拟机的一大区别是,容器并没有自己完整独立的内核,同一个物理机或者虚拟机上运行容器共用同一个内核。
一个容器里面可以支持什么样的操作,主要取决于我们在容器里面配置的环境是怎样的。
一句话来概括镜像和容器的关系就是:容器是镜像的运行时,镜像是容器的静态时。
容器化准备
在进行网站的容器化之前,我们需要先部署下容器的运行环境,好在这个过程非常的简单,Docker 为我们提提供了常用操作系统上的安装方式,在此我们以Mac、Windows、Linux为例:
Mac:https://download.docker.com/mac/stable/Docker.dmg
Windows:https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe
Linux:https://get.docker.com/
需要注意的是如果我们使用的是windows系统,需要看下当前操作系统是否支持虚拟化,比如win10 家庭版是不支持的,win10专业版是支持的,这个需要注意下。
鉴于我们使用的服务器多数是linux,在此我们就以linux的CentOS7.x为例看下Docker 的具体安装部署过程。
Docker 的安装方式有多种,比如源码编译安装、下载二进制包安装以及直接在线安装等。源码编译适合对Docker的把控比较细的场景,比如需要修改Docker本身的源码,或者更改Docker自身的一些比较精细的配置等;二进制安装适合对Docker不需要什么精细化的更改且,且服务器不通外网的场景,下载二进制的安装包后直接进行使用即可;yum 在线安装适合服务器通外网且对Docker自身没有精细化控制需要的场景。鉴于我们搭建个人网站的服务器一般都是通外网的,在此我们直接以yum在线安装为例,看下Docker的具体安装过程:
# 安装下redhat的扩展源
[root@localhost ~]# yum install -y epel-release# 清下版本缓存
[root@localhost ~]# yum clean all
Loaded plugins: fastestmirror, langpacks
Cleaning repos: Ceph Ceph-noarch ceph-source dockerrepo epel kubernetes
Cleaning up everything
Cleaning up list of fastest mirrors# 看下可以安装的Docker资源
[root@localhost ~]# yum list | grep docker
docker.x86_64 1.10.3-46.el7.centos.14 @extras
docker-common.x86_64 1.10.3-46.el7.centos.14 @extras
docker-selinux.x86_64 1.10.3-46.el7.centos.14 @extras
docker-compose.noarch 1.18.0-4.el7 epel
docker-engine.x86_64 17.05.0.ce-1.el7.centos dockerrepo
docker-engine-debuginfo.x86_64 17.05.0.ce-1.el7.centos dockerrepo
docker-engine-selinux.noarch 17.05.0.ce-1.el7.centos dockerrepo
golang-github-fsouza-go-dockerclient-devel.x86_64
kdocker.x86_64 4.9-1.el7 epel
python-docker-scripts.noarch 0.4.4-1.el7 epel
python-dockerfile-parse.noarch 0.0.5-1.el7 epel
python2-avocado-plugins-runner-docker.noarch
python2-docker-squash.noarch 1.0.7-3.el7 epel
python2-dockerpty.noarch 0.4.1-10.el7 epel
python36-docker.noarch 2.6.1-3.el7 epel
python36-docker-pycreds.noarch 0.2.1-2.el7 epel
python36-dockerpty.noarch 0.4.1-10.el7 epel# 直接yum 在线安装
[root@localhost ~]# yum install -y docker-io# 启动Docker 服务
[root@localhost ~]# systemctl start docker# 查看Docker服务状态
[root@localhost ~]# systemctl status docker
● docker.service - Docker Application Container EngineLoaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)Active: active (running) since Sun 2020-01-12 18:39:31 PST; 9h agoDocs: http://docs.docker.comMain PID: 1544 (docker-current)CGroup: /system.slice/docker.service└─1544 /usr/bin/docker-current daemon --exec-opt native.cgroupdriver=systemd --selinux-enabled --log-driver=journald --insecure-registry=harbor-inner.v190129.163skiff.com --insecure-registry=re...Jan 12 18:39:30 localhost.localdomain docker-current[1544]: time="2020-01-12T18:39:30.629872714-08:00" level=info msg="Graph migration to content-addressability took 0.00 seconds"
Jan 12 18:39:30 localhost.localdomain docker-current[1544]: time="2020-01-12T18:39:30.697717901-08:00" level=info msg="Firewalld running: true"
Jan 12 18:39:31 localhost.localdomain docker-current[1544]: time="2020-01-12T18:39:31.001124722-08:00" level=info msg="Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Da... IP address"
Jan 12 18:39:31 localhost.localdomain docker-current[1544]: time="2020-01-12T18:39:31.250233807-08:00" level=info msg="Loading containers: start."
Jan 12 18:39:31 localhost.localdomain docker-current[1544]: .....
Jan 12 18:39:31 localhost.localdomain docker-current[1544]: time="2020-01-12T18:39:31.359107953-08:00" level=info msg="Loading containers: done."
Jan 12 18:39:31 localhost.localdomain docker-current[1544]: time="2020-01-12T18:39:31.359128191-08:00" level=info msg="Daemon has completed initialization"
Jan 12 18:39:31 localhost.localdomain docker-current[1544]: time="2020-01-12T18:39:31.359142247-08:00" level=info msg="Docker daemon" commit=cb079f6-unsupported execdriver=native-0.2 graphdriv...rsion=1.10.3
Jan 12 18:39:31 localhost.localdomain systemd[1]: Started Docker Application Container Engine.
Jan 12 18:39:31 localhost.localdomain docker-current[1544]: time="2020-01-12T18:39:31.364346067-08:00" level=info msg="API listen on /var/run/docker.sock"
Hint: Some lines were ellipsized, use -l to show in full.# 配置Docker服务的开机自启
[root@localhost ~]# systemctl enable docker# 查看Docker 是否安装成功
[root@localhost ~]# docker info
Containers: 5Running: 0Paused: 0Stopped: 5
Images: 26
Server Version: 1.10.3
Storage Driver: devicemapperPool Name: docker-8:3-134990499-poolPool Blocksize: 65.54 kBBase Device Size: 10.74 GBBacking Filesystem: xfsData file: /dev/loop0Metadata file: /dev/loop1Data Space Used: 2.438 GBData Space Total: 107.4 GBData Space Available: 74.84 GBMetadata Space Used: 3.445 MBMetadata Space Total: 2.147 GBMetadata Space Available: 2.144 GBUdev Sync Supported: true
可以查看到这些info信息,则Docker就正常安装上了。
除了命令行界面,Docker也有自己的图形化界面,但一般很少使用,在此我们不再进行描述。
以上就是一个简单的Docker环境的部署过程,接下来我们就会用这个搭建好的Docker环境来部署我们的个人网站。
静态网站的容器化
由难到易,我们先以前面提到的个人简历网站为例看下容器化到底可以为我们带来什么。
个人简历网站只是一个简单的纯静态的网站,不会涉及到动态内容的解析,因此在此我们直接用一个nginx web 服务器来托管我们的网页文件。
那么问题来了,接下来我们是要制作nginx镜像吗?答案是:非必须。对于nginx这种很常见的组件,多数的镜像中心中都可以查到,所以我们直接从镜像中心中下载一个即可。如果不进行配置的话默认Docker 会从官网的镜像中心进行镜像的下载,国内环境直接下载的话可能会比较慢,好在国内也有很多的免费的可以下载容器镜像的站点,比如阿里云、腾讯云、网易云等。下面我们看下镜像的具体获取过程。
# 下载镜像前我们的服务器中是不包含任何镜像的[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE# 直接从网易云的镜像中心下载nginx镜像[root@localhost ~]# docker pull hub.c.163.com/public/nginx:1.2.1
Trying to pull repository hub.c.163.com/public/nginx ...
1.2.1: Pulling from hub.c.163.com/public/nginx
f46924f139ed: Pull complete
a3ed95caeb02: Pull complete
4849cac99801: Pull complete
ff472075fa83: Pull complete
Digest: sha256:ef9bd0fa2c97429562ecc209772ef07ebb9dfd66dc310ea02739d1f026dbf7b8
Status: Downloaded newer image for hub.c.163.com/public/nginx:1.2.1
由于我们下载的是公开的镜像,因此不需要进行docker login 的登录操作。
# 这个时候我们再查看下,就会发现本地已经多出了一个nginx镜像[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hub.c.163.com/public/nginx 1.2.1 2dc68ff797db 3 years ago 171.5 MB
虽然我们没有进行nginx组件下载和部署操作,但通过这简单的docker pull 操作我们就获取到了一个nginx的运行环境。
接下来我们需要将我们的简历对应的网页文件放到我们下载的nginx镜像中,那么怎么进行操作呢?这个时候我们需要用到Docker为我们提供的工具Dockerfile。
Dockerfile 是一个纯文本文件,里面存放的是我们对镜像的操作,在使用方式上非常简单。Dockerfile有自己的一组语法、命令,本文中我们也会用到一些,感兴趣的同学可以自己查阅学习下。
在创建Dockerfile前我们需要先新建一个网页文件,作为我们的个人简历页面。
1. 准备网页文件
# 新建一个目录存在网页文件和Dockerfile文件[root@localhost ~]# mkdir web1
[root@localhost ~]# cd web1# 新建网页文件
[root@localhost ~]# vim index.html
加下来需要在刚刚新建的index.html文件中加入一点示例代码,代表我们的简历内容:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>个人简历</title></head><body>个人简历网站。</body>
</html>
接下来我们先建Dockerfile文件,将网页文件加入到已经下载好的nginx镜像中。
2. 书写Dockerfile文件
# 创建Dockerfile文件[root@localhost web1]# vim Dockerfile在新建好的Dockerfile 中加入如下内容,将写好的网页文件添加到我们nginx镜像当中:# 声明我们的基镜像,接下来的操作都是在这个镜像的基础上进行的
FROM hub.c.163.com/public/nginx:1.2.1
# 将我们在服务器上写好的网页文件拷贝到nginx中
COPY ./index.html /usr/share/nginx/html/index.html
# 开放80端口,这样后面可以通过这个端口来访问我们的网站
EXPOSE 80
3. 构建新镜像
前面两步我们已经准备好了我们需要的文件,这一步我们需要用写好的Dockerfile 文件来构建新的镜像,构建镜像的过程非常简单,直接docker build即可:
# 构建镜像[root@localhost web1]# docker build -t web1:v1 .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM hub.c.163.com/public/nginx:1.2.1---> 2dc68ff797db
Step 2 : COPY ./index.html /usr/share/nginx/html/index.html---> 5bae75fcb366
Removing intermediate container dd981fe066a7
Step 3 : EXPOSE 80---> Running in 55a50dd13a52---> 81ede6cab5c5
Removing intermediate container 55a50dd13a52
Successfully built 81ede6cab5c5
这样我们的新镜像就构建好了,新镜像里面已经包含了我们的nginx web组件和网站的代码。如果Dockerfile中存在错误,则在构建过程中打出的日志里面会给出提示,提示非常详细,会为我们指明具体是在执行哪一步时触发的错误,以及错误本身具体的信息,方便我们排查和修改Dockerfile文件。
# 查看新构建的镜像[root@localhost web1]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
web1 v1 81ede6cab5c5 16 minutes ago 171.5 MB
hub.c.163.com/public/nginx 1.2.1 2dc68ff797db 3 years ago 171.5 MB
可以看到我们新构建的镜像已经存在了,
4. 运行容器
既然镜像已经有了,接下来我们就可以运行我们的容器了。
# 后台运行容器[root@localhost web1]# docker run --name web1 -p 80:80 -d web1:v1
7f8203be0991cb34059958f7bd14ee63cc8dedc033f29a1fdcf6c8d4cc016b53--name 是为我们的容器进行命名,此处我们命名为了web1。
-p 是指将容器中我们nginx的80端口映射为我们服务器上的80端口。
-d 是指后台运行我们的容器,后台运行运行容器有些类似我们的后台运行的守护进程。
# 查看下容器是否已经在后台正常的运行了
[root@localhost web1]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f8203be0991 web1:v1 "/bin/sh -c '/etc/ini" About a minute ago Up About a minute 22/tcp, 443/tcp, 0.0.0.0:80->80/tcp web1
从查询结果可以看出,我们的容器已经在运行中了,且容器除了映射了我们设置的80端口,基镜像nginx还默认对外映射了22端口和443端口,且我们nginx的80端口已经被映射为服务器上的80端口。
到此为止我们的个人简历网站就部署好了,接下来我们测试下网站是否可以正常访问。
直接访问我们的服务器地址即可:
如果需要进入容器内查看,也很简单,直接docker exec 进入即可:
# 进入容器
[root@localhost web1]# docker exec -ti web1 bash
root@7f8203be0991:/# hostname -I
172.17.0.2
进入容器后需要进行的操作就就和我们日常在虚拟机上操作一样了。
可以看到网站容器化后已经可以正常访问了,整个过程很简单,且后续的再对网站进行修改时也非常的便捷,代码修改后只需要修改下Dockerfile文件,打出新镜像,直接运行容器即可,基本不再需要去纠结哪些恼人的环境不一致带来的问题。
容器化小结
总结一下,整个过程其实非常的简单,简单到只需要三个步骤:
(1) 书写Dockerfile文件。
(2) 使用写好的Dockerfile文件构建镜像。
(3) 用上面构建好的新镜像运行容器。
福利扫描添加小编微信,备注“姓名+公司职位”,加入【云计算学习交流群】,和志同道合的朋友们共同打卡学习!
推荐阅读:
消息称苹果斥资2亿美元收购Xnor.ai;华为2000万英镑投资,加速发展HMS手机生态系统;国产SSD将迎来爆发……
如何打造“智能助理”?阿里对话开发平台这样做
浪潮十年:“云计算”中崛起,“智慧计算”中爆发
掌握 8 种语言、被阿里点赞,这名德国程序员简直开挂了!
为什么 k8s 在阿里能成功?| 问底中国 IT 技术演进
170个新项目,579个活跃代码仓库,Facebook开源年度回顾
真香,朕在看了!