作为一个网络工程专业的毕业生,网络工程专业的相关知识还是要掌握一点的。
一、认识云计算
1. 什么是云计算
通过互联网提供的计算服务,包括服务器、存储、数据库、应用程序等,采用按需付费的计费模式,用户无需购置物理硬件,前期投入低、无需维护机房、扩展性强
2. 云计算的类型
- 公有云:由云计算供应商拥有和运营,通过互联网提供计算资源,按需计费
- 私有云:企业和组织搭建的私有云服务,硬件、软件、基础结构都是私有的,安全性更强
- 混合云:同时使用公有云和私有云,允许组织将敏感数据保留在私有云中(安全性),同时使用公有云来部署服务(降低成本)
3. 云计算的服务模式
- IaaS:基础设施即服务,例如服务器、网络、存储、数据中心等基础设施
- PaaS:平台即服务,提供硬件和软件服务,提供现成的环境便于用户快速构建应用
- SaaS:软件即服务,提供软件服务,用户直接使用部署好的软件
二、Linux 基本命令
命令格式:程序 + 参数 + 对象
1. Linux 文件目录结构及管理
cd
:切换目录touch
:新建文件rm
:删除文件,参数:r 表示递归,f 表示强制删除ls
:查看目录内容,参数:l 表示列出详细信息,格式如下图pwd
:查看当前目录mkdir
:创建目录cat
:以文本形式输出文件内容- Linux 采用树形目录结构,有相对路径与绝对路径两种定位方式
2. Linux 文件权限管理
chmod
:修改文件权限的命令,权限用数字来表示,其中 4 代表读取权限(read);2 代表写入权限(write);1 代表可执行(execute)。- 文件权限分为文件所有者的权限、文件所有者所在的用户组内其它用户的权限、其它组内用户的权限。
- 综上所述,一个文件最高可以设置的权限是 777,即任何用户都可以读、写、执行这个文件。允许所有权限的命令就是
chmod 777 文件路径
。 - 可以使用
chmod +x 文件路径
命令快速给文件添加可执行权限,这个命令会经常用到。
3. Linux 用户、组权限管理
useradd
:新建用户passwd
:修改用户密码su
:切换用户
4. Linux 资源管理
top
:实时查看当前系统运行的进程和占用率ps
:单次输出进程信息,参数:e 查看所有进程,f 查看完整信息(包括用户、PPID、运行时间等) 例如:ps -ef | grep 程序名称
netstat
:查看网络连接信息,属于 net-tools 软件包的一部分。可以查看网络协议、地址、端口等。例如:netstat -tunlp | grep 程序名称或端口号
。参数说明:
-t 查看 tcp 协议;
-u 查看 udp 协议;
-n 端口以数字形式显示,否则直接显示服务名称;
-l 显示服务监听的端口;
-p 显示占用端口的进程。
ifconfig
:查看网卡信息,属于 net-tools 软件包的一部分。快速输出 IP 地址:ifconfig 网卡名称 | awk 'NR==2{print $2}'
(打印 ifconfig 输出结果中第二行第二列的内容)- firewall-cmd:检查并配置防火墙。防火墙会根据不同区域(zone)对不同来源的网络流量进行处理。常用命令:
命令 | 用途 |
---|---|
firewall-cmd --state | 查看防火墙状态 |
firewall-cmd --list-services | 查看当前开放了哪些服务对应的端口 |
firewall-cmd --get-services | 查看还有哪些服务可以开放对应的端口 |
firewall-cmd --get-active-zones | 查看当前各个网卡的区域 |
firewall-cmd --zone=public --list-ports | 查看 public 区域打开的端口 |
firewall-cmd --reload | 重新加载防火墙,用于使新的规则生效 |
firewall-cmd --add-service=服务名称 | 开放对应服务的端口,重启后失效 |
firewall-cmd --permanent --add-service=服务名称 | 开放对应服务的端口,永久生效 |
firewall-cmd --zone=public --add-port=端口号/协议 --permanent | 在 public 区域下永久开放指定端口(与上面的服务是冲突的) |
firewall-cmd --zone=public --query-port=80/tcp | 查看 public 区域下 80 端口的tcp 流量规则 |
firewall-cmd --zone= public --remove-port=80/tcp --permanent | 在 public 区域下永久关闭访问 80端口的 tcp 流量 |
5. 补充:vim 常用快捷键
i:进入编辑模式
I:进入编辑模式并将光标放在行首位置
a:进入编辑模式并将光标往后移动一位
A:进入编辑模式并将光标放在行尾位置
o:进入编辑模式,在光标所在行的下方新增一行,并将光标移到新行的行首
O:进入编辑模式,在光标所在行的上方新增一行,并将光标移到新行的行首
x:删除光标所在处的字符
X:删除光标所在处的前一个字符
0:跳转到光标所在行的行首
$:跳转到光标所在行的行尾
gg:跳转到文件开头
G:跳转到文件最后一行的开头
dd:删除光标所在行数据,并将其写入到缓存区
yy:复制光标所在行数据到缓存区
p:将缓存区内容粘贴到光标位置之后
P:将缓存区内容粘贴到光标位置之前
u:撤销最近的修改
U:撤销对光标所在行做的所有修改
r:替换光标所在位置的字符
R:替换从光标所在位置起的字符,并进入编辑模式
.:重复上一次的修改
\>>:向右缩进本行
<<:向左缩进本行
Ctrl+D:将光标向下翻半屏
Ctrl+U:将光标向上翻半屏
:%d:清空文件内容
:set nu/nonu:显示/隐藏行号
:set ai/noai:开启/关闭自动缩进
:/string:在文件中搜索 string
:%s/old/new/g:将文件中所有的 old 都替换为 new
三、软件管理
注意:该部分内容适用于 CentOS 7,其它的 Linux 发行版由于软件包管理器不同,命令也会不同。
1. 配置软件源
- 可以下载现成的配置文件替换,也可以手动编辑
- 下载文件可以用
wget
比较简单,也可以用curl
- yum 可以探测延迟最低的镜像服务器,但是延迟低不意味着下载速度快
替换 yum 镜像源的步骤:
- 备份旧的配置文件:
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
- 下载新的配置文件并替换:
wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
- 执行
yum clean all
清除缓存 - 执行
yum makecache
重新生成缓存
- 备份旧的配置文件:
安装第三方维护的 yum 软件源(部分软件包官方源没有提供):
- 执行
sudo yum install 软件源名称
(例如epel-release) - 执行
yum update
更新软件
- 执行
2. 软件管理
- yum 安装软件的命令:
yum install 软件包名称 -y
- 查看已安装的软件包:
rpm -qa
,可以结合 grep 过滤想要查找的软件包。 - yum 卸载软件的命令:
yum remove 软件包名称
- 启动软件:
systemctl start 服务名称
- 查看状态:
systemctl status 服务名称
- 关闭软件:
systemctl stop 服务名称
- 设置自启动:
systemctl enable 服务名称
- 禁止自启动:
systemctl disable 服务名称
四、网站部署
1. 查看 web 服务器信息
curl -I 域名或IP
2. 修改 nginx 首页内容
- 查看 nginx 的首页路径:
rpm -ql nginx | grep "index"
- 修改 index.html 文件内容即可
3. 安装 LAMP 框架
- LAMP 即 Linux + Apache(nginx)+ MySQL(MariaDB)+ PHP(后端应用)的缩写,是一套非常流行的开源软件包组合。
- 如果是 CentOS,需要先禁用 SELinux:修改
/etc/selinux/config
文件,将SELINUX=disabled,之后重启系统 - 安装 Apache、MariaDB、PHP:
yum install httpd mariadb-server php php-fpm php-mysql -y
- 记得给 httpd、php-fpm、mariadb 设置自启动(
systemctl enable
) 让 httpd 支持 php 的脚本:编辑
/etc/httpd/conf/httpd.conf
,查找DocumentRoot
关键字,修改内容如下:DocumentRoot "/var/www/html" TypesConfig /etc/mime.types AddType applications/x-httpd-php .php AddType applications/x-httpd-php-source .phps DirectoryIndex index.php index.html
- 将
/var/html/www/html
目录的权限改为 775 在
/var/html/www
目录下编写 index.php,内容如下:<meta charset=utf8> This is a new homepage. <?php phpinfo(); ?>
- 重启 httpd 服务,用浏览器访问首页检查是否能正常显示 PHP 环境信息
4. 部署 Discuz!
- 先安装并配置好 Apache、MariaDB、PHP
- 使用
yum
安装 unzip - 下载 Discuz 安装包并使用
unzip
解压 - 将 upload 目录下的内容移到 httpd 的网站根目录(位置可以从 httpd 的配置文件
/etc/httpd/conf/httpd.conf
中找到) - 修改网站根目录及其子目录的权限为777(
chmod -R
) - 使用浏览器访问安装页面(IP 地址/install)
5. 编译 nginx 源码并安装
- 下载对应平台的 nginx 源代码(https://nginx.org/en/download.html)
- 配置编译环境(gcc、pcre、pcre-devel、zlib、zlib-devel、openssl、openssl-devel)
- 配置编译参数(安装位置、配置文件位置、日志文件位置、临时文件位置、要安装的模块等):
./configure --prefix=安装位置 --error-log-path=错误日志文件位置 --with-http_ssl_module(SSL 模块,用于支持 https) --with-http_stub_status_module(监控模块)
- 编译并安装:
make && make install
- 安装完成后,进入「安装位置/sbin」目录下启动 nginx
nginx -s stop
关闭nginxnginx -s reload
重新加载nginx -t
检查配置文件
6. nginx 部署静态站点
- 修改位于 nginx 安装目录下 conf 目录的
nginx.conf
文件 在
nginx.conf
文件内,一个 server 文本块代表一个网站,location 文本块内的是网页文件的目录,root 表示文件所在目录,index 表示首页文件名称
7. nginx 基于端口的虚拟主机
- nginx 可以基于不同端口在同一个 nginx 实例中部署不同的网站
- 修改
nginx.conf
,在其中新增一个 server 文本块,并修改监听端口、服务器名称、文件路径 - 在防火墙中开放对应的端口并重新加载防火墙规则
- 重新加载 nginx,并用浏览器访问网站
8. nginx 日志
- nginx 能够记录用户的每一次访问请求
- 目的:掌握服务器动态信息,可以用来排障、提高安全性;也可以用于监测并分析用户行为
- 修改
nginx.conf
,在 server 文本块中添加「access_log 日志文件路径 main;
」,同时要把「log_format」对应的注释符号删除,之后保存文件并重载 nginx 之后可以用
tail -f 日志文件路径
来实时查看日志
9. nginx 反向代理
- nginx 可以将用户的访问请求转发给目的地服务器,再将目的地服务器返回的数据转发给用户
- 目的:原始资源服务器不需要暴露在互联网上,起到保护服务器的作用
修改
nginx.conf
,将「server」文本块内的「location」文本块内容改为:proxy_pass 目的服务器地址;
- 之后保存文件并重载 nginx 即可
五、MariaDB 数据库
MariaDB 是一个 MySQL 的开源分支,在 MySQL 成为了甲骨文旗下的产品后,开源社区对于甲骨文是否会继续对 MySQL 的免费版本提供支持有所担忧,所以 MariaDB 应运而生。
MariaDB 和 MySQL 都属于关系型数据库,由于性能高、成本低、可靠性好而一度成为最流行的数据库。
本文将会介绍 MariaDB 数据库的一些基本操作。
1. 安装和基本配置
- 安装 MariaDB 非常简单,在 Linux 下只需要通过包管理器即可安装:
yum install mariadb-server -y
- (可选)安装完成后,建议执行一次
mysql_secure_installation
命令,设置 root 用户密码(此 root 非彼 root,这个章节里提及的 root 是数据库的管理员用户)、是否保留测试数据库、是否允许 root 用户远程登录等。 - 安装完成后需要启动 MariaDB 服务,如果要顺便设置开机自启的话:
systemctl enable --now mariadb
启动服务后,运行
mysql -uroot -p
之后输入密码即可登录,如果没有设置 root 用户的密码,那在输入密码的步骤直接按回车即可。登录成功后,可以随时执行
exit
命令退出,如上图所示。
2. 创建一个数据库和表
在关系型数据库中,数据会按照数据库→表→记录的递进关系来存储。所以我们需要先拥有数据库和表,才能进行记录的编辑,也就是常说的「增删改查」。
默认情况下,MariaDB 会提供一个用于测试的数据库,但是我们通常不会用到它,而是另行创建自己所需的数据库。
创建一个数据库:
create database <数据库名称> character set utf8 collate utf8_bin;
其中「character set」设定的是数据库内容的编码方式;而「collate」是设定校对方式,「utf8_bin」会以二进制方式来校对,也就是数据库的内容会区分大小写,与之相对的还有不区分大小写的「uft-8_general_ci」可供选择。
注意:MariaDB 使用的是一种叫 SQL 的编程语言,每条语句结尾都要有一个英文的分号。而且所有的名称(例如,数据库,表,字段)都是区分大小写的。
创建成功后,MariaDB 会返回「Query OK」的消息,如下图所示:
之后就可以执行
show databases;
命令来查看当前有哪些数据库:- 我们需要进入数据库才能创建表格,所以此处需要执行
use <数据库名称>;
命令。如果我们需要在多个数据库间切换,不需要退出当前的数据库,使用use <数据库名称>;
命令即可切换数据库。 进入数据库后,可以先看看这个数据库里有什么:
show tables;
可以看到是一个空的集合,现在就可以创建一个表了。
表中的数据结构类似于 Excel 表格,分为列和行,列(field)是字段,行(row)是记录。
-- 创建一个名为 student 的表 create table student( -- 表的第一个字段叫 sno,它的格式是字符型,可以容纳 10 个字符,并且它是这个表的主键 sno char(10) primary key, -- 表的第二个字段叫 sname,也是字符型,能容纳 8 个字符,not null 表示这个字段的值不允许为空 sname char(8) not null, classnumber char(8) not null, -- 第四个字段有一个 default 属性,说明这个字段有默认值,如果在插入记录的时候没有设定这个字段的值,那就会用默认值代替 ssex char(2) default '女' not null );
一个字段的默认值有三种:DEFAULT NULL(允许空值)、NOT NULL(不允许空值)和 DEFAULT '默认值'。
如果输出结果为
Query OK, 0 rows affected
,说明表就创建成功了。创建一个表后,可以使用
desc <表名>;
命令查询表的结构:也可以用
show create table <表名>;
语句来查询创建这个表的 SQL 语句:
3. 修改字段的属性
在创建一个表后,我们可能需要对这个表的某些字段进行修改,或者增加新的字段。
修改字段的数据类型:如果创建表的时候选择的数据类型满足不了需求,就可以对数据类型进行修改。
SQL 语句:
alter table <表名> modify <字段名称> <新的数据类型(数据长度)> <新的默认值> <新的注释>;
摘自:https://www.w3cschool.cn/mariadb/mariadb_data_types.html
MariaDB数据类型可以分为数字,日期和时间以及字符串值。
数字数据类型
MariaDB支持的数字数据类型如下 -
- TINYINT - 此数据类型表示落入-128到127的有符号范围内的小整数,以及0到255的无符号范围。
- BOOLEAN - 此数据类型将值0与“false”相关联,值1与“true”相关联。
- SMALLINT - 此数据类型表示-32768到32768的有符号范围内的整数,以及0到65535的无符号范围。
- MEDIUMINT - 此数据类型表示有符号范围-8388608到8388607中的整数,无符号范围0到16777215。
- INT(也为INTEGER) - 此数据类型表示正常大小的整数。当标记为unsigned时,范围跨越0到4294967295.当有符号(默认设置)时,范围跨越-2147483648到2147483647.当列设置为ZEROFILL(无符号状态)时,其所有值都由零添加INT值中的M个数字。
- BIGINT - 此数据类型表示有符号范围9223372036854775808到9223372036854775807内的整数,无符号范围0到18446744073709551615。
- DECIMAL(DEC,NUMERIC,FIXED) - 该数据类型表示精确的定点数,M指定其数字,D指定小数后的数字。 M值不添加“ - ”或小数点。如果D设置为0,则不会出现小数或小数部分,并且该值将舍入为最接近的DECIMAL INSERT。最大允许位数为65,小数位数的最大值为30.默认值M的默认值为10,省略时D为0。
FLOAT - 此数据类型表示值0的小的浮点数或以下范围内的数字 -
- -3.402823466E + 38至-1.175494351E-38
- 1.175494351E-38至3.402823466E + 38
DOUBLE(也是REAL和DOUBLE PRECISION) - 此数据类型表示值0的正常大小的浮点数,或以下范围内的值 -
- -1.7976931348623157E + 308至-2.2250738585072014E-308
- 2.2250738585072014E-308至1.7976931348623157E + 308
- BIT - 此数据类型表示位字段,M指定每个值的位数。省略M时,默认值为1.位值可以通过“b'[value]'”应用,其中值表示0和1中的位值。零填充从左边自动发生全长;例如,“10”变为“0010”。
日期和时间数据类型
MariaDB支持的日期和时间数据类型如下 -
- DATE - 此数据类型表示日期范围“1000-01-01”到“9999-12-31”,并使用“YYYY-MM-DD”日期格式。
- TIME - 此数据类型表示“-838:59:59.999999”到“838:59:59.999999”的时间范围。
- DATETIME - 此数据类型表示范围“1000-01-01 00:00:00.000000”至“9999-12-31 23:59:59.999999”。它使用“YYYY-MM-DD HH:MM:SS”格式 。
- TIMESTAMP - 此数据类型表示“YYYY-MM-DD HH:MM:DD”格式的时间戳。 它主要用于详细描述数据库修改的时间,例如插入或更新。
- YEAR - 此数据类型表示4位数格式的年份。 四位数格式允许在1901到2155和0000范围内的值。
字符串数据类型
MariaDB支持的字符串类型值如下 -
- String literals - 此数据类型表示用引号括起来的字符序列。
- CHAR - 此数据类型表示包含指定长度的空格的右侧带有固定长度的字符串。 M表示字符的列长度,取值范围为0〜255,缺省值为1。
- VARCHAR - 此数据类型表示一个可变长度字符串,M范围(最大列长度)为0到65535。
- BINARY - 此数据类型表示二进制字节字符串,M为列长度(以字节为单位)。
- VARBINARY - 此数据类型表示可变长度的二进制字节字符串,M为列长度。
- TINYBLOB - 此数据类型表示最大长度为255(28 - 1)个字节的blob列。在存储中,每个都使用一个字节长度的前缀,表示值中的字节数量。
- BLOB - 此数据类型表示最大长度为65,535(216 - 1)个字节的blob列。在存储中,每个都使用两字节长度的前缀,表示值中的字节数量。
- MEDIUMBLOB - 此数据类型表示最大长度为16,777,215(224 - 1)个字节的blob列。在存储中,每个都使用一个三字节长度前缀,表示值中的字节数量。
- LONGBLOB - 此数据类型表示最大长度为4,294,967,295(232 - 1)个字节的blob列。在存储中,每个使用四字节长度的前缀,表示值中的字节数量。
- TINYTEXT - 此数据类型表示最大长度为255(28 - 1)个字符的文本列。在存储中,每个都使用一个字节长度的前缀,表示值中的字节数量。
- TEXT - 此数据类型表示最大长度为65,535(216 - 1)个字符的文本列。在存储中,每个都使用两字节长度的前缀,表示值中的字节数量。
- MEDIUMTEXT - 此数据类型表示最大长度为16,777,215(224 - 1)个字符的文本列。在存储中,每个都使用三字节长度前缀,表示值中的字节数量。
- LONGTEXT - 此数据类型表示最大长度为4,294,967,295或4GB(232 - 1)个字符的文本列。在存储中,每个使用四字节长度的前缀,表示值中的字节数量。
- ENUM - 此数据类型表示一个列表中只有一个值的字符串对象。
- SET - 此数据类型表示一个列表中具有零个或多个值的字符串对象,最多包含64个成员。 SET值在内部作为整数值存在。
修改字段名称:
ALTER TABLE <表名> CHANGE <旧字段名> <新字段名> <新数据类型>;
是的,
change
关键字也是能修改字段的数据类型、数据长度和注释的。新增一个字段:
alter table <表名> add <新字段名> <新字段的数据类型> after <已有字段名>;
使用 after 关键字,说明是在已有字段名的后面新增字段。
注意:没有 before 的用法,如果新字段要位于表中的第一位,那就把「after 已有字段名」改为「first」即可。
- 删除一个字段:
ALTER TABLE <表名> DROP <字段名>;
4. 插入和修改记录
插入一条记录:
insert into <表名>(<要插入的字段>) values(<每个字段的值>);
可以同时插入多条记录:
INSERT INTO products VALUES (1, “first row”), (2, “second row”);
甚至将 A 表的查询结果插入到 B 表中:
INSERT INTO B SELECT * FROM A WHERE status = 'available';
修改一条记录:
update <表名> set <字段>=<值> where <匹配字段>=<匹配的值>;
这里出现了一个新的关键字
where
,它是用来定位一条或一些记录的。它可以使用 AND 和 OR 运算符来指定多个条件,也可以使用 =、!=、>、<、>=、<= 这些运算符来确定查询的范围。
5. 选择和删除记录
选择记录的语法:
SELECT field, field2,... FROM table_name, table_name2,... WHERE...
SELECT语句提供了多个选项来指定使用的表:
- database_name.table_name
- table_name.column_name
- database_name.table_name.column_name
所有select语句必须包含一个或多个select表达式。 选择表达式由以下选项之一组成:
- 字段名。
- 使用运算符和函数的表达式。
- 规范"table_name.*"以选择给定表中的所有列。
- 字符"*"用于从FROM子句中指定的所有表中选择所有列。
如果只有模糊的条件,可以在 where 子句后面接上 like 子句进行模糊匹配:
SELECT field, field2,... FROM table_name, table_name2,... WHERE field LIKE condition
例如:
SELECT * from products_tbl WHERE product_manufacturer LIKE 'XYZ%';
注意:在 like 子句中,是用%来表示通配符的
- 如果要对查询的结果进行筛选,可以在语句结尾加上 HAVING 子句:
SELECT COUNT(field) as alias, field2 ... FROM table_name HAVING alias > 1 AND/OR ...;
如果要对查询的结果进行排序,可以在语句的结尾加上 ORDER BY 子句:
SELECT field, field2,... [or column] FROM table_name, table_name2,... ORDER BY field, field2,... ASC[or DESC]
其中,ASC 表示升序,DESC 表示降序。如果没有指定排序方式,默认按升序排列。
如果要对查询的结果按字段进行分组(例如查询一个名字出现了几次),可以使用 GROUP BY 子句:
SELECT column_name FROM table_name WHERE ... GROUP BY column_name;
如果要对结果集进一步统计,可以使用 WITH ROLLUP 关键字:
SELECT column_name, MIN/MAX/SUM/AVG/COUNT(column_name) as alias FROM table_name GROUP BY column_name WITH ROLLUP;
如果统计之后的结果集中有 NULL,可以用 coalesce 来替代掉 NULL。例如:
SELECT coalesce(<出现 NULL 值的字段>, alias), MIN/MAX/SUM/AVG/COUNT(column_name) as alias FROM table_name GROUP BY column_name WITH ROLLUP;
多表连接查询:如果需要从多个表中选择记录,可以使用 JOIN 子句,它可以将多个表的查询结果合并到单个对象中。JOIN 子句的语法如下:
SELECT column FROM table_1
INNER JOIN
table_2 ON table_1.column = table_2.column;
连接有三种形式:
- 左连接(LEFT JOIN):获取左表的所有记录,即使右表中没有匹配的记录
- 右连接(RIGHT JOIN):获取右表的所有记录,即使左表中没有匹配的记录
- 内连接(INNER JOIN):只获取两个表中字段匹配关系表达式的记录,相当于从两个表的交集中查询所需的记录
- 了解了前面的语法之后,删除记录就很简单了:
DELETE FROM table_name [WHERE …]
6. 约束
约束(CONSTRAINT)的主要作用是防止重复数据的存在,避免数据混乱和数据冗余的问题。
给已创建的表添加约束:ALTER TABLE table_name ADD CONSTRAINT constraint_name constraint_type(column_name or expression) expression;
约束主要有:主键、外键、唯一约束和检查约束。
- 主键:保证数据的唯一性,一个表只能有一个主键,主键可以是一个字段或多个字段的集合,而且主键的值不能为空。在创建表或修改表时用
PRIMARY KEY
关键字来设定主键。如果在插入记录时试图插入与主键的值重复的数据,系统会抛出错误。可以在插入时使用INSERT IGNORE INTO
让系统跳过重复的记录,达到在间隙中插入数据的目的;或者使用REPLACE INTO
来删除表中已有的重复记录,并插入新的记录。 - 外键:与主键约束一起使用,用于建立主表和从表的关联,约束两个表中数据的一致性和完整性。当主表删除某条记录时,从表中对应的记录也必须有相应的改变。
- 唯一约束:所有记录中不能出现重复的值。唯一约束与主键约束相似的是它们都可以确保列的唯一性。不同的是,唯一约束在一个表中可有多个,并且设置唯一约束的列允许有空值,但是只能有一个空值。而主键约束在一个表中只能有一个,且不允许有空值。比如,在用户信息表中,为了避免表中用户名重名,可以把用户名设置为唯一约束。
- 检查约束:用于对字段的值设置限制,避免无效数据输入。
7. 函数、存储过程和触发器
创建一个函数:
DELIMITER // CREATE FUNCTION func_student(id INT(11)) RETURNS VARCHAR(20) COMMENT '查询某个学生的姓名' BEGIN RETURN(SELECT name FROM tb_student WHERE tb_student.id = id); END //
创建一个存储过程:
-- 设置语句结束符为 // DELIMITER // CREATE PROCEDURE ShowStuScore() BEGIN SELECT * FROM tb_students_score; END //
调用函数或存储过程:CALL function_name/procedure_name(parameter);
创建一个触发器:
CREATE TRIGGER double_salary AFTER INSERT ON tb_emp6 FOR EACH ROW INSERT INTO tb_emp7 VALUES (NEW.id,NEW.name,deptId,2*NEW.salary);
注意:每个表都支持 INSERT、UPDATE 和 DELETE 的 BEFORE 与 AFTER,因此每个表最多支持 6 个触发器。
8. 视图和索引
创建一个视图:
CREATE VIEW v_students_info (s_id,s_name,d_id,s_age,s_sex,s_height,s_date) AS SELECT id,name,dept_id,age,sex,height,login_date FROM tb_students_info;
这样可以限制用户能访问的内容,保证数据库的安全。
索引可以在创建表或修改表属性时添加,也可以用 CREATE 语句创建索引:
CREATE <索引名> ON <表名> (<列名> [<长度>] [ ASC | DESC]);
9. 用户管理
- 创建用户:
CREATE USER <用户> [ IDENTIFIED BY [ PASSWORD ] 'password' ] [ ,用户 [ IDENTIFIED BY [ PASSWORD ] 'password' ]]
给用户授权:
GRANT priv_type [(column_list)] ON database.table TO user [IDENTIFIED BY [PASSWORD] 'password'] [, user[IDENTIFIED BY [PASSWORD] 'password']] ... [WITH with_option [with_option]...]
10. 备份和恢复
数据作为业务和操作的基础,会面临各种可能的威胁(例如,网络攻击,系统故障,系统崩溃和维护错误),所以备份至关重要。备份的主要选项包括逻辑备份和物理备份。 逻辑备份保存用于恢复数据的SQL语句。 物理备份包含数据副本。
- 与物理备份相比,逻辑备份提供了在具有不同配置的另一台机器上恢复数据的灵活性,物理备份通常限于相同的机器和数据库类型。 逻辑备份发生在数据库和表级,物理发生在目录和文件级。
- 物理备份的大小小于逻辑备份,并且执行和恢复所需的时间也更少。 物理备份还包括日志和配置文件,但逻辑备份不包括。
物理备份通常需要第三方软件来实现,MariaDB 自身提供的备份方式是逻辑备份工具 mysqldump
。它会将数据转储为SQL,CSV,XML和许多其他格式。 但要注意,它的备份数据中不包含存储过程,视图和事件。
mysqldump
主要有三种使用方式:
原始数据 - 通过--tab选项将表转储为原始数据文件,该选项还指定备份文件的存储位置
mysqldump -u root -p --no-create-info --tab=<备份文件存储位置> <数据库名> <表名>
直接输出内容 - 此选项允许将单个或多个表的 SQL 语句输出,并支持备份主机上的所有现有数据库。
mysqldump -u root -p <数据库名> <表名> > export_file.txt
远程传输 - 您还可以将数据库和表导出到另一个主机
mysqldump -u root -p <数据库名> | mysql -h <主机地址> <数据库名>
注意:InnoDB使用缓冲池来提高性能。所以建议在 MariaDB 的配置文件 my.cnf
或 my.ini
中将innodb_change_buffering
的值设为 none
。
六、自动化运维软件 Ansible
1. 业务部署流程和引发的问题
- 开发环境→测试环境→预生产环境→生产环境
- 为了解决手工维护大量机器带来的成本问题,人们提出了自动化运维
- 将需要重复执行的操作写入到脚本中,再让每台机器执行脚本,就可以自动完成运维任务
- 但是脚本的编写及维护较为复杂,且需要管理员掌握脚本语言
- 后来诞生了自动化运维软件,可以使用命令或者图形界面批量配置和管理系统
- 随着 DevOps 的概念诞生,运维需要掌握一定的开发技能
2. Ansible 的架构
- playbook:需要执行的剧本
- inventory file:记录主机信息和分组
- managed node:被管理的节点
- control machine:管理节点
- Ansible 默认使用 SSH 登录被管理的节点,所以需要提前配置好 SSH 免密登录(密码或者密钥)
- Ansible 无需安装客户端,也不需要以 root 用户登录,使用 yaml 语法编写配置文件
- Ansible 使用 Python 开发,由 paramiko 模块发起和管理 SSH 连接;PyYAML 模块维护配置文件
3. Ansible 部署
- 准备三台虚拟机,第一台 master,剩余两台 slave1 和 slave2,都安装 CentOS,并配置 epel 软件源
- ansible_master: 192.168.50.12
- ansible_slave1: 192.168.50.15
- ansible_slave2: 192.168.50.14
- 管理节点(服务端)安装 ansible:
yum install ansible libselinux-python
查询 ansible 版本信息:
ansible --version
- 被管理节点(客户端)安装 libselinux-python
- 服务端修改 ansible 目录下的
hosts
文件,添加客户端节点的地址 - 执行命令之前需要先用 SSH 连接一次客户端节点,添加客户端节点的指纹信息
服务端远程执行hostname命令输出客户端主机名称:
ansible 客户端节点地址 -m command -a 'hostname' -k -u 执行命令的用户身份
参数说明:
-m:模块
-a:模块的参数
-k:连接前询问密码
-u:指定身份
4. SSH 免密登录
由于每次需要输入密码过于繁琐,我们可以考虑通过密钥对的方式设置免密登录,这样每次执行命令时就不需要输入客户端的密码了。
方式 1:在 ansible 目录下的 hosts 文件中填写密码,之后就不再需要指定验证方式和身份
方式 2:SSH 密钥验证,在管理节点创建 SSH 密钥(ssh-keygen -t rsa),之后通过公钥分发脚本将公钥分发给被管理节点:
#!/bin/bash ssh_pass=12345678 key_path=~/.ssh/id_rsa.pub for ip in 14 15 do sshpass -p$ssh_pass ssh-copy-id -i $key_path "-o StrictHostKeyChecking=no" 192.168.50.$ip done
之后给脚本添加可执行权限,再使用
sh + 脚本路径
命令执行脚本即可
5. Ansible 的管理模式
Ad-HOC:通过命令行实现管理,适用于少量的、简单的命令。可以通过
ansible-doc
命令查询支持的模块命令 用途 `ansible-doc -l grep ^command` 输出模块列表并从中过滤出 command 模块 ansible-doc -s command
查看 command 模块的具体说明 - Playbook:通过剧本批量执行复杂任务,类比脚本
6. 基本命令模块:command
语法:ansible 节点 -m command -a 命令和参数
常用参数 | 用途 |
---|---|
chdir | 执行命令之前切换到指定目录 |
creates | 执行命令之前判断文件/目录是否存在,如果存在就跳过该条命令 |
free_form | 参数中可以输入任何系统命令 |
removes | 执行命令之前判断文件/目录是否存在,如果不存在就跳过该条命令 |
warn | 是否提供警告信息,默认开启 |
注意事项:
- 不能使用 shell 变量,也不能使用特殊符号
案例:
- 获取所有被管理节点的负载信息:
ansible all -m command -a "uptime"
- 切换到 /tmp 目录并打印当前工作目录:
ansible all -m command -a "pwd chdir=/tmp"
- 列出当前目录信息,但是因为存在 /root 目录,该命令会被跳过:
ansible slave1 -m command -a "uname -a creates=/root"
7. 支持 shell 语法的模块:shell
常用参数 | 用途 |
---|---|
chdir | 执行命令之前切换到指定目录 |
creates | 执行命令之前判断文件/目录是否存在,如果存在就跳过该条命令 |
free_form | 参数中可以输入任何系统命令 |
removes | 执行命令之前判断文件/目录是否存在,如果不存在就跳过该条命令 |
案例:
- 获取被管理节点的某个进程信息:
ansible all -m shell -a "ps -ef | grep bash | grep -v grep"
8. 脚本模块:script
只需管理节点有一份脚本,就可以在所有被管理节点上执行
常用参数 | 用途 |
---|---|
chdir | 执行命令之前切换到指定目录 |
creates | 执行命令之前判断文件/目录是否存在,如果存在就跳过该条命令 |
removes | 执行命令之前判断文件/目录是否存在,如果不存在就跳过该条命令 |
9. 软件包管理模块:yum
常用参数 | 用途 |
---|---|
name | 指定需要管理的软件包名称 |
state | 状态,包含三种:present/installed=安装;latest=安装最新版本;absent/removed=卸载 |
disable_gpg_check | 禁用 rpm 包的公钥验证,如果 yum 源没有开启验证,则需要将其禁用 |
enablerepo | 临时指定 yum 源,注意需要是源的名称,而不是 repo 文件的名称 |
disablerepo | 临时禁用 yum 源 |
案例:
安装 nginx 软件包
ansible all -m yum -a "name=nginx"
安装 nginx 并临时关闭公钥验证
ansible all -m yum -a 'name=nginx state=installed disable_gpg_check=yes'
卸载 nginx 软件包
ansible all -m yum -a 'name=nginx state=removed'
批量安装软件包
ansible all -m yum -a "name=nginx,telnet state=installed"
10. 自动化执行命令:playbook
运维人员可以通过提前编写好的 yml 文件来远程执行命令,并充分利用 Ansible 提供的众多模块。通过设置触发器或定时任务,即可实现自动化执行命令。
基本命令:ansible-playbook yml配置文件路径
yml 配置文件基本语法如下,yml 通过缩进来表示层级关系,所以必须要有缩进,至于用几个空格就看个人喜好了。
- hosts: 需要执行命令的客户端或组 tasks: - name: 任务描述 模块名称: 参数1: 值 参数2: 值 vars: 变量组名称: - 变量值1 - 变量值2 - 变量值3
案例1:批量安装软件包
- hosts: all tasks: - name: install some packages yum: name: "{{packages}}" vars: packages: - nginx - telnet - httpd
案例2:更新所有软件包,并排除名称包含「kernel」和「foo」的软件包
- hosts: all tasks: - name: upgrade all packages, excluding kernel & foo related packages yum: name: '*' state: latest exclude: kernel*, foo*
七、监控平台 Zabbix
1. 监控体系:
- 硬件监控:通过专用的监控硬件来完成物理设备的监控工作,例如硬件温度、风扇转速、处理器频率、硬件故障、网络接口状态等
- 系统监控:CPU 频率和占用率、内存占用、硬盘 IO 和占用、系统负载、进程和线程数量、TCP连接数
- 服务监控:httpd、nginx、php-fpm、mysql、memcache、redis、tomcat 等
- 性能监控:网站性能、服务器性能、数据库性能、存储性能
- 日志监控:监控运行的软件是否产生了错误日志
- 安全监控:统计不同的攻击来源和攻击类型,监控用户登录、密码修改、文件改动
- 网络监控:端口、访问 URL、速率、网卡累计流量、各种协议的数据包(ICMP、SMTP 等)
2. 监控方式
- 基础设施监控(硬件监控):硬件温度、风扇转速、存储占用(
df
、fdisk
、iotop
)、CPU 占用(lscpu
、uptime
、top
、htop
、glances
)、内存占用(free
)、网络吞吐(iftop
) - 应用监控:mysql、redis、nginx、php-fpm、python 等
3. Zabbix 的特点
- 能自定义监控内容,通过脚本采集所需的数据
- 数据可以写入到数据库,便于日后分析
- 可以通过模板快速部署一组监控项
- 每个监控项都可以看到历史记录,且 Web UI 友好
- 有触发器机制,可以定义复杂的告警逻辑
- 提供了告警确认机制,告知运维人员故障是否有人处理
- 支持邮件、短信、微信等告警方式
- 触发告警后,可以远程执行系统命令,诸如自我修复、重启、采集数据等
- 有原生的绘图模块,便于二次开发
4. Zabbix 安装
客户端可以不安装软件,Zabbix 可以通过 TCP、ICMP 等协议与客户端建立连接并进行监控。
4.1 服务端部署
- 如果是 CentOS,需要先禁用 SELinux:修改 /etc/selinux/config 文件,将SELINUX=disabled,之后重启系统
- 安装 Zabbix 软件源:
rpm -Uvh https://mirrors.aliyun.com/zabbix/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm
- 替换软件源中的地址为阿里云镜像:
sed -i 's#http://repo.zabbix.com#https://mirrors.aliyun.com/zabbix#' /etc/yum.repos.d/zabbix.repo
- 启用 Zabbix 前端源:修改
zabbix.repo
文件,将 zabbix-frontend 的值设为 enabled - 清理并重建 yum 缓存:
yum clean all
和yum makecache
- 安装 SCL,便于在同一个系统中安装不同版本的软件,SCL 版本的软件会安装在
/etc/opt/rh
目录下:yum install centos-release-scl -y
- 安装 Zabbix 组件:
yum install zabbix-server-mysql zabbix-agent zabbix-web-mysql-scl zabbix-apache-conf-scl -y
- 安装 MariaDB:
yum install mariadb-server -y
- 令 MariaDB 开机自启并立即启动:
systemctl enable --now mariadb
- 设置 MariaDB 的 root 密码:
mysql_secure_installation
登录 MariaDB,创建 Zabbix 所需的用户和数据库:
create database zabbix character set utf8 collate utf8_bin;
create user zabbix@localhost identified by 'password';
将 zabbix 数据库授权给 zabbix 用户:
grant all privileges on zabbix.* to zabbix@localhost;
flush privileges;
- 导入 Zabbix 数据库信息:
zcat /usr/share/doc/zabbix-server-mysql*/create.sql.gz | mysql -uzabbix -p zabbix
- 修改 Zabbix 配置文件
/etc/zabbix/zabbix_server.conf
,找到「DBPassword」字段,修改数据库的密码 - 修改 PHP 配置文件
/etc/opt/rh/rh-php72/php-fpm.d/zabbix.conf
,将「date.timezone」的值改为「Asia/Shanghai」并将行首的分号去掉 - 启动 Zabbix:
systemctl enable --now zabbix-server zabbix-agent httpd rh-php72-php-fpm
防火墙开放对应的服务端口:
firewall-cmd --permanent --add-service="zabbix-agent"
firewall-cmd --permanent --add-service="zabbix-server"
firewall-cmd --permanent --add-service="http"
firewall-cmd --permanent --add-service="https"
firewall-cmd --reload
- 访问 Zabbix:服务器地址/zabbix,默认用户名
Admin
,密码zabbix
校准时间:
yum install ntpdate -y
ntpdate -u ntp.aliyun.com
修改时区:
mv /etc/localtime{,.bak}
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
4.2 客户端安装
- zabbix-agent2 基于 golang 开发,并发性能好,默认使用 10050 端口
禁用 SELinux:
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
可以结合前面学习的 Ansible 远程执行指令:
ansible all -m command -a "sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config"
开放防火墙端口:
firewall-cmd --permanent --add-service="zabbix-agent"
firewall-cmd --reload
校准时间:
yum install ntpdate -y
ntpdate -u ntp.aliyun.com
修改时区:
mv /etc/localtime{,.bak}
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
- 安装 Zabbix 软件源:
rpm -Uvh https://mirrors.aliyun.com/zabbix/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm
- 替换软件源中的地址为阿里云镜像:
sed -i 's#http://repo.zabbix.com#https://mirrors.aliyun.com/zabbix#' /etc/yum.repos.d/zabbix.repo
- 安装 zabbix-agent2 软件包:
yum install zabbix-agent2 -y
- 修改 zabbix-agent2 配置文件
/etc/zabbix/zabbix_agent2.conf
,将「Server」字段和「ServerActive」的值改为服务器的地址。之后将「Hostname」的值改为空,并将下面「HostnameItem=system.hostname」这个字段的注释符去掉,让 Zabbix 直接读取本机名称。 - 启动 zabbix-agent2:
systemctl enable --now zabbix-agent2
服务端验证连通性:
yum install zabbix-get -y
zabbix_get -s '客户端 IP 地址' -p 10050 -k 'agent.ping'
4.3 服务端添加客户端
- 在服务端的「配置」---「主机」中点击「创建主机」
- 输入主机名称、群组、IP 地址、端口,然后点击「模板」,搜索「Linux」,选中「Template OS Linux by Zabbix agent」或其它可用的模板
- 添加模板后添加主机即可
5. 修复 Web UI 图表乱码问题
- 安装一个中文字体:
yum install wqy-microhei-fonts -y
- 覆盖原有的字体文件:
cp /usr/share/fonts/wqy-microhei/wqy-microhei.ttc /usr/share/fonts/dejavu/DejaVuSans.ttf
八、Docker 容器引擎
在生产环境中,部署应用,尤其是 Web 应用,一直是一件让运维头疼的事情,其中运行环境的差异是最大的难点。以 Docker 为代表的容器技术应运而生,容器中只包含了最小根文件系统、运行环境和应用,消除了不同物理机上运行环境的差异,运维人员不需要关心宿主机运行的是 Debian 还是 CentOS,只要能安装 Docker 这个容器引擎就行。而且 Docker 可以轻松构建属于自己的镜像,还可以利用 Docker Compose 实现一个脚本部署多个容器,大大提高了部署效率。
本章目标:搭建一个 Django + uWSGI + MySQL + Nginx 的 Web 应用。
目标拆解:使用三个 Docker 容器分别安装这些应用,其中:
- db 容器:安装 MySQL 8,用于存储网站数据
- web 容器:安装 Django + uWSGI,Django 是基于 Python 的 Web 应用框架,uWSGI 用于处理动态请求,并将其转发给 Django 应用处理
- nginx 容器:安装 Nginx,搭建反向代理并处理静态请求
写好每个容器的 Dockerfile 之后,编写 Docker Compose 配置文件,让 Docker Compose 一次性部署这三个容器并启动。
整个项目的框架图:
先从最简单的开始——安装 Docker 容器引擎,创建一个容器,部署一个最小的 Django 项目。
一、安装 Docker
不同的操作系统,安装 Docker 的方式也会有差异,所以具体的安装步骤建议直接参考 Docker 官网的文档,安装 Docker 引擎和 Docker Compose。
安装文档地址: https://docs.docker.com/engine/install/
二、部署一个 Django 项目
- 安装好 Python,用
pip install django
命令安装 Django 模块。 - 切换到一个新的目录,运行
django-admin startproject 项目名
命令,就会在当前目录创建一个 以项目名为名称的目录,这个就是 Django 项目所在的目录,里面有一个 manage.py 文件和一个与项目名一致的文件夹。后续的步骤需要切换到 manage.py 所在的目录下。 修改
<项目名>/settings.py
,把ALLOWED_HOST
的值修改为宿主机的 IP 地址。为什么是宿主机的地址?当用户访问 Web 应用时,用户输入的是宿主机的地址,之后通过 Docker 提供的端口映射功能,把请求转发到容器中的特定端口,经过转发之后,来源 IP 地址就变为了宿主机的地址,所以只需要允许宿主机地址访问 Web 应用即可。ALLOWED_HOSTS = ["192.168.216.132"]
新建一个文件名为
requirements.txt
,用来告知 pip 需要安装什么模块。该文件的内容只有一行:Django==4.2
新建一个文件名为
pip.conf
,用于设置 pip 的镜像源,提高模块下载速度。[global] index-url = https://mirrors.aliyun.com/pypi/simple/ [install] trusted-host=mirrors.aliyun.com
新建 dockerfile,向 Docker 引擎描述如何创建镜像。这里的基础镜像选择基于 alpine 构建的 Python 环境,占用体积会大幅减少。内容如下:
# 基础镜像 FROM python:3.9-alpine3.16 # 镜像作者 MAINTAINER Yulin Wei # 设置环境变量,防止 Python 缓冲输出 ENV PYTHONUNBUFFERED 1 # 设置 pip 镜像源 COPY pip.conf /root/.pip/pip.conf # 在容器内部创建目录 RUN mkdir -p /var/www/html/mysite1 # 切换工作目录 WORKDIR /var/www/html/mysite1 # 将当前目录文件复制到容器的工作目录 ADD . /var/www/html/mysite1 # 使用 pip 安装依赖模块 RUN pip install -r requirements.txt # 运行迁移命令 RUN python3 manage.py migrate # 设置容器启动时执行的命令 ENTRYPOINT ["python3", "manage.py"] CMD ["runserver", "0.0.0.0:8000"]
根据描述文件生成镜像
docker build -t django_image:v1 .
生成镜像之后,可以查看镜像列表:
docker images
显示如下结果就说明镜像创建成功了。如果对镜像不满意,可以使用
docker rmi <IMAGE ID>
命令来删除镜像。REPOSITORY TAG IMAGE ID CREATED SIZE django_image v1 08dee7494f35 5 seconds ago 88.7MB
使用镜像生成容器并运行,其中会设定一些参数:
- -it表示可交互
- -d表示后台运行
- --name指定容器名称
- -p设定端口映射,把容器的 8000 端口映射到宿主机的 80 端口
# 生成容器并运行 docker run -it -d --name testsite -p 80:8000 django_image:v1 9bd79deff7d63258cbee28a0f58c62331b6600fb4fc97e03a76bbffe7c0ad734 # 查看运行中的容器 docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9bd79deff7d6 django_image:v1 "python3" 4 seconds ago Up 2 seconds 0.0.0.0:80->8000/tcp, :::80->8000/tcp testsite
可以使用命令进入到容器中,执行其他的命令:
# 进入容器 docker exec -it testsite /bin/sh /var/www/html/mysite1 #
- 打开浏览器,输入宿主机的 IP 地址,就可以看到 Django 的欢迎页面了。
三、向镜像中添加 uWSGI
在上一个镜像中,为了方便测试,使用了 Django 自带的脚本来启动 Web 应用,但是在生产环境中这是不可取的。我们需要使用安全性和并发性能都更好的其它服务器软件来启动 Web 应用。
uWSGI 是一个 Web 服务器和应用服务器,可以将 Python 应用程序部署到生产环境中。它可以作为独立服务器或与 Nginx、Apache 等 Web 服务器配合使用。
uWSGI 可以与各种 Web 框架和应用程序一起使用,例如 Django、Flask、Pyramid 等。它支持多种协议和接口,例如 WSGI、HTTP、FastCGI 等,并提供高性能和可扩展性。
uWSGI 还具有许多有用的功能,例如自动进程管理、负载均衡、缓存、限流、监控等。它可以通过配置文件进行配置,并提供了丰富的命令行选项和 API 接口。
这一步我们需要构建一个新的镜像,准备一个新的 Django 项目,进入 manage.py 文件所在的目录。记得修改<项目名>/settings.py
,把ALLOWED_HOST
的值修改为宿主机的 IP 地址。
把上一个镜像中的 Dockerfile、pip.conf、requirements.txt 复制过来,稍作修改就可以使用了。
修改 requirements.txt 文件
- 新增一行:
uwsgi >= 2.0.18
- 新增一行:
新建一个脚本文件,名为
start.sh
并赋予可执行权限,文件内容如下:#!/bin/sh # 迁移模型,将模型写入数据库 python3 manage.py makemigrations python3 manage.py migrate # 启动服务 uwsgi --ini uwsgi.ini # 阻塞进程,防止容器自动退出 tail -f /dev/null
新建一个文本文件,名为
uwsgi.ini
,存放 uWSGI 的配置信息:[uwsgi] project=mysite2 base=/var/www/html chdir=%(base)/%(project) module=%(project).wsgi:application master=True processes=2 #这里直接使用uwsgi做web服务器,使用http协议。如果使用nginx,需要借助socket做 uwsgi 与 nginx 之间的通信。 http=0.0.0.0:8000 buffer-size=65536 pidfile=/tmp/%(project)-master.pid vacuum=True max-requests=5000 daemonize=/tmp/%(project)-uwsgi.log #设置一个请求的超时时间(秒),如果一个请求超过了这个时间,则请求被丢弃 harakiri=60 #当一个请求被harakiri丢弃时,会输出一条日志 harakiri-verbose=true
修改 Dockerfile,仅在有变动的地方做了注释。
FROM python:3.9-alpine3.16 MAINTAINER Yulin Wei ENV PYTHONUNBUFFERED 1 COPY pip.conf /root/.pip/pip.conf RUN mkdir -p /var/www/html/mysite2 WORKDIR /var/www/html/mysite2 COPY . /var/www/html/mysite2 # 设置 apk 镜像源 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories # 安装编译 uwsgi 所需的软件包 RUN apk add --no-cache build-base musl-dev linux-headers RUN pip install -r requirements.txt # 给脚本文件设置可执行权限 RUN chmod +x ./start.sh # 设置启动命令 ENTRYPOINT ["sh", "start.sh"]
- 构建镜像,
--no-cache
参数可以避免使用之前缓存的镜像层,导致镜像中没有集成最新的文件:docker build --no-cache -t django_image:v2 .
启动容器,记得让之前创建的容器停止运行。
- 停止容器:
docker stop <容器 ID>
- 启动容器:
docker run -it -d --name testsite2 -p 80:8000 django_image:v2
- 停止容器:
- 浏览器访问宿主机地址,就可以看到 Django 的欢迎页面了。
四、双容器部署
在生产环境中,往往不会让一个容器“单打独斗”,而是利用 SaaS 的思想,把一个应用部署在一个容器中,容器之间相互通信,打通前后端,实现 Web 应用的各项功能。
整个项目的文件布局如图所示:
mysite3 # web 容器
├── db.sqlite3
├── dockerfile
├── manage.py # django 项目管理脚本
├── media # 存放媒体文件的目录
├── mysite3 # 存放 django 项目文件的目录
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── pip.conf # pip 镜像源配置文件
├── requirements.txt # pip 模块描述文件
├── start.sh # 启动项目的脚本
├── static # 存放静态文件(css 和 js)的目录
└── uwsgi.ini # 配置 web 服务器和 web 应用的接口
nginx # nginx 容器
├── dockerfile
├── log
└── nginx.conf # nginx 配置文件
该项目的难点:
- 创建两个容器,分别部署不同的服务,容器间通过 Docker 的虚拟局域网通信
- 容器启动的先后顺序,先启动 Django 容器,再启动 Nginx 容器,否则 Nginx 收到动态请求之后不知道转发给谁
- 由于需要让 Nginx 处理静态请求,需要预先设置好静态文件的路径
接下来开始部署。
- 创建 Django 项目,进入 manage.py 所在的目录下,创建
static
和media
两个目录,用于存放静态文件。 - 把上一个项目的 pip.conf、Dockerfile、requirements.txt、start.sh 和 uwsgi.ini 拷贝到当前目录。
修改
<项目名/settings.py>
,设置ALLOWED_HOSTS
为宿主机 IP 地址,并添加静态文件路径:import os STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_URL = "/static/" MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = "/media/"
修改 Dockerfile,这次不需要复制整个项目到镜像中了,因为运行的时候会直接把宿主机中的项目目录挂载到容器中。
FROM python:3.9-alpine3.16 MAINTAINER Yulin Wei ENV PYTHONUNBUFFERED 1 COPY pip.conf /root/.pip/pip.conf RUN mkdir -p /var/www/html/mysite3 WORKDIR /var/www/html/mysite3 # 只复制安装模块所需的描述文件 COPY requirements.txt /var/www/html/mysite3 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories RUN apk add --no-cache build-base musl-dev linux-headers RUN pip install -r requirements.txt ENTRYPOINT ["sh", "start.sh"]
修改 start.sh ,在开头添加一行:
python manage.py collectstatic --noinput
修改 uwsgi.ini,更新项目路径,设置 socket 套接字通信。将 http 开头的一整行替换为:
socket=0.0.0.0:8000
构建 Django 镜像并启动,容器名为“testsite3”,这里要注意使用
-v
参数挂载网站目录到 manage.py 所在的目录下,这样就可以直接用宿主机本地的目录存放网站数据,不用担心容器被删除后网站数据会丢失。docker build --no-cache -t django_image:v3 . docker run -it -d --name testsite3 -p 8000:8000 -v /root/Downloads/mysite3:/var/www/html/mysite3 django_image:v3
- 可以使用
docker exec -it testsite3 cat /tmp/mysite3-uwsgi.log
查看日志,检查 uWSGI 是否正常运行。 - 之后执行
docker inspect testsite3 | grep "IPAddress"
命令查看容器的 IP 地址,这里查看到 IP 地址是 172.17.0.2。
- 可以使用
在另一个目录创建 Nginx 的镜像,Dockerfile 内容如下:
# nginx镜像 FROM nginx:latest # 删除原有配置文件,创建静态资源文件夹和ssl证书保存文件夹 RUN rm /etc/nginx/conf.d/default.conf \ && mkdir -p /usr/share/nginx/html/static \ && mkdir -p /usr/share/nginx/html/media \ && mkdir -p /usr/share/nginx/ssl # 添加配置文件 ADD ./nginx.conf /etc/nginx/conf.d/ # 关闭守护模式 CMD ["nginx", "-g", "daemon off;"]
添加 nginx.conf 文件,内容如下:
# nginx配置文件 upstream django { ip_hash; server 172.17.0.2:8000; # Django容器所在IP地址及开放端口,非宿主机外网IP } server { listen 80; # 监听80端口 server_name localhost; # 可以是nginx容器所在ip地址或127.0.0.1,不能写宿主机外网ip地址 location /static { alias /usr/share/nginx/html/static; # 静态资源路径 } location /media { alias /usr/share/nginx/html/media; # 媒体资源,用户上传文件路径 } location / { include /etc/nginx/uwsgi_params; uwsgi_pass django; uwsgi_read_timeout 600; uwsgi_connect_timeout 600; uwsgi_send_timeout 600; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header Host $http_host; # proxy_redirect off; # proxy_set_header X-Real-IP $remote_addr; # proxy_pass http://django; } } access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn;
构建 Nginx 镜像并启动容器。
docker build --no-cache -t mynginx:v1 docker run -it -d -p 80:80 --name testsite3-nginx \ -v /root/Downloads/mysite3/static:/usr/share/nginx/html/static \ -v /root/Downloads/mysite3/media:/usr/share/nginx/html/media \ -v /root/Downloads/nginx/var/log/nginx \ mynginx:v1
- 打开浏览器,输入宿主机 IP 地址,可以访问欢迎页面。
在这一部分,我们手动构建了两个容器,一个容器安装了 Django+uWSGI,另一个容器安装了 Nginx,并且成功部署了一个基本的 Django 项目。然而在实际的生产环境中,往往需要定义数量庞大的容器,且容器之间存在依赖关系,一个个手动创建容器不仅效率低还容易出错,所以需要一种可以定义和部署容器集群的工具,而 Docker 提供了 Docker Compose 来实现这个功能。接下来就会利用这个工具来构建三个容器组成的集群,并部署一个 Django+uWSGI+Nginx+MySQL 的Web 应用项目。
五、初探容器集群
Docker Compose 是一个用来定义和运行复杂容器的工具,它可以创建、启动多个容器,并实现容器的管理功能。
在这一部分,我们将使用 Docker Compose 编排并启动 3 个容器,这三个容器分别是:
- web 容器,部署 Django+uWSGI,开放 8000 端口
- db 容器,部署 MySQL,开放 3306 端口
- nginx 容器,部署 Nginx,开放 80 和 443 端口
在前面的例子中,可以通过给容器起名的方式来标识容器,这样就不需要根据容器 ID 或者 IP 地址来区分容器。
这三个容器存在的依赖关系:web 容器依赖于 db 容器,nginx 容器依赖于 web 容器。
所有文件的布局如下:
mysite4_root/
├── compose
│ ├── mysql
│ │ ├── conf
│ │ │ └── my.cnf
│ │ ├── init
│ │ │ └── init.sql
│ │ └── my.cnf
│ ├── nginx
│ │ ├── dockerfile
│ │ ├── log
│ │ │ ├── access.log
│ │ │ └── error.log
│ │ ├── nginx.conf
│ │ └── ssl
│ └── uwsgi
│ └── mysite4-uwsgi.log
├── docker-compose.yml
└── mysite4
├── dockerfile
├── manage.py
├── media
├── mysite4
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── pip.conf
├── requirements.txt
├── start.sh
├── static
└── uwsgi.ini
下面开始部署。
- 创建 Django 项目。进入一个空目录,本例中该目录名为
mysite4_root
,执行django-admin startproject <name>
命令创建一个新的 Django 项目,在本例中,项目名称为mysite4
。 创建
docker-compose.yml
文件,用于描述各个容器的配置信息,例如挂载的目录、环境变量、开放的端口、是否自动重启等等,这样就不需要用冗长的命令去创建容器了。注意,yaml 格式的文件对缩进是敏感的,一定要注意各个层级的缩进,每级缩进的空格的数量也要统一。version: "3" volumes: # 自定义数据卷,位于宿主机/var/lib/docker/volumes mysite4_db_vol: # 定义数据卷同步容器内mysql数据 mysite4_media_vol: # 定义数据卷同步media文件夹数据 services: db: # 服务名称 image: mysql:8.0 # 容器使用的镜像 environment: # 环境变量 - MYSQL_ROOT_PASSWORD=123456 # 数据库密码 - MYSQL_DATABASE=mysite4 # 数据库名称 - MYSQL_USER=dbuser # 数据库用户名 - MYSQL_PASSWORD=password # 用户密码 volumes: - mysite4_db_vol:/var/lib/mysql:rw # 挂载数据库目录, 可读可写 - ./compose/mysql/my.cnf:/etc/mysql/my.cnf # 挂载配置文件 - ./compose/mysql/init:/docker-entrypoint-initdb.d/ # 挂载初始化sql脚本 ports: - "3306:3306" # 开放端口 restart: always # 故障时自动重启容器 web: build: ./mysite4 # 使用mysite4目录下的Dockerfile构建容器 expose: - "8000" volumes: - ./mysite4:/var/www/html/mysite4 # 挂载项目目录 - mysite4_media_vol:/var/www/html/myproject/media # 以数据卷挂载媒体文件 - ./compose/uwsgi:/tmp # 挂载uwsgi日志 links: # 与容器建立关联 - db depends_on: # 建立依赖关系,指定容器启动后才能启动该容器 - db restart: always tty: true stdin_open: true nginx: build: ./compose/nginx ports: - "80:80" - "443:443" expose: - "80" volumes: - ./mysite4/static:/usr/share/nginx/html/static # 挂载静态文件 - ./compose/nginx/ssl:/usr/share/nginx/ssl # 挂载ssl证书目录 - ./compose/nginx/log:/var/log/nginx # 挂载日志 - mysite4_media_vol:/usr/share/nginx/html/media # 挂载媒体文件 links: - web depends_on: - web restart: always
准备 web 容器所需的文件。进入
mysite4
目录,创建 static 和 media 目录,编写dockerfile
、pip.conf
、requirements.txt
、start.sh
和uwsgi.ini
,可以沿用之前编写的文件,进行一些修改即可。- dockerfile:
FROM python:3.9-alpine3.16 MAINTAINER Yulin Wei ENV PYTHONUNBUFFERED 1 COPY pip.conf /root/.pip/pip.conf RUN mkdir -p /var/www/html/mysite4 WORKDIR /var/www/html/mysite4 COPY requirements.txt /var/www/html/mysite4 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories RUN apk add --no-cache build-base musl-dev linux-headers mariadb-dev RUN pip install -r requirements.txt CMD ["sh", "start.sh"]
- requirements.txt
Django == 4.2 uwsgi >= 2.0.18 mysqlclient >= 1.4.6
- uwsgi.ini
[uwsgi] project=mysite4 base=/var/www/html chdir=%(base)/%(project) module=%(project).wsgi:application master=True processes=2 socket=0.0.0.0:8000 buffer-size=65536 pidfile=/tmp/%(project)-master.pid vacuum=True max-requests=5000 daemonize=/tmp/%(project)-uwsgi.log #设置一个请求的超时时间(秒),如果一个请求超过了这个时间,则请求被丢弃 harakiri=60 #当一个请求被harakiri杀掉会,会输出一条日志 harakiri-verbose=true memory-report = true # 当占用了一定内存后,自动回收内存 reload-on-as = 1024 python-autoreload = 1
回到
mysite_root
目录,创建一个新的目录名为compose
,这里面放置的是 nginx 和 db 容器所需的文件。在 compose 目录下创建 nginx 和 mysql 文件夹。之后进入 nginx 文件夹,准备 nginx 容器所需的文件。- dockerfile:
# nginx镜像 FROM nginx:latest # 删除原有配置文件,创建静态资源文件夹和ssl证书保存文件夹 RUN rm /etc/nginx/conf.d/default.conf \ && mkdir -p /usr/share/nginx/html/static \ && mkdir -p /usr/share/nginx/html/media \ && mkdir -p /usr/share/nginx/ssl # 添加配置文件 ADD ./nginx.conf /etc/nginx/conf.d/ # 设置media文件夹所有权和读写权限 RUN chown -R www-data:www-data /usr/share/nginx/html/media RUN chmod -R 775 /usr/share/nginx/html/media # 启动nginx CMD ["nginx", "-g", "daemon off;"]
- nginx.conf
# nginx配置文件 upstream django { ip_hash; server web:8000; # Django容器主机名及开放端口,非宿主机外网IP } server { listen 80; # 监听80端口 server_name localhost; # 可以是nginx容器所在ip地址或127.0.0.1,不能写宿主机外网ip地址 location /static { alias /usr/share/nginx/html/static; # 静态资源路径 } location /media { alias /usr/share/nginx/html/media; # 媒体资源,用户上传文件路径 } location / { include /etc/nginx/uwsgi_params; uwsgi_pass django; uwsgi_read_timeout 600; uwsgi_connect_timeout 600; uwsgi_send_timeout 600; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header Host $http_host; # proxy_redirect off; # proxy_set_header X-Real-IP $remote_addr; # proxy_pass http://django; } } access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn;
进入 mysql 文件夹,准备 db 容器所需的文件。在里面创建一个 init 文件夹,用于放置初始化脚本。
- my.cnf:
[mysqld] user=mysql default-storage-engine=INNODB character-set-server=utf8 port = 3306 # 端口与docker-compose里映射端口保持一致 #bind-address= localhost #一定要注释掉,mysql所在容器和django所在容器不是同一个 IP basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock skip-name-resolve # 这个参数是禁止域名解析的,远程访问推荐开启skip_name_resolve。 [client] port = 3306 default-character-set=utf8 [mysql] no-auto-rehash default-character-set=utf8
- init 文件夹里的 init.sql:
GRANT ALL PRIVILEGES ON myproject.* TO dbuser@"%" IDENTIFIED BY "password"; FLUSH PRIVILEGES;
修改 Django 项目的配置文件
settings.py
,这里仅列出需要修改的部分:# 允许访问服务器的名单 ALLOWED_HOSTS = ["宿主机 IP 地址"] # 数据库配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mysite4', # 数据库名 'USER':'dbuser', # 你设置的用户名 - 非root用户 'PASSWORD':'password', # # 换成你自己密码 'HOST': 'db', # 注意:这里使用 db 容器的名称,docker会自动解析成ip 'PORT':'3306', # 端口 } } # 静态文件路径 import os STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_URL = "/static/" MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = "/media/"
使用 Docker Compose 构建镜像和启动容器。
- 构建镜像:
docker compose build
- 查看镜像:
docker images
- 创建容器并在后台运行:
docker compose up -d
- 查看运行中的容器列表:
docker ps
- 停止容器:
docker compose stop
,可在后面添加docker-compose.yml
中定义的服务名称来指定要停止的容器 - 启动已停止的容器:
docker compose start
- 停止并删除所有容器:
docker compose down
- 删除所有容器和关联镜像:
docker-compose down --rmi all
- 容器启动后,可以用浏览器访问宿主机的 IP 地址,检查是否能打开页面。
- 构建镜像:
如果服务出现异常,可以在以下位置查看日志文件:
- uWSGI 的日志可以在宿主机
mysite4_root/compose/uwsgi
中查看到; - Nginx 的日志可以在宿主机
mysite4_root/compose/nginx/log
中查看到; - MySQL 的数据存放在宿主机的
/var/lib/docker/volumes/mysite4_root_mysite4_db_vol
目录,即使容器被删除,数据也会一直保留,维护人员也可以对数据进行备份。
- uWSGI 的日志可以在宿主机
六、改进容器集群
在前面创建了一个由三个容器组成的容器集群,但是其中还存在一些问题:
- MySQL 的配置信息写在 Docker Compose 的配置文件中
- Nginx 的配置文件没有挂载到宿主机
- 容器间通信使用的是旧的 links 方式
- 启动 Django 应用时没有异常处理
下面着重描写需要修改的地方,项目的布局与上一个项目是一致的。
容器间通信
在上一个项目中,容器间通信使用的是 links 的方式实现,这种方式 docker 官方已经不推荐使用,所以要用桥接网络的形式来实现容器间的网络连接。- 修改
docker-compose.yml
:
version: "3" volumes: # 自定义数据卷,位于宿主机/var/lib/docker/volumes mysite4_db_vol: # 定义数据卷同步容器内mysql数据 mysite4_media_vol: # 定义数据卷同步media文件夹数据 # 设置虚拟网络,分配给各个容器,分配了同一个虚拟网络的容器就可以互通了 networks: nginx_network: driver: bridge db_network: driver: bridge services: db: # 前面的部分省略 # 去掉 links 部分,绑定虚拟网络 networks: - db_network restart: always # 故障时自动重启容器 web: # 前面的部分省略 # 去掉 links 部分,绑定虚拟网络 networks: - db_network - nginx_network depends_on: # 建立依赖关系,指定容器启动后才能启动该容器 - db restart: always tty: true stdin_open: true nginx: # 前面的部分省略 # 去掉 links 部分,绑定虚拟网络 networks: - nginx_network depends_on: - web restart: always
- 修改
修改配置文件
有些配置信息,例如用户名、密码等,可以用单独的文件来存储,构建镜像的时候再从文件中读取,这样在部署不同的项目时,只需要修改这些配置信息就可以了。下文以 db 容器的配置信息来举例。- 修改
docker-compose.yml
:
services: db: image: mysql:8.0 # 从文件中读取环境变量 env_file: - ./compose/mysql/.env
- 在
mysite4_root/compose/mysql
目录下创建.env
文件,写入数据库的配置信息:
MYSQL_ROOT_PASSWORD=123456 MYSQL_USER=dbuser MYSQL_DATABASE=mysite4 MYSQL_PASSWORD=password
- 修改 web 容器的 dockerfile:
FROM python:3.9-alpine3.16 MAINTAINER Yulin Wei ENV PYTHONUNBUFFERED 1 COPY pip.conf /root/.pip/pip.conf # 为应用根目录设置环境变量 ENV APP_HOME=/var/www/html/mysite4 RUN mkdir -p $APP_HOME WORKDIR $APP_HOME COPY requirements.txt $APP_HOME RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories RUN apk add --no-cache build-base musl-dev linux-headers mariadb-dev netcat-openbsd # 安装 netcat 软件包 RUN pip install -r requirements.txt ENTRYPOINT /bin/sh ./start.sh
- 修改
修改启动脚本
web 容器内安装的应用是通过start.sh
这个脚本文件来启动的,如果在这个脚本执行的时候,db 容器内的数据库尚未完成启动,就会抛出连接数据库失败的错误信息,所以在脚本文件中添加一个循环,当连接不上数据库时,等待三秒钟再尝试连接。- 修改
start.sh
:
# 尝试连接数据库,连接不上时等待 3 秒再执行 while ! nc -z db 3306 ; do echo "Waiting for the MySQL Server" sleep 3 done python3 manage.py collectstatic --noinput python3 manage.py makemigrations python3 manage.py migrate uwsgi --ini uwsgi.ini # 无限循环,防止脚本执行完成后 uWSGI 随之退出 tail -f /dev/null
- 修改
挂载宿主机上的 Nginx 配置文件到容器中,允许在容器运行时动态修改配置文件。
- 修改
docker-compose.yml
:
# 前面的内容省略 nginx: build: ./compose/nginx ports: - "80:80" - "443:443" expose: - "80" volumes: - ./mysite4/static:/usr/share/nginx/html/static # 挂载静态文件 - ./compose/nginx/ssl:/usr/share/nginx/ssl # 挂载ssl证书目录 - ./compose/nginx/log:/var/log/nginx # 挂载日志 - mysite4_media_vol:/usr/share/nginx/html/media # 挂载媒体文件 - ./compose/nginx/nginx.conf:/etc/nginx/conf.d/nginx.conf # 挂载配置文件 # 后面的内容省略
- 修改
compose/nginx/dockerfile
,将复制配置文件的部分删除。
- 修改
构建镜像和启动容器,使用浏览器查看是否能访问页面
- 构建镜像:
docker compose build
- 查看镜像:
docker images
- 创建容器并在后台运行:
docker compose up -d
- 查看运行中的容器列表:
docker ps
- 构建镜像:
进入容器,创建管理员用户,检查数据库相关功能是否正常
- 执行
docker compose exec web /bin/sh
命令进入 web 容器 - 进入容器后,执行
python3 manage.py createsuperuser
,根据提示输入用户名、邮箱和密码 - 创建用户后,使用浏览器输入
宿主机 IP 地址/admin
进入管理页面,查看是否能用刚才创建的用户登录
- 执行
参考资料来源:
运维工程师视频教程
Docker 部署 Django 教程