Linux运维工程师应该知道的20条Linux 命令

未分类

在这个全新的工具和多样化的开发环境井喷的大环境下,任何开发者和工程师都有必要学习一些基本的系统管理命令。特定的命令和工具包可帮助开发者组织、排查故障并优化他们的应用程序,而且当出现错误时,也可以为运维人员和系统管理员提供有价值的分类信息。

无论你是新手开发者还是希望管理自己的应用程序,下面 20 条基本的系统管理命令都可以帮助您更好地了解您的应用程序。它们还可以帮助解决为什么应用程序可在本地正常工作但不能在远程主机上工作这类的系统故障。这些命令适用于 Linux 开发环境、容器和虚拟机。

1. curl

curl 用于传输一个 URL。可以使用这条命令用于测试应用程序的端点或与上游服务端点的连接。curl 还可用于检查你的应用程序是否能连接到其他服务,例如数据库,或检查您的服务是否处于健康的状态。

举个例子,假如你的应用程序抛出一个 HTTP 500 错误,表示无法访问 MongoDB 数据库:

$ curl -I -s myapplication:5000

HTTP/1.0 500 INTERNAL SERVER ERROR

-I 选项用于显示头信息, -s 选项表示使用静默模式,不显示错误和进度。检查数据库的端点是否正确:

$ curl -I -s database:27017

HTTP/1.0 200 OK

那么可能是什么问题呢? 检查您的应用程序是否可以访问数据库以外的其他位置:

$ curl -I -s https://opensource.com

HTTP/1.1 200 OK

看起来这没问题,现在尝试访问数据库。您的应用程序正在使用数据库的主机名,因此请先尝试:

$ curl database:27017

curl: (6) Couldn't resolve host 'database'

这表示您的应用程序无法解析数据库,因为数据库的 URL 不可用或主机(容器或VM)没有可用于解析主机名的域名服务器。

2. python -m json.tool / jq

使用 curl 后,API 调用的输出可读性可能较差。有时候,你希望将生成的 JSON 数据格式化输出以查找特定的条目。Python 有一个内置的库可帮助您实现这个需求。可以使用 python -m json.tool 来缩进和组织 JSON。要使用 Python 的 JSON 模块,需要使用管道机制,将 JSON 文件的输出作为输入,写入到 python -m json.tool 命令行。

$ cat test.json
{"title":"Person","type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"age":{"description":"Age in years","type":"integer","minimum":0}},"required":["firstName","lastName"]}

要使用 Python 库,使用 -m (module) 选项将输出内容和 Python 库组合成管道。

$ cat test.json | python -m json.tool
{
    "properties": {
        "age": {
            "description": "Age in years",
            "minimum": 0,
            "type": "integer"
        },
        "firstName": {
            "type": "string"
        },
        "lastName": {
            "type": "string"
        }
    },
    "required": [
        "firstName",
        "lastName"
    ],
    "title": "Person",
    "type": "object"
}

对于更高级的 JSON 解析,可以安装 jq 。j q 提供了一些从 JSON 输入中提取特定值的选项。要像上面的 Python 模块一样将 JSON 输出格式化,只需将 jq 应用到输出即可。

$ cat test.json | jq
{
  "title": "Person",
  "type": "object",
  "properties": {
    "firstName": {
      "type": "string"
    },
    "lastName": {
      "type": "string"
    },
    "age": {
      "description": "Age in years",
      "type": "integer",
      "minimum": 0
    }
  },
  "required": [
    "firstName",
    "lastName"
  ]
}

3. ls

ls 用于列出目录中的文件,系统管理员和开发者会经常使用这个命令。在容器空间中,这条命令可以帮助确定容器镜像中的目录和文件。除了查找文件, ls 还可以用于检查权限。下面的示例中,由于权限问题,你不能运行 myapp。当你使用 ls -l 检查权限时,你会发现它的权限在 -rw-r–r– 中没有”x”,只有读写的权限。

$ ./myapp
bash: ./myapp: Permission denied
$ ls -l myapp
-rw-r--r--. 1 root root 33 Jul 21 18:36 myapp

4. tail

tail 显示文件的最后一部分内容。通常情况下,你不需要浏览每行日志以进行故障排除。而是需要检查日志中对应用程序的最新请求的说明。例如,当你向 Apache HTTP 服务器发起请求时,可以使用 tail 来检查日志中发生的情况。

未分类

使用 tail -f 来跟踪日志文件并在发起请求时查看它们。

-f选项表示跟随的意思,它可在日志被写入文件时输出它们。下面的示例具有每隔几秒访问端点的后台脚本,日志会记录请求。除了实时跟踪日志,还可以使用 tail 带上 -n 选项来查看文件的最后 100 行。

$ tail -n 100 /var/log/httpd/access_log

5. cat

cat主要用于查看文件内容和合并文件。你可能会使用 cat 来检查依赖项文件的内容,或确认已在本地构建的应用程序的版本。

$ cat requirements.txt
flask
flask_pymongo

上面的示例检查您的 Python Flask 应用程序是否已将 Flask 列为依赖项。

6. grep

grep 能使用特定模式匹配(包括正则表达式)搜索文本。如果你在另一条命令的输出中寻找特定的模式, grep 会高亮显示相关的行。可使用这条命令来搜索日志文件以及特定的进程等。如果想查看 Apache Tomcat 是否启动,你可能会命令行的数量给淹没。但讲输出的内容和 grep 命令组合成管道,可以将表示服务器已启动的行独立出来。

$ cat tomcat.log | grep org.apache.catalina.startup.Catalina.start
01-Jul-2017 18:03:47.542 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 681 ms

7. ps

ps 用于查看进程的各种状态信息。使用该命令可确定正在运行的应用程序或确认预期的进程。例如,如果要检查正在运行的 Tomcat Web 服务器,可使用带有选项的 ps 来获取 Tomcat 的进程 ID。

$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  2 18:55 ?        00:00:02 /docker-java-home/jre/bi
root        59     0  0 18:55 pts/0    00:00:00 /bin/sh
root        75    59  0 18:57 pts/0    00:00:00 ps -ef

为了更好的易读性,可使用 grep 和 ps 组合成管道。

$ ps -ef | grep tomcat
root         1     0  1 18:55 ?        00:00:02 /docker-java-home/jre/bi

8. env

env 用于列出所有环境变量及为其赋值。在故障排除期间,你可能会发现需要检查是否有错误的环境变量来阻止应用程序启动。在下面的示例中,该命令用于检查程序主机上设置的环境变量。

$ env
PYTHON_PIP_VERSION=9.0.1
HOME=/root
DB_NAME=test
PATH=/usr/local/bin:/usr/local/sbin
LANG=C.UTF-8
PYTHON_VERSION=3.4.6
PWD=/
DB_URI=mongodb://database:27017/test

请注意,该应用程序正在使用 Python 3,并具有连接到 MongoDB 数据库的环境变量。

9. top

top 用于显示系统中各个进程的信息和资源占用状况,类似于 Windows 的任务管理器。使用该命令可确定哪些进程正在运行,以及它们消耗了多少的内存和 CPU。一种常见的情况是当你运行一个应用程序时,它在一分钟后挂掉。这时,你首先检查应用程序的返回错误,发现是一个内存错误。

$ tail myapp.log
Traceback (most recent call last):
MemoryError

你的应用是否真的内存不足?要确认这个问题,可使用 top 来查看应用程序消耗多少 CPU 和内存。当使用 top 命令后,您注意到一个 Python 应用程序使用了大部分的 CPU,其内存使用量也迅速攀升。当它运行时,如果进程是你的应用程序,则按”C”键来查看完整命令并进行逆向工程。发现原来是你的内存密集型应用程序( memeater.py )。当你的应用程序已经用尽内存,系统会杀掉它并返回一个内存不足(OOM)的错误。

未分类

应用程序的内存和 CPU 使用量增加,最终因为内存不足而被杀掉。

未分类

通过按下”C”键,可以看到启动该应用程序的完整命令

除了检查应用程序,还可以使用 top 来调试其他使用 CPU 或内存的进程。

10. netstat

netstat 用于显示网络状态信息。该命令可显示正在使用的网络端口及其传入连接。但是, netstat 在 Linux 中不能开箱即用。如果需要安装它,需要在 net-tools 包中找到它。作为在本地进行试验或将应用程序推送到主机的开发者,可能会收到端口已被分配或地址已被使用的错误。使用 netstat 得到协议、进程和端口这些信息,下图表明 Apache HTTP 服务器已经在下面的主机上使用了 80 端口。

未分类

使用 netstat -tulpn 表明 Apache 已经在这台机器上使用了 80 端口。

11. ip address

如果 ip address 在你的主机上不能使用,必须使用 iproute2 包进行安装。 i p address 用于显示应用程序的主机接口和 IP 地址。可使用 i p address 来验证你的容器或主机的 IP 地址。例如,当你的容器连接到两个网络时, i p address 可显示哪个接口连接到了哪个网络。对于简单的检查,可以随时使用 ip address 命令获取主机的 IP 地址。下面的示例展示了在接口 eth0 上的 Web 层容器的 IP 地址为 172.17.0.2

未分类

使用 ip address 显示 eth0 接口的 IP 地址为 172.17.0.2

12. lsof

lsof用于列出当前系统打开的文件(list open files)。在某些 Linux 系统中,可能需要使用 lsof 包来安装 lsof 。在 Linux 中,几乎任何和系统的交互都被视为一个文件。因此,如果你的应用程序写入文件或代开网络连接, lsof 将会把这个交互映射为一个文件。与 netstat 类似,可使用 lsof 来检查侦听端口。例如,如果要检查 80 端口是否正在被使用,可使用 lsof 来检查哪个进程正在使用它。下面的示例中,可以看到 httpd (Apache) 在 80 端口上侦听。还可以使用 lsof 来检查 httpd 的进程ID,检查 Web 服务器的二进制文件所在位置( /usr/sbin/httpd )。

未分类

Lsof 表明了 httpd 在 80 端口上侦听。检查 httpd 的进程ID还可以显示所有需要运行的文件httpd。

打开文件列表中的打开文件的名称有助于确定进程的来源,特别是 Apache。

13. df

可以使用 df 显示空闲的磁盘空间(display free disk space)以排查磁盘空间问题。挡在容器管理器上运行应用程序时,可能会收到一条错误信息,提示容器主机上缺少可用空间。虽然磁盘空间应该由系统管理程序来管理和优化,你仍可以使用 df 找出目录中的现有空间并确认是否没有空间。

未分类

Df显示每个文件系统的磁盘空间、绝对空间以及其可用性。

-h 选项表示以可读性较高的方式来显示信息,上面的例子表示这个主机具有大量的磁盘空间。

14. du

du命令也是用于查看使用空间的,但是与 df 命令不同的是 du 命令是对文件和目录磁盘使用的空间的查看,要获取有关哪些文件在目录中使用磁盘空间的更多详细信息,可以使用 du 命令,和 df 命令还是有一些区别的。例如,你想了解那个日志文件占用 /var/log 目录最多的空间,可以使用 du 命令加上 -h 选项和用于获取总大小的 -s 选项。

$ du -sh /var/log/*
1.8M  /var/log/anaconda
384K  /var/log/audit
4.0K  /var/log/boot.log
0 /var/log/chrony
4.0K  /var/log/cron
4.0K  /var/log/maillog
64K /var/log/messages

上面的示例中显示了 /var/log 下的的最大目录为 /var/log/audit 。可以将 du 和 df 搭配使用,以确定在应用程序的主机上使用的磁盘空间。

15. id

要检查运行应用程序的用户,可使用 id 命令来返回用户身份。 id 命令可以显示真实有效的用户ID(UID)和组ID(GID)。下面的示例使用 Vagrant 来测试应用程序并隔离其开发环境。登录进 Vagrant 盒子后,如果尝试安装 Apache HTTP Server(依赖关系),系统会提示你需要以 root 身份执行该命令。要检查你的用户ID和组ID,使用 id 命令,会发现你正在”vagrant”组中以”vagrant”用户身份运行。

$ yum -y install httpd
Loaded plugins: fastestmirror
You need to be root to perform this command.
$ id
uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

要解决此问题,必须以超级用户的身份运行该命令,这将提供提升的权限。

16. chmod

c hmod 命令用来变更文件或目录的权限。当你在主机上首次运行应用程序的二进制文件时,可能会收到错误提示信息“拒绝访问”。如 ls 的示例所示,可以用于检查应用程序二进制文件的权限。

$ ls -l
total 4
-rw-rw-r--. 1 vagrant vagrant 34 Jul 11 02:17 test.sh

这表明您没有权限(没有“x”)来运行二进制文件。 c hmod 可以修改权限,使的用户能够运行二进制文件。

$ chmod +x test.sh
[vagrant@localhost ~]$ ls -l
total 4
-rwxrwxr-x. 1 vagrant vagrant 34 Jul 11 02:17 test.sh

如例子所示,这将更新权限,使其具有可执行的权限。现在当你尝试执行二进制文件时,应用程序不会抛出拒绝访问的错误。当将二进制文件加载到容器时, Chmod 可能很有用。它能保证容器具有合适的权限以执行二进制文件。

17. dig / nslookup

dig 命令是常用的域名查询工具,可以用来测试域名系统工作是否正常。域名服务器(DNS)有助于将 URL 解析为一组应用程序服务器。然而,你会发现有些 URL 不能被解析,这会导致应用程序的连接问题。例如,假如你尝试从应用程序的主机访问你的数据库。你收到一个”不能解析”的错误。要进行故障排查,你尝试使用 dig (DNS 查询工具)或 nslookup (查询 Internet 域名服务器)来确定应用程序似乎无法解析数据的原因。

$ nslookup mydatabase
Server:   10.0.2.3
Address:  10.0.2.3#53

** server can't find mydatabase: NXDOMAIN

使用 nslookup 显示无法解析 mydatabase 。尝试使用 dig 解决,但仍是相同的结果。

$ dig mydatabase

; <<>> DiG 9.9.4-RedHat-9.9.4-50.el7_3.1 <<>> mydatabase
;; global options: +cmd
;; connection timed out; no servers could be reached

这些错误可能是由许多不同的问题引起的。如果无法调试出根本原因,与系统管理员联系以进行更多的调查。对于本地测试,这些问题可能表示你的主机的域名服务器未正确配置。要使用这些命令,需要安装 BIND Utilities 包 。

18. iptables

iptables 用于阻止或允许 Linux 主机上的流量,用于 IP 包过滤器管理,类似于网络防火墙。此工具可阻止某些应用程序接收或发送请求。更具体地说,如果您的应用程序难以访问另一个端点,可能已被 iptables 拒绝流量访问该端点。例如,假设您的应用程序的主机无法访问 Opensource.com,您使用 curl 来测试连接。

$ curl -vvv opensource.com
* About to connect() to opensource.com port 80 (#0)
*   Trying 54.204.39.132...
* Connection timed out
* Failed connect to opensource.com:80; Connection timed out
* Closing connection 0
curl: (7) Failed connect to opensource.com:80; Connection timed out

连接超时。您怀疑某些东西可能会阻塞流量,因此您使用 -S 选项显示 iptables 规则。

$ iptables -S
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT DROP
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -i eth0 -p udp -m udp --sport 53 -j ACCEPT
-A OUTPUT -p tcp -m tcp --sport 22 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 53 -j ACCEPT

前三个规则显示,默认情况下流量已被丢弃。剩下的规则表示允许 SSH 和 DNS 流量。在这种情况下,如果需要允许流量到外部端点的规则,请跟上 sysadmin。如果这是用于本地开发或测试的主机,可使用 iptables 命令来允许合适的流量。添加允许到主机的流量的规则时一定要谨慎。

19. sestatus

通常会在企业管理的应用程序主机上使用 SELinux(一个 Linux 安全模块)。SELinux 对主机上运行的进程提供最低权限的访问,防止潜在的恶意进程访问系统上的重要文件。某些情况下,应用程序需要访问特定文件,但可能会发生错误。要检查 SELinux 是否阻止了应用程序,使用 tail 和 grep 在 /var/log/audit 日志记录中查找”denied”(被拒绝)的信息。否则,使用 sestatus 来检查是否启动了 SELinux。

$ sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      28

上面的输出表示应用程序的主机已启用 SELinux。在本地开发环境中,可以更新 SELinux 使得权限更宽松。

20. history

当你使用大量的命令进行测试和调试时,可能会忘记有用的命令。每个 shell 都有一个 history 命令的变体。它可显示自会话开始以来使用的命令的历史记录。可以使用 history 来记录用来排除应用程序故障的命令。history 命令用于显示指定数目的指令命令,读取历史命令文件中的目录到历史命令缓冲区和将历史命令缓冲区中的目录写入命令文件。

$ history
    1  clear
    2  df -h
    3  du

如果希望执行之前历史记录中的命令,但又不想重新输入,该怎么办?使用符号 ! 即可,可以使用符号 ! 执行指定序号的历史命令。例如,要执行第 2 个历史命令,则输入!2,

未分类

在需要重新执行的命令的指定编号前添加 ! 即可重新执行

这些基本命令能增强排查故障的专业技能,可检查为什么应用程序可以在一个开发环境中工作,而在另一个开发环境中则不可以。许多系统管理员使用这些命令来调试系统问题。了解一些有用的故障排查命令可帮助解决应用程序的问题。

Unix网络IO模型及Linux的IO多路复用模型

近段在看 Kafka 的网络模型时,遇到了很多 Java NIO 的内容,在学习 Java NIO 的过程中,发现需要把 UNIX 的这几种网络 IO 模型以及 Linux 的 IO 多路复用理解清楚,才能更好地理解 Java NIO,本文就是在学习 UNIX 的五种网络 IO 模型以及 Linux IO 多路复用模型后,做的一篇总结。

本文主要探讨的问题有以下两个:

  • Unix 中的五种网络 IO 模型;
  • Linux 中 IO 多路复用的实现。

基本概念

在介绍网络模型之前,先简单介绍一些基本概念。

1、文件描述符 fd

文件描述符(file descriptor,简称 fd)在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

在 Linux 中,内核将所有的外部设备都当做一个文件来进行操作,而对一个文件的读写操作会调用内核提供的系统命令,返回一个 fd,对一个 socket 的读写也会有相应的描述符,称为 socketfd(socket 描述符),实际上描述符就是一个数字,它指向内核中的一个结构体(文件路径、数据区等一些属性)。

2、用户空间与内核空间、内核态与用户态

这个是经常提到的概念,具体含义可以参考这篇文章用户空间与内核空间,进程上下文与中断上下文【总结】,大概内容如下:

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操心系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对 linux 操作系统而言(以32位操作系统为例)

  • 将最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF),供内核使用,称为内核空间;
  • 将较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF),供各个进程使用,称为用户空间。

每个进程可以通过系统调用进入内核,因此,Linux 内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有 4G 字节的虚拟空间。

  • 当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈;
  • 当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。

3、上下文切换

当一个进程在执行时,CPU 的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。

当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。在 Linux 中,当前进程上下文均保存在进程的任务数据结构中。在发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中继服务结束时能恢复被中断进程的执行。

UNIX 的网络 IO 模型

根据 UNIX 网络编程对 IO 模型的分类,UNIX 提供了以下 5 种 IO 模型。

1、阻塞 IO 模型

最常用的 IO 模型就是阻塞 IO 模型,在缺省条件下,所有文件操作都是阻塞的,以 socket 读为例来介绍一下此模型,如下图所示。

未分类

在用户空间调用 recvfrom,系统调用直到数据包达到且被复制到应用进程的缓冲区中或中间发生异常返回,在这个期间进程会一直等待。进程从调用 recvfrom 开始到它返回的整段时间内都是被阻塞的,因此,被称为阻塞 IO 模型。

2、非阻塞 IO 模型

recvfrom 从应用到内核的时,如果该缓冲区没有数据,就会直接返回 EWOULDBLOCK 错误,一般都对非阻塞 IO 模型进行轮询检查这个状态,看看内核是不是有数据到来,流程如下图所示。

未分类

也就是说非阻塞的 recvform 系统调用调用之后,进程并没有被阻塞,内核马上返回给进程。

  • 如果数据还没准备好,此时会返回一个 error。进程在返回之后,可以干点别的事情,然后再发起 recvform 系统调用。重复上面的过程,循环往复的进行 recvform 系统调用,这个过程通常被称之为轮询。

轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

在 Linux 下,可以通过设置 socket 使其变为 non-blocking。

3、IO 多路复用模型

Linux 提供 select、poll、epoll,进程通过讲一个或者多个 fd 传递给 select、poll、epoll 系统调用,阻塞在 select 操作(这个是内核级别的调用)上,这样的话,可以同时监听多个 fd 是否处于就绪状态。其中,

  • select/poll 是顺序扫描 fd 是否就绪,而且支持的 fd 数量有限;
  • epoll 是基于事件驱动方式代替顺序扫描性能更高。

这个后面详细讲述,具体流程如下图所示。

未分类

多路复用的特点是通过一种机制一个进程能同时等待 IO 文件描述符,内核监视这些文件描述符(套接字描述符),其中的任意一个进入读就绪状态,select, poll,epoll 函数就可以返回,它最大的优势就是可以同时处理多个连接。

4、信号驱动 IO 模型

首先需要开启 socket 信号驱动 IO 功能,并通过系统调用 sigaction 执行一个信号处理函数(非阻塞,立即返回)。当数据就绪时,会为该进程生成一个 SIGIO 信号,通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环喊出处理数据,流程如下图所示。

未分类

5、异步 IO 模型

告知内核启动某个事件,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通过我们,流程如下图所示。

未分类

与信号驱动模式的主要区别是:

  • 信号驱动 IO 由内核通知我们何时可以开始一个 IO 操作;
  • 异步 IO 操作由内核通知我们 IO 何时完成。

内核是通过向应用程序发送 signal 或执行一个基于线程的回调函数来完成这次 IO 处理过程,告诉用户 read 操作已经完成,在 Linux 中,通知的方式是信号:

  • 当进程正处于用户态时,应用需要立马进行处理,一般情况下,是先将事件登记一下,放进一个队列中;
  • 当进程正处于内核态时,比如正在以同步阻塞模式读磁盘,那么只能先把这个通知挂起来,等内核态的事情完成之后,再触发信号通知;
  • 如果这个进程现在被挂起来了,比如 sleep,那就把这个进程唤醒,等 CPU 空闲时,就会调度这个进程,触发信号通知。

6、几种 IO 模型比较

未分类

Linux 的 IO 多路复用模型

IO 多路复用通过把多个 IO 阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下,可以同时处理多个 client 请求,与传统的多线程/多进程模型相比,IO 多路复用的最大优势是系统开销小,系统不需要创建新的额外的进程或线程,也不需要维护这些进程和线程的运行,节省了系统资源,IO 多路复用的主要场景如下:

  • Server 需要同时处理多个处于监听状态或者连接状态的 socket;
  • Server 需要同时处理多种网络协议的 socket。

IO 多路复用实际上就是通过一种机制,一个进程可以监视多个描 fd,一旦某个 fd 就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,目前支持 IO 多路复用的系统有 select、pselect、poll、epoll,但它们本质上都是同步 IO。

在 Linux 网络编程中,最初是选用 select 做轮询和网络事件通知,然而 select 的一些固有缺陷导致了它的应用受到了很大的限制,最终 Linux 选择 epoll。

1、select

select 函数监视的 fd 分3类,分别是 writefds、readfds、和 exceptfds。调用后select 函数会阻塞,直到有 fd 就绪(有数据 可读、可写、或者有 except),或者超时(timeout 指定等待时间,如果立即返回设为 null 即可),函数返回。当select函数返回后,可以通过遍历 fdset,来找到就绪的 fd。

select 目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select 的一个最大的缺陷就是单个进程对打开的 fd 是有一定限制的,它由 FD_SETSIZE 限制,默认值是1024,如果修改的话,就需要重新编译内核,不过这会带来网络效率的下降。

select 和 poll 另一个缺陷就是随着 fd 数目的增加,可能只有很少一部分 socket 是活跃的,但是 select/poll 每次调用时都会线性扫描全部的集合,导致效率呈现线性的下降。

2、poll

poll 本质上和 select 没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个 fd 对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有 fd 后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历 fd。这个过程经历了多次无谓的遍历。

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样以下两个缺点:

  • 大量的 fd 的数组被整体复制于用户态和内核地址空间之间;
  • poll 还有一个特点是【水平触发】,如果报告了 fd 后,没有被处理,那么下次 poll 时会再次报告该 fd;
  • fd 增加时,线性扫描导致性能下降。

3、epoll

epoll 支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些 fd 变为就绪态,并且只会通知一次。还有一个特点是,epoll 使用【事件】的就绪通知方式,通过 epoll_ctl 注册 fd,一旦该 fd 就绪,内核就会采用类似 callback 的回调机制来激活该 fd,epoll_wait 便可以收到通知。

epoll的优点:

  • 没有最大并发连接的限制,它支持的 fd 上限受操作系统最大文件句柄数;
  • 效率提升,不是轮询的方式,不会随着 fd 数目的增加效率下降。epoll 只会对【活跃】的 socket 进行操作,这是因为在内核实现中 epoll 是根据每个 fd 上面的 callback 函数实现的,只有【活跃】的 socket 才会主动的去调用 callback 函数,其他 idle 状态的 socket 则不会。epoll 的性能不会受 fd 总数的限制。
  • select/poll 都需要内核把 fd 消息通知给用户空间,而 epoll 是通过内核和用户空间 mmap 同一块内存实现。

epoll 对 fd 的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT 模式是默认模式,LT 模式与 ET 模式的区别如下:

  • LT 模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件,下次调用 epoll_wait 时,会再次响应应用程序并通知此事件;
  • ET 模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件,如果不处理,下次调用 epoll_wait 时,不会再次响应应用程序并通知此事件。

4、三种模型的区别

未分类

介绍完 IO 多路复用之后,后续我们看一下 Java 网络编程中的 NIO 模型及其背后的实现机制。

Linux路由基础知识及配置方法

关于路由的基础知识

1、路由概念

  • 路由: 跨越从源主机到目标主机的一个互联网络来转发数据包的过程
  • 路由器:能够将数据包转发到正确的目的地,并在转发过程中选择最佳路径的设备
  • 路由表:在路由器中维护的路由条目,路由器根据路由表做路径选择
  • 直连路由:当在路由器上配置了接口的IP地址,并且接口状态为up的时候,路由表中就出现直连路由项
  • 静态路由:是由管理员手工配置的,是单向的。
  • 默认路由:当路由器在路由表中找不到目标网络的路由条目时,路由器把请求转发到默认路由接口 。

2、静态路由和默认路由的特点

静态路由特点:

  • 路由表是手工设置的;
  • 除非网络管理员干预,否则静态路由不会发生变化;
  • 路由表的形成不需要占用网络资源;

适用环境:一般用于网络规模很小、拓扑结构固定的网络中。

默认路由特点:

  • 在所有路由类型中,默认路由的优先级最低

适用环境:一般应用在只有一个出口的末端网络中或作为其他路由的补充

浮动静态路由:

  • 路由表中存在相同目标网络的路由条目时,根据路由条目优先级的高低,将请求转发到相应端口;
  • 链路冗余的作用;

3、路由器转发数据包时的封装过程

源IP和目标IP不发生变化,在网络的每一段传输时,源和目标MAC发生变化,进行重新封装,分别是每一段的源和目标地址

4、要完成对数据包的路由,一个路由器必须至少了解以下内容:

  • 目的地址
  • 相连路由器,并可以从哪里获得远程网络的信息
  • 到所有远程网络的可能路由
  • 到达每个远程网络的最佳路由
  • 如何维护并验证路由信息
  • 路由和交换的对比

路由工作在网络层

  • 根据“路由表”转发数据
  • 路由选择
  • 路由转发

交换工作在数据链路层

  • 根据“MAC地址表”转发数据
  • 硬件转发

linux运维中关于路由的一些操作

1、使用route -n命令查看Linux内核路由表

[root@dev ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.17    0.0.0.0         255.255.255.255 UH    0      0        0 ppp0
10.1.32.14      0.0.0.0         255.255.255.255 UH    0      0        0 tun0
10.1.32.12      0.0.0.0         255.255.255.255 UH    0      0        0 tun0
10.4.8.2        192.168.9.254   255.255.255.255 UGH   0      0        0 eth0
10.4.9.0        0.0.0.0         255.255.255.0   U     0      0        0 tun0
192.168.9.0     0.0.0.0         255.255.255.0   U     1      0        0 eth0
10.2.0.0        0.0.0.0         255.255.0.0     U     0      0        0 tun0
10.0.0.0        0.0.0.0         255.255.0.0     U     0      0        0 tun0
10.1.0.0        0.0.0.0         255.255.0.0     U     0      0        0 tun0
192.168.0.0     0.0.0.0         255.255.0.0     U     0      0        0 tun0
0.0.0.0         192.168.9.254   0.0.0.0         UG    0      0        0 eth0

未分类

2、三种路由类型说明

  • 主机路由

主机路由是路由选择表中指向单个IP地址或主机名的路由记录。主机路由的Flags字段为H。例如,在下面的示例中,本地主机通过IP地址192.168.1.1的路由器到达IP地址为10.0.0.10的主机。

Destination    Gateway       Genmask        Flags     Metric    Ref    Use    Iface
-----------    -------     -------            -----     ------    ---    ---    -----
10.0.0.10     192.168.1.1    255.255.255.255   UH       0    0      0    eth0
  • 网络路由

网络路由是代表主机可以到达的网络。网络路由的Flags字段为N。例如,在下面的示例中,本地主机将发送到网络192.19.12的数据包转发到IP地址为192.168.1.1的路由器。

Destination    Gateway       Genmask      Flags    Metric    Ref     Use    Iface
-----------    -------     -------         -----    -----   ---    ---    -----
192.19.12     192.168.1.1    255.255.255.0      UN      0       0     0    eth0
  • 默认路由

当主机不能在路由表中查找到目标主机的IP地址或网络路由时,数据包就被发送到默认路由(默认网关)上。默认路由的Flags字段为G。例如,在下面的示例中,默认路由是IP地址为192.168.1.1的路由器。

Destination    Gateway       Genmask    Flags     Metric    Ref    Use    Iface
-----------    -------     ------- -----      ------    ---    ---    -----
default       192.168.1.1     0.0.0.0    UG       0        0     0    eth0

3、配置路由route的命令

设置和查看路由表都可以用 route 命令,设置内核路由表的命令格式是:

route  [add|del] [-net|-host] target [netmask Nm] [gw Gw] [[dev] If]

参数解释:

  • add 添加一条路由规则
  • del 删除一条路由规则
  • -net 目的地址是一个网络
  • -host 目的地址是一个主机
  • target 目的网络或主机
  • netmask 目的地址的网络掩码
  • gw 路由数据包通过的网关
  • dev 为路由指定的网络接口

4、route命令使用举例

添加到主机的路由
# route add -host 192.168.1.2 dev eth0:0
# route add -host 10.20.30.148 gw 10.20.30.40

添加到网络的路由
# route add -net 10.20.30.40 netmask 255.255.255.248 eth0
# route add -net 10.20.30.48 netmask 255.255.255.248 gw 10.20.30.41
# route add -net 192.168.1.0/24 eth1

添加默认路由
# route add default gw 192.168.1.1

删除路由
# route del -host 192.168.1.2 dev eth0:0
# route del -host 10.20.30.148 gw 10.20.30.40
# route del -net 10.20.30.40 netmask 255.255.255.248 eth0
# route del -net 10.20.30.48 netmask 255.255.255.248 gw 10.20.30.41
# route del -net 192.168.1.0/24 eth1
# route del default gw 192.168.1.1                  //route del default   删除所有的默认路由

添加一条默认路由
# route add default gw 10.0.0.1      //默认只在内存中生效
开机自启动可以追加到/etc/rc.local文件里
# echo "route add default gw 10.0.0.1" >>/etc/rc.local

添加一条静态路由
# route add -net 192.168.2.0/24 gw 192.168.2.254
要永久生效的话要这样做:
# echo "any net 192.168.2.0/24 gw 192.168.2.254" >>/etc/sysconfig/static-routes

添加到一台主机的静态路由
# route add -host 192.168.2.2 gw 192.168.2.254
要永久生效的话要这样做:
# echo "any  host 192.168.2.2 gw 192.168.2.254 " >>/etc/sysconfig/static-routes
注:Linux 默认没有这个文件 ,得手动创建一个

5、设置包转发

在Linux中默认的内核配置已经包含了路由功能,但默认并没有在系统启动时启用此功能;
开启Linux的路由功能可以通过调整内核的网络参数来实现,方法如下:

  临时开启路由功能:
# echo 1 > /proc/sys/net/ipv4/ip_forward
或者
# sysctl -w net.ipv4.ip_forward=1
  永久开启路由功能
# vim /etc/sysctl.conf
net.ipv4.ip_forward = 1
# sysctl -p

6、静态路由配置

添加静态路由到路由表的语法如下:

ip route [destination_network] [mask] [next-hop_address] administrative_distance]

参数解析:

  • ip route 用于创建静态路由的命令。
  • Destination_network 需要发布到路由表中的网段。
  • Mask 在这一网络上使用的子网掩码。
  • Next-hop_address 下一跳路由器的地址。
  • administrative_distance 默认时,静态路由有一个取值为1 的管理性距离。在这个命令的尾部添加管理权来修改这个默认值。

例如

ip route 172.16.1.0 255.255.255.0 172.16.2.1

查看路由表除了使用route -n命令外,还可以使用ip route

[root@dev ~]# ip route
192.168.1.17 dev ppp0  proto kernel  scope link  src 192.168.1.190
10.1.32.14 dev tun0  scope link
10.1.32.12 dev tun0  scope link
10.4.8.2 via 192.168.9.254 dev eth0  src 192.168.9.200  mtu 1500 advmss 1460
10.4.9.0/24 dev tun0  scope link
192.168.9.0/24 dev eth0  proto kernel  scope link  src 192.168.9.200  metric 1
10.2.0.0/16 dev tun0  scope link
10.0.0.0/16 dev tun0  scope link
10.1.0.0/16 dev tun0  scope link
192.168.0.0/16 dev tun0  scope link
default via 192.168.9.254 dev eth0

—————————————-实例1——————————————–

未分类

如上图所示,PC0机器和PC1机器之间经过两个路由器,要想使这两台机器通信,路由设置如下:

  • Route0路由器设置:
ip add 192.168.1.1 255.255.255.0
ip add 192.168.2.1 255.255.255.0
ip route 192.168.3.0 255.255.255.0 192.168.2.2
  • Route1路由器设置:
ip add 192.168.2.2 255.255.255.0
ip add 192.168.3.1 255.255.255.0
ip route 192.168.1.0 255.255.255.0 192.168.2.1

—————————————-实例2——————————————–

未分类

如上图所示,使用A主机192.168.1.2能够ping通E主机192.168.4.2,这两台机能够通信。

操作思路:

  • 在主机B上设置默认路由下一跳为192.168.2.2,并开启路由转发功能;
  • 在主机C上设置2条静态路由,分别去192.168.1.0/24网段的下一跳为192.168.2.1,去192.168.4.0/24网段的下一跳为192.168.3.2,并开启路由转发功能;
  • 在主机D上设置默认路由下一跳为192.168.3.1,并开启路由转发功能。

操作记录:

1)A主机上操作:ip为192.168.1.2,设置网关为192.168.1.1
# route add default gw 192.168.1.1

2)B主机上操作:第一块网卡为192.168.1.1,第二块网卡为192.168.2.1
# ifconfig eth0 192.168.1.1
# ifconfig eth1 192.168.2.1   //可以在一块网卡上设置两个ip,比如是eth0,eth0:0

B主机设置默认路由,下一跳为192.168.2.2
# route add default gw 192.168.2.2

B主机开启路由转发功能
# echo 1 > /proc/sys/net/ipv4/ip_forward   //临时转发,可以在/etc/sysctl.conf里设置永久转发

3)C主机上操作:第一块网卡为192.168.2.2,第二块网卡为192.168.3.1
# ifconfig eth0 192.168.2.2
# ifconfig eth1 192.168.3.1   //如果就一块网卡,可以设置ifconfig eth0:0 192.168.3.1

C主机设置2条默认路由
# route add -net 192.168.1.0/24 gw 192.168.2.1
# route add -net 192.168.4.0/24 gw 192.168.3.2

C主机开启路由转发功能
# echo 1 > /proc/sys/net/ipv4/ip_forward

4)D主机上操作:第一块网卡为192.168.3.2,第二块网卡为192.168.4.1
# ifconfig eth0 192.168.3.2
# ifconfig eth1 192.168.4.1

D主机设置默认路由,下一跳为192.168.3.1
# route add default gw 192.168.3.1

D主机开启路由转发功能
# echo 1 > /proc/sys/net/ipv4/ip_forward

5)E主机上操作:ip为192.168.4.2,设置网关为192.168.4.1
# route add default gw 192.168.4.1

Linux下配置安卓开发环境和安装apktool

1、安装依懒源

yum install glibc.i686 zlib.i686 mesa-libglapi.i686 libstdc++-4.4.7-4.el6.i686 gitk.noarch gcc-c++.x86_64 bison.x86_64 flex.x86_64 gperf.x86_64 unix2dos.x86_64 mingw32-gcc-c++.x86_64 mingw32-binutils.x86_64 mingw32-w32api.noarch  

2、安装JDK

wget http://download.oracle.com/otn-pub/java/jdk/8u121-b13/e9e7ea248e2c4826b92b3f075a80e441/jdk-8u121-linux-x64.tar.gz?AuthParam=1491548745_9847eef52601a85fbf542c74583e1454  
tar zxf jdk-8u121-linux-x64.tar.gz -c /usr/local  
ln -s /usr/local/jdk1.8.0_121/ /usr/local/jdk

cat >> /etc/profile <<EOF  
export JAVA_HOME=/usr/local/jdk  
export JAVA_BIN=/usr/local/jdk/bin  
export PATH=$PATH:$JAVA_HOME/bin  
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar  
export JAVA_HOME JAVA_BIN PATH CLASSPATH  
EOF

source /etc/profile  

3、安装 android-sdk-linux

wget http://dl.gmirror.org/android/android-sdk_r24.4.1-linux.tgz  
tar zxf android-sdk_r24.4.1-linux.tgz -c /usr/local  
cd /usr/local/android-sdk-linux  
tools/android update sdk --no-ui  
cat >> /etc/profile <<EOF  
export ANDROID_SDK_HOME=/usr/local/android-sdk-linux  
export PATH=$PATH:$ANDROID_SDK_HOME/tools:$ANDROID_SDK_HOME/platform-tools  
EOF  
source /etc/profile  

4、apktool

wget https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.2.2.jar  
wget https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool  
mv apktool /usr/local/bin  
mv apktool_2.2.2.jar /usr/local/bin/apktool.jar  

其他

GCC报错:
https://blog.imdst.com/lib64-libc-so-6-version-glibc_2-14-not-found-jie-jue-ban-fa/

忘记linux登陆密码重置的方法

之前克隆了别人的一台虚拟机,问机主要了密码试了一下居然报错,机主自己也搞不清楚密码是对是错,嘤嘤嘤~

未分类

遇到这种情况,其实可以进入single模式去修改root密码,下边的方法适用于ubuntu12.04版本

1、重启ubuntu,随即长按shirft进入grub菜单,选择recovery-mode,不要按回车哦;

未分类

2、按”e”键进入编辑页面;如下(恢复模式)

未分类

3、将recovery nomodeset替换为rw single init=/bin/bash;

未分类

未分类

(注:如果是非恢复模式,是将界面中位置的ro single改为rw single init=/bin/bash)

4、按ctrl+x进入单用户模式,当前用户即为root;

未分类

5、到/etc目录下修改sudoers权限:chmod 0440 sudoers,搞定;同时passwd root修改root密码,下次就不这么麻烦了;

未分类

6、按ctrl+alt+del重启;

重启完成就可以用设置好的密码进入linux啦~

技术贴完成,谢谢观赏。

Linux权限管理使用总结

1、linux中的权限分组

linux中权限由10位字母组成,就是下图中标注出的权限部分(-代表没有取得该权限)
大家进入linux后输入命令ls -al即可查看到如下内容

未分类

在linux中权限中,每个用户属于一个权限组,其中包括几个概念即:文件所有者、所在组、其他组。

  • 文件所有者:一般来说可以简单的理解为文件创建者,但是创建后可以通过chown(change owner)命令来更改文件的所有者
  • 所在组:文件创建时所在组为创建者所在组,后期可通过chgrp(change group)命令来修改文件所在组
  • 其他组:除文件所在组以外的组称为其他组

2、具体权限介绍

linux权限的10位字母具体可以分为4段:

未分类

  • 第一段:表示文件是否为目录,若为目录第一个字母则为d,否则为-
  • 第二段:表示文件所有者权限,第一个r(read)表示为读权限,第二个w(write)表示为写权限,第三个x(execute)表示为可执行权限,若没有某个权限则权限出用-代替
  • 第三段:表示文件所在组权限,具体rwx的含义同上
  • 第四段:表示其他用户组权限,具体rwx的含义同上

3、权限的修改

其实linux中对于权限的修改十分简单,大家可以将权限理解为二进制的对应关系(每段rwx为一组),例如你想给一个文件读权限,而不给写权限和可执行权限,那么对应的就是100(二进制),也就是4,同理如果给rwx权限那么就是7。

那么我们如果想要给一个文件test.sh文件赋予rwxr-xr-x权限那么我们就应该执行chmod 755 test.sh命令,其中第一个7对应了文件所有者所持有的权限,第二个5对应了文件所在组的权限,第三个5对应了其他用户的权限。

但是!!!如果你想要更直观的看出给一个文件的具体权限可以使用下面的命令chmod u=rwx,g=rx,o=rx test.sh即可给予test.sh和上面一样的权限,其中u代表文件所有者权限,g代表文件所在组权限,o代表其他用户权限。

chmod u-x,g+w,o+w test.sh该命令表示对该文件去除文件所有者的执行权限,加上文件所在组和其他用户的写权限,以上两条命令可以更加直观的看出我们具体对文件权限做出了哪些修改。

从Unix到Linux的发展关系

一张图概括全世界最重要的操作系统Unix,以及后续Linux的发展过程,他们的版本关系。如果Unix不商业化,可能没有Linux的发展空间,现在已经是Linux的天下。

Unix的诞生

1965年时,贝尔实验室(Bell Labs)加入一项由通用电气(General Electric)和麻省理工学院(MIT)合作的计划;该计划要建立一套多使用者、多任务、多层次(multi-user、multi-processor、multi-level)的MULTICS操作系统。直到1969年,因MULTICS计划的工作进度太慢,该计划被停了下来。当时,Ken Thompson(后被称为UNIX之父)已经有一个称为”星际旅行”的程序(应该是一个游戏程序)在GE-635的机器上跑,但是反应非常慢,正巧被他发现了一部被闲置的PDP-7(Digital的主机),Ken Thompson和Dernis Ritchie就将”星际旅行”的程序移植到PDP-7上。而这部PDP-7就此在整个计算机历史上留下了芳名。

MULTICS其实是”Multiplexed Information and Computing Service”的缩写,在1970年时,那部PDP-7却只能支持两个使用者,当时,Brian Kernighan就开玩笑地称他们的系统其实是:”UNiplexed Information and Computing Service”,缩写为”UNICS”,后来,大家取其谐音,就称其为”UNIX”了。1970年可称为”UNIX元年”。

从Unix到Linux的发展关系

未分类

后来Unix被商业化,后来出现Linux,这后来的故事我们都知道了。

使用Django commands构建命令行做定时(crontab)任务

需求: 我想调用django里models.py或者views.py的函数对数据库做些增删改查的操作,可以加入crontab做管理,找了半天才发现可以用django commands来搞,就简单记录下来怎么用。

这个app的内容如下:

tree weblog/
weblog/
├── __init__.py
├── __init__.pyc
├── admin.py
├── admin.pyc
├── apps.py
├── management
│   ├── __init__.py
│   ├── __init__.pyc
│   └── commands
│       ├── __init__.py
│       ├── __init__.pyc
│       ├── delete_author.py
│       └── delete_author.pyc
├── migrations
│   ├── 0001_initial.py
│   ├── 0001_initial.pyc
│   ├── __init__.py
│   └── __init__.pyc
├── models.py
├── models.pyc
├── tests.py
└── views.py

3 directories, 19 files

简单写一个函数,功能是删除weblog_author表的一条记录:

cat weblog/management/commands/delete_author.py
from django.core.management.base import BaseCommand, CommandError
from weblog.models import Author

class Command(BaseCommand):
    help = 'delete author'

    def add_arguments(self, parser):
        parser.add_argument('author_id', nargs='+', type=int)

    def handle(self, *args, **options):
        for author_id in options['author_id']:
            try:
                author = Author.objects.get(pk=author_id)
            except Author.DoesNotExist:
                raise CommandError('Author "%s" does not exist' % author_id)

            author.delete()

            self.stdout.write(self.style.SUCCESS('Successfully delete author "%s"' % author_id))

执行

python manage.py delete_author  2
Successfully delete author “2"

加入到crontab里:

*/15 * * * * python /var/www/poll_mysite/manage.py delete_author  2 >/dev/null 2>&1

在docker swarm集群中使用HEALTHCHECK解决服务更新不可用的问题

这是我们使用自建 docker swarm 集群后在部署时遇到的一个问题,使用 docker service update 命令更新服务时,

docker service update -d=false --force service_name

在更新的过程中服务有短暂的时间不能访问。

该服务中运行的是 asp.net core web api ,所使用的 Dockerfile 如下:

FROM microsoft/aspnetcore:1.1.2
ARG PROJECT
WORKDIR /app
COPY ${PROJECT}/publish .
RUN echo "dotnet ${PROJECT}.dll --urls http://*:80" > run.sh

通过在服务更新期间在另外一个容器中运行下面的 curl 命令捕捉这个问题:

while true;do curl -sSf -w '%{http_code}' cloud_api/alive;sleep 2;done

service update 期间不能访问所更新的服务时,curl 会出现下面的输出:

curl: (7) Failed to connect to cloud_api port 80: Connection refused
000

怀疑是容器启动后,asp.net core web api 站点没有立即开始工作,dotnet 命令启动站点也需要一定的时间。

针对这个怀疑点,在 Dockcefile 中添加 HEALTHCHECK 指令,这样可以让 docker 在容器启动后对容器内应用进行健康检查,检查通过才将容器投入使用。

HEALTHCHECK --interval=5s --timeout=20s 
   CMD curl -fs localhost/alive || exit 1

注:localhost/alive 是容器内应用实现的一个健康检查 url 。

添加 HEALTHCHECK 后重新构建镜像并部署,执行 service update 命令问题没有出现,搞定!

docker swarm集群搭建及使用shipyard UI管理

一、规划

1、swarm01作为manager节点,swarm02和swarm03作为worker节点。

# cat /etc/hosts
127.0.0.1   localhost
192.168.139.175  swarm01 
192.168.139.176  swarm02 
192.168.139.177  swarm03

2、配置SSH免密登陆

# ssh-keygen -t rsa -P ''
# ssh-copy-id -i .ssh/id_rsa.pub [email protected]
# ssh-copy-id -i .ssh/id_rsa.pub [email protected]

二、安装docker和ansible

1、安装配置ansible

# yum -y install ansible
# cat /etc/ansible/hosts | grep -v ^# | grep -v ^$
[node]
192.168.139.176
192.168.139.177
# sed -i "s/SELINUX=enforcing/SELINUX=disabled" /etc/selinux/config
# ansible node -m copy -a 'src=/etc/selinux/config dest=/etc/selinux/'
# systemctl stop firewalld
# systemctl disable firewalld
# ansible node -a 'systemctl stop firewalld'
# ansible node -a 'systemctl disable firewalld'

注:这里选择关闭防火墙,实际环境中可自行开放端口。

2、安装docker

  • 在manager节点安装docker
# yum install -y yum-utils device-mapper-persistent-data lvm2
# yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# yum list docker-ce --showduplicates | sort -r
# yum -y install docker-ce
# docker --version
Docker version 17.06.0-ce, build 02c1d87
# systemctl start docker
# systemctl status docker
# systemctl enable docker
  • 使用ansible在worker节点安装docker
# ansible node -m copy -a 'src=/etc/yum.repos.d/docker-ce.repo dest=/etc/yum.repos.d/'
# ansible node -m yum -a "state=present name=docker-ce"
# ansible node -a 'docker --version'
192.168.139.173 | SUCCESS | rc=0 >>
Docker version 17.06.0-ce, build 02c1d87
192.168.139.174 | SUCCESS | rc=0 >>
Docker version 17.06.0-ce, build 02c1d87
# ansible node -a 'systemctl start docker'
# ansible node -a 'systemctl status docker'
# ansible node -a 'systemctl enable docker'

三、配置docker swarm集群

1、创建docker swarm集群

# docker swarm init --listen-addr 0.0.0.0
Swarm initialized: current node (a1tno675d14sm6bqlc512vf10) is now a manager.
To add a worker to this swarm, run the following command:
    docker swarm join --token SWMTKN-1-3sp9uxzokgr252u1jauoowv74930s7f8f5tsmm5mlk5oim359e-dk52k5uul50w49gbq4j1y7zzb 192.168.139.175:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

2、查看节点

# docker node ls
ID                           HOSTNAME      STATUS      AVAILABILITY   MANAGER STATUS
a1tno675d14sm6bqlc512vf10 *  swarm01        Ready         Active           Leader

3、查看加入集群manager管理节点的命令

# docker swarm join-token manager
To add a manager to this swarm, run the following command:
    docker swarm join --token SWMTKN-1-3sp9uxzokgr252u1jauoowv74930s7f8f5tsmm5mlk5oim359e-7tdlpdnkyfl1bnq34ftik9wxw 192.168.139.175:2377

4、查看加入集群worker节点的命令

# docker swarm join-token worker
To add a worker to this swarm, run the following command:
    docker swarm join --token SWMTKN-1-3sp9uxzokgr252u1jauoowv74930s7f8f5tsmm5mlk5oim359e-dk52k5uul50w49gbq4j1y7zzb 192.168.139.175:2377

5、将前面规划的两个worker节点加入集群

# docker swarm join --token SWMTKN-1-3sp9uxzokgr252u1jauoowv74930s7f8f5tsmm5mlk5oim359e-dk52k5uul50w49gbq4j1y7zzb 192.168.139.175:2377
This node joined a swarm as a worker.

6、查看worker节点是否已加入集群

# docker node ls
ID                        HOSTNAME    STATUS  AVAILABILITY  MANAGER STATUS
7zkbqgrjlsn8c09l3fagtfwre     swarm02  Ready      Active              
a1tno675d14sm6bqlc512vf10 *   swarm01  Ready      Active         Leader
apy9zys2ch4dlwbmgdqwc0pn3     swarm03  Ready      Active

7、查看docker swarm的管理网络

# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
05efca714d2f        bridge              bridge              local
c9cd9c37edd7        docker_gwbridge     bridge              local
10ac9e48d81b        host                host                local
n60tdenc5jy7        ingress             overlay             swarm
a9284277dc18        none                null                local

这里,一个docker swarm集群就搭建好了

四、搭建docker swarm的UI—Portainer

Portainer地址:https://portainer.io/。

1、使用该命令部署Portainer

# docker service create 
--name portainer 
--publish 9000:9000 
--constraint 'node.role == manager' 
--mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock 
portainer/portainer 
-H unix:///var/run/docker.sock
# docker images |grep portainer
portainer/portainer  latest  07cde96d4789   2 weeks ago  10.4MB
# docker service ls                    ###查看集群列表
ID              NAME        MODE     REPLICAS      IMAGE             PORTS
p5bo3n0fmqgz  portainer  replicated    1/1   portainer/portainer:latest   *:9000->9000/tcp

这就部署好了

2、浏览器输入http://localhost:9000进入该UI界面,如下所示,第一次进入Portainer,配置8位数的admin密码

未分类

密码修改完成后点击“validate”验证

未分类

如下图所示,输入admin用户名和密码进入Portainer

未分类

首页如下

未分类

查看swarm节点模块

未分类

这里可以在images模块pull镜像,在这里我pull了nginx

未分类

在Services模块下创建nginx服务,Services > Add service,这里创建三个副本,并将80端口映射出去,最后点击“Create Service”创建服务

未分类

刷新服务列表,查看是否创建成功

未分类

3、使用命令进行确认

# docker images | grep nginx
nginx   latest    b8efb18f159b     7 days ago     107MB
# ansible node -m shell -a 'docker images|grep nginx'
192.168.139.177 | SUCCESS | rc=0 >>
nginx   latest    b8efb18f159b     8 days ago     107MB
192.168.139.176 | SUCCESS | rc=0 >>
nginx   latest    b8efb18f159b     8 days ago     107MB
# docker service ls        ###查看服务的任务列表
ID             NAME     MODE     REPLICAS   IMAGE        PORTS
emrs3rj73bwh  Nginx  replicated   3/3    nginx:latest  *:80->80/tcp
p5bo3n0fmqgz  portainer  replicated  1/1  portainer/portainer:latest   *:9000->9000/tcp
# docker service ps Nginx  
ID                  NAME                IMAGE               NODE         
0smpndfx0bwc        Nginx.1             nginx:latest        swarm03      
werrrzlyfbf1        Nginx.2             nginx:latest        swarm01   
l7puro0787cj        Nginx.3             nginx:latest        swarm02 

DESIRED STATE       CURRENT STATE          ERROR               PORTS      
Running          Running 15 minutes ago                                   
Running          Running 15 minutes ago                                  
Running          Running 15 minutes ago

未分类

五、搭建docker swarm的UI—Shipyard

Shipyard的UI也是比较简单的,但是比较反复,它需要在每个节点都pull相应镜像才能加入Shipyard的UI。

1、先pull相应镜像到本地,这里我使用的是网易蜂巢的镜像,很快而且镜像也是比较新的

# docker pull hub.c.163.com/library/alpine:latest
# docker pull hub.c.163.com/library/rethinkdb:latest
# docker pull hub.c.163.com/longjuxu/microbox/etcd:latest
# docker pull hub.c.163.com/wangjiaen/shipyard/docker.io/shipyard/docker-proxy:latest
# docker pull hub.c.163.com/library/swarm:latest
# docker pull hub.c.163.com/wangjiaen/shipyard/docker.io/shipyard/shipyard:latest

2、给这些镜像新建一个tag标签

# docker tag 7328f6f8b418 alpine
# docker tag 4a511141860c rethinkdb
# docker tag 6aef84b9ec5a microbox/etcd 
# docker tag cfee14e5d6f2 shipyard/docker-proxy
# docker tag 0198d9ac25d1 swarm
# docker tag 36fb3dc0907d shipyard/shipyard

3、使用如下命令搭建Shipyard的UI

# curl -sSL https://shipyard-project.com/deploy | bash -s
Deploying Shipyard
 -> Starting Database
 -> Starting Discovery
 -> Starting Cert Volume
 -> Starting Proxy
 -> Starting Swarm Manager
 -> Starting Swarm Agent
 -> Starting Controller
Waiting for Shipyard on 192.168.139.175:8080
..
Shipyard available at http://192.168.139.175:8080
Username: admin Password: shipyard

4、根据提示输入http://localhost:8080,输入用户名admin,密码shipyard进入shipyard

未分类

5、进入shipyard首页容器界面

未分类

6、进入nodes模块查看,这里现在只有manager节点

未分类

7、在worker节点上pull并tag镜像,即是重复如上的第①和第②步,之后,在该worker节点上输入如下命令将其加入shipyard

# curl -sSL https://shipyard-project.com/deploy | ACTION=node DISCOVERY=etcd://192.168.139.175:4001 bash -s 
Adding Node
 -> Starting Cert Volume
 -> Starting Proxy
 -> Starting Swarm Manager
 -> Starting Swarm Agent
Node added to Swarm: 192.168.139.176

未分类

其他节点同理。

对比两种UI,其实都是比较简单的,个人认为Portainer较好,在manager节点pull一个镜像即可搭建UI。

问题:

  • manager:
# docker swarm init --advertise-addr 192.168.139.175
  • worker:
# docker swarm join --token SWMTKN-1-4dwtfbdvjmuf3limglbpy66k85ply2cn66hd0ugsaxfed5fj1d-3rp33pedt9k7ewpfizbzc9bvi 192.168.139.175:2377
Error response from daemon: Timeout was reached before node was joined. The attempt to join the swarm will continue in the background. Use the "docker info" command to see the current swarm status of your node.

出现worker节点无法加入集群的问题,这里需要设置监听地址全零。