[{"content":"LobeChat 是一款现代化设计的开源 ChatGPT/LLMs 聊天应用与开发框架，也是一个支持语音合成、多模态、可扩展的（function call）插件系统。LobeChat 默认使用客户端数据库（IndexedDB），同时也支持使用服务端数据库（下简称 DB 版）。 本文将以docker-compose的部署方式为例介绍如何私有化部署lobechat的DB版本。\nofficial doc 快速开始 官方的部署指引文档，最简单的私有化部署方式可以直接使用一个docker命令解决\n$ docker run -d -p 3210:3210 \\ -e OPENAI_API_KEY=sk-xxxx \\ -e ACCESS_CODE=lobe66 \\ --name lobe-chat \\ lobehub/lobe-chat 指令说明：\n默认映射端口为 3210, 请确保未被占用或手动更改端口映射 OPENAI_API_KEY这个环境变量需要使用自己的 OpenAI API Key 替换上述命令中的 sk-xxxx 同样的方式还可以添加别的key例如GOOGLE_API_KEY等，完整的可以参考官方给出的环境变量文档 关于数据库 LobeChat 默认使用客户端数据库（IndexedDB），同时也支持使用服务端数据库（下简称 DB 版）。LobeChat 采用了 Postgres 作为后端存储数据库。\nPostgreSQL 是一种强大的开源关系型数据库管理系统，具备高度扩展性和标准 SQL 支持。它提供了丰富的数据类型、并发处理、数据完整性、安全性及可编程性，适用于复杂应用和大规模数据管理。\n对于 LobeChat 的 DB 版，正常的部署流程都需要包含三个模块的配置：\n数据库配置； 身份验证服务配置； S3 存储服务配置。 同时，由于 LobeChat 支持了文件对话 / 知识库对话的能力，因此我们需要为 Postgres 安装 pgvector 插件，该插件提供了向量搜索的能力，是 LobeChat 实现 RAG 的重要构件之一。\n身份验证服务配置 身份验证服务的说明，我们可以参考这个文档，lobechat在实现的时候主要集成了两个不同的身份验证服务，用于满足不同场景的诉求，一种是 Clerk ，另外一种是 NextAuth。\nClerk 是一个身份验证 SaaS 服务，提供了开箱即用的身份验证能力，产品化程度很高，集成成本较低，体验很好。官方提供的 LobeChat Cloud，就是使用了 Clerk 作为身份验证服务。\nNextAuth 是一个开源的身份验证库，支持多种身份验证提供商，包括 Auth0、Cognito、GitHub、Google、Facebook、Apple、Twitter 等。NextAuth 本身提供了一套完整的身份验证解决方案，包括用户注册、登录、密码找回、多种身份验证提供商的集成等。\n在官方的 Docker 镜像 lobe-chat-database 中，官方推荐使用 NextAuth 作为身份验证服务。\nS3 存储服务配置 LobeChat 在 很早以前 就支持了多模态的 AI 会话，其中涉及到图片上传给大模型的功能。在客户端数据库方案中，图片文件直接以二进制数据存储在浏览器 IndexedDB 数据库，但在服务端数据库中这个方案并不可行。因为在 Postgres 中直接存储文件类二进制数据会大大浪费宝贵的数据库存储空间，并拖慢计算性能。\n这块最佳实践是使用文件存储服务（S3）来存储图片文件，同时 S3 也是文件上传 / 知识库功能所依赖的大容量静态文件存储方案。\n官方文档中的 S3 所指代的是指兼容 S3 存储方案，即支持 Amazon S3 API 的对象存储系统，常见例如 Cloudflare R2 、阿里云 OSS，可以自部署的 minio 等均支持 S3 兼容 API。\n这里我们使用的私有化部署方案，可以自己对接各个云厂商的S3存储服务，也可以自己部署一个minio服务。（本文以私有化部署minio为例）\n部署方式 官方支持了很多个平台的部署方式并且有相关的指引文档，因为这里涉及到多个服务的部署，个人认为比较方便管理维护的方式应该是使用docker-compose来统一部署维护，因此这里我们选择使用docker-compose模式来进行部署。\n安装docker engine 以ubuntu系统为例，docker的安装我们可以参考官网\nroot@infra-ubuntu:~/lobechat# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 24.04.1 LTS Release: 24.04 Codename: noble 首先配置docker的 apt 仓库。\n# Add Docker\u0026#39;s official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \\ \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \\ $(. /etc/os-release \u0026amp;\u0026amp; echo \u0026#34;$VERSION_CODENAME\u0026#34;) stable\u0026#34; | \\ sudo tee /etc/apt/sources.list.d/docker.list \u0026gt; /dev/null sudo apt-get update 然后直接安装docker\nsudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 安装完成之后可以拉取 hello-world 镜像来检查docker是否正常工作。\n$ sudo docker run hello-world 还可以使用这两个命令检查docker的状态\nroot@infra-ubuntu:~# docker --version Docker version 27.5.1, build 9f9e405 root@infra-ubuntu:~# docker info 安装docker-compose docker-compose的部署安装，我们可以参考这个文档\ncurl -SL https://github.com/docker/compose/releases/download/v2.32.4/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose 部署完成之后，我们进行检查\nroot@infra-ubuntu:~# docker-compose --version Docker Compose version v2.32.4 部署 LobeChat DB 版 生成docker-compose文件 部署流程参考官方文档\n先建一个目录用来存放部署的各种文件\nmkdir lobechat 下载官方的各个部署文件并直接启动服务\nroot@infra-ubuntu:~/lobechat# bash \u0026lt;(curl -fsSL https://raw.githubusercontent.com/lobehub/lobe-chat/HEAD/docker-compose/local/setup.sh) -f -l zh_CN docker-compose.yml 100%[====================================================================================================\u0026gt;] 2.95K --.-KB/s in 0s .env 100%[====================================================================================================\u0026gt;] 1.03K --.-KB/s in 0s init_data.json 100%[====================================================================================================\u0026gt;] 25.38K --.-KB/s in 0.05s s3_data.tar.gz 100%[====================================================================================================\u0026gt;] 11.77K --.-KB/s in 0.05s s3_data.tar.gz 解压成功到目录： . 重新生成安全密钥... 安全密钥生成结果如下： Casdoor: - Username: admin - Password: bac064 - Client Secret: 85db9fa40ab8155d26fed8c179906933 您已经完成了所有配置。请运行以下命令启动LobeChat： docker compose up -d 完整的环境变量在\u0026#39;.env\u0026#39;中可以在文档中找到：https://lobehub.com/zh/docs/self-hosting/environment-variables 警告：如果你正在生产环境中使用，请在日志中检查密钥是否已经生成！！！ 然后直接部署第一次\nroot@infra-ubuntu:~/lobechat# docker compose up -d \u0026lt;......\u0026gt; [+] Running 6/6 ✔ Network lobe-chat-database_lobe-network Created 0.1s ✔ Container lobe-network Started 1.0s ✔ Container lobe-postgres Healthy 7.0s ✔ Container lobe-casdoor Started 5.8s ✔ Container lobe-minio Started 0.3s ✔ Container lobe-chat Started 6.4s 接着，你需要修改下载下来的 docker-compose.yml 文件，执行一次全文替换，将 localhost 替换为 your_server_ip，随后重新启动：\nroot@infra-ubuntu:~/lobechat# sed -i \u0026#39;s/localhost/10.31.100.3/g\u0026#39; docker-compose.yml 再次启动\nroot@infra-ubuntu:~/lobechat# docker compose up -d [+] Running 5/5 ✔ Container lobe-network Running 0.0s ✔ Container lobe-postgres Healthy 11.2s ✔ Container lobe-chat Started 11.3s ✔ Container lobe-minio Started 0.1s ✔ Container lobe-casdoor Started 0.6s 配置casdoor 使用 setup.sh 脚本启动后，Casdoor WebUI 默认端口为 8000，你可以通过 http://your_server_ip:8000 访问，默认用户名和密码不要看lobechat官方文档里面的指示，不一定是对的，建议查看初始化过程中生产的init_data.json文件，里面有user字段，包含用户的用户名和密码，例如我这里的用户名和密码字段为：\n\u0026#34;name\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;bac064\u0026#34;, 在 身份认证 -\u0026gt; 应用 中找到lobechat\n找到重定向URLs这个配置，修改里面的localhost为我们的服务器IP\nhttp://your_server_ip:3210/api/auth/callback/casdoor 其他配置大多保持默认即可，你也可以在 身份认证 -\u0026gt; 应用 中修改默认配置\n配置 MinIO S3 使用 setup.sh 脚本启动后，MinIO WebUI 默认端口为 9001，你可以通过 http://your_server_ip:9001 访问，默认用户名 YOUR_MINIO_USER，密码 YOUR_MINIO_PASSWORD\n密码是存储在初始化配置里面的.env文件中的，可以查看里面的字段获得\nroot@infra-ubuntu:~/lobechat# cat .env | grep MINIO MINIO_PORT=9000 MINIO_ROOT_USER=YOUR_MINIO_USER MINIO_ROOT_PASSWORD=YOUR_MINIO_PASSWORD MINIO_LOBE_BUCKET=lobe 登录进去之后，我们可以看到已经创建给 lobechat 使用的 bucket ，这里再创建一个 bucket 给 casdoor 使用\n这里我们创建一个casdoor的bucket，注意如果修改了名字，下面配置中的casdoor也要一并修改。\n创建成功之后点击进去bucket详情，在Summary里面，点击Access Policy，选择custom，贴入下面的内容，然后保存\n{ \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Principal\u0026#34;: { \u0026#34;AWS\u0026#34;: [\u0026#34;*\u0026#34;] }, \u0026#34;Action\u0026#34;: [\u0026#34;s3:GetBucketLocation\u0026#34;], \u0026#34;Resource\u0026#34;: [\u0026#34;arn:aws:s3:::casdoor\u0026#34;] }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Principal\u0026#34;: { \u0026#34;AWS\u0026#34;: [\u0026#34;*\u0026#34;] }, \u0026#34;Action\u0026#34;: [\u0026#34;s3:ListBucket\u0026#34;], \u0026#34;Resource\u0026#34;: [\u0026#34;arn:aws:s3:::casdoor\u0026#34;], \u0026#34;Condition\u0026#34;: { \u0026#34;StringEquals\u0026#34;: { \u0026#34;s3:prefix\u0026#34;: [\u0026#34;files/*\u0026#34;] } } }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Principal\u0026#34;: { \u0026#34;AWS\u0026#34;: [\u0026#34;*\u0026#34;] }, \u0026#34;Action\u0026#34;: [\u0026#34;s3:PutObject\u0026#34;, \u0026#34;s3:DeleteObject\u0026#34;, \u0026#34;s3:GetObject\u0026#34;], \u0026#34;Resource\u0026#34;: [\u0026#34;arn:aws:s3:::casdoor/**\u0026#34;] } ], \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34; } 然后我们创建一个AccessKey\n注意退出这个页面之后不能再查看SecretKey的内容，需要保存好密钥，如下所示为之前创建的密钥。\ntfIcV3Gl7CjcHJkh5OsZ DuNmfJykcoFTVp8Tl3DEzIE20Jvvl9ii5N2pUn5p 在 Casdoor 的 身份认证 -\u0026gt; 提供商 中关联 MinIO S3 服务，注意分类要选择Storage，客户端 ID、客户端密钥为上一步创建的访问密钥中的 Access Key 和 Secret Key，10.31.100.3 应当被替换为实际部署MinIO服务的服务IP。\n以下是一个示例配置：\n在 Casdoor 的 身份认证 -\u0026gt; 应用 中，对 app-built-in 应用添加提供商，选择 minio，保存并退出\n我们可以在 Casdoor 的 身份认证 -\u0026gt; 资源 中，尝试上传文件以测试配置是否正确\n也可以直接测试是否可以修改头像，在用户管理里面，选择用户，然后点击编辑，就可以修改头像了。\n配置文件 最后附上完整的配置文件作为参考，注意里面的配置参数（如IP地址、持久化映射目录、密钥等需要自行修改）\ndocker-compose.yml文件内容如下\nroot@infra-ubuntu:~/lobechat# cat docker-compose.yml name: lobe-chat-database services: network-service: image: alpine container_name: lobe-network ports: - \u0026#39;${MINIO_PORT}:${MINIO_PORT}\u0026#39; # MinIO API - \u0026#39;9001:9001\u0026#39; # MinIO Console - \u0026#39;${CASDOOR_PORT}:${CASDOOR_PORT}\u0026#39; # Casdoor - \u0026#39;${LOBE_PORT}:3210\u0026#39; # LobeChat command: tail -f /dev/null networks: - lobe-network postgresql: image: pgvector/pgvector:pg16 container_name: lobe-postgres ports: - \u0026#39;5432:5432\u0026#39; volumes: - \u0026#39;./data:/var/lib/postgresql/data\u0026#39; environment: - \u0026#39;POSTGRES_DB=${LOBE_DB_NAME}\u0026#39; - \u0026#39;POSTGRES_PASSWORD=${POSTGRES_PASSWORD}\u0026#39; healthcheck: test: [\u0026#39;CMD-SHELL\u0026#39;, \u0026#39;pg_isready -U postgres\u0026#39;] interval: 5s timeout: 5s retries: 5 restart: always networks: - lobe-network minio: image: minio/minio container_name: lobe-minio network_mode: \u0026#39;service:network-service\u0026#39; volumes: - \u0026#39;./s3_data:/etc/minio/data\u0026#39; environment: - \u0026#39;MINIO_ROOT_USER=${MINIO_ROOT_USER}\u0026#39; - \u0026#39;MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}\u0026#39; - \u0026#39;MINIO_API_CORS_ALLOW_ORIGIN=http://10.31.100.3:${LOBE_PORT}\u0026#39; restart: always command: \u0026gt; server /etc/minio/data --address \u0026#34;:${MINIO_PORT}\u0026#34; --console-address \u0026#34;:9001\u0026#34; casdoor: image: casbin/casdoor container_name: lobe-casdoor entrypoint: /bin/sh -c \u0026#39;./server --createDatabase=true\u0026#39; network_mode: \u0026#39;service:network-service\u0026#39; depends_on: postgresql: condition: service_healthy environment: RUNNING_IN_DOCKER: \u0026#39;true\u0026#39; driverName: \u0026#39;postgres\u0026#39; dataSourceName: \u0026#39;user=postgres password=${POSTGRES_PASSWORD} host=postgresql port=5432 sslmode=disable dbname=casdoor\u0026#39; origin: \u0026#39;http://10.31.100.3:${CASDOOR_PORT}\u0026#39; runmode: \u0026#39;dev\u0026#39; volumes: - ./init_data.json:/init_data.json lobe: image: lobehub/lobe-chat-database container_name: lobe-chat network_mode: \u0026#39;service:network-service\u0026#39; depends_on: postgresql: condition: service_healthy network-service: condition: service_started minio: condition: service_started casdoor: condition: service_started environment: - \u0026#39;APP_URL=http://10.31.100.3:3210\u0026#39; - \u0026#39;NEXT_AUTH_SSO_PROVIDERS=casdoor\u0026#39; - \u0026#39;KEY_VAULTS_SECRET=Kix2wcUONd4CX51E/ZPAd36BqM4wzJgKjPtz2sGztqQ=\u0026#39; - \u0026#39;NEXT_AUTH_SECRET=NX2kaPE923dt6BL2U8e9oSre5RfoT7hg\u0026#39; - \u0026#39;AUTH_URL=http://10.31.100.3:${LOBE_PORT}/api/auth\u0026#39; - \u0026#39;AUTH_CASDOOR_ISSUER=http://10.31.100.3:${CASDOOR_PORT}\u0026#39; - \u0026#39;DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgresql:5432/${LOBE_DB_NAME}\u0026#39; - \u0026#39;S3_ENDPOINT=http://10.31.100.3:${MINIO_PORT}\u0026#39; - \u0026#39;S3_BUCKET=${MINIO_LOBE_BUCKET}\u0026#39; - \u0026#39;S3_PUBLIC_DOMAIN=http://10.31.100.3:${MINIO_PORT}\u0026#39; - \u0026#39;S3_ENABLE_PATH_STYLE=1\u0026#39; - \u0026#39;LLM_VISION_IMAGE_USE_BASE64=1\u0026#39; env_file: - .env restart: always volumes: data: driver: local s3_data: driver: local networks: lobe-network: driver: bridge .env文件内容如下\nroot@infra-ubuntu:~/lobechat# cat .env # Proxy, if you need it # HTTP_PROXY=http://localhost:7890 # HTTPS_PROXY=http://localhost:7890 # Other environment variables, as needed. You can refer to the environment variables configuration for the client version, making sure not to have ACCESS_CODE. # OPENAI_API_KEY=sk-xxxx # OPENAI_PROXY_URL=https://api.openai.com/v1 # OPENAI_MODEL_LIST=... # =========================== # ====== Preset config ====== # =========================== # if no special requirements, no need to change LOBE_PORT=3210 CASDOOR_PORT=8000 MINIO_PORT=9000 # Postgres related, which are the necessary environment variables for DB LOBE_DB_NAME=lobechat POSTGRES_PASSWORD=uWNZugjBqixf8dxC # Casdoor secret AUTH_CASDOOR_ID=a387a4892ee19b1a2249 AUTH_CASDOOR_SECRET=85db9fa40ab8155d26fed8c179906933 # MinIO S3 configuration MINIO_ROOT_USER=YOUR_MINIO_USER MINIO_ROOT_PASSWORD=YOUR_MINIO_PASSWORD # Configure the bucket information of MinIO MINIO_LOBE_BUCKET=lobe S3_ACCESS_KEY_ID=soaucnP8Bip0TDdUjxng S3_SECRET_ACCESS_KEY=ZPUzvY34umfcfxvWKSv0P00vczVMB6YmgJS5J9eO 另外需要注意的是init_data.json这个文件是casdoor组件初始化过程中产生的，存储着不少重要信息，切记不要随意删除。\n","permalink":"http://localhost:9011/20250126-lobechat_user_db_mode_deploy/","summary":"\u003cp\u003eLobeChat 是一款现代化设计的开源 ChatGPT/LLMs 聊天应用与开发框架，也是一个支持语音合成、多模态、可扩展的（function call）插件系统。LobeChat 默认使用客户端数据库（IndexedDB），同时也支持使用服务端数据库（下简称 DB 版）。\n本文将以docker-compose的部署方式为例介绍如何私有化部署lobechat的DB版本。\u003c/p\u003e","title":"lobechat数据库版本私有化部署"},{"content":"华为的SP333和SP380网卡应该就是MCX4121A-XCAT和MCX4121A-ACAT的OEM版本，本文主要介绍如何将10G的网卡SP333刷入25G网卡固件从而变为25G网卡。\n准备工作 固件我们直接去官网下载\n# 官方的固件下载地址 https://network.nvidia.com/support/firmware/connectx4lxen/ # 刷固件需要用到的MFT工具 https://network.nvidia.com/products/adapter-software/firmware-tools/ # 网卡驱动 https://network.nvidia.com/products/adapter-software/ethernet/windows/winof-2/ 华子的网卡固件在官网没办法下载，但是在超聚合官网可以下载\nhttps://support.xfusion.com/support/#/zh/software-basics/d4b7de78-c4c0-46db-bbbd-98926e13e2da 关于SP333和SP380这两个型号的华子官网介绍\n# SP333是2*10GE SFP+，向下兼容1GE速率（使用10GE SFP+光模块）。 https://support.huawei.com/enterprise/zh/doc/EDOC1100198574/672e0978 # SP380是2*25GE SFP28，向下兼容10GE速率（使用10G SFP+光模块）。 https://support.huawei.com/enterprise/zh/doc/EDOC1100056515/c20b2897 根据之前对MCX4网卡的了解，这两个应该就是MCX4121A-XCAT和MCX4121A-ACAT的OEM版本\nhttps://network.nvidia.com/files/doc-2020/pb-connectx-4-lx-en-card.pdf 这里需要提前说一下结论，这里刷固件的时候分别刷入了mellanox官方的MCX4121A-ACAT固件和华为的SP380固件：\n刷入mellanox官方固件的时候，网卡只有一个网口能正常识别到模块，另一个网口在系统中能看到网卡，但是无法识别线缆（手头的AOC和DAC都不行，换另一个网口则都正常），并且正常识别的网卡在win11中没办法正常通过DHCP获取IP（其他系统暂时没测试）\n刷入华为的SP380固件的时候，网卡信息能正常识别，两个网口各项功能看起来正常。建议还是刷入华为的固件比较稳妥。\nwindows 刷机 刷机命令参考如下\nflint --no_fw_ctrl -allow_psid_change -d {设备号} -i {文件路径(注:这里最好为根目录下,如不为根目录注意目录名中不要含有中文)} burn 这里我用powershell执行的命令参考如下：\nPS C:\\Users\\Mr7th\\Desktop\\NIC-SP380-CX4Lx-FW-14.31.1014\u0026gt; flint --no_fw_ctrl -allow_psid_change -d mt4117_pciconf0 -i .\\fw-ConnectX4Lx-rel-14_31_1014_H_SP380-FlexBoot-3.6.403_4117_14_24_13_RELEASE_X64_AARCH64_20210706_VPD.bin burn Done. Current FW version on flash: 14.32.1900 New FW version: 14.31.1014 Note: The new FW version is older than the current FW version on flash. Do you want to continue ? (y/n) [n] : y You are about to replace current PSID on flash - \u0026#34;MT_2420110034\u0026#34; with a different PSID - \u0026#34;HUA0000000023\u0026#34;. Note: It is highly recommended not to change the PSID. Do you want to continue ? (y/n) [n] : y Burning FW image without signatures - OK Restoring signature - OK -I- To load new FW run mlxfwreset or reboot machine. 刷固件需要等待挺长一段时间的，要有耐心，刷完之后我们使用mlxfwmanager命令检查当前固件信息\nPS C:\\Users\\Mr7th\\Desktop\\NIC-SP380-CX4Lx-FW-14.31.1014\u0026gt; mlxfwmanager Querying Mellanox devices firmware ... Device #1: ---------- Device Type: ConnectX4LX Part Number: MCX4121A-ACA_Ax Description: ConnectX-4 Lx EN network interface card; 25GbE dual-port SFP28; PCIe3.0 x8; ROHS R6 PSID: MT_2420110034 PCI Device Name: mt4117_pciconf0 Base MAC: N/A Versions: Current Available FW 14.31.1014 N/A FW (Running) 14.32.1900 N/A PXE 3.6.0502 N/A UEFI 14.25.0017 N/A Status: No matching image found 看起来没问题，我们直接重启电脑，再看看网卡的信息\nPS C:\\Users\\Mr7th\u0026gt; mlxfwmanager Querying Mellanox devices firmware ... Device #1: ---------- Device Type: ConnectX4LX Part Number: Huawei_SP380_ConnectX4Lx_2P_25GbE_Ax Description: Huawei SP380 2-port 25G Ethernet Adapter PCIE Gen3 x8 PSID: HUA0000000023 PCI Device Name: mt4117_pciconf0 Base MAC: N/A Versions: Current Available FW 14.31.1014 N/A PXE 3.6.0403 N/A UEFI 14.24.0013 N/A Status: No matching image found PS C:\\Users\\Mr7th\u0026gt; flint -d mt4117_pciconf0 q Image type: FS3 FW Version: 14.31.1014 FW Release Date: 30.6.2021 Product Version: 14.31.1014 Rom Info: type=UEFI version=14.24.13 cpu=AMD64,AARCH64 type=PXE version=3.6.403 cpu=AMD64 Description: UID GuidsNumber Base GUID: N/A 8 Base MAC: N/A 8 Image VSD: N/A Device VSD: N/A PSID: HUA0000000023 Security Attributes: N/A 按下win+R键，输入ncpa.cpl进去管理界面查看网卡信息\n可以看到这两个网卡都正常协商到了10G速率（只有10G交换机），并且都通过DHCP的方式正常获取到了IP、DNS等信息\n也可以通过下面这两个命令来查看网卡的速率信息\nPS C:\\Users\\Mr7th\u0026gt; wmic nic where netEnabled=true get name,speed Name Speed Realtek Gaming 2.5GbE Family Controller #2 2500000000 Mellanox ConnectX-4 Lx Ethernet Adapter #5 10000000000 Mellanox ConnectX-4 Lx Ethernet Adapter #6 10000000000 PS C:\\Users\\Mr7th\u0026gt; Get-NetAdapter | select interfaceDescription,name,status,linkSpeed interfaceDescription name Status LinkSpeed -------------------- ---- ------ --------- Mellanox ConnectX-4 Lx Ethernet Adapter #6 以太网 9 Up 10 Gbps Mellanox ConnectX-4 Lx Ethernet Adapter #5 以太网 8 Up 10 Gbps Intel(R) Wi-Fi 6E AX211 160MHz AX211 Disconnected 0 bps Bluetooth Device (Personal Area Network) #3 蓝牙网络连接 3 Disconnected 3 Mbps Realtek Gaming 2.5GbE Family Controller #2 Realtek Gaming 2.5GbE Up 2.5 Gbps 进到unraid里面之后我们能看到速率有25G的选项，说明正常\nroot@tiny-unraid:~# ethtool eth0 Settings for eth0: Supported ports: [ Backplane ] Supported link modes: 1000baseKX/Full 10000baseKR/Full 25000baseCR/Full 25000baseKR/Full 25000baseSR/Full Supported pause frame use: Symmetric Supports auto-negotiation: Yes Supported FEC modes: None RS BASER Advertised link modes: 1000baseKX/Full 10000baseKR/Full 25000baseCR/Full 25000baseKR/Full 25000baseSR/Full Advertised pause frame use: Symmetric Advertised auto-negotiation: Yes Advertised FEC modes: None Speed: 10000Mb/s Duplex: Full Auto-negotiation: on Port: Direct Attach Copper PHYAD: 0 Transceiver: internal Supports Wake-on: d Wake-on: d Link detected: yes root@tiny-unraid:~# ethtool eth1 Settings for eth1: Supported ports: [ Backplane ] Supported link modes: 1000baseKX/Full 10000baseKR/Full 25000baseCR/Full 25000baseKR/Full 25000baseSR/Full Supported pause frame use: Symmetric Supports auto-negotiation: Yes Supported FEC modes: None RS BASER Advertised link modes: 1000baseKX/Full 10000baseKR/Full 25000baseCR/Full 25000baseKR/Full 25000baseSR/Full Advertised pause frame use: Symmetric Advertised auto-negotiation: Yes Advertised FEC modes: None Speed: 10000Mb/s Duplex: Full Auto-negotiation: on Port: Direct Attach Copper PHYAD: 0 Transceiver: internal Supports Wake-on: d Wake-on: d Link detected: yes unraid 刷机 unraid原生并不自带mft刷机工具，因此我们要到APPS里面找到Mellanox-Firmware-Tools并下载安装，这个是MFT的一个开源实现版本，大部分功能逻辑和官方版本是几乎一致的。\n开始之前，先使用ethtool工具可以查看网卡信息\nroot@tiny-unraid:~# ethtool -i eth1 driver: mlx5_core version: 6.6.68-Unraid firmware-version: 14.17.1010 (HUA0010110034) expansion-rom-version: bus-info: 0000:01:00.1 supports-statistics: yes supports-test: yes supports-eeprom-access: no supports-register-dump: no supports-priv-flags: yes 通过系统自带的lspci查看网卡信息\nroot@tiny-unraid:~# lspci -nnv 01:00.0 Ethernet controller [0200]: Mellanox Technologies MT27710 Family [ConnectX-4 Lx] [15b3:1015] Subsystem: Mellanox Technologies Device [15b3:df30] Flags: bus master, fast devsel, latency 0, IRQ 16, IOMMU group 14 Memory at 60e2000000 (64-bit, prefetchable) [size=32M] Expansion ROM at 80e00000 [disabled] [size=1M] Capabilities: [60] Express Endpoint, IntMsgNum 0 Capabilities: [48] Vital Product Data Capabilities: [9c] MSI-X: Enable+ Count=64 Masked- Capabilities: [c0] Vendor Specific Information: Len=18 \u0026lt;?\u0026gt; Capabilities: [40] Power Management version 3 Capabilities: [100] Alternative Routing-ID Interpretation (ARI) Capabilities: [110] Advanced Error Reporting Capabilities: [180] Single Root I/O Virtualization (SR-IOV) Capabilities: [1c0] Secondary PCI Express Capabilities: [230] Access Control Services Kernel driver in use: vfio-pci Kernel modules: mlx5_core 01:00.1 Ethernet controller [0200]: Mellanox Technologies MT27710 Family [ConnectX-4 Lx] [15b3:1015] Subsystem: Mellanox Technologies Device [15b3:df30] Flags: bus master, fast devsel, latency 0, IRQ 17, IOMMU group 15 Memory at 60e0000000 (64-bit, prefetchable) [size=32M] Expansion ROM at 80d00000 [disabled] [size=1M] Capabilities: [60] Express Endpoint, IntMsgNum 0 Capabilities: [48] Vital Product Data Capabilities: [9c] MSI-X: Enable+ Count=64 Masked- Capabilities: [c0] Vendor Specific Information: Len=18 \u0026lt;?\u0026gt; Capabilities: [40] Power Management version 3 Capabilities: [100] Alternative Routing-ID Interpretation (ARI) Capabilities: [110] Advanced Error Reporting Capabilities: [180] Single Root I/O Virtualization (SR-IOV) Capabilities: [230] Access Control Services Kernel driver in use: mlx5_core Kernel modules: mlx5_core 查看网卡信息\nroot@tiny-unraid:~# mstconfig -d 01:00.0 query Device #1: ---------- Device type: ConnectX4LX Name: N/A Description: N/A Device: 01:00.0 Configurations: Next Boot ROCE_NEXT_PROTOCOL 254 NUM_PF_MSIX_VALID True(1) NUM_OF_VFS 8 NUM_OF_PF 2 SRIOV_EN True(1) PF_LOG_BAR_SIZE 5 VF_LOG_BAR_SIZE 0 NUM_PF_MSIX 63 NUM_VF_MSIX 11 INT_LOG_MAX_PAYLOAD_SIZE AUTOMATIC(0) CQE_COMPRESSION BALANCED(0) PCI_ATOMIC_MODE PCI_ATOMIC_DISABLED_EXT_ATOMIC_ENABLED(0) LRO_LOG_TIMEOUT0 6 LRO_LOG_TIMEOUT1 7 LRO_LOG_TIMEOUT2 8 LRO_LOG_TIMEOUT3 12 LOG_DCR_HASH_TABLE_SIZE 14 MAX_PACKET_LIFETIME 61 DCR_LIFO_SIZE 16384 NUM_OF_PLANES_P1 0 NUM_OF_PLANES_P2 0 IB_PROTO_WIDTH_EN_MASK_P1 0 IB_PROTO_WIDTH_EN_MASK_P2 0 ROCE_CC_PRIO_MASK_P1 0 ROCE_CC_CNP_MODERATION_P1 DEVICE_DEFAULT(0) ROCE_CC_PRIO_MASK_P2 0 ROCE_CC_CNP_MODERATION_P2 DEVICE_DEFAULT(0) CLAMP_TGT_RATE_AFTER_TIME_INC_P1 True(1) CLAMP_TGT_RATE_P1 False(0) RPG_TIME_RESET_P1 600 RPG_BYTE_RESET_P1 32767 RPG_THRESHOLD_P1 5 RPG_MAX_RATE_P1 0 RPG_AI_RATE_P1 5 RPG_HAI_RATE_P1 50 RPG_GD_P1 11 RPG_MIN_DEC_FAC_P1 50 RPG_MIN_RATE_P1 1 RATE_TO_SET_ON_FIRST_CNP_P1 100 DCE_TCP_G_P1 4 DCE_TCP_RTT_P1 1 RATE_REDUCE_MONITOR_PERIOD_P1 4 INITIAL_ALPHA_VALUE_P1 0 MIN_TIME_BETWEEN_CNPS_P1 0 CNP_802P_PRIO_P1 0 CNP_DSCP_P1 7 CLAMP_TGT_RATE_AFTER_TIME_INC_P2 True(1) CLAMP_TGT_RATE_P2 False(0) RPG_TIME_RESET_P2 600 RPG_BYTE_RESET_P2 32767 RPG_THRESHOLD_P2 5 RPG_MAX_RATE_P2 0 RPG_AI_RATE_P2 5 RPG_HAI_RATE_P2 50 RPG_GD_P2 11 RPG_MIN_DEC_FAC_P2 50 RPG_MIN_RATE_P2 1 RATE_TO_SET_ON_FIRST_CNP_P2 100 DCE_TCP_G_P2 4 DCE_TCP_RTT_P2 1 RATE_REDUCE_MONITOR_PERIOD_P2 4 INITIAL_ALPHA_VALUE_P2 0 MIN_TIME_BETWEEN_CNPS_P2 0 CNP_802P_PRIO_P2 0 CNP_DSCP_P2 7 LLDP_NB_DCBX_P1 False(0) LLDP_NB_RX_MODE_P1 OFF(0) LLDP_NB_TX_MODE_P1 OFF(0) LLDP_NB_DCBX_P2 False(0) LLDP_NB_RX_MODE_P2 OFF(0) LLDP_NB_TX_MODE_P2 OFF(0) ROCE_RTT_RESP_DSCP_P1 0 ROCE_RTT_RESP_DSCP_MODE_P1 DEVICE_DEFAULT(0) ROCE_RTT_RESP_DSCP_P2 0 ROCE_RTT_RESP_DSCP_MODE_P2 DEVICE_DEFAULT(0) DCBX_IEEE_P1 True(1) DCBX_CEE_P1 True(1) DCBX_WILLING_P1 True(1) DCBX_IEEE_P2 True(1) DCBX_CEE_P2 True(1) DCBX_WILLING_P2 True(1) KEEP_ETH_LINK_UP_P1 False(0) KEEP_IB_LINK_UP_P1 False(0) KEEP_LINK_UP_ON_BOOT_P1 False(0) KEEP_LINK_UP_ON_STANDBY_P1 False(0) KEEP_ETH_LINK_UP_P2 False(0) KEEP_IB_LINK_UP_P2 False(0) KEEP_LINK_UP_ON_BOOT_P2 False(0) KEEP_LINK_UP_ON_STANDBY_P2 False(0) NUM_OF_VL_P1 _4_VLs(3) NUM_OF_TC_P1 _8_TCs(0) NUM_OF_PFC_P1 8 NUM_OF_VL_P2 _4_VLs(3) NUM_OF_TC_P2 _8_TCs(0) NUM_OF_PFC_P2 8 DUP_MAC_ACTION_P1 LAST_CFG(0) DUP_MAC_ACTION_P2 LAST_CFG(0) PORT_OWNER True(1) ALLOW_RD_COUNTERS True(1) RENEG_ON_CHANGE True(1) TRACER_ENABLE False(0) IP_VER IPv4(0) BOOT_UNDI_NETWORK_WAIT 0 BOOT_VLAN 1 LEGACY_BOOT_PROTOCOL PXE(1) BOOT_VLAN_EN False(0) BOOT_PKEY 0 查看固件版本信息\nroot@tiny-unraid:~# mstflint -d 01:00.0 q Image type: FS3 FW Version: 14.17.1010 FW Release Date: 22.9.2016 Rom Info: type=PXE version=3.4.903 Description: UID GuidsNumber Base GUID: e868190300c8f3b8 8 Orig Base GUID: N/A 8 Base MAC: e86819c8ebcc 8 Orig Base MAC: N/A 8 Image VSD: N/A Device VSD: N/A PSID: HUA0010110034 Security Attributes: N/A 检查image信息\nroot@tiny-unraid:~# mstflint -d 01:00.0 v FS3 failsafe image /0x00000038-0x0000173b (0x001704)/ (BOOT2) - OK /0x00002000-0x0000201f (0x000020)/ (ITOC_HEADER) - OK /0x00004000-0x000040ff (0x000100)/ (RESET_INFO) - OK /0x00005000-0x00005fff (0x001000)/ (FW_MAIN_CFG) - OK /0x00006000-0x000064bf (0x0004c0)/ (FW_BOOT_CFG) - OK /0x00007000-0x00007aff (0x000b00)/ (HW_MAIN_CFG) - OK /0x00008000-0x0000813f (0x000140)/ (HW_BOOT_CFG) - OK /0x00009000-0x0000c4ff (0x003500)/ (PHY_UC_CONSTS) - OK /0x0000d000-0x0000d13f (0x000140)/ (IMAGE_SIGNATURE_256) - OK /0x0000e000-0x0000e13f (0x000140)/ (PUBLIC_KEYS_2048) - OK /0x0000f000-0x00030a13 (0x021a14)/ (ROM_CODE) - OK /0x00031000-0x0003133f (0x000340)/ (PCIE_PHY_UC_CODE) - OK /0x00032000-0x00039d7f (0x007d80)/ (PHY_UC_CODE) - OK /0x0003a000-0x00063b33 (0x029b34)/ (PCI_CODE) - OK /0x00064000-0x00074f03 (0x010f04)/ (IRON_PREP_CODE) - OK /0x00075000-0x0029c6ff (0x227700)/ (MAIN_CODE) - OK /0x0029d000-0x002aca23 (0x00fa24)/ (PCIE_LINK_CODE) - OK /0x002ad000-0x002b0487 (0x003488)/ (POST_IRON_BOOT_CODE) - OK /0x002b1000-0x002b3ba7 (0x002ba8)/ (UPGRADE_CODE) - OK /0x002b4000-0x002b43ff (0x000400)/ (IMAGE_INFO) - OK /0x002b4400-0x002d364f (0x01f250)/ (FW_ADB) - OK /0x002d3650-0x002d3e27 (0x0007d8)/ (DBG_FW_INI) - OK /0x002d3e28-0x002d3e2f (0x000008)/ (DBG_FW_PARAMS) - OK /0x00fa0000-0x00faffff (0x010000)/ (NV_DATA) - CRC IGNORED /0x00fb0000-0x00fbffff (0x010000)/ (NV_DATA) - CRC IGNORED /0x00fc0000-0x00fcffff (0x010000)/ (FW_NV_LOG) - CRC IGNORED /0x00fee000-0x00fee1ff (0x000200)/ (DEV_INFO) - OK /0x00ff8000-0x00ff813f (0x000140)/ (MFG_INFO) - OK /0x00ff8140-0x00ff813f (0x000000)/ (VPD_R0) - OK -I- FW image verification succeeded. Image is bootable. 解压下载好的安装包\nroot@tiny-unraid:~# unzip NIC-SP380-CX4Lx-FW-14.31.1014.zip Archive: NIC-SP380-CX4Lx-FW-14.31.1014.zip inflating: version.xml inflating: install.sh inflating: fw-ConnectX4Lx-rel-14_31_1014_H_SP380-FlexBoot-3.6.403_4117_14_24_13_RELEASE_X64_AARCH64_20210706_VPD.bin creating: tools/ inflating: tools/mdevices_info inflating: tools/mstop inflating: tools/flint inflating: tools/flint_ext inflating: tools/minit inflating: tools/mst inflating: tools/mcra creating: drivers/ 同样的操作可以检查固件信息\nroot@tiny-unraid:~# mstflint -i fw-ConnectX4Lx-rel-14_31_1014_H_SP380-FlexBoot-3.6.403_4117_14_24_13_RELEASE_X64_AARCH64_20210706_VPD.bin v FS3 failsafe image /0x00000038-0x00001d97 (0x001d60)/ (BOOT2) - OK /0x00002000-0x0000201f (0x000020)/ (ITOC_HEADER) - OK /0x00004000-0x00013dd3 (0x00fdd4)/ (IRON_PREP_CODE) - OK /0x00014000-0x000140ff (0x000100)/ (RESET_INFO) - OK /0x00015000-0x00015bff (0x000c00)/ (FW_MAIN_CFG) - OK /0x00016000-0x0001647f (0x000480)/ (FW_BOOT_CFG) - OK /0x00017000-0x000185ff (0x001600)/ (HW_MAIN_CFG) - OK /0x00019000-0x0001913f (0x000140)/ (HW_BOOT_CFG) - OK /0x0001a000-0x0001cc7f (0x002c80)/ (PHY_UC_CONSTS) - OK /0x0001d000-0x0001d13f (0x000140)/ (IMAGE_SIGNATURE_256) - OK /0x0001e000-0x0001e8ff (0x000900)/ (PUBLIC_KEYS_2048) - OK /0x0001f000-0x0001f08f (0x000090)/ (FORBIDDEN_VERSIONS) - OK /0x00020000-0x0002023f (0x000240)/ (IMAGE_SIGNATURE_512) - OK /0x00021000-0x000220ff (0x001100)/ (PUBLIC_KEYS_4096) - OK /0x00023000-0x00072fff (0x050000)/ (PROGRAMMABLE_HW_FW) - OK /0x00073000-0x00123827 (0x0b0828)/ (ROM_CODE) - OK /0x00124000-0x00133fff (0x010000)/ (CRDUMP_MASK_DATA) - OK /0x00134000-0x001349ff (0x000a00)/ (PCIE_PHY_UC_CODE) - OK /0x00135000-0x0013e91f (0x009920)/ (PHY_UC_CODE) - OK /0x0013f000-0x0016a4e7 (0x02b4e8)/ (PCI_CODE) - OK /0x0016b000-0x004ab11f (0x340120)/ (MAIN_CODE) - OK /0x004ac000-0x004b9c5f (0x00dc60)/ (PCIE_LINK_CODE) - OK /0x004ba000-0x004bae3f (0x000e40)/ (POST_IRON_BOOT_CODE) - OK /0x004bb000-0x004bcbfb (0x001bfc)/ (UPGRADE_CODE) - OK /0x004bd000-0x004bd3ff (0x000400)/ (IMAGE_INFO) - OK /0x004bd400-0x004bdf53 (0x000b54)/ (DBG_FW_INI) - OK /0x004bdf54-0x004bdf5b (0x000008)/ (DBG_FW_PARAMS) - OK /0x00fa0000-0x00faffff (0x010000)/ (NV_DATA) - CRC IGNORED /0x00fb0000-0x00fbffff (0x010000)/ (NV_DATA) - CRC IGNORED /0x00fc0000-0x00fcffff (0x010000)/ (FW_NV_LOG) - CRC IGNORED /0x00fee000-0x00fee1ff (0x000200)/ (DEV_INFO) - OK /0x00ff8000-0x00ff813f (0x000140)/ (MFG_INFO) - OK /0x00ff8140-0x00ff81bf (0x000080)/ (VPD_R0) - OK -I- FW image verification succeeded. Image is bootable. 更新固件完成后\nroot@tiny-unraid:~# mstflint --no_fw_ctrl -allow_psid_change -d 01:00.0 -i fw-ConnectX4Lx-rel-14_31_1014_H_SP380-FlexBoot-3.6.403_4117_14_24_13_RELEASE_X64_AARCH64_20210706_VPD.bin burn Done. Current FW version on flash: 14.17.1010 New FW version: 14.31.1014 You are about to replace current PSID on flash - \u0026#34;HUA0010110034\u0026#34; with a different PSID - \u0026#34;HUA0000000023\u0026#34;. Note: It is highly recommended not to change the PSID. Do you want to continue ? (y/n) [n] : y Burning FW image without signatures - OK Restoring signature - OK -I- To load new FW run mstfwreset or reboot machine. 我们再次检查固件信息\nroot@tiny-unraid:~# mstflint -d 01:00.0 q Image type: FS3 FW Version: 14.31.1014 FW Version(Running): 14.17.1010 FW Release Date: 30.6.2021 Rom Info: type=UEFI version=14.24.13 cpu=AMD64,AARCH64 type=PXE version=3.6.403 cpu=AMD64 Description: UID GuidsNumber Base GUID: e868190300c8f3b8 8 Orig Base GUID: N/A 8 Base MAC: e86819c8ebcc 8 Orig Base MAC: N/A 8 Image VSD: N/A Device VSD: N/A PSID: HUA0000000023 Orig PSID: HUA0010110034 Security Attributes: N/A 注意这个时候固件可以看到running版本和非running版本，我们通过ethtool查看当前的固件版本会发现新刷入的固件版本并未生效，需要重启\nroot@tiny-unraid:~# ethtool -i eth1 driver: mlx5_core version: 6.6.68-Unraid firmware-version: 14.17.1010 (HUA0010110034) expansion-rom-version: bus-info: 0000:01:00.1 supports-statistics: yes supports-test: yes supports-eeprom-access: no supports-register-dump: no supports-priv-flags: yes 重启机器之后查看网卡信息可以发现固件版本和网卡速率都是正常\nroot@tiny-unraid:~# ethtool eth1 Settings for eth1: Supported ports: [ Backplane ] Supported link modes: 1000baseKX/Full 10000baseKR/Full 25000baseCR/Full 25000baseKR/Full 25000baseSR/Full Supported pause frame use: Symmetric Supports auto-negotiation: Yes Supported FEC modes: None RS BASER Advertised link modes: 1000baseKX/Full 10000baseKR/Full 25000baseCR/Full 25000baseKR/Full 25000baseSR/Full Advertised pause frame use: Symmetric Advertised auto-negotiation: Yes Advertised FEC modes: None Speed: 10000Mb/s Duplex: Full Auto-negotiation: on Port: Direct Attach Copper PHYAD: 0 Transceiver: internal Supports Wake-on: d Wake-on: d Link detected: yes root@tiny-unraid:~# ethtool -i eth1 driver: mlx5_core version: 6.6.68-Unraid firmware-version: 14.31.1014 (HUA0000000023) expansion-rom-version: bus-info: 0000:01:00.1 supports-statistics: yes supports-test: yes supports-eeprom-access: no supports-register-dump: no supports-priv-flags: yes 这里建议不要刷入mellanox官方的固件（虽然看起来网卡的速度正常，但是却无法正确识别到光模块和线缆），使用华为的官方固件即可，刷入的方法是一样的。\n","permalink":"http://localhost:9011/20241211-huawei-sp333-firmware-burn/","summary":"\u003cp\u003e华为的SP333和SP380网卡应该就是MCX4121A-XCAT和MCX4121A-ACAT的OEM版本，本文主要介绍如何将10G的网卡SP333刷入25G网卡固件从而变为25G网卡。\u003c/p\u003e","title":"华为SP333网卡刷入25G固件"},{"content":"将 raw 格式转换为 qcow2 格式是 KVM 虚拟化环境中常见的操作，本文主要介绍如何使用qemu工具将虚拟机的镜像文件从raw格式转换为qcow2格式。\nKVM 虚拟机硬盘文件格式 KVM 虚拟机可以使用多种格式的硬盘文件，其中 raw 和 qcow2 是两种最常用的格式。\nraw 格式 工作原理：\nraw 格式文件是原始磁盘镜像，它直接复制了物理硬盘的扇区数据，没有经过任何压缩或转换。因此，raw 文件的大小与它所代表的虚拟磁盘大小完全相同。\n特点：\n优点：\n高性能： 由于没有额外的转换层，raw 格式文件读写速度最快。 简单直接： 结构简单，易于理解和管理。 兼容性好： 可以被大多数虚拟化软件和工具识别。 缺点：\n空间占用大： 即使虚拟磁盘内部有很多空闲空间，raw 文件也会占用全部空间。 不支持快照： 无法创建磁盘快照，备份和恢复操作效率低。 安全性较低： raw 文件包含了所有数据，包括已删除的文件，存在数据泄露风险。 适用场景：\n对性能要求极高的虚拟机，例如数据库服务器。 需要将虚拟磁盘镜像直接复制到物理硬盘的场景。 需要与其他不支持 qcow2 格式的虚拟化软件或工具兼容的场景。 qcow2 格式 工作原理：\nqcow2 格式文件是 QEMU 镜像文件格式，它是一种精简配置的磁盘镜像，采用了多种技术来提高存储效率和性能。\n写时复制 (Copy-on-Write)： qcow2 文件初始大小很小，只有在写入数据时才会分配实际空间。 压缩： qcow2 文件支持多种压缩算法，可以有效减小文件大小。 快照： qcow2 文件支持创建多个磁盘快照，方便进行备份、恢复和测试。 特点：\n优点：\n节省存储空间： 只占用实际使用的磁盘空间，支持压缩。 支持快照： 方便备份、恢复和测试。 安全性更高： 可以加密镜像文件，保护数据安全。 高级功能： 支持 AES 加密、zlib 压缩等高级功能。 缺点：\n性能略低： 由于引入了额外的转换层，性能略低于 raw 格式。 兼容性稍差： 主要用于 KVM 和 QEMU 等虚拟化软件。 适用场景：\n大多数 KVM 虚拟机场景。 需要节省存储空间的场景。 需要频繁创建快照进行备份和测试的场景。 对安全性要求较高的场景。 总结： 特性 raw 格式 qcow2 格式 文件大小 与虚拟磁盘大小相同 远小于虚拟磁盘大小，按需分配 性能 最快 略慢于 raw 格式 快照 不支持 支持 压缩 不支持 支持 安全性 较低 较高 适用场景 对性能要求极高、需要直接复制到物理硬盘 大多数场景，节省空间、支持快照、安全性更高 总的来说，qcow2 格式在功能、效率和安全性方面都优于 raw 格式，除非对性能有较高的要求，不然一般推荐使用qcow2作为 KVM 虚拟机使用的硬盘文件格式。\n转换格式 检查硬盘 改造前我们先进入虚拟机中检查一下硬盘的相关信息：\n[root@infra-centos7 ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vda 253:0 0 150G 0 disk ├─vda1 253:1 0 1G 0 part /boot └─vda2 253:2 0 149G 0 part / [root@infra-centos7 ~]# blkid /dev/vda1: UUID=\u0026#34;763539f9-fc49-443a-a246-4cedb357dc92\u0026#34; TYPE=\u0026#34;xfs\u0026#34; /dev/vda2: UUID=\u0026#34;4110a890-d483-4d07-af95-dda95c600e96\u0026#34; TYPE=\u0026#34;xfs\u0026#34; [root@infra-centos7 ~]# df -Th | grep vda /dev/vda2 xfs 149G 70G 80G 47% / /dev/vda1 xfs 1014M 279M 736M 28% /boot 然后在宿主机上面查看文件信息\nroot@tiny-unraid:/mnt/user/domains/infra-centos7# qemu-img info vdisk1.img image: vdisk1.img file format: raw virtual size: 150 GiB (161061273600 bytes) disk size: 150 GiB 转换命令 接着使用这个命令进行转换\nroot@tiny-unraid:/mnt/user/domains/infra-centos7# qemu-img convert -f raw -O qcow2 -c vdisk1.img vdisk1.qcow2 让我们逐个分析命令中的参数：\nqemu-img convert: 这是 qemu-img 工具的子命令，用于转换磁盘镜像文件的格式。 -f raw: 指定源磁盘镜像文件的格式为 raw。 -O qcow2: 指定目标磁盘镜像文件的格式为 qcow2。 -c: 可选参数，表示在转换过程中对 qcow2 镜像进行压缩。 vdisk1.img: 源磁盘镜像文件的名称。 vdisk1.qcow2: 转换后的目标磁盘镜像文件的名称。 命令执行过程：\nqemu-img 读取源文件 vdisk1.img，该文件为 raw 格式。 qemu-img 将 raw 格式的数据转换为 qcow2 格式。 如果使用了 -c 参数，qemu-img 会在转换过程中对 qcow2 镜像进行压缩。 qemu-img 将转换后的 qcow2 格式数据写入目标文件 vdisk1.qcow2。 转换完成之后我们查看qcow2文件的信息，可以看到转换前后的文件大小有非常明显的差别\nroot@tiny-unraid:/mnt/user/domains/infra-centos7# ls -lh total 182G -rwxrwxr-x 1 root users 150G Jun 24 14:07 vdisk1.img* -rw-r--r-- 1 root root 32G Jun 24 15:07 vdisk1.qcow2 root@tiny-unraid:/mnt/user/domains/infra-centos7# qemu-img info vdisk1.qcow2 image: vdisk1.qcow2 file format: qcow2 virtual size: 150 GiB (161061273600 bytes) disk size: 31.5 GiB cluster_size: 65536 Format specific information: compat: 1.1 compression type: zlib lazy refcounts: false refcount bits: 16 corrupt: false extended l2: false 这里还需要注意，如果担心ls命令看到的文件大小并不是文件真实占用硬盘空间的大小，可以考虑使用du命令来检查\n默认情况下使用dd命令来生成一个raw文件，并不会马上占用对应的硬盘大小。（例如dd一个100G大小的raw文件，实际并不会马上占用100G的硬盘，只会随着实际使用而不断增长）\n但是还有一种特殊的情况就是使用rsync命令备份这些文件的时候，因为raw文件的特性，如果一个100G的raw硬盘文件，只使用了30G，实际只占用了30G硬盘，因为rsync过程中对文件的复制操作，会导致复制过后的新文件大小变为100G。\n修改xml配置 修改前xml文件 qemu转换完成之后我们需要修改kvm虚拟机的xml配置文件\n\u0026lt;disk type=\u0026#39;file\u0026#39; device=\u0026#39;disk\u0026#39;\u0026gt; \u0026lt;driver name=\u0026#39;qemu\u0026#39; type=\u0026#39;raw\u0026#39; cache=\u0026#39;writeback\u0026#39;/\u0026gt; \u0026lt;source file=\u0026#39;/mnt/user/domains/infra-centos7/vdisk1.img\u0026#39;/\u0026gt; \u0026lt;target dev=\u0026#39;hdc\u0026#39; bus=\u0026#39;virtio\u0026#39;/\u0026gt; \u0026lt;boot order=\u0026#39;1\u0026#39;/\u0026gt; \u0026lt;address type=\u0026#39;pci\u0026#39; domain=\u0026#39;0x0000\u0026#39; bus=\u0026#39;0x03\u0026#39; slot=\u0026#39;0x00\u0026#39; function=\u0026#39;0x0\u0026#39;/\u0026gt; \u0026lt;/disk\u0026gt; 解析一下上面的这段配置\n1. 硬盘缓存模式 cache='writeback'\nwriteback 模式下，数据写入磁盘镜像文件时会先缓存到主机内存，稍后再写入磁盘。 优点：提升写入性能。 缺点：存在数据丢失风险，如果主机崩溃，缓存中的数据将丢失。 建议：\n除非对性能要求极高，否则建议使用更安全的缓存模式，例如：\ncache='none'：禁用缓存，每次写入都直接写入磁盘，最安全但性能较低。 cache='writethrough'：写入数据时同时写入缓存和磁盘，兼顾性能和安全性。 2. 硬盘总线类型 bus='virtio'\nvirtio 是半虚拟化驱动，性能优于模拟设备。 但需要客户机操作系统安装 virtio 驱动才能识别。 建议：\n确认客户机操作系统已安装 virtio 驱动，例如 Ubuntu 通常默认安装。 如果客户机操作系统不支持 virtio，则需要使用模拟设备，例如 bus='ide' 或 bus='scsi'。 3. PCI 地址分配 address type='pci' ...\n这段配置为虚拟硬盘分配了固定的 PCI 地址。 一般情况下，KVM 会自动分配 PCI 地址，无需手动指定。 如果对这个配置进行了修改导致报错却又不知道如何还原，可以尝试将这段配置直接删除，删除后再次启动虚拟机一般会自动生成对应的新配置。 4. disk type\n在 KVM 虚拟机配置文件中，\u0026lt;disk\u0026gt; 元素的 \u0026lt;driver\u0026gt; 子元素中的 type='raw' 指的是虚拟磁盘镜像文件的格式。\ntype='raw' 表示使用原始磁盘镜像格式。\n特点： 直接映射: 虚拟磁盘文件直接映射到主机文件系统上的一个文件，没有额外的格式转换，读写性能最好。 简单: 结构简单，创建和使用方便。 空间占用大: 即使虚拟磁盘内部文件系统有空闲空间，镜像文件大小也不会自动缩减。 其他可用的 type 参数：\n除了 raw 之外，qemu 驱动还支持其他几种虚拟磁盘镜像文件格式：\nqcow2 (推荐): QEMU 默认格式，支持许多高级功能，例如： 稀疏分配: 镜像文件大小可以动态增长，只占用实际使用的空间。 快照: 可以创建虚拟磁盘的时间点副本。 压缩: 可以压缩镜像文件，节省存储空间。 qed: KVM/QEMU 早期版本使用的格式，功能较少，现在已经很少使用。 vmdk: VMware 使用的虚拟磁盘格式，KVM 可以读取和使用，但不建议创建新的 vmdk 格式镜像。 修改后xml文件 这里主要修改的是source file和type字段\n\u0026lt;disk type=\u0026#39;file\u0026#39; device=\u0026#39;disk\u0026#39;\u0026gt; \u0026lt;driver name=\u0026#39;qemu\u0026#39; type=\u0026#39;qcow2\u0026#39; cache=\u0026#39;writeback\u0026#39;/\u0026gt; \u0026lt;source file=\u0026#39;/mnt/user/domains/infra-centos7/vdisk1.qcow2\u0026#39; index=\u0026#39;1\u0026#39;/\u0026gt; \u0026lt;backingStore/\u0026gt; \u0026lt;target dev=\u0026#39;hdc\u0026#39; bus=\u0026#39;virtio\u0026#39;/\u0026gt; \u0026lt;boot order=\u0026#39;1\u0026#39;/\u0026gt; \u0026lt;alias name=\u0026#39;virtio-disk2\u0026#39;/\u0026gt; \u0026lt;address type=\u0026#39;pci\u0026#39; domain=\u0026#39;0x0000\u0026#39; bus=\u0026#39;0x03\u0026#39; slot=\u0026#39;0x00\u0026#39; function=\u0026#39;0x0\u0026#39;/\u0026gt; \u0026lt;/disk\u0026gt; 重新登录机器之后我们检查硬盘的情况\n[root@infra-centos7 ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vda 253:0 0 150G 0 disk ├─vda1 253:1 0 1G 0 part /boot └─vda2 253:2 0 149G 0 part / [root@infra-centos7 ~]# blkid /dev/vda1: UUID=\u0026#34;763539f9-fc49-443a-a246-4cedb357dc92\u0026#34; TYPE=\u0026#34;xfs\u0026#34; /dev/vda2: UUID=\u0026#34;4110a890-d483-4d07-af95-dda95c600e96\u0026#34; TYPE=\u0026#34;xfs\u0026#34; [root@infra-centos7 ~]# df -Th | grep vda /dev/vda2 xfs 149G 70G 80G 47% / /dev/vda1 xfs 1014M 279M 736M 28% /boot 到这里整个转换就完成了。\n","permalink":"http://localhost:9011/20240624-qemu-img-to-qcow2/","summary":"\u003cp\u003e将 raw 格式转换为 qcow2 格式是 KVM 虚拟化环境中常见的操作，本文主要介绍如何使用qemu工具将虚拟机的镜像文件从raw格式转换为qcow2格式。\u003c/p\u003e","title":"镜像文件从raw格式转换为qcow2"},{"content":"本文主要介绍如何手动部署高可用的双向加密认证etcd集群。\n思路 这里我们大概介绍一下部署一个etcd集群的思路，如果是不使用证书加密可以跳过配置证书的环节\n检查机器环境，确定etcd使用的端口是默认的2379还是说需要使用自定义端口 初始化环境，创建对应的数据存储目录和证书存储目录 拉取CA证书，或者手动签发CA证书，随后使用CA证书签发对应的client证书、server证书和peer证书 配置systemd unit文件 拉取etcd二进制文件 启动etcd服务，查看集群状态 定义环境变量 在开始部署etcd集群之前，我们需要确定在部署过程中用到的各种变量和目录。\n首先是etcd本身需要使用到的三个端口，etcd 使用三个端口进行通信和提供服务：\n1. 客户端端口 (Client Port):\n作用: 用于与 etcd 客户端进行通信，例如 etcdctl 命令行工具、应用程序等。客户端通过该端口连接到 etcd 集群，进行数据读写、键值存储、服务发现等操作。 默认端口: 2379 2. 对等节点端口 (Peer Port):\n作用: 用于 etcd 集群内部节点之间进行通信，例如选举领导者、同步数据、心跳检测等。 默认端口: 2380 3. metrics 端口 (Metrics Port):\n作用: 用于暴露 etcd 集群的运行指标数据，例如请求数量、延迟、存储大小等。可以通过 Prometheus 等监控系统收集和展示这些指标数据。 默认端口: 2381 使用 shell 定义环境变量:\nexport ETCD_CLIENT_PORT=2379 export ETCD_PEER_PORT=2380 export ETCD_METRICS_PORT=2381 另外就是我们需要定义一个用于存储etcd的证书目录和数据目录\nexport ETCD_CERTS_DIR=/data/etcd/certs export ETCD_DATA_DIR=/data/etcd/data 最后就是定义我们这次搭建集群需要用到的三个机器ip\nexport etcd1=10.31.110.1 export etcd2=10.31.110.2 export etcd3=10.31.110.3 初始化环境 开始之前我们在这次集群用到的三台机器上面配置对应的环境变量并创建目录\nmkdir -p $ETCD_CERTS_DIR chown root.root -R $ETCD_CERTS_DIR chmod 755 -R $ETCD_CERTS_DIR mkdir -p $ETCD_DATA_DIR chown root.root -R $ETCD_DATA_DIR chmod 755 -R $ETCD_DATA_DIR 配置CA证书 配置私钥 不论是哪种方式配置CA证书，我们都需要先生成一个私钥。\ncd $ETCD_CERTS_DIR # 生成 CA 私钥: openssl genrsa -out ca-key.pem 2048 配置CSR 我们继续配置CSR证书，注意CSR证书并不是必须的，很多流程中都会省略\n# 创建 CA 证书签名请求 (CSR): openssl req -new -key ca-key.pem -out ca.csr -subj \u0026#34;/CN=TC Test CA/O=tinychen\u0026#34; 上面的这个 OpenSSL 命令用于生成一个自签名证书的证书签名请求 (CSR)。\n参数解析:\nopenssl req: 调用 OpenSSL 工具集中的 req 命令，用于处理证书请求。 -new: 生成一个新的证书请求。 -key ca-key.pem: 指定用于签署 CSR 的私钥文件。这里使用的是名为 ca-key.pem 的私钥文件，应该是您预先生成的。 -out ca.csr: 指定生成的 CSR 文件的输出路径和文件名。这里将生成名为 ca.csr 的 CSR 文件。 -subj \u0026quot;/CN=TC Test CA/O=tinychen\u0026quot;: 指定证书主题信息，采用 X.509 证书规范的格式。 /CN=TC Test CA: 通用名称 (Common Name)，通常是域名或服务器主机名，这里\u0026quot;TC Test CA\u0026quot;表示 \u0026ldquo;tinychen test ca\u0026rdquo;。 /O=tinychen: 组织 (Organization) 名称，这里也就是 \u0026ldquo;tinychen\u0026rdquo;。 配置CA证书 最后我们来生成一个ca证书\n# 自签名 CA 证书: openssl x509 -req -in ca.csr -signkey ca-key.pem -out ca.crt -days 36500 -extensions v3_ca -extfile \u0026lt;(cat \u0026lt;\u0026lt; EOF [ v3_ca ] basicConstraints = critical,CA:TRUE keyUsage = critical, cRLSign, keyCertSign subjectKeyIdentifier = hash EOF ) 这个 OpenSSL 命令使用之前生成的 CSR (ca.csr) 和私钥 (ca-key.pem) 来生成一个自签名的 CA 证书 (ca.crt)。\n参数解析:\nopenssl x509: 调用 OpenSSL 工具集中的 x509 命令，用于处理 X.509 证书。 -req: 指定输入文件是一个证书签名请求 (CSR)。 -in ca.csr: 指定要使用的 CSR 文件名为 ca.csr。 -signkey ca-key.pem: 指定用于签署证书的私钥文件为 ca-key.pem。 -out ca.crt: 指定生成的证书文件名为 ca.crt。 -days 36500: 设置证书的有效期为 36500 天 (大约 100 年)。 -extensions v3_ca: 指定要添加到证书的扩展信息来自配置文件中的 v3_ca 段。 -extfile \u0026lt;(cat \u0026lt;\u0026lt; EOF ... EOF): 使用 Here Document 将扩展信息配置传递给 OpenSSL。 Here Document 中的扩展信息配置:\n[v3_ca]: 定义一个名为 v3_ca 的扩展信息段。 basicConstraints = critical,CA:TRUE: 设置基本约束扩展，表明这是一个 CA 证书，可以用于签发其他证书。critical 标记表示这是一个关键扩展，必须被识别和遵守。 keyUsage = critical, cRLSign, keyCertSign: 设置密钥用法扩展，指定该证书可以用于签署证书吊销列表 (CRL) 和签发其他证书。 subjectKeyIdentifier = hash: 设置主题密钥标识符扩展，使用证书公钥的哈希值作为标识符。 复制CA证书 不论我们使用的是单向认证还是双向认证，最后集群内的所有节点，使用的ca证书应该都是一致的，因此我们在其中一台机器生成ca证书之后，可以将其复制到对应剩余的机器上面。这里以rsync为例进行复制。\nrsync -avz -e ssh ./ca* root@10.31.110.2:/data/etcd/certs/ rsync -avz -e ssh ./ca* root@10.31.110.3:/data/etcd/certs/ 关于CSR证书 使用 OpenSSL 生成 CA 证书，我们可以选择使用 CSR（证书签名请求）或直接生成自签名证书。两种方法都需要先生成一个私钥。\n使用 CSR 生成 CA 证书 这种方法更常见，因为它将私钥的生成和证书请求的创建分离，提高了安全性。\n步骤：\n生成私钥: openssl genrsa -out ca-key.pem 2048 这将生成一个 2048 位的 RSA 私钥，并将其保存到 ca-key.pem 文件中。 2. 创建证书签名请求 (CSR):\nopenssl req -new -key ca-key.pem -out ca.csr -subj \u0026#34;/CN=MyCA/O=MyOrganization\u0026#34; 这将使用私钥生成一个 CSR，并将其保存到 ca.csr 文件中。-subj 参数用于指定证书主题信息，例如通用名称 (CN) 和组织 (O)。 3. 生成自签名 CA 证书:\nopenssl x509 -req -in ca.csr -signkey ca-key.pem -out ca.crt -days 3650 -extensions v3_ca -extfile \u0026lt;(cat \u0026lt;\u0026lt; EOF [ v3_ca ] basicConstraints = critical,CA:TRUE keyUsage = critical, cRLSign, keyCertSign subjectKeyIdentifier = hash EOF ) 这将使用 CSR 和私钥生成一个自签名 CA 证书，并将其保存到 ca.crt 文件中。-days 参数指定证书的有效期，-extensions 和 -extfile 参数用于添加必要的 CA 扩展信息。\n直接生成自签名 CA 证书 这种方法将私钥生成和证书创建合并到一个步骤中，更加方便，但安全性稍低。\n步骤：\nopenssl req -x509 -newkey rsa:2048 -keyout ca-key.pem -out ca.crt -days 3650 -subj \u0026#34;/CN=MyCA/O=MyOrganization\u0026#34; -extensions v3_ca -extfile \u0026lt;(cat \u0026lt;\u0026lt; EOF [ v3_ca ] basicConstraints = critical,CA:TRUE keyUsage = critical, cRLSign, keyCertSign subjectKeyIdentifier = hash EOF ) 这将生成一个 2048 位的 RSA 私钥，并使用该私钥直接生成一个自签名 CA 证书，并将它们分别保存到 ca-key.pem 和 ca.crt 文件中。\n关于 CSR 的必要性:\n安全性: 使用 CSR 可以将私钥的生成和证书请求的创建分离，提高安全性。私钥始终保存在您的本地机器上，不会被发送到 CA 服务器。 灵活性: CSR 可以用于向不同的 CA 服务器申请证书，而无需每次都重新生成私钥。 证书文件 无论我们选择哪种方式生成 CA 证书，最终都会生成两个关键文件：\n私钥文件 (例如 ca-key.pem): 作用: 这是 CA 的私钥，用于签署证书和证书吊销列表 (CRL)。私钥必须严格保密，因为它可以用来伪造证书。 查看内容: 由于私钥文件包含敏感信息，不建议直接查看其内容。如果需要查看私钥信息，可以使用以下命令： openssl rsa -in ca-key.pem -text -noout 注意: 执行此命令时，请确保您位于安全的终端环境中，并且不要将私钥信息泄露给他人。\n证书文件 (例如 ca.crt): 作用: 这是 CA 的证书，包含 CA 的公钥、主题信息、有效期、扩展信息等。证书可以公开分享，用于验证由该 CA 签发的其他证书。 查看内容: 我们可以使用以下命令查看证书的详细信息： openssl x509 -in ca.crt -text -noout 该命令会以文本格式输出证书的详细信息，包括版本号、序列号、签名算法、颁发者、主题、有效期、公钥信息、扩展信息等。\n证书签名请求文件 (例如 ca.csr) 准确地说，CSR 不是证书，而是一个 证书签名请求 (Certificate Signing Request)。它本身并没有实际证书的功能，而是用于向证书颁发机构 (CA) 申请证书。\nCSR 的作用可以概括为：\n包含公钥信息： CSR 文件包含申请者生成的公钥信息，以及一些身份识别信息，例如域名、组织名称等。 请求 CA 签名： 将 CSR 文件提交给 CA 后，CA 会验证申请者的身份信息，并使用其私钥对 CSR 文件中的信息进行签名。 生成证书的基础： CA 签署后的 CSR 文件会生成最终的证书文件，其中包含了申请者的公钥信息以及 CA 的数字签名，用于证明证书的合法性和可信度。 简单来说，CSR 就像一张申请表，您填写好个人信息和需求（公钥），提交给相关部门（CA）审核签字，最终获得正式的文件（证书）。\n查看 CSR 文件内容的命令:\nopenssl req -in ca.csr -text -noout openssl req: 调用 OpenSSL 工具集中的 req 命令，用于处理证书请求。 -in ca.csr: 指定要查看的 CSR 文件名为 ca.csr。 -text: 以文本格式输出 CSR 文件的内容。 -noout: 不输出编码后的请求信息，只输出解析后的文本信息。 CSR 文件内容解析:\n执行上述命令后，我们会看到类似以下内容的输出：\nCertificate Request: Data: Version: 0 (0x0) Subject: CN=MyCA, O=MyOrganization Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public Key: (2048 bit) ... Attributes: ... Signature Algorithm: sha256WithRSAEncryption ... 主要信息包括:\nVersion: 证书请求的版本号。 Subject: 请求证书的实体信息，例如您的通用名称 (CN) 和组织 (O)。 Subject Public Key Info: 与私钥配对的公钥信息，包括算法和公钥值。 Attributes: 其他可选属性，例如证书扩展信息。 Signature Algorithm: 用于对证书请求进行签名的算法。 Signature: 对证书请求信息的数字签名。 小结 对于测试环境或个人使用，直接生成自签名 CA 证书更为方便。 使用 CSR 生成 CA 证书可以提高安全性，对于生产环境或需要更高安全性的场景，建议使用 CSR 生成 CA 证书。 私钥文件 (ca-key.pem) 包含 CA 的私钥，必须严格保密。 证书文件 (ca.crt) 包含 CA 的公钥和其他信息，可以公开分享。 生成server证书 生成服务器私钥: openssl genrsa -out server-key.pem 2048 创建服务器证书签名请求 (CSR): 该命令使用指定的私钥和主题信息生成一个 CSR 文件，并添加了三个 IP 地址作为 SAN 扩展信息。生成的 CSR 文件可以提交给 CA 进行签名，最终获得包含指定主题信息和 SAN 扩展信息的证书。\nopenssl req -new -key server-key.pem -out server.csr -subj \u0026#34;/C=CN/ST=GuangDong/L=Guangzhou/O=system:server/OU=TC/CN=server\u0026#34; -reqexts SAN -config \u0026lt;(cat \u0026lt;\u0026lt; EOF [req] distinguished_name = req_distinguished_name [req_distinguished_name] [SAN] subjectAltName=IP:$etcd1,IP:$etcd2,IP:$etcd3 EOF ) # 检查输出 openssl req -in server.csr -text -noout 这是一个使用 OpenSSL 命令行工具生成证书签名请求 (CSR) 的命令，让我们逐步解析：\n命令主体: openssl req: 调用 OpenSSL 工具的 req 命令，用于处理证书请求。 -new: 指示 OpenSSL 生成一个新的 CSR。 -key server-key.pem: 指定用于签署 CSR 的私钥文件路径，这里是 server-key.pem。 -out server.csr: 指定生成的 CSR 文件的输出路径和文件名，这里是 server.csr。 -subj \u0026quot;/C=CN/ST=GuangDong/L=Guangzhou/O=system:server/OU=TC/CN=server\u0026quot;: 指定 CSR 中包含的主题信息，采用 X.509 证书规范： C=CN: 国家代码，这里是中国 (CN)。 ST=GuangDong: 州/省份名称，这里是广东。 L=Guangzhou: 城市名称，这里是广州。 O=system:server: 组织名称，这里是 \u0026ldquo;system:server\u0026rdquo;。 OU=Shopee: 组织单位名称，这里是 \u0026ldquo;TC\u0026rdquo;。 CN=server: 通用名称，通常是域名或服务器主机名，这里是 \u0026ldquo;server\u0026rdquo;。 扩展信息: -reqexts SAN: 指定要添加的证书扩展信息，这里是 SAN (Subject Alternative Name)，用于添加额外的域名或 IP 地址。 -config \u0026lt;(cat \u0026lt;\u0026lt; EOF ... EOF): 使用内联文档的方式指定 OpenSSL 配置文件内容，主要用于配置 SAN 扩展信息。 配置文件内容: [req] distinguished_name = req_distinguished_name [req_distinguished_name] [SAN] subjectAltName=IP:$etcd1,IP:$etcd2,IP:$etcd3 [req]: 定义与证书请求相关的配置。 distinguished_name = req_distinguished_name: 指定使用名为 req_distinguished_name 的节来配置主题信息。 [req_distinguished_name]: 定义主题信息的配置，这里留空，表示使用命令行中 -subj 参数指定的主题信息。 [SAN]: 定义 SAN 扩展信息的配置。 subjectAltName=IP:$etcd1,IP:$etcd2,IP:$etcd3: 指定要添加的 SAN 信息，这里是三个 IP 地址，分别存储在环境变量 $etcd1、$etcd2 和 $etcd3 中。 使用 CA 证书签发服务器证书 openssl x509 -req -in server.csr -CA ca.crt -CAkey ca-key.pem -CAcreateserial -out server.pem -days 36500 -extensions v3_req -extfile etcd-server/server-ext.cnf openssl x509 -req -in server.csr -CA ca.crt -CAkey ca-key.pem -CAcreateserial -out server.pem -days 36500 -extensions v3_req -extfile \u0026lt;(cat \u0026lt;\u0026lt; EOF [ v3_req ] basicConstraints = critical, CA:FALSE keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = clientAuth, serverAuth subjectAltName = @alt_names authorityKeyIdentifier=keyid:always [ alt_names ] IP.1 = $etcd1 IP.2 = $etcd2 IP.3 = $etcd3 EOF ) # 检查输出 openssl x509 -in server.pem -text -noout 解释：\nbasicConstraints = critical, CA:FALSE: critical: 表示这是一个关键扩展，如果证书使用者不支持该扩展，则必须拒绝该证书。 CA:FALSE: 表示该证书不能用作证书颁发机构 (CA)。 keyUsage = critical, digitalSignature, keyEncipherment: critical: 表示这是一个关键扩展。 digitalSignature: 表示该证书可以用于数字签名。 keyEncipherment: 表示该证书可以用于密钥加密。 extendedKeyUsage = clientAuth, serverAuth: clientAuth: 表示该证书可以用于 TLS Web 客户端身份验证。 serverAuth: 表示该证书可以用于 TLS Web 服务器身份验证。 subjectAltName = @alt_names: 指定证书主题备用名称 (SAN)，包括 DNS 名称和 IP 地址。 注意：\n请将 alt_names 部分中的 DNS 名称和 IP 地址替换为我们使用的实际值。 使用此配置文件生成证书时，请确保使用 -extensions v3_req -extfile server-ext.cnf 参数。 X509v3 Authority Key Identifier (AKI) 是一个重要的证书扩展，它可以提高证书验证的效率和安全性。\n1. AKI 的作用：\n标识签发 CA： AKI 扩展包含签发该证书的 CA 的唯一标识信息，通常是 CA 证书的密钥标识符 (Key Identifier) 或主体密钥标识符 (Subject Key Identifier)。 加速证书验证： 当验证证书时，客户端可以使用 AKI 扩展快速找到签发 CA 的证书，而无需遍历整个证书链，从而提高验证速度。 防止证书替换攻击： 攻击者可能会尝试使用自己的 CA 签发伪造的证书，并替换掉合法的证书。AKI 扩展可以帮助检测这种攻击，因为伪造的证书的 AKI 信息会与合法证书不匹配。 2. 如何配置 AKI：\n在使用 OpenSSL 生成证书时，通常不需要手动配置 AKI 扩展，因为 OpenSSL 会自动根据 CA 证书生成正确的 AKI 信息。\nCA 证书： 在生成 CA 证书时，OpenSSL 会自动生成 Subject Key Identifier 扩展，并将其作为 AKI 信息包含在 CA 证书中。 最终实体证书： 在使用 CA 证书签发其他证书时，OpenSSL 会自动将 CA 证书的 Subject Key Identifier 提取出来，作为 AKI 信息包含在最终实体证书中。 3. 手动配置 AKI (可选)：\n如果您需要手动配置 AKI 扩展，可以使用 -ext 参数和配置文件。例如，以下命令使用 server-ext.cnf 文件中的 aki 扩展来生成服务器证书：\nserver-ext.cnf 文件中的 aki 扩展配置如下：\n[ v3_req ] authorityKeyIdentifier=keyid:always 这将强制 OpenSSL 在生成的服务器证书中包含 AKI 扩展，并将 keyid 设置为 always，表示始终使用 CA 证书的 Subject Key Identifier 作为 AKI 信息。\n小结：\nAKI 扩展可以提高证书验证的效率和安全性。 OpenSSL 通常会自动处理 AKI 扩展，无需手动配置。 您可以根据需要手动配置 AKI 扩展，例如指定特定的密钥标识符或禁用 AKI 扩展。 生成client证书 client证书的配置过程和前面的server证书一样，这里就不再赘述。\nopenssl genrsa -out client-key.pem 2048 openssl req -new -key client-key.pem -out client.csr -subj \u0026#34;/C=SG/ST=Singapore/L=Singapore/O=system:client/OU=Shopee/CN=client\u0026#34; -reqexts SAN -config \u0026lt;(cat \u0026lt;\u0026lt; EOF [req] distinguished_name = req_distinguished_name [req_distinguished_name] [SAN] subjectAltName=IP:$etcd1,IP:$etcd2,IP:$etcd3 EOF ) openssl x509 -req -in client.csr -CA ca.crt -CAkey ca-key.pem -CAcreateserial -out client.pem -days 36500 -extensions v3_req -extfile \u0026lt;(cat \u0026lt;\u0026lt; EOF [ v3_req ] basicConstraints = critical, CA:FALSE keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = clientAuth, serverAuth subjectAltName = @alt_names authorityKeyIdentifier=keyid:always [ alt_names ] IP.1 = $etcd1 IP.2 = $etcd2 IP.3 = $etcd3 EOF ) 生成peer证书 peer证书的配置过程也和前面二者一样，这里就不再赘述。\nopenssl genrsa -out peer-key.pem 2048 openssl req -new -key peer-key.pem -out peer.csr -subj \u0026#34;/C=SG/ST=Singapore/L=Singapore/O=system:peer/OU=Shopee/CN=peer\u0026#34; -reqexts SAN -config \u0026lt;(cat \u0026lt;\u0026lt; EOF [req] distinguished_name = req_distinguished_name [req_distinguished_name] [SAN] subjectAltName=IP:$etcd1,IP:$etcd2,IP:$etcd3 EOF ) openssl x509 -req -in peer.csr -CA ca.crt -CAkey ca-key.pem -CAcreateserial -out peer.pem -days 36500 -extensions v3_req -extfile \u0026lt;(cat \u0026lt;\u0026lt; EOF [ v3_req ] basicConstraints = critical, CA:FALSE keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = clientAuth, serverAuth subjectAltName = @alt_names authorityKeyIdentifier=keyid:always [ alt_names ] IP.1 = $etcd1 IP.2 = $etcd2 IP.3 = $etcd3 EOF ) 关于证书 三台机器的所有证书文件都要一模一样吗？\n在部署 etcd 集群时，证书文件的使用方式取决于我们选择的认证方式和安全需求。\n1. 单向认证:\n仅服务器需要证书： 如果只使用单向 TLS 认证（即只有服务器需要提供证书），那么三台机器的 ca.crt 文件需要一致，而 server.crt 和 server.key 文件则必须不同，每个节点都需要使用自己的证书和私钥。 客户端不需要证书： 客户端可以使用服务器的公钥证书 (server.crt) 来验证服务器的身份，但不需要提供自己的证书。 2. 双向认证:\n所有节点都需要不同证书： 如果使用双向 TLS 认证（即客户端和服务器都需要提供证书），那么三台机器的 ca.crt 文件需要一致，而 server.crt、server.key、peer.crt 和 peer.key 文件都必须不同，每个节点都需要使用自己独特的证书和私钥。 客户端需要证书： 客户端需要提供自己的证书和私钥来进行身份验证。 总结:\nca.crt: 所有节点都需要使用相同的 CA 证书。 server.crt, server.key: 每个节点都需要使用自己独特的服务器证书和私钥。 peer.crt, peer.key: 如果使用双向认证，每个节点都需要使用自己独特的 peer 证书和私钥。 建议:\n为了提高安全性，建议在部署 etcd 集群时使用双向 TLS 认证，并为每个节点生成唯一的证书和私钥。\n下载etcd etcd和etcdctl一般都在同样的地方，我们可以一并下载安装配置\nhttps://github.com/etcd-io/etcd/releases/ 从GitHub上面的官方release页面我们可以找到并下载最新的etcd二进制文件，然后我们可以将解压后的文件放到我们需要使用的目录中，本文以常用的/usr/local/bin/为例\nwget https://github.com/etcd-io/etcd/releases/download/v3.5.15/etcd-v3.5.15-linux-amd64.tar.gz tar -zxvf etcd-v3.5.15-linux-amd64.tar.gz cp -rp etcd-v3.5.15-linux-amd64/etcdctl /usr/local/bin/etcdctl cp -rp etcd-v3.5.15-linux-amd64/etcdutl /usr/local/bin/etcdutl cp -rp etcd-v3.5.15-linux-amd64/etcd /usr/local/bin/etcd chmod a+x /usr/local/bin/etcd /usr/local/bin/etcdutl /usr/local/bin/etcdctl chwon root:root /usr/local/bin/etcd /usr/local/bin/etcdutl /usr/local/bin/etcdctl 配置systemd 配置环境变量 # 注意这里在配置环境变量的时候要参考最前面的环境变量参数配置 export etcd1=10.31.110.1 export etcd2=10.31.110.2 export etcd3=10.31.110.3 etcd_role=etcd1 local_ip=10.31.110.1 unit file 然后这里要配置systemd文件\ncat \u0026lt;\u0026lt; EOF \u0026gt; /etc/systemd/system/etcd.service [Unit] Description=etcd - highly-available key value store Documentation=https://github.com/coreos/etcd Documentation=man:etcd After=network.target Wants=network-online.target [Service] Type=simple User=root PermissionsStartOnly=true ExecStart= /usr/local/bin/etcd \\ --name $etcd_role \\ --quota-backend-bytes=8589934592 \\ --initial-advertise-peer-urls https://$local_ip:$ETCD_PEER_PORT \\ --listen-peer-urls https://$local_ip:$ETCD_PEER_PORT \\ --listen-client-urls https://$local_ip:$ETCD_CLIENT_PORT,https://127.0.0.1:$ETCD_CLIENT_PORT \\ --advertise-client-urls https://$local_ip:$ETCD_CLIENT_PORT \\ --initial-cluster etcd1=https://$etcd1:$ETCD_PEER_PORT,etcd2=https://$etcd2:$ETCD_PEER_PORT,etcd3=https://$etcd3:$ETCD_PEER_PORT \\ --cert-file=$ETCD_CERTS_DIR/server.pem \\ --key-file=$ETCD_CERTS_DIR/server-key.pem \\ --peer-cert-file=$ETCD_CERTS_DIR/peer.pem \\ --peer-key-file=$ETCD_CERTS_DIR/peer-key.pem \\ --trusted-ca-file=$ETCD_CERTS_DIR/ca.crt \\ --peer-trusted-ca-file=$ETCD_CERTS_DIR/ca.crt \\ --peer-client-cert-auth \\ --client-cert-auth \\ --initial-cluster-token etcd-cluster-tinychen \\ --initial-cluster-state new \\ --listen-metrics-urls=http://0.0.0.0:$METRICS_PORT \\ --auto-compaction-retention=1h \\ --metrics=extensive \\ --data-dir=$ETCD_DATA_DIR LimitNOFILE=655360 KillMode=process Restart=always RestartSec=20 [Install] WantedBy=multi-user.target EOF 这里对上面的systemd unit文件做一个简单介绍：\n[Unit] 部分\nDescription: 对服务的简短描述，这里描述为 \u0026ldquo;etcd - highly-available key value store\u0026rdquo;。 Documentation: 指向服务文档的链接，这里提供了 etcd 的 GitHub 页面和 man 手册链接。 After=network.target: 指定 etcd 服务在 network.target 之后启动，确保网络已就绪。 Wants=network-online.target: 表示 etcd 服务希望 network-online.target 能够启动，但即使后者失败，etcd 也会继续启动。 [Service] 部分\nType=simple: 指定服务类型为简单类型，即启动一个守护进程，并由 systemd 直接管理。 User=root: 指定以 root 用户身份运行服务。 PermissionsStartOnly=true: 表示仅在启动服务时需要提升权限，启动后会放弃权限。 ExecStart: 指定启动服务的命令行，这里包含了 etcd 的可执行文件路径和一系列参数： /usr/local/bin/etcd: etcd 可执行文件的路径，可以根据实际安装路径修改。 --name $etcd_role: 指定 etcd 节点的角色名称，通常使用主机名或其他唯一标识符。 --quota-backend-bytes=8589934592: 设置 etcd 后端存储空间配额，单位为字节，这里设置为 8GB。 --initial-advertise-peer-urls https://$local_ip:$ETCD_PEER_PORT: 指定 etcd 节点用于与其他节点通信的地址和端口，使用 HTTPS 协议。 --listen-peer-urls https://$local_ip:$ETCD_PEER_PORT: 指定 etcd 节点监听其他节点连接的地址和端口，使用 HTTPS 协议。 --listen-client-urls https://$local_ip:$ETCD_CLIENT_PORT,https://127.0.0.1:$ETCD_CLIENT_PORT: 指定 etcd 节点监听客户端连接的地址和端口，可以使用多个地址，使用 HTTPS 协议。 --advertise-client-urls https://$local_ip:$ETCD_CLIENT_PORT: 指定 etcd 节点向客户端公布的地址和端口，使用 HTTPS 协议。 --initial-cluster etcd1=https://$etcd1:$ETCD_PEER_PORT,etcd2=https://$etcd2:$ETCD_PEER_PORT,etcd3=https://$etcd3:$ETCD_PEER_PORT: 指定 etcd 集群中所有节点的地址和端口，使用 HTTPS 协议。 --cert-file=$ETCD_CERTS_DIR/server.pem: 指定 etcd 服务端证书文件路径。 --key-file=$ETCD_CERTS_DIR/server-key.pem: 指定 etcd 服务端私钥文件路径。 --peer-cert-file=$ETCD_CERTS_DIR/peer.pem: 指定 etcd 节点间通信使用的证书文件路径。 --peer-key-file=$ETCD_CERTS_DIR/peer-key.pem: 指定 etcd 节点间通信使用的私钥文件路径。 --trusted-ca-file=$ETCD_CERTS_DIR/ca.crt: 指定 etcd 信任的 CA 证书文件路径。 --peer-trusted-ca-file=$ETCD_CERTS_DIR/ca.crt: 指定 etcd 节点间通信信任的 CA 证书文件路径。 --peer-client-cert-auth: 启用节点间通信的客户端证书认证。 --client-cert-auth: 启用客户端连接的证书认证。 --initial-cluster-token etcd-cluster: 指定 etcd 集群的初始令牌，用于标识集群。 --initial-cluster-state new: 指定 etcd 集群的初始状态为 new，表示这是一个新的集群。 --listen-metrics-urls=http://0.0.0.0:$METRICS_PORT: 指定 etcd 监听指标数据的地址和端口，使用 HTTP 协议。 --auto-compaction-retention=1h: 设置 etcd 自动压缩历史数据的保留时间，这里设置为 1 小时。 --metrics=extensive: 设置 etcd 收集的指标数据级别为 extensive，表示收集更详细的指标数据。 --data-dir=$ETCD_DATA_DIR: 指定 etcd 数据存储目录路径。 LimitNOFILE=655360: 设置 etcd 进程可以打开的最大文件描述符数量。 KillMode=process: 指定停止服务时，仅杀死主进程，不杀死子进程。 Restart=always: 指定服务在退出时自动重启，无论退出原因是什么。 RestartSec=20: 指定服务重启的间隔时间，这里设置为 20 秒。 [Install] 部分\nWantedBy=multi-user.target: 指定服务在 multi-user.target 运行级别启动，这是一个通用的多用户运行级别。 --initial-cluster-state 参数用于指定 etcd 集群的初始状态，它有两个可选值：\nnew: 表示这是一个全新的集群，所有节点都将参与选举，选出一个 leader 节点，并初始化集群数据。 existing: 表示这是一个已经存在的集群，新加入的节点将不会参与选举，而是从现有的 leader 节点同步数据。 使用场景：\n创建新集群: 当你第一次部署 etcd 集群时，应该将所有节点的 --initial-cluster-state 参数都设置为 new，以便它们能够选举 leader 并初始化集群。 添加新节点: 当你向一个已经存在的 etcd 集群添加新节点时，应该将新节点的 --initial-cluster-state 参数设置为 existing，以便它能够从 leader 节点同步数据，而不会影响集群的正常运行。 灾难恢复: 当 etcd 集群中的所有节点都不可用时，你可以使用 --initial-cluster-state=new 参数从备份数据中恢复集群。 注意事项：\n如果你将一个已经存在的集群的 --initial-cluster-state 参数设置为 new，那么所有节点都将认为这是一个全新的集群，并尝试选举 leader，这会导致数据丢失和服务中断。 确保所有节点的 --initial-cluster 参数配置一致，否则会导致集群无法正常工作。 总结：\n--initial-cluster-state 参数是 etcd 集群初始化过程中非常重要的一个参数，它决定了集群的初始状态和节点的行为。在配置 etcd 集群时，务必根据实际情况选择正确的参数值，以避免数据丢失和服务中断。\ncipher-suite 这里则是作为可选部分，调整etcd使用的加密算法从而进一步提高安全性，但是同时需要注意客户端侧的兼容性，并非强制需求配置。\nmkdir -p /etc/systemd/system/etcd.service.d cat \u0026lt;\u0026lt; EOF \u0026gt; /etc/systemd/system/etcd.service.d/cipher-suites.conf [Service] Environment=ETCD_CIPHER_SUITES=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384 EOF 检查集群 完成之后我们重启服务检查集群状态\nsystemctl daemon-reload systemctl restart etcd.service systemctl status etcd.service 当然我们还可以使用etcdctl命令来查看更详细的集群信息\netcdctl --endpoints https://$local_ip:$ETCD_CLIENT_PORT --cacert $ETCD_CERTS_DIR/ca.crt --cert $ETCD_CERTS_DIR/server.pem --key $ETCD_CERTS_DIR/server-key.pem endpoint status -w=table --cluster 其他的更多指令操作和内容，我们后面再慢慢探索吧。\n","permalink":"http://localhost:9011/20240619-etcd-ha-cluster-manually-deploy/","summary":"\u003cp\u003e本文主要介绍如何手动部署高可用的双向加密认证etcd集群。\u003c/p\u003e","title":"双向加密认证etcd集群的部署"},{"content":"本文主要介绍如何为crictl配置默认的image-endpoint和runtime-endpoint的参数。\n最近发现使用crictl查看容器状态的时候出现这个报错\n[root@k8s-10-31-90-1 ~]# crictl ps -a WARN[0000] runtime connect using default endpoints: [unix:///run/containerd/containerd.sock unix:///run/crio/crio.sock unix:///var/run/cri-dockerd.sock]. As the default settings are now deprecated, you should set the endpoint instead. WARN[0000] image connect using default endpoints: [unix:///run/containerd/containerd.sock unix:///run/crio/crio.sock unix:///var/run/cri-dockerd.sock]. As the default settings are now deprecated, you should set the endpoint instead. 从上面的WARN信息我们可以看出应该是对应的某些配置过期了，需要我们手动指定默认配置。\n先查看目前的集群状态，目前使用的是1.6.14版本的containerd和v1.30.1版本的K8S。\n[root@k8s-10-31-90-1 ~]# kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-10-31-90-1.tinychen.io Ready control-plane 492d v1.30.1 10.31.90.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-10-31-90-2.tinychen.io Ready control-plane 492d v1.30.1 10.31.90.2 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-10-31-90-3.tinychen.io Ready control-plane 492d v1.30.1 10.31.90.3 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-10-31-90-4.tinychen.io Ready \u0026lt;none\u0026gt; 492d v1.30.1 10.31.90.4 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-10-31-90-5.tinychen.io Ready \u0026lt;none\u0026gt; 492d v1.30.1 10.31.90.5 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-10-31-90-6.tinychen.io Ready \u0026lt;none\u0026gt; 492d v1.30.1 10.31.90.6 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 对应crictl的版本是v1.30.0\n[root@k8s-10-31-90-1 ~]# crictl --version crictl version v1.30.0 使用crictl查看相应的参数\n--config value, -c value Location of the client config file. If not specified and the default does not exist, the program\u0026#39;s directory is searched as well (default: \u0026#34;/etc/crictl.yaml\u0026#34;) [$CRI_CONFIG_FILE] --image-endpoint value, -i value Endpoint of CRI image manager service (default: uses \u0026#39;runtime-endpoint\u0026#39; setting) [$IMAGE_SERVICE_ENDPOINT] --runtime-endpoint value, -r value Endpoint of CRI container runtime service (default: uses in order the first successful one of [unix:///run/containerd/containerd.sock unix:///run/crio/crio.sock unix:///var/run/cri-dockerd.sock]). Default is now deprecated and the endpoint should be set instead. [$CONTAINER_RUNTIME_ENDPOINT] 这里我们可以看到crictl的配置文件默认是存放在/etc/crictl.yaml，image-endpoint和runtime-endpoint读取的参数配置是一样的，默认情况下都是依次读取unix:///run/containerd/containerd.sock 、unix:///run/crio/crio.sock、 unix:///var/run/cri-dockerd.sock这三个变量，但是看起来目前的新版本已经弃用了这个配置，并且建议我们自行配置。\n当前containerd服务的endpoint接口可以通过systemd命令查看\nsystemctl status containerd.service 所以我们只需要指定访问默认的接口即可\n[root@k8s-10-31-90-1 ~]# crictl config runtime-endpoint unix:///run/containerd/containerd.sock [root@k8s-10-31-90-1 ~]# crictl config image-endpoint unix:///run/containerd/containerd.sock [root@k8s-10-31-90-1 ~]# cat /etc/crictl.yaml runtime-endpoint: \u0026#34;unix:///run/containerd/containerd.sock\u0026#34; image-endpoint: \u0026#34;unix:///run/containerd/containerd.sock\u0026#34; timeout: 0 debug: false pull-image-on-create: false disable-pull-on-run: false 最后warning信息消失\n最后附上crictl的官方链接和对应的crictl config的配置参考。\n","permalink":"http://localhost:9011/20240607-crictl-config-endpoint/","summary":"\u003cp\u003e本文主要介绍如何为crictl配置默认的\u003ccode\u003eimage-endpoint\u003c/code\u003e和\u003ccode\u003eruntime-endpoint\u003c/code\u003e的参数。\u003c/p\u003e","title":"crictl配置endpoint"},{"content":"Argo CD是一个开源的GitOps工具，用于自动化部署、操作和观察基于Kubernetes的应用程序和配置，提供声明式应用程序定义、多环境支持、应用程序状态监控、RBAC和安全性、插件和扩展性等功能。通过与Git集成，它实现了将应用程序和配置定义存储在Git存储库中，并将其自动同步到Kubernetes环境，实现应用程序的自动化部署和更新。\n本文主要介绍argocd的基础架构和核心概念，同时会手把手进行高可用部署并进行初始化配置和应用部署，文章较长，有兴趣的同学可以根据目录进行跳转阅读。\n部署argocd 我们可以参考argocd官网的快速开始指南，在开始之前我们需要先准备一个能够正常使用的K8S集群，我们这里使用的是1.27.0版本的社区原生K8S进行部署。\n$ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-10-31-90-1.tinychen.io Ready control-plane 96d v1.27.0 10.31.90.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-10-31-90-2.tinychen.io Ready control-plane 96d v1.27.0 10.31.90.2 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-10-31-90-3.tinychen.io Ready control-plane 96d v1.27.0 10.31.90.3 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-10-31-90-4.tinychen.io Ready \u0026lt;none\u0026gt; 96d v1.27.0 10.31.90.4 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-10-31-90-5.tinychen.io Ready \u0026lt;none\u0026gt; 96d v1.27.0 10.31.90.5 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-10-31-90-6.tinychen.io Ready \u0026lt;none\u0026gt; 96d v1.27.0 10.31.90.6 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 该集群使用cilium作为CNI，containerd作为CRI，注意部署argocd并不需要用到持久化存储，但是最好能有一种手段用来暴露argocd的api接口（如四层的loadbalance或者七层的ingress/apigateway等）。\n架构组件 先来看一下官方提供的这个架构图，注意默认情况下Argo CD部署时会配置多种组件和Kubernetes controllers。这些Kubernetes controllers更倾向于CRD而不是模块化的组件，因此在架构图中并没有将其表现出来，与此相似的还有一些如configmap、service、secret的配置。\n官方的架构图中将整个Argo CD系统分为四个逻辑层级：UI、Application、Core和Infra。逻辑层还有助于使图表更易于理解，因为依赖关系以自上而下的关系表示。这意味着来自顶层的组件将被允许依赖于来自任何底层的任何组件。然而，来自底层的组件永远不会依赖于来自上层的任何组件。\nUI：这是表示层。用户主要通过这一层的组件与 Argo CD 进行交互（包括WebUI和CLI）。 Application：支持来自 UI 层的组件所需的功能。 Core：主要的 Argo CD gitops 功能由核心层的组件和 Kubernetes 控制器实现。 Infra：表示 Argo CD 作为其基础设施的一部分所依赖的工具。 以下是各个组件的主要职责，我们在平时维护的时候可以根据这些信息来快速定位故障：\nWebapp：Argo CD 附带一个强大的 Web 界面，允许管理部署在给定 Kubernetes 集群中的应用程序； CLI：Argo CD 提供了一个 CLI，用户可以使用它与 Argo CD API 进行交互。 CLI 还可用于自动化和脚本编写； API Server：定义由 Argo CD 公开的专有 API，该 API 为 Web 应用程序和 CLI 功能提供支持； Application Controller：应用程序控制器负责协调 Kubernetes 中的应用程序资源和项目资源，并将所需的应用程序状态（在 Git 中提供）与实时状态（在 Kubernetes 中）同步； ApplicationSet Controller：ApplicationSet Controller 负责协调 ApplicationSet 资源，applicationset是argo创建的一个CRD； Repo Server：Repo Server 在 Argo CD 架构中起着重要作用，因为它负责与 Git 存储库交互，为属于给定 application 的所有 Kubernetes 资源生成所需的状态； Redis：Argo CD 使用 Redis 来提供缓存层，减少发送到K8S的API Server和Git服务器的请求。它还支持一些 UI 操作； Kube API：Argo CD 控制器将连接到K8S的API Server以执行操作。因此如果需要管理多个集群时，在部署了argocd之外的集群是不需要部署任何argocd的客户端程序的，只需要创建一个ServiceAccount用于管理权限； Git：作为 gitops 工具，Argo CD 要求在 Git 存储库中提供所需的 Kubernetes 资源状态。 我们在这里使用git来代表实际的 git 存储库、Helm 存储库或 OCI 工件存储库。 Argo CD 支持所有这些选项； Dex：Argo CD 依赖于 Dex 来提供与外部 OIDC 提供商的身份验证。当然，可以使用其他工具代替 Dex； 核心概念 官方文档假定我们熟悉核心 Git、Docker、Kubernetes、持续交付（Continuous Delivery）和 GitOps 概念，对一些特定于 Argo CD 的概念进行了介绍，这里摘取了一些个人认为比较重要且基础的概念进行二次阐述。\nApplication：Argocd中的核心概念之一，在K8S中以CRD的形式存在，用来表示一组由manifest定义的K8S资源集合。例如我们需要部署一个服务，该服务涉及的K8S资源可能包括若干个deployment、svc以及configmap等多种类型，在argocd中将其统一按照Application的概念进行划分，这些资源都归属于同一个Application下进行配置。\nTarget state：期望该Application下的各种资源类型的运行状态，由对应Git仓库中的文件进行定义。\nLive state：目前该Application下的各种资源类型的运行状态。\nSync status：实时状态是否与目标状态匹配，部署的应用程序是否与Git仓库中定义的状态一致。正常情况下应是Synced并且指向该Git的最新版本分支。\nSync：将 Git 中的最新代码与实时状态进行对比并将变更同步到K8S集群中，将其可以视为主动触发同步git状态的操作。\nSync operation status：同步是否成功。\nRefresh：将Git中的最新代码与实时状态进行对比。\nHealth：应用程序的健康状况，如是否正常运行，能否接收并处理外部请求等。\n高可用部署argocd 高可用部署的yaml文件可以在github上面获取\nhttps://github.com/argoproj/argo-cd/blob/master/manifests/ha/namespace-install.yaml 同时需要注意对应的CRD资源需要单独部署\nhttps://github.com/argoproj/argo-cd/tree/master/manifests/crds 部署的文档可以参考官方的latest版本\nhttps://argo-cd.readthedocs.io/en/latest/operator-manual/installation/ 我们来看一下部署文件的结构：\n$ tree argocd/ argocd/ |-- crds | |-- application-crd.yaml | |-- applicationset-crd.yaml | |-- appproject-crd.yaml | `-- kustomization.yaml `-- ha `-- namespace-install.yaml 2 directories, 5 files 部署环节我们可以分为三步，部署CRD资源、创建namespace、部署workload及相关配置。\n首先我们定位到crds目录下面，直接部署对应的三个crd文件用于创建对应的crd资源。\n$ kubectl apply -f application-crd.yaml -f applicationset-crd.yaml -f appproject-crd.yaml customresourcedefinition.apiextensions.k8s.io/applications.argoproj.io created customresourcedefinition.apiextensions.k8s.io/applicationsets.argoproj.io created customresourcedefinition.apiextensions.k8s.io/appprojects.argoproj.io created 接着手动创建一个namespace，名字不一定为argocd，可以是你喜欢的任意值，但是注意后面指定的namespace要和这里相同。\n$ kubectl create ns argocd namespace/argocd created 最后定位到ha目录下，直接部署argocd对应的全部工作负载，注意部署的时候指定namespace，否则会部署到默认的default namespace下。\n$ kubectl apply -f namespace-install.yaml -n argocd serviceaccount/argocd-application-controller created serviceaccount/argocd-applicationset-controller created serviceaccount/argocd-dex-server created serviceaccount/argocd-notifications-controller created serviceaccount/argocd-redis-ha created serviceaccount/argocd-redis-ha-haproxy created serviceaccount/argocd-repo-server created serviceaccount/argocd-server created role.rbac.authorization.k8s.io/argocd-application-controller created role.rbac.authorization.k8s.io/argocd-applicationset-controller created role.rbac.authorization.k8s.io/argocd-dex-server created role.rbac.authorization.k8s.io/argocd-notifications-controller created role.rbac.authorization.k8s.io/argocd-redis-ha created role.rbac.authorization.k8s.io/argocd-redis-ha-haproxy created role.rbac.authorization.k8s.io/argocd-server created rolebinding.rbac.authorization.k8s.io/argocd-application-controller created rolebinding.rbac.authorization.k8s.io/argocd-applicationset-controller created rolebinding.rbac.authorization.k8s.io/argocd-dex-server created rolebinding.rbac.authorization.k8s.io/argocd-notifications-controller created rolebinding.rbac.authorization.k8s.io/argocd-redis-ha created rolebinding.rbac.authorization.k8s.io/argocd-redis-ha-haproxy created rolebinding.rbac.authorization.k8s.io/argocd-server created configmap/argocd-cm created configmap/argocd-cmd-params-cm created configmap/argocd-gpg-keys-cm created configmap/argocd-notifications-cm created configmap/argocd-rbac-cm created configmap/argocd-redis-ha-configmap created configmap/argocd-redis-ha-health-configmap created configmap/argocd-ssh-known-hosts-cm created configmap/argocd-tls-certs-cm created secret/argocd-notifications-secret created secret/argocd-secret created service/argocd-applicationset-controller created service/argocd-dex-server created service/argocd-metrics created service/argocd-notifications-controller-metrics created service/argocd-redis-ha created service/argocd-redis-ha-announce-0 created service/argocd-redis-ha-announce-1 created service/argocd-redis-ha-announce-2 created service/argocd-redis-ha-haproxy created service/argocd-repo-server created service/argocd-server created service/argocd-server-metrics created deployment.apps/argocd-applicationset-controller created deployment.apps/argocd-dex-server created deployment.apps/argocd-notifications-controller created deployment.apps/argocd-redis-ha-haproxy created deployment.apps/argocd-repo-server created deployment.apps/argocd-server created statefulset.apps/argocd-application-controller created statefulset.apps/argocd-redis-ha-server created networkpolicy.networking.k8s.io/argocd-application-controller-network-policy created networkpolicy.networking.k8s.io/argocd-applicationset-controller-network-policy created networkpolicy.networking.k8s.io/argocd-dex-server-network-policy created networkpolicy.networking.k8s.io/argocd-notifications-controller-network-policy created networkpolicy.networking.k8s.io/argocd-redis-ha-proxy-network-policy created networkpolicy.networking.k8s.io/argocd-redis-ha-server-network-policy created networkpolicy.networking.k8s.io/argocd-repo-server-network-policy created networkpolicy.networking.k8s.io/argocd-server-network-policy created 部署完成后检查部署的pod是否正常运行，此时所有的pod应该都在argocd这个namespace下。\n$ kubectl get pods -n argocd -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES argocd-application-controller-0 1/1 Running 0 5m42s 10.33.4.166 k8s-10-31-90-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-applicationset-controller-559cc454d9-wx5vf 1/1 Running 0 5m43s 10.33.4.217 k8s-10-31-90-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-dex-server-76487b6695-vvkwr 1/1 Running 0 5m43s 10.33.4.133 k8s-10-31-90-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-notifications-controller-65b795c756-mtxb5 1/1 Running 0 5m43s 10.33.3.18 k8s-10-31-90-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-redis-ha-haproxy-6745ff898d-6rb2n 1/1 Running 0 5m43s 10.33.4.85 k8s-10-31-90-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-redis-ha-haproxy-6745ff898d-dbn58 1/1 Running 0 5m43s 10.33.3.212 k8s-10-31-90-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-redis-ha-haproxy-6745ff898d-w2hlh 1/1 Running 0 5m43s 10.33.5.211 k8s-10-31-90-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-redis-ha-server-0 3/3 Running 0 5m42s 10.33.5.163 k8s-10-31-90-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-redis-ha-server-1 3/3 Running 0 2m41s 10.33.4.188 k8s-10-31-90-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-redis-ha-server-2 3/3 Running 0 86s 10.33.3.113 k8s-10-31-90-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-repo-server-5c8f8d4fbd-fp9jt 1/1 Running 0 5m42s 10.33.4.230 k8s-10-31-90-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-repo-server-5c8f8d4fbd-gchwd 1/1 Running 0 5m42s 10.33.3.213 k8s-10-31-90-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-server-5684688df7-7grxl 1/1 Running 0 5m42s 10.33.5.55 k8s-10-31-90-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; argocd-server-5684688df7-gxbsf 1/1 Running 0 5m42s 10.33.3.226 k8s-10-31-90-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 最后检查对应的svc是否存在，同理此时所有的svc应该也都在argocd这个namespace下。\n$ kubectl get svc -n argocd NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE argocd-applicationset-controller ClusterIP 10.33.182.73 \u0026lt;none\u0026gt; 7000/TCP,8080/TCP 12m argocd-dex-server ClusterIP 10.33.130.105 \u0026lt;none\u0026gt; 5556/TCP,5557/TCP,5558/TCP 12m argocd-metrics ClusterIP 10.33.167.226 \u0026lt;none\u0026gt; 8082/TCP 12m argocd-notifications-controller-metrics ClusterIP 10.33.191.67 \u0026lt;none\u0026gt; 9001/TCP 12m argocd-redis-ha ClusterIP None \u0026lt;none\u0026gt; 6379/TCP,26379/TCP 12m argocd-redis-ha-announce-0 ClusterIP 10.33.182.204 \u0026lt;none\u0026gt; 6379/TCP,26379/TCP 12m argocd-redis-ha-announce-1 ClusterIP 10.33.158.122 \u0026lt;none\u0026gt; 6379/TCP,26379/TCP 12m argocd-redis-ha-announce-2 ClusterIP 10.33.147.25 \u0026lt;none\u0026gt; 6379/TCP,26379/TCP 12m argocd-redis-ha-haproxy ClusterIP 10.33.190.134 \u0026lt;none\u0026gt; 6379/TCP 12m argocd-repo-server ClusterIP 10.33.187.87 \u0026lt;none\u0026gt; 8081/TCP,8084/TCP 12m argocd-server ClusterIP 10.33.160.176 \u0026lt;none\u0026gt; 80/TCP,443/TCP 12m argocd-server-metrics ClusterIP 10.33.177.208 \u0026lt;none\u0026gt; 8083/TCP 12m 需要注意的是，在较新版本的namespace-install.yaml文件中，因为默认开启了redis的认证功能，还需要我们自己手动创建一个对应的secret\napiVersion: v1 kind: Secret metadata: labels: app.kubernetes.io/name: argocd-redis app.kubernetes.io/part-of: argocd name: argocd-redis namespace: argocd type: Opaque data: auth: dGlueWNoZW4uY29tCg== 注意上面的auth值应该是原密码经过base64编码后的值，例如\n# echo tinychen.com | base64 dGlueWNoZW4uY29tCg== 部署CLI CLI的部署可以参考文档\nhttps://argo-cd.readthedocs.io/en/latest/cli_installation/ 我们直接下载对应系统版本的二进制文件即可。\n$ curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64 $ sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd $ rm argocd-linux-amd64 安装完成之后输入argocd --help查看CLI是否能够正常运行\n$ argocd --help argocd controls a Argo CD server Usage: argocd [flags] argocd [command] Available Commands: account Manage account settings admin Contains a set of commands useful for Argo CD administrators and requires direct Kubernetes access app Manage applications appset Manage ApplicationSets cert Manage repository certificates and SSH known hosts entries cluster Manage cluster credentials completion output shell completion code for the specified shell (bash or zsh) context Switch between contexts gpg Manage GPG keys used for signature verification help Help about any command login Log in to Argo CD logout Log out from Argo CD proj Manage projects relogin Refresh an expired authenticate token repo Manage repository connection parameters repocreds Manage repository connection parameters version Print version information Flags: --auth-token string Authentication token --client-crt string Client certificate file --client-crt-key string Client certificate key file --config string Path to Argo CD config (default \u0026#34;/root/.config/argocd/config\u0026#34;) --core If set to true then CLI talks directly to Kubernetes instead of talking to Argo CD API server --grpc-web Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2. --grpc-web-root-path string Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2. Set web root. -H, --header strings Sets additional header to all requests made by Argo CD CLI. (Can be repeated multiple times to add multiple headers, also supports comma separated headers) -h, --help help for argocd --http-retry-max int Maximum number of retries to establish http connection to Argo CD server --insecure Skip server certificate and domain verification --kube-context string Directs the command to the given kube-context --logformat string Set the logging format. One of: text|json (default \u0026#34;text\u0026#34;) --loglevel string Set the logging level. One of: debug|info|warn|error (default \u0026#34;info\u0026#34;) --plaintext Disable TLS --port-forward Connect to a random argocd-server port using port forwarding --port-forward-namespace string Namespace name which should be used for port forwarding --server string Argo CD server address --server-crt string Server certificate file Use \u0026#34;argocd [command] --help\u0026#34; for more information about a command. argocd CLI的默认配置文件会放置在~/.config/argocd/config目录下，当然也可以像kubectl一样使用--config手动指定。\n暴露argocd 配置argocd-server 想要使用集群外的argocd cli来操控集群内的argocd，比较优雅的方式是通过LoadBalancer暴露一个EXTERNAL-IP给集群外的服务访问\n如果你的集群支持LoadBalancer类型的服务，可以参考下面的配置：\n$ kubectl patch svc argocd-server -n argocd -p \u0026#39;{\u0026#34;spec\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;LoadBalancer\u0026#34;}}\u0026#39; 这里使用的测试集群部署了purelb，因此还可以精细化操作一下，指定特定的VIP方便我们后续配置\n# 我们直接一步到位，直接添加注解、设置svc的类型为LoadBalancer并手动指定loadBalancerIP $ kubectl patch svc argocd-server -n argocd -p \u0026#39;{\u0026#34;metadata\u0026#34;:{\u0026#34;annotations\u0026#34;:{\u0026#34;purelb.io/service-group\u0026#34;:\u0026#34;bgp-ippool\u0026#34;}},\u0026#34;spec\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;LoadBalancer\u0026#34;,\u0026#34;loadBalancerIP\u0026#34;:\u0026#34;10.33.192.2\u0026#34;}}\u0026#39; 随后我们检查一下服务是否暴露成功\n$ kubectl get svc -n argocd argocd-server NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE argocd-server LoadBalancer 10.33.160.176 10.33.192.2 80:30149/TCP,443:31991/TCP 88m 正常情况下我们通过这里暴露出来的VIP 10.33.192.2可以访问对应的web界面，同时CLI工具也可以直接连接10.33.192.2进行操作。\n当然我们也可以通过ingress来进行配置，具体可以参考官方的文档，上面提供了多种ingress的配置案例。\n我们这里为了方便用户访问webUI，使用NGX做一层反向代理，配置域名为argocd.tinychen.com，转发到10.33.192.2的443端口。\n默认情况下argocd会暴露两个端口，其中80端口会自动重定向到443端口，因为我们配置反向代理的需要需要额外注意。\n443 - gRPC/HTTPS 80 - HTTP (redirects to HTTPS) 如果配置的反向代理服务器不支持HTTP2可以考虑使用\u0026ndash;grpc-web参数\n\u0026ndash;grpc-web Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2.\n修改初始账号密码 argocd的初始密码是使用secrets明文存储在k8s的argocd-initial-admin-secret中，我们可以直接获取并进行base64解码\n$ kubectl get secrets -n argocd argocd-initial-admin-secret -o yaml apiVersion: v1 data: password: UUFkdDFNRGZsaXFhZjVtUQ== kind: Secret metadata: creationTimestamp: \u0026#34;2023-05-04T07:50:34Z\u0026#34; name: argocd-initial-admin-secret namespace: argocd resourceVersion: \u0026#34;23555422\u0026#34; uid: 9c0db11e-7db0-4432-9bf4-da7c5668b4e5 type: Opaque $ echo UUFkdDFNRGZsaXFhZjVtUQ== | base64 -d QAdt1MDfliqaf5mQ 当然也可以通过CLI工具来快速获取初始密码\n$ argocd admin initial-password -n argocd QAdt1MDfliqaf5mQ This password must be only used for first time login. We strongly recommend you update the password using `argocd account update-password`. 获取初始密码之后，我们可以使用初始账号admin和初始密码进行登录，然后进行密码的修改。\n# 先使用初始密码登录 $ argocd login argocd.tinychen.com Username: admin Password: \u0026#39;admin:login\u0026#39; logged in successfully Context \u0026#39;argocd.tinychen.com\u0026#39; updated # 随即修改密码 $ argocd account update-password *** Enter password of currently logged in user (admin): *** Enter new password for user admin: *** Confirm new password for user admin: Password updated Context \u0026#39;argocd.tinychen.com\u0026#39; updated # 随后再次退出然后即可使用新密码进行重新登录 $ argocd logout argocd.tinychen.com Logged out from \u0026#39;argocd.tinychen.com\u0026#39; argocd cli的配置文件默认存储在~/.config/argocd/config目录下\n集群配置 argocd原生是支持同时管理多个集群的，同时因为argocd是部署在K8S集群中，所以argocd本身所处的集群默认是存在配置中的。我们可以通过CLI或者webUI来查看默认的集群，需要注意的是如果还没有在集群中部署应用的话，argocd是不会主动去探测集群的状态的。\n$ argocd cluster list SERVER NAME VERSION STATUS MESSAGE PROJECT https://kubernetes.default.svc in-cluster Unknown Cluster has no applications and is not being monitored. 添加新集群只能通过CLI来进行操作，webUI可以对集群进行重命名和删除等操作。\n$ kubectl config get-contexts -o name --kubeconfig /root/k8s-90-config kubernetes-admin@kubernetes 将集群添加到argocd之后会在集群中添加一个名为argocd-manager的ServiceAccount。\n$ argocd cluster add kubernetes-admin@kubernetes --kubeconfig /root/k8s-90-config --name k8s-90-cluster WARNING: This will create a service account `argocd-manager` on the cluster referenced by context `kubernetes-admin@kubernetes` with full cluster level privileges. Do you want to continue [y/N]? y INFO[0003] ServiceAccount \u0026#34;argocd-manager\u0026#34; created in namespace \u0026#34;kube-system\u0026#34; INFO[0003] ClusterRole \u0026#34;argocd-manager-role\u0026#34; created INFO[0003] ClusterRoleBinding \u0026#34;argocd-manager-role-binding\u0026#34; created INFO[0008] Created bearer token secret for ServiceAccount \u0026#34;argocd-manager\u0026#34; Cluster \u0026#39;https://10.31.90.0:8443\u0026#39; added 大部分k8s默认生成的kubeconfig文件中的集群名都是kubernetes-admin@kubernetes，这对多集群管理稍有不便，如果想要单独指定在argocd中的集群名称可以在导入的时候使用\u0026ndash;name 参数\n添加完成后可以查看到集群，但是由于尚未部署app，所以目前状态也并未同步\n$ argocd cluster list SERVER NAME VERSION STATUS MESSAGE PROJECT https://10.31.90.0:8443 k8s-90-cluster https://kubernetes.default.svc in-cluster 配置git仓库 argocd中的git仓库和repo是一对多的关系，也就是说一个git仓库可以用于创建多个app，所以这里我们需要先配置git仓库。\n特别注意，Project在创建成功之后是无法修改的。如果想要使用project对多个项目进行管理，建议先创建project，再配置git仓库，最后配置apps\n如果有比较大量的git仓库存储需求，建议考虑自行搭建一个gitlab服务器，当然使用公共的gitlab、github和helm仓库等都是可以的。\nargocd支持多种拉取仓库的方式，下面我们主要介绍https和ssh这两种较为常见的方式。\n通过https连接仓库 https方式连接一般比较简单，常用于拉取一些公共git仓库。如下面示范的拉取argocd官方的git仓库作为示例：\n创建成功后我们可以在UI中看到该仓库的状态为Successful。\n在CLI中也能看到详细的状态信息。\n$ argocd repo list TYPE NAME REPO INSECURE OCI LFS CREDS STATUS MESSAGE PROJECT git https://github.com/argoproj/argocd-example-apps false false false false Successful default 当然https的拉取方式也是可以配置用户名密码已经TLS双向认证等多种限制手段的。\n通过ssh连接仓库 首先我们生成一个密钥对，这个密钥对用来给git仓库添加权限\n$ ssh-keygen -C argocd@argocd.tinychen.com -f ~/.ssh/argocd 接着我们为对应的git仓库创建对应的depoly keys，depoly keys的详细说明可以参考gitlab的官方文档。depoly keys可以在gitlab的仓库的Settings -\u0026gt; Repository -\u0026gt; Deploy keys找到，然后把刚刚生成的key里面的公钥放进去即可，至于过期时间（Expiration date）则根据自己的实际需要进行配置，留空则为无限制。\n随后我们在argocd的webui节面创建一个repo，这里需要注意如果使用的是ssh并且端口不是默认的22，需要在URL中带上具体的端口信息。如图所示就指定了SSH的端口为9022。\nargocd在部署的时候自带了常用的几个网站的ssh-known-hosts并存储在argocd-ssh-known-hosts-cm这个configmap中\n$ kubectl get cm -n argocd argocd-ssh-known-hosts-cm NAME DATA AGE argocd-ssh-known-hosts-cm 1 25h 如果我们使用的是自建的gitlab服务器，建议勾选Skip server verification这个参数，或者将对应的ssh-known-hosts添加到argocd-ssh-known-hosts-cm这个configmap中，否则在同步git仓库的时候容易出现报错。\n也可以在webUI中的Settings-\u0026gt;Repository certificates and known hosts中配置ssh-known-hosts和TLS Certificate\n$ argocd repo list TYPE NAME REPO INSECURE OCI LFS CREDS STATUS MESSAGE PROJECT git https://github.com/argoproj/argocd-example-apps false false false false Successful default git k8s-90-manifests git@gitlab.tinychen.com:9022:tinychen/k8s-90-manifests.git true false true false Successful default 配置app 完成上面的所有步骤之后，我们可以开始配置最终的应用了。\n先来看基础配置Application Name和Project Name都是对应argocd中的参数，也是对应我们前面部署的两个crd SYNC POLICY可以选择自动同步还是手动同步 PRUNE RESOURCES可以选择是否删除在git中移除的资源，如某个git版本中创建了一个svc，随后又删除了，如果不勾选该选项，则argocd不会自动删除该svc SELF HEAL可以理解为自愈，即检测到定义的资源状态和git中不一致的时候，是否自动同步为一致；如git中定义副本数为10，随后手动扩容至20但并未修改git中的配置文件，勾选这一项则会触发argocd自动将副本数缩减回10 SKIP SCHEMA VALIDATION跳过语法检查，个人不建议开启 AUTO-CREATE NAMESPACE可以设置如果对应的namespace不存在的话是否自动创建 APPLY OUT OF SYNC ONLY类似于增量部署而不是全量部署，仅部署和git版本中不一样的资源，而不是全部重新部署一次，可以降低K8S集群的压力 REPLACE会将部署的命令从默认的kubectl apply变更为kubectl replace/create，可以解决部分资源修改之后无法直接apply部署的问题，同时也有可能会导致资源的重复创建 RETRY可以设定部署失败后重试的次数和频率 关于sync options的完整参数解析可以查看官方的文档https://argo-cd.readthedocs.io/en/latest/user-guide/sync-options/，这里只挑了一些个人常用且熟悉的参数进行介绍，如果不知道一个参数的作用是什么，建议保持默认值。\nSOURCE这里主要用于指定git仓库、对应的分支及具体的子目录\nDESTINATION这里用来指定部署的K8S集群和namespace。\nDirectory这一栏里面有个DIRECTORY RECURSE参数，勾选之后会递归识别子目录下面的文件。\n这里还可以下拉变更为helm、Kustomize、Plugin等其他配置\n部署完成之后我们就可以使用argocd来管理对应的应用了\n配置git webhooks ArgoCD每三分钟会拉取一次git仓库的内容以检测manifests的变化。如果想要消除这个由轮询机制带来的三分钟延迟，我们可以考虑使用webhook来进行通知。Argo CD 支持来自 GitHub、GitLab、Bitbucket、Bitbucket Server 和 Gogs 的 Git webhook 通知，官方提供了基于Github配置webhook事件的教程，我们这里使用自建的gitlab来进行配置。\n首先我们找到用来测试的git仓库，在gitlab界面中找到Settings-\u0026gt;Webhooks，然后按照下面红框所示的进行操作。\nURL这里一般都是配置自己argocd的访问域名再加上/api/webhook后缀，注意需要配置https访问 注意这里面的Secret token可以用来增强webhook接口的安全性，大家可以根据自己的实际需求来进行配置。token这里我配置了一串字符串，注意要保存下来，后面还需要在argocd侧进行配置 Trigger这里只需要勾选Push events即可，因为我们只需要在有人push变更到git仓库之后，gitlab自动去触发webhook接口调用argocd来完成更新 SSL认证这里建议默认勾选，argocd默认都是走https请求的。\n添加完成之后可以看到这个project里面就新增了一个Project Hooks，这时候还不能正常调用，我们还需要到argocd里面配置前面提到的Secret token。\nargocd使用的secret都以secrets的形式存储在K8S中，我们这里主要用到的是argocd-secret。\n$ kubectl edit secrets -n argocd argocd-secret -o yaml 我们在里面手动添加一个stringData，然后把前面的Secret token贴进去，格式类似下面的示范即可。\napiVersion: v1 kind: Secret metadata: labels: app.kubernetes.io/name: argocd-secret app.kubernetes.io/part-of: argocd name: argocd-secret type: Opaque stringData: # gitlab webhook secret webhook.gitlab.secret: vbdUUDkHszh35VZ6 完成之后我们再回到gitlab上面，点击Test，选择Push events，如果这时候有下图所示的返回信息则说明已经配置成功且webhook可以正常工作。\n最后我们可以手动测试一下webhook的效果：随意编辑一个yaml文件，更改其中的副本数，git push之后查看argocd的webUI界面确定是否会即可生效即可完成测试。\n这里不一定需要修改副本数，也可以修改资源配额等其他比较明显能触发pod重建之类的参数，确保我们能比较轻松明显地在argocd上面查看到变化的内容。\n最后要提到的是：不太建议通过校对git的版本号来进确定argocd的同步状态，因为不管是SYNC STATUS还是LAST SYNC里面的版本号都不一定是该git仓库对应分支的最新版本号，正常情况下的版本号应该是涉及到这个argocd的app里面所需要用到的文件的最新的版本号；当然我们也可以通过手动点击sync按钮来触发同步操作，这时候SYNC STATUS和LAST SYNC里面的版本号就都同步到最新的git版本（尽管这时候可能并不需要进行K8S的同步操作）。\n","permalink":"http://localhost:9011/20230506-argocd-intro-and-ha-deploy/","summary":"\u003cp\u003eArgo CD是一个开源的GitOps工具，用于自动化部署、操作和观察基于Kubernetes的应用程序和配置，提供声明式应用程序定义、多环境支持、应用程序状态监控、RBAC和安全性、插件和扩展性等功能。通过与Git集成，它实现了将应用程序和配置定义存储在Git存储库中，并将其自动同步到Kubernetes环境，实现应用程序的自动化部署和更新。\u003c/p\u003e\n\u003cp\u003e本文主要介绍argocd的基础架构和核心概念，同时会手把手进行高可用部署并进行初始化配置和应用部署，文章较长，有兴趣的同学可以根据目录进行跳转阅读。\u003c/p\u003e","title":"Argocd核心概念及高可用部署"},{"content":"Anycast 是一种为一组端点提供多个路由路径的技术，主要可以用于扩展无状态服务，例如 DNS 或 HTTP。很多大型的互联网服务商已经将Anycast作为他们的基础组件架构技术之一并用于构建异地多活的高可用服务。本文将主要对什么是Anycast、Anycast的原理和优势、Anycast与DNS结合等方面进行介绍。\nAnycast概述 本章节主要介绍Anycast相关的基础概念，包括Anycast和其他几种cast的工作方式对比、Anycast的工作原理、Anycast的优势和成本。\nAnycast基础术语 在开始介绍Anycast之前，我们先介绍一下计算机的网络中数据包传递的几种方式：\n单播（Unicast）：发送者和接收者之间的一对一关联 广播（Broadcast）：一对多关联，数据包被路由到广播网络上所有可能的端点 多播（Multicast）：一对多或多对多关联。一次数据包传输被路由到多个接收者（所有可访问节点的一个子集）；还有一种特殊形式的多播，可以将数据包传送到一组由其地理位置标识的目的地，一般称之为Geocast，可以视为多播的分支 任播（Anycast）：一对多关联；数据包被路由到由算法选择的由相同地址标识的一组收件人中的一个成员 上面这些都是比较专业的术语介绍，如何通俗易懂地理解它们呢？\n假设我们的主人公小陈同学上班的时候，一到饭点就习惯和老陈同学去吃饭，所以每次吃饭之前，小陈会在手机上私聊老陈，问他中午吃什么。\n这个私聊的过程，我们可以理解为单播。此时的发送者是小陈，接收者是老陈，发送者和接收者之间的一对一关联\n假设到了晚饭时间，老陈今天回家陪老婆孩子吃饭了，小陈到了饭点了没人和他一起吃饭，于是小陈在办公室里问大家一句：今晚有没有人一起吃饭呀？\n小陈向办公室里众人喊话的过程，我们可以理解为一种广播。此时的发送者是小陈，接收者是办公室里面能听到小陈说话的所有人。由于小陈说话的范围是有限的（这里为整个办公室），因此我们可以把小陈这次说话（广播）的范围称之为广播域。\n假设可怜的小陈还是找不到人跟他一起吃饭，但是被同事推荐了一个干饭群，群里全是小陈这样孤单的干饭人，所以小陈在群里问有人和他一起吃晚饭吗？\n这个小陈在群里发问的过程，我们可以理解为组播。此时的发送者是小陈，接收者是群里的所有人。\n这里需要注意的是广播和组播的区别，广播的情况下，接收者并不一定想要接受这个消息，而组播则相反。因为只要是广播包，理论上广播域内的所有人都会收到，就好比小陈在办公室里面问的时候，不管办公室里面的人想不想吃饭，都会接收到小陈在找人吃饭的这个消息；作为对比，小陈在干饭群里面发的消息，我们可以理解为群里的人都是想找人一起吃饭的，群友都有接收吃饭相关的消息的意愿。\n假设小陈回到家之后，晚上肚子饿了想吃宵夜并打开了外卖软件下了一个订单，此时他的订单需要一个骑手帮他配送，但是他不需要操心由谁来给他配送，因为这都是由外卖软件负责的。外卖软件根据自己的大数据杀熟算法，从众多骑手里面挑选了一个骑手并派单给他。\n这里小陈叫外卖的过程可以视为是一个anycast任播。此时消息的发送者依旧可以视为是小陈，但是不同的是他这时候只需要一个骑手给他配送外卖即可，而后面实际上有很多个骑手都是可以配送的，具体由哪个骑手配送，实际上是由系统的算法决定的，而在计算机网络里面。这个分配的算法一般是由路由器决定的。\nAnycast是什么 Anycast 是一种为一组端点提供多个路由路径的技术，每个端点都分配有相同的 IP 地址。 组中的每个设备在网络上通告相同的地址，路由协议用于选择最佳目的地。Anycast 主要可以用于扩展无状态服务，例如 DNS 或 HTTP，方法是将多个节点放在同一 IP 地址后面，并使用等价多路径 (ECMP) 路由来引导这些节点之间的流量。 任播与单播不同，单播中每个端点都有自己独立的 IP 地址。\n这里需要注意两个点：\nIPv4协议本身是不支持Anycast技术的 IPv6协议原生支持Anycast技术，但是普及率要远低于IPv4 因此到这里为止，Anycast技术落地的最大问题其实是如何在IPv4网络中实现Anycast。好在有BGP协议的帮忙，在一个大型的网络内，多个不同的路由器都发布同样的IP的路由，根据BGP协议，不同的客户端被路由到不同的路由器节点上，再由路由器选择对应的端点传输数据，就可以实现IPv4网络下的Anycast。\nAnycast的优势和成本 Anycast与传统的单播路由相比，Anycast技术有着不少的优势：\n自动负载平衡：Anycast技术可以将请求有效地控制在特定的网络区域内（一般是网络路径最短的节点优先被请求），Anycast往往和ECMP搭配使用，利用ECMP可以有效地负载均衡各个 DNS 服务器之间的查询； 更低的网络延迟：Anycast会根据路由距离优先转发到最短路径的节点上，这可以有效地降低请求的延迟； 更高的可用性：在DDoS中，Anycast路由可以抑制其影响，因为攻击流量将分布在整个网络中；同时由于Anycast可以自动故障转移，使用Anycast的服务可以更简单地实现异地多活； 简化配置：使用了Anycast的服务在多个不同地区都可以配置相同的VIP，这可以大幅降低配置成本； 下图为使用Anycast前后遭到DDoS攻击的变化：\n有利必有弊，Anycast在带来这些优势的同时，也会带来一些额外的维护成本：如外网BGP IP实际上带宽价格可能十分昂贵，一般的用户可能无法承担；Anycast网络架构的建设也需要一定的硬件设备和专业人员进行维护；同时由于有多个地域部署节点，在定位排查问题的时候也会增加一定的难度。部署一个真正的Anycast网络并不容易。 实施时要求CDN提供商维护自己的网络硬件，同时与世界上的多个运营商建立良好的关系，并不断优化其网络路由以确保用户可以通过最佳网络路径访问服务。\nAnycast和DNS 前面我们介绍了IPv4网络中的Anycast实现主要依赖于BGP路由协议，而这可能会存在一定的问题，例如在连接的过程中，路由器后面的某个端点出现故障，那么就可能导致对应的连接出现异常。因此一般认为Anycast和TCP一起使用会有一定的风险，但是如果使用本身就是无状态的UDP，那么就不会存在这个问题。而使用UDP的应用，接触和使用最多的就是DNS了，同时DNS的根域名服务器也广泛地使用了Anycast技术实现多地区高可用，这也就使得DNS成为Anycast技术的首选落地场景。\n我们来看看下面这张图了解一些Anycast-DNS的工作原理，并借此深入到其他的应用。\n图中紫色的为DNS服务器，深蓝色的是路由器，不同的区块可以表示为不同的地区。路由器之间相互连接并使用BGP协议交换相互之间的路由信息，存在于多个地理位置的DNS服务器使用路由软件（frrouting、bird等）各自向其本地网关（路由器）通告一个相同的 Anycast VIP 地址。当 DNS 客户端发起对Anycast VIP 地址的查询时，路由器将评估可用路由，并根据自身的路由信息（路由距离、跃点信息、ECMP算法等）选择后面的一个DNS服务器来接收这个请求。\nAnycast故障迁移 按照上面的架构，我们不难发现可以将问题出现的位置根据架构从应用本身到机房数据中心开始自底向上进行定位：\n进程崩溃：\n如果核心进程（如DNS）崩溃，则运行在服务器上的监控守护程序会检测到故障。 监控守护程序主要用于监控核心进程和路由软件（frrouting、bird等）的运行状态，确保在核心进程崩溃后，主要关闭路由软件（frrouting、bird等）进程，从而使得从上层交换机到该服务器的BGP路由会被撤销，达到摘除线上流量的目的。\n撤消路由后，数据中心的路由器将流量发送到权重次低的路由（优先转发到同数据中心的其他配置了ECMP的其他服务器，如果该数据中心内的所有服务器都崩溃了，则会根据BGP路由的配置转发到下一个数据中心）。\n服务器崩溃：\n如果整个服务器崩溃，路由软件也随之崩溃。 交换机到该服务器的所有 BGP 路由都被撤销，路由器将流量发送到路由权重次低的服务器（一般来说优先转发到该数据中心的同机房配置了ECMP的其他服务器，如果该机房内的所有服务器都崩溃了，则会根据BGP路由的配置转发到下一个数据中心）。\n交换机崩溃：\n如果交换机出现故障，所有到交换机后面服务器的BGP路由都会自动撤销。 因为交换机和上层的路由器之间不再有BGP路由，也就不会再有线上流量转发到服务器上。\n路由器崩溃：\n如果路由器发生故障，则该路由器负责的数据中心的所有的 BGP 路由都将被撤销。 到数据中心的流量会自动故障转移到下一个最近的数据中心。 理论上这种情况出现，意味着整个数据中心内的所有服务的内网调用都有可能不正常。\n不可抗力：\ncloudflare在他们的博客上面还列举了其他的一些诸如核战争级别的不可抗力故障，这里不作赘述。\n","permalink":"http://localhost:9011/20230303-anycast-overview/","summary":"\u003cp\u003eAnycast 是一种为一组端点提供多个路由路径的技术，主要可以用于扩展无状态服务，例如 DNS 或 HTTP。很多大型的互联网服务商已经将Anycast作为他们的基础组件架构技术之一并用于构建异地多活的高可用服务。本文将主要对什么是Anycast、Anycast的原理和优势、Anycast与DNS结合等方面进行介绍。\u003c/p\u003e","title":"Anycast概览"},{"content":"本文主要介绍如何在calico集群彻底删除calico并重新安装配置cilium组件作为集群的cni。\n为什么标题写着有损迁移呢，因为在迁移过程中集群的网络会中断，所有的pod都不能正常工作。关于无损的迁移方案，此前在jet stack上面看到过有位大神发了一篇文章，有兴趣的可以看看。其实测试环境的话无所谓有损无损，但是生产环境不建议这么操作，实际上估计也不会有这么操作的吧。\n关于本次使用的calico集群的部署过程可以参考之前的文章k8s系列13-calico部署BGP模式的高可用k8s集群。\n此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n集群信息 node信息 [root@k8s-calico-master-10-31-90-1 ~]# kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-calico-master-10-31-90-1.tinychen.io Ready control-plane 24d v1.26.0 10.31.90.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-calico-master-10-31-90-2.tinychen.io Ready control-plane 24d v1.26.0 10.31.90.2 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-calico-master-10-31-90-3.tinychen.io Ready control-plane 24d v1.26.0 10.31.90.3 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-calico-worker-10-31-90-4.tinychen.io Ready \u0026lt;none\u0026gt; 24d v1.26.0 10.31.90.4 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-calico-worker-10-31-90-5.tinychen.io Ready \u0026lt;none\u0026gt; 24d v1.26.0 10.31.90.5 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 k8s-calico-worker-10-31-90-6.tinychen.io Ready \u0026lt;none\u0026gt; 24d v1.26.0 10.31.90.6 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.1.4-1.el7.elrepo.x86_64 containerd://1.6.14 ip信息 IP Hostname 10.31.90.0 k8s-calico-apiserver.tinychen.io 10.31.90.1 k8s-calico-master-10-31-90-1.tinychen.io 10.31.90.2 k8s-calico-master-10-31-90-2.tinychen.io 10.31.90.3 k8s-calico-master-10-31-90-3.tinychen.io 10.31.90.4 k8s-calico-worker-10-31-90-4.tinychen.io 10.31.90.5 k8s-calico-worker-10-31-90-5.tinychen.io 10.31.90.6 k8s-calico-worker-10-31-90-6.tinychen.io 10.33.0.0/17 podSubnet 10.33.128.0/18 serviceSubnet 10.33.192.0/18 LoadBalancerSubnet 变更目标 此次修改集群的目标是删除原有的calico，并重新安装cilium，同时开启kubeProxyReplacement和BGP路由可达。\n删除calico 如果之前是使用yaml部署并且保留了原来的文件的，可以直接使用yaml进行卸载\nkubectl delete -f tigera-operator.yaml --grace-period=0 --force kubectl delete -f custom-resources.yaml --grace-period=0 --force CNI的部署可以参考官网的自建K8S部署教程，官网主要给出了两种部署方式，分别是通过Calico operator和Calico manifests来进行部署和管理calico，operator是通过deployment的方式部署一个calico的operator到集群中，再用其来管理calico的安装升级等生命周期操作。manifests则是将相关都使用yaml的配置文件进行管理，这种方式管理起来相对前者比较麻烦，但是对于高度自定义的K8S集群有一定的优势。\n一般来说可能没卸载干净，这里我们再检查一下遗漏的资源\n# 检查所有名字里面带有 calico|tigera 的资源: kubectl get all --all-namespaces | egrep \u0026#34;calico|tigera\u0026#34; # 检查所有名字里面带有 calico|tigera 的 api resources: kubectl api-resources --verbs=list --namespaced -o name | egrep \u0026#34;calico|tigera\u0026#34; # 检查所有名字里面带有 calico|tigera 的 不带namespace信息的 api resources: kubectl api-resources --verbs=list -o name | egrep \u0026#34;calico|tigera\u0026#34; 当出现资源无法删除的时候可以通过检查其finalizers字段来定位信息\n# 检查calico-node这个serviceaccounts的配置文件，查看对应的finalizers和status中的conditions定位故障原因 kubectl get serviceaccounts calico-node -n calico-system -o yaml 如果是finalizers中存在tigera.io/cni-protector导致资源无法被顺利删除，可以尝试修改为finalizers: []。这个问题看起来似乎是个Kubernetes上游的BUG，在github上面能找到相关的issue，主要集中在使用tigera-operator部署的calico。\nThis is an upstream Kubernetes (not AKS) issue. We can confirm this impacts 1.11.x - I believe this is the main upstream bug tracking this kubernetes/kubernetes#60807 however there are many bugs filed tracking this same behavior with the finalizers.\nWe will be unable to resolve this until a new upstream release which addresses this issue is released by the Kubernetes team. Marking as a known issue.\nhttps://github.com/tigera/operator/issues/2031\nhttps://github.com/projectcalico/calico/issues/6629\nhttps://github.com/kubernetes/kubernetes/issues/60807\n最后删除所有节点上面残留的cni配置文件，然后重启集群的所有机器\n# 删除cni下相关的配置文件 $ rm -rf /etc/cni/net.d/ 重启机器之后会把此前calico创建的路由信息、iptables规则和cni网卡删除，当然不想重启也可以手动删除干净\n# 清理路由信息 $ ip route flush proto bird # 清理calico相关网卡 $ ip link list | grep cali | awk \u0026#39;{print $2}\u0026#39; | cut -c 1-15 | xargs -I {} ip link delete {} # 删除ipip模块 $ modprobe -r ipip # 清理iptables规则 $ iptables-save | grep -i cali | iptables -F $ iptables-save | grep -i cali | iptables -X # 清理ipvsadm规则 $ ipvsadm -C 部署cilium cilium的部署此前在博客里面介绍过多次了，包括overlay模式的部署、bgp模式的部署、kubeProxyReplacement模式的部署，以及eBPF的参数优化等，可以参考之前的汇总链接。这里我们直接使用kubeProxyReplacement模式+kube-routerBGP路由可达，另外eBPF的参数优化等也在部署cilium的时候一并部署上去。\nkube-proxy 因为我们这里使用cilium的kubeProxyReplacement模式，所以先删除kube-proxy\n# 在master节点上备份kube-proxy相关的配置 $ kubectl get ds -n kube-system kube-proxy -o yaml \u0026gt; kube-proxy-ds.yaml $ kubectl get cm -n kube-system kube-proxy -o yaml \u0026gt; kube-proxy-cm.yaml # 删除掉kube-proxy这个daemonset $ kubectl -n kube-system delete ds kube-proxy daemonset.apps \u0026#34;kube-proxy\u0026#34; deleted # 删除掉kube-proxy的configmap，防止以后使用kubeadm升级K8S的时候重新安装了kube-proxy（1.19版本之后的K8S） $ kubectl -n kube-system delete cm kube-proxy configmap \u0026#34;kube-proxy\u0026#34; deleted # 在每台机器上面使用root权限清除掉iptables规则和ipvs规则以及ipvs0网卡 $ iptables-save | grep -v KUBE | iptables-restore $ ipvsadm -C $ ip link del kube-ipvs0 cilium 首先部署helm\n$ wget https://get.helm.sh/helm-v3.11.0-linux-amd64.tar.gz $ tar -zxvf helm-v3.11.0-linux-amd64.tar.gz $ cp -rp linux-amd64/helm /usr/local/bin/ $ helm version version.BuildInfo{Version:\u0026#34;v3.11.0\u0026#34;, GitCommit:\u0026#34;472c5736ab01133de504a826bd9ee12cbe4e7904\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, GoVersion:\u0026#34;go1.18.10\u0026#34;} 然后添加repo\n$ helm repo add cilium https://helm.cilium.io/ \u0026#34;cilium\u0026#34; has been added to your repositories $ helm repo list NAME URL cilium https://helm.cilium.io/ 最后安装cilium和hubble\nSEED=$(head -c12 /dev/urandom | base64 -w0) helm install cilium cilium/cilium --version 1.12.6 \\ --namespace kube-system \\ --set k8sServiceHost=10.31.90.0 \\ --set k8sServicePort=8443 \\ --set kubeProxyReplacement=strict \\ --set tunnel=disabled \\ --set ipam.mode=kubernetes \\ --set ipv4NativeRoutingCIDR=10.33.0.0/17 \\ --set lbExternalClusterIP=true \\ --set enableIPv4Masquerade=false \\ --set enableIPv6Masquerade=false \\ --set ipam.operator.clusterPoolIPv4PodCIDRList=10.33.0.0/17 \\ --set ipam.operator.clusterPoolIPv4MaskSize=24 \\ --set hubble.relay.enabled=true \\ --set hubble.ui.enabled=true \\ --set loadBalancer.algorithm=maglev \\ --set maglev.tableSize=65521 \\ --set maglev.hashSeed=$SEED \\ --set loadBalancer.mode=hybrid \\ --set socketLB.hostNamespaceOnly=true \\ --set loadBalancer.acceleration=native 部署完成后记得检查cilium相关的各个pod是否正常，与此同时集群中因为缺少cni而变为pending或者是unknown状态的pod也会重新分配IP并变回running。\nkube-router kube-router主要是用来发布BGP路由，实现podIP和loadbalancerIP的路由可达，我们先下载部署kube-router的yaml文件。\n$ curl -LO https://raw.githubusercontent.com/cloudnativelabs/kube-router/v1.2/daemonset/generic-kuberouter-only-advertise-routes.yaml 在参数中配置bgp的peer信息，这里我添加了两个peer，分别为10.31.254.253和10.31.100.100。下面的peer-router-ips、peer-router-asns、cluster-asn需要根据自己的实际情况进行修改。\n- --run-router=true - --run-firewall=false - --run-service-proxy=false - --enable-cni=false - --enable-pod-egress=false - --enable-ibgp=true - --enable-overlay=true - --advertise-pod-cidr=true - --advertise-cluster-ip=true - --advertise-external-ip=true - --advertise-loadbalancer-ip=true - --bgp-graceful-restart=true - --peer-router-ips=10.31.254.253,10.31.100.100 - --peer-router-asns=64512,64516 - --cluster-asn=64517 最后部署kube-router，注意带上namespace参数\n$ kubectl apply -f generic-kuberouter-only-advertise-routes.yaml -n kube-system 部署完成后检查各节点和对应的BGP Peer路由信息是否正确。\n# 检测cilium的状态 $ kubectl -n kube-system exec ds/cilium -- cilium status # 查看k8s集群的node状态 $ kubectl -n kube-system exec ds/cilium -- cilium node list # 查看k8s集群的service列表 $ kubectl -n kube-system exec ds/cilium -- cilium service list # 查看对应cilium所处node上面的endpoint信息 $ kubectl -n kube-system exec ds/cilium -- cilium endpoint list cilium的各项参数检测完成之后，基本可以确定集群的网络处于正常。\n","permalink":"http://localhost:9011/20230201-k8s-15-migrate-cni-from-calico-to-cilium/","summary":"\u003cp\u003e本文主要介绍如何在calico集群彻底删除calico并重新安装配置cilium组件作为集群的cni。\u003c/p\u003e\n\u003cp\u003e为什么标题写着有损迁移呢，因为在迁移过程中集群的网络会中断，所有的pod都不能正常工作。关于无损的迁移方案，此前在jet stack上面看到过有位大神发了一篇\u003ca href=\"https://www.jetstack.io/blog/cni-migration/\"\u003e文章\u003c/a\u003e，有兴趣的可以看看。其实测试环境的话无所谓有损无损，但是生产环境不建议这么操作，实际上估计也不会有这么操作的吧。\u003c/p\u003e\n\u003cp\u003e关于本次使用的calico集群的部署过程可以参考之前的文章\u003ca href=\"https://tinychen.com/20230109-k8s-13-deploy-ha-k8s-with-calico-bgp/\"\u003ek8s系列13-calico部署BGP模式的高可用k8s集群\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列15-calico有损迁移至cilium"},{"content":"本文主要介绍如何在calico集群上开启eBPF加速网络数据转发，同时会对eBPF及其在calico中的一些优势特点进行介绍。\n关于本次使用的calico集群的部署过程可以参考之前的文章k8s系列13-calico部署BGP模式的高可用k8s集群。\n此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\neBPF 关于eBPF eBPF是一项革命性的技术，起源于 Linux 内核，可以在操作系统内核中运行沙盒程序。它用于安全有效地扩展内核的功能，而无需更改内核源代码或加载内核模块。\n它允许将小程序加载到内核中，并附加到钩子上，这些钩子在某些事件发生时被触发。这甚至允许大量定制内核的行为。虽然 eBPF 虚拟机对于每种类型的钩子都是相同的，但钩子的功能却有很大差异。因为将程序加载到内核中可能很危险，所以内核通过非常严格的静态验证器运行所有程序；静态验证器对程序进行沙盒处理，确保它只能访问允许的内存部分，并确保它必须迅速终止。\neBPF is a Linux kernel feature that allows fast yet safe mini-programs to be loaded into the kernel in order to customise its operation.\neBPF的优势 ebpf的几个优势：\n更高的吞吐IO 更低的CPU资源占用 无需kube-proxy即可对K8S服务实现原生支持 更低的首个数据包延迟 外部请求可保留客户端源IP（Preserves external client source IP） 支持 DSR (Direct Server Return) 相比kube-proxy，ebpf数据面同步转发规则时占用的资源更少 更多的信息可以查看calico官方的这篇介绍文章。\ncalico配置eBPF 升级内核 我们使用的centos7系统，根据文档比较适合的内核版本需要大于5.8，因此我们直接使用elrepo源升级最新的6.1.4版本内核。\n# 查看elrepo源中支持的内核版本 [root@k8s-calico-master-10-31-90-1 ~]# yum --disablerepo=\u0026#34;*\u0026#34; --enablerepo=\u0026#34;elrepo-kernel\u0026#34; list available Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile elrepo-kernel | 3.0 kB 00:00:00 elrepo-kernel/x86_64/primary_db | 2.1 MB 00:00:00 Available Packages elrepo-release.noarch 7.0-6.el7.elrepo elrepo-kernel kernel-lt.x86_64 5.4.228-1.el7.elrepo elrepo-kernel kernel-lt-devel.x86_64 5.4.228-1.el7.elrepo elrepo-kernel kernel-lt-doc.noarch 5.4.228-1.el7.elrepo elrepo-kernel kernel-lt-headers.x86_64 5.4.228-1.el7.elrepo elrepo-kernel kernel-lt-tools.x86_64 5.4.228-1.el7.elrepo elrepo-kernel kernel-lt-tools-libs.x86_64 5.4.228-1.el7.elrepo elrepo-kernel kernel-lt-tools-libs-devel.x86_64 5.4.228-1.el7.elrepo elrepo-kernel kernel-ml.x86_64 6.1.4-1.el7.elrepo elrepo-kernel kernel-ml-devel.x86_64 6.1.4-1.el7.elrepo elrepo-kernel kernel-ml-doc.noarch 6.1.4-1.el7.elrepo elrepo-kernel kernel-ml-headers.x86_64 6.1.4-1.el7.elrepo elrepo-kernel kernel-ml-tools.x86_64 6.1.4-1.el7.elrepo elrepo-kernel kernel-ml-tools-libs.x86_64 6.1.4-1.el7.elrepo elrepo-kernel kernel-ml-tools-libs-devel.x86_64 6.1.4-1.el7.elrepo elrepo-kernel perf.x86_64 5.4.228-1.el7.elrepo elrepo-kernel python-perf.x86_64 5.4.228-1.el7.elrepo elrepo-kernel # 看起来ml版本的内核比较满足我们的需求,直接使用yum进行安装 sudo yum --enablerepo=elrepo-kernel install kernel-ml -y # 使用grubby工具查看系统中已经安装的内核版本信息 sudo grubby --info=ALL # 设置新安装的6.1.4版本内核为默认内核版本，此处的index=0要和上面查看的内核版本信息一致 sudo grubby --set-default-index=0 # 查看默认内核是否修改成功 sudo grubby --default-kernel # 重启系统切换到新内核 init 6 # 重启后检查内核版本是否为新的6.1.4 uname -a 确认系统内核升级成功后我们继续下一步\n$ ansible calico -m shell -a \u0026#34;uname -rv\u0026#34; 10.31.90.4 | CHANGED | rc=0 \u0026gt;\u0026gt; 6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023 10.31.90.5 | CHANGED | rc=0 \u0026gt;\u0026gt; 6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023 10.31.90.3 | CHANGED | rc=0 \u0026gt;\u0026gt; 6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023 10.31.90.2 | CHANGED | rc=0 \u0026gt;\u0026gt; 6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023 10.31.90.1 | CHANGED | rc=0 \u0026gt;\u0026gt; 6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023 10.31.90.6 | CHANGED | rc=0 \u0026gt;\u0026gt; 6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023 配置API Server 在默认情况下，calico是通过kube-proxy和集群内的apiserver进行通信的。当我们开启了ebpf之后，往往会关闭集群的kube-proxy功能，此时为了保证calico能够和apiserver进行通信，就需要我们先手动配置一个稳定可用的地址。一般来说，使用我们初始化集群的时候配置的VIP和端口即可。\n$ cat kubernetes-services-endpoint.yaml kind: ConfigMap apiVersion: v1 metadata: name: kubernetes-services-endpoint namespace: tigera-operator data: KUBERNETES_SERVICE_HOST: \u0026#34;k8s-calico-apiserver.tinychen.io\u0026#34; KUBERNETES_SERVICE_PORT: \u0026#34;8443\u0026#34; $ kubectl create -f kubernetes-services-endpoint.yaml configmap/kubernetes-services-endpoint created 更新配置后检查pod是否重启成功，以及集群是否正常\n$ watch kubectl get pods -n calico-system $ calicoctl node status 配置kube-proxy 因为ebpf会和kube-proxy冲突，比较好的方式是禁用掉kube-proxy。而禁用kube-proxy主要有两种方式，一种是直接将其对应的daemonset删除，另一种则是通过nodeSelector的方式将特定的node设置为不运行kube-proxy。从官方的文档来看两种方式各有优势，如果是初始化k8s集群的时候就直接使用ebpf的话可以考虑直接在初始化参数中将kube-proxy禁用，这里我们已经安装好了kube-proxy，则使用nodeSelector的方式控制会更为优雅。\n$ kubectl patch ds -n kube-system kube-proxy -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;template\u0026#34;:{\u0026#34;spec\u0026#34;:{\u0026#34;nodeSelector\u0026#34;:{\u0026#34;non-calico\u0026#34;: \u0026#34;true\u0026#34;}}}}}\u0026#39; 此时我们再查看集群的kube-proxy状态可以发现DESIRED和CURRENT均为0\n$ kubectl get ds -n kube-system kube-proxy NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-proxy 0 0 0 0 0 kubernetes.io/os=linux,non-calico=true 4d9h 配置eBPF 开启eBPF只需要修改linuxDataplane参数即可，注意eBPF模式不支持配置hostPorts，因此需要同时将其设置为空。\n$ kubectl patch installation.operator.tigera.io default --type merge -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;calicoNetwork\u0026#34;:{\u0026#34;linuxDataplane\u0026#34;:\u0026#34;BPF\u0026#34;, \u0026#34;hostPorts\u0026#34;:null}}}\u0026#39; installation.operator.tigera.io/default patched 等待calico滚动重启完成，即成功开启eBPF。注意在滚动重启的过程中，会存在部分node使用eBPF而部分node使用iptables的情况。\n配置DSR eBPF还有一项不错的功能称之为DSR（Direct Server Return），即pod在接收到外部的请求之后，直接由pod本身对客户端进行回包，而不是再经由原来的路径回包，这样可以有效缩短回包路径从而提升性能。\nDSR模式的工作效果和LVS的DR模式十分相似，但是一般来说，DSR需要pod和客户端之间的网络本身是正常联通的，所以默认情况下该项功能并没有开启。\n我们可以使用calicoctl来修改felixconfiguration中的bpfExternalServiceMode参数，将其从默认的Tunnel修改为DSR即可启用。\n# 开启DSR模式 $ calicoctl patch felixconfiguration default --patch=\u0026#39;{\u0026#34;spec\u0026#34;: {\u0026#34;bpfExternalServiceMode\u0026#34;: \u0026#34;DSR\u0026#34;}}\u0026#39; Successfully patched 1 \u0026#39;FelixConfiguration\u0026#39; resource 如果需要关闭DSR模式只需要逆向修改回去即可\n# 关闭DSR模式 $ calicoctl patch felixconfiguration default --patch=\u0026#39;{\u0026#34;spec\u0026#34;: {\u0026#34;bpfExternalServiceMode\u0026#34;: \u0026#34;Tunnel\u0026#34;}}\u0026#39; 检验eBPF calico-bpf 官方在calico-node中内置了calico-bpf来帮助我们排查定位eBPF模式下的一些问题，我们也可以用它来检测集群的eBPF是否工作正常。\n对应的ds/calico-node也可以替换为某个特定node上面的calico-node的pod名称\n# 查看calico-bpf工具使用说明 $ kubectl exec -n calico-system ds/calico-node -- calico-node -bpf # 查看eth0网卡计数器 $ kubectl exec -n calico-system ds/calico-node -- calico-node -bpf counters dump --iface=eth0 # 查看conntrack情况 $ kubectl exec -n calico-system ds/calico-node -- calico-node -bpf conntrack dump # 查看路由表 $ kubectl exec -n calico-system ds/calico-node -- calico-node -bpf routes dump 访问服务 我们在集群外的机器进行测试，分别访问podIP 、clusterIP和loadbalancerIP，查看是否能够正确返回客户端的IP地址10.31.100.100。\n# 访问pod IP root@tiny-unraid:~# curl 10.33.26.2 10.31.100.100:43240 # 访问clusterIP root@tiny-unraid:~# curl 10.33.151.137 10.31.100.100:52758 # 访问loadbalancerIP root@tiny-unraid:~# curl 10.33.192.0 10.31.100.100:7319 在配置了eBPF的情况下，从集群外访问clusterIP和loadbalancerIP都是能够正常返回客户端的IP地址的。\n关闭eBPF 关闭eBPF的操作也是非常简单，只需要逆向操作上面的过程即可，这里直接关闭calico的eBPF功能（DSR只在eBPF下生效），再开启k8s集群的kube-proxy即可。\n# 设置linuxDataplane为iptables模式 $ kubectl patch installation.operator.tigera.io default --type merge -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;calicoNetwork\u0026#34;:{\u0026#34;linuxDataplane\u0026#34;:\u0026#34;Iptables\u0026#34;}}}\u0026#39; # 关闭nodeSelector,开启k8s集群的kube-proxy $ kubectl patch ds -n kube-system kube-proxy --type merge -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;template\u0026#34;:{\u0026#34;spec\u0026#34;:{\u0026#34;nodeSelector\u0026#34;:{\u0026#34;non-calico\u0026#34;: null}}}}}\u0026#39; 配置完成后，等待全部节点的calico重启完成并且kube-proxy启动完成即可。\n","permalink":"http://localhost:9011/20230117-k8s-14-calico-enable-ebpf/","summary":"\u003cp\u003e本文主要介绍如何在calico集群上开启eBPF加速网络数据转发，同时会对eBPF及其在calico中的一些优势特点进行介绍。\u003c/p\u003e\n\u003cp\u003e关于本次使用的calico集群的部署过程可以参考之前的文章\u003ca href=\"https://tinychen.com/20230109-k8s-13-deploy-ha-k8s-with-calico-bgp/\"\u003ek8s系列13-calico部署BGP模式的高可用k8s集群\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列14-calico开启eBPF"},{"content":"本文主要在centos7系统上基于containerd和v3.24.5版本的calico组件部署v1.26.0版本的堆叠ETCD高可用k8s原生集群，在LoadBalancer上选择了PureLB和calico结合bird实现BGP路由可达的K8S集群部署。\n此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n准备工作 集群信息 机器均为16C16G的虚拟机，硬盘为100G。\nIP Hostname 10.31.90.0 k8s-calico-apiserver.tinychen.io 10.31.90.1 k8s-calico-master-10-31-90-1.tinychen.io 10.31.90.2 k8s-calico-master-10-31-90-2.tinychen.io 10.31.90.3 k8s-calico-master-10-31-90-3.tinychen.io 10.31.90.4 k8s-calico-worker-10-31-90-4.tinychen.io 10.31.90.5 k8s-calico-worker-10-31-90-5.tinychen.io 10.31.90.6 k8s-calico-worker-10-31-90-6.tinychen.io 10.33.0.0/17 podSubnet 10.33.128.0/18 serviceSubnet 10.33.192.0/18 LoadBalancerSubnet 检查mac和product_uuid 同一个k8s集群内的所有节点需要确保mac地址和product_uuid均唯一，开始集群初始化之前需要检查相关信息\n# 检查mac地址 ip link ifconfig -a # 检查product_uuid sudo cat /sys/class/dmi/id/product_uuid 配置ssh免密登录（可选） 如果k8s集群的节点有多个网卡，确保每个节点能通过正确的网卡互联访问\n# 在root用户下面生成一个公用的key，并配置可以使用该key免密登录 su root ssh-keygen cd /root/.ssh/ cat id_rsa.pub \u0026gt;\u0026gt; authorized_keys chmod 600 authorized_keys cat \u0026gt;\u0026gt; ~/.ssh/config \u0026lt;\u0026lt;EOF Host k8s-calico-master-10-31-90-1 HostName 10.31.90.1 User root Port 22 IdentityFile ~/.ssh/id_rsa Host k8s-calico-master-10-31-90-2 HostName 10.31.90.2 User root Port 22 IdentityFile ~/.ssh/id_rsa Host k8s-calico-master-10-31-90-3 HostName 10.31.90.3 User root Port 22 IdentityFile ~/.ssh/id_rsa Host k8s-calico-worker-10-31-90-4 HostName 10.31.90.4 User root Port 22 IdentityFile ~/.ssh/id_rsa Host k8s-calico-worker-10-31-90-5 HostName 10.31.90.5 User root Port 22 IdentityFile ~/.ssh/id_rsa Host k8s-calico-worker-10-31-90-6 HostName 10.31.90.6 User root Port 22 IdentityFile ~/.ssh/id_rsa EOF 修改hosts文件 cat \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt;EOF 10.31.90.0 k8s-calico-apiserver k8s-calico-apiserver.tinychen.io 10.31.90.1 k8s-calico-master-10-31-90-1 k8s-calico-master-10-31-90-1.tinychen.io 10.31.90.2 k8s-calico-master-10-31-90-2 k8s-calico-master-10-31-90-2.tinychen.io 10.31.90.3 k8s-calico-master-10-31-90-3 k8s-calico-master-10-31-90-3.tinychen.io 10.31.90.4 k8s-calico-worker-10-31-90-4 k8s-calico-worker-10-31-90-4.tinychen.io 10.31.90.5 k8s-calico-worker-10-31-90-5 k8s-calico-worker-10-31-90-5.tinychen.io 10.31.90.6 k8s-calico-worker-10-31-90-6 k8s-calico-worker-10-31-90-6.tinychen.io EOF 关闭swap内存 # 使用命令直接关闭swap内存 swapoff -a # 修改fstab文件禁止开机自动挂载swap分区 sed -i \u0026#39;/swap / s/^\\(.*\\)$/#\\1/g\u0026#39; /etc/fstab 配置时间同步 这里可以根据自己的习惯选择ntp或者是chrony同步均可，同步的时间源服务器可以选择阿里云的ntp1.aliyun.com或者是国家时间中心的ntp.ntsc.ac.cn。\n使用ntp同步 # 使用yum安装ntpdate工具 yum install ntpdate -y # 使用国家时间中心的源同步时间 ntpdate ntp.ntsc.ac.cn # 最后查看一下时间 hwclock 使用chrony同步 # 使用yum安装chrony yum install chrony -y # 设置开机启动并开启chony并查看运行状态 systemctl enable chronyd.service systemctl start chronyd.service systemctl status chronyd.service # 当然也可以自定义时间服务器 vim /etc/chrony.conf # 修改前 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server 0.centos.pool.ntp.org iburst server 1.centos.pool.ntp.org iburst server 2.centos.pool.ntp.org iburst server 3.centos.pool.ntp.org iburst # 修改后 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server ntp.ntsc.ac.cn iburst # 重启服务使配置文件生效 systemctl restart chronyd.service # 查看chrony的ntp服务器状态 chronyc sourcestats -v chronyc sources -v 关闭selinux # 使用命令直接关闭 setenforce 0 # 也可以直接修改/etc/selinux/config文件 sed -i \u0026#39;s/^SELINUX=enforcing$/SELINUX=disabled/\u0026#39; /etc/selinux/config 配置防火墙 k8s集群之间通信和服务暴露需要使用较多端口，为了方便，直接禁用防火墙\n# centos7使用systemctl禁用默认的firewalld服务 systemctl disable firewalld.service 配置netfilter参数 这里主要是需要配置内核加载br_netfilter和iptables放行ipv6和ipv4的流量，确保集群内的容器能够正常通信。\ncat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter EOF cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sudo sysctl --system 配置IPVS IPVS是专门设计用来应对负载均衡场景的组件，kube-proxy 中的 IPVS 实现通过减少对 iptables 的使用来增加可扩展性。在 iptables 输入链中不使用 PREROUTING，而是创建一个假的接口，叫做 kube-ipvs0，当k8s集群中的负载均衡配置变多的时候，IPVS能实现比iptables更高效的转发性能。\n注意在4.19之后的内核版本中使用nf_conntrack模块来替换了原有的nf_conntrack_ipv4模块\n(Notes: use nf_conntrack instead of nf_conntrack_ipv4 for Linux kernel 4.19 and later)\n# 在使用ipvs模式之前确保安装了ipset和ipvsadm sudo yum install ipset ipvsadm -y # 手动加载ipvs相关模块 modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack # 配置开机自动加载ipvs相关模块 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/ipvs.conf ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack EOF $ lsmod | grep -e ip_vs -e nf_conntrack nf_conntrack_netlink 49152 0 nfnetlink 20480 2 nf_conntrack_netlink ip_vs_sh 16384 0 ip_vs_wrr 16384 0 ip_vs_rr 16384 0 ip_vs 159744 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr nf_conntrack 159744 5 xt_conntrack,nf_nat,nf_conntrack_netlink,xt_MASQUERADE,ip_vs nf_defrag_ipv4 16384 1 nf_conntrack nf_defrag_ipv6 24576 2 nf_conntrack,ip_vs libcrc32c 16384 4 nf_conntrack,nf_nat,xfs,ip_vs $ cut -f1 -d \u0026#34; \u0026#34; /proc/modules | grep -e ip_vs -e nf_conntrack nf_conntrack_netlink ip_vs_sh ip_vs_wrr ip_vs_rr ip_vs nf_conntrack 安装container runtime 安装containerd 详细的官方文档可以参考这里，由于在刚发布的1.24版本中移除了docker-shim，因此安装的版本≥1.24的时候需要注意容器运行时的选择。这里我们安装的版本为最新的1.26，因此我们不能继续使用docker，这里我们将其换为containerd\n修改Linux内核参数 # 首先生成配置文件确保配置持久化 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter # Setup required sysctl params, these persist across reboots. cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF # Apply sysctl params without reboot sudo sysctl --system 安装containerd centos7比较方便的部署方式是利用已有的yum源进行安装，这里我们可以使用docker官方的yum源来安装containerd\n# 导入docker官方的yum源 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 查看yum源中存在的各个版本的containerd.io yum list containerd.io --showduplicates | sort -r # 直接安装最新版本的containerd.io yum install containerd.io -y # 启动containerd sudo systemctl start containerd # 最后我们还要设置一下开机启动 sudo systemctl enable --now containerd 关于CRI 官方表示，对于k8s来说，不需要安装cri-containerd，并且该功能会在后面的2.0版本中废弃。\nFAQ: For Kubernetes, do I need to download cri-containerd-(cni-)\u0026lt;VERSION\u0026gt;-\u0026lt;OS-\u0026lt;ARCH\u0026gt;.tar.gz too?\nAnswer: No.\nAs the Kubernetes CRI feature has been already included in containerd-\u0026lt;VERSION\u0026gt;-\u0026lt;OS\u0026gt;-\u0026lt;ARCH\u0026gt;.tar.gz, you do not need to download the cri-containerd-.... archives to use CRI.\nThe cri-containerd-... archives are deprecated, do not work on old Linux distributions, and will be removed in containerd 2.0.\n安装cni-plugins 使用yum源安装的方式会把runc安装好，但是并不会安装cni-plugins，因此这部分还是需要我们自行安装。\nThe containerd.io package contains runc too, but does not contain CNI plugins.\n我们直接在github上面找到系统对应的架构版本，这里为amd64，然后解压即可。\n# Download the cni-plugins-\u0026lt;OS\u0026gt;-\u0026lt;ARCH\u0026gt;-\u0026lt;VERSION\u0026gt;.tgz archive from https://github.com/containernetworking/plugins/releases , verify its sha256sum, and extract it under /opt/cni/bin: # 下载源文件和sha512文件并校验 $ wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz $ wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz.sha512 $ sha512sum -c cni-plugins-linux-amd64-v1.1.1.tgz.sha512 # 创建目录并解压 $ mkdir -p /opt/cni/bin $ tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.1.1.tgz 配置cgroup drivers CentOS7使用的是systemd来初始化系统并管理进程，初始化进程会生成并使用一个 root 控制组 (cgroup), 并充当 cgroup 管理器。 Systemd 与 cgroup 集成紧密，并将为每个 systemd 单元分配一个 cgroup。 我们也可以配置容器运行时和 kubelet 使用 cgroupfs。 连同 systemd 一起使用 cgroupfs 意味着将有两个不同的 cgroup 管理器。而当一个系统中同时存在cgroupfs和systemd两者时，容易变得不稳定，因此最好更改设置，令容器运行时和 kubelet 使用 systemd 作为 cgroup 驱动，以此使系统更为稳定。 对于containerd, 需要设置配置文件/etc/containerd/config.toml中的 SystemdCgroup 参数。\n参考k8s官方的说明文档：\nhttps://kubernetes.io/docs/setup/production-environment/container-runtimes/#containerd-systemd\n[plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc] ... [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc.options] SystemdCgroup = true 接下来我们开始配置containerd的cgroup driver\n# 查看默认的配置文件，我们可以看到是没有启用systemd $ containerd config default | grep SystemdCgroup SystemdCgroup = false # 使用yum安装的containerd的配置文件非常简单 $ cat /etc/containerd/config.toml | egrep -v \u0026#34;^#|^$\u0026#34; disabled_plugins = [\u0026#34;cri\u0026#34;] # 导入一个完整版的默认配置文件模板为config.toml $ mv /etc/containerd/config.toml /etc/containerd/config.toml.origin $ containerd config default \u0026gt; /etc/containerd/config.toml # 修改SystemdCgroup参数并重启 $ sed -i \u0026#39;s/SystemdCgroup = false/SystemdCgroup = true/g\u0026#39; /etc/containerd/config.toml $ systemctl restart containerd # 重启之后我们再检查配置就会发现已经启用了SystemdCgroup $ containerd config dump | grep SystemdCgroup SystemdCgroup = true # 查看containerd状态的时候我们可以看到cni相关的报错 # 这是因为我们先安装了cni-plugins但是还没有安装k8s的cni插件 # 属于正常情况 $ systemctl status containerd -l May 12 09:57:31 tiny-kubeproxy-free-master-18-1.k8s.tcinternal containerd[5758]: time=\u0026#34;2022-05-12T09:57:31.100285056+08:00\u0026#34; level=error msg=\u0026#34;failed to load cni during init, please check CRI plugin status before setting up network for pods\u0026#34; error=\u0026#34;cni config load failed: no network config found in /etc/cni/net.d: cni plugin not initialized: failed to load cni config\u0026#34; 关于kubelet的cgroup driver k8s官方有详细的文档介绍了如何设置kubelet的cgroup driver，需要特别注意的是，在1.22版本开始，如果没有手动设置kubelet的cgroup driver，那么默认会设置为systemd\nNote: In v1.22, if the user is not setting the cgroupDriver field under KubeletConfiguration, kubeadm will default it to systemd.\n一个比较简单的指定kubelet的cgroup driver的方法就是在kubeadm-config.yaml加入cgroupDriver字段\n# kubeadm-config.yaml kind: ClusterConfiguration apiVersion: kubeadm.k8s.io/v1beta3 kubernetesVersion: v1.21.0 --- kind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 cgroupDriver: systemd 我们可以直接查看configmaps来查看初始化之后集群的kubeadm-config配置。\n$ kubectl describe configmaps kubeadm-config -n kube-system Name: kubeadm-config Namespace: kube-system Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; Data ==== ClusterConfiguration: ---- apiServer: extraArgs: authorization-mode: Node,RBAC timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: v1.23.6 networking: dnsDomain: cali-cluster.tclocal serviceSubnet: 10.88.0.0/18 scheduler: {} BinaryData ==== Events: \u0026lt;none\u0026gt; 当然因为我们需要安装的版本高于1.22.0并且使用的就是systemd，因此可以不用再重复配置。\n安装kube三件套 对应的官方文档可以参考这里\nhttps://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl\nkube三件套就是kubeadm、kubelet 和 kubectl，三者的具体功能和作用如下：\nkubeadm：用来初始化集群的指令。 kubelet：在集群中的每个节点上用来启动 Pod 和容器等。 kubectl：用来与集群通信的命令行工具。 需要注意的是：\nkubeadm不会帮助我们管理kubelet和kubectl，其他两者也是一样的，也就是说这三者是相互独立的，并不存在谁管理谁的情况； kubelet的版本必须小于等于API-server的版本，否则容易出现兼容性的问题； kubectl并不是集群中的每个节点都需要安装，也并不是一定要安装在集群中的节点，可以单独安装在自己本地的机器环境上面，然后配合kubeconfig文件即可使用kubectl命令来远程管理对应的k8s集群； CentOS7的安装比较简单，我们直接使用官方提供的yum源即可。需要注意的是这里需要设置selinux的状态，但是前面我们已经关闭了selinux，因此这里略过这步。\n# 直接导入谷歌官方的yum源 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\\$basearch enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg exclude=kubelet kubeadm kubectl EOF # 当然如果连不上谷歌的源，可以考虑使用国内的阿里镜像源 cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF # 接下来直接安装三件套即可 sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes # 如果网络环境不好出现gpgcheck验证失败导致无法正常读取yum源，可以考虑关闭该yum源的repo_gpgcheck sed -i \u0026#39;s/repo_gpgcheck=1/repo_gpgcheck=0/g\u0026#39; /etc/yum.repos.d/kubernetes.repo # 或者在安装的时候禁用gpgcheck sudo yum install -y kubelet kubeadm kubectl --nogpgcheck --disableexcludes=kubernetes # 如果想要安装特定版本，可以使用这个命令查看相关版本的信息 sudo yum list --nogpgcheck kubelet kubeadm kubectl --showduplicates --disableexcludes=kubernetes # 安装完成后配置开机自启kubelet sudo systemctl enable --now kubelet 初始化集群 etcd高可用 etcd高可用架构参考这篇官方文档，主要可以分为堆叠etcd方案和外置etcd方案，两者的区别就是etcd是否部署在apiserver所在的node机器上面，这里我们主要使用的是堆叠etcd部署方案。\napiserver高可用 apisever高可用配置参考这篇官方文档。目前apiserver的高可用比较主流的官方推荐方案是使用keepalived和haproxy，由于centos7自带的版本较旧，重新编译又过于麻烦，因此我们可以参考官方给出的静态pod的部署方式，提前将相关的配置文件放置到/etc/kubernetes/manifests目录下即可(需要提前手动创建好目录)。官方表示对于我们这种堆叠部署控制面master节点和etcd的方式而言这是一种优雅的解决方案。\nThis is an elegant solution, in particular with the setup described under Stacked control plane and etcd nodes.\n首先我们需要准备好三台master节点上面的keepalived配置文件和haproxy配置文件：\n! /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { router_id LVS_DEVEL } vrrp_script check_apiserver { script \u0026#34;/etc/keepalived/check_apiserver.sh\u0026#34; interval 3 weight -2 fall 10 rise 2 } vrrp_instance VI_1 { state ${STATE} interface ${INTERFACE} virtual_router_id ${ROUTER_ID} priority ${PRIORITY} authentication { auth_type PASS auth_pass ${AUTH_PASS} } virtual_ipaddress { ${APISERVER_VIP} } track_script { check_apiserver } } 实际上我们需要区分三台控制面节点的状态\n! /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { router_id CALICO_MASTER_90_1 } vrrp_script check_apiserver { script \u0026#34;/etc/keepalived/check_apiserver.sh\u0026#34; interval 3 weight -2 fall 10 rise 2 } vrrp_instance calico_ha_apiserver_10_31_90_0 { state MASTER interface eth0 virtual_router_id 90 priority 100 authentication { auth_type PASS auth_pass pass@77 } virtual_ipaddress { 10.31.90.0 } track_script { check_apiserver } } ! /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { router_id CALICO_MASTER_90_2 } vrrp_script check_apiserver { script \u0026#34;/etc/keepalived/check_apiserver.sh\u0026#34; interval 3 weight -2 fall 10 rise 2 } vrrp_instance calico_ha_apiserver_10_31_90_0 { state BACKUP interface eth0 virtual_router_id 90 priority 99 authentication { auth_type PASS auth_pass pass@77 } virtual_ipaddress { 10.31.90.0 } track_script { check_apiserver } } ! /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { router_id CALICO_MASTER_90_3 } vrrp_script check_apiserver { script \u0026#34;/etc/keepalived/check_apiserver.sh\u0026#34; interval 3 weight -2 fall 10 rise 2 } vrrp_instance calico_ha_apiserver_10_31_90_0 { state BACKUP interface eth0 virtual_router_id 90 priority 98 authentication { auth_type PASS auth_pass pass@77 } virtual_ipaddress { 10.31.90.0 } track_script { check_apiserver } } 这是haproxy的配置文件模板：\n# /etc/haproxy/haproxy.cfg #--------------------------------------------------------------------- # Global settings #--------------------------------------------------------------------- global log /dev/log local0 log /dev/log local1 notice daemon #--------------------------------------------------------------------- # common defaults that all the \u0026#39;listen\u0026#39; and \u0026#39;backend\u0026#39; sections will # use if not designated in their block #--------------------------------------------------------------------- defaults mode http log global option httplog option dontlognull option http-server-close option forwardfor except 127.0.0.0/8 option redispatch retries 1 timeout http-request 10s timeout queue 20s timeout connect 5s timeout client 20s timeout server 20s timeout http-keep-alive 10s timeout check 10s #--------------------------------------------------------------------- # apiserver frontend which proxys to the control plane nodes #--------------------------------------------------------------------- frontend apiserver bind *:${APISERVER_DEST_PORT} mode tcp option tcplog default_backend apiserver #--------------------------------------------------------------------- # round robin balancing for apiserver #--------------------------------------------------------------------- backend apiserver option httpchk GET /healthz http-check expect status 200 mode tcp option ssl-hello-chk balance roundrobin server ${HOST1_ID} ${HOST1_ADDRESS}:${APISERVER_SRC_PORT} check # [...] 这是keepalived的检测脚本，注意这里的${APISERVER_VIP}和${APISERVER_DEST_PORT}要替换为集群的实际VIP和端口\n#!/bin/sh APISERVER_VIP=\u0026#34;10.31.90.0\u0026#34; APISERVER_DEST_PORT=\u0026#34;8443\u0026#34; errorExit() { echo \u0026#34;*** $*\u0026#34; 1\u0026gt;\u0026amp;2 exit 1 } curl --silent --max-time 2 --insecure https://localhost:${APISERVER_DEST_PORT}/ -o /dev/null || errorExit \u0026#34;Error GET https://localhost:${APISERVER_DEST_PORT}/\u0026#34; if ip addr | grep -q ${APISERVER_VIP}; then curl --silent --max-time 2 --insecure https://${APISERVER_VIP}:${APISERVER_DEST_PORT}/ -o /dev/null || errorExit \u0026#34;Error GET https://${APISERVER_VIP}:${APISERVER_DEST_PORT}/\u0026#34; fi 这是keepalived的部署文件/etc/kubernetes/manifests/keepalived.yaml，注意这里的配置文件路径要和上面的对应一致。\napiVersion: v1 kind: Pod metadata: creationTimestamp: null name: keepalived namespace: kube-system spec: containers: - image: osixia/keepalived:2.0.17 name: keepalived resources: {} securityContext: capabilities: add: - NET_ADMIN - NET_BROADCAST - NET_RAW volumeMounts: - mountPath: /usr/local/etc/keepalived/keepalived.conf name: config - mountPath: /etc/keepalived/check_apiserver.sh name: check hostNetwork: true volumes: - hostPath: path: /etc/keepalived/keepalived.conf name: config - hostPath: path: /etc/keepalived/check_apiserver.sh name: check status: {} 这是haproxy的部署文件/etc/kubernetes/manifests/haproxy.yaml，注意这里的配置文件路径要和上面的对应一致，且${APISERVER_DEST_PORT}要换成我们对应的apiserver的端口，这里我们改为8443，避免和原有的6443端口冲突\napiVersion: v1 kind: Pod metadata: name: haproxy namespace: kube-system spec: containers: - image: haproxy:2.1.4 name: haproxy livenessProbe: failureThreshold: 8 httpGet: host: localhost path: /healthz #port: ${APISERVER_DEST_PORT} port: 8443 scheme: HTTPS volumeMounts: - mountPath: /usr/local/etc/haproxy/haproxy.cfg name: haproxyconf readOnly: true hostNetwork: true volumes: - hostPath: path: /etc/haproxy/haproxy.cfg type: FileOrCreate name: haproxyconf status: {} 编写配置文件 在集群中所有节点都执行完上面的操作之后，我们就可以开始创建k8s集群了。因为我们这次需要进行高可用部署，所以初始化的时候先挑任意一台master控制面节点进行操作即可。\n# 我们先使用kubeadm命令查看一下主要的几个镜像版本 $ kubeadm config images list registry.k8s.io/kube-apiserver:v1.26.0 registry.k8s.io/kube-controller-manager:v1.26.0 registry.k8s.io/kube-scheduler:v1.26.0 registry.k8s.io/kube-proxy:v1.26.0 registry.k8s.io/pause:3.9 registry.k8s.io/etcd:3.5.6-0 registry.k8s.io/coredns/coredns:v1.9.3 # 为了方便编辑和管理，我们还是把初始化参数导出成配置文件 $ kubeadm config print init-defaults \u0026gt; kubeadm-calico-ha.conf 考虑到大多数情况下国内的网络无法使用谷歌的镜像源(1.25版本开始从k8s.gcr.io换为registry.k8s.io)，我们可以直接在配置文件中修改imageRepository参数为阿里的镜像源registry.aliyuncs.com/google_containers kubernetesVersion字段用来指定我们要安装的k8s版本 localAPIEndpoint参数需要修改为我们的master节点的IP和端口，初始化之后的k8s集群的apiserver地址就是这个 criSocket从1.24.0版本开始已经默认变成了containerd podSubnet、serviceSubnet和dnsDomain两个参数默认情况下可以不用修改，这里我按照自己的需求进行了变更 nodeRegistration里面的name参数修改为对应master节点的hostname controlPlaneEndpoint参数配置的才是我们前面配置的集群高可用apiserver的地址 新增配置块使用ipvs，具体可以参考官方文档 apiVersion: kubeadm.k8s.io/v1beta3 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 10.31.90.1 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock imagePullPolicy: IfNotPresent name: k8s-calico-master-10-31-90-1.tinychen.io taints: null --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: 1.26.0 controlPlaneEndpoint: \u0026#34;k8s-calico-apiserver.tinychen.io:8443\u0026#34; networking: dnsDomain: cali-cluster.tclocal serviceSubnet: 10.33.128.0/18 podSubnet: 10.33.0.0/17 scheduler: {} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs 初始化集群 此时我们再查看对应的配置文件中的镜像版本，就会发现已经变成了对应阿里云镜像源的版本\n# 查看一下对应的镜像版本，确定配置文件是否生效 $ kubeadm config images list --config kubeadm-calico-ha.conf registry.aliyuncs.com/google_containers/kube-apiserver:v1.26.0 registry.aliyuncs.com/google_containers/kube-controller-manager:v1.26.0 registry.aliyuncs.com/google_containers/kube-scheduler:v1.26.0 registry.aliyuncs.com/google_containers/kube-proxy:v1.26.0 registry.aliyuncs.com/google_containers/pause:3.9 registry.aliyuncs.com/google_containers/etcd:3.5.6-0 registry.aliyuncs.com/google_containers/coredns:v1.9.3 # 确认没问题之后我们直接拉取镜像 $ kubeadm config images pull --config kubeadm-calico-ha.conf [config/images] Pulled registry.aliyuncs.com/google_containers/kube-apiserver:v1.26.0 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-controller-manager:v1.26.0 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-scheduler:v1.26.0 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-proxy:v1.26.0 [config/images] Pulled registry.aliyuncs.com/google_containers/pause:3.9 [config/images] Pulled registry.aliyuncs.com/google_containers/etcd:3.5.6-0 [config/images] Pulled registry.aliyuncs.com/google_containers/coredns:v1.9.3 # 初始化，注意添加参数--upload-certs确保证书能够上传到kubernetes集群中以secret保存 $ kubeadm init --config kubeadm-calico-ha.conf --upload-certs [init] Using Kubernetes version: v1.26.0 [preflight] Running pre-flight checks [preflight] Pulling images required for setting up a Kubernetes cluster [preflight] This might take a minute or two, depending on the speed of your internet connection [preflight] You can also perform this action in beforehand using \u0026#39;kubeadm config images pull\u0026#39; ...此处略去一堆输出... 当我们看到下面这个输出结果的时候，我们的集群就算是初始化成功了。\nYour Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf You should now deploy a pod network to the cluster. Run \u0026#34;kubectl apply -f [podnetwork].yaml\u0026#34; with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ You can now join any number of the control-plane node running the following command on each as root: kubeadm join k8s-calico-apiserver.tinychen.io:8443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:b451b6484f9b68fbd5b7959b2ae2333088322a12b941bf143131c15acca8728d \\ --control-plane --certificate-key 2dad0007267f115f594f4db514f4f664fd0fef4a639791f97893afb1409dbfa5 Please note that the certificate-key gives access to cluster sensitive data, keep it secret! As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use \u0026#34;kubeadm init phase upload-certs --upload-certs\u0026#34; to reload certs afterward. Then you can join any number of worker nodes by running the following on each as root: kubeadm join k8s-calico-apiserver.tinychen.io:8443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:b451b6484f9b68fbd5b7959b2ae2333088322a12b941bf143131c15acca8728d 接下来我们在剩下的两个master节点上面执行上面输出的命令，注意要执行带有--control-plane --certificate-key这两个参数的命令，其中--control-plane参数是确定该节点为master控制面节点，而--certificate-key参数则是把我们前面初始化集群的时候通过--upload-certs上传到k8s集群中的证书下载下来使用。\nThis node has joined the cluster and a new control plane instance was created: * Certificate signing request was sent to apiserver and approval was received. * The Kubelet was informed of the new secure connection details. * Control plane label and taint were applied to the new node. * The Kubernetes control plane instances scaled up. * A new etcd member was added to the local/stacked etcd cluster. To start administering your cluster from this node, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Run \u0026#39;kubectl get nodes\u0026#39; to see this node join the cluster. 最后再对剩下的三个worker节点执行普通的加入集群命令，当看到下面的输出的时候说明节点成功加入集群了。\nThis node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run \u0026#39;kubectl get nodes\u0026#39; on the control-plane to see this node join the cluster. 如果不小心没保存初始化成功的输出信息，或者是以后还需要新增节点也没有关系，我们可以使用kubectl工具查看或者生成token\n# 查看现有的token列表 $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS abcdef.0123456789abcdef 23h 2022-12-09T08:14:37Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token dss91p.3r5don4a3e9r2f29 1h 2022-12-08T10:14:36Z \u0026lt;none\u0026gt; Proxy for managing TTL for the kubeadm-certs secret \u0026lt;none\u0026gt; # 如果token已经失效，那就再创建一个新的token $ kubeadm token create 8hmoux.jabpgvs521r8rsqm $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS 8hmoux.jabpgvs521r8rsqm 23h 2022-12-09T08:29:29Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token abcdef.0123456789abcdef 23h 2022-12-09T08:14:37Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token dss91p.3r5don4a3e9r2f29 1h 2022-12-08T10:14:36Z \u0026lt;none\u0026gt; Proxy for managing TTL for the kubeadm-certs secret \u0026lt;none\u0026gt; # 如果找不到--discovery-token-ca-cert-hash参数，则可以在master节点上使用openssl工具来获取 $ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2\u0026gt;/dev/null | openssl dgst -sha256 -hex | sed \u0026#39;s/^.* //\u0026#39; cc68219b233262d8834ad5d6e96166be487c751b53fb9ec19a5ca3599b538a33 配置kubeconfig 刚初始化成功之后，我们还没办法马上查看k8s集群信息，需要配置kubeconfig相关参数才能正常使用kubectl连接apiserver读取集群信息。\n# 对于非root用户，可以这样操作 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 如果是root用户，可以直接导入环境变量 export KUBECONFIG=/etc/kubernetes/admin.conf # 添加kubectl的自动补全功能 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 前面我们提到过kubectl不一定要安装在集群内，实际上只要是任何一台能连接到apiserver的机器上面都可以安装kubectl并且根据步骤配置kubeconfig，就可以使用kubectl命令行来管理对应的k8s集群。\n配置完成后，我们再执行相关命令就可以查看集群的信息了，但是此时节点的状态还是NotReady，接下来就需要部署CNI了。\n$ kubectl cluster-info Kubernetes control plane is running at https://k8s-calico-apiserver.tinychen.io:8443 CoreDNS is running at https://k8s-calico-apiserver.tinychen.io:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39;. $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-calico-master-10-31-90-1.tinychen.io NotReady control-plane 7m55s v1.26.0 10.31.90.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 containerd://1.6.14 k8s-calico-master-10-31-90-2.tinychen.io NotReady control-plane 4m44s v1.26.0 10.31.90.2 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 containerd://1.6.14 k8s-calico-master-10-31-90-3.tinychen.io NotReady control-plane 2m44s v1.26.0 10.31.90.3 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 containerd://1.6.14 k8s-calico-worker-10-31-90-4.tinychen.io NotReady \u0026lt;none\u0026gt; 2m9s v1.26.0 10.31.90.4 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 containerd://1.6.14 k8s-calico-worker-10-31-90-5.tinychen.io NotReady \u0026lt;none\u0026gt; 91s v1.26.0 10.31.90.5 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 containerd://1.6.14 k8s-calico-worker-10-31-90-6.tinychen.io NotReady \u0026lt;none\u0026gt; 63s v1.26.0 10.31.90.6 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 containerd://1.6.14 $ kubectl get pods -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system coredns-5bbd96d687-l84hq 0/1 Pending 0 8m11s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system coredns-5bbd96d687-wbmdq 0/1 Pending 0 8m11s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-k8s-calico-master-10-31-90-1.tinychen.io 1/1 Running 0 8m10s 10.31.90.1 k8s-calico-master-10-31-90-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-k8s-calico-master-10-31-90-2.tinychen.io 1/1 Running 0 4m51s 10.31.90.2 k8s-calico-master-10-31-90-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-k8s-calico-master-10-31-90-3.tinychen.io 1/1 Running 0 3m 10.31.90.3 k8s-calico-master-10-31-90-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system haproxy-k8s-calico-master-10-31-90-1.tinychen.io 1/1 Running 0 8m10s 10.31.90.1 k8s-calico-master-10-31-90-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system haproxy-k8s-calico-master-10-31-90-2.tinychen.io 1/1 Running 0 4m45s 10.31.90.2 k8s-calico-master-10-31-90-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system haproxy-k8s-calico-master-10-31-90-3.tinychen.io 1/1 Running 0 3m 10.31.90.3 k8s-calico-master-10-31-90-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system keepalived-k8s-calico-master-10-31-90-1.tinychen.io 1/1 Running 0 8m10s 10.31.90.1 k8s-calico-master-10-31-90-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system keepalived-k8s-calico-master-10-31-90-2.tinychen.io 1/1 Running 0 4m57s 10.31.90.2 k8s-calico-master-10-31-90-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system keepalived-k8s-calico-master-10-31-90-3.tinychen.io 1/1 Running 0 3m1s 10.31.90.3 k8s-calico-master-10-31-90-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-k8s-calico-master-10-31-90-1.tinychen.io 1/1 Running 0 8m9s 10.31.90.1 k8s-calico-master-10-31-90-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-k8s-calico-master-10-31-90-2.tinychen.io 1/1 Running 0 4m43s 10.31.90.2 k8s-calico-master-10-31-90-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-k8s-calico-master-10-31-90-3.tinychen.io 1/1 Running 0 3m 10.31.90.3 k8s-calico-master-10-31-90-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-k8s-calico-master-10-31-90-1.tinychen.io 1/1 Running 0 8m10s 10.31.90.1 k8s-calico-master-10-31-90-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-k8s-calico-master-10-31-90-2.tinychen.io 1/1 Running 0 4m58s 10.31.90.2 k8s-calico-master-10-31-90-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-k8s-calico-master-10-31-90-3.tinychen.io 1/1 Running 0 3m 10.31.90.3 k8s-calico-master-10-31-90-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-9x6gc 1/1 Running 0 108s 10.31.90.5 k8s-calico-worker-10-31-90-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-jnfqm 1/1 Running 0 8m10s 10.31.90.1 k8s-calico-master-10-31-90-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-kb2d5 1/1 Running 0 80s 10.31.90.6 k8s-calico-worker-10-31-90-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-n5g6b 1/1 Running 0 5m1s 10.31.90.2 k8s-calico-master-10-31-90-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-tsqz8 1/1 Running 0 2m26s 10.31.90.4 k8s-calico-worker-10-31-90-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-wcgch 1/1 Running 0 3m1s 10.31.90.3 k8s-calico-master-10-31-90-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-k8s-calico-master-10-31-90-1.tinychen.io 1/1 Running 0 8m10s 10.31.90.1 k8s-calico-master-10-31-90-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-k8s-calico-master-10-31-90-2.tinychen.io 1/1 Running 0 4m51s 10.31.90.2 k8s-calico-master-10-31-90-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-k8s-calico-master-10-31-90-3.tinychen.io 1/1 Running 0 3m 10.31.90.3 k8s-calico-master-10-31-90-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 安装CNI 部署calico CNI的部署我们参考官网的自建K8S部署教程，官网主要给出了两种部署方式，分别是通过Calico operator和Calico manifests来进行部署和管理calico，operator是通过deployment的方式部署一个calico的operator到集群中，再用其来管理calico的安装升级等生命周期操作。manifests则是将相关都使用yaml的配置文件进行管理，这种方式管理起来相对前者比较麻烦，但是对于高度自定义的K8S集群有一定的优势。\n这里我们使用operator的方式进行部署。\n首先我们把需要用到的两个部署文件下载到本地。\ncurl https://raw.githubusercontent.com/projectcalico/calico/v3.24.5/manifests/tigera-operator.yaml -O curl https://raw.githubusercontent.com/projectcalico/calico/v3.24.5/manifests/custom-resources.yaml -O 随后我们修改custom-resources.yaml里面的pod ip段信息和划分子网的大小。\n# cat custom-resources.yaml # This section includes base Calico installation configuration. # For more information, see: https://projectcalico.docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Installation apiVersion: operator.tigera.io/v1 kind: Installation metadata: name: default spec: # Configures Calico networking. calicoNetwork: # Note: The ipPools section cannot be modified post-install. ipPools: - blockSize: 24 cidr: 10.33.0.0/17 encapsulation: VXLANCrossSubnet natOutgoing: Enabled nodeSelector: all() --- # This section configures the Calico API server. # For more information, see: https://projectcalico.docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.APIServer apiVersion: operator.tigera.io/v1 kind: APIServer metadata: name: default spec: {} 最后我们直接部署\nkubectl create -f tigera-operator.yaml kubectl create -f custom-resources.yaml 此时部署完成之后我们应该可以看到所有的pod和node都已经处于正常工作状态。接下来我们进入高级配置阶段\n安装calicoctl 接下来我们就要部署calicoctl来帮助我们管理calico的相关配置，为了使用 Calico 的许多功能，需要 calicoctl 命令行工具。它用于管理 Calico 策略和配置，以及查看详细的集群状态。\nThe calicoctl command line tool is required in order to use many of Calico’s features. It is used to manage Calico policies and configuration, as well as view detailed cluster status.\n这里我们可以直接使用二进制部署安装\ncurl -L https://github.com/projectcalico/calico/releases/download/v3.24.5/calicoctl-linux-amd64 -o /usr/local/bin/calicoctl chmod +x /usr/local/bin/calicoctl 至于配置也比较简单，因为我们这里使用的是直接连接apiserver的方式，所以直接配置环境变量即可\nexport CALICO_DATASTORE_TYPE=kubernetes export CALICO_KUBECONFIG=~/.kube/config calicoctl get workloadendpoints -A calicoctl node status 配置BGP 一般来说，calico的BGP拓扑可以分为三种配置：\nFull-mesh（全网状连接）：启用 BGP 后，Calico 的默认行为是创建内部 BGP (iBGP) 连接的全网状连接，其中每个节点相互对等。这允许 Calico 在任何 L2 网络上运行，无论是公共云还是私有云，或者是配置了基于IPIP的overlays网络。**Calico 不将 BGP 用于 VXLAN overlays网络。**全网状结构非常适合 100 个或更少节点的中小型部署，但在规模明显更大的情况下，全网状结构的效率会降低，calico建议使用路由反射器（Route reflectors）。\nRoute reflectors（路由反射器）：要构建大型内部 BGP (iBGP) 集群，可以使用BGP 路由反射器来减少每个节点上使用的 BGP 对等体的数量。在这个模型中，一些节点充当路由反射器，并被配置为在它们之间建立一个完整的网格。然后将其他节点配置为与这些路由反射器的子集对等（通常为 2 个用于冗余），与全网状相比减少了 BGP 对等连接的总数。\nTop of Rack (ToR)：在本地部署中，我们可以直接让calico和物理网络基础设施建立BGP连接，一般来说这需要先把calico默认自带的Full-mesh配置禁用掉，然后将calico和本地的L3 ToR路由建立连接。当整个自建集群的规模很大的时候（通常仅当每个 L2 域中的节点数大于100时），还可以考虑在每个机架内使用BGP的路由反射器（Route reflectors）。\n要深入了解常见的本地部署模型，请参阅Calico over IP Fabrics。\n我们这里只是一个小规模的测试集群（6节点），暂时用不上路由反射器这类复杂的配置，因此我们参考第三种TOR的模式，让node直接和我们测试网络内的L3路由器建立BGP连接即可。\n在刚初始化的情况下，我们的calico是还没有创建BGPConfiguration，此时我们需要先手动创建，并且禁用nodeToNodeMesh配置，同时还需要借助calico将集群的ClusterIP和ExternalIP都发布出去。\n$ cat calico-bgp-configuration.yaml apiVersion: projectcalico.org/v3 kind: BGPConfiguration metadata: name: default spec: logSeverityScreen: Info nodeToNodeMeshEnabled: false asNumber: 64517 serviceClusterIPs: - cidr: 10.33.128.0/18 serviceExternalIPs: - cidr: 10.33.192.0/18 listenPort: 179 bindMode: NodeIP communities: - name: bgp-large-community value: 64517:300:100 prefixAdvertisements: - cidr: 10.33.0.0/17 communities: - bgp-large-community - 64517:120 另一个就是需要准备BGPPeer的配置，可以同时配置一个或者多个，下面的示例配置了两个BGPPeer，并且ASN号各不相同。其中keepOriginalNextHop默认是不配置的，这里特别配置为true，确保通过BGP宣发pod IP段路由的时候只宣发对应的node，而不是针对podIP也开启ECMP功能。详细的配置可以参考官方文档\n$ cat calico-bgp-peer.yaml apiVersion: projectcalico.org/v3 kind: BGPPeer metadata: name: openwrt-peer spec: peerIP: 10.31.254.253 keepOriginalNextHop: true asNumber: 64512 --- apiVersion: projectcalico.org/v3 kind: BGPPeer metadata: name: tiny-unraid-peer spec: peerIP: 10.31.100.100 keepOriginalNextHop: true asNumber: 64516 配置完成之后我们直接部署即可，这时候集群默认的node-to-node-mesh就已经被我们禁用，此外还可以看到我们配置的两个BGPPeer已经顺利建立连接并发布路由了。\n$ kubectl create -f calico-bgp-configuration.yaml $ kubectl create -f calico-bgp-peer.yaml $ calicoctl node status Calico process is running. IPv4 BGP status +---------------+-----------+-------+----------+-------------+ | PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO | +---------------+-----------+-------+----------+-------------+ | 10.31.254.253 | global | up | 08:03:49 | Established | | 10.31.100.100 | global | up | 08:12:01 | Established | +---------------+-----------+-------+----------+-------------+ IPv6 BGP status No IPv6 peers found. 配置LoadBalancer 目前市面上开源的K8S-LoadBalancer主要就是MetalLB、OpenELB和PureLB这三种，三者的工作原理和使用教程我都写文章分析过，针对目前这种使用场景，我个人认为最合适的是使用PureLB，因为他的组件高度模块化，并且可以自由选择实现ECMP模式的路由协议和软件（MetalLB和OpenELB都是自己通过gobgp实现的BGP协议），能更好的和我们前面的calico BGP模式组合在一起，借助calico自带的BGP配置把LoadBalancer IP发布到集群外。\n关于purelb的详细工作原理和部署使用方式可以参考我之前写的这篇文章，这里不再赘述。\nAllocator：用来监听API中的LoadBalancer类型服务，并且负责分配IP。 LBnodeagent： 作为daemonset部署到每个可以暴露请求并吸引流量的节点上，并且负责监听服务的状态变化同时负责把VIP添加到本地网卡或者是虚拟网卡 KubeProxy：k8s的内置组件，并非是PureLB的一部分，但是PureLB依赖其进行正常工作，当对VIP的请求达到某个具体的节点之后，需要由kube-proxy来负责将其转发到对应的pod 因为我们此前已经配置了calico的BGP模式，并且会由它来负责BGP宣告的相关操作，因此在这里我们直接使用purelb的BGP模式，并且不需要自己再额外部署bird或frr来进行BGP路由发布，同时也不需要LBnodeagent组件来帮助暴露并吸引流量，只需要Allocator帮助我们完成LoadBalancerIP的分配操作即可。\n# 下载官方提供的yaml文件到本地进行部署 $ wget https://gitlab.com/api/v4/projects/purelb%2Fpurelb/packages/generic/manifest/0.0.1/purelb-complete.yaml # 请注意，由于 Kubernetes 的最终一致性架构，此manifest清单的第一个应用程序可能会失败。发生这种情况是因为清单既定义了CRD，又使用该CRD创建了资源。如果发生这种情况，请再次应用manifest清单，应该就会部署成功。 $ kubectl apply -f purelb-complete.yaml $ kubectl apply -f purelb-complete.yaml # lbnodeagent的这个ds我们这里用不到，因此可以直接删除。 $ kubectl delete ds -n purelb lbnodeagent # 接下来我们部署一个ipam的sg，命名为bgp-ippool，ip段就使用我们预留的 10.33.192.0/18 $ cat purelb-ipam.yaml apiVersion: purelb.io/v1 kind: ServiceGroup metadata: name: bgp-ippool namespace: purelb spec: local: v4pool: subnet: \u0026#39;10.33.192.0/18\u0026#39; pool: \u0026#39;10.33.192.0-10.33.255.254\u0026#39; aggregation: /32 $ kubectl apply -f purelb-ipam.yaml $ kubectl get sg -n purelb NAME AGE bgp-ippool 64s 到这里我们的PureLB就部署完了，相比完整的ECMP模式要少部署了路由协议软件和额外删除了lbnodeagent，接下来可以开始测试了。\n部署测试用例 集群部署完成之后我们在k8s集群中部署一个nginx测试一下是否能够正常工作。首先我们创建一个名为nginx-quic的命名空间（namespace），然后在这个命名空间内创建一个名为nginx-quic-deployment的deployment用来部署pod，最后再创建一个service用来暴露服务，这里我们同时使用nodeport和LoadBalancer两种方式来暴露服务，并且其中一个LoadBalancer的服务还要指定LoadBalancerIP方便我们测试。\n# cat ngx-system.yaml apiVersion: v1 kind: Namespace metadata: name: nginx-quic --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-quic-deployment namespace: nginx-quic spec: selector: matchLabels: app: nginx-quic replicas: 4 template: metadata: labels: app: nginx-quic spec: containers: - name: nginx-quic image: tinychen777/nginx-quic:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-headless-service namespace: nginx-quic spec: selector: app: nginx-quic clusterIP: None --- apiVersion: v1 kind: Service metadata: name: nginx-quic-service namespace: nginx-quic spec: externalTrafficPolicy: Cluster selector: app: nginx-quic ports: - protocol: TCP port: 8080 # match for service access port targetPort: 80 # match for pod access port nodePort: 30088 # match for external access port type: NodePort --- apiVersion: v1 kind: Service metadata: name: nginx-clusterip-service namespace: nginx-quic spec: selector: app: nginx-quic ports: - protocol: TCP port: 8080 # match for service access port targetPort: 80 # match for pod access port type: ClusterIP --- apiVersion: v1 kind: Service metadata: annotations: purelb.io/service-group: bgp-ippool name: nginx-lb-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: true externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-quic ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer loadBalancerIP: 10.33.192.80 --- apiVersion: v1 kind: Service metadata: annotations: purelb.io/service-group: bgp-ippool name: nginx-lb2-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: true externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-quic ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer 部署完成之后我们检查各项服务的状态\n$ kubectl get svc -n nginx-quic -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR nginx-clusterip-service ClusterIP 10.33.141.36 \u0026lt;none\u0026gt; 8080/TCP 2d22h app=nginx-quic nginx-headless-service ClusterIP None \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 2d22h app=nginx-quic nginx-lb-service LoadBalancer 10.33.151.137 10.33.192.80 80:30167/TCP 2d22h app=nginx-quic nginx-lb2-service LoadBalancer 10.33.154.206 10.33.192.0 80:31868/TCP 2d22h app=nginx-quic nginx-quic-service NodePort 10.33.150.169 \u0026lt;none\u0026gt; 8080:30088/TCP 2d22h app=nginx-quic $ kubectl get pods -n nginx-quic -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-quic-deployment-5d7d9559dd-2f4kx 1/1 Running 0 2d22h 10.33.26.2 k8s-calico-worker-10-31-90-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-5d7d9559dd-8gm7s 1/1 Running 0 2d22h 10.33.93.3 k8s-calico-worker-10-31-90-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-5d7d9559dd-jwhth 1/1 Running 0 2d22h 10.33.93.2 k8s-calico-worker-10-31-90-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-5d7d9559dd-qxhqh 1/1 Running 0 2d22h 10.33.12.2 k8s-calico-worker-10-31-90-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 随后我们分别在集群内外的机器进行测试，分别访问podIP 、clusterIP和loadbalancerIP。\n# 查看是否能够正确返回集群外的客户端的IP地址10.31.100.100 # 在集群外访问pod IP root@tiny-unraid:~# curl 10.33.26.2 10.31.100.100:43240 # 在集群外访问clusterIP root@tiny-unraid:~# curl 10.33.151.137 10.31.90.5:52758 # 在集群外访问loadbalancerIP root@tiny-unraid:~# curl 10.33.192.0 10.31.90.5:7319 # 在集群外访问loadbalancerIP root@tiny-unraid:~# curl 10.33.192.80 10.31.90.5:38170 # 查看是否能够正确返回集群内的node的IP地址10.31.90.1 # 在集群内的node进行测试 [root@k8s-calico-master-10-31-90-1 ~]# curl 10.33.26.2 10.31.90.1:40222 [root@k8s-calico-master-10-31-90-1 ~]# curl 10.33.151.137 10.31.90.1:50773 [root@k8s-calico-master-10-31-90-1 ~]# curl 10.33.192.0 10.31.90.1:19219 [root@k8s-calico-master-10-31-90-1 ~]# curl 10.33.192.80 10.31.90.1:22346 # 查看是否能够正确返回集群内的pod的IP地址10.33.93.3 # 在集群内的pod进行测试 [root@nginx-quic-deployment-5d7d9559dd-8gm7s /]# curl 10.33.26.2 10.33.93.3:39560 [root@nginx-quic-deployment-5d7d9559dd-8gm7s /]# curl 10.33.151.137 10.33.93.3:58160 [root@nginx-quic-deployment-5d7d9559dd-8gm7s /]# curl 10.33.192.0 10.31.90.6:34183 [root@nginx-quic-deployment-5d7d9559dd-8gm7s /]# curl 10.33.192.80 10.31.90.6:64266 最后检测一下路由器端的情况，可以看到对应的podIP、clusterIP和loadbalancerIP段路由\nB\u0026gt;* 10.33.5.0/24 [20/0] via 10.31.90.1, eth0, weight 1, 2d19h22m B\u0026gt;* 10.33.12.0/24 [20/0] via 10.31.90.5, eth0, weight 1, 2d19h22m B\u0026gt;* 10.33.23.0/24 [20/0] via 10.31.90.2, eth0, weight 1, 2d19h22m B\u0026gt;* 10.33.26.0/24 [20/0] via 10.31.90.4, eth0, weight 1, 2d19h22m B\u0026gt;* 10.33.57.0/24 [20/0] via 10.31.90.3, eth0, weight 1, 2d19h22m B\u0026gt;* 10.33.93.0/24 [20/0] via 10.31.90.6, eth0, weight 1, 2d19h22m B\u0026gt;* 10.33.128.0/18 [20/0] via 10.31.90.1, eth0, weight 1, 00:00:20 * via 10.31.90.2, eth0, weight 1, 00:00:20 * via 10.31.90.3, eth0, weight 1, 00:00:20 * via 10.31.90.4, eth0, weight 1, 00:00:20 * via 10.31.90.5, eth0, weight 1, 00:00:20 * via 10.31.90.6, eth0, weight 1, 00:00:20 B\u0026gt;* 10.33.192.0/18 [20/0] via 10.31.90.1, eth0, weight 1, 2d19h21m * via 10.31.90.2, eth0, weight 1, 2d19h21m * via 10.31.90.3, eth0, weight 1, 2d19h21m * via 10.31.90.4, eth0, weight 1, 2d19h21m * via 10.31.90.5, eth0, weight 1, 2d19h21m * via 10.31.90.6, eth0, weight 1, 2d19h21m 到这里整个K8S集群就部署完成了。\n","permalink":"http://localhost:9011/20230109-k8s-13-deploy-ha-k8s-with-calico-bgp/","summary":"\u003cp\u003e本文主要在centos7系统上基于\u003ccode\u003econtainerd\u003c/code\u003e和\u003ccode\u003ev3.24.5\u003c/code\u003e版本的calico组件部署\u003ccode\u003ev1.26.0\u003c/code\u003e版本的堆叠ETCD高可用k8s原生集群，在\u003ccode\u003eLoadBalancer\u003c/code\u003e上选择了\u003ccode\u003ePureLB\u003c/code\u003e和\u003ccode\u003ecalico\u003c/code\u003e结合\u003ccode\u003ebird\u003c/code\u003e实现\u003cstrong\u003eBGP路由可达\u003c/strong\u003e的K8S集群部署。\u003c/p\u003e\n\u003cp\u003e此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列13-calico部署BGP模式的高可用k8s集群"},{"content":"本文主要介绍如何使用kubeadm对K8S集群进行升级。\n此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n概述 K8S集群的升级主要可以分为三步：\n升级一个主控制面节点 升级其余的控制面节点 升级剩下的worker节点 本次升级的集群为三主三从组合，使用cilium和containerd，K8S集群版本为1.25.4，计划升级到1.26.0版本。\n$ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-cilium-master-10-31-80-1.tinychen.io Ready control-plane 15d v1.25.4 10.31.80.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-2.tinychen.io Ready control-plane 15d v1.25.4 10.31.80.2 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-3.tinychen.io Ready control-plane 15d v1.25.4 10.31.80.3 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-4.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.4 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-5.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.5 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-6.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.6 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 准备工作 开始升级之前我们先做一些准备工作：\n仔细阅读版本更新说明：主要关注当前版本到目标升级版本之间的变更，尤其是大版本升级更加要注意； K8S集群必须使用静态控制面节点 K8S集群的etcd必须是静态pod部署或者是外置etcd 备份重要的数据和业务：虽然kubeadm升级只涉及k8s的内部组件，但是对于一些重要业务和app级别的有状态服务，还是有备无患比较放心 禁用集群的SWAP内存 一些注意事项：\n对于任何kubelet的小版本升级（minor version upgrade），一定要先驱逐该node上面的所有负载（drain the node），避免残留一些诸如coredns之类的重要workload影响整个集群的稳定性； 由于container的spec hash value在集群升级后会发生变更，因此所有的container在集群升级完成后都会重启； 可以使用systemctl status kubelet或者journalctl -xeu kubelet来查看kubelet的日志从而确定其升级是否成功； 不建议在kubeadm upgrade升级集群的同时使用--config来重新配置集群，如果需要更新集群的配置，可以参考这篇官方教程； 升级kubeadm 在升级集群之前我们需要先对集群上面的所有节点的kubeadm都进行升级，这里我们使用yum升级到对应的1.26.0版本即可。\n# 查看所有可用的kubeadm的版本 $ yum list --showduplicates kubeadm --disableexcludes=kubernetes # 然后升级kubeadm的版本到1.26.0 $ yum install -y kubeadm-1.26.0-0 --disableexcludes=kubernetes 完成之后我们检查kubeadm的版本信息，顺利输出下面的信息则说明升级成功\n$ kubeadm version kubeadm version: \u0026amp;version.Info{Major:\u0026#34;1\u0026#34;, Minor:\u0026#34;26\u0026#34;, GitVersion:\u0026#34;v1.26.0\u0026#34;, GitCommit:\u0026#34;b46a3f887ca979b1a5d14fd39cb1af43e7e5d12d\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, BuildDate:\u0026#34;2022-12-08T19:57:06Z\u0026#34;, GoVersion:\u0026#34;go1.19.4\u0026#34;, Compiler:\u0026#34;gc\u0026#34;, Platform:\u0026#34;linux/amd64\u0026#34;} 升级控制面节点 升级K8S组件 首先我们从三个控制面节点中挑选一个进行升级，这里先对10.31.80.1进行升级。\n接下来我们查看一下升级计划，这里会列出升级过程中涉及的组件和API，以及升级前后的变化和是否需要我们进行手动操作等详细信息：\n$ kubeadm upgrade plan [upgrade/config] Making sure the configuration is correct: [upgrade/config] Reading configuration from the cluster... [upgrade/config] FYI: You can look at this config file with \u0026#39;kubectl -n kube-system get cm kubeadm-config -o yaml\u0026#39; [preflight] Running pre-flight checks. [upgrade] Running cluster health checks [upgrade] Fetching available versions to upgrade to [upgrade/versions] Cluster version: v1.25.4 [upgrade/versions] kubeadm version: v1.26.0 [upgrade/versions] Target version: v1.26.0 [upgrade/versions] Latest version in the v1.25 series: v1.25.5 W1223 17:23:27.554231 15530 configset.go:177] error unmarshaling configuration schema.GroupVersionKind{Group:\u0026#34;kubeproxy.config.k8s.io\u0026#34;, Version:\u0026#34;v1alpha1\u0026#34;, Kind:\u0026#34;KubeProxyConfiguration\u0026#34;}: strict decoding error: unknown field \u0026#34;udpIdleTimeout\u0026#34; Components that must be upgraded manually after you have upgraded the control plane with \u0026#39;kubeadm upgrade apply\u0026#39;: COMPONENT CURRENT TARGET kubelet 6 x v1.25.4 v1.25.5 Upgrade to the latest version in the v1.25 series: COMPONENT CURRENT TARGET kube-apiserver v1.25.4 v1.25.5 kube-controller-manager v1.25.4 v1.25.5 kube-scheduler v1.25.4 v1.25.5 kube-proxy v1.25.4 v1.25.5 CoreDNS v1.9.3 v1.9.3 etcd 3.5.5-0 3.5.6-0 You can now apply the upgrade by executing the following command: kubeadm upgrade apply v1.25.5 _____________________________________________________________________ Components that must be upgraded manually after you have upgraded the control plane with \u0026#39;kubeadm upgrade apply\u0026#39;: COMPONENT CURRENT TARGET kubelet 6 x v1.25.4 v1.26.0 Upgrade to the latest stable version: COMPONENT CURRENT TARGET kube-apiserver v1.25.4 v1.26.0 kube-controller-manager v1.25.4 v1.26.0 kube-scheduler v1.25.4 v1.26.0 kube-proxy v1.25.4 v1.26.0 CoreDNS v1.9.3 v1.9.3 etcd 3.5.5-0 3.5.6-0 You can now apply the upgrade by executing the following command: kubeadm upgrade apply v1.26.0 _____________________________________________________________________ The table below shows the current state of component configs as understood by this version of kubeadm. Configs that have a \u0026#34;yes\u0026#34; mark in the \u0026#34;MANUAL UPGRADE REQUIRED\u0026#34; column require manual config upgrade or resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually upgrade to is denoted in the \u0026#34;PREFERRED VERSION\u0026#34; column. API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED kubeproxy.config.k8s.io v1alpha1 v1alpha1 no kubelet.config.k8s.io v1beta1 v1beta1 no _____________________________________________________________________ 由于本次升级的版本跨度并不大，因此并无太多需要变动的地方，我们可以直接升级。\nkubeadm upgrade命令在升级的过程中还会同时更新集群的证书，如果不想更新的话可以加上参数--certificate-renewal=false。\nFor more information see the certificate management guide.\n$ kubeadm upgrade apply v1.26.0 [upgrade/config] Making sure the configuration is correct: [upgrade/config] Reading configuration from the cluster... [upgrade/config] FYI: You can look at this config file with \u0026#39;kubectl -n kube-system get cm kubeadm-config -o yaml\u0026#39; W1223 17:30:04.493109 17731 configset.go:177] error unmarshaling configuration schema.GroupVersionKind{Group:\u0026#34;kubeproxy.config.k8s.io\u0026#34;, Version:\u0026#34;v1alpha1\u0026#34;, Kind:\u0026#34;KubeProxyConfiguration\u0026#34;}: strict decoding error: unknown field \u0026#34;udpIdleTimeout\u0026#34; [preflight] Running pre-flight checks. [upgrade] Running cluster health checks [upgrade/version] You have chosen to change the cluster version to \u0026#34;v1.26.0\u0026#34; [upgrade/versions] Cluster version: v1.25.4 [upgrade/versions] kubeadm version: v1.26.0 [upgrade] Are you sure you want to proceed? [y/N]: y [upgrade/prepull] Pulling images required for setting up a Kubernetes cluster [upgrade/prepull] This might take a minute or two, depending on the speed of your internet connection [upgrade/prepull] You can also perform this action in beforehand using \u0026#39;kubeadm config images pull\u0026#39; [upgrade/apply] Upgrading your Static Pod-hosted control plane to version \u0026#34;v1.26.0\u0026#34; (timeout: 5m0s)... [upgrade/etcd] Upgrading to TLS for etcd [upgrade/staticpods] Preparing for \u0026#34;etcd\u0026#34; upgrade [upgrade/staticpods] Renewing etcd-server certificate [upgrade/staticpods] Renewing etcd-peer certificate [upgrade/staticpods] Renewing etcd-healthcheck-client certificate [upgrade/staticpods] Moved new manifest to \u0026#34;/etc/kubernetes/manifests/etcd.yaml\u0026#34; and backed up old manifest to \u0026#34;/etc/kubernetes/tmp/kubeadm-backup-manifests-2022-12-23-17-31-20/etcd.yaml\u0026#34; [upgrade/staticpods] Waiting for the kubelet to restart the component [upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s) [apiclient] Found 3 Pods for label selector component=etcd [upgrade/staticpods] Component \u0026#34;etcd\u0026#34; upgraded successfully! [upgrade/etcd] Waiting for etcd to become available [upgrade/staticpods] Writing new Static Pod manifests to \u0026#34;/etc/kubernetes/tmp/kubeadm-upgraded-manifests3224088652\u0026#34; [upgrade/staticpods] Preparing for \u0026#34;kube-apiserver\u0026#34; upgrade [upgrade/staticpods] Renewing apiserver certificate [upgrade/staticpods] Renewing apiserver-kubelet-client certificate [upgrade/staticpods] Renewing front-proxy-client certificate [upgrade/staticpods] Renewing apiserver-etcd-client certificate [upgrade/staticpods] Moved new manifest to \u0026#34;/etc/kubernetes/manifests/kube-apiserver.yaml\u0026#34; and backed up old manifest to \u0026#34;/etc/kubernetes/tmp/kubeadm-backup-manifests-2022-12-23-17-31-20/kube-apiserver.yaml\u0026#34; [upgrade/staticpods] Waiting for the kubelet to restart the component [upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s) [apiclient] Found 3 Pods for label selector component=kube-apiserver [upgrade/staticpods] Component \u0026#34;kube-apiserver\u0026#34; upgraded successfully! [upgrade/staticpods] Preparing for \u0026#34;kube-controller-manager\u0026#34; upgrade [upgrade/staticpods] Renewing controller-manager.conf certificate [upgrade/staticpods] Moved new manifest to \u0026#34;/etc/kubernetes/manifests/kube-controller-manager.yaml\u0026#34; and backed up old manifest to \u0026#34;/etc/kubernetes/tmp/kubeadm-backup-manifests-2022-12-23-17-31-20/kube-controller-manager.yaml\u0026#34; [upgrade/staticpods] Waiting for the kubelet to restart the component [upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s) [apiclient] Found 3 Pods for label selector component=kube-controller-manager [upgrade/staticpods] Component \u0026#34;kube-controller-manager\u0026#34; upgraded successfully! [upgrade/staticpods] Preparing for \u0026#34;kube-scheduler\u0026#34; upgrade [upgrade/staticpods] Renewing scheduler.conf certificate [upgrade/staticpods] Moved new manifest to \u0026#34;/etc/kubernetes/manifests/kube-scheduler.yaml\u0026#34; and backed up old manifest to \u0026#34;/etc/kubernetes/tmp/kubeadm-backup-manifests-2022-12-23-17-31-20/kube-scheduler.yaml\u0026#34; [upgrade/staticpods] Waiting for the kubelet to restart the component [upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s) [apiclient] Found 3 Pods for label selector component=kube-scheduler [upgrade/staticpods] Component \u0026#34;kube-scheduler\u0026#34; upgraded successfully! [upload-config] Storing the configuration used in ConfigMap \u0026#34;kubeadm-config\u0026#34; in the \u0026#34;kube-system\u0026#34; Namespace [kubelet] Creating a ConfigMap \u0026#34;kubelet-config\u0026#34; in namespace kube-system with the configuration for the kubelets in the cluster [kubelet-start] Writing kubelet configuration to file \u0026#34;/var/lib/kubelet/config.yaml\u0026#34; [bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes [bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials [bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token [bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster [addons] Applied essential addon: CoreDNS W1223 17:33:49.448558 17731 endpoint.go:57] [endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address [addons] Applied essential addon: kube-proxy [upgrade/successful] SUCCESS! Your cluster was upgraded to \u0026#34;v1.26.0\u0026#34;. Enjoy! [upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven\u0026#39;t already done so. 看到最后输出类似的信息就说明该节点已经升级成功。\n[upgrade/successful] SUCCESS! Your cluster was upgraded to \u0026#34;v1.26.0\u0026#34;. Enjoy! [upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven\u0026#39;t already done so. 但是这时候不要急，我们此时只是升级了一个节点，还需要继续对剩下的两个节点进行同样的操作\n注意这时候在第二个节点执行kubeadm upgrade plan命令看到的信息和之前已经不一样了，因为在第一个控制面节点升级的时候已经更新了集群内的configmap。\n$ kubeadm version kubeadm version: \u0026amp;version.Info{Major:\u0026#34;1\u0026#34;, Minor:\u0026#34;26\u0026#34;, GitVersion:\u0026#34;v1.26.0\u0026#34;, GitCommit:\u0026#34;b46a3f887ca979b1a5d14fd39cb1af43e7e5d12d\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, BuildDate:\u0026#34;2022-12-08T19:57:06Z\u0026#34;, GoVersion:\u0026#34;go1.19.4\u0026#34;, Compiler:\u0026#34;gc\u0026#34;, Platform:\u0026#34;linux/amd64\u0026#34;} $ kubeadm upgrade plan [upgrade/config] Making sure the configuration is correct: [upgrade/config] Reading configuration from the cluster... [upgrade/config] FYI: You can look at this config file with \u0026#39;kubectl -n kube-system get cm kubeadm-config -o yaml\u0026#39; [preflight] Running pre-flight checks. [upgrade] Running cluster health checks [upgrade] Fetching available versions to upgrade to [upgrade/versions] Cluster version: v1.26.0 [upgrade/versions] kubeadm version: v1.26.0 [upgrade/versions] Target version: v1.26.0 [upgrade/versions] Latest version in the v1.26 series: v1.26.0 所以这里我们对其余的控制面节点更新命令也对应变成了\n$ kubeadm upgrade node 当看到类似的输出信息时则说明升级成功\n[upgrade] The configuration for this node was successfully updated! [upgrade] Now you should go ahead and upgrade the kubelet package using your package manager. 升级kubelet和kubectl 上面的更新操作完成之后我们只是把K8S集群中的相关pod都升级了一遍，但是kubelet并没有升级，因此这里看到的版本信息还是1.25.4\n$ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-cilium-master-10-31-80-1.tinychen.io Ready control-plane 15d v1.25.4 10.31.80.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-2.tinychen.io Ready control-plane 15d v1.25.4 10.31.80.2 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-3.tinychen.io Ready control-plane 15d v1.25.4 10.31.80.3 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-4.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.4 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-5.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.5 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-6.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.6 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 升级kubelet之前我们要对节点进行驱逐操作，将上面除了daemonset之外的全部工作负载都驱逐掉\n$ kubectl drain k8s-cilium-master-10-31-80-1.tinychen.io --ignore-daemonsets node/k8s-cilium-master-10-31-80-1.tinychen.io cordoned Warning: ignoring DaemonSet-managed Pods: kube-system/cilium-gj4vm, kube-system/kube-proxy-r7pj8, kube-system/kube-router-szdml node/k8s-cilium-master-10-31-80-1.tinychen.io drained 接着就可以使用yum更新kubelet和kubectl\n$ yum install -y kubelet-1.26.0-0 kubectl-1.26.0-0 --disableexcludes=kubernetes $ systemctl daemon-reload $ systemctl restart kubelet # 查看日志检查相关服务是否正常 $ systemctl status kubelet -l $ journalctl -xeu kubelet 这时候再查看相关的状态就能看到节点已经升级成功，版本信息更新为1.26.0\n$ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-cilium-master-10-31-80-1.tinychen.io Ready,SchedulingDisabled control-plane 15d v1.26.0 10.31.80.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-2.tinychen.io Ready control-plane 15d v1.25.4 10.31.80.2 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-3.tinychen.io Ready control-plane 15d v1.25.4 10.31.80.3 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-4.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.4 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-5.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.5 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-6.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.6 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 确定该节点正常后，就能恢复它的调度\n$ kubectl uncordon k8s-cilium-master-10-31-80-1.tinychen.io 接着对剩下的两个节点进行同样的操作，都更新完成之后我们可以看到整个集群的控制面就完成升级了。\n$ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-cilium-master-10-31-80-1.tinychen.io Ready control-plane 15d v1.26.0 10.31.80.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-2.tinychen.io Ready control-plane 15d v1.26.0 10.31.80.2 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-3.tinychen.io Ready control-plane 15d v1.26.0 10.31.80.3 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-4.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.4 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-5.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.5 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-6.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.25.4 10.31.80.6 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 升级worker节点 worker节点的upgrade操作则要简单很多，因为上面没有控制面相关的组件，所以只需要更新kubelet的配置即可。\n$ kubeadm upgrade node [upgrade] Reading configuration from the cluster... [upgrade] FYI: You can look at this config file with \u0026#39;kubectl -n kube-system get cm kubeadm-config -o yaml\u0026#39; [preflight] Running pre-flight checks [preflight] Skipping prepull. Not a control plane node. [upgrade] Skipping phase. Not a control plane node. [kubelet-start] Writing kubelet configuration to file \u0026#34;/var/lib/kubelet/config.yaml\u0026#34; [upgrade] The configuration for this node was successfully updated! [upgrade] Now you should go ahead and upgrade the kubelet package using your package manager. 接下来就是重复上面的驱逐节点--\u0026gt; 升级kubelet --\u0026gt; 检查服务 --\u0026gt; 恢复节点的步骤即可\n一般worker节点上不安装kubectl，因此也就不需要升级kubectl了\n# 在控制面节点上使用kubectl执行 $ kubectl drain k8s-cilium-worker-10-31-80-4.tinychen.io --ignore-daemonsets # 在worker节点上执行 $ yum install -y kubelet-1.26.0-0 --disableexcludes=kubernetes $ systemctl daemon-reload $ systemctl restart kubelet # 在控制面节点上使用kubectl执行 $ kubectl uncordon k8s-cilium-worker-10-31-80-4.tinychen.io 接着对所有的worker节点重复上面的操作即可完成整个K8S集群的升级\n# 在worker节点上执行 $ kubeadm upgrade node # 在控制面节点上使用kubectl执行 $ kubectl drain \u0026lt;换成node的名字\u0026gt; --ignore-daemonsets # 在worker节点上执行 $ yum install -y kubelet-1.26.0-0 --disableexcludes=kubernetes $ systemctl daemon-reload $ systemctl restart kubelet # 在控制面节点上使用kubectl执行 $ kubectl uncordon \u0026lt;换成node的名字\u0026gt; 最后检查集群状态可以看到所有的node都已经升级到1.26.0\n$ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-cilium-master-10-31-80-1.tinychen.io Ready control-plane 15d v1.26.0 10.31.80.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-2.tinychen.io Ready control-plane 15d v1.26.0 10.31.80.2 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-3.tinychen.io Ready control-plane 15d v1.26.0 10.31.80.3 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-4.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.26.0 10.31.80.4 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-5.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.26.0 10.31.80.5 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-6.tinychen.io Ready \u0026lt;none\u0026gt; 15d v1.26.0 10.31.80.6 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 升级失败 这里的升级失败情况我们主要考虑的是控制面节点升级失败，因为worker节点的升级过程相对简单，而且升级前如果按步骤驱逐掉worker节点上面的工作负载，那么最坏的情况也不过是将该节点从集群中删除后重新安装再加入集群，对整体影响并不算大。但是如果控制面节点在升级过程中出现故障，是很有可能导致整个集群崩溃的，因此这里官方也给了一些升级失败的应对手段。\n升级失败自动回滚：这是不幸中的万幸，使用kubeadm升级集群节点如果失败了，理论上是会自动回滚到升级前的旧版本，如果回滚成功，那么此时的集群应该是可以正常工作的；\n升级过程被意外中断：升级过程中因为网络状况等各种问题被中断了，之后只需要再次执行之前的升级命令即可；因为官方声称kubeadm的操作是幂等的，也就是说只需要在升级完成后检查集群状态正常即可；\n升级失败手动回滚：kubeadm在升级之前会把相关的manifests文件和etcd的数据都备份到/etc/kubernetes/tmp目录下面，最坏的情况就是需要我们自己去手动恢复数据并重启相关服务\n","permalink":"http://localhost:9011/20221224-k8s-12-kubeadm-upgrade-cluster/","summary":"\u003cp\u003e本文主要介绍如何使用\u003ccode\u003ekubeadm\u003c/code\u003e对K8S集群进行升级。\u003c/p\u003e\n\u003cp\u003e此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列12-kubeadm升级k8s集群"},{"content":"本文主要介绍在使用了Cilium的K8S集群中如何开启KubeProxyReplacement功能来替代K8S集群原生的kube-proxy组件，同时还会介绍Cilium的几个特色功能如Maglev一致性哈希、DSR模式、Socket旁路和XDP加速等。\n关于本文实操使用的K8S集群的部署过程可以参考上一篇文章k8s系列10-使用kube-router和cilium部署BGP模式的k8s集群。此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n配置KubeProxyReplacement 检查集群状态 关于使用cilium替代kube-proxy的官方文档可以参考这里，需要注意的是我们这里是在已经部署好cilium和kube-proxy的K8S集群上面操作，因此会和官方的全新初始化安装稍有不同，但是大致原理类似。\n首先检查一下我们现在的cilium状态，可以看到KubeProxyReplacement参数默认是设置为Disabled\n$ kubectl -n kube-system exec ds/cilium -- cilium status | grep KubeProxyReplacement Defaulted container \u0026#34;cilium-agent\u0026#34; out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init) KubeProxyReplacement: Disabled 再检查一下集群中的kube-proxy组件状态，可以看到各个组件都工作正常。\n$ kubectl get pods -n kube-system | grep kube-proxy kube-proxy-86g7b 1/1 Running 1 (18h ago) 19h kube-proxy-gqbd4 1/1 Running 1 (18h ago) 19h kube-proxy-gtcqc 1/1 Running 1 (18h ago) 19h kube-proxy-kdjr9 1/1 Running 1 (18h ago) 19h kube-proxy-pbj8s 1/1 Running 1 (18h ago) 19h kube-proxy-tvltv 1/1 Running 1 (18h ago) 19h $ kubectl get ds -n kube-system | grep kube-proxy kube-proxy 6 6 6 6 6 kubernetes.io/os=linux 45h $ kubectl get cm -n kube-system | grep kube-proxy kube-proxy 2 45h 删除kube-proxy 在删除kube-proxy之前我们先备份一下相关的配置\n# 在master节点上备份kube-proxy相关的配置 $ kubectl get ds -n kube-system kube-proxy -o yaml \u0026gt; kube-proxy-ds.yaml $ kubectl get cm -n kube-system kube-proxy -o yaml \u0026gt; kube-proxy-cm.yaml # 在每台机器上面使用root权限备份一下iptables规则 $ iptables-save \u0026gt; kube-proxy-iptables-save.bak 接下来我们删除kube-proxy相关的daemonset、configmap、iptables规则和ipvs规则。\n# 删除掉kube-proxy这个daemonset $ kubectl -n kube-system delete ds kube-proxy # 删除掉kube-proxy的configmap，防止以后使用kubeadm升级K8S的时候重新安装了kube-proxy（1.19版本之后的K8S） $ kubectl -n kube-system delete cm kube-proxy # 在每台机器上面使用root权限清除掉iptables规则和ipvs规则 $ iptables-save | grep -v KUBE | iptables-restore $ ipvsadm -C 配置cilium 删除kube-proxy之后此时的K8S集群应该会处于不正常工作的状态，不用紧张，现在我们开启cilium的kube-proxy-replacement功能来替代kube-proxy。\n因为我们集群已经部署了cililum，因此我们可以直接通过修改configmap的方式开启kube-proxy-replacement；修改cilium-config，将kube-proxy-replacement: disabled修改为kube-proxy-replacement: strict\n$ kubectl edit cm -n kube-system cilium-config configmap/cilium-config edited $ kubectl get cm -n kube-system cilium-config -o yaml | grep kube-proxy-replacement kube-proxy-replacement: strict $ kubectl rollout restart ds/cilium deployment/cilium-operator -n kube-system 另外默认情况下cilium不会允许集群外的机器访问clusterip，需要开启这个功能的话可以在配置中添加bpf-lb-external-clusterip: \u0026quot;true\u0026quot;\n$ kubectl get cm -n kube-system cilium-config -o yaml | grep bpf-lb-external-clusterip bpf-lb-external-clusterip: \u0026#34;true\u0026#34; 如果是使用helm来初始化安装或者是更新的话可以考虑添加下面的这两个参数，最后的效果应该是一致的：\n需要特别注意如果一开始就是用helm部署的话，这里要加上参数手动指定k8s的apiserver地址和端口。\n# 使用helm来更新的话可以添加下面的这几个参数 --set kubeProxyReplacement=strict \\ --set bpf.lbExternalClusterIP=true \\ --set k8sServiceHost=k8s-cilium-apiserver.tinychen.io \\ --set k8sServicePort=8443 \\ # 检查一下更新之后的configmap $ kubectl get cm -n kube-system cilium-config -o yaml | egrep \u0026#34;bpf-lb-external-clusterip|kube-proxy-replacement\u0026#34; bpf-lb-external-clusterip: \u0026#34;true\u0026#34; kube-proxy-replacement: strict 个人建议使用helm和configmap来管理cilium的配置这两种方式挑一种即可，不要两个方式混用，避免一些配置被覆盖\nCilium的参数比较多，在helm和configmap中的名字不一样，可以参考github上面的配置和官方给出的helm说明\n检测cilium cilium重启完成之后检测相关的服务状态：\n$ kubectl -n kube-system exec ds/cilium -- cilium status | grep KubeProxyReplacement Defaulted container \u0026#34;cilium-agent\u0026#34; out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init) KubeProxyReplacement: Strict [eth0 10.31.80.4 (Direct Routing)] 注意这时候cilium里面能够看到的Service Type应该还包含了LoadBalancer和NodePort等类型\n$ kubectl -n kube-system exec ds/cilium -- cilium service list Defaulted container \u0026#34;cilium-agent\u0026#34; out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init) ID Frontend Service Type Backend 1 10.32.169.6:80 ClusterIP 1 =\u0026gt; 10.32.3.93:80 (active) 2 =\u0026gt; 10.32.3.85:80 (active) 3 =\u0026gt; 10.32.5.204:80 (active) 4 =\u0026gt; 10.32.4.25:80 (active) 2 10.32.192.192:80 LoadBalancer 1 =\u0026gt; 10.32.3.93:80 (active) 2 =\u0026gt; 10.32.3.85:80 (active) 3 =\u0026gt; 10.32.5.204:80 (active) 4 =\u0026gt; 10.32.4.25:80 (active) 3 10.32.176.164:8080 ClusterIP 1 =\u0026gt; 10.32.3.93:80 (active) 2 =\u0026gt; 10.32.3.85:80 (active) 3 =\u0026gt; 10.32.5.204:80 (active) 4 =\u0026gt; 10.32.4.25:80 (active) 4 10.31.80.3:30088 NodePort 1 =\u0026gt; 10.32.3.93:80 (active) 2 =\u0026gt; 10.32.3.85:80 (active) 3 =\u0026gt; 10.32.5.204:80 (active) 4 =\u0026gt; 10.32.4.25:80 (active) 5 0.0.0.0:30088 NodePort 1 =\u0026gt; 10.32.3.93:80 (active) 2 =\u0026gt; 10.32.3.85:80 (active) 3 =\u0026gt; 10.32.5.204:80 (active) 4 =\u0026gt; 10.32.4.25:80 (active) 6 10.32.131.127:443 ClusterIP 1 =\u0026gt; 10.31.80.6:4244 (active) 2 =\u0026gt; 10.31.80.3:4244 (active) 3 =\u0026gt; 10.31.80.1:4244 (active) 4 =\u0026gt; 10.31.80.5:4244 (active) 5 =\u0026gt; 10.31.80.4:4244 (active) 6 =\u0026gt; 10.31.80.2:4244 (active) 7 10.32.171.0:80 ClusterIP 1 =\u0026gt; 10.32.4.114:4245 (active) 8 10.32.128.10:53 ClusterIP 1 =\u0026gt; 10.32.5.239:53 (active) 2 =\u0026gt; 10.32.5.21:53 (active) 9 10.32.128.10:9153 ClusterIP 1 =\u0026gt; 10.32.5.239:9153 (active) 2 =\u0026gt; 10.32.5.21:9153 (active) 10 10.32.184.206:80 ClusterIP 1 =\u0026gt; 10.32.3.138:8081 (active) 11 10.31.80.3:30081 NodePort 1 =\u0026gt; 10.32.3.138:8081 (active) 12 0.0.0.0:30081 NodePort 1 =\u0026gt; 10.32.3.138:8081 (active) 13 10.32.172.208:80 ClusterIP 1 =\u0026gt; 10.32.3.93:80 (active) 2 =\u0026gt; 10.32.3.85:80 (active) 3 =\u0026gt; 10.32.5.204:80 (active) 4 =\u0026gt; 10.32.4.25:80 (active) 14 10.32.192.0:80 LoadBalancer 1 =\u0026gt; 10.32.3.93:80 (active) 2 =\u0026gt; 10.32.3.85:80 (active) 3 =\u0026gt; 10.32.5.204:80 (active) 4 =\u0026gt; 10.32.4.25:80 (active) 15 10.32.188.57:8080 ClusterIP 1 =\u0026gt; 10.32.5.220:8080 (active) 16 10.31.80.3:31243 NodePort 1 =\u0026gt; 10.32.5.220:8080 (active) 17 0.0.0.0:31243 NodePort 1 =\u0026gt; 10.32.5.220:8080 (active) 18 10.32.142.224:8080 ClusterIP 1 =\u0026gt; 10.32.4.248:8080 (active) 19 0.0.0.0:31578 NodePort 1 =\u0026gt; 10.32.4.248:8080 (active) 20 10.31.80.3:31578 NodePort 1 =\u0026gt; 10.32.4.248:8080 (active) 21 10.32.128.1:443 ClusterIP 1 =\u0026gt; 10.31.80.1:6443 (active) 2 =\u0026gt; 10.31.80.2:6443 (active) 3 =\u0026gt; 10.31.80.3:6443 (active) 此时再查看ipvs规则可以看到为空，重启节点后对应的kube-ipvs0网卡也会消失，iptables中的相关规则也不会再生成。\n$ ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn $ ip link show kube-ipvs0 Device \u0026#34;kube-ipvs0\u0026#34; does not exist. $ iptables-save | grep KUBE-SVC [ empty line ] 此前我们已经配置了该集群的podIP、clusterIP和loadbalancerIP均为集群外路由可达，即可ping通，可正常请求。此时已经开启了cilium的kube-proxy-replacement模式之后，只有pod IP是能正常ping通并请求的；loadbalancerIP和clusterIP都是无法ping通，但是能正常请求；主要原因是cilium本身的eBPF代码默认并没有对loadbalancerIP和clusterIP的icmp数据包进行处理，导致ping请求无法被响应。\nkube-proxy + kube-router without kube-proxy + kube-router 集群内：Pod IP ping测试：Y\nTCP测试：Y\nUDP测试：Y\nping测试：Y\nTCP测试：Y\nUDP测试：Y\n集群内：Cluster IP ping测试：Y\nTCP测试：Y\nUDP测试：Y\nping测试：N\nTCP测试：Y\nUDP测试：Y\n集群内：LoadBalancer IP ping测试：Y\nTCP测试：Y\nUDP测试：Y\nping测试：N\nTCP测试：Y\nUDP测试：Y\n集群外：Pod IP ping测试：Y\nTCP测试：Y\nUDP测试：Y\nping测试：Y\nTCP测试：Y\nUDP测试：Y\n集群外：Cluster IP ping测试：Y\nTCP测试：Y\nUDP测试：Y\nping测试：N\nTCP测试：Y\nUDP测试：Y\n集群外：LoadBalancer IP ping测试：Y\nTCP测试：Y\nUDP测试：Y\nping测试：N\nTCP测试：Y\nUDP测试：Y\n一致性哈希 Cilium官方声称已经实现了完整的四七层代理，因此在切换到它的KubeProxyReplacement模式之后我们可以使用一些cilium的特有功能，比如一致性哈希。\n在负载均衡算法中使用传统的哈希算法时，增减一个后端节点都会导致几乎所有的哈希规则进行重新映射，使得原先的客户端会被转发到新的后端节点，这对于缓存服务器来说是非常不友好的。因此在这种场景下一般会使用一致性哈希算法，其特点是当哈希表槽位数（大小）的改变平均只需要对K/n 个关键字重新映射，其中K是关键字的数量，n是槽位数量。也就是说使用了一致性哈希算法之后，可以大幅度减小后端节点变化带来的哈希映射关系变动，从而使得请求转发更加的均匀稳定。\n一致性哈希算法有很多种具体实现，而Cilium主要是通过实现谷歌此前开源的Maglev的一种变体来达到一致性哈希的效果，这提高了发生故障时的弹性并提供了更好的负载平衡属性。因为添加到集群的节点将在整个集群中为给定的 5 元组做出一致的后端选择，而无需与其他节点同步状态。Cilium声称可以在后端出现变动的时候将影响控制到1%以内。\n这里需要解释一下这两个特定于 Maglev 的参数：maglev.tableSize 和maglev.hashSeed。\nmaglev.tableSize用来指定maglev查找单个服务的表的大小，maglev建议该值(M) 应远大于实际的最大后端节点数量 (N)，实际上我们为了实现后端出现变动的时候将影响控制到1%以内的目标，需要将M的值设置成大于N的100倍才比较合理，另外M必须是一个质数，Cilium默认将M设置为16381（对应最大后端节点数量在160左右）。下表是cilium给出的一些可以使用的参考值，我们可以根据自己的实际业务进行调整：\nmaglev.tableSize value 251 509 1021 2039 4093 8191 16381 32749 65521 131071 maglev.hashSeed则是用来设置maglev算法的seed值，官方推荐设置，这样就不需要依赖内置的seed值。该值每台机器需要一致，这样才能保证每台机器的哈希结果一致。maglev.hashSeed应该是一个base64编码的12位随机字符串，可以使用head -c12 /dev/urandom | base64 -w0命令生成。\n配置maglev算法和hash相关参数，主要配置三个值：\n# 首先随机生成一个SEED值用于哈希算法 $ head -c12 /dev/urandom | base64 -w0 djthA7ezcQmtolON $ kubectl get cm -n kube-system cilium-config -o yaml | egrep \u0026#34;bpf-lb-algorithm|bpf-lb-maglev-hash-seed|bpf-lb-maglev-table-size\u0026#34; bpf-lb-algorithm: maglev bpf-lb-maglev-hash-seed: djthA7ezcQmtolON bpf-lb-maglev-table-size: \u0026#34;65521\u0026#34; SEED=$(head -c12 /dev/urandom | base64 -w0) # 使用helm来更新的话可以添加下面的参数 --set loadBalancer.algorithm=maglev \\ --set maglev.tableSize=65521 \\ --set maglev.hashSeed=$SEED \\ 注意开启了maglev一致性哈希之后，因为需要维护哈希表，所以cilium的ds进程会占用更多的内存；同时需要注意的是maglev一致性哈希只会对集群外部的流量生效（即通过nodeport、externalIP等方式进来的流量），因为对于集群内部的东西流量，往往都是直接转发到对应的pod上面，不需要经过中间的选择转发环节，也就没有maglev一致性哈希的工作环节了。当然，Cilium的一致性哈希支持XDP加速。\nDirect Server Return (DSR) 默认情况下，Cilium 的 eBPF NodePort转发是通过SNAT模式实现的，也就是说，当节点外部的流量通过诸如LoadBalancer、NodePort等方式进入集群内，并且需要转发到非本node的pod上面时，该node将通过执行SNAT转换将请求转发到对应的后端pod上面。这不需要对数据包的MTU进行变更，代价是后端pod回复数据包的时候需要再次经由node进行reverse SNAT再把请求发回给客户端。\nCilium提供了一个DSR模式来对此场景进行优化，即当数据包转发给后端pod之后，由pod直接返回给客户端，而不再经由node进行转发回复，这样可以减少一跳的转发，并且减少了一次NAT转换。DSR模式需要cilium工作在Native-Routing模式，如果是使用了隧道模式，那么将无法正常工作。\nDSR模式相较于SNAT模式的另一个优势就是能够保留客户端的源IP，即后端服务能够根据客户端IP进行更灵活的控制策略。考虑到一个后端pod实际上可能会被多个SVC同时使用，Cilium会在IPv4 Options/IPv6 Destination Option extension header中编码特定的cilium信息，将对应的service IP/port信息传递给后端pod，对应的代价就是报文用来传递实际业务数据的MTU会减小。对于 TCP 服务，Cilium 只对 SYN 数据包的service IP/port进行编码，而不会对后续数据包进行编码。\nCilium还支持混合DSR和SNAT模式，即对TCP连接进行DSR，对UDP连接进行SNAT。当workload主要使用TCP进行数据传输的时候，这可以有效地折中优化转发链路和减小MTU两者的影响。\n注意在某些公有云环境中DSR模式可能会不生效，因为底层网络可能会丢弃Cilium特定的IP数据包。如果处理请求的pod不在接受nodeport请求的node上面，那么出现连接问题的时候优先确定数据包是否转发到了对应pod所在的节点上。如果不是这种情况，则建议切换回默认 SNAT 模式作为解决方法。此外，在某些实施源/目标 IP 地址检查（例如 AWS）的公共云提供商环境中，必须禁用检查才能使 DSR 模式工作。\n设置loadBalancer.mode为dsr，将对所有服务（TCP+UDP）都使用DSR模式。\n$ kubectl get cm -n kube-system cilium-config -o yaml | grep bpf-lb-mode bpf-lb-mode: dsr $ kubectl rollout restart ds/cilium deployment/cilium-operator -n kube-system # 使用helm来更新的话可以添加下面的参数 --set loadBalancer.mode=dsr 设置loadBalancer.mode为hybrid，将对TCP服务使用DSR模式，UDP服务使用SNT模式。\n$ kubectl get cm -n kube-system cilium-config -o yaml | grep bpf-lb-mode bpf-lb-mode: hybrid $ kubectl rollout restart ds/cilium deployment/cilium-operator -n kube-system # 使用helm来更新的话可以添加下面的参数 --set loadBalancer.mode=hybrid 设置完成之后我们部署一个测试服务，直接返回remote_addr和remote_port，用来验证DSR模式是否正常工作：\nkey value PodIP 10.32.3.126 LoadBalanceIP 10.32.192.80 ClientIP 10.31.88.1 NodeIP 10.31.80.1-6 首先我们查看snat模式下的情况：\n当在集群外通过LoadBalanceIP、ClusterIP、NodePort等方式访问的时候，转发路径如下：\nClient --\u0026gt; Node --\u0026gt; Pod --\u0026gt; Node --\u0026gt; Client 此时客户端和pod之间的通信来回都是需要经过Node进行NAT转换。\n# bpf-lb-mode: snat # 我们直接访问podIP，是可以返回客户端的真实IP和端口的 $ curl 10.32.3.126 10.31.88.1:49940 # bpf-lb-mode: snat # 通过LoadBalancerIP访问，此时返回的IP和端口是node的，而不是真实访问的客户端的 $ curl 10.32.192.80 10.31.80.1:50066 开启了DSR模式之后的转发路径变成下面这样：\nClient --\u0026gt; Node --\u0026gt; Pod --\u0026gt; Client 此时Pod响应客户端的请求不需要再经由Node转发，而是由Pod直接返回给客户端。\n# bpf-lb-mode: dsr # 无论是通过LoadBalancerIP还是podIP访问，都是可以返回客户端的真实IP和端口的 $ curl 10.32.3.126 10.31.88.1:34220 $ curl 10.32.192.80 10.31.88.1:52650 Socket 旁路\u0026amp;XDP加速 Socket LoadBalancer Bypass in Pod Namespace Cilium对Scoket旁路的全称是Socket LoadBalancer Bypass in Pod Namespace，即对于同一个namspace下的服务，如果namespace中的某个pod通过LoadBalancer来访问同namespace下的另一个服务，实际上请求是会被转发到同namespace中的另一个pod，但是在底层的socket看来，还是由该客户端pod对LoadBalancerIP发起请求产生的socket连接，以及后续需要的NAT转换等操作。\n开启了旁路功能之后，可以绕过上述的流程，直接把请求转发给对应的后端pod，缩短转发链路从而提高性能。\n$ kubectl get cm -n kube-system cilium-config -o yaml | egrep \u0026#34;bpf-lb-sock-hostns-only\u0026#34; bpf-lb-sock-hostns-only: \u0026#34;true\u0026#34; $ kubectl rollout restart ds/cilium deployment/cilium-operator -n kube-system # 使用helm来更新的话可以添加下面的参数 --set socketLB.hostNamespaceOnly=true LoadBalancer \u0026amp; NodePort XDP Acceleration Cilium还支持对外部流量（ExternalIP、NodePort等）服务使用XDP加速从而提高性能表现，由于XDP加速本身依赖Linux内核，同时也对网卡的型号和驱动有要求，因此最好先确定机器对应的网卡驱动（使用ethtool -i eth0查看）和内核版本是否符合需求。官方有给出一份支持列表，对于绝大部分高速网卡和大多数的虚拟机网卡驱动（virtio_net）都是支持的。\n$ kubectl get cm -n kube-system cilium-config -o yaml | egrep \u0026#34;bpf-lb-sock-hostns-only\u0026#34; bpf-lb-acceleration: native $ kubectl rollout restart ds/cilium deployment/cilium-operator -n kube-system # 使用helm来更新的话可以添加下面的参数 --set loadBalancer.acceleration=native # 更新完成之后我们可以通过下面的命令来检查效果 $ kubectl -n kube-system exec ds/cilium -- cilium status --verbose | grep XDP Defaulted container \u0026#34;cilium-agent\u0026#34; out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init) XDP Acceleration: Native Native-Routing-masquerade 注意该功能与是否开启KubeProxyReplacement模式无关，只需要集群开启了Native-routing模式，即确保podIP在集群外路由可达，即可配置该功能。\n默认情况下Cilium会开启IP伪装功能，即pod在访问集群外的服务时，源IP会被伪装成所在node节点的IP而不是本身的真实IP，我们可以根据实际需求来进行调整，通过调整enable-ipv(4|6)-masquerade参数可以分别控制IPv4/IPv6网络下的IP伪装功能。\n$ kubectl get cm -n kube-system cilium-config -o yaml | egrep \u0026#34;enable-ipv(4|6)-masquerade\u0026#34; enable-ipv4-masquerade: \u0026#34;true\u0026#34; enable-ipv6-masquerade: \u0026#34;true\u0026#34; # 当集群内的pod访问集群外部的服务，会返回所在node的IP $ kubectl -n nginx-quic exec -it deployments/nginx-quic-deployment -- curl ipport.tinychen.com 10.31.80.6:44372 # 当集群内的pod访问集群内部的服务，则返回pod自身的真实IP $ kubectl -n nginx-quic exec -it deployments/nginx-quic-deployment -- curl 10.32.192.80 10.32.5.96:42688 $ kubectl get cm -n kube-system cilium-config -o yaml | egrep \u0026#34;enable-ipv(4|6)-masquerade\u0026#34; enable-ipv4-masquerade: \u0026#34;false\u0026#34; enable-ipv6-masquerade: \u0026#34;false\u0026#34; # 关闭masquerade功能之后，访问集群内外部的服务均可以返回pod自身的IP $ kubectl -n nginx-quic exec -it deployments/nginx-quic-deployment -- curl ipport.tinychen.com 10.32.5.96:58512 $ kubectl -n nginx-quic exec -it deployments/nginx-quic-deployment -- curl 10.32.192.80 10.32.5.96:36324 ","permalink":"http://localhost:9011/20221222-k8s-11-kubernetes-without-kubeproxy/","summary":"\u003cp\u003e本文主要介绍在使用了Cilium的K8S集群中如何开启\u003ccode\u003eKubeProxyReplacement\u003c/code\u003e功能来替代K8S集群原生的\u003ccode\u003ekube-proxy\u003c/code\u003e组件，同时还会介绍Cilium的几个特色功能如Maglev一致性哈希、DSR模式、Socket旁路和XDP加速等。\u003c/p\u003e\n\u003cp\u003e关于本文实操使用的K8S集群的部署过程可以参考上一篇文章\u003ca href=\"https://tinychen.com/20221209-k8s-10-deploy-ha-k8s-with-cilium-bgp/\"\u003ek8s系列10-使用kube-router和cilium部署BGP模式的k8s集群\u003c/a\u003e。此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列11-cilium部署KubeProxyReplacement模式"},{"content":"本文主要在centos7系统上基于containerd和stable版本（1.12.4）的cilium组件部署v1.25.4版本的堆叠ETCD高可用k8s原生集群，在LoadBalancer上选择了PureLB和kube-router结合cilium实现BGP路由可达的K8S集群部署。\n此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n准备工作 集群信息 机器均为16C16G的虚拟机，硬盘为100G。\nIP Hostname 10.31.80.0 k8s-cilium-apiserver.tinychen.io 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io 10.31.80.4 k8s-cilium-worker-10-31-80-4.tinychen.io 10.31.80.5 k8s-cilium-worker-10-31-80-5.tinychen.io 10.31.80.6 k8s-cilium-worker-10-31-80-6.tinychen.io 10.32.0.0/17 podSubnet 10.32.128.0/18 serviceSubnet 10.32.192.0/18 LoadBalancerSubnet 检查mac和product_uuid 同一个k8s集群内的所有节点需要确保mac地址和product_uuid均唯一，开始集群初始化之前需要检查相关信息\n# 检查mac地址 ip link ifconfig -a # 检查product_uuid sudo cat /sys/class/dmi/id/product_uuid 配置ssh免密登录（可选） 如果k8s集群的节点有多个网卡，确保每个节点能通过正确的网卡互联访问\n# 在root用户下面生成一个公用的key，并配置可以使用该key免密登录 su root ssh-keygen cd /root/.ssh/ cat id_rsa.pub \u0026gt;\u0026gt; authorized_keys chmod 600 authorized_keys cat \u0026gt;\u0026gt; ~/.ssh/config \u0026lt;\u0026lt;EOF Host k8s-cilium-master-10-31-80-1 HostName 10.31.80.1 User root Port 22 IdentityFile ~/.ssh/id_rsa Host k8s-cilium-master-10-31-80-2 HostName 10.31.80.2 User root Port 22 IdentityFile ~/.ssh/id_rsa Host k8s-cilium-master-10-31-80-3 HostName 10.31.80.3 User root Port 22 IdentityFile ~/.ssh/id_rsa Host k8s-cilium-worker-10-31-80-4 HostName 10.31.80.4 User root Port 22 IdentityFile ~/.ssh/id_rsa Host k8s-cilium-worker-10-31-80-5 HostName 10.31.80.5 User root Port 22 IdentityFile ~/.ssh/id_rsa Host k8s-cilium-worker-10-31-80-6 HostName 10.31.80.6 User root Port 22 IdentityFile ~/.ssh/id_rsa EOF 修改hosts文件 cat \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt;EOF 10.31.80.1 k8s-cilium-master-10-31-80-1 k8s-cilium-master-10-31-80-1.tinychen.io 10.31.80.2 k8s-cilium-master-10-31-80-2 k8s-cilium-master-10-31-80-2.tinychen.io 10.31.80.3 k8s-cilium-master-10-31-80-3 k8s-cilium-master-10-31-80-3.tinychen.io 10.31.80.4 k8s-cilium-worker-10-31-80-4 k8s-cilium-worker-10-31-80-4.tinychen.io 10.31.80.5 k8s-cilium-worker-10-31-80-5 k8s-cilium-worker-10-31-80-5.tinychen.io 10.31.80.6 k8s-cilium-worker-10-31-80-6 k8s-cilium-worker-10-31-80-6.tinychen.io 10.31.80.0 k8s-cilium-apiserver k8s-cilium-apiserver.tinychen.io EOF 关闭swap内存 # 使用命令直接关闭swap内存 swapoff -a # 修改fstab文件禁止开机自动挂载swap分区 sed -i \u0026#39;/swap / s/^\\(.*\\)$/#\\1/g\u0026#39; /etc/fstab 配置时间同步 这里可以根据自己的习惯选择ntp或者是chrony同步均可，同步的时间源服务器可以选择阿里云的ntp1.aliyun.com或者是国家时间中心的ntp.ntsc.ac.cn。\n使用ntp同步 # 使用yum安装ntpdate工具 yum install ntpdate -y # 使用国家时间中心的源同步时间 ntpdate ntp.ntsc.ac.cn # 最后查看一下时间 hwclock 使用chrony同步 # 使用yum安装chrony yum install chrony -y # 设置开机启动并开启chony并查看运行状态 systemctl enable chronyd.service systemctl start chronyd.service systemctl status chronyd.service # 当然也可以自定义时间服务器 vim /etc/chrony.conf # 修改前 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server 0.centos.pool.ntp.org iburst server 1.centos.pool.ntp.org iburst server 2.centos.pool.ntp.org iburst server 3.centos.pool.ntp.org iburst # 修改后 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server ntp.ntsc.ac.cn iburst # 重启服务使配置文件生效 systemctl restart chronyd.service # 查看chrony的ntp服务器状态 chronyc sourcestats -v chronyc sources -v 关闭selinux # 使用命令直接关闭 setenforce 0 # 也可以直接修改/etc/selinux/config文件 sed -i \u0026#39;s/^SELINUX=enforcing$/SELINUX=disabled/\u0026#39; /etc/selinux/config 配置防火墙 k8s集群之间通信和服务暴露需要使用较多端口，为了方便，直接禁用防火墙\n# centos7使用systemctl禁用默认的firewalld服务 systemctl disable firewalld.service 配置netfilter参数 这里主要是需要配置内核加载br_netfilter和iptables放行ipv6和ipv4的流量，确保集群内的容器能够正常通信。\ncat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter EOF cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sudo sysctl --system 关闭IPV6（不建议） 和之前部署其他的CNI不一样，cilium很多服务监听默认情况下都是双栈的（使用cilium-cli操作的时候），因此建议开启系统的IPV6网络支持（即使没有可用的IPV6路由也可以）\n当然没有ipv6网络也是可以的，只是在使用cilium-cli的一些开启port-forward命令时会报错而已。\n# 直接在内核中添加ipv6禁用参数 grubby --update-kernel=ALL --args=ipv6.disable=1 配置IPVS IPVS是专门设计用来应对负载均衡场景的组件，kube-proxy 中的 IPVS 实现通过减少对 iptables 的使用来增加可扩展性。在 iptables 输入链中不使用 PREROUTING，而是创建一个假的接口，叫做 kube-ipvs0，当k8s集群中的负载均衡配置变多的时候，IPVS能实现比iptables更高效的转发性能。\n因为cilium需要升级系统内核，因此这里的内核版本高于4.19\n注意在4.19之后的内核版本中使用nf_conntrack模块来替换了原有的nf_conntrack_ipv4模块\n(Notes: use nf_conntrack instead of nf_conntrack_ipv4 for Linux kernel 4.19 and later)\n# 在使用ipvs模式之前确保安装了ipset和ipvsadm sudo yum install ipset ipvsadm -y # 手动加载ipvs相关模块 modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack # 配置开机自动加载ipvs相关模块 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/ipvs.conf ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack EOF $ lsmod | grep -e ip_vs -e nf_conntrack nf_conntrack_netlink 49152 0 nfnetlink 20480 2 nf_conntrack_netlink ip_vs_sh 16384 0 ip_vs_wrr 16384 0 ip_vs_rr 16384 0 ip_vs 159744 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr nf_conntrack 159744 5 xt_conntrack,nf_nat,nf_conntrack_netlink,xt_MASQUERADE,ip_vs nf_defrag_ipv4 16384 1 nf_conntrack nf_defrag_ipv6 24576 2 nf_conntrack,ip_vs libcrc32c 16384 4 nf_conntrack,nf_nat,xfs,ip_vs $ cut -f1 -d \u0026#34; \u0026#34; /proc/modules | grep -e ip_vs -e nf_conntrack nf_conntrack_netlink ip_vs_sh ip_vs_wrr ip_vs_rr ip_vs nf_conntrack 配置Linux内核（cilium必选） cilium和其他的cni组件最大的不同在于其底层使用了ebpf技术，而该技术对于Linux的系统内核版本有较高的要求，完成的要求可以查看官网的详细链接，这里我们着重看内核版本、内核参数这两个部分。\nLinux内核版本 默认情况下我们可以参考cilium官方给出的一个系统要求总结。因为我们是在k8s集群中部署（使用容器），因此只需要关注Linux内核版本和etcd版本即可。根据前面部署的经验我们可以知道1.23.6版本的k8s默认使用的etcd版本是3.5.+，因此重点就来到了Linux内核版本这里。\nRequirement Minimum Version In cilium container Linux kernel \u0026gt;= 4.9.17 no Key-Value store (etcd) \u0026gt;= 3.1.0 no clang+LLVM \u0026gt;= 10.0 yes iproute2 \u0026gt;= 5.9.0 yes This requirement is only needed if you run cilium-agent natively. If you are using the Cilium container image cilium/cilium, clang+LLVM is included in the container image.\niproute2 is only needed if you run cilium-agent directly on the host machine. iproute2 is included in the cilium/cilium container image.\n毫无疑问CentOS7内置的默认内核版本3.10.x版本的内核是无法满足需求的，但是在升级内核之前，我们再看看其他的一些要求。\ncilium官方还给出了一份列表描述了各项高级功能对内核版本的要求：\nCilium Feature Minimum Kernel Version IPv4 fragment handling \u0026gt;= 4.10 Restrictions on unique prefix lengths for CIDR policy rules \u0026gt;= 4.11 IPsec Transparent Encryption in tunneling mode \u0026gt;= 4.19 WireGuard Transparent Encryption \u0026gt;= 5.6 Host-Reachable Services \u0026gt;= 4.19.57, \u0026gt;= 5.1.16, \u0026gt;= 5.2 Kubernetes Without kube-proxy \u0026gt;= 4.19.57, \u0026gt;= 5.1.16, \u0026gt;= 5.2 Bandwidth Manager \u0026gt;= 5.1 Local Redirect Policy (beta) \u0026gt;= 4.19.57, \u0026gt;= 5.1.16, \u0026gt;= 5.2 Full support for Session Affinity \u0026gt;= 5.7 BPF-based proxy redirection \u0026gt;= 5.7 BPF-based host routing \u0026gt;= 5.10 Socket-level LB bypass in pod netns \u0026gt;= 5.7 Egress Gateway (beta) \u0026gt;= 5.2 VXLAN Tunnel Endpoint (VTEP) Integration \u0026gt;= 5.2 可以看到如果需要满足上面所有需求的话，需要内核版本高于5.10，本着学习测试研究作死的精神，反正都升级了，干脆就升级到新一些的版本吧。这里我们可以直接使用elrepo源来升级内核到较新的内核版本。\n# 查看elrepo源中支持的内核版本 $ yum --disablerepo=\u0026#34;*\u0026#34; --enablerepo=\u0026#34;elrepo-kernel\u0026#34; list available Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile Available Packages elrepo-release.noarch 7.0-6.el7.elrepo elrepo-kernel kernel-lt.x86_64 5.4.225-1.el7.elrepo elrepo-kernel kernel-lt-devel.x86_64 5.4.225-1.el7.elrepo elrepo-kernel kernel-lt-doc.noarch 5.4.225-1.el7.elrepo elrepo-kernel kernel-lt-headers.x86_64 5.4.225-1.el7.elrepo elrepo-kernel kernel-lt-tools.x86_64 5.4.225-1.el7.elrepo elrepo-kernel kernel-lt-tools-libs.x86_64 5.4.225-1.el7.elrepo elrepo-kernel kernel-lt-tools-libs-devel.x86_64 5.4.225-1.el7.elrepo elrepo-kernel kernel-ml.x86_64 6.0.11-1.el7.elrepo elrepo-kernel kernel-ml-devel.x86_64 6.0.11-1.el7.elrepo elrepo-kernel kernel-ml-doc.noarch 6.0.11-1.el7.elrepo elrepo-kernel kernel-ml-headers.x86_64 6.0.11-1.el7.elrepo elrepo-kernel kernel-ml-tools.x86_64 6.0.11-1.el7.elrepo elrepo-kernel kernel-ml-tools-libs.x86_64 6.0.11-1.el7.elrepo elrepo-kernel kernel-ml-tools-libs-devel.x86_64 6.0.11-1.el7.elrepo elrepo-kernel perf.x86_64 5.4.225-1.el7.elrepo elrepo-kernel python-perf.x86_64 5.4.225-1.el7.elrepo elrepo-kernel # 看起来ml版本的内核比较满足我们的需求,直接使用yum进行安装 sudo yum --enablerepo=elrepo-kernel install kernel-ml -y # 使用grubby工具查看系统中已经安装的内核版本信息 sudo grubby --info=ALL # 设置新安装的6.0.11-1版本内核为默认内核版本，此处的index=0要和上面查看的内核版本信息一致 sudo grubby --set-default-index=0 # 查看默认内核是否修改成功 sudo grubby --default-kernel # 重启系统切换到新内核 init 6 # 重启后检查内核版本是否为新的6.0.11-1 uname -a Linux内核参数 首先我们查看自己当前内核版本的参数，基本上可以分为y、n、m三个选项\ny：yes，Build directly into the kernel. 表示该功能被编译进内核中，默认启用 n：no，Leave entirely out of the kernel. 表示该功能未被编译进内核中，不启用 m：module，Build as a module, to be loaded if needed. 表示该功能被编译为模块，按需启用 # 查看当前使用的内核版本的编译参数 cat /boot/config-$(uname -r) cilium官方对各项功能所需要开启的内核参数列举如下：\nIn order for the eBPF feature to be enabled properly, the following kernel configuration options must be enabled. This is typically the case with distribution kernels. When an option can be built as a module or statically linked, either choice is valid.\n为了正确启用 eBPF 功能，必须启用以下内核配置选项。这通常因内核版本情况而异。任何一个选项都可以构建为模块或静态链接，两个选择都是有效的。\n我们暂时只看最基本的Base Requirements\nCONFIG_BPF=y CONFIG_BPF_SYSCALL=y CONFIG_NET_CLS_BPF=y CONFIG_BPF_JIT=y CONFIG_NET_CLS_ACT=y CONFIG_NET_SCH_INGRESS=y CONFIG_CRYPTO_SHA1=y CONFIG_CRYPTO_USER_API_HASH=y CONFIG_CGROUPS=y CONFIG_CGROUP_BPF=y 对比我们使用的6.0.11-1.el7.elrepo.x86_64内核可以发现有两个模块是为m\n# egrep \u0026#34;^CONFIG_BPF=|^CONFIG_BPF_SYSCALL=|^CONFIG_NET_CLS_BPF=|^CONFIG_BPF_JIT=|^CONFIG_NET_CLS_ACT=|^CONFIG_NET_SCH_INGRESS=|^CONFIG_CRYPTO_SHA1=|^CONFIG_CRYPTO_USER_API_HASH=|^CONFIG_CGROUPS=|^CONFIG_CGROUP_BPF=\u0026#34; /boot/config-6.0.11-1.el7.elrepo.x86_64 CONFIG_BPF=y CONFIG_BPF_SYSCALL=y CONFIG_BPF_JIT=y CONFIG_CGROUPS=y CONFIG_CGROUP_BPF=y CONFIG_NET_SCH_INGRESS=m CONFIG_NET_CLS_BPF=m CONFIG_NET_CLS_ACT=y CONFIG_CRYPTO_SHA1=y CONFIG_CRYPTO_USER_API_HASH=y 缺少的这两个模块我们可以在/usr/lib/modules/$(uname -r)目录下面找到它们：\n$ realpath ./kernel/net/sched/sch_ingress.ko /usr/lib/modules/6.0.11-1.el7.elrepo.x86_64/kernel/net/sched/sch_ingress.ko $ realpath ./kernel/net/sched/cls_bpf.ko /usr/lib/modules/6.0.11-1.el7.elrepo.x86_64/kernel/net/sched/cls_bpf.ko 确认相关内核模块存在我们直接加载内核即可：\n# 直接使用modprobe命令加载 $ modprobe cls_bpf $ modprobe sch_ingress $ lsmod | egrep \u0026#34;cls_bpf|sch_ingress\u0026#34; sch_ingress 16384 0 cls_bpf 24576 0 # 配置开机自动加载cilium所需相关模块 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/cilium-base-requirements.conf cls_bpf sch_ingress EOF 其他cilium高级功能所需要的内核功能也类似，这里不做赘述。\n安装container runtime 安装containerd 详细的官方文档可以参考这里，由于在刚发布的1.24版本中移除了docker-shim，因此安装的版本≥1.24的时候需要注意容器运行时的选择。这里我们安装的版本为高于1.24，因此我们不能继续使用docker，这里我们将其换为containerd\n修改Linux内核参数 # 首先生成配置文件确保配置持久化 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter # Setup required sysctl params, these persist across reboots. cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF # Apply sysctl params without reboot sudo sysctl --system 安装containerd centos7比较方便的部署方式是利用已有的yum源进行安装，这里我们可以使用docker官方的yum源来安装containerd\n# 导入docker官方的yum源 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 查看yum源中存在的各个版本的containerd.io yum list containerd.io --showduplicates | sort -r # 直接安装最新版本的containerd.io yum install containerd.io -y # 启动containerd sudo systemctl start containerd # 最后我们还要设置一下开机启动 sudo systemctl enable --now containerd 关于CRI 官方表示，对于k8s来说，不需要安装cri-containerd，并且该功能会在后面的2.0版本中废弃。\nFAQ: For Kubernetes, do I need to download cri-containerd-(cni-)\u0026lt;VERSION\u0026gt;-\u0026lt;OS-\u0026lt;ARCH\u0026gt;.tar.gz too?\nAnswer: No.\nAs the Kubernetes CRI feature has been already included in containerd-\u0026lt;VERSION\u0026gt;-\u0026lt;OS\u0026gt;-\u0026lt;ARCH\u0026gt;.tar.gz, you do not need to download the cri-containerd-.... archives to use CRI.\nThe cri-containerd-... archives are deprecated, do not work on old Linux distributions, and will be removed in containerd 2.0.\n安装cni-plugins 使用yum源安装的方式会把runc安装好，但是并不会安装cni-plugins，因此这部分还是需要我们自行安装。\nThe containerd.io package contains runc too, but does not contain CNI plugins.\n我们直接在github上面找到系统对应的架构版本，这里为amd64，然后解压即可。\n# Download the cni-plugins-\u0026lt;OS\u0026gt;-\u0026lt;ARCH\u0026gt;-\u0026lt;VERSION\u0026gt;.tgz archive from https://github.com/containernetworking/plugins/releases , verify its sha256sum, and extract it under /opt/cni/bin: # 下载源文件和sha512文件并校验 $ wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz $ wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz.sha512 $ sha512sum -c cni-plugins-linux-amd64-v1.1.1.tgz.sha512 # 创建目录并解压 $ mkdir -p /opt/cni/bin $ tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.1.1.tgz 配置cgroup drivers CentOS7使用的是systemd来初始化系统并管理进程，初始化进程会生成并使用一个 root 控制组 (cgroup), 并充当 cgroup 管理器。 Systemd 与 cgroup 集成紧密，并将为每个 systemd 单元分配一个 cgroup。 我们也可以配置容器运行时和 kubelet 使用 cgroupfs。 连同 systemd 一起使用 cgroupfs 意味着将有两个不同的 cgroup 管理器。而当一个系统中同时存在cgroupfs和systemd两者时，容易变得不稳定，因此最好更改设置，令容器运行时和 kubelet 使用 systemd 作为 cgroup 驱动，以此使系统更为稳定。 对于containerd, 需要设置配置文件/etc/containerd/config.toml中的 SystemdCgroup 参数。\n参考k8s官方的说明文档：\nhttps://kubernetes.io/docs/setup/production-environment/container-runtimes/#containerd-systemd\n[plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc] ... [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc.options] SystemdCgroup = true 接下来我们开始配置containerd的cgroup driver\n# 查看默认的配置文件，我们可以看到是没有启用systemd $ containerd config default | grep SystemdCgroup SystemdCgroup = false # 使用yum安装的containerd的配置文件非常简单 $ cat /etc/containerd/config.toml | egrep -v \u0026#34;^#|^$\u0026#34; disabled_plugins = [\u0026#34;cri\u0026#34;] # 导入一个完整版的默认配置文件模板为config.toml $ mv /etc/containerd/config.toml /etc/containerd/config.toml.origin $ containerd config default \u0026gt; /etc/containerd/config.toml # 修改SystemdCgroup参数并重启 $ sed -i \u0026#39;s/SystemdCgroup = false/SystemdCgroup = true/g\u0026#39; /etc/containerd/config.toml $ systemctl restart containerd # 重启之后我们再检查配置就会发现已经启用了SystemdCgroup $ containerd config dump | grep SystemdCgroup SystemdCgroup = true # 查看containerd状态的时候我们可以看到cni相关的报错 # 这是因为我们先安装了cni-plugins但是还没有安装k8s的cni插件 # 属于正常情况 $ systemctl status containerd -l May 12 09:57:31 tiny-kubeproxy-free-master-18-1.k8s.tcinternal containerd[5758]: time=\u0026#34;2022-05-12T09:57:31.100285056+08:00\u0026#34; level=error msg=\u0026#34;failed to load cni during init, please check CRI plugin status before setting up network for pods\u0026#34; error=\u0026#34;cni config load failed: no network config found in /etc/cni/net.d: cni plugin not initialized: failed to load cni config\u0026#34; 关于kubelet的cgroup driver k8s官方有详细的文档介绍了如何设置kubelet的cgroup driver，需要特别注意的是，在1.22版本开始，如果没有手动设置kubelet的cgroup driver，那么默认会设置为systemd\nNote: In v1.22, if the user is not setting the cgroupDriver field under KubeletConfiguration, kubeadm will default it to systemd.\n一个比较简单的指定kubelet的cgroup driver的方法就是在kubeadm-config.yaml加入cgroupDriver字段\n# kubeadm-config.yaml kind: ClusterConfiguration apiVersion: kubeadm.k8s.io/v1beta3 kubernetesVersion: v1.21.0 --- kind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 cgroupDriver: systemd 我们可以直接查看configmaps来查看初始化之后集群的kubeadm-config配置。\n$ kubectl describe configmaps kubeadm-config -n kube-system Name: kubeadm-config Namespace: kube-system Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; Data ==== ClusterConfiguration: ---- apiServer: extraArgs: authorization-mode: Node,RBAC timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: v1.23.6 networking: dnsDomain: cali-cluster.tclocal serviceSubnet: 10.88.0.0/18 scheduler: {} BinaryData ==== Events: \u0026lt;none\u0026gt; 当然因为我们需要安装的版本高于1.22.0并且使用的就是systemd，因此可以不用再重复配置。\n安装kube三件套 对应的官方文档可以参考这里\nhttps://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl\nkube三件套就是kubeadm、kubelet 和 kubectl，三者的具体功能和作用如下：\nkubeadm：用来初始化集群的指令。 kubelet：在集群中的每个节点上用来启动 Pod 和容器等。 kubectl：用来与集群通信的命令行工具。 需要注意的是：\nkubeadm不会帮助我们管理kubelet和kubectl，其他两者也是一样的，也就是说这三者是相互独立的，并不存在谁管理谁的情况； kubelet的版本必须小于等于API-server的版本，否则容易出现兼容性的问题； kubectl并不是集群中的每个节点都需要安装，也并不是一定要安装在集群中的节点，可以单独安装在自己本地的机器环境上面，然后配合kubeconfig文件即可使用kubectl命令来远程管理对应的k8s集群； CentOS7的安装比较简单，我们直接使用官方提供的yum源即可。需要注意的是这里需要设置selinux的状态，但是前面我们已经关闭了selinux，因此这里略过这步。\n# 直接导入谷歌官方的yum源 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\\$basearch enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg exclude=kubelet kubeadm kubectl EOF # 当然如果连不上谷歌的源，可以考虑使用国内的阿里镜像源 cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF # 接下来直接安装三件套即可 sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes # 如果网络环境不好出现gpgcheck验证失败导致无法正常读取yum源，可以考虑关闭该yum源的repo_gpgcheck sed -i \u0026#39;s/repo_gpgcheck=1/repo_gpgcheck=0/g\u0026#39; /etc/yum.repos.d/kubernetes.repo # 或者在安装的时候禁用gpgcheck sudo yum install -y kubelet kubeadm kubectl --nogpgcheck --disableexcludes=kubernetes # 如果想要安装特定版本，可以使用这个命令查看相关版本的信息 sudo yum list --nogpgcheck kubelet kubeadm kubectl --showduplicates --disableexcludes=kubernetes # 安装完成后配置开机自启kubelet sudo systemctl enable --now kubelet 初始化集群 etcd高可用 etcd高可用架构参考这篇官方文档，主要可以分为堆叠etcd方案和外置etcd方案，两者的区别就是etcd是否部署在apiserver所在的node机器上面，这里我们主要使用的是堆叠etcd部署方案。\napiserver高可用 apisever高可用配置参考这篇官方文档。目前apiserver的高可用比较主流的官方推荐方案是使用keepalived和haproxy，由于centos7自带的版本较旧，重新编译又过于麻烦，因此我们可以参考官方给出的静态pod的部署方式，提前将相关的配置文件放置到/etc/kubernetes/manifests目录下即可(需要提前手动创建好目录)。官方表示对于我们这种堆叠部署控制面master节点和etcd的方式而言这是一种优雅的解决方案。\nThis is an elegant solution, in particular with the setup described under Stacked control plane and etcd nodes.\n首先我们需要准备好三台master节点上面的keepalived配置文件和haproxy配置文件：\n! /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { router_id LVS_DEVEL } vrrp_script check_apiserver { script \u0026#34;/etc/keepalived/check_apiserver.sh\u0026#34; interval 3 weight -2 fall 10 rise 2 } vrrp_instance VI_1 { state ${STATE} interface ${INTERFACE} virtual_router_id ${ROUTER_ID} priority ${PRIORITY} authentication { auth_type PASS auth_pass ${AUTH_PASS} } virtual_ipaddress { ${APISERVER_VIP} } track_script { check_apiserver } } 实际上我们需要区分三台控制面节点的状态\n! /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { router_id CILIUM_MASTER_80_1 } vrrp_script check_apiserver { script \u0026#34;/etc/keepalived/check_apiserver.sh\u0026#34; interval 3 weight -2 fall 10 rise 2 } vrrp_instance cilium_ha_apiserver_10_31_80_0 { state MASTER interface eth0 virtual_router_id 80 priority 100 authentication { auth_type PASS auth_pass pass@77 } virtual_ipaddress { 10.31.80.0 } track_script { check_apiserver } } ! /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { router_id CILIUM_MASTER_80_2 } vrrp_script check_apiserver { script \u0026#34;/etc/keepalived/check_apiserver.sh\u0026#34; interval 3 weight -2 fall 10 rise 2 } vrrp_instance cilium_ha_apiserver_10_31_80_0 { state BACKUP interface eth0 virtual_router_id 80 priority 90 authentication { auth_type PASS auth_pass pass@77 } virtual_ipaddress { 10.31.80.0 } track_script { check_apiserver } } ! /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { router_id CILIUM_MASTER_80_3 } vrrp_script check_apiserver { script \u0026#34;/etc/keepalived/check_apiserver.sh\u0026#34; interval 3 weight -2 fall 10 rise 2 } vrrp_instance cilium_ha_apiserver_10_31_80_0 { state BACKUP interface eth0 virtual_router_id 80 priority 80 authentication { auth_type PASS auth_pass pass@77 } virtual_ipaddress { 10.31.80.0 } track_script { check_apiserver } } 这是haproxy的配置文件模板：\n# /etc/haproxy/haproxy.cfg #--------------------------------------------------------------------- # Global settings #--------------------------------------------------------------------- global log /dev/log local0 log /dev/log local1 notice daemon #--------------------------------------------------------------------- # common defaults that all the \u0026#39;listen\u0026#39; and \u0026#39;backend\u0026#39; sections will # use if not designated in their block #--------------------------------------------------------------------- defaults mode http log global option httplog option dontlognull option http-server-close option forwardfor except 127.0.0.0/8 option redispatch retries 1 timeout http-request 10s timeout queue 20s timeout connect 5s timeout client 20s timeout server 20s timeout http-keep-alive 10s timeout check 10s #--------------------------------------------------------------------- # apiserver frontend which proxys to the control plane nodes #--------------------------------------------------------------------- frontend apiserver bind *:${APISERVER_DEST_PORT} mode tcp option tcplog default_backend apiserver #--------------------------------------------------------------------- # round robin balancing for apiserver #--------------------------------------------------------------------- backend apiserver option httpchk GET /healthz http-check expect status 200 mode tcp option ssl-hello-chk balance roundrobin server ${HOST1_ID} ${HOST1_ADDRESS}:${APISERVER_SRC_PORT} check # [...] 这是keepalived的检测脚本，注意这里的${APISERVER_VIP}和${APISERVER_DEST_PORT}要替换为集群的实际VIP和端口\n#!/bin/sh APISERVER_VIP=\u0026#34;10.31.80.0\u0026#34; APISERVER_DEST_PORT=\u0026#34;8443\u0026#34; errorExit() { echo \u0026#34;*** $*\u0026#34; 1\u0026gt;\u0026amp;2 exit 1 } curl --silent --max-time 2 --insecure https://localhost:${APISERVER_DEST_PORT}/ -o /dev/null || errorExit \u0026#34;Error GET https://localhost:${APISERVER_DEST_PORT}/\u0026#34; if ip addr | grep -q ${APISERVER_VIP}; then curl --silent --max-time 2 --insecure https://${APISERVER_VIP}:${APISERVER_DEST_PORT}/ -o /dev/null || errorExit \u0026#34;Error GET https://${APISERVER_VIP}:${APISERVER_DEST_PORT}/\u0026#34; fi 这是keepalived的部署文件/etc/kubernetes/manifests/keepalived.yaml，注意这里的配置文件路径要和上面的对应一致。\napiVersion: v1 kind: Pod metadata: creationTimestamp: null name: keepalived namespace: kube-system spec: containers: - image: osixia/keepalived:2.0.17 name: keepalived resources: {} securityContext: capabilities: add: - NET_ADMIN - NET_BROADCAST - NET_RAW volumeMounts: - mountPath: /usr/local/etc/keepalived/keepalived.conf name: config - mountPath: /etc/keepalived/check_apiserver.sh name: check hostNetwork: true volumes: - hostPath: path: /etc/keepalived/keepalived.conf name: config - hostPath: path: /etc/keepalived/check_apiserver.sh name: check status: {} 这是haproxy的部署文件/etc/kubernetes/manifests/haproxy.yaml，注意这里的配置文件路径要和上面的对应一致，且${APISERVER_DEST_PORT}要换成我们对应的apiserver的端口，这里我们改为8443，避免和原有的6443端口冲突\napiVersion: v1 kind: Pod metadata: name: haproxy namespace: kube-system spec: containers: - image: haproxy:2.1.4 name: haproxy livenessProbe: failureThreshold: 8 httpGet: host: localhost path: /healthz #port: ${APISERVER_DEST_PORT} port: 8443 scheme: HTTPS volumeMounts: - mountPath: /usr/local/etc/haproxy/haproxy.cfg name: haproxyconf readOnly: true hostNetwork: true volumes: - hostPath: path: /etc/haproxy/haproxy.cfg type: FileOrCreate name: haproxyconf status: {} 编写配置文件 在集群中所有节点都执行完上面的操作之后，我们就可以开始创建k8s集群了。因为我们这次需要进行高可用部署，所以初始化的时候先挑任意一台master控制面节点进行操作即可。\n# 我们先使用kubeadm命令查看一下主要的几个镜像版本 $ kubeadm config images list registry.k8s.io/kube-apiserver:v1.25.4 registry.k8s.io/kube-controller-manager:v1.25.4 registry.k8s.io/kube-scheduler:v1.25.4 registry.k8s.io/kube-proxy:v1.25.4 registry.k8s.io/pause:3.8 registry.k8s.io/etcd:3.5.5-0 registry.k8s.io/coredns/coredns:v1.9.3 # 为了方便编辑和管理，我们还是把初始化参数导出成配置文件 $ kubeadm config print init-defaults \u0026gt; kubeadm-cilium-ha.conf 考虑到大多数情况下国内的网络无法使用谷歌的镜像源(1.25版本开始从k8s.gcr.io换为registry.k8s.io)，我们可以直接在配置文件中修改imageRepository参数为阿里的镜像源registry.aliyuncs.com/google_containers kubernetesVersion字段用来指定我们要安装的k8s版本 localAPIEndpoint参数需要修改为我们的master节点的IP和端口，初始化之后的k8s集群的apiserver地址就是这个 criSocket从1.24.0版本开始已经默认变成了containerd podSubnet、serviceSubnet和dnsDomain两个参数默认情况下可以不用修改，这里我按照自己的需求进行了变更 nodeRegistration里面的name参数修改为对应master节点的hostname controlPlaneEndpoint参数配置的才是我们前面配置的集群高可用apiserver的地址 新增配置块使用ipvs，具体可以参考官方文档 apiVersion: kubeadm.k8s.io/v1beta3 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 10.31.80.1 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock imagePullPolicy: IfNotPresent name: k8s-cilium-master-10-31-80-1.tinychen.io taints: null --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: 1.25.4 controlPlaneEndpoint: \u0026#34;k8s-cilium-apiserver.tinychen.io:8443\u0026#34; networking: dnsDomain: cili-cluster.tclocal serviceSubnet: 10.32.128.0/18 podSubnet: 10.32.0.0/17 scheduler: {} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs 初始化集群 此时我们再查看对应的配置文件中的镜像版本，就会发现已经变成了对应阿里云镜像源的版本\n# 查看一下对应的镜像版本，确定配置文件是否生效 $ kubeadm config images list --config kubeadm-cilium-ha.conf registry.aliyuncs.com/google_containers/kube-apiserver:v1.25.4 registry.aliyuncs.com/google_containers/kube-controller-manager:v1.25.4 registry.aliyuncs.com/google_containers/kube-scheduler:v1.25.4 registry.aliyuncs.com/google_containers/kube-proxy:v1.25.4 registry.aliyuncs.com/google_containers/pause:3.8 registry.aliyuncs.com/google_containers/etcd:3.5.5-0 registry.aliyuncs.com/google_containers/coredns:v1.9.3 # 确认没问题之后我们直接拉取镜像 $ kubeadm config images pull --config kubeadm-cilium-ha.conf [config/images] Pulled registry.aliyuncs.com/google_containers/kube-apiserver:v1.25.4 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-controller-manager:v1.25.4 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-scheduler:v1.25.4 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-proxy:v1.25.4 [config/images] Pulled registry.aliyuncs.com/google_containers/pause:3.8 [config/images] Pulled registry.aliyuncs.com/google_containers/etcd:3.5.5-0 [config/images] Pulled registry.aliyuncs.com/google_containers/coredns:v1.9.3 # 初始化，注意添加参数--upload-certs确保证书能够上传到kubernetes集群中以secret保存 $ kubeadm init --config kubeadm-cilium-ha.conf --upload-certs [init] Using Kubernetes version: v1.25.4 [preflight] Running pre-flight checks [WARNING Service-Kubelet]: kubelet service is not enabled, please run \u0026#39;systemctl enable kubelet.service\u0026#39; [preflight] Pulling images required for setting up a Kubernetes cluster [preflight] This might take a minute or two, depending on the speed of your internet connection [preflight] You can also perform this action in beforehand using \u0026#39;kubeadm config images pull\u0026#39; ...此处略去一堆输出... 当我们看到下面这个输出结果的时候，我们的集群就算是初始化成功了。\nYour Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf You should now deploy a pod network to the cluster. Run \u0026#34;kubectl apply -f [podnetwork].yaml\u0026#34; with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ You can now join any number of the control-plane node running the following command on each as root: kubeadm join k8s-cilium-apiserver.tinychen.io:6443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:cc68219b233262d8834ad5d6e96166be487c751b53fb9ec19a5ca3599b538a33 \\ --control-plane --certificate-key e0bfc81fd9277731611f4b4351beed53a1d0c4e1c82932734a38919ddd76a185 Please note that the certificate-key gives access to cluster sensitive data, keep it secret! As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use \u0026#34;kubeadm init phase upload-certs --upload-certs\u0026#34; to reload certs afterward. Then you can join any number of worker nodes by running the following on each as root: kubeadm join k8s-cilium-apiserver.tinychen.io:6443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:cc68219b233262d8834ad5d6e96166be487c751b53fb9ec19a5ca3599b538a33 接下来我们在剩下的两个master节点上面执行上面输出的命令，注意要执行带有--control-plane --certificate-key这两个参数的命令，其中--control-plane参数是确定该节点为master控制面节点，而--certificate-key参数则是把我们前面初始化集群的时候通过--upload-certs上传到k8s集群中的证书下载下来使用。\nThis node has joined the cluster and a new control plane instance was created: * Certificate signing request was sent to apiserver and approval was received. * The Kubelet was informed of the new secure connection details. * Control plane label and taint were applied to the new node. * The Kubernetes control plane instances scaled up. * A new etcd member was added to the local/stacked etcd cluster. To start administering your cluster from this node, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Run \u0026#39;kubectl get nodes\u0026#39; to see this node join the cluster. 最后再对剩下的三个worker节点执行普通的加入集群命令，当看到下面的输出的时候说明节点成功加入集群了。\nThis node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run \u0026#39;kubectl get nodes\u0026#39; on the control-plane to see this node join the cluster. 如果不小心没保存初始化成功的输出信息，或者是以后还需要新增节点也没有关系，我们可以使用kubectl工具查看或者生成token\n# 查看现有的token列表 $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS abcdef.0123456789abcdef 23h 2022-12-09T08:14:37Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token dss91p.3r5don4a3e9r2f29 1h 2022-12-08T10:14:36Z \u0026lt;none\u0026gt; Proxy for managing TTL for the kubeadm-certs secret \u0026lt;none\u0026gt; # 如果token已经失效，那就再创建一个新的token $ kubeadm token create 8hmoux.jabpgvs521r8rsqm $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS 8hmoux.jabpgvs521r8rsqm 23h 2022-12-09T08:29:29Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token abcdef.0123456789abcdef 23h 2022-12-09T08:14:37Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token dss91p.3r5don4a3e9r2f29 1h 2022-12-08T10:14:36Z \u0026lt;none\u0026gt; Proxy for managing TTL for the kubeadm-certs secret \u0026lt;none\u0026gt; # 如果找不到--discovery-token-ca-cert-hash参数，则可以在master节点上使用openssl工具来获取 $ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2\u0026gt;/dev/null | openssl dgst -sha256 -hex | sed \u0026#39;s/^.* //\u0026#39; cc68219b233262d8834ad5d6e96166be487c751b53fb9ec19a5ca3599b538a33 配置kubeconfig 刚初始化成功之后，我们还没办法马上查看k8s集群信息，需要配置kubeconfig相关参数才能正常使用kubectl连接apiserver读取集群信息。\n# 对于非root用户，可以这样操作 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 如果是root用户，可以直接导入环境变量 export KUBECONFIG=/etc/kubernetes/admin.conf # 添加kubectl的自动补全功能 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 前面我们提到过kubectl不一定要安装在集群内，实际上只要是任何一台能连接到apiserver的机器上面都可以安装kubectl并且根据步骤配置kubeconfig，就可以使用kubectl命令行来管理对应的k8s集群。\n配置完成后，我们再执行相关命令就可以查看集群的信息了，但是此时节点的状态还是NotReady，接下来就需要部署CNI了。\n$ kubectl cluster-info Kubernetes control plane is running at https://k8s-cilium-apiserver.tinychen.io:8443 CoreDNS is running at https://k8s-cilium-apiserver.tinychen.io:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39;. $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-cilium-master-10-31-80-1.tinychen.io NotReady control-plane 16m v1.25.4 10.31.80.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-2.tinychen.io NotReady control-plane 12m v1.25.4 10.31.80.2 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-master-10-31-80-3.tinychen.io NotReady control-plane 7m42s v1.25.4 10.31.80.3 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-4.tinychen.io NotReady \u0026lt;none\u0026gt; 5m28s v1.25.4 10.31.80.4 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-5.tinychen.io NotReady \u0026lt;none\u0026gt; 4m40s v1.25.4 10.31.80.5 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 k8s-cilium-worker-10-31-80-6.tinychen.io NotReady \u0026lt;none\u0026gt; 4m9s v1.25.4 10.31.80.6 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 6.0.11-1.el7.elrepo.x86_64 containerd://1.6.11 $ kubectl get pods -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system coredns-c676cc86f-6jg9b 0/1 Pending 0 31m \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system coredns-c676cc86f-qmx9s 0/1 Pending 0 31m \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 0 31m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 0 28m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 0 22m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system haproxy-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 12 (10m ago) 31m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system haproxy-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 9 (11m ago) 28m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system haproxy-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 8 (11m ago) 22m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system keepalived-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 0 31m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system keepalived-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 0 27m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system keepalived-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 0 22m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 0 31m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 0 27m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 0 22m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 1 (28m ago) 31m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 0 28m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 0 22m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-52zmk 1/1 Running 0 31m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-dg4nt 1/1 Running 0 20m 10.31.80.5 k8s-cilium-worker-10-31-80-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-gbgr5 1/1 Running 0 28m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-gpzxv 1/1 Running 0 19m 10.31.80.6 k8s-cilium-worker-10-31-80-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-kn9gq 1/1 Running 0 23m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-xw8nl 1/1 Running 0 20m 10.31.80.4 k8s-cilium-worker-10-31-80-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 1 (28m ago) 31m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 0 28m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 0 22m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 安装CNI 部署helm3 cilium的部署依赖helm3，因此我们在部署cilium之前需要先安装helm3。\nhelm3的部署非常的简单，我们只要去GitHub找到对应系统版本的二进制文件，下载解压后放到系统的执行目录就可以使用了。\n$ wget https://get.helm.sh/helm-v3.10.2-linux-amd64.tar.gz $ tar -zxvf helm-v3.10.2-linux-amd64.tar.gz $ cp -rp linux-amd64/helm /usr/local/bin/ $ helm version version.BuildInfo{Version:\u0026#34;v3.10.2\u0026#34;, GitCommit:\u0026#34;50f003e5ee8704ec937a756c646870227d7c8b58\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, GoVersion:\u0026#34;go1.18.8\u0026#34;} 部署cilium 完整的部署指南可以参考官方文档，首先我们添加helm的repo。\n$ helm repo add cilium https://helm.cilium.io/ \u0026#34;cilium\u0026#34; has been added to your repositories $ helm repo list NAME URL cilium https://helm.cilium.io/ 参考官网的文档，这里我们需要指定集群的APIserver的IP和端口\nhelm install cilium ./cilium \\ --namespace kube-system \\ --set kubeProxyReplacement=strict \\ --set k8sServiceHost=REPLACE_WITH_API_SERVER_IP \\ --set k8sServicePort=REPLACE_WITH_API_SERVER_PORT 但是考虑到cilium默认使用的podCIDR为10.0.0.0/8，很可能会和我们集群内的网络冲突，最好的方案就是初始化的时候指定podCIDR，关于初始化的时候podCIDR的设置，可以参考官方的这个文章。\nhelm install cilium cilium/cilium --version 1.12.4 \\ --namespace kube-system \\ --set k8sServiceHost=REPLACE_WITH_API_SERVER_IP \\ --set k8sServicePort=REPLACE_WITH_API_SERVER_PORT \\ --set ipam.operator.clusterPoolIPv4PodCIDRList=\u0026lt;IPv4CIDR\u0026gt; \\ --set ipam.operator.clusterPoolIPv4MaskSize=\u0026lt;IPv4MaskSize\u0026gt; 最后可以得到我们的初始化安装参数\nhelm install cilium cilium/cilium --version 1.12.4 \\ --namespace kube-system \\ --set k8sServiceHost=k8s-cilium-apiserver.tinychen.io \\ --set k8sServicePort=8443 \\ --set ipam.operator.clusterPoolIPv4PodCIDRList=10.32.0.0/17 \\ --set ipam.operator.clusterPoolIPv4MaskSize=24 然后我们使用指令进行安装\n$ helm install cilium cilium/cilium --version 1.12.4 \\ \u0026gt; --namespace kube-system \\ \u0026gt; --set k8sServiceHost=k8s-cilium-apiserver.tinychen.io \\ \u0026gt; --set k8sServicePort=8443 \\ \u0026gt; --set ipam.operator.clusterPoolIPv4PodCIDRList=10.32.0.0/17 \\ \u0026gt; --set ipam.operator.clusterPoolIPv4MaskSize=24 NAME: cilium LAST DEPLOYED: Thu Dec 8 17:01:13 2022 NAMESPACE: kube-system STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: You have successfully installed Cilium with Hubble. Your release version is 1.12.4. For any further help, visit https://docs.cilium.io/en/v1.12/gettinghelp 此时我们再查看集群的daemonset和deployment状态：\n# 这时候查看集群的daemonset和deployment状态可以看到cilium相关的服务已经正常 $ kubectl get ds -A NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-system cilium 6 6 6 6 6 kubernetes.io/os=linux 3m14s kube-system kube-proxy 6 6 6 6 6 kubernetes.io/os=linux 49m $ kubectl get deploy -A NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE kube-system cilium-operator 2/2 2 2 3m29s kube-system coredns 2/2 2 2 50m 再查看所有的pod，状态都正常，ip也和我们初始化的时候分配的ip段一致，说明初始化的参数设置生效了。\n# 再查看所有的pod，状态都正常，ip按预期进行了分配 $# kubectl get pods -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system cilium-5ppb6 1/1 Running 0 3m4s 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-chrch 1/1 Running 0 3m4s 10.31.80.5 k8s-cilium-worker-10-31-80-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-f2sdc 1/1 Running 0 3m4s 10.31.80.4 k8s-cilium-worker-10-31-80-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-fbrdl 1/1 Running 0 3m4s 10.31.80.6 k8s-cilium-worker-10-31-80-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-g7dzj 1/1 Running 0 3m4s 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-g7k5m 1/1 Running 0 3m4s 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-operator-5598954549-62q7t 1/1 Running 0 3m4s 10.31.80.4 k8s-cilium-worker-10-31-80-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-operator-5598954549-zctb8 1/1 Running 0 3m4s 10.31.80.6 k8s-cilium-worker-10-31-80-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system coredns-c676cc86f-6jg9b 1/1 Running 0 49m 10.32.0.64 k8s-cilium-worker-10-31-80-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system coredns-c676cc86f-qmx9s 1/1 Running 0 49m 10.32.0.145 k8s-cilium-worker-10-31-80-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 0 49m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 0 45m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 0 40m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system haproxy-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 12 (28m ago) 49m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system haproxy-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 9 (29m ago) 45m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system haproxy-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 8 (29m ago) 40m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system keepalived-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 0 49m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system keepalived-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 0 45m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system keepalived-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 0 40m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 0 49m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 0 45m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 0 40m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 1 (45m ago) 49m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 0 45m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 0 40m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-52zmk 1/1 Running 0 49m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-dg4nt 1/1 Running 0 37m 10.31.80.5 k8s-cilium-worker-10-31-80-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-gbgr5 1/1 Running 0 45m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-gpzxv 1/1 Running 0 37m 10.31.80.6 k8s-cilium-worker-10-31-80-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-kn9gq 1/1 Running 0 40m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-xw8nl 1/1 Running 0 38m 10.31.80.4 k8s-cilium-worker-10-31-80-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-k8s-cilium-master-10-31-80-1.tinychen.io 1/1 Running 1 (45m ago) 49m 10.31.80.1 k8s-cilium-master-10-31-80-1.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-k8s-cilium-master-10-31-80-2.tinychen.io 1/1 Running 0 45m 10.31.80.2 k8s-cilium-master-10-31-80-2.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-k8s-cilium-master-10-31-80-3.tinychen.io 1/1 Running 0 40m 10.31.80.3 k8s-cilium-master-10-31-80-3.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 这时候我们再进入pod中检查cilium的状态\n# --verbose参数可以查看详细的状态信息 # cilium-97fn7需要替换为任意一个cilium的pod $ kubectl exec -it -n kube-system cilium-5ppb6 -- cilium status --verbose Defaulted container \u0026#34;cilium-agent\u0026#34; out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init) KVStore: Ok Disabled Kubernetes: Ok 1.25 (v1.25.4) [linux/amd64] Kubernetes APIs: [\u0026#34;cilium/v2::CiliumClusterwideNetworkPolicy\u0026#34;, \u0026#34;cilium/v2::CiliumEndpoint\u0026#34;, \u0026#34;cilium/v2::CiliumNetworkPolicy\u0026#34;, \u0026#34;cilium/v2::CiliumNode\u0026#34;, \u0026#34;core/v1::Namespace\u0026#34;, \u0026#34;core/v1::Node\u0026#34;, \u0026#34;core/v1::Pods\u0026#34;, \u0026#34;core/v1::Service\u0026#34;, \u0026#34;discovery/v1::EndpointSlice\u0026#34;, \u0026#34;networking.k8s.io/v1::NetworkPolicy\u0026#34;] KubeProxyReplacement: Disabled Host firewall: Disabled CNI Chaining: none Cilium: Ok 1.12.4 (v1.12.4-6eaecaf) NodeMonitor: Listening for events on 16 CPUs with 64x4096 of shared memory Cilium health daemon: Ok IPAM: IPv4: 2/254 allocated from 10.32.3.0/24, Allocated addresses: 10.32.3.145 (router) 10.32.3.170 (health) BandwidthManager: Disabled Host Routing: Legacy Masquerading: IPTables [IPv4: Enabled, IPv6: Disabled] Clock Source for BPF: ktime Controller Status: 18/18 healthy Name Last success Last error Count Message cilium-health-ep 10s ago never 0 no error dns-garbage-collector-job 16s ago never 0 no error endpoint-1050-regeneration-recovery never never 0 no error endpoint-671-regeneration-recovery never never 0 no error endpoint-gc 2m17s ago never 0 no error ipcache-inject-labels 2m13s ago 2m15s ago 0 no error k8s-heartbeat 17s ago never 0 no error link-cache 11s ago never 0 no error metricsmap-bpf-prom-sync 1s ago never 0 no error resolve-identity-1050 2m10s ago never 0 no error resolve-identity-671 2m11s ago never 0 no error sync-endpoints-and-host-ips 11s ago never 0 no error sync-lb-maps-with-k8s-services 2m11s ago never 0 no error sync-policymap-1050 1m1s ago never 0 no error sync-policymap-671 9s ago never 0 no error sync-to-k8s-ciliumendpoint (1050) 10s ago never 0 no error sync-to-k8s-ciliumendpoint (671) 1s ago never 0 no error template-dir-watcher never never 0 no error Proxy Status: OK, ip 10.32.3.145, 0 redirects active on ports 10000-20000 Global Identity Range: min 256, max 65535 Hubble: Ok Current/Max Flows: 250/4095 (6.11%), Flows/s: 1.74 Metrics: Disabled KubeProxyReplacement Details: Status: Disabled Socket LB: Disabled Session Affinity: Disabled Graceful Termination: Enabled NAT46/64 Support: Disabled Services: - ClusterIP: Enabled - NodePort: Disabled - LoadBalancer: Disabled - externalIPs: Disabled - HostPort: Disabled BPF Maps: dynamic sizing: on (ratio: 0.002500) Name Size Non-TCP connection tracking 73621 TCP connection tracking 147243 Endpoint policy 65535 Events 16 IP cache 512000 IP masquerading agent 16384 IPv4 fragmentation 8192 IPv4 service 65536 IPv6 service 65536 IPv4 service backend 65536 IPv6 service backend 65536 IPv4 service reverse NAT 65536 IPv6 service reverse NAT 65536 Metrics 1024 NAT 147243 Neighbor table 147243 Global policy 16384 Per endpoint policy 65536 Session affinity 65536 Signal 16 Sockmap 65535 Sock reverse NAT 73621 Tunnel 65536 Encryption: Disabled Cluster health: 6/6 reachable (2022-12-08T09:03:25Z) Name IP Node Endpoints k8s-cilium-master-10-31-80-1.tinychen.io (localhost) 10.31.80.1 reachable reachable k8s-cilium-master-10-31-80-2.tinychen.io 10.31.80.2 reachable reachable k8s-cilium-master-10-31-80-3.tinychen.io 10.31.80.3 reachable reachable k8s-cilium-worker-10-31-80-4.tinychen.io 10.31.80.4 reachable reachable k8s-cilium-worker-10-31-80-5.tinychen.io 10.31.80.5 reachable reachable k8s-cilium-worker-10-31-80-6.tinychen.io 10.31.80.6 reachable reachable 其实到这里cilium的部署就可以说是ok了的，整个集群的cni都处于正常状态，其余的工作负载也都能够正常运行了。\n部署hubble cilium还有一大特点就是其可观测性比其他的cni要优秀很多，想要体验到cilium的可观测性，我们就需要在k8s集群中安装hubble。同时hubble提供了ui界面来更好的实现集群内网络的可观测性，这里我们也一并把hubble-ui安装上。\nhelm3安装hubble 我们继续接着上面的helm3来安装hubble，因为我们已经安装了cilium，因此这里需要使用upgrade来进行升级安装，并且使用--reuse-values来复用之前的安装参数\nhelm upgrade cilium cilium/cilium --version 1.12.4 \\ --namespace kube-system \\ --reuse-values \\ --set hubble.relay.enabled=true \\ --set hubble.ui.enabled=true 然后我们直接进行安装\n$ helm upgrade cilium cilium/cilium --version 1.12.4 \\ \u0026gt; --namespace kube-system \\ \u0026gt; --reuse-values \\ \u0026gt; --set hubble.relay.enabled=true \\ \u0026gt; --set hubble.ui.enabled=true Release \u0026#34;cilium\u0026#34; has been upgraded. Happy Helming! NAME: cilium LAST DEPLOYED: Thu Dec 8 17:06:51 2022 NAMESPACE: kube-system STATUS: deployed REVISION: 2 TEST SUITE: None NOTES: You have successfully installed Cilium with Hubble Relay and Hubble UI. Your release version is 1.12.4. For any further help, visit https://docs.cilium.io/en/v1.12/gettinghelp 随后我们查看相关的集群状态，可以看到相对应的pod、deploy和svc都工作正常\n$ kubectl get pod -A | grep hubble kube-system hubble-relay-67ffc5f588-qr8nt 1/1 Running 0 56s kube-system hubble-ui-5dc4d884b6-84qlp 2/2 Running 0 56s $ kubectl get deploy -A | grep hubble kube-system hubble-relay 1/1 1 1 3m12s kube-system hubble-ui 1/1 1 1 3m12s $ kubectl get svc -A | grep hubble kube-system hubble-peer ClusterIP 10.32.131.127 \u0026lt;none\u0026gt; 443/TCP 9m3s kube-system hubble-relay ClusterIP 10.32.171.0 \u0026lt;none\u0026gt; 80/TCP 3m22s kube-system hubble-ui ClusterIP 10.32.184.206 \u0026lt;none\u0026gt; 80/TCP 3m22s cilium-cli安装hubble 使用cilium-cli功能来安装hubble也非常简单：\n# 首先安装cilium-cli工具 # cilium的cli工具是一个二进制的可执行文件 $ curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz{,.sha256sum} $ sha256sum --check cilium-linux-amd64.tar.gz.sha256sum cilium-linux-amd64.tar.gz: OK $ sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin cilium # 然后直接启用hubble $ cilium hubble enable # 再启用hubble-ui $ cilium hubble enable --ui # 接着查看cilium状态 $ cilium status /¯¯\\ /¯¯\\__/¯¯\\ Cilium: OK \\__/¯¯\\__/ Operator: OK /¯¯\\__/¯¯\\ Hubble: OK \\__/¯¯\\__/ ClusterMesh: disabled \\__/ Deployment hubble-ui Desired: 1, Ready: 1/1, Available: 1/1 DaemonSet cilium Desired: 6, Ready: 6/6, Available: 6/6 Deployment hubble-relay Desired: 1, Ready: 1/1, Available: 1/1 Deployment cilium-operator Desired: 2, Ready: 2/2, Available: 2/2 Containers: cilium Running: 6 hubble-relay Running: 1 cilium-operator Running: 2 hubble-ui Running: 1 Cluster Pods: 4/4 managed by Cilium Image versions cilium-operator quay.io/cilium/operator-generic:v1.12.4@sha256:071089ec5bca1f556afb8e541d9972a0dfb09d1e25504ae642ced021ecbedbd1: 2 hubble-ui quay.io/cilium/hubble-ui-backend:v0.9.2@sha256:a3ac4d5b87889c9f7cc6323e86d3126b0d382933bd64f44382a92778b0cde5d7: 1 hubble-ui quay.io/cilium/hubble-ui:v0.9.2@sha256:d3596efc94a41c6b772b9afe6fe47c17417658956e04c3e2a28d293f2670663e: 1 cilium quay.io/cilium/cilium:v1.12.4@sha256:4b074fcfba9325c18e97569ed1988464309a5ebf64bbc79bec6f3d58cafcb8cf: 6 hubble-relay quay.io/cilium/hubble-relay:v1.12.4@sha256:dc5b396e94f986f83ccef89f13a91c29df482d4af491ff3bd4d40c05873d351a: 1 安装hubble客户端 和cilium一样，hubble也提供了一个客户端来让我们操作\n# 首先我们需要安装hubble的客户端，安装原理和过程与安装cilium几乎一致 $ export HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt) $ curl -L --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-amd64.tar.gz{,.sha256sum} $ sha256sum --check hubble-linux-amd64.tar.gz.sha256sum $ sudo tar xzvfC hubble-linux-amd64.tar.gz /usr/local/bin $ rm hubble-linux-amd64.tar.gz{,.sha256sum} $ hubble --version hubble v0.10.0 然后我们需要暴露hubble api服务的端口，直接使用kubectl的port-forward功能把hubble-relay这个服务的80端口暴露到4245端口上\n# 仅暴露在IPV4网络中 $ kubectl port-forward -n kube-system svc/hubble-relay --address 0.0.0.0 4245:80 \u0026amp; # 同时暴露在IPV6和IPV4网络中 $ kubectl port-forward -n kube-system svc/hubble-relay --address 0.0.0.0 --address :: 4245:80 \u0026amp; 如果使用cilium-cli工具安装的hubble也可以使用cilium暴露api端口，需要注意的是该命令默认会暴露到IPV6和IPV4网络中，如果宿主机节点不支持ipv6网络会报错\n$ cilium hubble port-forward\u0026amp; api端口暴露完成之后我们就可以测试一下hubble客户端的工作状态是否正常\n$ hubble status Handling connection for 4245 Healthcheck (via localhost:4245): Ok Current/Max Flows: 10,903/12,285 (88.75%) Flows/s: 5.98 Connected Nodes: 3/3 这里需要注意如果发现hubble的状态不正常，查看日志发现\n$ kubectl logs -f hubble-relay-67ffc5f588-qr8nt -n kube-system level=warning msg=\u0026#34;Failed to create peer client for peers synchronization; will try again after the timeout has expired\u0026#34; error=\u0026#34;context deadline exceeded\u0026#34; subsys=hubble-relay target=\u0026#34;hubble-peer.kube-system.svc.cluster.local:443\u0026#34; 主要是因为前面初始化的时候我们定义了集群名为cili-cluster.tclocal，因此集群中coredns的配置没有cluster.local的解析，我们手动增加一个即可。\n$ kubectl get cm coredns -n kube-system -o yaml apiVersion: v1 data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cili-cluster.tclocal in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance } cluster.local.:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 cache 30 loop reload loadbalance } kind: ConfigMap metadata: creationTimestamp: \u0026#34;2022-12-08T08:14:37Z\u0026#34; name: coredns namespace: kube-system resourceVersion: \u0026#34;11835\u0026#34; uid: fbdcef03-87a6-4ddb-b620-c55a17c0d7d7 暴露hubble-ui 官方介绍里面是使用cilium工具直接暴露hubble-ui的访问端口到宿主机上面的12000端口\n# 将hubble-ui这个服务的80端口暴露到宿主机上面的12000端口上面 $ cilium hubble ui\u0026amp; [2] 5809 ℹ️ Opening \u0026#34;http://localhost:12000\u0026#34; in your browser... 实际上执行的操作等同于下面这个命令\n# 同时暴露在IPV6和IPV4网络中 # kubectl port-forward -n kube-system svc/hubble-ui --address 0.0.0.0 --address :: 12000:80 # 仅暴露在IPV4网络中 # kubectl port-forward -n kube-system svc/hubble-ui --address 0.0.0.0 12000:80 这里我们使用nodeport的方式来暴露hubble-ui，首先我们查看原来的hubble-ui这个svc的配置\n$ kubectl get svc -n kube-system hubble-ui -o yaml ...此处略去一堆输出... - name: http port: 80 protocol: TCP targetPort: 8081 selector: k8s-app: hubble-ui sessionAffinity: None type: ClusterIP ...此处略去一堆输出... 我们把默认的ClusterIP修改为NodePort，并且指定端口为nodePort: 30081\n$ kubectl get svc -n kube-system hubble-ui -o yaml ...此处略去一堆输出... ports: - name: http nodePort: 30081 port: 80 protocol: TCP targetPort: 8081 selector: k8s-app: hubble-ui sessionAffinity: None type: NodePort ...此处略去一堆输出... 修改前后对比查看状态\n# 修改前，使用ClusterIP $ kubectl get svc -A | grep hubble-ui kube-system hubble-ui ClusterIP 10.32.184.206 \u0026lt;none\u0026gt; 80/TCP 82s # 修改后，使用NodePort $ kubectl get svc -A | grep hubble-ui kube-system hubble-ui NodePort 10.32.184.206 \u0026lt;none\u0026gt; 80:30081/TCP 13m 这时候我们在浏览器中访问http://10.31.80.1:30081就可以看到hubble的ui界面了\n配置BGP路由 使用bird2 cilium官方给出的基于bird宣告BGP路由的架构图如下：\n这里假设的是存在两个核心路由器，实际上根据网络环境的不同可以动态变化（可以是多个路由器或者是能跑BGP路由的三层交换机），在下面的测试环境中我们只使用一个openwrt来充当路由器的角色 bird不从核心路由器和其他节点学习路由，这使得每个节点的内核路由表保持干净和小，并且没有性能问题（这里的性能问题指的是bird的性能问题）。 在这个方案中，每个节点只是将 pod 出口流量发送到节点的默认网关（核心路由器），并让后者进行路由。 上述的这个方案最大的特点就是每个node节点都会把整个pod的CIDR发布到对端路由器上，并不会精确控制每个node的路由条目。\n好处是只要还有一个node的BGP连接正常，那么集群外部就能够访问所有node节点上面的pod；坏处就是集群外部访问pod的流量不一定会直接转发到对应pod所在的node上面，很可能会转发到其他的node上面，再通过这个node上面的具体路由走隧道转发到对应的pod上。\n# 首先使用yum安装bird2 $ yum install bird2 -y # 如果没有bird2的话可以先添加epel源 $ yum install epel-release -y # 配置好开机启动 $ systemctl enable bird $ systemctl restart bird $ birdc show route BIRD 2.0.10 ready. bird的配置文件也是相对比较简单，下面是其中一台机器的配置，这里10.31.80.1是本机IP，64515是cilium集群的ASN号，而10.31.254.253是路由器的IP，64512是路由器端的ASN号。\n注意这里的配置开启了BFD、ECMP和graceful restart，更详细的高级配置可以参考cilium的官方文档。\nlog syslog all; router id 10.31.80.1; protocol device { scan time 10; # Scan interfaces every 10 seconds } # Disable automatically generating direct routes to all network interfaces. protocol direct { disabled; # Disable by default } # Forbid synchronizing BIRD routing tables with the OS kernel. protocol kernel { ipv4 { # Connect protocol to IPv4 table by channel import none; # Import to table, default is import all export none; # Export to protocol. default is export none }; # Configure ECMP merge paths yes limit 16 ; } # Static IPv4 routes. protocol static { ipv4; route 10.32.0.0/17 via \u0026#34;cilium_host\u0026#34;; } protocol bfd { interface \u0026#34;{{ grains[\u0026#39;node_mgnt_device\u0026#39;] }}\u0026#34; { min rx interval 100 ms; min tx interval 100 ms; idle tx interval 300 ms; multiplier 10; }; neighbor 10.31.254.253; } # BGP peers protocol bgp uplink0 { description \u0026#34;OpenWRT BGP uplink 0\u0026#34;; local 10.31.80.1 as 64515; neighbor 10.31.254.253 as 64512; graceful restart; bfd on; ipv4 { import filter {reject;}; export filter {accept;}; }; } 六个机器都成功配置之后我们在路由器侧应该是可以看到类似的路由条目，此时在集群外的机器上面如果添加了对应pod网段的路由的话，是可以通过pod IP直接访问到集群内的pod。\n# 查看路由器上面的状态 tiny-openwrt# show ip route ...... B\u0026gt;* 10.32.0.0/17 [20/0] via 10.31.80.1, eth0, weight 1, 00:00:12 * via 10.31.80.2, eth0, weight 1, 00:00:12 * via 10.31.80.3, eth0, weight 1, 00:00:12 * via 10.31.80.4, eth0, weight 1, 00:00:12 * via 10.31.80.5, eth0, weight 1, 00:00:12 * via 10.31.80.6, eth0, weight 1, 00:00:12 ...... # 查看对应node上面的bird状态 [root@k8s-cilium-master-10-31-80-1 ~]# birdc BIRD 2.0.10 ready. bird\u0026gt; show route Table master4: 10.32.0.0/17 unicast [static1 15:33:26.703] * (200) dev cilium_host bird\u0026gt; 使用kube-router 配置kube-router curl -LO https://raw.githubusercontent.com/cloudnativelabs/kube-router/v1.2/daemonset/generic-kuberouter-only-advertise-routes.yaml 默认的arg参数如下，完整的配置参考官方文档\nargs: - \u0026#34;--run-router=true\u0026#34; - \u0026#34;--run-firewall=false\u0026#34; - \u0026#34;--run-service-proxy=false\u0026#34; - \u0026#34;--bgp-graceful-restart=true\u0026#34; - \u0026#34;--enable-cni=false\u0026#34; - \u0026#34;--enable-ibgp=false\u0026#34; - \u0026#34;--enable-overlay=false\u0026#34; - \u0026#34;--peer-router-ips=\u0026lt;CHANGE ME\u0026gt;\u0026#34; - \u0026#34;--peer-router-asns=\u0026lt;CHANGE ME\u0026gt;\u0026#34; - \u0026#34;--cluster-asn=\u0026lt;CHANGE ME\u0026gt;\u0026#34; - \u0026#34;--advertise-cluster-ip=true\u0026#34; - \u0026#34;--advertise-external-ip=true\u0026#34; - \u0026#34;--advertise-loadbalancer-ip=true\u0026#34; 我们需要对其进行修改，官方表示下面的这些参数必须要和要求的一致：\n- \u0026#34;--run-router=true\u0026#34; - \u0026#34;--run-firewall=false\u0026#34; - \u0026#34;--run-service-proxy=false\u0026#34; - \u0026#34;--enable-cni=false\u0026#34; - \u0026#34;--enable-pod-egress=false\u0026#34; 这些参数建议一致：\n- \u0026#34;--enable-ibgp=true\u0026#34; - \u0026#34;--enable-overlay=true\u0026#34; - \u0026#34;--advertise-cluster-ip=true\u0026#34; - \u0026#34;--advertise-external-ip=true\u0026#34; - \u0026#34;--advertise-loadbalancer-ip=true\u0026#34; 最后剩下的参数就是根据实际的网络状态来进行配置\n- \u0026#34;--peer-router-ips=10.31.254.253\u0026#34; - \u0026#34;--peer-router-asns=64512\u0026#34; - \u0026#34;--cluster-asn=64515\u0026#34; 如果需要同时和多个BGP peer建立连接可以参考官方的这个配置\n- \u0026#34;--cluster-asn=65001\u0026#34; - \u0026#34;--peer-router-ips=10.0.0.1,10.0.2\u0026#34; - \u0026#34;--peer-router-asns=65000,65000\u0026#34; 最后我这里使用的参数如下\n- \u0026#34;--run-router=true\u0026#34; - \u0026#34;--run-firewall=false\u0026#34; - \u0026#34;--run-service-proxy=false\u0026#34; - \u0026#34;--enable-cni=false\u0026#34; - \u0026#34;--enable-pod-egress=false\u0026#34; - \u0026#34;--enable-ibgp=true\u0026#34; - \u0026#34;--enable-overlay=true\u0026#34; - \u0026#34;--advertise-cluster-ip=true\u0026#34; - \u0026#34;--advertise-external-ip=true\u0026#34; - \u0026#34;--advertise-loadbalancer-ip=true\u0026#34; - \u0026#34;--bgp-graceful-restart=true\u0026#34; - \u0026#34;--peer-router-ips=10.31.254.253\u0026#34; - \u0026#34;--peer-router-asns=64512\u0026#34; - \u0026#34;--cluster-asn=64515\u0026#34; 注意在官方文档中说明了\u0026ndash;advertise-pod-cidr这个参数才是真正的把pod对应的CIDR发布出去的，但是由于这个值默认是true，因此我们不需要在这里进行显性配置。\n\u0026ndash;advertise-pod-cidr Add Node\u0026rsquo;s POD cidr to the RIB so that it gets advertised to the BGP peers. (default true)\n配置完成之后我们直接进行部署\n# 直接使用kubectl 进行部署 $ kubectl apply -f generic-kuberouter-only-advertise-routes.yaml daemonset.apps/kube-router created serviceaccount/kube-router created clusterrole.rbac.authorization.k8s.io/kube-router created clusterrolebinding.rbac.authorization.k8s.io/kube-router created # 最后我们检查一下pod的运行状态 $ kubectl -n kube-system get pods -l k8s-app=kube-router NAME READY STATUS RESTARTS AGE kube-router-498zv 1/1 Running 0 78s kube-router-8jm6b 1/1 Running 0 78s kube-router-cknvc 1/1 Running 0 78s kube-router-hgglx 1/1 Running 0 78s kube-router-p5tks 1/1 Running 0 78s kube-router-rjdbh 1/1 Running 0 78s 配置cilium 随后我们还需要修改cilium的配置：\n将ipam修改为kubernetes，因为kube-router是从k8s直接获取CIDR信息的 将tunnel修改为disabled，因为可以通过kube-router获取路由信息直接路由到对应的节点上，就不需要再进行IP隧道/封装了 # 默认情况下的值 $ kubectl get cm -n kube-system cilium-config -o yaml | egrep \u0026#34;tunnel|ipam\u0026#34; ipam: cluster-pool tunnel: vxlan # 修改前的路由条目 $ ip r default via 10.31.254.253 dev eth0 proto static metric 100 10.31.0.0/16 dev eth0 proto kernel scope link src 10.31.80.1 metric 100 10.32.0.0/24 via 10.32.3.145 dev cilium_host src 10.32.3.145 mtu 1450 10.32.1.0/24 via 10.32.3.145 dev cilium_host src 10.32.3.145 mtu 1450 10.32.2.0/24 via 10.32.3.145 dev cilium_host src 10.32.3.145 mtu 1450 10.32.3.0/24 via 10.32.3.145 dev cilium_host src 10.32.3.145 10.32.3.145 dev cilium_host scope link 10.32.4.0/24 via 10.32.3.145 dev cilium_host src 10.32.3.145 mtu 1450 10.32.5.0/24 via 10.32.3.145 dev cilium_host src 10.32.3.145 mtu 1450 # 修改后的值 $ kubectl get cm -n kube-system cilium-config -o yaml | egrep \u0026#34;tunnel|ipam\u0026#34; ipam: kubernetes tunnel: disabled # 修改后的路由条目 $ ip r default via 10.31.254.253 dev eth0 proto static metric 100 10.31.0.0/16 dev eth0 proto kernel scope link src 10.31.80.1 metric 100 10.32.0.0/24 via 10.32.3.145 dev cilium_host src 10.32.3.145 mtu 1450 10.32.1.0/24 via 10.31.80.2 dev eth0 proto 17 10.32.2.0/24 via 10.31.80.3 dev eth0 proto 17 10.32.3.0/24 via 10.31.80.4 dev eth0 proto 17 10.32.3.145 dev cilium_host scope link 10.32.4.0/24 via 10.31.80.5 dev eth0 proto 17 10.32.5.0/24 via 10.31.80.6 dev eth0 proto 17 需要注意的是如果cilium-config配置里面没有ipv4-native-routing-cidr这个参数的话也需要加上，配置为pod的CIDR即可（ipv4-native-routing-cidr: 10.32.0.0/17），否则在重启cilium的时候会出现下面这个报错，关于Native-Routing的配置和原理可以参考官方的这个文档\nlevel=fatal msg=\u0026ldquo;Error while creating daemon\u0026rdquo; error=\u0026ldquo;invalid daemon configuration: native routing cidr must be configured with option \u0026ndash;ipv4-native-routing-cidr in combination with \u0026ndash;enable-ipv4-masquerade \u0026ndash;tunnel=disabled \u0026ndash;ipam=kubernetes \u0026ndash;enable-ipv4=true\u0026rdquo; subsys=daemon\n可以明显的看到修改之后的路由条目默认情况下都不再通过封装的隧道接口cilium_host，而是直接通过kube-router发布的BGP路由直达对应的node节点上面。\n部署loadbalancer 因为这里我们没有使用cilium的withoutkubeproxy模式，因此有部分cilium的高级功能无法使用，尽管我们前面已经把clusterIP和podIP都通过BGP宣告出去，在部分场景下面还是需要有LoadBalancer类型的服务作为补充。\n目前市面上开源的K8S-LoadBalancer主要就是MetalLB、OpenELB和PureLB这三种，三者的工作原理和使用教程我都写文章分析过，针对目前这种使用场景，我个人认为最合适的是使用PureLB，因为他的组件高度模块化，并且可以自由选择实现ECMP模式的路由协议和软件（MetalLB和OpenELB都是自己通过gobgp实现的BGP协议），能更好的和我们前面的cilium+kube-router组合在一起。\n原理架构 关于purelb的详细工作原理和部署使用方式可以参考我之前写的这篇文章，这里不再赘述。\nAllocator：用来监听API中的LoadBalancer类型服务，并且负责分配IP。 LBnodeagent： 作为daemonset部署到每个可以暴露请求并吸引流量的节点上，并且负责监听服务的状态变化同时负责把VIP添加到本地网卡或者是虚拟网卡 KubeProxy：k8s的内置组件，并非是PureLB的一部分，但是PureLB依赖其进行正常工作，当对VIP的请求达到某个具体的节点之后，需要由kube-proxy来负责将其转发到对应的pod 因为我们此前已经部署了kube-router，并且会由它来负责BGP宣告的相关操作，因此在这里我们直接使用purelb的BGP模式，并且不需要自己再额外部署bird或frr来进行BGP路由发布，同时也不需要LBnodeagent组件来帮助暴露并吸引流量，只需要Allocator帮助我们完成LoadBalancerIP的分配操作即可。\n部署purelb # 下载官方提供的yaml文件到本地进行部署 $ wget https://gitlab.com/api/v4/projects/purelb%2Fpurelb/packages/generic/manifest/0.0.1/purelb-complete.yaml # 请注意，由于 Kubernetes 的最终一致性架构，此manifest清单的第一个应用程序可能会失败。发生这种情况是因为清单既定义了CRD，又使用该CRD创建了资源。如果发生这种情况，请再次应用manifest清单，应该就会部署成功。 $ kubectl apply -f purelb-complete.yaml $ kubectl apply -f purelb-complete.yaml # 检测部署后的各个资源及工作负载是否正常 $ kubectl get pods -n purelb -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES allocator-8657d47b5c-rdd46 1/1 Running 0 71s 10.32.3.149 k8s-cilium-worker-10-31-80-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; lbnodeagent-hnffq 1/1 Running 0 71s 10.31.80.4 k8s-cilium-worker-10-31-80-4.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; lbnodeagent-jzs8s 1/1 Running 0 71s 10.31.80.6 k8s-cilium-worker-10-31-80-6.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; lbnodeagent-qr4cb 1/1 Running 0 71s 10.31.80.5 k8s-cilium-worker-10-31-80-5.tinychen.io \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; $ kubectl get deploy -n purelb NAME READY UP-TO-DATE AVAILABLE AGE allocator 1/1 1 1 86s $ kubectl get crd | grep purelb lbnodeagents.purelb.io 2022-12-09T12:38:13Z servicegroups.purelb.io 2022-12-09T12:38:13Z $ kubectl get --namespace=purelb servicegroups.purelb.io No resources found in purelb namespace. $ kubectl get --namespace=purelb lbnodeagent.purelb.io NAME AGE default 89s # 查看创建的相关API资源，注意这里的lbnodeagents我们此处用不到，可以忽略 $ kubectl api-resources --api-group=purelb.io NAME SHORTNAMES APIVERSION NAMESPACED KIND lbnodeagents lbna,lbnas purelb.io/v1 true LBNodeAgent servicegroups sg,sgs purelb.io/v1 true ServiceGroup lbnodeagent的这个ds我们这里用不到，因此可以直接删除。\n$ kubectl delete ds -n purelb lbnodeagent daemonset.apps \u0026#34;lbnodeagent\u0026#34; deleted $ kubectl get ds -n purelb No resources found in purelb namespace. 配置IP池 接下来我们部署一个ipam的sg，命名为bgp-ippool，ip段就使用我们预留的10.32.192.0/18\n$ cat purelb-ipam.yaml apiVersion: purelb.io/v1 kind: ServiceGroup metadata: name: bgp-ippool namespace: purelb spec: local: v4pool: subnet: \u0026#39;10.32.192.0/18\u0026#39; pool: \u0026#39;10.32.192.0-10.32.255.254\u0026#39; aggregation: /32 $ kubectl apply -f purelb-ipam.yaml servicegroup.purelb.io/bgp-ippool created $ kubectl get sg -n purelb NAME AGE bgp-ippool 8s 到这里我们的PureLB就部署完了，相比完整的ECMP模式要少部署了路由协议软件和额外删除了lbnodeagent，接下来可以开始测试了。\n部署测试用例 集群部署完成之后我们在k8s集群中部署一个nginx测试一下是否能够正常工作。首先我们创建一个名为nginx-quic的命名空间（namespace），然后在这个命名空间内创建一个名为nginx-quic-deployment的deployment用来部署pod，最后再创建一个service用来暴露服务，这里我们同时使用nodeport和LoadBalancer两种方式来暴露服务，并且其中一个LoadBalancer的服务还要指定LoadBalancerIP方便我们测试。\n$ cat nginx-quic.yaml apiVersion: v1 kind: Namespace metadata: name: nginx-quic --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-quic-deployment namespace: nginx-quic spec: selector: matchLabels: app: nginx-quic replicas: 4 template: metadata: labels: app: nginx-quic spec: containers: - name: nginx-quic image: tinychen777/nginx-quic:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-quic-service namespace: nginx-quic spec: externalTrafficPolicy: Cluster selector: app: nginx-quic ports: - protocol: TCP port: 8080 # match for service access port targetPort: 80 # match for pod access port nodePort: 30088 # match for external access port type: NodePort --- apiVersion: v1 kind: Service metadata: annotations: purelb.io/service-group: bgp-ippool name: nginx-lb-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-quic ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer loadBalancerIP: 10.32.192.192 --- apiVersion: v1 kind: Service metadata: annotations: purelb.io/service-group: bgp-ippool name: nginx-lb2-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-quic ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer 部署完成后我们直接查看状态\n# 直接部署 $ kubectl apply -f nginx-quic.yaml namespace/nginx-quic created deployment.apps/nginx-quic-deployment created service/nginx-quic-service created # 查看deployment的运行状态 $ kubectl get deployment -o wide -n nginx-quic NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR nginx-quic-deployment 4/4 4 4 63m nginx-quic tinychen777/nginx-quic:latest app=nginx-quic # 查看service的运行状态 $ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.32.169.6 10.32.192.192 80/TCP 86m nginx-lb2-service LoadBalancer 10.32.172.208 10.32.192.0 80/TCP 8m52s nginx-quic-service NodePort 10.32.176.164 \u0026lt;none\u0026gt; 8080:30088/TCP 3h9m # 查看pod的运行状态 $ kubectl get pods -n nginx-quic NAME READY STATUS RESTARTS AGE nginx-quic-deployment-748867774b-75xrq 1/1 Running 0 3h14m nginx-quic-deployment-748867774b-pwqpg 1/1 Running 0 3h14m nginx-quic-deployment-748867774b-tm2p5 1/1 Running 0 3h14m nginx-quic-deployment-748867774b-tw86v 1/1 Running 0 3h14m # 查看IPVS规则 $ ipvsadm -lnt 10.32.176.164:8080 Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.32.176.164:8080 rr -\u0026gt; 10.32.3.138:80 Masq 1 0 0 -\u0026gt; 10.32.3.223:80 Masq 1 0 0 -\u0026gt; 10.32.4.80:80 Masq 1 0 0 -\u0026gt; 10.32.5.88:80 Masq 1 0 0 $ ipvsadm -lnt 10.31.80.1:30088 Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.31.80.1:30088 rr -\u0026gt; 10.32.3.138:80 Masq 1 0 0 -\u0026gt; 10.32.3.223:80 Masq 1 0 0 -\u0026gt; 10.32.4.80:80 Masq 1 0 0 -\u0026gt; 10.32.5.88:80 Masq 1 0 0 $ ipvsadm -lnt 10.32.192.192:80 Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.32.192.192:80 rr -\u0026gt; 10.32.3.138:80 Masq 1 0 0 -\u0026gt; 10.32.3.223:80 Masq 1 0 0 -\u0026gt; 10.32.4.80:80 Masq 1 0 0 -\u0026gt; 10.32.5.88:80 Masq 1 0 0 $ ipvsadm -lnt 10.32.192.0:80 Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.32.192.0:80 rr -\u0026gt; 10.32.3.138:80 Masq 1 0 0 -\u0026gt; 10.32.3.223:80 Masq 1 0 0 -\u0026gt; 10.32.4.80:80 Masq 1 0 0 -\u0026gt; 10.32.5.88:80 Masq 1 0 0 最后我们进行测试，这个nginx-quic的镜像默认情况下会返回在nginx容器中获得的用户请求的IP和端口，由于我们前面配置的时候把podIP和clusterIP都通过BGP发布出去了，因此我们在集群外直接访问podIP、clusterIP、nodeport和loadbalancerIP都可以成功访问。\n# root @ tiny-openwrt in ~ [22:17:46] $ curl 10.32.192.0 10.31.80.4:50151 # root @ tiny-openwrt in ~ [22:17:47] $ curl 10.32.192.192 10.31.80.3:1969 # root @ tiny-openwrt in ~ [22:17:50] $ curl 10.32.5.88:80 10.31.254.253:52160 # root @ tiny-openwrt in ~ [22:17:58] $ curl 10.32.176.164:8080 10.31.80.5:20972 # root @ tiny-openwrt in ~ [22:18:02] $ curl 10.31.80.4:30088 10.31.80.4:3220 最后我们检查一下路由器侧的情况，正常情况下可以看到kube-router发布的ECMP路由：\nB\u0026gt;* 10.32.192.0/32 [20/0] via 10.31.80.1, eth0, weight 1, 00:00:14 * via 10.31.80.2, eth0, weight 1, 00:00:14 * via 10.31.80.3, eth0, weight 1, 00:00:14 * via 10.31.80.4, eth0, weight 1, 00:00:14 * via 10.31.80.5, eth0, weight 1, 00:00:14 * via 10.31.80.6, eth0, weight 1, 00:00:14 B\u0026gt;* 10.32.192.192/32 [20/0] via 10.31.80.1, eth0, weight 1, 01:17:54 * via 10.31.80.2, eth0, weight 1, 01:17:54 * via 10.31.80.3, eth0, weight 1, 01:17:54 * via 10.31.80.4, eth0, weight 1, 01:17:54 * via 10.31.80.5, eth0, weight 1, 01:17:54 * via 10.31.80.6, eth0, weight 1, 01:17:54 到这里整个K8S集群就部署完成了。\n","permalink":"http://localhost:9011/20221209-k8s-10-deploy-ha-k8s-with-cilium-bgp/","summary":"\u003cp\u003e本文主要在centos7系统上基于\u003ccode\u003econtainerd\u003c/code\u003e和\u003ccode\u003estable\u003c/code\u003e版本（\u003ccode\u003e1.12.4\u003c/code\u003e）的\u003ccode\u003ecilium\u003c/code\u003e组件部署\u003ccode\u003ev1.25.4\u003c/code\u003e版本的堆叠ETCD高可用k8s原生集群，在\u003ccode\u003eLoadBalancer\u003c/code\u003e上选择了\u003ccode\u003ePureLB\u003c/code\u003e和\u003ccode\u003ekube-router\u003c/code\u003e结合\u003ccode\u003ecilium\u003c/code\u003e实现\u003cstrong\u003eBGP路由可达\u003c/strong\u003e的K8S集群部署。\u003c/p\u003e\n\u003cp\u003e此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列10-使用kube-router和cilium部署BGP模式的k8s集群"},{"content":"本文主要介绍了目前CoreDNS服务在外部域名递归结果过程中出现的一些问题以及使用dnsredir插件进行分流和alternate插件进行重试优化的操作。\n自建DNS服务现状 一般来说，无论是bind9、coredns、dnsmasq、pdns哪类dns服务器，我们自建的监听在UDP53端口上的DNS服务在DNS解析功能方面承担着两个角色：分别为权威域名服务器和递归域名服务器。\n权威域名服务器主要为我们内部使用的域名提供服务，为了避免和提供给外部用户访问的域名冲突，这类内网域名一般会使用如local域、internal域，或者是和自身业务无关的org域、io域等后缀； 递归域名服务器主要是正常的外部域名解析服务，如163.com、baidu.com、google.com这一类常用的外网域名等； DNS解析逻辑 针对我们自建的权威域名服务器，解析的结果非常的确定，当服务器中存在这条记录就能正常解析，否则就是异常。但是对于递归域名服务器的工作流程来说，有些特殊的域名解析就会出现问题。\n我们以下图为例介绍在递归解析过程中可能会出现的问题：\n因为主要是分析到DNS服务器的解析流程，因此这里的第一二步直接跳过；\n图中的本地DNS服务器可以视为我们的CoreDNS服务器；\n正常情况下，我们的CoreDNS服务器和根域名服务器之间的连接是正常的，也就是说④⑤这两步正常情况下是能顺利返回结果的；\n那这里出现异常的情况主要就是请求了一些非法的不合规域名，比如请求tinychen.comillegal这种奇怪的域名，它的顶级域名是.comillegal，如果这个顶级域名是不存在的，那么DNS解析就会返回异常，无法解析；\n正常情况下，⑥⑦这两步是由本地DNS服务器去访问顶级域名服务器的IP来获取对应域名的权威域名服务器，一般常用的顶级域名如.com 、.cn这类顶级域名的服务器IP肯定是能正常连上的，但是一些小众的顶级域名就不一定能正常连接了，这类比较常见的是一些小国家的自有顶级域名，详细列表可以参考维基百科；\n最后就是权威域名服务器，这里也是最容易出问题的地方；\n正常情况下，本地DNS服务器从顶级域名服务器里面拿到权威域名服务器的IP之后就会去权威域名服务器这里获取到最后的域名解析结果，但是这里容易出现两个问题：\n本地DNS服务器和权威域名服务器之间的连接容易出现问题，权威域名服务器一般是各个域名使用者自己维护或者是使用一些DNS服务商提供的服务器，这些服务器出现无法连接或者是崩溃的概率要远大于前面的根域名服务器和顶级域名服务器 权威域名服务器返回的结果不一定能够正确的传送到本地DNS服务器中，大部分情况下DNS查询并不是加密的，使用明文的UDP进行查询，是比较容易被中间的运营商进行劫持，这里也是DNS污染常见的操作范围； 还有一种常见的DNS污染手段就是市面上的免费公共DNS服务器提供者针对某个特殊域名的解析进行修改，使得用户在使用这些免费的公共DNS解析时没办法解析到正常的IP从而导致该域名提供的服务异常\n总结上面的流程分析，我们自建DNS服务在进行外部域名递归解析的时候就可能会遇到下面的几类域名：\n解析正常，一般是国内的主流域名； 因为顶级域名服务器或者权威域名服务器无法正常连接导致无法正常解析，一般为海外域名； 因为DNS污染导致虽然能进行解析，但是解析结果和实际情况大相径庭，这类域名比较复杂，各种情况都有； CoreDNS解析逻辑 得益于CoreDNS丰富多样的插件，我们可以使用插件来对DNS的解析流程进行优化，分流不同的域名到不同的服务器，同时还可以针对不同的返回码进行重试。下面介绍一个对CoreDNS进行优化，加入了DNS解析分流功能和DNS解析失败重试功能来补充各种使用场景的架构。\n插件分析 首先是使用hosts插件，这个插件相当于在CoreDNS上面实现了我们修改服务器/etc/hosts文件的效果，可以用于对一些域名进行简单的劫持，例如一些域名想要拉黑，可以在里面配置解析为127.0.0.1（家庭网络屏蔽广告域名的常用手段）；又或者是有部分域名同时有内外网多个入口的，在机房内网DNS解析直接劫持为内网IP，节省外网流量等。需要注意的是hosts插件仅支持A记录的修改，一些复杂的场景如CNAME记录、MX记录、TXT记录等则无能为力了。\n接下来就是重头戏dnsredir插件和alternate插件了。其中dnsredir插件是github上面开源的一个第三方插件，alternate插件则是CoreDNS官网上的External Plugins，由CoreDNS维护；两者可靠性相对较高，有需求的同学也可以对其二次开发，关于CoreDNS编译外部插件的教程可以查看之前的文章。\n官方对alternate插件的介绍是一个基于DNS查询返回码RCODE来把DNS查询请求重定向的插件。举个例子，当我们向CoreDNS查询域名解析tinychen.com的时候，CoreDNS将查询转发给114.114.114.114，然后得到了NXDOMAIN的返回码，这时候一般就说明tinychen.com这个域名在114DNS是没有解析结果的，但是我们不死心，使用alternate插件把RCODE是NXDOMAIN的查询再次转发给8.8.8.8，这时候说不定就能得到域名的解析结果。\nalternate - allow redirecting queries to an alternate set of upstreams based on RCODE\n还是继续上面的场景，假设我们已经知道tinychen.com这个域名在114DNS是没办法查询到正常结果，而在8.8.8.8DNS能正常解析，我们能否直接去8.8.8.8查询呢？\n答案是肯定的。这时候就要用到我们的dnsredir插件了。它可以根据我们提供的域名列表，将不同的域名转发到不同的DNS服务器进行查询，从而达到DNS查询解析优化的效果，尤其是对应大部分海外域名解析，有条件的同学可以尝试将其转发到海外DNS节点解析，解析效果应该会有明显的提升。\n解析逻辑分析 alternate插件和dnsredir插件分别从响应码RCODE和域名两个维度对DNS解析进行分流/重定向，再结合CoreDNS本身配置的灵活性，可以有数种组合，这里只是提供一个示范案例作为参考。\n注意上图的插件每个的顺序都是可以调整并且不断递归查询，因此理论上可以进行无限的横向和纵向扩展用于满足日后的增长需求。\n首先还是针对递归查询的外部DNS域名，这里以.(root):53表示监听在53端口的根域名查询；\n进来的第一层匹配就是前面提及的最高优先级全局劫持名单查询，这一层主要是对恶意域名进行屏蔽过滤；\n当需要查询的域名不在全局劫持名单中的时候就会触发fallthrough条件，进入到下一层的dnsredir组件进行分流；\ndnsredir组件的主要功能就是根据我们配置的域名列表来进行转发，这里我们把域名分为三大类，分别进行不同的逻辑处理；\n对于列表里面的海外域名，我们将其转发到海外的DNS解析服务器进行解析，这里统称为海外DNS服务器，例如部分有条件的同学可以在海外线路的机房又或者是公有云的海外节点自建DNS服务器；\n当然在自建的海外线路节点DNS服务器也是会有可能碰到无法正常解析的，这时候就需要依赖alternate组件将请求二次转发到海外公共DNS服务器，比如一些海外的公共DNS服务器如8.8.8.8和1.1.1.1等；\n对于一些需要自己定义的域名，则再维护另外一份自定义域名列表和RFC1035格式的域名解析结果文件；\n这时候相当于再单独自建一个.(root)根域名给自定义域名使用，dnsredir组件将这些自定义的域名转发到这个自建的根域进行解析，正常情况下就会直接解析出自己定义的域名结果；\n如果自定义的域名列表和解析结果出现了偏差，导致部分域名在分流列表中缺不在解析结果中，会使得查询失败返回NXDOMAIN，这时候就需要依赖alternate组件将请求二次转发到正常的DNS解析流程\n最后就是正常的域名解析，优先使用本机的自建递归缓存服务器进行查询，当查询失败的时候可以转去国内的一些公共递归DNS如114或者是自建的海外DNS等进行补充查询\n得益于CoreDNS自身的灵活性，上述的全部插件逻辑可以随意进行组合分配递归调整用于适配不同的业务逻辑。\nQ\u0026amp;A 用于给dnsredir组件分流的域名列表格式？\ndnsredir组件使用的分流域名格式列表和dnsmasq的格式一致，格式参考如下：\nserver=/a1.mzstatic.com/114.114.114.114 server=/a2.mzstatic.com/114.114.114.114 server=/a3.mzstatic.com/114.114.114.114 server=/a4.mzstatic.com/114.114.114.114 server=/a5.mzstatic.com/114.114.114.114 server=/adcdownload.apple.com.akadns.net/114.114.114.114 server=/adcdownload.apple.com/114.114.114.114 server=/appldnld.apple.com/114.114.114.114 server=/appldnld.g.aaplimg.com/114.114.114.114 server=/appleid.cdn-apple.com/114.114.114.114 server=/apps.apple.com/114.114.114.114 server=/apps.mzstatic.com/114.114.114.114 server=/cdn-cn1.apple-mapkit.com/114.114.114.114 server=/cdn-cn2.apple-mapkit.com/114.114.114.114 server=/cdn-cn3.apple-mapkit.com/114.114.114.114 server=/cdn-cn4.apple-mapkit.com/114.114.114.114 需要注意的是dnsredir组件只会读取上述配置中的域名，而不会读取后面的DNS服务器IP，实际转发的DNS服务器IP在CoreDNS中的配置文件定义；\n为什么使用RFC1035格式的文本文件作为自定义域名的配置文件？\nCoreDNS本身支持多种外部后端存储方式（mysql、redis、etcd、pdsql等），使用RFC1035格式的文本文件主要是基于性能、稳定性和可维护性考量。\nCoreDNS会把文本文件的内容全部load到内存中，每次查询都是在内存中查询操作，性能最优，无需额外的网络IO消耗； 无需单独维护额外的数据库和中间件； 排除故障的时候可以直接查看文本文件定位问题； 因为文本文件在每台机器上面都有一份，因此单个CoreDNS实例出现故障不会影响其余的CoreDNS节点； RFC1035格式的文本文件的一个简单示例如下，更具体的操作可以查看bind9的官方文档 ","permalink":"http://localhost:9011/20221120-dns-13-coredns-10-dnsredir-and-alternate/","summary":"\u003cp\u003e本文主要介绍了目前CoreDNS服务在外部域名递归结果过程中出现的一些问题以及使用dnsredir插件进行分流和alternate插件进行重试优化的操作。\u003c/p\u003e","title":"CoreDNS篇10-分流与重定向"},{"content":"CoreDNS作为现阶段k8s的默认DNS服务以及服务发现的重要一环，其内置的kubernetes插件可谓是举足轻重。本文主要讲解介绍CoreDNS内置的核心插件kubernetes的使用方式和适用场景。\nCoreDNS的kubernetes插件的具体实现遵循k8s官方提供的标准指南Kubernetes DNS-Based Service Discovery Specification，这也是它能够替代kube-dns成为kubebernetes中默认的DNS的重要原因。\n虽然 Kubernetes 中的服务发现可以通过其他协议和机制提供（如consul等服务注册发现中心），但DNS是非常常用的一种协议，同时考虑到K8S中的东西流量互访主要也是通过域名实现，因此K8S官方非常推荐使用DNS插件来实现K8S中的服务发现。\nThis document is a specification for DNS-based Kubernetes service discovery. While service discovery in Kubernetes may be provided via other protocols and mechanisms, DNS is very commonly used and is a highly recommended add-on. The actual DNS service itself need not be provided by the default Kube-DNS implementation. This document is intended to provide a baseline for commonality between implementations.\n在开始介绍kubernetes插件之前，我们需要先了解一些K8S中的基础DNS知识。\nK8S中的DNS服务 众所周知，在K8S中，IP是随时会发生变化的，变化最频繁的就是Pod IP，Cluster IP也并不是一定不会发生变化，EXTERNAL-IP虽然可以手动指定静态IP保持不变，但是主要面向的是集群外部的服务；因此在K8S集群中，最好的服务之间相互访问的方式就是通过域名。\nDNS创建规则 在K8S集群中，Kubernetes 为 Service 和 Pod 创建 DNS 记录。\n前面我们介绍了K8S中的每个SVC都会有一个对应的域名，域名的组成格式为$service_name.$namespace_name.svc.$cluster_name，同时也会给这个SVC下的所有Pod都创建一个$pod_name.$service_name.$namespace_name.svc.$cluster_name的这种域名，这个域名的解析结果就是Pod IP。\nPod域名有两个比较明显的特征：\n一是域名的组成比较特殊，因为域名中使用了Pod的名称，而pod名称在K8S中是会发生变化的（例如在服务更新或者滚动重启时），同时由于默认情况下Pod的命名是没有太明显的规律（大部分名字中会包含一串随机UUID） 二是域名的解析结果特殊，相较于集群内的其他类型域名，Pod域名的解析是可以精确到特定的某个Pod，因此一些特殊的需要点对点通信的服务可以使用这类Pod域名 DNS策略配置 DNS 策略可以逐个 Pod 来设定。目前 Kubernetes 支持以下特定 Pod 的 DNS 策略。 这些策略可以在 Pod 规约中的 dnsPolicy 字段设置：\nDefault: Pod 从运行所在的K8S宿主机节点继承域名解析配置； ClusterFirst: 不指定任何dnsPolicy配置情况下的默认选项，所有查询的域名都会根据生成的集群的K8S域名等信息生成的 /etc/resolv.conf 配置进行解析和转发到集群内部的DNS服务进行解析； ClusterFirstWithHostNet：主要用于以 hostNetwork 方式运行的 Pod，如果这些pod想要使用K8S集群内的DNS服务，则可以配置为这个字段； None: 此设置允许 Pod 忽略 Kubernetes 环境中的 DNS 设置，Pod 会使用其 dnsConfig 字段 所配置的 DNS 设置； 说明： 下面主要介绍ClusterFirst模式\nDNS解析规则 DNS 查询参照 Pod 中的 /etc/resolv.conf 配置，kubelet 会为每个 Pod 生成此文件。因此在每个pod里面都有一个类似下面这样的 /etc/resolv.conf文件，通过修改其中的配置可以更改DNS的查询规则：\nnameserver 10.32.0.10 search \u0026lt;namespace\u0026gt;.svc.cluster.local svc.cluster.local cluster.local options ndots:5 这里的配置有几个需要注意的点：\nnameserver：集群中的DNS服务器IP，一般来说就是CoreDNS的ClusterIP search：需要搜索的域，默认情况下会从该pod所属的namespace开始逐级补充 options ndots：触发上面的search的域名点数，默认为1，上限15，在K8S中一般为5；例如在Linux中tinychen.com这个域名的ndots是1，tinychen.com.这个域名的ndots才是2（需要注意所有域名其实都有一个根域.，因此tinychen.com的全称应该是tinychen.com.） 这是一个比较通用的案例，我们再来看一个比较特殊的配置\n# 首先进入一个pod查看里面的DNS解析配置 [root@tiny-calico-master-88-1 tiny-calico]# kubectl exec -it -n ngx-system ngx-ex-deploy-6bf6c99d95-5qh2w /bin/bash kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. [root@ngx-ex-deploy-6bf6c99d95-5qh2w /]# cat /etc/resolv.conf nameserver 10.88.0.10 search ngx-system.svc.cali-cluster.tclocal svc.cali-cluster.tclocal cali-cluster.tclocal k8s.tcinternal options ndots:5 [root@ngx-ex-deploy-6bf6c99d95-5qh2w /]# exit 这个pod里面的/etc/resolv.conf配置文件有两个和前面不同的地方：\ncluster.local变成了cali-cluster.tclocal\n这里我们可以看到coredns的配置中就是配置的cali-cluster.tclocal，也就是说/etc/resolv.conf中的配置其实是和coredns中的配置一样，更准确的说是和该K8S集群初始化时配置的集群名一样\n# 再查看K8S集群中的coredns的configmap\t[root@tiny-calico-master-88-1 tiny-calico]# kubectl get configmaps -n kube-system coredns -oyaml apiVersion: v1 data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cali-cluster.tclocal in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . 10.31.100.100 { max_concurrent 1000 } cache 30 loop reload loadbalance } kind: ConfigMap metadata: creationTimestamp: \u0026#34;2022-05-06T05:19:08Z\u0026#34; name: coredns namespace: kube-system resourceVersion: \u0026#34;3986029\u0026#34; uid: 54f5f803-a5ab-4c77-b149-f02229bcad0a search新增了一个k8s.tcinternal\n实际上我们再查看K8S的宿主机节点的DNS配置规则时会发现这个k8s.tcinternal是从宿主机上面继承而来的\n# 最后查看宿主机节点上面的DNS解析配置 [root@tiny-calico-master-88-1 tiny-calico]# cat /etc/resolv.conf # Generated by NetworkManager search k8s.tcinternal nameserver 10.31.254.253 DNS解析流程 温馨提示：阅读这部分内容的时候要特别注意域名结尾是否有一个点号.\n当ndots小于options ndots 前面我们说过options ndots的值默认情况下是1，在K8S中为5，为了效果明显，我们这里使用K8S中的5作为示例：\n这里同样是在一个命名空间demo-ns中有两个SVC，分别为demo-svc1和demo-svc2，那么他们的/etc/resolv.conf应该是下面这样的：\nnameserver 10.32.0.10 search demo-ns.svc.cluster.local svc.cluster.local cluster.local options ndots:5 我们在demo-svc1中直接请求域名demo-svc2，此时ndots为1，小于配置中的5，因此会触发上面的search规则，这时第一个解析的域名就是demo-svc2.demo-ns.svc.cluster.local，当解析不出来的时候继续下面的demo-svc2.svc.cluster.local、demo-svc2.cluster.local，最后才是直接去解析demo-svc2.。\n注意上面的规则适用于任何一个域名，也就是当我们试图在pod中去访问一个外部域名如tinychen.com的时候也会依次进行上述查询。\n当ndots大于等于options ndots 我们在demo-svc1中直接请求域名demo-svc2.demo-ns.svc.cluster.local，此时的ndots为4，还是会触发上面的search规则。\n而请求域名demo-svc2.demo-ns.svc.cluster.local.，ndots为5，等于配置中的5，因此不会触发上面的search规则，直接去解析demo-svc2.demo-ns.svc.cluster.local.这个域名并返回结果\n如果我们请求更长的域名如POD域名pod-1.demo-svc2.demo-ns.svc.cluster.local.，此时的ndots为6，大于配置中的5，因此也不会触发上面的search规则，会直接查询域名并返回解析\n小结 通过上面的分析我们不难得出下面几点结论：\n同命名空间（namespace）内的服务直接通过$service_name进行互相访问而不需要使用全域名（FQDN），此时DNS解析速度最快； 跨命名空间（namespace）的服务，可以通过$service_name.$namespace_name进行互相访问，此时DNS解析第一次查询失败，第二次才会匹配到正确的域名； 所有的服务之间通过全域名（FQDN）$service_name.$namespace_name.svc.$cluster_name.访问的时候DNS解析的速度最快； 在K8S集群内访问大部分的常见外网域名（ndots小于5）都会触发search规则，因此在访问外部域名的时候可以使用FQDN，即在域名的结尾配置一个点号. kubernetes插件 kubernetes插件的主要作用就是用来连接k8s集群的apiserver并对外提供符合规范的域名解析服务，该插件在每个配置块中仅能使用一次，但在一个coredns实例中可以存在多个配置块，也就意味着一个coredns实例实际上是可以连接多个k8s集群并对外提供域名解析的。\n接下来我们详细看一下kubernetes插件的各种具体配置，下面的这个是官方给出的一个配置文件示例：\nkubernetes [ZONES...] { endpoint URL tls CERT KEY CACERT kubeconfig KUBECONFIG [CONTEXT] namespaces NAMESPACE... namespace_labels EXPRESSION labels EXPRESSION pods POD-MODE endpoint_pod_names ttl TTL noendpoints fallthrough [ZONES...] ignore empty_service } endpoint\n用来指定k8s集群的apiserver地址，如https://10.31.88.1:6443，当然也可以是域名等其他形式，如果不配置，那么默认情况下会使用对应的service account去连接当前k8s集群内的apiserver，如果不是在k8s集群中部署，那么就会连接失败。\ntls\nCERT KEY CACERT是远程 k8s 连接的 TLS 证书、密钥和 CA 证书文件名。如果前面的endpoint没有配置，那么这个配置项会被忽略。\nkubeconfig\nKUBECONFIG [CONTEXT]使用 kubeconfig 文件验证与远程 k8s 集群的连接。 [CONTEXT]是可选配置的，如果未设置，则将使用 kubeconfig中默认的[CONTEXT]。它支持 TLS、用户名和密码或基于令牌的身份验证。\n如果前面的endpoint没有配置，那么这个配置项会被忽略。\nnamespaces\nNAMESPACE [NAMESPACE…] 用来限制对外暴露的命名空间，多个命名空间之间使用空格间隔。如果不配置的话，则会暴露所有的命名空间。\nnamespace_labels\nnamespace_labels EXPRESSION可以限定DNS的查询范围，仅有匹配labels的命名空间才能被查询到。\nlabels\nlabels EXPRESSION可以限定DNS的查询范围，仅有匹配lalels的service才能被查询到。\n注意这里的labels匹配的是service中的labels，而前面的labels匹配的是namespace中的labels。这两个labels的配置写法可以和使用kubectl命令中的-l参数完全一致。\n如果要使用多个labels匹配规则，注意不要使用空格，而是对应的表达式进行匹配：\n-l, \u0026ndash;selector=\u0026rsquo;\u0026rsquo;: Selector (label query) to filter on, supports \u0026lsquo;=\u0026rsquo;, \u0026lsquo;==\u0026rsquo;, and \u0026lsquo;!=\u0026rsquo;.(e.g. -l key1=value1,key2=value2)\npods\npods POD-MODE设置处理基于 IP 的 pod A 记录的模式，例如客户端向coredns查询域名1-2-3-4.ns.pod.cluster.local.，该参数用于控制响应的结果，提供此选项是为了方便在直接连接到 pod 时使用 SSL 证书。\n**POD-MODE **有效值：\ndisabled： 默认。不处理 pod 请求，总是返回NXDOMAIN insecure：总是从请求中返回带有 IP 的 A 记录（不检查 k8s），即查询域名1-2-3-4.ns.pod.cluster.local.的时候，不论是否存在一个IP地址为1.2.3.4的pod，都返回这个结果给客户端。如果与通配符 SSL 证书一起被恶意使用，此选项很容易被滥用。提供此选项是为了向后兼容 kube-dns。 verified: 如果在同一个命名空间中存在匹配 IP 的 Pod，则返回 A 记录，即查询域名1-2-3-4.ns.pod.cluster.local.的时候，只有当该ns中确实存在一个IP地址为1.2.3.4的pod，才返回这个结果给客户端，否则返回NXDOMAIN。与insecure模式相比，此选项需要更多的内存，因为它需要监控所有的pods。 endpoint_pod_names\n使用endpoints所对应的pod名称作为A记录中的端点名称，例如， endpoint-name.my-service.namespace.svc.cluster.local. in A 1.2.3.4\n在没有配置该参数的情况下，endpoints名称选择如下：优先使用endpoints的hostname，如果endpoints没有配置hostname，则使用 IP 地址的虚线形式（例如，1-2-3-4.my-service.namespace.svc.cluster.local.）\n如果配置了该参数，则endpoints名称选择如下：优先使用endpoints的hostname，如果endpoints没有配置hostname，则使用endpoints对应的pod名称，如果pod名称不存在或者长度超过63，则使用 IP 地址的虚线形式。\nttl\n设置标准的DNS域名TTL，默认值为 5 秒。允许的最小 TTL 为 0 秒，最大值为 3600 秒。将 TTL 设置为 0 将防止记录被缓存（如果查询的客户端遵循DNS规范）。\nnoendpoints\n配置该参数将禁用对K8S集群中的endpoints记录功能，因此所有endpoints查询和headless服务查询都将返回 NXDOMAIN。\nfallthrough [ZONES…]\n正常情况下一个客户端对CoreDNS发起了一个DNS查询，如果该记录不存在，那么就会直接返回一个NXDOMAIN的响应。\n但是我们可以通过配置fallthrough参数来将这些NXDOMAIN的域名转发到配置块中的下一个插件。\n例如在fallthrough插件后面还使用了诸如file插件之类的配置了DNS解析，那么这个请求就会转发到file插件进行查询并响应\nzones参数可以用来控制哪些域的域名会被fallthrough插件转发，留空的情况下是所有的域名都会被转发，当然也可以指定部分域名如(for example in-addr.arpa and ip6.arpa)，此时就只有in-addr.arpa 和 ip6.arpa的查询出现NXDOMAIN才会被转发到下一个插件进行查询\nignore empty_service\n如果一个service当中没有任何可用的endpoints（即关联的所有pods都不是ready状态），那么会返回一个NXDOMAIN。\n这个配置项的主要作用就是让这类不正常的服务域名查询的时候能够返回NXDOMAIN响应码，从而触发配置的其他插件（如上面提到的fallthrough）进行组合操作。\n一些其他问题 延迟启动 当CoreDNS启用了kubernetes插件之后，CoreDNS实例在启动的时候会延迟5s的时间再对外提供服务，这5s内CoreDNS会尝试和K8S的apiserver建立连接并同步信息。\n如果5s内CoreDNS还是无法和k8s的apiserver完成信息同步工作，那么会开始对外提供服务，并且继续尝试同步信息，但是在成功和apiserver建立连接并同步信息之前，所有k8s相关的域名查询都会返回SERVFAIL。\n连接中断 如果在CoreDNS实例正常运行的时候，突然和k8s的apiserver断开连接，并且一直没有恢复，那么此时的CoreDNS实例是依旧正常运行的，对应的K8S集群域名也是能够正常解析的，但是解析出来的endpoint信息就有可能不是最新的。\n如果此时再对CoreDNS实例进行重启操作，那么具体的过程就和上面讲述的延迟启动一样，最后会导致所有k8s相关的域名查询都会返回SERVFAIL。\n配置检查 kubernetes的健康状态会暴露在ready插件中，如果出现配置错误可以通过请求ready插件暴露的接口发现，但是如果出现连接异常这种情况，ready接口是无法探测出来的。\n","permalink":"http://localhost:9011/20221107-dns-12-coredns-09-kubernetes/","summary":"\u003cp\u003eCoreDNS作为现阶段k8s的默认DNS服务以及服务发现的重要一环，其内置的kubernetes插件可谓是举足轻重。本文主要讲解介绍CoreDNS内置的核心插件\u003ccode\u003ekubernetes\u003c/code\u003e的使用方式和适用场景。\u003c/p\u003e","title":"CoreDNS篇9-kubernetes插件"},{"content":"本文主要讲解介绍CoreDNS内置的两个健康检查插件health和ready的使用方式和适用场景。\nhealth插件 health插件默认情况下会在8080端口的/health路径下提供健康状态查询服务，当CoreDNS服务正常的时候，会返回200的http状态码并附带一个OK的内容。\n[root@coredns-10-31-53-1 conf]# curl -v http://10.31.53.1:8080/health * About to connect() to 10.31.53.1 port 8080 (#0) * Trying 10.31.53.1... * Connected to 10.31.53.1 (10.31.53.1) port 8080 (#0) \u0026gt; GET /health HTTP/1.1 \u0026gt; User-Agent: curl/7.29.0 \u0026gt; Host: 10.31.53.1:8080 \u0026gt; Accept: */* \u0026gt; \u0026lt; HTTP/1.1 200 OK \u0026lt; Date: Thu, 28 Jul 2022 03:52:56 GMT \u0026lt; Content-Length: 2 \u0026lt; Content-Type: text/plain; charset=utf-8 \u0026lt; * Connection #0 to host 10.31.53.1 left intact OK 比较特别的是health插件还附带了一个lameduck功能，lameduck的效果就是在coredns进程关闭之前延迟对应的时间。假设我们设置了lameduck 10s，那么coredns在接收到退出进程命令的时候会延迟10s的时间再结束进程。\nhealth [ADDRESS] { lameduck DURATION } 需要特别注意的是，假设我们在多个配置块中都使用了lameduck功能，那么时间会叠加。举个例子，假设我们在10个配置块中都设置了lameduck 10s，那么coredns在接收到退出进程命令的时候会延迟10*10=100s的时间再结束进程。\n此外还有一个小问题，在开启health插件之后会导致health插件对应的端口会有较多的TIME_WAIT连接，目前怀疑是插件本身会请求自身端口进行检查导致产生TIME_WAIT连接。\n[root@coredns-10-31-53-1 conf]# netstat -nt | grep 8080 | grep -c TIME_WAIT 61 ready插件 ready插件和health插件有些类似，默认情况下定义在8181端口的/ready路径下返回CoreDNS服务器的状态，正常情况下也是返回200的http状态码并附带一个OK的内容。\n[root@coredns-10-31-53-1 conf]# curl -v http://10.31.53.1:8181/ready * About to connect() to 10.31.53.1 port 8181 (#0) * Trying 10.31.53.1... * Connected to 10.31.53.1 (10.31.53.1) port 8181 (#0) \u0026gt; GET /ready HTTP/1.1 \u0026gt; User-Agent: curl/7.29.0 \u0026gt; Host: 10.31.53.1:8181 \u0026gt; Accept: */* \u0026gt; \u0026lt; HTTP/1.1 200 OK \u0026lt; Date: Thu, 28 Jul 2022 03:53:25 GMT \u0026lt; Content-Length: 2 \u0026lt; Content-Type: text/plain; charset=utf-8 \u0026lt; * Connection #0 to host 10.31.53.1 left intact OK 当CoreDNS服务中的某个组件的相关配置出现异常的时候，则会返回503的http状态码并附带一个出现问题的组件名称。\n[root@coredns-10-31-53-1 conf]# curl -vv http://10.31.53.1:8181/ready * About to connect() to 10.31.53.1 port 8181 (#0) * Trying 10.31.53.1... * Connected to 10.31.53.1 (10.31.53.1) port 8181 (#0) \u0026gt; GET /ready HTTP/1.1 \u0026gt; User-Agent: curl/7.29.0 \u0026gt; Host: 10.31.53.1:8181 \u0026gt; Accept: */* \u0026gt; \u0026lt; HTTP/1.1 503 Service Unavailable \u0026lt; Date: Thu, 28 Jul 2022 03:51:44 GMT \u0026lt; Content-Length: 10 \u0026lt; Content-Type: text/plain; charset=utf-8 \u0026lt; * Connection #0 to host 10.31.53.1 left intact kubernetes 而此时访问health组件的接口返回的响应码还是200以及OK\n[root@coredns-10-31-53-1 conf]# curl -v http://10.31.53.1:8080/health * About to connect() to 10.31.53.1 port 8080 (#0) * Trying 10.31.53.1... * Connected to 10.31.53.1 (10.31.53.1) port 8080 (#0) \u0026gt; GET /health HTTP/1.1 \u0026gt; User-Agent: curl/7.29.0 \u0026gt; Host: 10.31.53.1:8080 \u0026gt; Accept: */* \u0026gt; \u0026lt; HTTP/1.1 200 OK \u0026lt; Date: Thu, 28 Jul 2022 03:59:45 GMT \u0026lt; Content-Length: 2 \u0026lt; Content-Type: text/plain; charset=utf-8 \u0026lt; * Connection #0 to host 10.31.53.1 left intact OK 从systemd的服务状态中我们不难看出，此时的coredns是处于运行状态，但是kubernetes插件工作异常。这也就较好地说明了health插件在工作时主要关注coredns本身的运行状态，而ready插件会同时关注组件的工作状态是否正常。\n[root@coredns-10-31-53-1 conf]# systemctl status coredns ● coredns.service - CoreDNS Loaded: loaded (/usr/lib/systemd/system/coredns.service; enabled; vendor preset: disabled) Active: active (running) since Thu 2022-07-28 11:52:50 CST; 8min ago Docs: https://coredns.io/manual/toc/ Main PID: 14478 (coredns) Tasks: 13 Memory: 23.8M CGroup: /system.slice/coredns.service └─14478 /home/coredns/sbin/coredns -dns.port=53 -conf /home/coredns/conf/corefile Jul 28 11:52:50 coredns-10-31-53-1.tinychen.io coredns[14478]: [INFO] plugin/reload: Running configuration MD5 = e3edb2bb003af1e51a1b82bfaebba8f4 Jul 28 11:52:50 coredns-10-31-53-1.tinychen.io coredns[14478]: CoreDNS-1.8.6 Jul 28 11:52:50 coredns-10-31-53-1.tinychen.io coredns[14478]: linux/amd64, go1.17.1, 13a9191 Jul 28 11:52:50 coredns-10-31-53-1.tinychen.io coredns[14478]: [INFO] 127.0.0.1:53443 - 17600 \u0026#34;HINFO IN 6988510158354025264.1665891352749413348.cali-cluster.tclocal. udp 78 false 512\u0026#34; NXDOMAIN qr,aa,rd 192 0.000385901s Jul 28 11:57:05 coredns-10-31-53-1.tinychen.io coredns[14478]: [INFO] Reloading Jul 28 11:57:10 coredns-10-31-53-1.tinychen.io coredns[14478]: [WARNING] plugin/kubernetes: starting server with unsynced Kubernetes API Jul 28 11:57:10 coredns-10-31-53-1.tinychen.io coredns[14478]: [INFO] 127.0.0.1:41957 - 46173 \u0026#34;HINFO IN 3749714491109172199.3469953470964448055.cali-cluster.tclocal. udp 78 false 512\u0026#34; SERVFAIL qr,aa,rd 192 0.00012492s Jul 28 11:57:10 coredns-10-31-53-1.tinychen.io coredns[14478]: [INFO] plugin/reload: Running configuration MD5 = 2365432f92773a3434ec9ab810392378 Jul 28 11:57:10 coredns-10-31-53-1.tinychen.io coredns[14478]: [INFO] Reloading complete Jul 28 11:59:49 coredns-10-31-53-1.tinychen.io coredns[14478]: [INFO] plugin/ready: Still waiting on: \u0026#34;kubernetes\u0026#34; 小结 从上面的对比我们不难发现就单纯的就检测程序本身状态而言，两者都是能够满足需求的。而在默认的k8s中部署的coredns，我们查看其配置文件可以发现两者的用途并不一致，health插件主要用于livenessProbe，用于检测该pod是否正常运行，是否需要销毁重建等；而ready插件主要用于readinessProbe，用于检测coredns的状态是否可以ready并对外提供服务。\nlivenessProbe: failureThreshold: 5 httpGet: path: /health port: 8080 scheme: HTTP initialDelaySeconds: 60 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 5 readinessProbe: failureThreshold: 3 httpGet: path: /ready port: 8181 scheme: HTTP periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 更多关于Liveness和Readiness的配置可以参考kubernetes的官方配置文档\nThe kubelet uses liveness probes to know when to restart a container. For example, liveness probes could catch a deadlock, where an application is running, but unable to make progress. Restarting a container in such a state can help to make the application more available despite bugs.\nThe kubelet uses readiness probes to know when a container is ready to start accepting traffic. A Pod is considered ready when all of its containers are ready. One use of this signal is to control which Pods are used as backends for Services. When a Pod is not ready, it is removed from Service load balancers.\nThe kubelet uses startup probes to know when a container application has started. If such a probe is configured, it disables liveness and readiness checks until it succeeds, making sure those probes don\u0026rsquo;t interfere with the application startup. This can be used to adopt liveness checks on slow starting containers, avoiding them getting killed by the kubelet before they are up and running.\n","permalink":"http://localhost:9011/20220728-dns-11-coredns-08-healthcheck/","summary":"\u003cp\u003e本文主要讲解介绍CoreDNS内置的两个健康检查插件\u003ccode\u003ehealth\u003c/code\u003e和\u003ccode\u003eready\u003c/code\u003e的使用方式和适用场景。\u003c/p\u003e","title":"CoreDNS篇8-健康检查"},{"content":"本文主要介绍了K8S集群中的服务发现和流量暴露机制，包括K8S中的workload类型、service类型、DNS解析原理以及四层服务暴露和七层服务暴露的规则。\n云原生基础概念 K8S架构 下图为K8S官方文档中对K8S架构设计的一个简要介绍示意图，这个架构图侧重于从云厂商的角度展示了云厂商的API、K8S集群中控制面（Control Plane）和工作节点（Node）之间的关系，但是将留给第三方实现的如CRI、CNI、CSI等从中剥离出去了。\n在官方架构图的基础上我们将CRI和CNI引入到架构图中，可以得到下面的这个模型：\nkube-apiserver对外暴露了Kubernetes API。它是的 Kubernetes 前端控制层。它被设计为水平扩展，即通过部署更多实例来缩放。 etcd用于 Kubernetes 的后端存储。etcd 负责保存Kubernetes Cluster的配置信息和各种资源的状态信息，始终为 Kubernetes 集群的 etcd 数据提供备份计划。当数据发生变化时，etcd 会快速地通知Kubernetes相关组件。 kube-scheduler主要的工作就是调度新创建的Pod，当集群中出现了新的Pod还没有确定分配到哪一个Node节点的时候，kube-scheduler会根据各个节点的负载，以及应用对高可用、性能、数据亲和性的需求等各个方面进行分析并将其分配到最合适的节点上。 kube-controller-manager运行控制器，它们是处理集群中常规任务的后台线程。逻辑上，每个控制器是一个单独的进程，但为了降低复杂性，它们都被编译成独立的可执行文件，并在单个进程中运行。这些控制器包括：节点控制器(Node Controller)、副本控制器(Replication Controller)、端点控制器(Endpoints Controller)、服务帐户和令牌控制器(Service Account \u0026amp; Token Controllers)等 kube-proxy是集群中每个节点上运行的网络代理， kube-proxy通过维护主机上的网络规则并执行连接转发，实现了Kubernetes服务抽象。service在逻辑上代表了后端的多个Pod，外界通过service访问Pod。service接收到的请求就是通过kube-proxy转发到Pod上的，kube-proxy服务负责将访问service的TCP/UDP数据流转发到后端的容器。如果有多个副本，kube-proxy会实现负载均衡。 K8S的三大插件分别管控运行时、网络和存储，即Container Runtime Interface (CRI)、Container Network Interface (CNI)和Container-Storage-Interface (CSI)。注意CRI和CNI是每个K8S集群都必须要部署的基础组件，而CSI则不一定，一般来说只有在我们需要运行有状态服务的时候才需要用到CSI。 CNI基础 K8S本身不实现集群内的网络模型，而是通过将其抽象出来提供了CNI接口给第三方实现，这样一来节省了开发资源可以集中精力到K8S本身，二来可以利用开源社区的力量打造一整个丰富的生态，CNI的一些实现细节和要求我们都可以在github上面找到，我们这里暂不深入解析。\n重点来看一下K8S对集群内的网络模型定义：\nK8S集群中任意两个POD可以直接通信，并且不需要进行NAT K8S集群中的每个Pod都必须要有自己的唯一、独立且可被访问的IP(IP-per-Pod) K8S并不关心各个CNI如何具体实现上述基础规则，只要最终的网络模型符合标准即可。因此我们可以确保不论使用什么CNI，K8S集群内的Pod网络都是一张巨大的平面网络，每个Pod在这张网络中地位是平等的，这种设计对于集群内的服务发现、负载均衡、服务迁移、应用配置等诸多场景都带来了极大的便利。\nOverlay networks Overlay网络可以理解为建立在另一个网络之上的虚拟网络，这个概念在SDN里面里面经常出现。和虚拟网卡需要依赖实际存在的物理网卡才能通信类似，Overlay网络也不能凭空出现，它需要依赖的底层网络通常被称为Underlay网络。Underlay 网络是专门用来承载用户 IP 流量的基础架构层，它与 Overlay 网络之间的关系有点类似物理机和虚拟机。Underlay 网络和物理机都是真正存在的实体，它们分别对应着真实存在的网络设备和计算设备，而 Overlay 网络和虚拟机都是依托在下层实体使用软件虚拟出来的层级。\n在使用了Overlay网络的K8S集群中，我们可以把底层的Underlay网络看作是K8S集群的Node节点所在的网络，而上层的Overlay网络一般用来处理Pod之间的网络通信。正常情况下，Underlay网络和Overlay网络之间互不干扰，两者并不知道对方的网络情况。但是由于Overlay网络是需要依赖Underlay网络进行传输数据的，因此在Overlay网络的数据发送到Underlay网络进行传输的时候，需要进行数据包的封装，将其变为Underlay网络可以理解的数据包；反之当数据从Underlay网络传送回Overlay网络的时候需要进行数据包的解封。在K8S的Overlay网络实现中，用于封装的两种常见网络协议是 VXLAN 和 IP-in-IP。\n使用Overlay网络的主要优点是：\n高度灵活性，Overlay网络和底层硬件网络设施分离，因此在跨机房、跨数据中心等场景有着传统的Underlay网络无法比拟的优势 使用Overlay网络的主要缺点是：\n轻微的性能影响。封装数据包的过程占用少量 CPU，数据包中用于编码封装（VXLAN 或 IP-in-IP 标头）所需的额外字节减少了可以发送的内部数据包的最大大小，进而可以意味着需要为相同数量的总数据发送更多数据包。 Pod IP 地址不可在集群外路由。 边界网关协议（BGP） BGP（Border Gateway Protocol/边界网关协议）是一种基于标准的网络协议，用于在网络中共享路由。它是互联网的基本组成部分之一，具有出色的扩展特性。在K8S中，BGP是出场率很高的一个路由协议，有很多相关的CNI或者是LoadBalancer都会使用BGP协议来实现诸如路由可达或者是ECMP等特性。\n目前对BGP协议支持最好、使用最广泛的CNI应该是Calico，另外Cilium也有仍处于beta阶段的BGP模式的支持。\n可路由性（routability） 不同的K8S集群网络的一个重要区别就是Pod的IP在K8S集群外的可路由性。\n由于K8S集群内的Pod之间必然是路由可达的，因此这里探讨的是集群外的服务到集群内的Pod之间的路由可达。\n路由不可达 所谓路由不可达，即K8S集群外的机器没办法和集群内的Pod直接建立连接，集群外的服务器不知道如何将数据包路由到 Pod IP。\n这种情况下当集群内的Pod需要主动和集群外的服务建立连接的时候，会通过K8S进行SNAT(Source Network Address Translation)。此时在集群外的服务器看到的连接对端IP是这个Pod所在的K8S集群节点的Node IP而不是Pod自身的IP，对于集群外的服务器发送返回数据的目的IP也永远都是这个K8S集群节点的Node IP，数据在Node IP上面再转换发送回Pod。这种情况下，集群外的服务器是无法得知Pod的IP，也无法直接获取真实的请求IP。\n反之则更复杂，因为集群外的服务器不知道如何将数据包路由到 Pod IP ，所以也没办法主动去请求这些Pod，此时只能通过K8S的services（NodePort、LoadBalancer、Ingress）来将服务暴露到集群外，此时集群外的服务器的访问对象是某个K8S的服务，而不是具体的某个Pod。\n路由可达 如果 Pod IP 地址可在集群外部路由，则 pod 可以在没有 SNAT 的情况下直接连接到集群外的服务器，而集群外的服务器也可以直接连接到 pod，而无需通过 通过K8S的services（NodePort、LoadBalancer、Ingress）。\n可在集群外路由的 Pod IP 地址的优点是：\n减少网络层级、降低网络层面架构复杂性、降低使用人员的理解成本、维护成本和Debug成本等 针对一些特殊的应用场景（如集群外的机器需要直接和Pod进行连接），在这种架构中实现更加简单 可在集群外路由的 Pod IP 地址的主要缺点是：\nPod IP 在集群外的网络中也必须要唯一。如果有多个K8S集群都需要实现集群外路由可达，那么就需要给每个集群的Pod使用不同的CIDR。这对内部IP的使用规划有一定的要求，并且当集群足够大的时候，还需要考虑内网IP耗尽的可能。 可路由性的决定因素 如果集群使用的是overlay网络，一般来说Pod IP无法在集群外部路由 如果不使用overlay网络，则取决于部署的环境（云厂商/本地部署）、使用的CNI（Calico-BGP、Cilium-BGP等）以及实际网络规划等 目前K8S网络的集群外可路由性实现一般都是通过BGP协议 K8S服务暴露 正常情况下，我们部署在K8S集群中的工作负载是需要对外提供服务的。这里的**“对外”**指的是对该负载以外的所有外部服务器提供服务，而根据这些外部服务器是否位于K8S集群中，我们可以分为K8S集群内部流量和K8S集群外流量。\nWorkload与SVC 开始之前，我们要明确几个点：\nK8S中的工作负载（Workload）一般指的是集群中的真实工作任务。比如无状态服务常用的deployments、有状态服务常用的statefulsets、一些特殊用途的daemonsets、定时服务常用的cronjobs等，这些都属于是K8S中的工作负载（Workload）。 K8S中的service（SVC）更多倾向于是一种规则集合，将符合某些特定条件的pod全部归属到一个Service中，然后组成一个特定的Service。注意这些pod是可以属于不同的工作负载（Workload）。 K8S中的每个SVC都会有一个对应的域名，域名的组成格式为$service_name.$namespace_name.svc.$cluster_name，一般来说k8s集群中的$cluster_name就是cluster.local，这个字段一般在集群创建的时候就会设定好，之后想要再更改会十分麻烦。 综上所诉，我们可以得出以下结论：\nK8S中的工作负载（Workload）和服务暴露（service）是相互隔离开来的，分别交给不同的api来实现 每个SVC都会有一个服务名+命名空间+svc+集群名/$service_name.$namespace_name.svc.$cluster_name的域名可以用来访问（如app.namespace.svc.cluster.local） K8S集群内的服务之间访问主要就是通过这个域名来实现的 SVC的种类 一般来说svc可以分为四类：Headless、ClusterIP、NodePort、LoadBalancer。四者之间的关系并非是完全互斥，具体如下：\nHeadless Services Headless类型服务和其他三者完全互斥，可以通过指定 Cluster IP（spec.clusterIP）的值为 \u0026quot;None\u0026quot; 来创建 Headless Service； 此时该服务的域名解析的结果就是这个服务关联的所有Pod IP，使用该域名访问的时候请求会直接到达pod； 这时的负载均衡策略相当于是仅使用了DNS解析做负载均衡，并没有使用k8s内置的kube-proxy进行负载均衡； Headless类型服务不会创建对应域名所属的SRV记录； Headless Services**这种方式优点在于足够简单、请求的链路短，但是缺点也很明显，就是DNS的缓存问题带来的不可控。**很多程序查询DNS并不会参考规范的TTL值，要么频繁的查询给DNS服务器带来巨大的压力，要么查询之后一直缓存导致服务变更了还在请求旧的IP。\nClusterIP Services 在 Kubernetes 集群中，每个 Node 运行一个 kube-proxy 进程。 kube-proxy 负责为 Service 实现了一种 VIP（虚拟 IP）的形式，一般称之为ClusterIP Services。\nClusterIP是最常用的服务类型，也是默认的服务类型，同时也是NodePort、LoadBalancer这两个服务的基础； 对于ClusterIP类型的服务，K8S会给该服务分配一个称为CLUSTER-IP的VIP； ClusterIP是单独的IP网段，区别于K8S的宿主机节点IP网段和Pod IP网段，也是在集群初始化的时候定义的； ClusterIP可以在每一台k8s宿主机节点上面的kube-ipvs0网卡里面看到； ClusterIP类型的服务的域名解析的结果就是这个VIP，请求会先经过VIP，再由kube-proxy分发到各个pod上面； 如果k8s使用了ipvs，可以在K8S宿主机节点上面使用ipvsadm命令来查看这些负载均衡的转发规则； ClusterIP类型服务还会创建对应域名所属的SRV记录，SRV记录中的端口为ClusterIP的端口 ClusterIP Services这种方式的优点是有VIP位于Pod前面，可以有效避免前面提及的直接DNS解析带来的各类问题；缺点也很明显，当请求量大的时候，kube-proxy组件的处理性能会首先成为整个请求链路的瓶颈。\nNodePort Service 从NodePort开始，服务就不仅局限于在K8S集群内暴露，开始可以对集群外提供服务 NodePort类型会从K8S的宿主机节点上面挑选一个端口分配给某个服务（默认范围是30000-32767），用户可以通过请求任意一个K8S节点IP的该指定端口来访问这个服务 NodePort服务域名解析的解析结果是一个CLUSTER-IP，在集群内部请求的负载均衡逻辑和实现与ClusterIP Service是一致的 NodePort服务的请求路径是从K8S节点IP直接到Pod，并不会经过ClusterIP，但是这个转发逻辑依旧是由kube-proxy实现 NodePort Service这种方式的优点是非常简单的就能把服务通过K8S自带的功能暴露到集群外部；缺点也很明显：NodePort本身的端口限制（数量和选择范围都有限）以及请求量大时的kube-proxy组件的性能瓶颈问题。\nLoadBalancer Service LoadBalancer服务类型是K8S对集群外服务暴露的最高级最优雅的方式，同时也是门槛最高的方式； LoadBalancer服务类型需要K8S集群支持一个云原生的LoadBalancer，这部分功能K8S本身没有实现，而是将其交给云厂商/第三方，因此对于云环境的K8S集群可以直接使用云厂商提供的LoadBalancer，当然也有一些开源的云原生LoadBalancer，如MetalLB、OpenELB、PureLB等； LoadBalancer服务域名解析的解析结果是一个CLUSTER-IP； LoadBalancer服务同时会分配一个EXTERNAL-IP，集群外的机器可以通过这个EXTERNAL-IP来访问服务； LoadBalancer服务默认情况下会同时创建NodePort，也就是说一个LoadBalancer类型的服务同时是一个NodePort服务，同时也是一个clusterIP服务；一些云原生LoadBalancer可以通过指定allocateLoadBalancerNodePorts: false来拒绝创建NodePort服务； 我们还是借用OpenELB官网的图来解释一下这个流程，注意这里为BGP模式。\nLoadBalancer Service这种方式的优点是方便、高效、适用场景广泛，几乎可以覆盖所有对外的服务暴露；缺点则是成熟可用的云原生LoadBalancer选择不多，实现门槛较高。\nPort概念辨析 在我们进行SVC和Workload部署配置的时候，经常会碰到各种名字中带有Port的配置项，这也是K8S中容易让人混淆的几个概念之一，这里主要介绍一下NodePort、Port、targetPort和containerPort这个四个概念。四者的关系我们可以通过下面这种图比较清晰地区分出来：\nnodePort: 只存在于Loadbalancer服务和NodePort服务中，用于指定K8S集群的宿主机节点的端口，默认范围是30000-32767，K8S集群外部可以通过NodeIP:nodePort 来访问某个service； port: 只作用于CLUSTER-IP和EXTERNAL-IP，也就是对Loadbalancer服务、NodePort服务和ClusterIP服务均有作用，K8S集群内部可以通过CLUSTER-IP:port来访问，K8S集群外部可以通过EXTERNAL-IP:port来访问； targetPort: Pod的外部访问端口，port和nodePort的流量会参照对应的ipvs规则转发到Pod上面的这个端口，也就是说数据的转发路径是NodeIP:nodePort -\u0026gt; PodIP:targetPort、CLUSTER-IP:port -\u0026gt; PodIP:targetPort、EXTERNAL-IP:port -\u0026gt; PodIP:targetPort containerPort：和其余三个概念不属于同一个维度，containerPort主要是在工作负载（Workload）中配置，其余三者均是在service中配置。containerPort主要作用在Pod内部的container，用来告知K8S这个container内部提供服务的端口，因此理论上containerPort应该要和container内部实际监听的端口一致，这样才能确保服务正常；但是实际上由于各个CNI的实现不通以及K8S配置的网络策略差异，containerPort的作用并不明显，很多时候配置错误或者是不配置也能正常工作； 综上所述，我们可以得知四者的主要区别，那么我们在实际使用的时候，最好就需要确保targetPort、containerPort和Pod里面运行程序实际监听的端口三者保持一致，即可确保请求的数据转发链路正常。\nK8S中的DNS服务 众所周知，在K8S中，IP是随时会发生变化的，变化最频繁的就是Pod IP，Cluster IP也并不是一定不会发生变化，EXTERNAL-IP虽然可以手动指定静态IP保持不变，但是主要面向的是集群外部的服务；因此在K8S集群中，最好的服务之间相互访问的方式就是通过域名。\nDNS创建规则 在K8S集群中，Kubernetes 为 Service 和 Pod 创建 DNS 记录。\n前面我们介绍了K8S中的每个SVC都会有一个对应的域名，域名的组成格式为$service_name.$namespace_name.svc.$cluster_name，同时也会给这个SVC下的所有Pod都创建一个$pod_name.$service_name.$namespace_name.svc.$cluster_name的这种域名，这个域名的解析结果就是Pod IP。\nPod域名有两个比较明显的特征：\n一是域名的组成比较特殊，因为域名中使用了Pod的名称，而pod名称在K8S中是会发生变化的（例如在服务更新或者滚动重启时），同时由于默认情况下Pod的命名是没有太明显的规律（大部分名字中会包含一串随机UUID） 二是域名的解析结果特殊，相较于集群内的其他类型域名，Pod域名的解析是可以精确到特定的某个Pod，因此一些特殊的需要点对点通信的服务可以使用这类Pod域名 DNS策略配置 DNS 策略可以逐个 Pod 来设定。目前 Kubernetes 支持以下特定 Pod 的 DNS 策略。 这些策略可以在 Pod 规约中的 dnsPolicy 字段设置：\nDefault: Pod 从运行所在的K8S宿主机节点继承域名解析配置； ClusterFirst: 不指定任何dnsPolicy配置情况下的默认选项，所有查询的域名都会根据生成的集群的K8S域名等信息生成的 /etc/resolv.conf 配置进行解析和转发到集群内部的DNS服务进行解析； ClusterFirstWithHostNet：主要用于以 hostNetwork 方式运行的 Pod，如果这些pod想要使用K8S集群内的DNS服务，则可以配置为这个字段； None: 此设置允许 Pod 忽略 Kubernetes 环境中的 DNS 设置，Pod 会使用其 dnsConfig 字段 所配置的 DNS 设置； 说明： 下面主要介绍ClusterFirst模式\nDNS解析规则 DNS 查询参照 Pod 中的 /etc/resolv.conf 配置，kubelet 会为每个 Pod 生成此文件。因此在每个pod里面都有一个类似下面这样的 /etc/resolv.conf文件，通过修改其中的配置可以更改DNS的查询规则：\nnameserver 10.32.0.10 search \u0026lt;namespace\u0026gt;.svc.cluster.local svc.cluster.local cluster.local options ndots:5 这里的配置有几个需要注意的点：\nnameserver：集群中的DNS服务器IP，一般来说就是CoreDNS的ClusterIP search：需要搜索的域，默认情况下会从该pod所属的namespace开始逐级补充 options ndots：触发上面的search的域名点数，默认为1，上限15，在K8S中一般为5；例如在Linux中tinychen.com这个域名的ndots是1，tinychen.com.这个域名的ndots才是2（需要注意所有域名其实都有一个根域.，因此tinychen.com的全称应该是tinychen.com.） 这是一个比较通用的案例，我们再来看一个比较特殊的配置\n# 首先进入一个pod查看里面的DNS解析配置 [root@tiny-calico-master-88-1 tiny-calico]# kubectl exec -it -n ngx-system ngx-ex-deploy-6bf6c99d95-5qh2w /bin/bash kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. [root@ngx-ex-deploy-6bf6c99d95-5qh2w /]# cat /etc/resolv.conf nameserver 10.88.0.10 search ngx-system.svc.cali-cluster.tclocal svc.cali-cluster.tclocal cali-cluster.tclocal k8s.tcinternal options ndots:5 [root@ngx-ex-deploy-6bf6c99d95-5qh2w /]# exit 这个pod里面的/etc/resolv.conf配置文件有两个和前面不同的地方：\ncluster.local变成了cali-cluster.tclocal\n这里我们可以看到coredns的配置中就是配置的cali-cluster.tclocal，也就是说/etc/resolv.conf中的配置其实是和coredns中的配置一样，更准确的说是和该K8S集群初始化时配置的集群名一样\n# 再查看K8S集群中的coredns的configmap\t[root@tiny-calico-master-88-1 tiny-calico]# kubectl get configmaps -n kube-system coredns -oyaml apiVersion: v1 data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cali-cluster.tclocal in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . 10.31.100.100 { max_concurrent 1000 } cache 30 loop reload loadbalance } kind: ConfigMap metadata: creationTimestamp: \u0026#34;2022-05-06T05:19:08Z\u0026#34; name: coredns namespace: kube-system resourceVersion: \u0026#34;3986029\u0026#34; uid: 54f5f803-a5ab-4c77-b149-f02229bcad0a search新增了一个k8s.tcinternal\n实际上我们再查看K8S的宿主机节点的DNS配置规则时会发现这个k8s.tcinternal是从宿主机上面继承而来的\n# 最后查看宿主机节点上面的DNS解析配置 [root@tiny-calico-master-88-1 tiny-calico]# cat /etc/resolv.conf # Generated by NetworkManager search k8s.tcinternal nameserver 10.31.254.253 DNS解析流程 温馨提示：阅读这部分内容的时候要特别注意域名结尾是否有一个点号.\n当ndots小于options ndots 前面我们说过options ndots的值默认情况下是1，在K8S中为5，为了效果明显，我们这里使用K8S中的5作为示例：\n这里同样是在一个命名空间demo-ns中有两个SVC，分别为demo-svc1和demo-svc2，那么他们的/etc/resolv.conf应该是下面这样的：\nnameserver 10.32.0.10 search demo-ns.svc.cluster.local svc.cluster.local cluster.local options ndots:5 我们在demo-svc1中直接请求域名demo-svc2，此时ndots为1，小于配置中的5，因此会触发上面的search规则，这时第一个解析的域名就是demo-svc2.demo-ns.svc.cluster.local，当解析不出来的时候继续下面的demo-svc2.svc.cluster.local、demo-svc2.cluster.local，最后才是直接去解析demo-svc2.。\n注意上面的规则适用于任何一个域名，也就是当我们试图在pod中去访问一个外部域名如tinychen.com的时候也会依次进行上述查询。\n当ndots大于等于options ndots 我们在demo-svc1中直接请求域名demo-svc2.demo-ns.svc.cluster.local，此时的ndots为4，还是会触发上面的search规则。\n而请求域名demo-svc2.demo-ns.svc.cluster.local.，ndots为5，等于配置中的5，因此不会触发上面的search规则，直接去解析demo-svc2.demo-ns.svc.cluster.local.这个域名并返回结果\n如果我们请求更长的域名如POD域名pod-1.demo-svc2.demo-ns.svc.cluster.local.，此时的ndots为6，大于配置中的5，因此也不会触发上面的search规则，会直接查询域名并返回解析\n小结 通过上面的分析我们不难得出下面几点结论：\n同命名空间（namespace）内的服务直接通过$service_name进行互相访问而不需要使用全域名（FQDN），此时DNS解析速度最快； 跨命名空间（namespace）的服务，可以通过$service_name.$namespace_name进行互相访问，此时DNS解析第一次查询失败，第二次才会匹配到正确的域名； 所有的服务之间通过全域名（FQDN）$service_name.$namespace_name.svc.$cluster_name.访问的时候DNS解析的速度最快； 在K8S集群内访问大部分的常见外网域名（ndots小于5）都会触发search规则，因此在访问外部域名的时候可以使用FQDN，即在域名的结尾配置一个点号. 四层服务暴露 对于K8S集群中的服务暴露到集群外部提供服务，一般的方式可以分为四层服务暴露和七层服务暴露，因为前者一般来说是后者的基础，因此这里我们先对四层服务暴露进行介绍。\n在开始之前我们需要明确四层的概念，这里的四层指的是OSI七层模型中的第四层，即TCP、UDP协议所处于的传输层，也就是我们常说的协议+IP+端口层面的负载均衡，常见的四层负载均衡器有LVS、DPVS、Haproxy(四层七层均可)、nginx(四层七层均可)等，在K8S中的四层服务暴露最常用的两种手段就是我们前面提及的Nodeport和LoadBalancer。\n我们先来看下面的这个架构图，注意整个蓝色的点线范围内的是一个K8S集群，为了方便区分，我们这里假设从集群外部进来的流量都是南北流量（客户端-服务器流量），集群内部的流量全部都是东西流量（服务器-服务器流量）。\n我们从下到上开始看起\n图中有frontend、backend、devops等多个命名空间，这是常见的用来隔离不同资源的手段，在实际的落地场景中可以根据不同的业务、使用人群等进行划分，具体的划分维度和标准最好视实际业务情况而定；\n实际上不止是workload，service也是会根据不同的namespace进行划分，包括K8S集群中的大部分api，我们在查找的时候都是需要指定namespace\n在k8s service这一层，图中主要展示了用于集群内部访问的Cluster-IP和Headless两种方式\n需要注意的是Headless服务是和其余三种服务类型相斥的，同时它也不会经过kube-proxy进行负载均衡，因此在Headless服务的蓝色实线框中是空白的，而Cluster-IP中则是kube-proxy组件\n再往上就是每个K8S集群中一般都会有的DNS服务，CoreDNS从K8S的v1.11版本开始可以用来提供命名服务，从v1.13 开始替代 kube-dns成为默认DNS 服务\n在 Kubernetes 1.21 版本中，kubeadm 移除了对将 kube-dns 作为 DNS 应用的支持。 对于 kubeadm v1.24，所支持的唯一的集群 DNS 应用是 CoreDNS\nCoreDNS本身也是一个workload，它是位于kube-system这个命名空间下的一个deployments.apps\nCoreDNS也是通过请求K8S集群内的api-server来获取k8s集群内的服务信息\n再往上就是位于整个K8S集群的边界处，这里首先必然会有一个api-server，它会同时对集群内和集群外暴露控制接口，我们可以通过这个接口来获取K8S集群的信息\napi-server本身并不存储信息，K8S集群本身的集群信息大部分都是存储在etcd服务中，api-server会去etcd中读取相关数据并返回；\n最后就是用来暴露四层服务的服务，一般是NodePort或者是LoadBalancer，因为端口和IP等原因，实际上使用的时候大部分都是以LoadBalancer的方式暴露出去；\nLoadBalancer服务对外暴露的并不是一个IP，而是一个IP+端口，也就是说实际上一个IP的多个端口可以为不同类型的服务提供服务，例如80和443端口提供http/https服务，3306提供数据库服务等；\nK8S并没有内置LoadBalancer，因此需要实现LoadBalancer，目前主流有两种方式：一是使用云厂商如AWS、Azure、阿里腾讯等提供的LoadBalancer，这些LoadBalancer基本都是闭源的解决方案，基本仅适用于他们自家的云环境，但是作为收费服务，在技术支持和售后已经产品成熟度方面均有不错的表现；二是使用已有的一些开源LoadBalancer，主要就是MetalLB、OpenELB以及PureLB，关于三者的详细介绍，之前已经写过相关的文章，有兴趣的可以点击链接进去看看。\n开源的LoadBalancer基本都是拥有两种主要工作模式：Layer2模式和BGP模式。无论是Layer2模式还是BGP模式，核心思路都是通过某种方式将特定VIP的流量引到k8s集群中，然后再通过kube-proxy将流量转发到后面的特定服务。\n七层服务暴露 四层LoadBalancer服务暴露的方式优点是适用范围广，因为工作在四层，因此几乎能适配所有类型的应用。但是也有一些缺点：\n对于大多数应用场景都是http协议的请求来说，并不需要给每个服务都配置一个EXTERNAL-IP来暴露服务，这样一来资源严重浪费（公网IP十分珍贵），二来包括IP地址已经HTTPS使用的证书管理等均十分麻烦 比较常见的场景是对应的每个配置都配置一个域名（virtual host）或者是路由规则（routing rule），然后统一对外暴露一个或者少数几个EXTERNAL-IP，将所有的请求流量都导入到一个统一个集中入口网关（如Nginx），再由这个网关来进行各种负载均衡（load balancing）、路由管理（routing rule）、证书管理（SSL termination）等等 在K8S中，一般交由ingress来完成上述这些事情。\nIngress Ingress 是对集群中服务的外部访问进行管理的 API 对象，典型的访问方式是 HTTP。Ingress 可以提供负载均衡（load balancing）、路由管理（routing rule）、证书管理（SSL termination）等功能。Ingress 公开从集群外部到集群内服务的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源上定义的规则控制。\n下图为K8S官方提供的一个关于ingres工作的示意图：\n这里我们需要注意区分Ingress和Ingress Controllers，两者是两个不同的概念，并不等同\n从概念上来看，Ingress和前面提到的service很像，Ingress本身并不是一个真实存在的工作负载（workload） 而Ingress Controllers更倾向于部署在K8S集群中的一些特殊网关（如NGX），以K8S官方维护的ingress-nginx为例，它本质上其实是一个带有Ingress资源类型的特殊deployments，Ingress Controllers是Ingress的具体实现 Ingress Controllers 上面我们得知所谓的Ingress Controllers本身其实就是特殊的workload（一般是deployment），因此它们本身也是需要通过某种方式暴露到集群外才能提供服务，这里一般都是选择通过LoadBalancer进行四层服务暴露。\n针对上面四层服务暴露的架构图，我们在入口处的LoadBalancer后面加入一个Ingress，就可以得到七层Ingress服务暴露的架构图。\n在这个图中的处理逻辑和四层服务暴露一致，唯一不同的就是HTTP协议的流量是先经过入口处的loadbalancer，再转发到ingress里面，ingress再根据里面的ingress rule来进行判断转发； k8s的ingress-nginx是会和集群内的api-server通信并且获取服务信息，因为Ingress Controllers本身就具有负载均衡的能力，因此在把流量转发到后端的具体服务时，不会经过ClusterIP（就算服务类型是ClusterIP也不经过），而是直接转发到所属的Pod IP上； 总结 到这里关于K8S的基本服务暴露所需要了解的知识就介绍完了，由于K8S本身确实十分复杂，本文在介绍的时候只能蜻蜓点水，随着K8S的不断发展，现在普通的服务暴露已经不能很好的满足部分场景的高端需求，随后又引发了很多诸如服务网格（service mesh）、边车模型（sidecar）、无边车模型（sidecarless）等等的进化。\n","permalink":"http://localhost:9011/20220627-k8s-09-service-discovery-and-traffic-exposure/","summary":"\u003cp\u003e本文主要介绍了K8S集群中的服务发现和流量暴露机制，包括K8S中的workload类型、service类型、DNS解析原理以及四层服务暴露和七层服务暴露的规则。\u003c/p\u003e","title":"k8s系列09-服务发现与流量暴露"},{"content":"本文主要在k8s原生集群上部署v0.6.1版本的PureLB作为k8s的LoadBalancer，主要涉及PureLB的Layer2模式和ECMP模式两种部署方案。由于PureLB的ECMP支持多种路由协议，这里选用的是在k8s中常见的BGP进行配置。由于BGP的相关原理和配置比较复杂，这里仅涉及简单的BGP配置。\n文中使用的k8s集群是在CentOS7系统上基于docker和cilium组件部署v1.23.6版本，此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n工作原理 PureLB的工作原理和其他的负载均衡器（MetalLB、OpenELB）类似，也可以大致分为Layer2模式和BGP模式，但是PureLB的两个模式和（MetalLB/OpenELB）还有着较大的区别。\nMore simply, PureLB either uses the LoadBalancing functionality provided natively by k8s and/or combines k8s LoadBalancing with the routers Equal Cost Multipath (ECMP) load-balancing.\nMetalLB/OpenELB的BGP模式是指通过跑BGP协议实现ECMP从而实现高可用，并且因为MetalLB/OpenELB只支持BGP这一个路由协议，所以称为BGP模式，或者也可以称之为ECMP模式； PureLB会在k8s的宿主机节点上面添加一个新的虚拟网卡，通过这种方式使得我们可以使用Linux的网络栈看到k8s集群中使用的LoadBalancerVIP，同样得益于使用了Linux网络栈，因此PureLB可以使用任意路由协议实现ECMP（BGP、OSPF等），这种模式更倾向于ECMP模式而不止是BGP模式 MetalLB/OpenELB的Layer2模式会把所有的VIP的请求通过ARP/NDP吸引到一台节点上面，所有的流量都会经过这个节点，属于典型的鸡蛋放在一个篮子里 PureLB的Layer2模式也和MetalLB/OpenELB不同，它可以根据单个VIP来选择节点，从而将多个VIP分散到集群中的不同节点上，尽可能的把流量均衡的分散到集群中的每个节点，一定程度上将鸡蛋分散，避免了严重的单点故障 解释PureLB的工作原理比较简单，我们看一下官方的这个架构图：\nInstead of thinking of PureLB as advertising services, think of PureLB as attracting packets to allocated addresses with KubeProxy forwarding those packets within the cluster via the Container Network Interface Network (POD Network) between nodes.\nAllocator：用来监听API中的LoadBalancer类型服务，并且负责分配IP。 LBnodeagent： 作为daemonset部署到每个可以暴露请求并吸引流量的节点上，并且负责监听服务的状态变化同时负责把VIP添加到本地网卡或者是虚拟网卡 KubeProxy：k8s的内置组件，并非是PureLB的一部分，但是PureLB依赖其进行正常工作，当对VIP的请求达到某个具体的节点之后，需要由kube-proxy来负责将其转发到对应的pod 和MetalLB与OpenELB不同，PureLB并不需要自己去发送GARP/GNDP数据包，它执行的操作是把IP添加到k8s集群宿主机的网卡上面。具体来说就是：\n首先正常情况下每个机器上面都有一个本地网卡用于集群之间的常规通信，我们暂且称之为eth0 然后PureLB会在每台机器上面创建一个虚拟网卡，默认名字为kube-lb0 PureLB的allocator监听k8s-api中的LoadBalancer类型服务，并且负责分配IP PureLB的lbnodeagent收到allocator分配的IP之后，开始对这个VIP进行判断 如果这个VIP和k8s宿主机是同网段的，那么会将其添加到本地网卡eth0上，此时我们可以在该节点上使用ip addr show eth0看到这个VIP 如果这个VIP和k8s宿主机是不同网段的，那么会将其添加到虚拟网卡kube-lb0上，此时我们可以在该节点上使用ip addr show kube-lb0看到这个VIP 一般来说Layer2模式的IP是和k8s宿主机节点同网段，ECMP模式是和k8s宿主机节点不同网段 接下来的发送GARP/GNDP数据包、路由协议通信等操作全部交给Linux网络栈自己或者是专门的路由软件（bird、frr等）实现，PureLB不需要参与这个过程 从上面这个逻辑我们不难看出：PureLB在设计实现原理的时候，尽可能地优先使用已有的基础架构设施。这样一来是可以尽可能地减少开发工作量，不必重复造轮子；二来是可以给用户提供尽可能多的接入选择，降低用户的入门门槛。\nLayer2模式 准备工作 在开始部署PureLB之前，我们需要进行一些准备工作，主要就是端口检查和arp参数设置。\nPureLB使用了CRD，原生的k8s集群需要版本不小于1.15才能支持CRD\nPureLB也使用了Memberlist来进行选主，因此需要确保7934端口没有被占用（包括TCP和UDP），否则会出现脑裂的情况\nPureLB uses a library called Memberlist to provide local network address failover faster than standard k8s timeouts would require. If you plan to use local network address and have applied firewalls to your nodes, it is necessary to add a rule to allow the memberlist election to occur. The port used by Memberlist in PureLB is Port 7934 UDP/TCP, memberlist uses both TCP and UDP, open both.\n修改arp参数，和其他的开源LoadBalancer一样，也要把kube-proxy的arp参数设置为严格strictARP: true\n把k8s集群中的ipvs配置打开strictARP之后，k8s集群中的kube-proxy会停止响应kube-ipvs0网卡之外的其他网卡的arp请求。\nstrict ARP开启之后相当于把 将 arp_ignore 设置为 1 并将 arp_announce 设置为 2 启用严格的 ARP，这个原理和LVS中的DR模式对RS的配置一样，可以参考之前的文章中的解释。\n# 查看kube-proxy中的strictARP配置 $ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARP strictARP: false # 手动修改strictARP配置为true $ kubectl edit configmap -n kube-system kube-proxy configmap/kube-proxy edited # 使用命令直接修改并对比不同 $ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e \u0026#34;s/strictARP: false/strictARP: true/\u0026#34; | kubectl diff -f - -n kube-system # 确认无误后使用命令直接修改并生效 $ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e \u0026#34;s/strictARP: false/strictARP: true/\u0026#34; | kubectl apply -f - -n kube-system # 重启kube-proxy确保配置生效 $ kubectl rollout restart ds kube-proxy -n kube-system # 确认配置生效 $ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARP strictARP: true 部署PureLB 老规矩我们还是使用manifest文件进行部署，当然官方还提供了helm等部署方式。\n$ wget https://gitlab.com/api/v4/projects/purelb%2Fpurelb/packages/generic/manifest/0.0.1/purelb-complete.yaml $ kubectl apply -f purelb/purelb-complete.yaml namespace/purelb created customresourcedefinition.apiextensions.k8s.io/lbnodeagents.purelb.io created customresourcedefinition.apiextensions.k8s.io/servicegroups.purelb.io created serviceaccount/allocator created serviceaccount/lbnodeagent created Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ podsecuritypolicy.policy/allocator created podsecuritypolicy.policy/lbnodeagent created role.rbac.authorization.k8s.io/pod-lister created clusterrole.rbac.authorization.k8s.io/purelb:allocator created clusterrole.rbac.authorization.k8s.io/purelb:lbnodeagent created rolebinding.rbac.authorization.k8s.io/pod-lister created clusterrolebinding.rbac.authorization.k8s.io/purelb:allocator created clusterrolebinding.rbac.authorization.k8s.io/purelb:lbnodeagent created deployment.apps/allocator created daemonset.apps/lbnodeagent created error: unable to recognize \u0026#34;purelb/purelb-complete.yaml\u0026#34;: no matches for kind \u0026#34;LBNodeAgent\u0026#34; in version \u0026#34;purelb.io/v1\u0026#34; $ kubectl apply -f purelb/purelb-complete.yaml namespace/purelb unchanged customresourcedefinition.apiextensions.k8s.io/lbnodeagents.purelb.io configured customresourcedefinition.apiextensions.k8s.io/servicegroups.purelb.io configured serviceaccount/allocator unchanged serviceaccount/lbnodeagent unchanged Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ podsecuritypolicy.policy/allocator configured podsecuritypolicy.policy/lbnodeagent configured role.rbac.authorization.k8s.io/pod-lister unchanged clusterrole.rbac.authorization.k8s.io/purelb:allocator unchanged clusterrole.rbac.authorization.k8s.io/purelb:lbnodeagent unchanged rolebinding.rbac.authorization.k8s.io/pod-lister unchanged clusterrolebinding.rbac.authorization.k8s.io/purelb:allocator unchanged clusterrolebinding.rbac.authorization.k8s.io/purelb:lbnodeagent unchanged deployment.apps/allocator unchanged daemonset.apps/lbnodeagent unchanged lbnodeagent.purelb.io/default created 请注意，由于 Kubernetes 的最终一致性架构，此manifest清单的第一个应用程序可能会失败。发生这种情况是因为清单既定义了CRD，又使用该CRD创建了资源。如果发生这种情况，请再次应用manifest清单，应该就会部署成功。\nPlease note that due to Kubernetes\u0026rsquo; eventually-consistent architecture the first application of this manifest can fail. This happens because the manifest both defines a Custom Resource Definition and creates a resource using that definition. If this happens then apply the manifest again and it should succeed because Kubernetes will have processed the definition in the mean time.\n检查一下部署的服务\n$ kubectl get pods -n purelb -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES allocator-5bf9ddbf9b-p976d 1/1 Running 0 2m 10.0.2.140 tiny-cilium-worker-188-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; lbnodeagent-df2hn 1/1 Running 0 2m 10.31.188.12 tiny-cilium-worker-188-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; lbnodeagent-jxn9h 1/1 Running 0 2m 10.31.188.1 tiny-cilium-master-188-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; lbnodeagent-xn8dz 1/1 Running 0 2m 10.31.188.11 tiny-cilium-worker-188-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; $ kubectl get deploy -n purelb NAME READY UP-TO-DATE AVAILABLE AGE allocator 1/1 1 1 10m [root@tiny-cilium-master-188-1 purelb]# kubectl get ds -n purelb NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE lbnodeagent 3 3 3 3 3 kubernetes.io/os=linux 10m $ kubectl get crd | grep purelb lbnodeagents.purelb.io 2022-05-20T06:42:01Z servicegroups.purelb.io 2022-05-20T06:42:01Z $ kubectl get --namespace=purelb servicegroups.purelb.io No resources found in purelb namespace. $ kubectl get --namespace=purelb lbnodeagent.purelb.io NAME AGE default 55m 和MetalLB/OpenELB不一样的是，PureLB使用了另外的一个单独的虚拟网卡kube-lb0而不是默认的kube-ipvs0网卡\n$ ip addr show kube-lb0 15: kube-lb0: \u0026lt;BROADCAST,NOARP,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000 link/ether 12:27:b1:48:4e:3a brd ff:ff:ff:ff:ff:ff inet6 fe80::1027:b1ff:fe48:4e3a/64 scope link valid_lft forever preferred_lft forever 配置purelb 上面部署的时候我们知道purelb主要创建了两个CRD，分别是lbnodeagents.purelb.io和servicegroups.purelb.io\n$ kubectl api-resources --api-group=purelb.io NAME SHORTNAMES APIVERSION NAMESPACED KIND lbnodeagents lbna,lbnas purelb.io/v1 true LBNodeAgent servicegroups sg,sgs purelb.io/v1 true ServiceGroup lbnodeagents.purelb.io 默认情况下已经创建好了一个名为default的lbnodeagent，我们可以看一下它的几个配置项\n$ kubectl describe --namespace=purelb lbnodeagent.purelb.io/default Name: default Namespace: purelb Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; API Version: purelb.io/v1 Kind: LBNodeAgent Metadata: Creation Timestamp: 2022-05-20T06:42:23Z Generation: 1 Managed Fields: API Version: purelb.io/v1 Fields Type: FieldsV1 fieldsV1: f:metadata: f:annotations: .: f:kubectl.kubernetes.io/last-applied-configuration: f:spec: .: f:local: .: f:extlbint: f:localint: Manager: kubectl-client-side-apply Operation: Update Time: 2022-05-20T06:42:23Z Resource Version: 1765489 UID: 59f0ad8c-1024-4432-8f95-9ad574b28fff Spec: Local: Extlbint: kube-lb0 Localint: default Events: \u0026lt;none\u0026gt; 注意上面的Spec:Local:字段中的Extlbint和Localint\nExtlbint字段指定的是PureLB使用的虚拟网卡名称，默认为kube-lb0，如果修改为自定义名称，记得同时修改bird中的配置 Localint字段指定的是用来实际通信的物理网卡，默认情况下会使用正则表达式来匹配，当然也可以自定义，如果集群节点是单网卡机器基本无需修改 servicegroups.purelb.io servicegroups默认情况下并没有创建，需要我们进行手动配置，注意purellb是支持ipv6的，配置方式和ipv4一致，只是这里没有需求就没有单独配置v6pool。\napiVersion: purelb.io/v1 kind: ServiceGroup metadata: name: layer2-ippool namespace: purelb spec: local: v4pool: subnet: \u0026#39;10.31.188.64/26\u0026#39; pool: \u0026#39;10.31.188.64-10.31.188.126\u0026#39; aggregation: /32 然后我们直接部署并检查\n$ kubectl apply -f purelb-ipam.yaml servicegroup.purelb.io/layer2-ippool created $ kubectl get sg -n purelb NAME AGE layer2-ippool 50s $ kubectl describe sg -n purelb Name: layer2-ippool Namespace: purelb Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; API Version: purelb.io/v1 Kind: ServiceGroup Metadata: Creation Timestamp: 2022-05-20T07:58:32Z Generation: 1 Managed Fields: API Version: purelb.io/v1 Fields Type: FieldsV1 fieldsV1: f:metadata: f:annotations: .: f:kubectl.kubernetes.io/last-applied-configuration: f:spec: .: f:local: .: f:v4pool: .: f:aggregation: f:pool: f:subnet: Manager: kubectl-client-side-apply Operation: Update Time: 2022-05-20T07:58:32Z Resource Version: 1774182 UID: 92422ea9-231d-4280-a8b5-ec6c61605dd9 Spec: Local: v4pool: Aggregation: /32 Pool: 10.31.188.64-10.31.188.126 Subnet: 10.31.188.64/26 Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Parsed 4m13s purelb-allocator ServiceGroup parsed successfully 部署service PureLB的部分CRD特性需要我们手动在Service中通过添加注解（annotations）来启用，这里我们只需要指定purelb.io/service-group来确定使用的IP池即可\nannotations: purelb.io/service-group: layer2-ippool 完整的测试服务相关manifest如下：\napiVersion: v1 kind: Namespace metadata: name: nginx-quic --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-lb namespace: nginx-quic spec: selector: matchLabels: app: nginx-lb replicas: 4 template: metadata: labels: app: nginx-lb spec: containers: - name: nginx-lb image: tinychen777/nginx-quic:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: annotations: purelb.io/service-group: layer2-ippool name: nginx-lb-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer --- apiVersion: v1 kind: Service metadata: annotations: purelb.io/service-group: layer2-ippool name: nginx-lb2-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer --- apiVersion: v1 kind: Service metadata: annotations: purelb.io/service-group: layer2-ippool name: nginx-lb3-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer 确认没有问题之后我们直接部署，会创建namespace/nginx-quic、deployment.apps/nginx-lb、service/nginx-lb-service 、service/nginx-lb2-service 、service/nginx-lb3-service 这几个资源\n$ kubectl apply -f nginx-quic-lb.yaml namespace/nginx-quic unchanged deployment.apps/nginx-lb created service/nginx-lb-service created service/nginx-lb2-service created service/nginx-lb3-service created $ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.188.54.81 10.31.188.64 80/TCP 101s nginx-lb2-service LoadBalancer 10.188.34.171 10.31.188.65 80/TCP 101s nginx-lb3-service LoadBalancer 10.188.6.24 10.31.188.66 80/TCP 101s 查看k8s的服务日志就能知道VIP在哪个节点上\n$ kubectl describe service nginx-lb-service -n nginx-quic Name: nginx-lb-service Namespace: nginx-quic Labels: \u0026lt;none\u0026gt; Annotations: purelb.io/allocated-by: PureLB purelb.io/allocated-from: layer2-ippool purelb.io/announcing-IPv4: tiny-cilium-worker-188-11.k8s.tcinternal,eth0 purelb.io/service-group: layer2-ippool Selector: app=nginx-lb Type: LoadBalancer IP Family Policy: SingleStack IP Families: IPv4 IP: 10.188.54.81 IPs: 10.188.54.81 LoadBalancer Ingress: 10.31.188.64 Port: \u0026lt;unset\u0026gt; 80/TCP TargetPort: 80/TCP Endpoints: 10.0.1.45:80,10.0.1.49:80,10.0.2.181:80 + 1 more... Session Affinity: None External Traffic Policy: Cluster Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal AddressAssigned 3m12s purelb-allocator Assigned {Ingress:[{IP:10.31.188.64 Hostname: Ports:[]}]} from pool layer2-ippool Normal AnnouncingLocal 3m8s (x7 over 3m12s) purelb-lbnodeagent Node tiny-cilium-worker-188-11.k8s.tcinternal announcing 10.31.188.64 on interface eth0 $ kubectl describe service nginx-lb2-service -n nginx-quic Name: nginx-lb2-service Namespace: nginx-quic Labels: \u0026lt;none\u0026gt; Annotations: purelb.io/allocated-by: PureLB purelb.io/allocated-from: layer2-ippool purelb.io/announcing-IPv4: tiny-cilium-master-188-1.k8s.tcinternal,eth0 purelb.io/service-group: layer2-ippool Selector: app=nginx-lb Type: LoadBalancer IP Family Policy: SingleStack IP Families: IPv4 IP: 10.188.34.171 IPs: 10.188.34.171 LoadBalancer Ingress: 10.31.188.65 Port: \u0026lt;unset\u0026gt; 80/TCP TargetPort: 80/TCP Endpoints: 10.0.1.45:80,10.0.1.49:80,10.0.2.181:80 + 1 more... Session Affinity: None External Traffic Policy: Cluster Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal AddressAssigned 4m20s purelb-allocator Assigned {Ingress:[{IP:10.31.188.65 Hostname: Ports:[]}]} from pool layer2-ippool Normal AnnouncingLocal 4m17s (x5 over 4m20s) purelb-lbnodeagent Node tiny-cilium-master-188-1.k8s.tcinternal announcing 10.31.188.65 on interface eth0 $ kubectl describe service nginx-lb3-service -n nginx-quic Name: nginx-lb3-service Namespace: nginx-quic Labels: \u0026lt;none\u0026gt; Annotations: purelb.io/allocated-by: PureLB purelb.io/allocated-from: layer2-ippool purelb.io/announcing-IPv4: tiny-cilium-worker-188-11.k8s.tcinternal,eth0 purelb.io/service-group: layer2-ippool Selector: app=nginx-lb Type: LoadBalancer IP Family Policy: SingleStack IP Families: IPv4 IP: 10.188.6.24 IPs: 10.188.6.24 LoadBalancer Ingress: 10.31.188.66 Port: \u0026lt;unset\u0026gt; 80/TCP TargetPort: 80/TCP Endpoints: 10.0.1.45:80,10.0.1.49:80,10.0.2.181:80 + 1 more... Session Affinity: None External Traffic Policy: Cluster Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal AddressAssigned 4m33s purelb-allocator Assigned {Ingress:[{IP:10.31.188.66 Hostname: Ports:[]}]} from pool layer2-ippool Normal AnnouncingLocal 4m29s (x6 over 4m33s) purelb-lbnodeagent Node tiny-cilium-worker-188-11.k8s.tcinternal announcing 10.31.188.66 on interface eth0 我们找一台局域网内的其他机器查看可以发现三个VIP的mac地址并不完全一样，符合上面的日志输出结果\n$ ip neigh | grep 10.31.188.6 10.31.188.65 dev eth0 lladdr 52:54:00:69:0a:ab REACHABLE 10.31.188.64 dev eth0 lladdr 52:54:00:3c:88:cb REACHABLE 10.31.188.66 dev eth0 lladdr 52:54:00:3c:88:cb REACHABLE 我们再查看节点上面的网络地址，除了大家都有的kube-ipvs0网卡上面有所有的VIP，PureLB和MetalLB/OpenELB最大的不同在于PureLB还能在对应节点的物理网卡上面准确地看到对应的Service所属的VIP。\n$ ansible cilium -m command -a \u0026#34;ip addr show eth0\u0026#34; 10.31.188.11 | CHANGED | rc=0 \u0026gt;\u0026gt; 2: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 52:54:00:3c:88:cb brd ff:ff:ff:ff:ff:ff inet 10.31.188.11/16 brd 10.31.255.255 scope global noprefixroute eth0 valid_lft forever preferred_lft forever inet 10.31.188.64/16 brd 10.31.255.255 scope global secondary eth0 valid_lft forever preferred_lft forever inet 10.31.188.66/16 brd 10.31.255.255 scope global secondary eth0 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fe3c:88cb/64 scope link valid_lft forever preferred_lft forever 10.31.188.12 | CHANGED | rc=0 \u0026gt;\u0026gt; 2: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 52:54:00:32:a7:42 brd ff:ff:ff:ff:ff:ff inet 10.31.188.12/16 brd 10.31.255.255 scope global noprefixroute eth0 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fe32:a742/64 scope link valid_lft forever preferred_lft forever 10.31.188.1 | CHANGED | rc=0 \u0026gt;\u0026gt; 2: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 52:54:00:69:0a:ab brd ff:ff:ff:ff:ff:ff inet 10.31.188.1/16 brd 10.31.255.255 scope global noprefixroute eth0 valid_lft forever preferred_lft forever inet 10.31.188.65/16 brd 10.31.255.255 scope global secondary eth0 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fe69:aab/64 scope link valid_lft forever preferred_lft forever 指定VIP 同样的，需要指定IP的话我们可以添加spec:loadBalancerIP:字段来指定VIP\napiVersion: v1 kind: Service metadata: annotations: purelb.io/service-group: layer2-ippool name: nginx-lb4-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer loadBalancerIP: 10.31.188.100 关于nodeport PureLB支持allocateLoadBalancerNodePorts特性，可以通过设置allocateLoadBalancerNodePorts: false来关闭自动为LoadBalancer服务分配nodeport这个功能。\nECMP模式 因为purelb使用了Linux的网络栈，因此在ECMP的实现这一块就有更多的选择，这里我们参考官方的实现方案，使用BGP+Bird的方案来实现。\nIP Hostname 10.31.188.1 tiny-cilium-master-188-1.k8s.tcinternal 10.31.188.11 tiny-cilium-worker-188-11.k8s.tcinternal 10.31.188.12 tiny-cilium-worker-188-12.k8s.tcinternal 10.188.0.0/18 serviceSubnet 10.31.254.251 BGP-Router(frr) 10.189.0.0/16 PuerLB-BGP-IPpool 其中PureLB的ASN是64515，路由器的ASN为64512。\n准备工作 我们先把官方的GitHub仓库拉到本地，然后实际上我们部署需要的配置文件只有bird-cm.yml和bird.yml这两个即可。\n$ git clone https://gitlab.com/purelb/bird_router.git $ ls bird*yml bird-cm.yml bird.yml 接下来我们对其进行一些修改，首先是configmap文件bird-cm.yml，我们只需要修改description、as、neighbor这三个字段：\ndescription：建立BGP连接的路由器的描述，一般我习惯命名为IP的数字加横杠 as：自己的ASN neighbor：建立BGP连接的路由器的IP地址 namespace：官方默认新建了一个router的namespace来管理，这里我们为了方便统一到purelb apiVersion: v1 kind: ConfigMap metadata: name: bird-cm namespace: purelb # 中间略过一堆配置 protocol bgp uplink1 { description \u0026#34;10-31-254-251\u0026#34;; local k8sipaddr as 64515; neighbor 10.31.254.251 external; ipv4 {\t# IPv4 unicast (1/1) # RTS_DEVICE matches routes added to kube-lb0 by protocol device export where source ~ [ RTS_STATIC, RTS_BGP, RTS_DEVICE ]; import filter bgp_reject; # we are only advertizing }; ipv6 {\t# IPv6 unicast # RTS_DEVICE matches routes added to kube-lb0 by protocol device export where source ~ [ RTS_STATIC, RTS_BGP, RTS_DEVICE ]; import filter bgp_reject; }; } 接下来是bird的daemonset配置文件，这里不一定要根据我的步骤修改，大家可以按照实际需求来处理\nnamespace：官方默认新建了一个router的namespace来管理，这里我们为了方便统一到purelb imagePullPolicy：官方默认是Always，这里我们修改为IfNotPresent apiVersion: apps/v1 kind: DaemonSet metadata: name: bird namespace: purelb # 中间略过一堆配置 image: registry.gitlab.com/purelb/bird_router:latest imagePullPolicy: IfNotPresent 部署bird 部署的话非常简单，直接部署上面的两个配置文件即可，注意上面我们把namespace修改为了purelb，因此这里创建namespace这一步可以省略\n# Create the router namespace $ kubectl create namespace router # Apply the edited configmap $ kubectl apply -f bird-cm.yml # Deploy the Bird Router $ kubectl apply -f bird.yml 接着我们检查一下部署的状态\n$ kubectl get ds -n purelb NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE bird 2 2 2 0 2 \u0026lt;none\u0026gt; 27m lbnodeagent 3 3 3 3 3 kubernetes.io/os=linux 42h $ kubectl get cm -n purelb NAME DATA AGE bird-cm 1 28m kube-root-ca.crt 1 42h $ kubectl get pods -n purelb NAME READY STATUS RESTARTS AGE allocator-5bf9ddbf9b-p976d 1/1 Running 0 42h bird-4qtrm 1/1 Running 0 16s bird-z9cq2 1/1 Running 0 49s lbnodeagent-df2hn 1/1 Running 0 42h lbnodeagent-jxn9h 1/1 Running 0 42h lbnodeagent-xn8dz 1/1 Running 0 42h 默认情况下bird不会调度到master节点，这样可以保证master节点不参与到ECMP的负载均衡中，减少master节点上面的网络流量从而提高master的稳定性\n如果想让master也参与到ECMP中，可以在bird.yaml的daemonset配置中新增如下配置\ntolerations: - effect: NoSchedule key: node-role.kubernetes.io/master 配置路由器 路由器我们还是使用frr来进行配置\nroot@tiny-openwrt-plus:~# cat /etc/frr/frr.conf frr version 8.2.2 frr defaults traditional hostname tiny-openwrt-plus log file /home/frr/frr.log log syslog password zebra ! router bgp 64512 bgp router-id 10.31.254.251 no bgp ebgp-requires-policy ! neighbor 10.31.188.11 remote-as 64515 neighbor 10.31.188.11 description 10-31-188-11 neighbor 10.31.188.12 remote-as 64515 neighbor 10.31.188.12 description 10-31-188-12 ! ! address-family ipv4 unicast !maximum-paths 3 exit-address-family exit ! access-list vty seq 5 permit 127.0.0.0/8 access-list vty seq 10 deny any ! line vty access-class vty exit ! 配置完成之后我们重启服务，然后查看路由器这端的BGP状态，这时候看到和两个worker节点之间的BGP状态建立正常就说明配置没有问题\ntiny-openwrt-plus# show ip bgp summary IPv4 Unicast Summary (VRF default): BGP router identifier 10.31.254.251, local AS number 64512 vrf-id 0 Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc 10.31.188.11 4 64515 3 4 0 0 0 00:00:13 0 3 10-31-188-11 10.31.188.12 4 64515 3 4 0 0 0 00:00:13 0 3 10-31-188-12 创建ServiceGroup 我们还需要给BGP模式创建一个ServiceGroup，用于管理BGP网段的IP，建议IP段使用和k8s的宿主机节点不同网段的IP\napiVersion: purelb.io/v1 kind: ServiceGroup metadata: name: bgp-ippool namespace: purelb spec: local: v4pool: subnet: \u0026#39;10.189.0.0/16\u0026#39; pool: \u0026#39;10.189.0.0-10.189.255.254\u0026#39; aggregation: /32 完成之后我们直接部署并检查\n$ kubectl apply -f purelb-sg-bgp.yaml servicegroup.purelb.io/bgp-ippool created $ kubectl get sg -n purelb NAME AGE bgp-ippool 7s layer2-ippool 41h 部署测试服务 这里我们还是直接使用上面已经创建的nginx-lb这个deployments，然后直接新建两个service进行测试\napiVersion: v1 kind: Service metadata: annotations: purelb.io/service-group: bgp-ippool name: nginx-lb5-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer --- apiVersion: v1 kind: Service metadata: annotations: purelb.io/service-group: bgp-ippool name: nginx-lb6-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer loadBalancerIP: 10.189.100.100 此时我们检查部署的状态\n$ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.188.54.81 10.31.188.64 80/TCP 40h nginx-lb2-service LoadBalancer 10.188.34.171 10.31.188.65 80/TCP 40h nginx-lb3-service LoadBalancer 10.188.6.24 10.31.188.66 80/TCP 40h nginx-lb4-service LoadBalancer 10.188.50.164 10.31.188.100 80/TCP 40h nginx-lb5-service LoadBalancer 10.188.7.75 10.189.0.0 80/TCP 11s nginx-lb6-service LoadBalancer 10.188.27.208 10.189.100.100 80/TCP 11s 再使用curl进行测试\n[root@tiny-centos7-100-2 ~]# curl 10.189.100.100 10.0.1.47:57768 [root@tiny-centos7-100-2 ~]# curl 10.189.100.100 10.0.1.47:57770 [root@tiny-centos7-100-2 ~]# curl 10.189.100.100 10.31.188.11:47439 [root@tiny-centos7-100-2 ~]# curl 10.189.100.100 10.31.188.11:33964 [root@tiny-centos7-100-2 ~]# curl 10.189.100.100 10.0.1.47:57776 [root@tiny-centos7-100-2 ~]# curl 10.189.100.100 10.0.1.47:57778 [root@tiny-centos7-100-2 ~]# curl 10.189.0.0 10.31.188.12:53078 [root@tiny-centos7-100-2 ~]# curl 10.189.0.0 10.0.2.151:59660 [root@tiny-centos7-100-2 ~]# curl 10.189.0.0 10.0.2.151:59662 [root@tiny-centos7-100-2 ~]# curl 10.189.0.0 10.31.188.12:21972 [root@tiny-centos7-100-2 ~]# curl 10.189.0.0 10.31.188.12:28855 [root@tiny-centos7-100-2 ~]# curl 10.189.0.0 10.0.2.151:59668 然后我们再查看kube-lb0网卡上面的IP信息，可以看到每台节点上面都有两个BGP模式的LoadBalancer的IP\n[tinychen /root/ansible]# ansible cilium -m command -a \u0026#34;ip addr show kube-lb0\u0026#34; 10.31.188.11 | CHANGED | rc=0 \u0026gt;\u0026gt; 19: kube-lb0: \u0026lt;BROADCAST,NOARP,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000 link/ether d6:65:b8:31:18:ce brd ff:ff:ff:ff:ff:ff inet 10.189.0.0/32 scope global kube-lb0 valid_lft forever preferred_lft forever inet 10.189.100.100/32 scope global kube-lb0 valid_lft forever preferred_lft forever inet6 fe80::d465:b8ff:fe31:18ce/64 scope link valid_lft forever preferred_lft forever 10.31.188.12 | CHANGED | rc=0 \u0026gt;\u0026gt; 21: kube-lb0: \u0026lt;BROADCAST,NOARP,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000 link/ether aa:10:d5:cd:2b:98 brd ff:ff:ff:ff:ff:ff inet 10.189.0.0/32 scope global kube-lb0 valid_lft forever preferred_lft forever inet 10.189.100.100/32 scope global kube-lb0 valid_lft forever preferred_lft forever inet6 fe80::a810:d5ff:fecd:2b98/64 scope link valid_lft forever preferred_lft forever 10.31.188.1 | CHANGED | rc=0 \u0026gt;\u0026gt; 15: kube-lb0: \u0026lt;BROADCAST,NOARP,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000 link/ether 12:27:b1:48:4e:3a brd ff:ff:ff:ff:ff:ff inet 10.189.0.0/32 scope global kube-lb0 valid_lft forever preferred_lft forever inet 10.189.100.100/32 scope global kube-lb0 valid_lft forever preferred_lft forever inet6 fe80::1027:b1ff:fe48:4e3a/64 scope link valid_lft forever preferred_lft forever 最后我们查看路由器上面的路由表，可以确定ECMP开启成功\ntiny-openwrt-plus# show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR, f - OpenFabric, \u0026gt; - selected route, * - FIB route, q - queued, r - rejected, b - backup t - trapped, o - offload failure K\u0026gt;* 0.0.0.0/0 [0/0] via 10.31.254.254, eth0, 00:08:51 C\u0026gt;* 10.31.0.0/16 is directly connected, eth0, 00:08:51 B\u0026gt;* 10.189.0.0/32 [20/0] via 10.31.188.11, eth0, weight 1, 00:00:19 * via 10.31.188.12, eth0, weight 1, 00:00:19 B\u0026gt;* 10.189.100.100/32 [20/0] via 10.31.188.11, eth0, weight 1, 00:00:19 * via 10.31.188.12, eth0, weight 1, 00:00:19 总结 PureLB和前面我们提到过的MetalLB以及OpenELB有着非常大的不同，尽管三者的主要工作模式都是分为Layer2模式和BGP模式。还是老规矩，我们先来看两种工作模式的优缺点，再来总结PureLB。\nLayer2 mode优缺点 优点：\n通用性强，对比BGP模式不需要BGP路由器支持，几乎可以适用于任何网络环境；当然云厂商的网络环境例外 VIP会被分散到多个节点上面，解决了MetalLB和OpenELB的Layer2模式下的流量单点瓶颈问题 使用了Linux网络栈，可以通过iproute之类的命令直接查看到vip所在的节点 缺点：\n当VIP所在节点宕机之后，需要较长时间进行故障转移（官方没说多久），PureLB和MetalLB一样都使用了memberlist来进行选主（并表示此举更优），当VIP所在节点宕机之后重新选主的时间要比传统的keepalived使用的vrrp协议（一般为1s）要更长 改进方案：\n有条件的可以考虑使用BGP模式 可以针对一个负载workload创建多个service，并对外暴露多个VIP，由于PureLB会把VIP分散到多个节点上，这样可以一定程度上实现高可用 既不能用BGP模式也不能接受Layer2模式的，基本和目前主流的三个开源负载均衡器无缘了（三者都是Layer2模式和BGP模式且原理类似，优缺点相同） ECMP mode优缺点 ECMP模式的优缺点几乎和Layer2模式相反\n优点：\n无单点故障，在开启ECMP的前提下，k8s集群内所有的节点都有请求流量，都会参与负载均衡并转发请求 支持了Linux网络栈，因此可以使用bird、quagga、frr等各种路由软件实现标准的路由协议 缺点：\n条件苛刻，需要有特殊路由器支持，配置起来也更复杂； ECMP的故障转移（failover）并不是特别地优雅，这个问题的严重程度取决于使用的ECMP算法；当集群的节点出现变动导致BGP连接出现变动，所有的连接都会进行重新哈希（使用三元组或五元组哈希），这对一些服务来说可能会有影响； 路由器中使用的哈希值通常 不稳定，因此每当后端集的大小发生变化时（例如，当一个节点的 BGP 会话关闭时），现有的连接将被有效地随机重新哈希，这意味着大多数现有的连接最终会突然被转发到不同的后端，而这个后端可能和此前的后端毫不相干且不清楚上下文状态信息。\n改进方案：\nPureLB官方只简单提及了使用路由协议的一些问题：\nDepending on the router and its configuration, load balancing techniques will vary however they are all generally based upon a 4 tuple hash of sourceIP, sourcePort, destinationIP, destinationPort. The router will also have a limit to the number of ECMP paths that can be used, in modern TOR switches, this can be set to a size larger than a /24 subnet, however in old routers, the count can be less than 10. This needs to be considered in the infrastructure design and PureLB combined with routing software can help create a design that avoids this limitation. Another important consideration can be how the router load balancer cache is populated and updated when paths are removed, again modern devices provide better behavior.\n不过由于都是使用ECMP，我们可以参考MetalLB官方给出的资料，下面是MetalLB给出的一些改进方案，列出来给大家参考一下\n使用更稳定的ECMP算法来减少后端变动时对现有连接的影响，如“resilient ECMP” or “resilient LAG” 将服务部署到特定的节点上减少可能带来的影响 在流量低峰期进行变更 将服务分开部署到两个不同的LoadBalanceIP的服务中，然后利用DNS进行流量切换 在客户端加入透明的用户无感的重试逻辑 在LoadBalance后面加入一层ingress来实现更优雅的failover（但是并不是所有的服务都可以使用ingress） 接受现实……（Accept that there will be occasional bursts of reset connections. For low-availability internal services, this may be acceptable as-is.） PureLB优缺点 这里尽量客观的总结概况一些客观事实，是否为优缺点可能会因人而异：\nPureLB使用了CRD来实现更优秀的IPAM，也是三者中唯一一个支持外置IPAM的 PureLB对Linux网络栈有更好的支持（可以使用iproute等工具查看LoadBalancerVIP） PureLB可以使用任意路由协议实现ECMP（BGP、OSPF等） PureLB和使用BGP模式的CNI集成更加方便 PureLB的社区热度不如MetalLB和OpenELB，也没有加入CNCF，只表示CNCF提供了一个slack通道给用户进行交流（The CNCF have generously provided the PureLB community a Slack Channel in the Kubernetes workspace.） PureLB的文档相对齐全，但是还是有些小纰漏 PureLB的Layer2模式不存在单点流量瓶颈 总的来说PureLB是一款非常不错的云原生负载均衡器，在软件本身的设计模式上面应该是参考了MetalLB等前辈的思路，同时又青出于蓝而胜于蓝。唯一美中不足的是社区热度不高，让人有些担心这个项目以后的发展情况。如果在三者中选一个使用layer2模式的话，个人推荐首选PureLB；如果是使用BGP模式，则建议结合自己的CNI组件和IPAM等情况综合考虑。\n","permalink":"http://localhost:9011/20220524-k8s-08-loadbalancer-purelb/","summary":"\u003cp\u003e本文主要在k8s原生集群上部署\u003ccode\u003ev0.6.1\u003c/code\u003e版本的\u003ccode\u003ePureLB\u003c/code\u003e作为k8s的\u003ccode\u003eLoadBalancer\u003c/code\u003e，主要涉及\u003cstrong\u003ePureLB\u003c/strong\u003e的\u003cstrong\u003eLayer2模式\u003c/strong\u003e和\u003cstrong\u003eECMP模式\u003c/strong\u003e两种部署方案。由于PureLB的ECMP支持多种路由协议，这里选用的是在k8s中常见的BGP进行配置。由于BGP的相关原理和配置比较复杂，这里仅涉及简单的BGP配置。\u003c/p\u003e\n\u003cp\u003e文中使用的k8s集群是在CentOS7系统上基于\u003ccode\u003edocker\u003c/code\u003e和\u003ccode\u003ecilium\u003c/code\u003e组件部署\u003ccode\u003ev1.23.6\u003c/code\u003e版本，此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列08-负载均衡器之PureLB"},{"content":"本文主要在k8s原生集群上部署v0.4.4版本的OpenELB作为k8s的LoadBalancer，主要涉及OpenELB的Layer2模式和BGP模式两种部署方案。由于BGP的相关原理和配置比较复杂，这里仅涉及简单的BGP配置。\n文中使用的k8s集群是在CentOS7系统上基于docker和calico组件部署v1.23.6版本，此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n工作原理 简介 OpenELB 是一个开源的云原生负载均衡器实现，可以在基于裸金属服务器、边缘以及虚拟化的 Kubernetes 环境中使用 LoadBalancer 类型的 Service 对外暴露服务。OpenELB 项目最初由 KubeSphere 社区 发起，目前已作为 CNCF 沙箱项目 加入 CNCF 基金会，由 OpenELB 开源社区维护与支持。\n与MetalLB类似，OpenELB也拥有两种主要工作模式：Layer2模式和BGP模式。OpenELB的BGP模式目前暂不支持IPv6。\n无论是Layer2模式还是BGP模式，核心思路都是通过某种方式将特定VIP的流量引到k8s集群中，然后再通过kube-proxy将流量转发到后面的特定服务。\nLayer2模式 Layer2模式需要我们的k8s集群基础环境支持发送anonymous ARP/NDP packets。因为OpenELB是针对裸金属服务器设计的，因此如果是在云环境中部署，需要注意是否满足条件。\n下图来自OpenELB官方，这里简单阐述一下Layer2模式的工作原理：\n图中有一个类型为LoadBalancer的Service，其VIP为192.168.0.91（和k8s的节点相同网段），后端有两个pod（分别为pod1和pod2） 安装在 Kubernetes 集群中的 OpenELB 随机选择一个节点（图中为 worker 1）来处理 Service 请求。当局域网中出现arp request数据包来查询192.168.0.91的mac地址的时候，OpenELB会进行回应（使用 worker 1 的 MAC 地址），此时路由器（也可能是交换机）将 Service 的VIP 192.168.0.91和 worker 1 的 MAC 地址绑定，之后所有请求到192.168.0.91的数据包都会被转发到worker1上 Service 流量到达 worker 1 后， worker 1 上的 kube-proxy 将流量转发到后端的两个pod进行负载均衡，这些pod不一定在work1上 主要的工作流程就如同上面描述的一般，但是还有几个需要额外注意的点：\n如果 worker 1 出现故障，OpenELB 会重新向路由器发送 APR/NDP 数据包，将 Service IP 地址映射到 worker 2 的 MAC 地址，Service 流量切换到 worker 2 主备切换过程并不是瞬间完成的，中间会产生一定时间的服务中断（具体多久官方也没说，实际上应该是却决于检测到节点宕机的时间加上重新选主的时间） 如果集群中已经部署了多个 openelb-manager 副本，OpenELB 使用 Kubernetes 的领导者选举特性算法来进行选主，从而确保只有一个副本响应 ARP/NDP 请求 BGP模式 OpenELB的BGP模式使用的是gobgp实现的BGP协议，通过使用BGP协议和路由器建立BGP连接并实现ECMP负载均衡，从而实现高可用的LoadBalancer。\n我们还是借用官网的图来解释一下这个流程，注意BGP模式暂不支持IPv6。\n图中有一个类型为LoadBalancer的Service，其VIP为172.22.0.2（和k8s的节点不同网段），后端有两个pod（分别为pod1和pod2） 安装在 Kubernetes 集群中的 OpenELB 与 BGP 路由器建立 BGP 连接，并将去往 172.22.0.2 的路由发布到 BGP 路由器，在配置得当的情况下，路由器上面的路由表可以看到 172.22.0.2 这个VIP的下一条有多个节点（均为k8s的宿主机节点） 当外部客户端机器尝试访问 Service 时，BGP 路由器根据从 OpenELB 获取的路由，在 master、worker 1 和 worker 2 节点之间进行流量负载均衡。Service 流量到达一个节点后，该节点上的 kube-proxy 将流量转发到后端的两个pod进行负载均衡，这些pod不一定在该节点上 Layer2 Mode 配置ARP参数 部署Layer2模式需要把k8s集群中的ipvs配置打开strictARP，开启之后k8s集群中的kube-proxy会停止响应kube-ipvs0网卡之外的其他网卡的arp请求，而由MetalLB接手处理。\nstrict ARP开启之后相当于把 将 arp_ignore 设置为 1 并将 arp_announce 设置为 2 启用严格的 ARP，这个原理和LVS中的DR模式对RS的配置一样，可以参考之前的文章中的解释。\nstrict ARP configure arp_ignore and arp_announce to avoid answering ARP queries from kube-ipvs0 interface\n# 查看kube-proxy中的strictARP配置 $ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARP strictARP: false # 手动修改strictARP配置为true $ kubectl edit configmap -n kube-system kube-proxy configmap/kube-proxy edited # 使用命令直接修改并对比不同 $ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e \u0026#34;s/strictARP: false/strictARP: true/\u0026#34; | kubectl diff -f - -n kube-system # 确认无误后使用命令直接修改并生效 $ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e \u0026#34;s/strictARP: false/strictARP: true/\u0026#34; | kubectl apply -f - -n kube-system # 重启kube-proxy确保配置生效 $ kubectl rollout restart ds kube-proxy -n kube-system # 确认配置生效 $ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARP strictARP: true 部署openelb 这里我们还是使用yaml进行部署，官方把所有部署的资源整合到了一个文件中，我们还是老规矩先下载到本地再进行部署\n$ wget https://raw.githubusercontent.com/openelb/openelb/master/deploy/openelb.yaml $ kubectl apply -f openelb.yaml namespace/openelb-system created customresourcedefinition.apiextensions.k8s.io/bgpconfs.network.kubesphere.io created customresourcedefinition.apiextensions.k8s.io/bgppeers.network.kubesphere.io created customresourcedefinition.apiextensions.k8s.io/eips.network.kubesphere.io created serviceaccount/kube-keepalived-vip created serviceaccount/openelb-admission created role.rbac.authorization.k8s.io/leader-election-role created role.rbac.authorization.k8s.io/openelb-admission created clusterrole.rbac.authorization.k8s.io/kube-keepalived-vip created clusterrole.rbac.authorization.k8s.io/openelb-admission created clusterrole.rbac.authorization.k8s.io/openelb-manager-role created rolebinding.rbac.authorization.k8s.io/leader-election-rolebinding created rolebinding.rbac.authorization.k8s.io/openelb-admission created clusterrolebinding.rbac.authorization.k8s.io/kube-keepalived-vip created clusterrolebinding.rbac.authorization.k8s.io/openelb-admission created clusterrolebinding.rbac.authorization.k8s.io/openelb-manager-rolebinding created service/openelb-admission created deployment.apps/openelb-manager created job.batch/openelb-admission-create created job.batch/openelb-admission-patch created mutatingwebhookconfiguration.admissionregistration.k8s.io/openelb-admission created validatingwebhookconfiguration.admissionregistration.k8s.io/openelb-admission created 接下来我们看看都部署了什么CRD资源，这几个CRD资源主要就是方便我们管理openelb，这也是OpenELB相对MetalLB的优势。\n$ kubectl get crd NAME CREATED AT bgpconfs.network.kubesphere.io 2022-05-19T06:37:19Z bgppeers.network.kubesphere.io 2022-05-19T06:37:19Z eips.network.kubesphere.io 2022-05-19T06:37:19Z $ kubectl get ns openelb-system -o wide NAME STATUS AGE openelb-system Active 2m27s 实际上主要工作的负载就是这两个jobs.batch和这一个deployment\n$ kubectl get pods -n openelb-system NAME READY STATUS RESTARTS AGE openelb-admission-create-57tzm 0/1 Completed 0 5m11s openelb-admission-patch-j5pl4 0/1 Completed 0 5m11s openelb-manager-5cdc8697f9-h2wd6 1/1 Running 0 5m11s $ kubectl get deploy -n openelb-system NAME READY UP-TO-DATE AVAILABLE AGE openelb-manager 1/1 1 1 5m38s $ kubectl get jobs.batch -n openelb-system NAME COMPLETIONS DURATION AGE openelb-admission-create 1/1 11s 11m openelb-admission-patch 1/1 12s 11m 创建EIP 接下来我们需要配置loadbalancerIP所在的网段资源，这里我们创建一个Eip对象来进行定义，后面对IP段的管理也是在这里进行。\napiVersion: network.kubesphere.io/v1alpha2 kind: Eip metadata: # Eip 对象的名称。 name: eip-layer2-pool spec: # Eip 对象的地址池 address: 10.31.88.101-10.31.88.200 # openELB的运行模式，默认为bgp protocol: layer2 # OpenELB 在其上侦听 ARP/NDP 请求的网卡。该字段仅在protocol设置为时有效layer2。 interface: eth0 # 指定是否禁用 Eip 对象 # false表示可以继续分配 # true表示不再继续分配 disable: false status: # 指定 Eip 对象中的IP地址是否已用完。 occupied: false # 指定 Eip 对象中有多少个 IP 地址已分配给服务。 # 直接留空，系统会自动生成 usage: # Eip 对象中的 IP 地址总数。 poolSize: 100 # 指定使用的 IP 地址和使用 IP 地址的服务。服务以Namespace/Service name格式显示（例如，default/test-svc）。 # 直接留空，系统会自动生成 used: # Eip 对象中的第一个 IP 地址。 firstIP: 10.31.88.101 # Eip 对象中的最后一个 IP 地址。 lastIP: 10.31.88.200 ready: true # 指定IP协议栈是否为 IPv4。目前，OpenELB 仅支持 IPv4，其值只能是true. v4: true 配置完成后我们直接部署即可\n$ kubectl apply -f openelb/openelb-eip.yaml eip.network.kubesphere.io/eip-layer2-pool created 部署完成后检查eip的状态\n$ kubectl get eip NAME CIDR USAGE TOTAL eip-layer2-pool 10.31.88.101-10.31.88.200 100 创建测试服务 然后我们创建对应的服务\napiVersion: v1 kind: Namespace metadata: name: nginx-quic --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-lb namespace: nginx-quic spec: selector: matchLabels: app: nginx-lb replicas: 4 template: metadata: labels: app: nginx-lb spec: containers: - name: nginx-lb image: tinychen777/nginx-quic:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-lb-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer --- apiVersion: v1 kind: Service metadata: name: nginx-lb2-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer --- apiVersion: v1 kind: Service metadata: name: nginx-lb3-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer 然后我们检查部署状态：\n$ kubectl apply -f nginx-quic-lb.yaml namespace/nginx-quic unchanged deployment.apps/nginx-lb created service/nginx-lb-service created service/nginx-lb2-service created service/nginx-lb3-service created $ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.88.17.248 10.31.88.101 80:30643/TCP 58m nginx-lb2-service LoadBalancer 10.88.13.220 10.31.88.102 80:31114/TCP 58m nginx-lb3-service LoadBalancer 10.88.18.110 10.31.88.103 80:30485/TCP 58m 此时我们再查看eip的状态可以看到新部署的三个LoadBalancer服务：\n$ kubectl get eip NAME CIDR USAGE TOTAL eip-layer2-pool 10.31.88.101-10.31.88.200 3 100 [root@tiny-calico-master-88-1 tiny-calico]# kubectl get eip -o yaml apiVersion: v1 items: - apiVersion: network.kubesphere.io/v1alpha2 kind: Eip metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {\u0026#34;apiVersion\u0026#34;:\u0026#34;network.kubesphere.io/v1alpha2\u0026#34;,\u0026#34;kind\u0026#34;:\u0026#34;Eip\u0026#34;,\u0026#34;metadata\u0026#34;:{\u0026#34;annotations\u0026#34;:{},\u0026#34;name\u0026#34;:\u0026#34;eip-layer2-pool\u0026#34;},\u0026#34;spec\u0026#34;:{\u0026#34;address\u0026#34;:\u0026#34;10.31.88.101-10.31.88.200\u0026#34;,\u0026#34;disable\u0026#34;:false,\u0026#34;interface\u0026#34;:\u0026#34;eth0\u0026#34;,\u0026#34;protocol\u0026#34;:\u0026#34;layer2\u0026#34;},\u0026#34;status\u0026#34;:{\u0026#34;firstIP\u0026#34;:\u0026#34;10.31.88.101\u0026#34;,\u0026#34;lastIP\u0026#34;:\u0026#34;10.31.88.200\u0026#34;,\u0026#34;occupied\u0026#34;:false,\u0026#34;poolSize\u0026#34;:100,\u0026#34;ready\u0026#34;:true,\u0026#34;usage\u0026#34;:1,\u0026#34;used\u0026#34;:{\u0026#34;10.31.88.101\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;},\u0026#34;v4\u0026#34;:true}} creationTimestamp: \u0026#34;2022-05-19T08:21:58Z\u0026#34; finalizers: - finalizer.ipam.kubesphere.io/v1alpha1 generation: 2 name: eip-layer2-pool resourceVersion: \u0026#34;1623927\u0026#34; uid: 9b091518-7b64-43ae-9fd4-abaf50563160 spec: address: 10.31.88.101-10.31.88.200 interface: eth0 protocol: layer2 status: firstIP: 10.31.88.101 lastIP: 10.31.88.200 poolSize: 100 ready: true usage: 3 used: 10.31.88.101: nginx-quic/nginx-lb-service 10.31.88.102: nginx-quic/nginx-lb2-service 10.31.88.103: nginx-quic/nginx-lb3-service v4: true kind: List metadata: resourceVersion: \u0026#34;\u0026#34; selfLink: \u0026#34;\u0026#34; 关于nodeport openELB似乎并不支持allocateLoadBalancerNodePorts字段，在指定了allocateLoadBalancerNodePorts为false的情况下还是为服务创建了nodeport，查看部署后的配置可以发现参数被修改回了true，并且无法修改为false。\n[root@tiny-calico-master-88-1 tiny-calico]# kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.88.17.248 10.31.88.101 80:30643/TCP 106m nginx-lb2-service LoadBalancer 10.88.13.220 10.31.88.102 80:31114/TCP 106m nginx-lb3-service LoadBalancer 10.88.18.110 10.31.88.113 80:30485/TCP 106m [root@tiny-calico-master-88-1 tiny-calico]# kubectl get svc -n nginx-quic nginx-lb-service -o yaml | grep \u0026#34;allocateLoadBalancerNodePorts:\u0026#34; allocateLoadBalancerNodePorts: true 关于VIP 如何查找VIP Layer2模式下，所有的k8s节点的kube-ipvs0接口上都会看到所有的VIP，因此确定VIP的节点还是和MetalLB一样，需要通过查看pod中的日志，或者是看arp表来确定\n$ kubectl logs -f -n openelb-system openelb-manager-5cdc8697f9-h2wd6 ...省略一堆日志输出... {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652949142.6426723,\u0026#34;logger\u0026#34;:\u0026#34;IPAM\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;assignIP\u0026#34;,\u0026#34;args\u0026#34;:{\u0026#34;Key\u0026#34;:\u0026#34;nginx-quic/nginx-lb3-service\u0026#34;,\u0026#34;Addr\u0026#34;:\u0026#34;10.31.88.113\u0026#34;,\u0026#34;Eip\u0026#34;:\u0026#34;eip-layer2-pool\u0026#34;,\u0026#34;Protocol\u0026#34;:\u0026#34;layer2\u0026#34;,\u0026#34;Unalloc\u0026#34;:false},\u0026#34;result\u0026#34;:{\u0026#34;Addr\u0026#34;:\u0026#34;10.31.88.113\u0026#34;,\u0026#34;Eip\u0026#34;:\u0026#34;eip-layer2-pool\u0026#34;,\u0026#34;Protocol\u0026#34;:\u0026#34;layer2\u0026#34;,\u0026#34;Sp\u0026#34;:{}},\u0026#34;err\u0026#34;:null} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652949142.65095,\u0026#34;logger\u0026#34;:\u0026#34;arpSpeaker\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;map ingress ip\u0026#34;,\u0026#34;ingress\u0026#34;:\u0026#34;10.31.88.113\u0026#34;,\u0026#34;nodeIP\u0026#34;:\u0026#34;10.31.88.1\u0026#34;,\u0026#34;nodeMac\u0026#34;:\u0026#34;52:54:00:74:eb:11\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652949142.6509972,\u0026#34;logger\u0026#34;:\u0026#34;arpSpeaker\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;send gratuitous arp packet\u0026#34;,\u0026#34;eip\u0026#34;:\u0026#34;10.31.88.113\u0026#34;,\u0026#34;nodeIP\u0026#34;:\u0026#34;10.31.88.1\u0026#34;,\u0026#34;hwAddr\u0026#34;:\u0026#34;52:54:00:74:eb:11\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652949142.6510363,\u0026#34;logger\u0026#34;:\u0026#34;arpSpeaker\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;send gratuitous arp packet\u0026#34;,\u0026#34;eip\u0026#34;:\u0026#34;10.31.88.113\u0026#34;,\u0026#34;nodeIP\u0026#34;:\u0026#34;10.31.88.1\u0026#34;,\u0026#34;hwAddr\u0026#34;:\u0026#34;52:54:00:74:eb:11\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652949142.6849823,\u0026#34;msg\u0026#34;:\u0026#34;setup openelb service\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb3-service\u0026#34;} 查看arp表来确定MAC地址从而确定VIP所在的节点\n$ ip neigh | grep 10.31.88.113 10.31.88.113 dev eth0 lladdr 52:54:00:74:eb:11 STALE $ arp -a | grep 10.31.88.113 ? (10.31.88.113) at 52:54:00:74:eb:11 [ether] on eth0 如何指定VIP 如果需要指定VIP，我们还是在service中修改loadBalancerIP字段从而指定VIP：\napiVersion: v1 kind: Service metadata: name: nginx-lb3-service namespace: nginx-quic annotations: lb.kubesphere.io/v1alpha1: openelb protocol.openelb.kubesphere.io/v1alpha1: layer2 eip.openelb.kubesphere.io/v1alpha2: eip-layer2-pool spec: allocateLoadBalancerNodePorts: true externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer # 使用 loadBalancerIP 字段从而指定VIP loadBalancerIP: 10.31.88.113 修改完成之后我们重新部署，openelb会自动生效并且将服务的EXTERNAL-IP变更为新IP。\n$ kubectl get svc -n nginx-quic -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR nginx-lb-service LoadBalancer 10.88.17.248 10.31.88.101 80:30643/TCP 18h app=nginx-lb nginx-lb2-service LoadBalancer 10.88.13.220 10.31.88.102 80:31114/TCP 18h app=nginx-lb nginx-lb3-service LoadBalancer 10.88.18.110 10.31.88.113 80:30485/TCP 18h app=nginx-lb Layer2 工作原理 我们查看pod的日志，可以看到更多的详细信息：\n$ kubectl logs -f -n openelb-system openelb-manager-5cdc8697f9-h2wd6 ...省略一堆日志输出... {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652950197.1421876,\u0026#34;logger\u0026#34;:\u0026#34;arpSpeaker\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;got ARP request, sending response\u0026#34;,\u0026#34;interface\u0026#34;:\u0026#34;eth0\u0026#34;,\u0026#34;ip\u0026#34;:\u0026#34;10.31.88.101\u0026#34;,\u0026#34;senderIP\u0026#34;:\u0026#34;10.31.254.250\u0026#34;,\u0026#34;senderMAC\u0026#34;:\u0026#34;6c:b1:58:56:e9:d4\u0026#34;,\u0026#34;responseMAC\u0026#34;:\u0026#34;52:54:00:56:df:ae\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652950496.9334905,\u0026#34;logger\u0026#34;:\u0026#34;arpSpeaker\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;got ARP request, sending response\u0026#34;,\u0026#34;interface\u0026#34;:\u0026#34;eth0\u0026#34;,\u0026#34;ip\u0026#34;:\u0026#34;10.31.88.102\u0026#34;,\u0026#34;senderIP\u0026#34;:\u0026#34;10.31.254.250\u0026#34;,\u0026#34;senderMAC\u0026#34;:\u0026#34;6c:b1:58:56:e9:d4\u0026#34;,\u0026#34;responseMAC\u0026#34;:\u0026#34;52:54:00:74:eb:11\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652950497.1327593,\u0026#34;logger\u0026#34;:\u0026#34;arpSpeaker\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;got ARP request, sending response\u0026#34;,\u0026#34;interface\u0026#34;:\u0026#34;eth0\u0026#34;,\u0026#34;ip\u0026#34;:\u0026#34;10.31.88.113\u0026#34;,\u0026#34;senderIP\u0026#34;:\u0026#34;10.31.254.250\u0026#34;,\u0026#34;senderMAC\u0026#34;:\u0026#34;6c:b1:58:56:e9:d4\u0026#34;,\u0026#34;responseMAC\u0026#34;:\u0026#34;52:54:00:74:eb:11\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652950497.1528928,\u0026#34;logger\u0026#34;:\u0026#34;arpSpeaker\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;got ARP request, sending response\u0026#34;,\u0026#34;interface\u0026#34;:\u0026#34;eth0\u0026#34;,\u0026#34;ip\u0026#34;:\u0026#34;10.31.88.101\u0026#34;,\u0026#34;senderIP\u0026#34;:\u0026#34;10.31.254.250\u0026#34;,\u0026#34;senderMAC\u0026#34;:\u0026#34;6c:b1:58:56:e9:d4\u0026#34;,\u0026#34;responseMAC\u0026#34;:\u0026#34;52:54:00:56:df:ae\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652950796.9339302,\u0026#34;logger\u0026#34;:\u0026#34;arpSpeaker\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;got ARP request, sending response\u0026#34;,\u0026#34;interface\u0026#34;:\u0026#34;eth0\u0026#34;,\u0026#34;ip\u0026#34;:\u0026#34;10.31.88.102\u0026#34;,\u0026#34;senderIP\u0026#34;:\u0026#34;10.31.254.250\u0026#34;,\u0026#34;senderMAC\u0026#34;:\u0026#34;6c:b1:58:56:e9:d4\u0026#34;,\u0026#34;responseMAC\u0026#34;:\u0026#34;52:54:00:74:eb:11\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652950797.1334393,\u0026#34;logger\u0026#34;:\u0026#34;arpSpeaker\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;got ARP request, sending response\u0026#34;,\u0026#34;interface\u0026#34;:\u0026#34;eth0\u0026#34;,\u0026#34;ip\u0026#34;:\u0026#34;10.31.88.113\u0026#34;,\u0026#34;senderIP\u0026#34;:\u0026#34;10.31.254.250\u0026#34;,\u0026#34;senderMAC\u0026#34;:\u0026#34;6c:b1:58:56:e9:d4\u0026#34;,\u0026#34;responseMAC\u0026#34;:\u0026#34;52:54:00:74:eb:11\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1652950797.1572392,\u0026#34;logger\u0026#34;:\u0026#34;arpSpeaker\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;got ARP request, sending response\u0026#34;,\u0026#34;interface\u0026#34;:\u0026#34;eth0\u0026#34;,\u0026#34;ip\u0026#34;:\u0026#34;10.31.88.101\u0026#34;,\u0026#34;senderIP\u0026#34;:\u0026#34;10.31.254.250\u0026#34;,\u0026#34;senderMAC\u0026#34;:\u0026#34;6c:b1:58:56:e9:d4\u0026#34;,\u0026#34;responseMAC\u0026#34;:\u0026#34;52:54:00:56:df:ae\u0026#34;} 可以看到openelb-manager会持续的监听局域网中的ARP request请求，当遇到请求的IP是自己IP池里面已经使用的VIP时会主动响应。如果openelb-manager存在多个副本的时候，它们会先使用k8s的选主算法来进行选主，然后再由选举出来的主节点进行ARP报文的响应。\nIn Layer 2 mode, OpenELB uses the leader election feature of Kubernetes to ensure that only one replica responds to ARP/NDP requests. OpenELB-manager高可用 默认情况下，openelb-manager只会部署一个副本，对于可用性要求较高的生产环境可能无法满足需求，官方也给出了部署多个副本的教程。\n官方教程的方式是推荐通过给节点添加label的方式来控制副本的部署数量和位置，这里我们将其配置为每个节点都运行一个服务（类似于daemonset）。首先我们给需要部署的节点打上labels。\n# 我们给集群内的三个节点都打上label $ kubectl label --overwrite nodes tiny-calico-master-88-1.k8s.tcinternal tiny-calico-worker-88-11.k8s.tcinternal tiny-calico-worker-88-12.k8s.tcinternal lb.kubesphere.io/v1alpha1=openelb # 查看当前节点的labels $ kubectl get nodes -o wide --show-labels=true | grep openelb tiny-calico-master-88-1.k8s.tcinternal Ready control-plane,master 16d v1.23.6 10.31.88.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 docker://20.10.14 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=tiny-calico-master-88-1.k8s.tcinternal,kubernetes.io/os=linux,lb.kubesphere.io/v1alpha1=openelb,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers= tiny-calico-worker-88-11.k8s.tcinternal Ready \u0026lt;none\u0026gt; 16d v1.23.6 10.31.88.11 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 docker://20.10.14 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=tiny-calico-worker-88-11.k8s.tcinternal,kubernetes.io/os=linux,lb.kubesphere.io/v1alpha1=openelb tiny-calico-worker-88-12.k8s.tcinternal Ready \u0026lt;none\u0026gt; 16d v1.23.6 10.31.88.12 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 docker://20.10.14 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=tiny-calico-worker-88-12.k8s.tcinternal,kubernetes.io/os=linux,lb.kubesphere.io/v1alpha1=openelb 然后我们先把副本的数量缩容到0。\n$ kubectl scale deployment openelb-manager --replicas=0 -n openelb-system 接着修改配置，在部署节点的nodeSelector字段中增加我们前面新加的labels\n$ kubectl get deployment openelb-manager -n openelb-system -o yaml ...略去一堆输出... nodeSelector: kubernetes.io/os: linux lb.kubesphere.io/v1alpha1: openelb ...略去一堆输出... 扩容副本数量到3。\n$ kubectl scale deployment openelb-manager --replicas=3 -n openelb-system 检查deployment状态\n$ kubectl get po -n openelb-system -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES openelb-admission-create-fvfzk 0/1 Completed 0 78m 10.88.84.89 tiny-calico-worker-88-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; openelb-admission-patch-tlmns 0/1 Completed 1 78m 10.88.84.90 tiny-calico-worker-88-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; openelb-manager-6457bdd569-6n9zr 1/1 Running 0 62m 10.31.88.1 tiny-calico-master-88-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; openelb-manager-6457bdd569-c6qfd 1/1 Running 0 62m 10.31.88.12 tiny-calico-worker-88-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; openelb-manager-6457bdd569-gh995 1/1 Running 0 62m 10.31.88.11 tiny-calico-worker-88-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 至此就完成了openelb-manager的高可用部署改造。\nBGP Mode IP Hostname 10.31.88.1 tiny-calico-master-88-1.k8s.tcinternal 10.31.88.11 tiny-calico-worker-88-11.k8s.tcinternal 10.31.88.12 tiny-calico-worker-88-12.k8s.tcinternal 10.88.64.0/18 podSubnet 10.88.0.0/18 serviceSubnet 10.89.0.0/16 OpenELB-BGP-IPpool 10.31.254.251 BGP Router 这里路由器的AS号为64512，OpenELB的AS号为64154。\n创建BGP配置 这里我们需要创建两个CRD，分别为BgpConf和BgpPeer，用来配置对端BGP路由器的信息和自己OpenELB的BGP配置。\n需要特别注意的是bgpconf这里配置的监听端口，OpenELB在默认情况下会同时监听IPv4和IPv6网络栈，因此需要确保集群开启了IPv6网络栈，否则会无法正确建立BGP连接。pod内的报错日志类似\u0026quot;listen tcp6 [::]:17900: socket: address family not supported by protocol\u0026quot; 如果k8s集群的CNI插件使用的是类似calico的bgp模式，已经使用了服务器的179端口，那么listenPort就要修改为其他端口避免冲突，这里我们修改为17900避免和集群的calico使用的bird冲突 apiVersion: network.kubesphere.io/v1alpha2 kind: BgpConf metadata: # BgpConf 对象的名称，无需设置，因此openelb只认default，设置成别的会被忽略 name: default spec: # openelb 的ASN，必须与BgpPeer配置中的spec:conf:peerAS值不同。 as: 64514 # OpenELB 监听的端口。默认值为179（默认 BGP 端口号）。 # 如果 Kubernetes 集群中的其他组件（例如 Calico）也使用 BGP 和 179 端口，则必须设置不同的值以避免冲突。 listenPort: 17900 # 本地路由器ID，通常设置为Kubernetes主节点的主网卡IP地址。 # 如果不指定该字段，则使用openelb-manager所在节点的第一个IP地址。 routerId: 10.31.88.1 --- apiVersion: network.kubesphere.io/v1alpha2 kind: BgpPeer metadata: # BgpPeer 对象的名称 name: bgppeer-openwrt-plus spec: conf: # 对端BGP路由器的ASN，必须与BgpConf配置中的spec:as值不同。 peerAs: 64512 # 对端BGP路由器的IP neighborAddress: 10.31.254.251 # 指定IP协议栈（IPv4/IPv6）。 # 不用修改，因此目前，OpenELB 仅支持 IPv4。 afiSafis: - config: family: afi: AFI_IP safi: SAFI_UNICAST enabled: true addPaths: config: # OpenELB 可以发送到对等 BGP 路由器以进行等价多路径 (ECMP) 路由的最大等效路由数。 sendMax: 10 nodeSelector: matchLabels: # 如果 Kubernetes 集群节点部署在不同的路由器下，并且每个节点都有一个 OpenELB 副本，则需要配置此字段，以便正确节点上的 OpenELB 副本与对端 BGP 路由器建立 BGP 连接。 # 默认情况下，所有 openelb-manager 副本都会响应 BgpPeer 配置并尝试与对等 BGP 路由器建立 BGP 连接。 openelb.kubesphere.io/rack: leaf1 然后我们部署并查看信息\n$ kubectl apply -f openelb/openelb-bgp.yaml bgpconf.network.kubesphere.io/default created bgppeer.network.kubesphere.io/bgppeer-openwrt-plus created $ kubectl get bgpconf NAME AGE default 16s $ kubectl get bgppeer NAME AGE bgppeer-openwrt-plus 20s 修改EIP 和之前的Layer2模式类似，我们也需要创建一个BGP模式使用的Eip。\napiVersion: network.kubesphere.io/v1alpha2 kind: Eip metadata: # Eip 对象的名称。 name: eip-bgp-pool spec: # Eip 对象的地址池 address: 10.89.0.1-10.89.255.254 # openELB的运行模式，默认为bgp protocol: bgp # OpenELB 在其上侦听 ARP/NDP 请求的网卡。该字段仅在protocol设置为时有效layer2。 interface: eth0 # 指定是否禁用 Eip 对象 # false表示可以继续分配 # true表示不再继续分配 disable: false status: # 指定 Eip 对象中的IP地址是否已用完。 occupied: false # 指定 Eip 对象中有多少个 IP 地址已分配给服务。 # 直接留空，系统会自动生成 usage: # Eip 对象中的 IP 地址总数。 poolSize: 65534 # 指定使用的 IP 地址和使用 IP 地址的服务。服务以Namespace/Service name格式显示（例如，default/test-svc）。 # 直接留空，系统会自动生成 used: # Eip 对象中的第一个 IP 地址。 firstIP: 10.89.0.1 # Eip 对象中的最后一个 IP 地址。 lastIP: 10.89.255.254 ready: true # 指定IP协议栈是否为 IPv4。目前，OpenELB 仅支持 IPv4，其值只能是true. v4: true 然后我们部署并查看信息，可以看到两个eip资源都存在系统中。\n$ kubectl apply -f openelb/openelb-eip-bgp.yaml eip.network.kubesphere.io/eip-bgp-pool created [root@tiny-calico-master-88-1 tiny-calico]# kubectl get eip NAME CIDR USAGE TOTAL eip-bgp-pool 10.89.0.1-10.89.255.254 65534 eip-layer2-pool 10.31.88.101-10.31.88.200 3 100 配置路由器 以家里常见的openwrt路由器为例，我们先在上面安装quagga组件，当然要是使用的openwrt版本编译了frr模块的话推荐使用frr来进行配置。\n如果使用的是别的发行版Linux（如CentOS或者Debian）推荐直接使用frr进行配置。\n我们先在openwrt上面直接使用opkg安装quagga\n$ opkg update $ opkg install quagga quagga-zebra quagga-bgpd quagga-vtysh 如果使用的openwrt版本足够新，是可以直接使用opkg安装frr组件的\n$ opkg update $ opkg install frr frr-babeld frr-bfdd frr-bgpd frr-eigrpd frr-fabricd frr-isisd frr-ldpd frr-libfrr frr-nhrpd frr-ospf6d frr-ospfd frr-pbrd frr-pimd frr-ripd frr-ripngd frr-staticd frr-vrrpd frr-vtysh frr-watchfrr frr-zebra 如果是使用frr记得在配置中开启bgpd参数再重启frr\n$ sed -i \u0026#39;s/bgpd=no/bgpd=yes/g\u0026#39; /etc/frr/daemons $ /etc/init.d/frr restart 路由器这边我们使用frr进行BGP协议的配置，需要注意的是因为前面我们把端口修改为17900，因此这里也需要进行同步配置节点端口为17900。\nroot@tiny-openwrt-plus:~# cat /etc/frr/frr.conf frr version 8.2.2 frr defaults traditional hostname tiny-openwrt-plus log file /home/frr/frr.log log syslog ! password zebra ! router bgp 64512 bgp router-id 10.31.254.251 no bgp ebgp-requires-policy ! ! neighbor 10.31.88.1 remote-as 64514 neighbor 10.31.88.1 port 17900 neighbor 10.31.88.1 description 10-31-88-1 neighbor 10.31.88.11 remote-as 64514 neighbor 10.31.88.11 port 17900 neighbor 10.31.88.11 description 10-31-88-11 neighbor 10.31.88.12 remote-as 64514 neighbor 10.31.88.12 port 17900 neighbor 10.31.88.12 description 10-31-88-12 ! ! address-family ipv4 unicast !maximum-paths 3 exit-address-family exit ! access-list vty seq 5 permit 127.0.0.0/8 access-list vty seq 10 deny any ! line vty access-class vty exit ! 部署测试服务 这里我们创建两个服务进行测试，需要注意和上面的layer2模式不同，这里的注解要同步修改为使用bgp和对应的eip。\napiVersion: v1 kind: Service metadata: name: nginx-lb2-service namespace: nginx-quic annotations: lb.kubesphere.io/v1alpha1: openelb protocol.openelb.kubesphere.io/v1alpha1: bgp eip.openelb.kubesphere.io/v1alpha2: eip-bgp-pool spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer --- apiVersion: v1 kind: Service metadata: name: nginx-lb3-service namespace: nginx-quic annotations: lb.kubesphere.io/v1alpha1: openelb protocol.openelb.kubesphere.io/v1alpha1: bgp eip.openelb.kubesphere.io/v1alpha2: eip-bgp-pool spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer loadBalancerIP: 10.89.100.100 部署完成之后我们再查看服务的状态，可以看到没有指定loadBalancerIP的服务会自动按顺序分配一个可用IP，而指定了IP的会分配为我们手动指定的IP，同时layer2模式的也可以共存。\n$ kubectl get eip -A NAME CIDR USAGE TOTAL eip-bgp-pool 10.89.0.1-10.89.255.254 2 65534 eip-layer2-pool 10.31.88.101-10.31.88.200 1 100 $ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.88.15.40 10.31.88.101 80:30972/TCP 2m48s nginx-lb2-service LoadBalancer 10.88.60.227 10.89.0.1 80:30425/TCP 2m48s nginx-lb3-service LoadBalancer 10.88.11.160 10.89.100.100 80:30597/TCP 2m48s 再查看路由器上面的路由表，可以看到两个BGP模式的VIP有多个下一条路由，则说明ECMP开启成功\ntiny-openwrt-plus# show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR, f - OpenFabric, \u0026gt; - selected route, * - FIB route, q - queued, r - rejected, b - backup t - trapped, o - offload failure K\u0026gt;* 0.0.0.0/0 [0/0] via 10.31.254.254, eth0, 12:56:32 C\u0026gt;* 10.31.0.0/16 is directly connected, eth0, 12:56:32 B\u0026gt;* 10.89.0.1/32 [20/0] via 10.31.88.1, eth0, weight 1, 00:00:28 * via 10.31.88.11, eth0, weight 1, 00:00:28 * via 10.31.88.12, eth0, weight 1, 00:00:28 B\u0026gt;* 10.89.100.100/32 [20/0] via 10.31.88.1, eth0, weight 1, 00:00:28 * via 10.31.88.11, eth0, weight 1, 00:00:28 * via 10.31.88.12, eth0, weight 1, 00:00:28 最后再找一台机器进行curl测试\n[tinychen /root]# curl 10.89.0.1 10.31.88.1:22432 [tinychen /root]# curl 10.89.100.100 10.31.88.11:59470 总结 OpenELB的两种模式的优缺点和MetalLB几乎一模一样，两者在这方面的优缺点概况文档可以结合起来一起看：\nLayer2 mode优缺点 优点：\n通用性强，对比BGP模式不需要BGP路由器支持，几乎可以适用于任何网络环境；当然云厂商的网络环境例外 缺点：\n所有的流量都会在同一个节点上，该节点的容易成为流量的瓶颈 当VIP所在节点宕机之后，需要较长时间进行故障转移（官方没说多久），这主要是因为OpenELB使用了k8s同款的选主算法来进行选主，当VIP所在节点宕机之后重新选主的时间要比传统的keepalived使用的vrrp协议（一般为1s）要更长 难以定位VIP所在节点，OpenELB并没有提供一个简单直观的方式让我们查看到底哪一个节点是VIP所属节点，基本只能通过抓包或者查看pod日志来确定，当集群规模变大的时候这会变得非常的麻烦 改进方案：\n有条件的可以考虑使用BGP模式 既不能用BGP模式也不能接受Layer2模式的，基本和目前主流的三个开源负载均衡器无缘了（三者都是Layer2模式和BGP模式且原理类似，优缺点相同） BGP mode优缺点 BGP模式的优缺点几乎和Layer2模式相反\n优点：\n无单点故障，在开启ECMP的前提下，k8s集群内所有的节点都有请求流量，都会参与负载均衡并转发请求 缺点：\n条件苛刻，需要有BGP路由器支持，配置起来也更复杂； ECMP的故障转移（failover）并不是特别地优雅，这个问题的严重程度取决于使用的ECMP算法；当集群的节点出现变动导致BGP连接出现变动，所有的连接都会进行重新哈希（使用三元组或五元组哈希），这对一些服务来说可能会有影响； 路由器中使用的哈希值通常 不稳定，因此每当后端集的大小发生变化时（例如，当一个节点的 BGP 会话关闭时），现有的连接将被有效地随机重新哈希，这意味着大多数现有的连接最终会突然被转发到不同的后端，而这个后端可能和此前的后端毫不相干且不清楚上下文状态信息。\n改进方案：\nOpenELB官方并没有给出BGP模式的优缺点分析和改进方案，但是我们可以参考MetalLB官方给出的资料：\nMetalLB给出了一些改进方案，下面列出来给大家参考一下\n使用更稳定的ECMP算法来减少后端变动时对现有连接的影响，如“resilient ECMP” or “resilient LAG” 将服务部署到特定的节点上减少可能带来的影响 在流量低峰期进行变更 将服务分开部署到两个不同的LoadBalanceIP的服务中，然后利用DNS进行流量切换 在客户端加入透明的用户无感的重试逻辑 在LoadBalance后面加入一层ingress来实现更优雅的failover（但是并不是所有的服务都可以使用ingress） 接受现实……（Accept that there will be occasional bursts of reset connections. For low-availability internal services, this may be acceptable as-is.） OpenELB优缺点 这里尽量客观的总结概况一些客观事实，是否为优缺点可能会因人而异：\nOpenELB是国内的青云科技团队主导开发的产品（也是kubesphere的开发团队） OpenELB的部署方式并不是使用daemonset+deployment的方式，而是使用deployment+jobs.batch， OpenELB的deployment默认状态下并没有高可用部署，需要自己手动配置 OpenELB的文档非常少，比MetalLB的还要少，仅有少数几篇必要的文档，建议先全部阅读完再开始上手 OpenELB对IPv6的支持不完善（BGP模式下暂不支持IPv6） OpenELB可以通过注解来同时部署使用BGP模式和Layer2模式 OpenELB使用了CRD来一定程度上改善了IPAM的问题，可以通过查看EIP的状态来查看IP池的分配情况和对应服务 OpenELB在Layer2模式下无法快速定位当前的VIP所在节点 OpenELB官方表示已经有一定的数量的企业采用（包括生产和测试环境），但是没有具体披露使用的模式和规模 总的来说，OpenELB是一款不错的负载均衡器，在前人MetalLB的基础上做了一些改进，有一定的社区热度、有一定的专业团队进行维护；但是目前感觉还处于比较初级的阶段，有较多的功能还没有开发完善，使用起来偶尔会有些不大不小的问题。青云科技官方表示会在后面的kubesphere v3.3.0版本内置OpenELB用于服务暴露，届时应该会有更多的用户参与进来。\n","permalink":"http://localhost:9011/20220523-k8s-07-loadbalancer-openelb/","summary":"\u003cp\u003e本文主要在k8s原生集群上部署\u003ccode\u003ev0.4.4\u003c/code\u003e版本的\u003ccode\u003eOpenELB\u003c/code\u003e作为k8s的\u003ccode\u003eLoadBalancer\u003c/code\u003e，主要涉及\u003cstrong\u003eOpenELB\u003c/strong\u003e的\u003cstrong\u003eLayer2模式\u003c/strong\u003e和\u003cstrong\u003eBGP模式\u003c/strong\u003e两种部署方案。由于BGP的相关原理和配置比较复杂，这里仅涉及简单的BGP配置。\u003c/p\u003e\n\u003cp\u003e文中使用的k8s集群是在CentOS7系统上基于\u003ccode\u003edocker\u003c/code\u003e和\u003ccode\u003ecalico\u003c/code\u003e组件部署\u003ccode\u003ev1.23.6\u003c/code\u003e版本，此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列07-负载均衡器之OpenELB"},{"content":"本文主要在k8s原生集群上部署v0.12.1版本的MetalLB作为k8s的LoadBalancer，主要涉及MetalLB的Layer2模式和BGP模式两种部署方案。由于BGP的相关原理和配置比较复杂，这里仅涉及简单的BGP配置。\n文中使用的k8s集群是在CentOS7系统上基于docker和flannel组件部署v1.23.6版本，此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n工作原理 简介 在开始之前，我们需要了解一下MetalLB的工作原理。\nMetalLB hooks into your Kubernetes cluster, and provides a network load-balancer implementation. In short, it allows you to create Kubernetes services of type LoadBalancer in clusters that don’t run on a cloud provider, and thus cannot simply hook into paid products to provide load balancers.\nIt has two features that work together to provide this service: address allocation, and external announcement.\nMetalLB是 Kubernetes 集群中关于LoadBalancer的一个具体实现，主要用于暴露k8s集群的服务到集群外部访问。MetalLB可以让我们在k8s集群中创建服务类型为LoadBalancer的服务，并且无需依赖云厂商提供的LoadBalancer。\n它具有两个共同提供此服务的工作负载（workload）：地址分配（address allocation）和外部公告（external announcement）；对应的就是在k8s中部署的controller和speaker。\naddress allocation 地址分配（address allocation）这个功能比较好理解，首先我们需要给MetalLB分配一段IP，接着它会根据k8s的service中的相关配置来给LoadBalancer的服务分配IP，从官网文档中我们可以得知LoadBalancer的IP可以手动指定，也可以让MetalLB自动分配；同时还可以在MetalLB的configmap中配置多个IP段，并且单独设置每个IP段是否开启自动分配。\n地址分配（address allocation）主要就是由作为deployment部署的controller来实现，它负责监听集群中的service状态并且分配IP\nexternal announcement 外部公告（external announcement）的主要功能就是要把服务类型为LoadBalancer的服务的EXTERNAL-IP公布到网络中去，确保客户端能够正常访问到这个IP。MetalLB对此的实现方式主要有三种：ARP/NDP和BGP；其中ARP/NDP分别对应IPv4/IPv6协议的Layer2模式，BGP路由协议则是对应BGP模式。外部公告（external announcement）主要就是由作为daemonset部署的speaker来实现，它负责在网络中发布ARP/NDP报文或者是和BGP路由器建立连接并发布BGP报文。\n关于网络 不管是Layer2模式还是BGP模式，两者都不使用Linux的网络栈，也就是说我们没办法使用诸如ip命令之类的操作准确的查看VIP所在的节点和相应的路由，相对应的是在每个节点上面都能看到一个kube-ipvs0网卡接口上面的IP。同时，两种模式都只是负责把VIP的请求引到对应的节点上面，之后的请求怎么到达pod，按什么规则轮询等都是由kube-proxy实现的。\n两种不同的模式各有优缺点和局限性，我们先把两者都部署起来再进行分析。\n准备工作 系统要求 在开始部署MetalLB之前，我们需要确定部署环境能够满足最低要求：\n一个k8s集群，要求版本不低于1.13.0，且没有负载均衡器相关插件 k8s集群上的CNI组件和MetalLB兼容 预留一段IPv4地址给MetalLB作为LoadBalance的VIP使用 如果使用的是MetalLB的BGP模式，还需要路由器支持BGP协议 如果使用的是MetalLB的Layer2模式，因为使用了memberlist算法来实现选主，因此需要确保各个k8s节点之间的7946端口可达（包括TCP和UDP协议），当然也可以根据自己的需求配置为其他端口 cni插件的兼容性 MetalLB官方给出了对主流的一些CNI的兼容情况，考虑到MetalLB主要还是利用了k8s自带的kube-proxy组件做流量转发，因此对大多数的CNI兼容情况都相当不错。\nCNI 兼容性 主要问题 Calico Mostly (see known issues) 主要在于BGP模式的兼容性，但是社区也提供了解决方案 Canal Yes - Cilium Yes - Flannel Yes - Kube-ovn Yes - Kube-router Mostly (see known issues) 无法支持 builtin external BGP peering mode Weave Net Mostly (see known issues) externalTrafficPolicy: Local支持情况视版本而定 从兼容性上面我们不难看出，大多数情况是没问题的，出现兼容性问题的主要原因就是和BGP有冲突。实际上BGP相关的兼容性问题几乎存在于每个开源的k8s负载均衡器上面。\n云厂商的兼容性 MetalLB官方给出的列表中，我们可以看到对大多数云厂商的兼容性都很差，原因也很简单，大多数的云环境上面都没办法运行BGP协议，而通用性更高的layer2模式则因为各个云厂商的网络环境不同而没办法确定是否能够兼容\nThe short version is: cloud providers expose proprietary APIs instead of standard protocols to control their network layer, and MetalLB doesn’t work with those APIs.\n当然如果使用了云厂商的服务，最好的方案是直接使用云厂商提供的LoadBalance服务。\nLayer2 mode 部署环境 本次MetalLB的部署环境为基于docker和flannel部署的1.23.6版本的k8s集群\nIP Hostname 10.31.8.1 tiny-flannel-master-8-1.k8s.tcinternal 10.31.8.11 tiny-flannel-worker-8-11.k8s.tcinternal 10.31.8.12 tiny-flannel-worker-8-12.k8s.tcinternal 10.8.64.0/18 podSubnet 10.8.0.0/18 serviceSubnet 10.31.8.100-10.31.8.200 MetalLB IPpool 配置ARP参数 部署Layer2模式需要把k8s集群中的ipvs配置打开strictARP，开启之后k8s集群中的kube-proxy会停止响应kube-ipvs0网卡之外的其他网卡的arp请求，而由MetalLB接手处理。\nstrict ARP开启之后相当于把 将 arp_ignore 设置为 1 并将 arp_announce 设置为 2 启用严格的 ARP，这个原理和LVS中的DR模式对RS的配置一样，可以参考之前的文章中的解释。\nstrict ARP configure arp_ignore and arp_announce to avoid answering ARP queries from kube-ipvs0 interface\n# 查看kube-proxy中的strictARP配置 $ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARP strictARP: false # 手动修改strictARP配置为true $ kubectl edit configmap -n kube-system kube-proxy configmap/kube-proxy edited # 使用命令直接修改并对比不同 $ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e \u0026#34;s/strictARP: false/strictARP: true/\u0026#34; | kubectl diff -f - -n kube-system # 确认无误后使用命令直接修改并生效 $ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e \u0026#34;s/strictARP: false/strictARP: true/\u0026#34; | kubectl apply -f - -n kube-system # 重启kube-proxy确保配置生效 $ kubectl rollout restart ds kube-proxy -n kube-system # 确认配置生效 $ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARP strictARP: true 部署MetalLB MetalLB的部署也十分简单，官方提供了manifest文件部署（yaml部署），helm3部署和Kustomize部署三种方式，这里我们还是使用manifest文件部署。\n大多数的官方教程为了简化部署的步骤，都是写着直接用kubectl命令部署一个yaml的url，这样子的好处是部署简单快捷，但是坏处就是本地自己没有存档，不方便修改等操作，因此我个人更倾向于把yaml文件下载到本地保存再进行部署。\n# 下载v0.12.1的两个部署文件 $ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml $ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml # 如果使用frr来进行BGP路由管理，则下载这两个部署文件 $ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml $ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb-frr.yaml 下载官方提供的yaml文件之后，我们再提前准备好configmap的配置，github上面有提供一个参考文件，layer2模式需要的配置并不多，这里我们只做最基础的一些参数配置定义即可：\nprotocol这一项我们配置为layer2 addresses这里我们可以使用CIDR来批量配置（198.51.100.0/24），也可以指定首尾IP来配置（192.168.0.150-192.168.0.200），这里我们指定一段和k8s节点在同一个子网的IP $ cat \u0026gt; configmap-metallb.yaml \u0026lt;\u0026lt;EOF apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 10.31.8.100-10.31.8.200 EOF 接下来就可以开始进行部署，整体可以分为三步：\n部署namespace 部署deployment和daemonset 配置configmap # 创建namespace $ kubectl apply -f namespace.yaml namespace/metallb-system created $ kubectl get ns NAME STATUS AGE default Active 8d kube-node-lease Active 8d kube-public Active 8d kube-system Active 8d metallb-system Active 8s nginx-quic Active 8d # 部署deployment和daemonset，以及相关所需的其他资源 $ kubectl apply -f metallb.yaml Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ podsecuritypolicy.policy/controller created podsecuritypolicy.policy/speaker created serviceaccount/controller created serviceaccount/speaker created clusterrole.rbac.authorization.k8s.io/metallb-system:controller created clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created role.rbac.authorization.k8s.io/config-watcher created role.rbac.authorization.k8s.io/pod-lister created role.rbac.authorization.k8s.io/controller created clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created rolebinding.rbac.authorization.k8s.io/config-watcher created rolebinding.rbac.authorization.k8s.io/pod-lister created rolebinding.rbac.authorization.k8s.io/controller created daemonset.apps/speaker created deployment.apps/controller created # 这里主要就是部署了controller这个deployment来检查service的状态 $ kubectl get deploy -n metallb-system NAME READY UP-TO-DATE AVAILABLE AGE controller 1/1 1 1 86s # speaker则是使用ds部署到每个节点上面用来协商VIP、收发ARP、NDP等数据包 $ kubectl get ds -n metallb-system NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE speaker 3 3 3 3 3 kubernetes.io/os=linux 64s $ kubectl get pod -n metallb-system -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES controller-57fd9c5bb-svtjw 1/1 Running 0 117s 10.8.65.4 tiny-flannel-worker-8-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; speaker-bf79q 1/1 Running 0 117s 10.31.8.11 tiny-flannel-worker-8-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; speaker-fl5l8 1/1 Running 0 117s 10.31.8.12 tiny-flannel-worker-8-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; speaker-nw2fm 1/1 Running 0 117s 10.31.8.1 tiny-flannel-master-8-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; $ kubectl apply -f configmap-layer2.yaml configmap/config created 部署测试服务 我们还是自定义一个服务来进行测试，测试镜像使用nginx，默认情况下会返回请求客户端的IP和端口\n$ cat \u0026gt; nginx-quic-lb.yaml \u0026lt;\u0026lt;EOF apiVersion: v1 kind: Namespace metadata: name: nginx-quic --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-lb namespace: nginx-quic spec: selector: matchLabels: app: nginx-lb replicas: 4 template: metadata: labels: app: nginx-lb spec: containers: - name: nginx-lb image: tinychen777/nginx-quic:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-lb-service namespace: nginx-quic spec: externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer loadBalancerIP: 10.31.8.100 EOF 注意上面的配置中我们把service配置中的type字段指定为LoadBalancer，并且指定了loadBalancerIP为10.31.8.100\n注意：并非所有的LoadBalancer都允许设置 loadBalancerIP。\n如果LoadBalancer支持该字段，那么将根据用户设置的 loadBalancerIP 来创建负载均衡器。\n如果没有设置 loadBalancerIP 字段，将会给负载均衡器指派一个临时 IP。\n如果设置了 loadBalancerIP，但LoadBalancer并不支持这种特性，那么设置的 loadBalancerIP 值将会被忽略掉。\n# 创建一个测试服务检查效果 $ kubectl apply -f nginx-quic-lb.yaml namespace/nginx-quic created deployment.apps/nginx-lb created service/nginx-lb-service created 查看服务状态，这时候TYPE已经变成LoadBalancer，EXTERNAL-IP显示为我们定义的10.31.8.100\n# 查看服务状态，这时候TYPE已经变成LoadBalancer $ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.8.32.221 10.31.8.100 80:30181/TCP 25h 此时我们再去查看k8s机器中的nginx-lb-service状态，可以看到ClusetIP、LoadBalancer-VIP和nodeport的相关信息以及流量策略TrafficPolicy等配置\n$ kubectl get svc -n nginx-quic nginx-lb-service -o yaml apiVersion: v1 kind: Service metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {\u0026#34;apiVersion\u0026#34;:\u0026#34;v1\u0026#34;,\u0026#34;kind\u0026#34;:\u0026#34;Service\u0026#34;,\u0026#34;metadata\u0026#34;:{\u0026#34;annotations\u0026#34;:{},\u0026#34;name\u0026#34;:\u0026#34;nginx-lb-service\u0026#34;,\u0026#34;namespace\u0026#34;:\u0026#34;nginx-quic\u0026#34;},\u0026#34;spec\u0026#34;:{\u0026#34;externalTrafficPolicy\u0026#34;:\u0026#34;Cluster\u0026#34;,\u0026#34;internalTrafficPolicy\u0026#34;:\u0026#34;Cluster\u0026#34;,\u0026#34;loadBalancerIP\u0026#34;:\u0026#34;10.31.8.100\u0026#34;,\u0026#34;ports\u0026#34;:[{\u0026#34;port\u0026#34;:80,\u0026#34;protocol\u0026#34;:\u0026#34;TCP\u0026#34;,\u0026#34;targetPort\u0026#34;:80}],\u0026#34;selector\u0026#34;:{\u0026#34;app\u0026#34;:\u0026#34;nginx-lb\u0026#34;},\u0026#34;type\u0026#34;:\u0026#34;LoadBalancer\u0026#34;}} creationTimestamp: \u0026#34;2022-05-16T06:01:23Z\u0026#34; name: nginx-lb-service namespace: nginx-quic resourceVersion: \u0026#34;1165135\u0026#34; uid: f547842e-4547-4d01-abbc-89ac8b059a2a spec: allocateLoadBalancerNodePorts: true clusterIP: 10.8.32.221 clusterIPs: - 10.8.32.221 externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster ipFamilies: - IPv4 ipFamilyPolicy: SingleStack loadBalancerIP: 10.31.8.100 ports: - nodePort: 30181 port: 80 protocol: TCP targetPort: 80 selector: app: nginx-lb sessionAffinity: None type: LoadBalancer status: loadBalancer: ingress: - ip: 10.31.8.100 查看IPVS规则，这时候可以看到ClusetIP、LoadBalancer-VIP和nodeport的转发规则，默认情况下在创建LoadBalance的时候还会创建nodeport服务：\n$ ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 172.17.0.1:30181 rr -\u0026gt; 10.8.65.15:80 Masq 1 0 0 -\u0026gt; 10.8.65.16:80 Masq 1 0 0 -\u0026gt; 10.8.66.12:80 Masq 1 0 0 -\u0026gt; 10.8.66.13:80 Masq 1 0 0 TCP 10.8.32.221:80 rr -\u0026gt; 10.8.65.15:80 Masq 1 0 0 -\u0026gt; 10.8.65.16:80 Masq 1 0 0 -\u0026gt; 10.8.66.12:80 Masq 1 0 0 -\u0026gt; 10.8.66.13:80 Masq 1 0 0 TCP 10.8.64.0:30181 rr -\u0026gt; 10.8.65.15:80 Masq 1 0 0 -\u0026gt; 10.8.65.16:80 Masq 1 0 0 -\u0026gt; 10.8.66.12:80 Masq 1 0 0 -\u0026gt; 10.8.66.13:80 Masq 1 0 0 TCP 10.8.64.1:30181 rr -\u0026gt; 10.8.65.15:80 Masq 1 0 0 -\u0026gt; 10.8.65.16:80 Masq 1 0 0 -\u0026gt; 10.8.66.12:80 Masq 1 0 0 -\u0026gt; 10.8.66.13:80 Masq 1 0 0 TCP 10.31.8.1:30181 rr -\u0026gt; 10.8.65.15:80 Masq 1 0 0 -\u0026gt; 10.8.65.16:80 Masq 1 0 0 -\u0026gt; 10.8.66.12:80 Masq 1 0 0 -\u0026gt; 10.8.66.13:80 Masq 1 0 0 TCP 10.31.8.100:80 rr -\u0026gt; 10.8.65.15:80 Masq 1 0 0 -\u0026gt; 10.8.65.16:80 Masq 1 0 0 -\u0026gt; 10.8.66.12:80 Masq 1 0 0 -\u0026gt; 10.8.66.13:80 Masq 1 0 0 使用curl检查服务是否正常\n$ curl 10.31.8.100:80 10.8.64.0:60854 $ curl 10.8.1.166:80 10.8.64.0:2562 $ curl 10.31.8.1:30974 10.8.64.0:1635 $ curl 10.31.8.100:80 10.8.64.0:60656 关于VIP 在每台k8s节点机器上面的kube-ipvs0网卡上面都能看到这个LoadBalancer的VIP：\n$ ip addr show kube-ipvs0 5: kube-ipvs0: \u0026lt;BROADCAST,NOARP\u0026gt; mtu 1500 qdisc noop state DOWN group default link/ether 4e:ba:e8:25:cf:17 brd ff:ff:ff:ff:ff:ff inet 10.8.0.1/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.8.0.10/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.8.32.221/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.31.8.100/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever 想要定位到VIP在那个节点上面则比较麻烦，我们可以找一台和K8S集群处于同一个二层网络的机器，查看arp表，再根据mac地址找到对应的节点IP，这样子可以反查到IP在哪个节点上面。\n$ arp -a | grep 10.31.8.100 ? (10.31.8.100) at 52:54:00:5c:9c:97 [ether] on eth0 $ arp -a | grep 52:54:00:5c:9c:97 tiny-flannel-worker-8-12.k8s.tcinternal (10.31.8.12) at 52:54:00:5c:9c:97 [ether] on eth0 ? (10.31.8.100) at 52:54:00:5c:9c:97 [ether] on eth0 $ ip a | grep 52:54:00:5c:9c:97 link/ether 52:54:00:5c:9c:97 brd ff:ff:ff:ff:ff:ff 又或者我们可以查看speaker的pod日志，我们可以找到对应的服务IP被宣告的日志记录\n$ kubectl logs -f -n metallb-system speaker-fl5l8 {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;serviceAnnounced\u0026#34;,\u0026#34;ips\u0026#34;:[\u0026#34;10.31.8.100\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;service has IP, announcing\u0026#34;,\u0026#34;pool\u0026#34;:\u0026#34;default\u0026#34;,\u0026#34;protocol\u0026#34;:\u0026#34;layer2\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-16T06:11:34.099204376Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;serviceAnnounced\u0026#34;,\u0026#34;ips\u0026#34;:[\u0026#34;10.31.8.100\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;service has IP, announcing\u0026#34;,\u0026#34;pool\u0026#34;:\u0026#34;default\u0026#34;,\u0026#34;protocol\u0026#34;:\u0026#34;layer2\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-16T06:12:09.527334808Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;serviceAnnounced\u0026#34;,\u0026#34;ips\u0026#34;:[\u0026#34;10.31.8.100\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;service has IP, announcing\u0026#34;,\u0026#34;pool\u0026#34;:\u0026#34;default\u0026#34;,\u0026#34;protocol\u0026#34;:\u0026#34;layer2\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-16T06:12:09.547734268Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;serviceAnnounced\u0026#34;,\u0026#34;ips\u0026#34;:[\u0026#34;10.31.8.100\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;service has IP, announcing\u0026#34;,\u0026#34;pool\u0026#34;:\u0026#34;default\u0026#34;,\u0026#34;protocol\u0026#34;:\u0026#34;layer2\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-16T06:12:34.267651651Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;serviceAnnounced\u0026#34;,\u0026#34;ips\u0026#34;:[\u0026#34;10.31.8.100\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;service has IP, announcing\u0026#34;,\u0026#34;pool\u0026#34;:\u0026#34;default\u0026#34;,\u0026#34;protocol\u0026#34;:\u0026#34;layer2\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-16T06:12:34.286130424Z\u0026#34;} 关于nodeport 相信不少细心的同学已经发现了，我们在创建LoadBalancer服务的时候，默认情况下k8s会帮我们自动创建一个nodeport服务，这个操作可以通过指定Service中的allocateLoadBalancerNodePorts字段来定义开关，默认情况下为true\n不同的loadbalancer实现原理不同，有些是需要依赖nodeport来进行流量转发，有些则是直接转发请求到pod中。对于MetalLB而言，是通过kube-proxy将请求的流量直接转发到pod，因此我们需要关闭nodeport的话可以修改service中的spec.allocateLoadBalancerNodePorts字段，将其设置为false，那么在创建svc的时候就不会分配nodeport。\n但是需要注意的是如果是对已有service进行修改，关闭nodeport（从true改为false），k8s不会自动去清除已有的ipvs规则，这需要我们自行手动删除。\n我们重新定义创建一个svc\napiVersion: v1 kind: Service metadata: name: nginx-lb-service namespace: nginx-quic spec: allocateLoadBalancerNodePorts: false externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster selector: app: nginx-lb ports: - protocol: TCP port: 80 # match for service access port targetPort: 80 # match for pod access port type: LoadBalancer loadBalancerIP: 10.31.8.100 此时再去查看对应的svc状态和ipvs规则会发现已经没有nodeport相关的配置\n$ ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.8.62.180:80 rr -\u0026gt; 10.8.65.18:80 Masq 1 0 0 -\u0026gt; 10.8.65.19:80 Masq 1 0 0 -\u0026gt; 10.8.66.14:80 Masq 1 0 0 -\u0026gt; 10.8.66.15:80 Masq 1 0 0 TCP 10.31.8.100:80 rr -\u0026gt; 10.8.65.18:80 Masq 1 0 0 -\u0026gt; 10.8.65.19:80 Masq 1 0 0 -\u0026gt; 10.8.66.14:80 Masq 1 0 0 -\u0026gt; 10.8.66.15:80 Masq 1 0 0 $ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.8.62.180 10.31.8.100 80/TCP 23s 如果是把已有服务的spec.allocateLoadBalancerNodePorts从true改为false，原有的nodeport不会自动删除，因此最好在初始化的时候就规划好相关参数\n$ kubectl get svc -n nginx-quic nginx-lb-service -o yaml | egrep \u0026#34; allocateLoadBalancerNodePorts: \u0026#34; allocateLoadBalancerNodePorts: false $ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.8.62.180 10.31.8.100 80:31405/TCP 85m BGP mode 网络拓扑 测试环境的网络拓扑非常的简单，MetalLB的网段为了和前面Layer2模式进行区分，更换为10.9.0.0/16，具体信息如下\nIP Hostname 10.31.8.1 tiny-flannel-master-8-1.k8s.tcinternal 10.31.8.11 tiny-flannel-worker-8-11.k8s.tcinternal 10.31.8.12 tiny-flannel-worker-8-12.k8s.tcinternal 10.31.254.251 OpenWrt 10.9.0.0/16 MetalLB BGP IPpool 三台k8s的节点直连Openwrt路由器，OpenWRT作为k8s节点的网关的同时，还在上面跑BGP协议，将对MetalLB使用的VIP的请求路由到各个k8s节点上。\n在开始配置之前，我们需要给路由器和k8s节点都分配一个私有的AS号，这里可以参考wiki上面的AS号划分使用。这里我们路由器使用AS号为64512，MetalLB使用AS号为64513。\n安装路由软件 以家里常见的openwrt路由器为例，我们先在上面安装quagga组件，当然要是使用的openwrt版本编译了frr模块的话推荐使用frr来进行配置。\n如果使用的是别的发行版Linux（如CentOS或者Debian）推荐直接使用frr进行配置。\n我们先在openwrt上面直接使用opkg安装quagga\n$ opkg update $ opkg install quagga quagga-zebra quagga-bgpd quagga-vtysh 如果使用的openwrt版本足够新，是可以直接使用opkg安装frr组件的\n$ opkg update $ opkg install frr frr-babeld frr-bfdd frr-bgpd frr-eigrpd frr-fabricd frr-isisd frr-ldpd frr-libfrr frr-nhrpd frr-ospf6d frr-ospfd frr-pbrd frr-pimd frr-ripd frr-ripngd frr-staticd frr-vrrpd frr-vtysh frr-watchfrr frr-zebra 如果是使用frr记得在配置中开启bgpd参数再重启frr\n$ sed -i \u0026#39;s/bgpd=no/bgpd=yes/g\u0026#39; /etc/frr/daemons $ /etc/init.d/frr restart 配置路由器BGP 下面的服务配置以frr为例，实际上使用quagga的话也是使用vtysh进行配置或者是直接修改配置文件，两者区别不大。\n检查服务是否监听了2601和2605端口\nroot@OpenWrt:~# netstat -ntlup | egrep \u0026#34;zebra|bgpd\u0026#34; tcp 0 0 0.0.0.0:2601 0.0.0.0:* LISTEN 3018/zebra tcp 0 0 0.0.0.0:2605 0.0.0.0:* LISTEN 3037/bgpd BGP协议使用的179端口还没有被监听是因为我们还没有进行配置，这里我们可以直接使用vtysh进行配置或者是直接修改配置文件然后重启服务。\n直接在命令行输入vtysh就可以进入到vtysh的配置终端（和kvm虚拟化的virsh类似），这时候注意留意终端的提示符变化了\nroot@OpenWrt:~# vtysh Hello, this is Quagga (version 1.2.4). Copyright 1996-2005 Kunihiro Ishiguro, et al. OpenWrt# 但是命令行配置比较麻烦，我们也可以直接修改配置文件然后重启服务。\nquagga修改的bgp配置文件默认是/etc/quagga/bgpd.conf，不同的发行版和安装方式可能会不同。\n$ cat /etc/quagga/bgpd.conf ! ! Zebra configuration saved from vty ! 2022/05/19 11:01:35 ! password zebra ! router bgp 64512 bgp router-id 10.31.254.251 neighbor 10.31.8.1 remote-as 64513 neighbor 10.31.8.1 description 10-31-8-1 neighbor 10.31.8.11 remote-as 64513 neighbor 10.31.8.11 description 10-31-8-11 neighbor 10.31.8.12 remote-as 64513 neighbor 10.31.8.12 description 10-31-8-12 maximum-paths 3 ! address-family ipv6 exit-address-family exit ! access-list vty permit 127.0.0.0/8 access-list vty deny any ! line vty access-class vty ! 如果使用的是frr，那么配置文件会有所变化，需要修改的是/etc/frr/frr.conf，不同的发行版和安装方式可能会不同。\n$ cat /etc/frr/frr.conf frr version 8.2.2 frr defaults traditional hostname tiny-openwrt-plus ! password zebra ! router bgp 64512 bgp router-id 10.31.254.251 no bgp ebgp-requires-policy neighbor 10.31.8.1 remote-as 64513 neighbor 10.31.8.1 description 10-31-8-1 neighbor 10.31.8.11 remote-as 64513 neighbor 10.31.8.11 description 10-31-8-11 neighbor 10.31.8.12 remote-as 64513 neighbor 10.31.8.12 description 10-31-8-12 ! address-family ipv4 unicast exit-address-family exit ! access-list vty seq 5 permit 127.0.0.0/8 access-list vty seq 10 deny any ! line vty access-class vty exit ! 完成配置后需要重启服务\n# 重启frr的命令 $ /etc/init.d/frr restart # 重启quagge的命令 $ /etc/init.d/quagga restart 重启后我们进入vtysh查看bgp的状态\ntiny-openwrt-plus# show ip bgp summary IPv4 Unicast Summary (VRF default): BGP router identifier 10.31.254.251, local AS number 64512 vrf-id 0 BGP table version 0 RIB entries 0, using 0 bytes of memory Peers 3, using 2149 KiB of memory Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc 10.31.8.1 4 64513 0 0 0 0 0 never Active 0 10-31-8-1 10.31.8.11 4 64513 0 0 0 0 0 never Active 0 10-31-8-11 10.31.8.12 4 64513 0 0 0 0 0 never Active 0 10-31-8-12 Total number of neighbors 3 这时候再查看路由器的监听端口，可以看到BGP已经跑起来了\n$ netstat -ntlup | egrep \u0026#34;zebra|bgpd\u0026#34; tcp 0 0 127.0.0.1:2605 0.0.0.0:* LISTEN 31625/bgpd tcp 0 0 127.0.0.1:2601 0.0.0.0:* LISTEN 31618/zebra tcp 0 0 0.0.0.0:179 0.0.0.0:* LISTEN 31625/bgpd tcp 0 0 :::179 :::* LISTEN 31625/bgpd 配置MetalLB BGP 首先我们修改configmap\napiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | peers: - peer-address: 10.31.254.251 peer-port: 179 peer-asn: 64512 my-asn: 64513 address-pools: - name: default protocol: bgp addresses: - 10.9.0.0/16 修改完成后我们重新部署configmap，并检查metallb的状态\n$ kubectl apply -f configmap-metal.yaml configmap/config configured $ kubectl get cm -n metallb-system config -o yaml apiVersion: v1 data: config: | peers: - peer-address: 10.31.254.251 peer-port: 179 peer-asn: 64512 my-asn: 64513 address-pools: - name: default protocol: bgp addresses: - 10.9.0.0/16 kind: ConfigMap metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {\u0026#34;apiVersion\u0026#34;:\u0026#34;v1\u0026#34;,\u0026#34;data\u0026#34;:{\u0026#34;config\u0026#34;:\u0026#34;peers:\\n- peer-address: 10.31.254.251\\n peer-port: 179\\n peer-asn: 64512\\n my-asn: 64513\\naddress-pools:\\n- name: default\\n protocol: bgp\\n addresses:\\n - 10.9.0.0/16\\n\u0026#34;},\u0026#34;kind\u0026#34;:\u0026#34;ConfigMap\u0026#34;,\u0026#34;metadata\u0026#34;:{\u0026#34;annotations\u0026#34;:{},\u0026#34;name\u0026#34;:\u0026#34;config\u0026#34;,\u0026#34;namespace\u0026#34;:\u0026#34;metallb-system\u0026#34;}} creationTimestamp: \u0026#34;2022-05-16T04:37:54Z\u0026#34; name: config namespace: metallb-system resourceVersion: \u0026#34;1412854\u0026#34; uid: 6d94ca36-93fe-4ea2-9407-96882ad8e35c 此时我们从路由器上面可以看到已经和三个k8s节点建立了BGP连接\ntiny-openwrt-plus# show ip bgp summary IPv4 Unicast Summary (VRF default): BGP router identifier 10.31.254.251, local AS number 64512 vrf-id 0 BGP table version 3 RIB entries 5, using 920 bytes of memory Peers 3, using 2149 KiB of memory Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc 10.31.8.1 4 64513 6 4 0 0 0 00:00:45 3 3 10-31-8-1 10.31.8.11 4 64513 6 4 0 0 0 00:00:45 3 3 10-31-8-11 10.31.8.12 4 64513 6 4 0 0 0 00:00:45 3 3 10-31-8-12 Total number of neighbors 3 如果出现某个节点的BGP连接建立失败的情况，可以重启该节点上面的speaker来重试建立BGP连接\n$ kubectl delete po speaker-fl5l8 -n metallb-system 配置Service 当configmap更改生效之后，原有服务的EXTERNAL-IP不会重新分配\n$ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.8.4.92 10.31.8.100 80/TCP 18h 此时我们可以重启controller，让它重新为我们的服务分配EXTERNAL-IP\n$ kubectl delete po -n metallb-system controller-57fd9c5bb-svtjw pod \u0026#34;controller-57fd9c5bb-svtjw\u0026#34; deleted 重启完成之后我们再检查svc的状态，如果svc的配置中关于LoadBalancer的VIP是自动分配的（即没有指定loadBalancerIP字段），那么这时候应该就已经拿到新的IP在正常运行了，但是我们这个服务的loadBalancerIP之前手动指定为10.31.8.100了，这里的EXTERNAL-IP状态就变为pending。\n$ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.8.4.92 \u0026lt;pending\u0026gt; 80/TCP 18h 重新修改loadBalancerIP为10.9.1.1，此时可以看到服务已经正常\n$ kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.8.4.92 10.9.1.1 80/TCP 18h 再查看controller的日志可以看到\n$ kubectl logs controller-57fd9c5bb-d6jsl -n metallb-system {\u0026#34;branch\u0026#34;:\u0026#34;HEAD\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;commit\u0026#34;:\u0026#34;v0.12.1\u0026#34;,\u0026#34;goversion\u0026#34;:\u0026#34;gc / go1.16.14 / amd64\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;MetalLB controller starting version 0.12.1 (commit v0.12.1, branch HEAD)\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.440872105Z\u0026#34;,\u0026#34;version\u0026#34;:\u0026#34;0.12.1\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;configmap\u0026#34;:\u0026#34;metallb-system/config\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;configLoaded\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;config (re)loaded\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.610395481Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;error\u0026#34;:\u0026#34;[\\\u0026#34;10.31.8.100\\\u0026#34;] is not allowed in config\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;clearAssignment\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;current IP not allowed by config, clearing\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.611009691Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;clearAssignment\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;user requested a different IP than the one currently assigned\u0026#34;,\u0026#34;reason\u0026#34;:\u0026#34;differentIPRequested\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.611062419Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;error\u0026#34;:\u0026#34;controller not synced\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;error\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;controller not synced yet, cannot allocate IP; will retry after sync\u0026#34;,\u0026#34;op\u0026#34;:\u0026#34;allocateIPs\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.611080525Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;stateSynced\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;controller synced, can allocate IPs now\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.611117023Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;error\u0026#34;:\u0026#34;[\\\u0026#34;10.31.8.100\\\u0026#34;] is not allowed in config\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;clearAssignment\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;current IP not allowed by config, clearing\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.617013146Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;clearAssignment\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;user requested a different IP than the one currently assigned\u0026#34;,\u0026#34;reason\u0026#34;:\u0026#34;differentIPRequested\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.617089367Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;error\u0026#34;:\u0026#34;[\\\u0026#34;10.31.8.100\\\u0026#34;] is not allowed in config\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;error\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;IP allocation failed\u0026#34;,\u0026#34;op\u0026#34;:\u0026#34;allocateIPs\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.617122976Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;serviceUpdated\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;updated service object\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.626039403Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;error\u0026#34;:\u0026#34;[\\\u0026#34;10.31.8.100\\\u0026#34;] is not allowed in config\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;error\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;IP allocation failed\u0026#34;,\u0026#34;op\u0026#34;:\u0026#34;allocateIPs\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:45:45.626361986Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;ipAllocated\u0026#34;,\u0026#34;ip\u0026#34;:[\u0026#34;10.9.1.1\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;IP address assigned by controller\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:47:19.943434144Z\u0026#34;} 再查看speaker的日志我们可以看到和路由之间成功建立BGP连接的日志、使用了不符合规范的loadBalancerIP10.31.8.100的报错日志，以及为loadBalancerIP10.9.1.1分配BGP路由的日志\n$ kubectl logs -n metallb-system speaker-bf79q {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;configmap\u0026#34;:\u0026#34;metallb-system/config\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;peerAdded\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;peer configured, starting BGP session\u0026#34;,\u0026#34;peer\u0026#34;:\u0026#34;10.31.254.251\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:41:55.046091105Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;configmap\u0026#34;:\u0026#34;metallb-system/config\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;configLoaded\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;config (re)loaded\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:41:55.046268735Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;error\u0026#34;:\u0026#34;assigned IP not allowed by config\u0026#34;,\u0026#34;ips\u0026#34;:[\u0026#34;10.31.8.100\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;error\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;IP allocated by controller not allowed by config\u0026#34;,\u0026#34;op\u0026#34;:\u0026#34;setBalancer\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:41:55.051955069Z\u0026#34;} struct { Version uint8; ASN16 uint16; HoldTime uint16; RouterID uint32; OptsLen uint8 }{Version:0x4, ASN16:0xfc00, HoldTime:0xb4, RouterID:0xa1ffefd, OptsLen:0x1e} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;sessionUp\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;localASN\u0026#34;:64513,\u0026#34;msg\u0026#34;:\u0026#34;BGP session established\u0026#34;,\u0026#34;peer\u0026#34;:\u0026#34;10.31.254.251:179\u0026#34;,\u0026#34;peerASN\u0026#34;:64512,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:41:55.052734174Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;triggering discovery\u0026#34;,\u0026#34;op\u0026#34;:\u0026#34;memberDiscovery\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:42:40.183574415Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;node event - forcing sync\u0026#34;,\u0026#34;node addr\u0026#34;:\u0026#34;10.31.8.12\u0026#34;,\u0026#34;node event\u0026#34;:\u0026#34;NodeLeave\u0026#34;,\u0026#34;node name\u0026#34;:\u0026#34;tiny-flannel-worker-8-12.k8s.tcinternal\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:44:03.649494062Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;error\u0026#34;:\u0026#34;assigned IP not allowed by config\u0026#34;,\u0026#34;ips\u0026#34;:[\u0026#34;10.31.8.100\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;error\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;IP allocated by controller not allowed by config\u0026#34;,\u0026#34;op\u0026#34;:\u0026#34;setBalancer\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:44:03.655003303Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;node event - forcing sync\u0026#34;,\u0026#34;node addr\u0026#34;:\u0026#34;10.31.8.12\u0026#34;,\u0026#34;node event\u0026#34;:\u0026#34;NodeJoin\u0026#34;,\u0026#34;node name\u0026#34;:\u0026#34;tiny-flannel-worker-8-12.k8s.tcinternal\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:44:06.247929645Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;error\u0026#34;:\u0026#34;assigned IP not allowed by config\u0026#34;,\u0026#34;ips\u0026#34;:[\u0026#34;10.31.8.100\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;error\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;IP allocated by controller not allowed by config\u0026#34;,\u0026#34;op\u0026#34;:\u0026#34;setBalancer\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:44:06.25369106Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;updatedAdvertisements\u0026#34;,\u0026#34;ips\u0026#34;:[\u0026#34;10.9.1.1\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;making advertisements using BGP\u0026#34;,\u0026#34;numAds\u0026#34;:1,\u0026#34;pool\u0026#34;:\u0026#34;default\u0026#34;,\u0026#34;protocol\u0026#34;:\u0026#34;bgp\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:47:19.953729779Z\u0026#34;} {\u0026#34;caller\u0026#34;:\u0026#34;level.go:63\u0026#34;,\u0026#34;event\u0026#34;:\u0026#34;serviceAnnounced\u0026#34;,\u0026#34;ips\u0026#34;:[\u0026#34;10.9.1.1\u0026#34;],\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;service has IP, announcing\u0026#34;,\u0026#34;pool\u0026#34;:\u0026#34;default\u0026#34;,\u0026#34;protocol\u0026#34;:\u0026#34;bgp\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;nginx-quic/nginx-lb-service\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2022-05-18T03:47:19.953912236Z\u0026#34;} 我们在集群外的任意一个机器进行测试\n$ curl -v 10.9.1.1 * About to connect() to 10.9.1.1 port 80 (#0) * Trying 10.9.1.1... * Connected to 10.9.1.1 (10.9.1.1) port 80 (#0) \u0026gt; GET / HTTP/1.1 \u0026gt; User-Agent: curl/7.29.0 \u0026gt; Host: 10.9.1.1 \u0026gt; Accept: */* \u0026gt; \u0026lt; HTTP/1.1 200 OK \u0026lt; Server: nginx \u0026lt; Date: Wed, 18 May 2022 04:17:41 GMT \u0026lt; Content-Type: text/plain \u0026lt; Content-Length: 16 \u0026lt; Connection: keep-alive \u0026lt; 10.8.64.0:43939 * Connection #0 to host 10.9.1.1 left intact 检查ECMP 此时再查看路由器上面的路由状态，可以看到有关于10.9.1.1的/32路由，这时候的下一条有多个IP，说明已经成功开启了ECMP。\ntiny-openwrt-plus# show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR, f - OpenFabric, \u0026gt; - selected route, * - FIB route, q - queued, r - rejected, b - backup t - trapped, o - offload failure K\u0026gt;* 0.0.0.0/0 [0/0] via 10.31.254.254, eth0, 00:04:52 B\u0026gt;* 10.9.1.1/32 [20/0] via 10.31.8.1, eth0, weight 1, 00:01:40 * via 10.31.8.11, eth0, weight 1, 00:01:40 * via 10.31.8.12, eth0, weight 1, 00:01:40 C\u0026gt;* 10.31.0.0/16 is directly connected, eth0, 00:04:52 我们再创建多几个服务进行测试\n# kubectl get svc -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb-service LoadBalancer 10.8.4.92 10.9.1.1 80/TCP 23h nginx-lb2-service LoadBalancer 10.8.10.48 10.9.1.2 80/TCP 64m nginx-lb3-service LoadBalancer 10.8.6.116 10.9.1.3 80/TCP 64m 再查看此时路由器的状态\ntiny-openwrt-plus# show ip bgp BGP table version is 3, local router ID is 10.31.254.251, vrf id 0 Default local pref 100, local AS 64512 Status codes: s suppressed, d damped, h history, * valid, \u0026gt; best, = multipath, i internal, r RIB-failure, S Stale, R Removed Nexthop codes: @NNN nexthop\u0026#39;s vrf id, \u0026lt; announce-nh-self Origin codes: i - IGP, e - EGP, ? - incomplete RPKI validation codes: V valid, I invalid, N Not found Network Next Hop Metric LocPrf Weight Path *= 10.9.1.1/32 10.31.8.12 0 64513 ? *\u0026gt; 10.31.8.1 0 64513 ? *= 10.31.8.11 0 64513 ? *= 10.9.1.2/32 10.31.8.12 0 64513 ? *\u0026gt; 10.31.8.1 0 64513 ? *= 10.31.8.11 0 64513 ? *= 10.9.1.3/32 10.31.8.12 0 64513 ? *\u0026gt; 10.31.8.1 0 64513 ? *= 10.31.8.11 0 64513 ? Displayed 3 routes and 9 total paths tiny-openwrt-plus# show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR, f - OpenFabric, \u0026gt; - selected route, * - FIB route, q - queued, r - rejected, b - backup t - trapped, o - offload failure K\u0026gt;* 0.0.0.0/0 [0/0] via 10.31.254.254, eth0, 00:06:12 B\u0026gt;* 10.9.1.1/32 [20/0] via 10.31.8.1, eth0, weight 1, 00:03:00 * via 10.31.8.11, eth0, weight 1, 00:03:00 * via 10.31.8.12, eth0, weight 1, 00:03:00 B\u0026gt;* 10.9.1.2/32 [20/0] via 10.31.8.1, eth0, weight 1, 00:03:00 * via 10.31.8.11, eth0, weight 1, 00:03:00 * via 10.31.8.12, eth0, weight 1, 00:03:00 B\u0026gt;* 10.9.1.3/32 [20/0] via 10.31.8.1, eth0, weight 1, 00:03:00 * via 10.31.8.11, eth0, weight 1, 00:03:00 * via 10.31.8.12, eth0, weight 1, 00:03:00 C\u0026gt;* 10.31.0.0/16 is directly connected, eth0, 00:06:12 只有当路由表显示我们的LoadBalancerIP的下一跳有多个IP的时候，才说明ECMP配置成功，否则需要检查BGP的配置是否正确。\n总结 Layer2 mode优缺点 优点：\n通用性强，对比BGP模式不需要BGP路由器支持，几乎可以适用于任何网络环境；当然云厂商的网络环境例外 缺点：\n所有的流量都会在同一个节点上，该节点的容易成为流量的瓶颈 当VIP所在节点宕机之后，需要较长时间进行故障转移（一般在10s），这主要是因为MetalLB使用了memberlist来进行选主，当VIP所在节点宕机之后重新选主的时间要比传统的keepalived使用的vrrp协议要更长 难以定位VIP所在节点，MetalLB并没有提供一个简单直观的方式让我们查看到底哪一个节点是VIP所属节点，基本只能通过抓包或者查看pod日志来确定，当集群规模变大的时候这会变得非常的麻烦 改进方案：\n有条件的可以考虑使用BGP模式 既不能用BGP模式也不能接受Layer2模式的，基本和目前主流的三个开源负载均衡器无缘了（三者都是Layer2模式和BGP模式且原理类似，优缺点相同） BGP mode优缺点 BGP模式的优缺点几乎和Layer2模式相反\n优点：\n无单点故障，在开启ECMP的前提下，k8s集群内所有的节点都有请求流量，都会参与负载均衡并转发请求 缺点：\n条件苛刻，需要有BGP路由器支持，配置起来也更复杂； ECMP的故障转移（failover）并不是特别地优雅，这个问题的严重程度取决于使用的ECMP算法；当集群的节点出现变动导致BGP连接出现变动，所有的连接都会进行重新哈希（使用三元组或五元组哈希），这对一些服务来说可能会有影响； 路由器中使用的哈希值通常 不稳定，因此每当后端集的大小发生变化时（例如，当一个节点的 BGP 会话关闭时），现有的连接将被有效地随机重新哈希，这意味着大多数现有的连接最终会突然被转发到不同的后端，而这个后端可能和此前的后端毫不相干且不清楚上下文状态信息。\n改进方案：\nMetalLB给出了一些改进方案，下面列出来给大家参考一下\n使用更稳定的ECMP算法来减少后端变动时对现有连接的影响，如“resilient ECMP” or “resilient LAG” 将服务部署到特定的节点上减少可能带来的影响 在流量低峰期进行变更 将服务分开部署到两个不同的LoadBalanceIP的服务中，然后利用DNS进行流量切换 在客户端加入透明的用户无感的重试逻辑 在LoadBalance后面加入一层ingress来实现更优雅的failover（但是并不是所有的服务都可以使用ingress） 接受现实……（Accept that there will be occasional bursts of reset connections. For low-availability internal services, this may be acceptable as-is.） MetalLB的优缺点 这里尽量客观的总结概况一些客观事实，是否为优缺点可能会因人而异：\n开源时间久（相对于云原生负载均衡器而言），有一定的社区基础和热度，但是项目目前还处于beta状态 部署简单快捷，默认情况下需要配置的参数不多，可以快速上手 官方文档不多，只有一些基础的配置和说明，想要深入了解，可能需要阅读源码 进阶管理配置不便，如果你想精确了解服务的当前状态，可能会比较麻烦 configmap修改之后生效不是特别的优雅，很多情况下需要我们手动重启pod 总的来说，MetalLB作为一款处于beta阶段的开源负载均衡器，很好地弥补了这一块领域的空白，并且对后面开源的一些同类服务有着一定的影响。但是从实际生产落地的角度，给我的感觉就是目前更倾向于有得用且能用，并不能算得上好用，但是又考虑到MetalLB最开始只是一个个人开源项目，最近才有专门的组织进行管理维护，这也是可以理解的，希望它能够发展得更好吧。\n","permalink":"http://localhost:9011/20220519-k8s-06-loadbalancer-metallb/","summary":"\u003cp\u003e本文主要在k8s原生集群上部署\u003ccode\u003ev0.12.1\u003c/code\u003e版本的\u003ccode\u003eMetalLB\u003c/code\u003e作为k8s的\u003ccode\u003eLoadBalancer\u003c/code\u003e，主要涉及\u003cstrong\u003eMetalLB\u003c/strong\u003e的\u003cstrong\u003eLayer2模式\u003c/strong\u003e和\u003cstrong\u003eBGP模式\u003c/strong\u003e两种部署方案。由于BGP的相关原理和配置比较复杂，这里仅涉及简单的BGP配置。\u003c/p\u003e\n\u003cp\u003e文中使用的k8s集群是在CentOS7系统上基于\u003ccode\u003edocker\u003c/code\u003e和\u003ccode\u003eflannel\u003c/code\u003e组件部署\u003ccode\u003ev1.23.6\u003c/code\u003e版本，此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列06-负载均衡器之MatelLB"},{"content":"本文主要在centos7系统上基于containerd和stable版本（1.11.4）的cilium组件部署v1.24.0版本的k8s原生集群，由于集群主要用于自己平时学习和测试使用，加上资源有限，暂不涉及高可用部署。\n此外，由于cilium已经实现了对kube-proxy的一整套替代方案，这里部署k8s集群的时候会使用cilium的kubeproxy-free方案。\n此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n准备工作 集群信息 机器均为8C8G的虚拟机，硬盘为100G。\nIP Hostname 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal 10.31.18.11 tiny-kubeproxy-free-worker-18-11.k8s.tcinternal 10.31.18.12 tiny-kubeproxy-free-worker-18-12.k8s.tcinternal 10.18.64.0/18 podSubnet 10.18.0.0/18 serviceSubnet 检查mac和product_uuid 同一个k8s集群内的所有节点需要确保mac地址和product_uuid均唯一，开始集群初始化之前需要检查相关信息\n# 检查mac地址 ip link ifconfig -a # 检查product_uuid sudo cat /sys/class/dmi/id/product_uuid 配置ssh免密登录（可选） 如果k8s集群的节点有多个网卡，确保每个节点能通过正确的网卡互联访问\n# 在root用户下面生成一个公用的key，并配置可以使用该key免密登录 su root ssh-keygen cd /root/.ssh/ cat id_rsa.pub \u0026gt;\u0026gt; authorized_keys chmod 600 authorized_keys cat \u0026gt;\u0026gt; ~/.ssh/config \u0026lt;\u0026lt;EOF Host tiny-kubeproxy-free-master-18-1.k8s.tcinternal HostName 10.31.18.1 User root Port 22 IdentityFile ~/.ssh/id_rsa Host tiny-kubeproxy-free-worker-18-11.k8s.tcinternal HostName 10.31.18.11 User root Port 22 IdentityFile ~/.ssh/id_rsa Host tiny-kubeproxy-free-worker-18-12.k8s.tcinternal HostName 10.31.18.12 User root Port 22 IdentityFile ~/.ssh/id_rsa EOF 修改hosts文件 cat \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt;EOF 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal 10.31.18.11 tiny-kubeproxy-free-worker-18-11.k8s.tcinternal 10.31.18.12 tiny-kubeproxy-free-worker-18-12.k8s.tcinternal EOF 关闭swap内存 # 使用命令直接关闭swap内存 swapoff -a # 修改fstab文件禁止开机自动挂载swap分区 sed -i \u0026#39;/swap / s/^\\(.*\\)$/#\\1/g\u0026#39; /etc/fstab 配置时间同步 这里可以根据自己的习惯选择ntp或者是chrony同步均可，同步的时间源服务器可以选择阿里云的ntp1.aliyun.com或者是国家时间中心的ntp.ntsc.ac.cn。\n使用ntp同步 # 使用yum安装ntpdate工具 yum install ntpdate -y # 使用国家时间中心的源同步时间 ntpdate ntp.ntsc.ac.cn # 最后查看一下时间 hwclock 使用chrony同步 # 使用yum安装chrony yum install chrony -y # 设置开机启动并开启chony并查看运行状态 systemctl enable chronyd.service systemctl start chronyd.service systemctl status chronyd.service # 当然也可以自定义时间服务器 vim /etc/chrony.conf # 修改前 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server 0.centos.pool.ntp.org iburst server 1.centos.pool.ntp.org iburst server 2.centos.pool.ntp.org iburst server 3.centos.pool.ntp.org iburst # 修改后 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server ntp.ntsc.ac.cn iburst # 重启服务使配置文件生效 systemctl restart chronyd.service # 查看chrony的ntp服务器状态 chronyc sourcestats -v chronyc sources -v 关闭selinux # 使用命令直接关闭 setenforce 0 # 也可以直接修改/etc/selinux/config文件 sed -i \u0026#39;s/^SELINUX=enforcing$/SELINUX=disabled/\u0026#39; /etc/selinux/config 配置防火墙 k8s集群之间通信和服务暴露需要使用较多端口，为了方便，直接禁用防火墙\n# centos7使用systemctl禁用默认的firewalld服务 systemctl disable firewalld.service 配置netfilter参数 这里主要是需要配置内核加载br_netfilter和iptables放行ipv6和ipv4的流量，确保集群内的容器能够正常通信。\ncat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter EOF cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sudo sysctl --system 关闭IPV6（不建议） 和之前部署其他的CNI不一样，cilium很多服务监听默认情况下都是双栈的（使用cilium-cli操作的时候），因此建议开启系统的IPV6网络支持（即使没有可用的IPV6路由也可以）\n当然没有ipv6网络也是可以的，只是在使用cilium-cli的一些开启port-forward命令时会报错而已。\n# 直接在内核中添加ipv6禁用参数 grubby --update-kernel=ALL --args=ipv6.disable=1 配置IPVS（可以不用） IPVS是专门设计用来应对负载均衡场景的组件，kube-proxy 中的 IPVS 实现通过减少对 iptables 的使用来增加可扩展性。在 iptables 输入链中不使用 PREROUTING，而是创建一个假的接口，叫做 kube-ipvs0，当k8s集群中的负载均衡配置变多的时候，IPVS能实现比iptables更高效的转发性能。\n如果我们使用的是cilium来完全替代kube-proxy，那么实际上就用不到ipvs和iptables，因此这一步理论上是可以跳过的。\n因为cilium需要升级系统内核，因此这里的内核版本高于4.19\n注意在4.19之后的内核版本中使用nf_conntrack模块来替换了原有的nf_conntrack_ipv4模块\n(Notes: use nf_conntrack instead of nf_conntrack_ipv4 for Linux kernel 4.19 and later)\n# 在使用ipvs模式之前确保安装了ipset和ipvsadm sudo yum install ipset ipvsadm -y # 手动加载ipvs相关模块 modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack # 配置开机自动加载ipvs相关模块 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/ipvs.conf ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack EOF sudo sysctl --system # 最好重启一遍系统确定是否生效 $ lsmod | grep -e ip_vs -e nf_conntrack nf_conntrack_netlink 49152 0 nfnetlink 20480 2 nf_conntrack_netlink ip_vs_sh 16384 0 ip_vs_wrr 16384 0 ip_vs_rr 16384 0 ip_vs 159744 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr nf_conntrack 159744 5 xt_conntrack,nf_nat,nf_conntrack_netlink,xt_MASQUERADE,ip_vs nf_defrag_ipv4 16384 1 nf_conntrack nf_defrag_ipv6 24576 2 nf_conntrack,ip_vs libcrc32c 16384 4 nf_conntrack,nf_nat,xfs,ip_vs $ cut -f1 -d \u0026#34; \u0026#34; /proc/modules | grep -e ip_vs -e nf_conntrack nf_conntrack_netlink ip_vs_sh ip_vs_wrr ip_vs_rr ip_vs nf_conntrack 配置Linux内核（cilium必选） cilium和其他的cni组件最大的不同在于其底层使用了ebpf技术，而该技术对于Linux的系统内核版本有较高的要求，完成的要求可以查看官网的详细链接，这里我们着重看内核版本、内核参数这两个部分。\nLinux内核版本 默认情况下我们可以参考cilium官方给出的一个系统要求总结。因为我们是在k8s集群中部署（使用容器），因此只需要关注Linux内核版本和etcd版本即可。根据前面部署的经验我们可以知道1.23.6版本的k8s默认使用的etcd版本是3.5.+，因此重点就来到了Linux内核版本这里。\nRequirement Minimum Version In cilium container Linux kernel \u0026gt;= 4.9.17 no Key-Value store (etcd) \u0026gt;= 3.1.0 no clang+LLVM \u0026gt;= 10.0 yes iproute2 \u0026gt;= 5.9.0 yes This requirement is only needed if you run cilium-agent natively. If you are using the Cilium container image cilium/cilium, clang+LLVM is included in the container image.\niproute2 is only needed if you run cilium-agent directly on the host machine. iproute2 is included in the cilium/cilium container image.\n毫无疑问CentOS7内置的默认内核版本3.10.x版本的内核是无法满足需求的，但是在升级内核之前，我们再看看其他的一些要求。\ncilium官方还给出了一份列表描述了各项高级功能对内核版本的要求：\nCilium Feature Minimum Kernel Version IPv4 fragment handling \u0026gt;= 4.10 Restrictions on unique prefix lengths for CIDR policy rules \u0026gt;= 4.11 IPsec Transparent Encryption in tunneling mode \u0026gt;= 4.19 WireGuard Transparent Encryption \u0026gt;= 5.6 Host-Reachable Services \u0026gt;= 4.19.57, \u0026gt;= 5.1.16, \u0026gt;= 5.2 Kubernetes Without kube-proxy \u0026gt;= 4.19.57, \u0026gt;= 5.1.16, \u0026gt;= 5.2 Bandwidth Manager \u0026gt;= 5.1 Local Redirect Policy (beta) \u0026gt;= 4.19.57, \u0026gt;= 5.1.16, \u0026gt;= 5.2 Full support for Session Affinity \u0026gt;= 5.7 BPF-based proxy redirection \u0026gt;= 5.7 BPF-based host routing \u0026gt;= 5.10 Socket-level LB bypass in pod netns \u0026gt;= 5.7 Egress Gateway (beta) \u0026gt;= 5.2 VXLAN Tunnel Endpoint (VTEP) Integration \u0026gt;= 5.2 可以看到如果需要满足上面所有需求的话，需要内核版本高于5.10，本着学习测试研究作死的精神，反正都升级了，干脆就升级到新一些的版本吧。这里我们可以直接使用elrepo源来升级内核到较新的内核版本。\n# 查看elrepo源中支持的内核版本 $ yum --disablerepo=\u0026#34;*\u0026#34; --enablerepo=\u0026#34;elrepo-kernel\u0026#34; list available Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile Available Packages elrepo-release.noarch 7.0-5.el7.elrepo elrepo-kernel kernel-lt.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-devel.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-doc.noarch 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-headers.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-tools.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-tools-libs.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-tools-libs-devel.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-ml.x86_64 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-devel.x86_64 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-doc.noarch 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-headers.x86_64 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-tools.x86_64 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-tools-libs.x86_64 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-tools-libs-devel.x86_64 5.17.6-1.el7.elrepo elrepo-kernel perf.x86_64 5.17.6-1.el7.elrepo elrepo-kernel python-perf.x86_64 5.17.6-1.el7.elrepo elrepo-kernel # 看起来ml版本的内核比较满足我们的需求,直接使用yum进行安装 sudo yum --enablerepo=elrepo-kernel install kernel-ml -y # 使用grubby工具查看系统中已经安装的内核版本信息 sudo grubby --info=ALL # 设置新安装的5.17.6版本内核为默认内核版本，此处的index=0要和上面查看的内核版本信息一致 sudo grubby --set-default-index=0 # 查看默认内核是否修改成功 sudo grubby --default-kernel # 重启系统切换到新内核 init 6 # 重启后检查内核版本是否为新的5.17.6 uname -a Linux内核参数 首先我们查看自己当前内核版本的参数，基本上可以分为y、n、m三个选项\ny：yes，Build directly into the kernel. 表示该功能被编译进内核中，默认启用 n：no，Leave entirely out of the kernel. 表示该功能未被编译进内核中，不启用 m：module，Build as a module, to be loaded if needed. 表示该功能被编译为模块，按需启用 # 查看当前使用的内核版本的编译参数 cat /boot/config-$(uname -r) cilium官方对各项功能所需要开启的内核参数列举如下：\nIn order for the eBPF feature to be enabled properly, the following kernel configuration options must be enabled. This is typically the case with distribution kernels. When an option can be built as a module or statically linked, either choice is valid.\n为了正确启用 eBPF 功能，必须启用以下内核配置选项。这通常因内核版本情况而异。任何一个选项都可以构建为模块或静态链接，两个选择都是有效的。\n我们暂时只看最基本的Base Requirements\nCONFIG_BPF=y CONFIG_BPF_SYSCALL=y CONFIG_NET_CLS_BPF=y CONFIG_BPF_JIT=y CONFIG_NET_CLS_ACT=y CONFIG_NET_SCH_INGRESS=y CONFIG_CRYPTO_SHA1=y CONFIG_CRYPTO_USER_API_HASH=y CONFIG_CGROUPS=y CONFIG_CGROUP_BPF=y 对比我们使用的5.17.6-1.el7.elrepo.x86_64内核可以发现有两个模块是为m\n$ egrep \u0026#34;^CONFIG_BPF=|^CONFIG_BPF_SYSCALL=|^CONFIG_NET_CLS_BPF=|^CONFIG_BPF_JIT=|^CONFIG_NET_CLS_ACT=|^CONFIG_NET_SCH_INGRESS=|^CONFIG_CRYPTO_SHA1=|^CONFIG_CRYPTO_USER_API_HASH=|^CONFIG_CGROUPS=|^CONFIG_CGROUP_BPF=\u0026#34; /boot/config-5.17.6-1.el7.elrepo.x86_64 CONFIG_BPF=y CONFIG_BPF_SYSCALL=y CONFIG_BPF_JIT=y CONFIG_CGROUPS=y CONFIG_CGROUP_BPF=y CONFIG_NET_SCH_INGRESS=m CONFIG_NET_CLS_BPF=m CONFIG_NET_CLS_ACT=y CONFIG_CRYPTO_SHA1=y CONFIG_CRYPTO_USER_API_HASH=y 缺少的这两个模块我们可以在/usr/lib/modules/$(uname -r)目录下面找到它们：\n$ realpath ./kernel/net/sched/sch_ingress.ko /usr/lib/modules/5.17.6-1.el7.elrepo.x86_64/kernel/net/sched/sch_ingress.ko $ realpath ./kernel/net/sched/cls_bpf.ko /usr/lib/modules/5.17.6-1.el7.elrepo.x86_64/kernel/net/sched/cls_bpf.ko 确认相关内核模块存在我们直接加载内核即可：\n# 直接使用modprobe命令加载 $ modprobe cls_bpf $ modprobe sch_ingress $ lsmod | egrep \u0026#34;cls_bpf|sch_ingress\u0026#34; sch_ingress 16384 0 cls_bpf 24576 0 # 配置开机自动加载cilium所需相关模块 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/cilium-base-requirements.conf cls_bpf sch_ingress EOF 其他cilium高级功能所需要的内核功能也类似，这里不做赘述。\n安装container runtime 安装containerd 详细的官方文档可以参考这里，由于在刚发布的1.24版本中移除了docker-shim，因此安装的版本≥1.24的时候需要注意容器运行时的选择。这里我们安装的版本为最新的1.24，因此我们不能继续使用docker，这里我们将其换为containerd\n修改Linux内核参数 # 首先生成配置文件确保配置持久化 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter # Setup required sysctl params, these persist across reboots. cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF # Apply sysctl params without reboot sudo sysctl --system 安装containerd centos7比较方便的部署方式是利用已有的yum源进行安装，这里我们可以使用docker官方的yum源来安装containerd\n# 导入docker官方的yum源 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 查看yum源中存在的各个版本的containerd.io yum list containerd.io --showduplicates | sort -r # 直接安装最新版本的containerd.io yum install containerd.io -y # 启动containerd sudo systemctl start containerd # 最后我们还要设置一下开机启动 sudo systemctl enable --now containerd 关于CRI 官方表示，对于k8s来说，不需要安装cri-containerd，并且该功能会在后面的2.0版本中废弃。\nFAQ: For Kubernetes, do I need to download cri-containerd-(cni-)\u0026lt;VERSION\u0026gt;-\u0026lt;OS-\u0026lt;ARCH\u0026gt;.tar.gz too?\nAnswer: No.\nAs the Kubernetes CRI feature has been already included in containerd-\u0026lt;VERSION\u0026gt;-\u0026lt;OS\u0026gt;-\u0026lt;ARCH\u0026gt;.tar.gz, you do not need to download the cri-containerd-.... archives to use CRI.\nThe cri-containerd-... archives are deprecated, do not work on old Linux distributions, and will be removed in containerd 2.0.\n安装cni-plugins 使用yum源安装的方式会把runc安装好，但是并不会安装cni-plugins，因此这部分还是需要我们自行安装。\nThe containerd.io package contains runc too, but does not contain CNI plugins.\n我们直接在github上面找到系统对应的架构版本，这里为amd64，然后解压即可。\n# Download the cni-plugins-\u0026lt;OS\u0026gt;-\u0026lt;ARCH\u0026gt;-\u0026lt;VERSION\u0026gt;.tgz archive from https://github.com/containernetworking/plugins/releases , verify its sha256sum, and extract it under /opt/cni/bin: # 下载源文件和sha512文件并校验 $ wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz $ wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz.sha512 $ sha512sum -c cni-plugins-linux-amd64-v1.1.1.tgz.sha512 # 创建目录并解压 $ mkdir -p /opt/cni/bin $ tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.1.1.tgz 配置cgroup drivers CentOS7使用的是systemd来初始化系统并管理进程，初始化进程会生成并使用一个 root 控制组 (cgroup), 并充当 cgroup 管理器。 Systemd 与 cgroup 集成紧密，并将为每个 systemd 单元分配一个 cgroup。 我们也可以配置容器运行时和 kubelet 使用 cgroupfs。 连同 systemd 一起使用 cgroupfs 意味着将有两个不同的 cgroup 管理器。而当一个系统中同时存在cgroupfs和systemd两者时，容易变得不稳定，因此最好更改设置，令容器运行时和 kubelet 使用 systemd 作为 cgroup 驱动，以此使系统更为稳定。 对于containerd, 需要设置配置文件/etc/containerd/config.toml中的 SystemdCgroup 参数。\n参考k8s官方的说明文档：\nhttps://kubernetes.io/docs/setup/production-environment/container-runtimes/#containerd-systemd\n[plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc] ... [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc.options] SystemdCgroup = true 接下来我们开始配置containerd的cgroup driver\n# 查看默认的配置文件，我们可以看到是没有启用systemd $ containerd config default | grep SystemdCgroup SystemdCgroup = false # 使用yum安装的containerd的配置文件非常简单 $ cat /etc/containerd/config.toml | egrep -v \u0026#34;^#|^$\u0026#34; disabled_plugins = [\u0026#34;cri\u0026#34;] # 导入一个完整版的默认配置文件模板为config.toml $ mv /etc/containerd/config.toml /etc/containerd/config.toml.origin $ containerd config default \u0026gt; /etc/containerd/config.toml # 修改SystemdCgroup参数并重启 $ sed -i \u0026#39;s/SystemdCgroup = false/SystemdCgroup = true/g\u0026#39; /etc/containerd/config.toml $ systemctl restart containerd # 查看containerd状态的时候我们可以看到cni相关的报错 # 这是因为我们先安装了cni-plugins但是还没有安装k8s的cni插件 # 属于正常情况 $ systemctl status containerd -l May 12 09:57:31 tiny-kubeproxy-free-master-18-1.k8s.tcinternal containerd[5758]: time=\u0026#34;2022-05-12T09:57:31.100285056+08:00\u0026#34; level=error msg=\u0026#34;failed to load cni during init, please check CRI plugin status before setting up network for pods\u0026#34; error=\u0026#34;cni config load failed: no network config found in /etc/cni/net.d: cni plugin not initialized: failed to load cni config\u0026#34; 关于kubelet的cgroup driver k8s官方有详细的文档介绍了如何设置kubelet的cgroup driver，需要特别注意的是，在1.22版本开始，如果没有手动设置kubelet的cgroup driver，那么默认会设置为systemd\nNote: In v1.22, if the user is not setting the cgroupDriver field under KubeletConfiguration, kubeadm will default it to systemd.\n一个比较简单的指定kubelet的cgroup driver的方法就是在kubeadm-config.yaml加入cgroupDriver字段\n# kubeadm-config.yaml kind: ClusterConfiguration apiVersion: kubeadm.k8s.io/v1beta3 kubernetesVersion: v1.21.0 --- kind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 cgroupDriver: systemd 我们可以直接查看configmaps来查看初始化之后集群的kubeadm-config配置。\n$ kubectl describe configmaps kubeadm-config -n kube-system Name: kubeadm-config Namespace: kube-system Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; Data ==== ClusterConfiguration: ---- apiServer: extraArgs: authorization-mode: Node,RBAC timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: v1.23.6 networking: dnsDomain: cali-cluster.tclocal serviceSubnet: 10.88.0.0/18 scheduler: {} BinaryData ==== Events: \u0026lt;none\u0026gt; 当然因为我们需要安装的版本高于1.22.0并且使用的就是systemd，因此可以不用再重复配置。\n安装kube三件套 对应的官方文档可以参考这里\nhttps://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl\nkube三件套就是kubeadm、kubelet 和 kubectl，三者的具体功能和作用如下：\nkubeadm：用来初始化集群的指令。 kubelet：在集群中的每个节点上用来启动 Pod 和容器等。 kubectl：用来与集群通信的命令行工具。 需要注意的是：\nkubeadm不会帮助我们管理kubelet和kubectl，其他两者也是一样的，也就是说这三者是相互独立的，并不存在谁管理谁的情况； kubelet的版本必须小于等于API-server的版本，否则容易出现兼容性的问题； kubectl并不是集群中的每个节点都需要安装，也并不是一定要安装在集群中的节点，可以单独安装在自己本地的机器环境上面，然后配合kubeconfig文件即可使用kubectl命令来远程管理对应的k8s集群； CentOS7的安装比较简单，我们直接使用官方提供的yum源即可。需要注意的是这里需要设置selinux的状态，但是前面我们已经关闭了selinux，因此这里略过这步。\n# 直接导入谷歌官方的yum源 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\\$basearch enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg exclude=kubelet kubeadm kubectl EOF # 当然如果连不上谷歌的源，可以考虑使用国内的阿里镜像源 cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF # 接下来直接安装三件套即可 sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes # 如果网络环境不好出现gpgcheck验证失败导致无法正常读取yum源，可以考虑关闭该yum源的repo_gpgcheck sed -i \u0026#39;s/repo_gpgcheck=1/repo_gpgcheck=0/g\u0026#39; /etc/yum.repos.d/kubernetes.repo # 或者在安装的时候禁用gpgcheck sudo yum install -y kubelet kubeadm kubectl --nogpgcheck --disableexcludes=kubernetes # 如果想要安装特定版本，可以使用这个命令查看相关版本的信息 sudo yum list --nogpgcheck kubelet kubeadm kubectl --showduplicates --disableexcludes=kubernetes # 安装完成后配置开机自启kubelet sudo systemctl enable --now kubelet 初始化集群 编写配置文件 在集群中所有节点都执行完上面的三点操作之后，我们就可以开始创建k8s集群了。因为我们这次不涉及高可用部署，因此初始化的时候直接在我们的目标master节点上面操作即可。\n# 我们先使用kubeadm命令查看一下主要的几个镜像版本 $ kubeadm config images list k8s.gcr.io/kube-apiserver:v1.24.0 k8s.gcr.io/kube-controller-manager:v1.24.0 k8s.gcr.io/kube-scheduler:v1.24.0 k8s.gcr.io/kube-proxy:v1.24.0 k8s.gcr.io/pause:3.7 k8s.gcr.io/etcd:3.5.3-0 k8s.gcr.io/coredns/coredns:v1.8.6 # 为了方便编辑和管理，我们还是把初始化参数导出成配置文件 $ kubeadm config print init-defaults \u0026gt; kubeadm-kubeproxy-free.conf 考虑到大多数情况下国内的网络无法使用谷歌的k8s.gcr.io镜像源，我们可以直接在配置文件中修改imageRepository参数为阿里的镜像源 kubernetesVersion字段用来指定我们要安装的k8s版本 localAPIEndpoint参数需要修改为我们的master节点的IP和端口，初始化之后的k8s集群的apiserver地址就是这个 criSocket从1.24.0版本开始已经默认变成了containerd podSubnet、serviceSubnet和dnsDomain两个参数默认情况下可以不用修改，这里我按照自己的需求进行了变更 nodeRegistration里面的name参数修改为对应master节点的hostname 新增配置块使用ipvs，具体可以参考官方文档 apiVersion: kubeadm.k8s.io/v1beta3 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 10.31.18.1 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock imagePullPolicy: IfNotPresent name: tiny-kubeproxy-free-master-18-1.k8s.tcinternal taints: null --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: 1.24.0 networking: dnsDomain: free-cluster.tclocal serviceSubnet: 10.18.0.0/18 podSubnet: 10.18.64.0/18 scheduler: {} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs 初始化集群 此时我们再查看对应的配置文件中的镜像版本，就会发现已经变成了对应阿里云镜像源的版本\n参考cilium官方的教程我们可以在集群初始化的时候添加参数--skip-phases=addon/kube-proxy跳过kube-proxy的安装\n# 查看一下对应的镜像版本，确定配置文件是否生效 $ kubeadm config images list --config kubeadm-kubeproxy-free.conf registry.aliyuncs.com/google_containers/kube-apiserver:v1.24.0 registry.aliyuncs.com/google_containers/kube-controller-manager:v1.24.0 registry.aliyuncs.com/google_containers/kube-scheduler:v1.24.0 registry.aliyuncs.com/google_containers/kube-proxy:v1.24.0 registry.aliyuncs.com/google_containers/pause:3.7 registry.aliyuncs.com/google_containers/etcd:3.5.3-0 registry.aliyuncs.com/google_containers/coredns:v1.8.6 # 确认没问题之后我们直接拉取镜像 $ kubeadm config images pull --config kubeadm-kubeproxy-free.conf [config/images] Pulled registry.aliyuncs.com/google_containers/kube-apiserver:v1.24.0 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-controller-manager:v1.24.0 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-scheduler:v1.24.0 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-proxy:v1.24.0 [config/images] Pulled registry.aliyuncs.com/google_containers/pause:3.7 [config/images] Pulled registry.aliyuncs.com/google_containers/etcd:3.5.3-0 [config/images] Pulled registry.aliyuncs.com/google_containers/coredns:v1.8.6 # 初始化，注意添加参数跳过kube-proxy的安装 $ kubeadm init --config kubeadm-kubeproxy-free.conf --skip-phases=addon/kube-proxy [init] Using Kubernetes version: v1.24.0 [preflight] Running pre-flight checks [preflight] Pulling images required for setting up a Kubernetes cluster [preflight] This might take a minute or two, depending on the speed of your internet connection [preflight] You can also perform this action in beforehand using \u0026#39;kubeadm config images pull\u0026#39; ...此处略去一堆输出... 当我们看到下面这个输出结果的时候，我们的集群就算是初始化成功了。\nYour Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf You should now deploy a pod network to the cluster. Run \u0026#34;kubectl apply -f [podnetwork].yaml\u0026#34; with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 10.31.18.1:6443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:7772f5461bdf4dc399618dc226e2d718d35f14b079e904cd68a5b148eaefcbdd 配置kubeconfig 刚初始化成功之后，我们还没办法马上查看k8s集群信息，需要配置kubeconfig相关参数才能正常使用kubectl连接apiserver读取集群信息。\n# 对于非root用户，可以这样操作 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 如果是root用户，可以直接导入环境变量 export KUBECONFIG=/etc/kubernetes/admin.conf # 添加kubectl的自动补全功能 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 前面我们提到过kubectl不一定要安装在集群内，实际上只要是任何一台能连接到apiserver的机器上面都可以安装kubectl并且根据步骤配置kubeconfig，就可以使用kubectl命令行来管理对应的k8s集群。\n配置完成后，我们再执行相关命令就可以查看集群的信息了。\n$ kubectl cluster-info Kubernetes control plane is running at https://10.31.18.1:6443 CoreDNS is running at https://10.31.18.1:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39;. $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME tiny-kubeproxy-free-master-18-1.k8s.tcinternal NotReady control-plane 2m46s v1.24.0 10.31.18.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 5.17.6-1.el7.elrepo.x86_64 containerd://1.6.4 $ kubectl get pods -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system coredns-74586cf9b6-shpt4 0/1 Pending 0 2m42s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system coredns-74586cf9b6-wgvgm 0/1 Pending 0 2m42s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-tiny-kubeproxy-free-master-18-1.k8s.tcinternal 1/1 Running 0 2m56s 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-tiny-kubeproxy-free-master-18-1.k8s.tcinternal 1/1 Running 0 2m57s 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-tiny-kubeproxy-free-master-18-1.k8s.tcinternal 1/1 Running 0 2m55s 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-tiny-kubeproxy-free-master-18-1.k8s.tcinternal 1/1 Running 0 2m55s 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # 这时候查看daemonset可以看到是没有kube-proxy的 $ kubectl get ds -A No resources found 添加worker节点 这时候我们还需要继续添加剩下的两个节点作为worker节点运行负载，直接在剩下的节点上面运行集群初始化成功时输出的命令就可以成功加入集群。\n因为我们前面的kubeadm初始化master节点的时候没有启用kube-proxy，所以在添加节点的时候会出现警告，但是不影响我们继续添加节点。\n$ kubeadm join 10.31.18.1:6443 --token abcdef.0123456789abcdef \\ \u0026gt; --discovery-token-ca-cert-hash sha256:7772f5461bdf4dc399618dc226e2d718d35f14b079e904cd68a5b148eaefcbdd [preflight] Running pre-flight checks [preflight] Reading configuration from the cluster... [preflight] FYI: You can look at this config file with \u0026#39;kubectl -n kube-system get cm kubeadm-config -o yaml\u0026#39; W0512 10:34:36.673112 7960 configset.go:78] Warning: No kubeproxy.config.k8s.io/v1alpha1 config is loaded. Continuing without it: configmaps \u0026#34;kube-proxy\u0026#34; is forbidden: User \u0026#34;system:bootstrap:abcdef\u0026#34; cannot get resource \u0026#34;configmaps\u0026#34; in API group \u0026#34;\u0026#34; in the namespace \u0026#34;kube-system\u0026#34; [kubelet-start] Writing kubelet configuration to file \u0026#34;/var/lib/kubelet/config.yaml\u0026#34; [kubelet-start] Writing kubelet environment file with flags to file \u0026#34;/var/lib/kubelet/kubeadm-flags.env\u0026#34; [kubelet-start] Starting the kubelet [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap... This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run \u0026#39;kubectl get nodes\u0026#39; on the control-plane to see this node join the cluster. 如果不小心没保存初始化成功的输出信息也没有关系，我们可以使用kubectl工具查看或者生成token\n# 查看现有的token列表 $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS abcdef.0123456789abcdef 23h 2022-05-13T02:28:58Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token # 如果token已经失效，那就再创建一个新的token $ kubeadm token create ri4jzg.wkn47l10cjvefep5 $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS abcdef.0123456789abcdef 23h 2022-05-13T02:28:58Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token ri4jzg.wkn47l10cjvefep5 23h 2022-05-13T02:40:15Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token # 如果找不到--discovery-token-ca-cert-hash参数，则可以在master节点上使用openssl工具来获取 $ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2\u0026gt;/dev/null | openssl dgst -sha256 -hex | sed \u0026#39;s/^.* //\u0026#39; 7772f5461bdf4dc399618dc226e2d718d35f14b079e904cd68a5b148eaefcbdd 添加完成之后我们再查看集群的节点可以发现这时候已经多了两个node，但是此时节点的状态还是NotReady，接下来就需要部署CNI了。\n$ kubectl get nodes NAME STATUS ROLES AGE VERSION tiny-kubeproxy-free-master-18-1.k8s.tcinternal NotReady control-plane 11m v1.24.0 tiny-kubeproxy-free-worker-18-11.k8s.tcinternal NotReady \u0026lt;none\u0026gt; 5m57s v1.24.0 tiny-kubeproxy-free-worker-18-12.k8s.tcinternal NotReady \u0026lt;none\u0026gt; 65s v1.24.0 安装CNI 部署helm3 cilium的部署依赖helm3，因此我们在部署cilium之前需要先安装helm3。\nhelm3的部署非常的简单，我们只要去GitHub找到对应系统版本的二进制文件，下载解压后放到系统的执行目录就可以使用了。\n$ wget https://get.helm.sh/helm-v3.8.2-linux-amd64.tar.gz $ tar -zxvf helm-v3.8.2-linux-amd64.tar.gz $ cp -rp linux-amd64/helm /usr/local/bin/ $ helm version version.BuildInfo{Version:\u0026#34;v3.8.2\u0026#34;, GitCommit:\u0026#34;6e3701edea09e5d55a8ca2aae03a68917630e91b\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, GoVersion:\u0026#34;go1.17.5\u0026#34;} 部署cilium 完整的部署指南可以参考官方文档，首先我们添加helm的repo。\n$ helm repo add cilium https://helm.cilium.io/ \u0026#34;cilium\u0026#34; has been added to your repositories $ helm repo list NAME URL cilium https://helm.cilium.io/ 参考官网的文档，这里我们需要指定集群的APIserver的IP和端口\nhelm install cilium ./cilium \\ --namespace kube-system \\ --set kubeProxyReplacement=strict \\ --set k8sServiceHost=REPLACE_WITH_API_SERVER_IP \\ --set k8sServicePort=REPLACE_WITH_API_SERVER_PORT 但是考虑到cilium默认使用的podCIDR为10.0.0.0/8，很可能会和我们集群内的网络冲突，最好的方案就是初始化的时候指定podCIDR，关于初始化的时候podCIDR的设置，可以参考官方的这个文章。\nhelm install cilium cilium/cilium --version 1.11.4 \\ --namespace kube-system \\ --set kubeProxyReplacement=strict \\ --set k8sServiceHost=REPLACE_WITH_API_SERVER_IP \\ --set k8sServicePort=REPLACE_WITH_API_SERVER_PORT \\ --set ipam.operator.clusterPoolIPv4PodCIDRList=\u0026lt;IPv4CIDR\u0026gt; \\ --set ipam.operator.clusterPoolIPv4MaskSize=\u0026lt;IPv4MaskSize\u0026gt; 最后可以得到我们的初始化安装参数\nhelm install cilium cilium/cilium --version 1.11.4 \\ --namespace kube-system \\ --set kubeProxyReplacement=strict \\ --set k8sServiceHost=10.31.18.1 \\ --set k8sServicePort=6443 \\ --set ipam.operator.clusterPoolIPv4PodCIDRList=10.18.64.0/18 \\ --set ipam.operator.clusterPoolIPv4MaskSize=24 然后我们使用指令进行安装\n$ helm install cilium cilium/cilium --version 1.11.4 --namespace kube-system --set kubeProxyReplacement=strict --set k8sServiceHost=10.31.18.1 --set k8sServicePort=6443 --set ipam.operator.clusterPoolIPv4PodCIDRList=10.18.64.0/18 --set ipam.operator.clusterPoolIPv4MaskSize=24 W0512 11:03:06.636996 8753 warnings.go:70] spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[1].matchExpressions[0].key: beta.kubernetes.io/os is deprecated since v1.14; use \u0026#34;kubernetes.io/os\u0026#34; instead W0512 11:03:06.637058 8753 warnings.go:70] spec.template.metadata.annotations[scheduler.alpha.kubernetes.io/critical-pod]: non-functional in v1.16+; use the \u0026#34;priorityClassName\u0026#34; field instead NAME: cilium LAST DEPLOYED: Thu May 12 11:03:04 2022 NAMESPACE: kube-system STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: You have successfully installed Cilium with Hubble. Your release version is 1.11.4. For any further help, visit https://docs.cilium.io/en/v1.11/gettinghelp 此时我们再查看集群的daemonset和deployment状态：\n# 这时候查看集群的daemonset和deployment状态可以看到cilium相关的服务已经正常 $ kubectl get ds -A NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-system cilium 3 3 3 3 3 \u0026lt;none\u0026gt; 4m57s $ kubectl get deploy -A NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE kube-system cilium-operator 2/2 2 2 5m4s kube-system coredns 2/2 2 2 39m 再查看所有的pod，状态都正常，ip也和我们初始化的时候分配的ip段一致，说明初始化的参数设置生效了。\n# 再查看所有的pod，状态都正常，ip按预期进行了分配 $ kubectl get pods -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system cilium-97fn7 1/1 Running 0 7m14s 10.31.18.11 tiny-kubeproxy-free-worker-18-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-k2gxc 1/1 Running 0 7m14s 10.31.18.12 tiny-kubeproxy-free-worker-18-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-operator-86884f4747-c2ps5 1/1 Running 0 7m14s 10.31.18.12 tiny-kubeproxy-free-worker-18-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-operator-86884f4747-zrm4m 1/1 Running 0 7m14s 10.31.18.11 tiny-kubeproxy-free-worker-18-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system cilium-t69js 1/1 Running 0 7m14s 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system coredns-74586cf9b6-shpt4 1/1 Running 0 41m 10.18.65.64 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system coredns-74586cf9b6-wgvgm 1/1 Running 0 41m 10.18.65.237 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-tiny-kubeproxy-free-master-18-1.k8s.tcinternal 1/1 Running 0 41m 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-tiny-kubeproxy-free-master-18-1.k8s.tcinternal 1/1 Running 0 41m 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-tiny-kubeproxy-free-master-18-1.k8s.tcinternal 1/1 Running 0 41m 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-tiny-kubeproxy-free-master-18-1.k8s.tcinternal 1/1 Running 0 41m 10.31.18.1 tiny-kubeproxy-free-master-18-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 这时候我们再进入pod中检查cilium的状态\n# --verbose参数可以查看详细的状态信息 # cilium-97fn7需要替换为任意一个cilium的pod $ kubectl exec -it -n kube-system cilium-97fn7 -- cilium status --verbose Defaulted container \u0026#34;cilium-agent\u0026#34; out of: cilium-agent, mount-cgroup (init), clean-cilium-state (init) KVStore: Ok Disabled Kubernetes: Ok 1.24 (v1.24.0) [linux/amd64] Kubernetes APIs: [\u0026#34;cilium/v2::CiliumClusterwideNetworkPolicy\u0026#34;, \u0026#34;cilium/v2::CiliumEndpoint\u0026#34;, \u0026#34;cilium/v2::CiliumNetworkPolicy\u0026#34;, \u0026#34;cilium/v2::CiliumNode\u0026#34;, \u0026#34;core/v1::Namespace\u0026#34;, \u0026#34;core/v1::Node\u0026#34;, \u0026#34;core/v1::Pods\u0026#34;, \u0026#34;core/v1::Service\u0026#34;, \u0026#34;discovery/v1::EndpointSlice\u0026#34;, \u0026#34;networking.k8s.io/v1::NetworkPolicy\u0026#34;] KubeProxyReplacement: Strict [eth0 10.31.18.11 (Direct Routing)] Host firewall: Disabled Cilium: Ok 1.11.4 (v1.11.4-9d25463) NodeMonitor: Listening for events on 8 CPUs with 64x4096 of shared memory Cilium health daemon: Ok IPAM: IPv4: 2/254 allocated from 10.18.66.0/24, Allocated addresses: 10.18.66.223 (health) 10.18.66.232 (router) BandwidthManager: Disabled Host Routing: Legacy Masquerading: IPTables [IPv4: Enabled, IPv6: Disabled] Clock Source for BPF: ktime Controller Status: 21/21 healthy Name Last success Last error Count Message bpf-map-sync-cilium_ipcache 3s ago 8m59s ago 0 no error cilium-health-ep 41s ago never 0 no error dns-garbage-collector-job 59s ago never 0 no error endpoint-2503-regeneration-recovery never never 0 no error endpoint-82-regeneration-recovery never never 0 no error endpoint-gc 3m59s ago never 0 no error ipcache-inject-labels 8m49s ago 8m53s ago 0 no error k8s-heartbeat 29s ago never 0 no error mark-k8s-node-as-available 8m41s ago never 0 no error metricsmap-bpf-prom-sync 4s ago never 0 no error resolve-identity-2503 3m41s ago never 0 no error resolve-identity-82 3m42s ago never 0 no error sync-endpoints-and-host-ips 42s ago never 0 no error sync-lb-maps-with-k8s-services 8m42s ago never 0 no error sync-node-with-ciliumnode (tiny-kubeproxy-free-worker-18-11.k8s.tcinternal) 8m53s ago 8m55s ago 0 no error sync-policymap-2503 33s ago never 0 no error sync-policymap-82 30s ago never 0 no error sync-to-k8s-ciliumendpoint (2503) 11s ago never 0 no error sync-to-k8s-ciliumendpoint (82) 2s ago never 0 no error template-dir-watcher never never 0 no error update-k8s-node-annotations 8m53s ago never 0 no error Proxy Status: OK, ip 10.18.66.232, 0 redirects active on ports 10000-20000 Hubble: Ok Current/Max Flows: 422/4095 (10.31%), Flows/s: 0.75 Metrics: Disabled KubeProxyReplacement Details: Status: Strict Socket LB Protocols: TCP, UDP Devices: eth0 10.31.18.11 (Direct Routing) Mode: SNAT Backend Selection: Random Session Affinity: Enabled Graceful Termination: Enabled XDP Acceleration: Disabled Services: - ClusterIP: Enabled - NodePort: Enabled (Range: 30000-32767) - LoadBalancer: Enabled - externalIPs: Enabled - HostPort: Enabled BPF Maps: dynamic sizing: on (ratio: 0.002500) Name Size Non-TCP connection tracking 65536 TCP connection tracking 131072 Endpoint policy 65535 Events 8 IP cache 512000 IP masquerading agent 16384 IPv4 fragmentation 8192 IPv4 service 65536 IPv6 service 65536 IPv4 service backend 65536 IPv6 service backend 65536 IPv4 service reverse NAT 65536 IPv6 service reverse NAT 65536 Metrics 1024 NAT 131072 Neighbor table 131072 Global policy 16384 Per endpoint policy 65536 Session affinity 65536 Signal 8 Sockmap 65535 Sock reverse NAT 65536 Tunnel 65536 Encryption: Disabled Cluster health: 3/3 reachable (2022-05-12T03:12:22Z) Name IP Node Endpoints tiny-kubeproxy-free-worker-18-11.k8s.tcinternal (localhost) 10.31.18.11 reachable reachable tiny-kubeproxy-free-master-18-1.k8s.tcinternal 10.31.18.1 reachable reachable tiny-kubeproxy-free-worker-18-12.k8s.tcinternal 10.31.18.12 reachable reachable 其实到这里cilium的部署就可以说是ok了的，整个集群的cni都处于正常状态，其余的工作负载也都能够正常运行了。\n部署hubble cilium还有一大特点就是其可观测性比其他的cni要优秀很多，想要体验到cilium的可观测性，我们就需要在k8s集群中安装hubble。同时hubble提供了ui界面来更好的实现集群内网络的可观测性，这里我们也一并把hubble-ui安装上。\nhelm3安装hubble 我们继续接着上面的helm3来安装hubble，因为我们已经安装了cilium，因此这里需要使用upgrade来进行升级安装，并且使用--reuse-values来复用之前的安装参数\nhelm upgrade cilium cilium/cilium --version 1.11.4 \\ --namespace kube-system \\ --reuse-values \\ --set hubble.relay.enabled=true \\ --set hubble.ui.enabled=true 然后我们直接进行安装\n$ helm upgrade cilium cilium/cilium --version 1.11.4 \\ \u0026gt; --namespace kube-system \\ \u0026gt; --reuse-values \\ \u0026gt; --set hubble.relay.enabled=true \\ \u0026gt; --set hubble.ui.enabled=true Release \u0026#34;cilium\u0026#34; has been upgraded. Happy Helming! NAME: cilium LAST DEPLOYED: Thu May 12 11:34:43 2022 NAMESPACE: kube-system STATUS: deployed REVISION: 2 TEST SUITE: None NOTES: You have successfully installed Cilium with Hubble Relay and Hubble UI. Your release version is 1.11.4. For any further help, visit https://docs.cilium.io/en/v1.11/gettinghelp 随后我们查看相关的集群状态，可以看到相对应的pod、deploy和svc都工作正常\n$ kubectl get pod -A | grep hubble kube-system hubble-relay-cdf4c8cdd-wgdqg 1/1 Running 0 66s kube-system hubble-ui-86856f9f6c-vw8lt 3/3 Running 0 66s $ kubectl get deploy -A | grep hubble kube-system hubble-relay 1/1 1 1 74s kube-system hubble-ui 1/1 1 1 74s $ kubectl get svc -A | grep hubble kube-system hubble-relay ClusterIP 10.18.58.2 \u0026lt;none\u0026gt; 80/TCP 82s kube-system hubble-ui ClusterIP 10.18.22.156 \u0026lt;none\u0026gt; 80/TCP 82s cilium-cli安装hubble 使用cilium-cli功能来安装hubble也非常简单：\n# 首先安装cilium-cli工具 # cilium的cli工具是一个二进制的可执行文件 $ curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz{,.sha256sum} $ sha256sum --check cilium-linux-amd64.tar.gz.sha256sum cilium-linux-amd64.tar.gz: OK $ sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin cilium # 然后直接启用hubble $ cilium hubble enable # 再启用hubble-ui $ cilium hubble enable --ui # 接着查看cilium状态 $ cilium status /¯¯\\ /¯¯\\__/¯¯\\ Cilium: OK \\__/¯¯\\__/ Operator: OK /¯¯\\__/¯¯\\ Hubble: OK \\__/¯¯\\__/ ClusterMesh: disabled \\__/ Deployment cilium-operator Desired: 2, Ready: 2/2, Available: 2/2 Deployment hubble-relay Desired: 1, Ready: 1/1, Available: 1/1 Deployment hubble-ui Desired: 1, Ready: 1/1, Available: 1/1 DaemonSet cilium Desired: 3, Ready: 3/3, Available: 3/3 Containers: cilium Running: 3 cilium-operator Running: 2 hubble-relay Running: 1 hubble-ui Running: 1 Cluster Pods: 4/4 managed by Cilium Image versions hubble-relay quay.io/cilium/hubble-relay:v1.11.4@sha256:460d50bd0c6bcdfa3c62b0488541c102a4079f5def07d2649ff67bc24fd0dd3f: 1 hubble-ui quay.io/cilium/hubble-ui:v0.8.5@sha256:4eaca1ec1741043cfba6066a165b3bf251590cf4ac66371c4f63fbed2224ebb4: 1 hubble-ui quay.io/cilium/hubble-ui-backend:v0.8.5@sha256:2bce50cf6c32719d072706f7ceccad654bfa907b2745a496da99610776fe31ed: 1 hubble-ui docker.io/envoyproxy/envoy:v1.18.4@sha256:e5c2bb2870d0e59ce917a5100311813b4ede96ce4eb0c6bfa879e3fbe3e83935: 1 cilium quay.io/cilium/cilium:v1.11.4@sha256:d9d4c7759175db31aa32eaa68274bb9355d468fbc87e23123c80052e3ed63116: 3 cilium-operator quay.io/cilium/operator-generic:v1.11.4@sha256:bf75ad0dc47691a3a519b8ab148ed3a792ffa2f1e309e6efa955f30a40e95adc: 2 安装hubble客户端 和cilium一样，hubble也提供了一个客户端来让我们操作，不同的是我\n# 首先我们需要安装hubble的客户端，安装原理和过程与安装cilium几乎一致 $ export HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt) $ curl -L --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-amd64.tar.gz{,.sha256sum} $ sha256sum --check hubble-linux-amd64.tar.gz.sha256sum $ sudo tar xzvfC hubble-linux-amd64.tar.gz /usr/local/bin $ rm hubble-linux-amd64.tar.gz{,.sha256sum} 然后我们需要暴露hubble api服务的端口，直接使用kubectl的port-forward功能把hubble-relay这个服务的80端口暴露到4245端口上\n# 仅暴露在IPV4网络中 $ kubectl port-forward -n kube-system svc/hubble-relay --address 0.0.0.0 4245:80 \u0026amp; # 同时暴露在IPV6和IPV4网络中 $ kubectl port-forward -n kube-system svc/hubble-relay --address 0.0.0.0 --address :: 4245:80 \u0026amp; 如果使用cilium-cli工具安装的hubble也可以使用cilium暴露api端口，需要注意的是该命令默认会暴露到IPV6和IPV4网络中，如果宿主机节点不支持ipv6网络会报错\n$ cilium hubble port-forward\u0026amp; api端口暴露完成之后我们就可以测试一下hubble客户端的工作状态是否正常\n$ hubble status Handling connection for 4245 Healthcheck (via localhost:4245): Ok Current/Max Flows: 10,903/12,285 (88.75%) Flows/s: 5.98 Connected Nodes: 3/3 暴露hubble-ui 官方介绍里面是使用cilium工具直接暴露hubble-ui的访问端口到宿主机上面的12000端口\n# 将hubble-ui这个服务的80端口暴露到宿主机上面的12000端口上面 $ cilium hubble ui\u0026amp; [2] 5809 ℹ️ Opening \u0026#34;http://localhost:12000\u0026#34; in your browser... 实际上执行的操作等同于下面这个命令\n# 同时暴露在IPV6和IPV4网络中 # kubectl port-forward -n kube-system svc/hubble-ui --address 0.0.0.0 --address :: 12000:80 # 仅暴露在IPV4网络中 # kubectl port-forward -n kube-system svc/hubble-ui --address 0.0.0.0 12000:80 这里我们使用nodeport的方式来暴露hubble-ui，首先我们查看原来的hubble-ui这个svc的配置\n$ kubectl get svc -n kube-system hubble-ui -o yaml ...此处略去一堆输出... - name: http port: 80 protocol: TCP targetPort: 8081 selector: k8s-app: hubble-ui sessionAffinity: None type: ClusterIP ...此处略去一堆输出... 我们把默认的ClusterIP修改为NodePort，并且指定端口为nodePort: 30081\n$ kubectl get svc -n kube-system hubble-ui -o yaml ...此处略去一堆输出... ports: - name: http nodePort: 30081 port: 80 protocol: TCP targetPort: 8081 selector: k8s-app: hubble-ui sessionAffinity: None type: NodePort ...此处略去一堆输出... 修改前后对比查看状态\n# 修改前，使用ClusterIP $ kubectl get svc -A | grep hubble-ui kube-system hubble-ui ClusterIP 10.18.22.156 \u0026lt;none\u0026gt; 80/TCP 82s # 修改后，使用NodePort $ kubectl get svc -A | grep hubble-ui kube-system hubble-ui NodePort 10.18.22.156 \u0026lt;none\u0026gt; 80:30081/TCP 47m 这时候我们在浏览器中访问http://10.31.18.1:30081就可以看到hubble的ui界面了\n部署测试用例 集群部署完成之后我们在k8s集群中部署一个nginx测试一下是否能够正常工作。首先我们创建一个名为nginx-quic的命名空间（namespace），然后在这个命名空间内创建一个名为nginx-quic-deployment的deployment用来部署pod，最后再创建一个service用来暴露服务，这里我们先使用nodeport的方式暴露端口方便测试。\n$ cat nginx-quic.yaml apiVersion: v1 kind: Namespace metadata: name: nginx-quic --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-quic-deployment namespace: nginx-quic spec: selector: matchLabels: app: nginx-quic replicas: 4 template: metadata: labels: app: nginx-quic spec: containers: - name: nginx-quic image: tinychen777/nginx-quic:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-quic-service namespace: nginx-quic spec: externalTrafficPolicy: Cluster selector: app: nginx-quic ports: - protocol: TCP port: 8080 # match for service access port targetPort: 80 # match for pod access port nodePort: 30088 # match for external access port type: NodePort 部署完成后我们直接查看状态\n# 直接部署 $ kubectl apply -f nginx-quic.yaml namespace/nginx-quic created deployment.apps/nginx-quic-deployment created service/nginx-quic-service created # 查看deployment的运行状态 $ kubectl get deployment -o wide -n nginx-quic NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR nginx-quic-deployment 4/4 4 4 2m49s nginx-quic tinychen777/nginx-quic:latest app=nginx-quic # 查看service的运行状态 $ kubectl get service -o wide -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR nginx-quic-service NodePort 10.18.54.119 \u0026lt;none\u0026gt; 8080:30088/TCP 3m app=nginx-quic # 查看pod的运行状态 $ kubectl get pods -o wide -n nginx-quic NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-quic-deployment-5d9b4fbb47-4gc6g 1/1 Running 0 3m10s 10.18.66.66 tiny-kubeproxy-free-worker-18-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-5d9b4fbb47-4j5p6 1/1 Running 0 3m10s 10.18.64.254 tiny-kubeproxy-free-worker-18-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-5d9b4fbb47-8gg9j 1/1 Running 0 3m10s 10.18.66.231 tiny-kubeproxy-free-worker-18-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-5d9b4fbb47-9bv2t 1/1 Running 0 3m10s 10.18.64.5 tiny-kubeproxy-free-worker-18-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # 查看IPVS规则 # 由于使用了cilium的kube-proxy-free方案，这时候Linux网络中是没有ipvs规则的 $ ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn # 查看cilium里面的状态 $ kubectl exec -it -n kube-system cilium-97fn7 -- cilium service list Defaulted container \u0026#34;cilium-agent\u0026#34; out of: cilium-agent, mount-cgroup (init), clean-cilium-state (init) ID Frontend Service Type Backend 1 10.18.0.1:443 ClusterIP 1 =\u0026gt; 10.31.18.1:6443 2 10.18.0.10:9153 ClusterIP 1 =\u0026gt; 10.18.65.237:9153 2 =\u0026gt; 10.18.65.64:9153 3 10.18.0.10:53 ClusterIP 1 =\u0026gt; 10.18.65.237:53 2 =\u0026gt; 10.18.65.64:53 4 10.18.22.156:80 ClusterIP 1 =\u0026gt; 10.18.64.53:8081 5 10.18.58.2:80 ClusterIP 1 =\u0026gt; 10.18.66.189:4245 6 10.31.18.11:30081 NodePort 1 =\u0026gt; 10.18.64.53:8081 7 0.0.0.0:30081 NodePort 1 =\u0026gt; 10.18.64.53:8081 8 10.18.54.119:8080 ClusterIP 1 =\u0026gt; 10.18.64.254:80 2 =\u0026gt; 10.18.66.66:80 3 =\u0026gt; 10.18.64.5:80 4 =\u0026gt; 10.18.66.231:80 9 10.31.18.11:30088 NodePort 1 =\u0026gt; 10.18.64.254:80 2 =\u0026gt; 10.18.66.66:80 3 =\u0026gt; 10.18.64.5:80 4 =\u0026gt; 10.18.66.231:80 10 0.0.0.0:30088 NodePort 1 =\u0026gt; 10.18.64.254:80 2 =\u0026gt; 10.18.66.66:80 3 =\u0026gt; 10.18.64.5:80 4 =\u0026gt; 10.18.66.231:80 最后我们进行测试，这个nginx-quic的镜像默认情况下会返回在nginx容器中获得的用户请求的IP和端口\n# 首先我们在集群内进行测试 # 直接访问pod $ curl 10.18.64.254:80 10.18.65.204:60032 # 直接访问service的ClusterIP，这时请求会被转发到pod中 $ curl 10.18.54.119:8080 10.18.65.204:38774 # 直接访问nodeport，这时请求会被转发到pod中，不会经过ClusterIP # 此时实际返回的IP要取决于被转发到的后端pod是否在当前的k8s节点上 $ curl 10.31.18.1:30088 10.18.65.204:51254 $ curl 10.31.18.11:30088 10.18.65.204:38784 $ curl 10.31.18.12:30088 10.18.65.204:60048 # 接着我们在集群外进行测试 # 直接访问三个节点的nodeport，这时请求会被转发到pod中，不会经过ClusterIP # 此时实际返回的IP要取决于被转发到的后端pod是否在当前的k8s节点上 $ curl 10.31.18.1:30088 10.18.65.204:43586 $ curl 10.31.18.11:30088 10.18.66.232:63415 $ curl 10.31.18.11:30088 10.31.100.100:12192 $ curl 10.31.18.12:30088 10.18.64.152:40782 $ curl 10.31.18.12:30088 10.31.100.100:12178 ","permalink":"http://localhost:9011/20220512-k8s-05-deploy-k8s-without-kubeproxy/","summary":"\u003cp\u003e本文主要在centos7系统上基于\u003ccode\u003econtainerd\u003c/code\u003e和\u003ccode\u003estable\u003c/code\u003e版本（\u003ccode\u003e1.11.4\u003c/code\u003e）的\u003ccode\u003ecilium\u003c/code\u003e组件部署\u003ccode\u003ev1.24.0\u003c/code\u003e版本的k8s原生集群，由于集群主要用于自己平时学习和测试使用，加上资源有限，暂不涉及高可用部署。\u003c/p\u003e\n\u003cp\u003e此外，由于\u003ccode\u003ecilium\u003c/code\u003e已经实现了对\u003ccode\u003ekube-proxy\u003c/code\u003e的一整套替代方案，这里部署k8s集群的时候会使用cilium的\u003ccode\u003ekubeproxy-free\u003c/code\u003e方案。\u003c/p\u003e\n\u003cp\u003e此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列05-使用containerd和cilium部署kubeproxy-free的k8s集群"},{"content":"本文主要在centos7系统上基于docker和cilium组件部署v1.23.6版本的k8s原生集群，由于集群主要用于自己平时学习和测试使用，加上资源有限，暂不涉及高可用部署。\n此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n准备工作 cilium-集群节点信息 机器均为8C8G的虚拟机，硬盘为100G。\nIP Hostname 10.31.188.1 tiny-cilium-master-188-1.k8s.tcinternal 10.31.188.11 tiny-cilium-worker-188-11.k8s.tcinternal 10.31.188.12 tiny-cilium-worker-188-12.k8s.tcinternal 10.188.0.0/18 serviceSubnet 检查mac和product_uuid 同一个k8s集群内的所有节点需要确保mac地址和product_uuid均唯一，开始集群初始化之前需要检查相关信息\n# 检查mac地址 ip link ifconfig -a # 检查product_uuid sudo cat /sys/class/dmi/id/product_uuid 配置ssh免密登录（可选） 如果k8s集群的节点有多个网卡，确保每个节点能通过正确的网卡互联访问\n# 在root用户下面生成一个公用的key，并配置可以使用该key免密登录 su root ssh-keygen cd /root/.ssh/ cat id_rsa.pub \u0026gt;\u0026gt; authorized_keys chmod 600 authorized_keys cat \u0026gt;\u0026gt; ~/.ssh/config \u0026lt;\u0026lt;EOF Host tiny-cilium-master-188-1.k8s.tcinternal HostName 10.31.188.1 User root Port 22 IdentityFile ~/.ssh/id_rsa Host tiny-cilium-worker-188-11.k8s.tcinternal HostName 10.31.188.11 User root Port 22 IdentityFile ~/.ssh/id_rsa Host tiny-cilium-worker-188-12.k8s.tcinternal HostName 10.31.188.12 User root Port 22 IdentityFile ~/.ssh/id_rsa EOF 修改hosts文件 cat \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt;EOF 10.31.188.1 tiny-cilium-master-188-1 tiny-cilium-master-188-1.k8s.tcinternal 10.31.188.11 tiny-cilium-worker-188-11 tiny-cilium-worker-188-11.k8s.tcinternal 10.31.188.12 tiny-cilium-worker-188-12 tiny-cilium-worker-188-12.k8s.tcinternal EOF 关闭swap内存 # 使用命令直接关闭swap内存 swapoff -a # 修改fstab文件禁止开机自动挂载swap分区 sed -i \u0026#39;/swap / s/^\\(.*\\)$/#\\1/g\u0026#39; /etc/fstab 配置时间同步 这里可以根据自己的习惯选择ntp或者是chrony同步均可，同步的时间源服务器可以选择阿里云的ntp1.aliyun.com或者是国家时间中心的ntp.ntsc.ac.cn。\n使用ntp同步 # 使用yum安装ntpdate工具 yum install ntpdate -y # 使用国家时间中心的源同步时间 ntpdate ntp.ntsc.ac.cn # 最后查看一下时间 hwclock 使用chrony同步 # 使用yum安装chrony yum install chrony -y # 设置开机启动并开启chony并查看运行状态 systemctl enable chronyd.service systemctl start chronyd.service systemctl status chronyd.service # 当然也可以自定义时间服务器 vim /etc/chrony.conf # 修改前 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server 0.centos.pool.ntp.org iburst server 1.centos.pool.ntp.org iburst server 2.centos.pool.ntp.org iburst server 3.centos.pool.ntp.org iburst # 修改后 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server ntp.ntsc.ac.cn iburst # 重启服务使配置文件生效 systemctl restart chronyd.service # 查看chrony的ntp服务器状态 chronyc sourcestats -v chronyc sources -v 关闭selinux # 使用命令直接关闭 setenforce 0 # 也可以直接修改/etc/selinux/config文件 sed -i \u0026#39;s/^SELINUX=enforcing$/SELINUX=disabled/\u0026#39; /etc/selinux/config 配置防火墙 k8s集群之间通信和服务暴露需要使用较多端口，为了方便，直接禁用防火墙\n# centos7使用systemctl禁用默认的firewalld服务 systemctl disable firewalld.service 配置netfilter参数 这里主要是需要配置内核加载br_netfilter和iptables放行ipv6和ipv4的流量，确保集群内的容器能够正常通信。\ncat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter EOF cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sudo sysctl --system 关闭IPV6（不建议） 和之前部署其他的CNI不一样，cilium很多服务监听默认情况下都是双栈的（使用cilium-cli操作的时候），因此建议开启系统的IPV6网络支持（即使没有可用的IPV6路由也可以）\n当然没有ipv6网络也是可以的，只是在使用cilium-cli的一些开启port-forward命令时会报错而已。\n# 直接在内核中添加ipv6禁用参数 grubby --update-kernel=ALL --args=ipv6.disable=1 配置IPVS（建议） IPVS是专门设计用来应对负载均衡场景的组件，kube-proxy 中的 IPVS 实现通过减少对 iptables 的使用来增加可扩展性。在 iptables 输入链中不使用 PREROUTING，而是创建一个假的接口，叫做 kube-ipvs0，当k8s集群中的负载均衡配置变多的时候，IPVS能实现比iptables更高效的转发性能。\n因为cilium需要升级系统内核，因此这里的内核版本高于4.19\n注意在4.19之后的内核版本中使用nf_conntrack模块来替换了原有的nf_conntrack_ipv4模块\n(Notes: use nf_conntrack instead of nf_conntrack_ipv4 for Linux kernel 4.19 and later)\n# 在使用ipvs模式之前确保安装了ipset和ipvsadm sudo yum install ipset ipvsadm -y # 手动加载ipvs相关模块 modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack # 配置开机自动加载ipvs相关模块 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/ipvs.conf ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack EOF sudo sysctl --system # 最好重启一遍系统确定是否生效 $ lsmod | grep -e ip_vs -e nf_conntrack nf_conntrack_netlink 49152 0 nfnetlink 20480 2 nf_conntrack_netlink ip_vs_sh 16384 0 ip_vs_wrr 16384 0 ip_vs_rr 16384 0 ip_vs 159744 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr nf_conntrack 159744 5 xt_conntrack,nf_nat,nf_conntrack_netlink,xt_MASQUERADE,ip_vs nf_defrag_ipv4 16384 1 nf_conntrack nf_defrag_ipv6 24576 2 nf_conntrack,ip_vs libcrc32c 16384 4 nf_conntrack,nf_nat,xfs,ip_vs $ cut -f1 -d \u0026#34; \u0026#34; /proc/modules | grep -e ip_vs -e nf_conntrack nf_conntrack_netlink ip_vs_sh ip_vs_wrr ip_vs_rr ip_vs nf_conntrack 配置Linux内核（cilium必选） cilium和其他的cni组件最大的不同在于其底层使用了ebpf技术，而该技术对于Linux的系统内核版本有较高的要求，完成的要求可以查看官网的详细链接，这里我们着重看内核版本、内核参数这两个部分。\nLinux内核版本 默认情况下我们可以参考cilium官方给出的一个系统要求总结。因为我们是在k8s集群中部署（使用容器），因此只需要关注Linux内核版本和etcd版本即可。根据前面部署的经验我们可以知道1.23.6版本的k8s默认使用的etcd版本是3.5.+，因此重点就来到了Linux内核版本这里。\nRequirement Minimum Version In cilium container Linux kernel \u0026gt;= 4.9.17 no Key-Value store (etcd) \u0026gt;= 3.1.0 no clang+LLVM \u0026gt;= 10.0 yes iproute2 \u0026gt;= 5.9.0 yes This requirement is only needed if you run cilium-agent natively. If you are using the Cilium container image cilium/cilium, clang+LLVM is included in the container image.\niproute2 is only needed if you run cilium-agent directly on the host machine. iproute2 is included in the cilium/cilium container image.\n毫无疑问CentOS7内置的默认内核版本3.10.x版本的内核是无法满足需求的，但是在升级内核之前，我们再看看其他的一些要求。\ncilium官方还给出了一份列表描述了各项高级功能对内核版本的要求：\nCilium Feature Minimum Kernel Version IPv4 fragment handling \u0026gt;= 4.10 Restrictions on unique prefix lengths for CIDR policy rules \u0026gt;= 4.11 IPsec Transparent Encryption in tunneling mode \u0026gt;= 4.19 WireGuard Transparent Encryption \u0026gt;= 5.6 Host-Reachable Services \u0026gt;= 4.19.57, \u0026gt;= 5.1.16, \u0026gt;= 5.2 Kubernetes Without kube-proxy \u0026gt;= 4.19.57, \u0026gt;= 5.1.16, \u0026gt;= 5.2 Bandwidth Manager \u0026gt;= 5.1 Local Redirect Policy (beta) \u0026gt;= 4.19.57, \u0026gt;= 5.1.16, \u0026gt;= 5.2 Full support for Session Affinity \u0026gt;= 5.7 BPF-based proxy redirection \u0026gt;= 5.7 BPF-based host routing \u0026gt;= 5.10 Socket-level LB bypass in pod netns \u0026gt;= 5.7 Egress Gateway (beta) \u0026gt;= 5.2 VXLAN Tunnel Endpoint (VTEP) Integration \u0026gt;= 5.2 可以看到如果需要满足上面所有需求的话，需要内核版本高于5.10，本着学习测试研究作死的精神，反正都升级了，干脆就升级到新一些的版本吧。这里我们可以直接使用elrepo源来升级内核到较新的内核版本。\n# 查看elrepo源中支持的内核版本 $ yum --disablerepo=\u0026#34;*\u0026#34; --enablerepo=\u0026#34;elrepo-kernel\u0026#34; list available Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile Available Packages elrepo-release.noarch 7.0-5.el7.elrepo elrepo-kernel kernel-lt.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-devel.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-doc.noarch 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-headers.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-tools.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-tools-libs.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-lt-tools-libs-devel.x86_64 5.4.192-1.el7.elrepo elrepo-kernel kernel-ml.x86_64 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-devel.x86_64 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-doc.noarch 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-headers.x86_64 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-tools.x86_64 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-tools-libs.x86_64 5.17.6-1.el7.elrepo elrepo-kernel kernel-ml-tools-libs-devel.x86_64 5.17.6-1.el7.elrepo elrepo-kernel perf.x86_64 5.17.6-1.el7.elrepo elrepo-kernel python-perf.x86_64 5.17.6-1.el7.elrepo elrepo-kernel # 看起来ml版本的内核比较满足我们的需求,直接使用yum进行安装 sudo yum --enablerepo=elrepo-kernel install kernel-ml -y # 使用grubby工具查看系统中已经安装的内核版本信息 sudo grubby --info=ALL # 设置新安装的5.17.6版本内核为默认内核版本，此处的index=0要和上面查看的内核版本信息一致 sudo grubby --set-default-index=0 # 查看默认内核是否修改成功 sudo grubby --default-kernel # 重启系统切换到新内核 init 6 # 重启后检查内核版本是否为新的5.17.6 uname -a Linux内核参数 首先我们查看自己当前内核版本的参数，基本上可以分为y、n、m三个选项\ny：yes，Build directly into the kernel. 表示该功能被编译进内核中，默认启用 n：no，Leave entirely out of the kernel. 表示该功能未被编译进内核中，不启用 m：module，Build as a module, to be loaded if needed. 表示该功能被编译为模块，按需启用 # 查看当前使用的内核版本的编译参数 cat /boot/config-$(uname -r) cilium官方对各项功能所需要开启的内核参数列举如下：\nIn order for the eBPF feature to be enabled properly, the following kernel configuration options must be enabled. This is typically the case with distribution kernels. When an option can be built as a module or statically linked, either choice is valid.\n为了正确启用 eBPF 功能，必须启用以下内核配置选项。这通常因内核版本情况而异。任何一个选项都可以构建为模块或静态链接，两个选择都是有效的。\n我们暂时只看最基本的Base Requirements\nCONFIG_BPF=y CONFIG_BPF_SYSCALL=y CONFIG_NET_CLS_BPF=y CONFIG_BPF_JIT=y CONFIG_NET_CLS_ACT=y CONFIG_NET_SCH_INGRESS=y CONFIG_CRYPTO_SHA1=y CONFIG_CRYPTO_USER_API_HASH=y CONFIG_CGROUPS=y CONFIG_CGROUP_BPF=y 对比我们使用的5.17.6-1.el7.elrepo.x86_64内核可以发现有两个模块是为m\n$ egrep \u0026#34;^CONFIG_BPF=|^CONFIG_BPF_SYSCALL=|^CONFIG_NET_CLS_BPF=|^CONFIG_BPF_JIT=|^CONFIG_NET_CLS_ACT=|^CONFIG_NET_SCH_INGRESS=|^CONFIG_CRYPTO_SHA1=|^CONFIG_CRYPTO_USER_API_HASH=|^CONFIG_CGROUPS=|^CONFIG_CGROUP_BPF=\u0026#34; /boot/config-5.17.6-1.el7.elrepo.x86_64 CONFIG_BPF=y CONFIG_BPF_SYSCALL=y CONFIG_BPF_JIT=y CONFIG_CGROUPS=y CONFIG_CGROUP_BPF=y CONFIG_NET_SCH_INGRESS=m CONFIG_NET_CLS_BPF=m CONFIG_NET_CLS_ACT=y CONFIG_CRYPTO_SHA1=y CONFIG_CRYPTO_USER_API_HASH=y 缺少的这两个模块我们可以在/usr/lib/modules/$(uname -r)目录下面找到它们：\n$ realpath ./kernel/net/sched/sch_ingress.ko /usr/lib/modules/5.17.6-1.el7.elrepo.x86_64/kernel/net/sched/sch_ingress.ko $ realpath ./kernel/net/sched/cls_bpf.ko /usr/lib/modules/5.17.6-1.el7.elrepo.x86_64/kernel/net/sched/cls_bpf.ko 确认相关内核模块存在我们直接加载内核即可：\n# 直接使用modprobe命令加载 $ modprobe cls_bpf $ modprobe sch_ingress $ lsmod | egrep \u0026#34;cls_bpf|sch_ingress\u0026#34; sch_ingress 16384 0 cls_bpf 24576 0 # 配置开机自动加载cilium所需相关模块 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/cilium-base-requirements.conf cls_bpf sch_ingress EOF 其他cilium高级功能所需要的内核功能也类似，这里不做赘述。\n安装container runtime 安装docker 详细的官方文档可以参考这里，由于在刚发布的1.24版本中移除了docker-shim，因此安装的版本≥1.24的时候需要注意容器运行时的选择。这里我们安装的版本低于1.24，因此我们继续使用docker。\ndocker的具体安装可以参考我之前写的这篇文章，这里不做赘述。\n# 安装必要的依赖组件并且导入docker官方提供的yum源 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 我们直接安装最新版本的docker yum install docker-ce docker-ce-cli containerd.io 配置cgroup drivers CentOS7使用的是systemd来初始化系统并管理进程，初始化进程会生成并使用一个 root 控制组 (cgroup), 并充当 cgroup 管理器。 Systemd 与 cgroup 集成紧密，并将为每个 systemd 单元分配一个 cgroup。 我们也可以配置容器运行时和 kubelet 使用 cgroupfs。 连同 systemd 一起使用 cgroupfs 意味着将有两个不同的 cgroup 管理器。而当一个系统中同时存在cgroupfs和systemd两者时，容易变得不稳定，因此最好更改设置，令容器运行时和 kubelet 使用 systemd 作为 cgroup 驱动，以此使系统更为稳定。 对于 Docker, 需要设置 native.cgroupdriver=systemd 参数。\n参考官方的说明文档：\nhttps://kubernetes.io/docs/setup/production-environment/container-runtimes/#cgroup-drivers\n参考配置说明文档\nhttps://kubernetes.io/zh/docs/setup/production-environment/container-runtimes/#docker\nsudo mkdir /etc/docker cat \u0026lt;\u0026lt;EOF | sudo tee /etc/docker/daemon.json { \u0026#34;exec-opts\u0026#34;: [\u0026#34;native.cgroupdriver=systemd\u0026#34;], \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34; }, \u0026#34;storage-driver\u0026#34;: \u0026#34;overlay2\u0026#34; } EOF sudo systemctl enable docker sudo systemctl daemon-reload sudo systemctl restart docker # 最后检查一下Cgroup Driver是否为systemd $ docker info | grep systemd Cgroup Driver: systemd 关于kubelet的cgroup driver k8s官方有详细的文档介绍了如何设置kubelet的cgroup driver，需要特别注意的是，在1.22版本开始，如果没有手动设置kubelet的cgroup driver，那么默认会设置为systemd\nNote: In v1.22, if the user is not setting the cgroupDriver field under KubeletConfiguration, kubeadm will default it to systemd.\n一个比较简单的指定kubelet的cgroup driver的方法就是在kubeadm-config.yaml加入cgroupDriver字段\n# kubeadm-config.yaml kind: ClusterConfiguration apiVersion: kubeadm.k8s.io/v1beta3 kubernetesVersion: v1.21.0 --- kind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 cgroupDriver: systemd 我们可以直接查看configmaps来查看初始化之后集群的kubeadm-config配置。\n$ kubectl describe configmaps kubeadm-config -n kube-system Name: kubeadm-config Namespace: kube-system Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; Data ==== ClusterConfiguration: ---- apiServer: extraArgs: authorization-mode: Node,RBAC timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: v1.23.6 networking: dnsDomain: cali-cluster.tclocal serviceSubnet: 10.88.0.0/18 scheduler: {} BinaryData ==== Events: \u0026lt;none\u0026gt; 当然因为我们需要安装的版本高于1.22.0并且使用的就是systemd，因此可以不用再重复配置。\n安装kube三件套 对应的官方文档可以参考这里\nhttps://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl\nkube三件套就是kubeadm、kubelet 和 kubectl，三者的具体功能和作用如下：\nkubeadm：用来初始化集群的指令。 kubelet：在集群中的每个节点上用来启动 Pod 和容器等。 kubectl：用来与集群通信的命令行工具。 需要注意的是：\nkubeadm不会帮助我们管理kubelet和kubectl，其他两者也是一样的，也就是说这三者是相互独立的，并不存在谁管理谁的情况； kubelet的版本必须小于等于API-server的版本，否则容易出现兼容性的问题； kubectl并不是集群中的每个节点都需要安装，也并不是一定要安装在集群中的节点，可以单独安装在自己本地的机器环境上面，然后配合kubeconfig文件即可使用kubectl命令来远程管理对应的k8s集群； CentOS7的安装比较简单，我们直接使用官方提供的yum源即可。需要注意的是这里需要设置selinux的状态，但是前面我们已经关闭了selinux，因此这里略过这步。\n# 直接导入谷歌官方的yum源 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\\$basearch enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg exclude=kubelet kubeadm kubectl EOF # 当然如果连不上谷歌的源，可以考虑使用国内的阿里镜像源 cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF # 接下来直接安装三件套即可 sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes # 如果网络环境不好出现gpgcheck验证失败导致无法正常读取yum源，可以考虑关闭该yum源的repo_gpgcheck sed -i \u0026#39;s/repo_gpgcheck=1/repo_gpgcheck=0/g\u0026#39; /etc/yum.repos.d/kubernetes.repo # 或者在安装的时候禁用gpgcheck sudo yum install -y kubelet kubeadm kubectl --nogpgcheck --disableexcludes=kubernetes # 如果想要安装特定版本，可以使用这个命令查看相关版本的信息 sudo yum list --nogpgcheck kubelet kubeadm kubectl --showduplicates --disableexcludes=kubernetes # 这里我们为了保留使用docker-shim，因此我们按照1.24.0版本的前一个版本1.23.6 sudo yum install -y kubelet-1.23.6-0 kubeadm-1.23.6-0 kubectl-1.23.6-0 --nogpgcheck --disableexcludes=kubernetes # 安装完成后配置开机自启kubelet sudo systemctl enable --now kubelet 初始化集群 编写配置文件 在集群中所有节点都执行完上面的三点操作之后，我们就可以开始创建k8s集群了。因为我们这次不涉及高可用部署，因此初始化的时候直接在我们的目标master节点上面操作即可。\n# 我们先使用kubeadm命令查看一下主要的几个镜像版本 # 因为我们此前指定安装了旧的1.23.6版本，这里的apiserver镜像版本也会随之回滚 $ kubeadm config images list I0509 12:06:03.036544 3593 version.go:255] remote version is much newer: v1.24.0; falling back to: stable-1.23 k8s.gcr.io/kube-apiserver:v1.23.6 k8s.gcr.io/kube-controller-manager:v1.23.6 k8s.gcr.io/kube-scheduler:v1.23.6 k8s.gcr.io/kube-proxy:v1.23.6 k8s.gcr.io/pause:3.6 k8s.gcr.io/etcd:3.5.1-0 k8s.gcr.io/coredns/coredns:v1.8.6 # 为了方便编辑和管理，我们还是把初始化参数导出成配置文件 $ kubeadm config print init-defaults \u0026gt; kubeadm-cilium.conf 考虑到大多数情况下国内的网络无法使用谷歌的k8s.gcr.io镜像源，我们可以直接在配置文件中修改imageRepository参数为阿里的镜像源 kubernetesVersion字段用来指定我们要安装的k8s版本 localAPIEndpoint参数需要修改为我们的master节点的IP和端口，初始化之后的k8s集群的apiserver地址就是这个 podSubnet、serviceSubnet和dnsDomain两个参数默认情况下可以不用修改，这里我按照自己的需求进行了变更 nodeRegistration里面的name参数修改为对应master节点的hostname 新增配置块使用ipvs，具体可以参考官方文档 apiVersion: kubeadm.k8s.io/v1beta3 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 10.31.188.1 bindPort: 6443 nodeRegistration: criSocket: /var/run/dockershim.sock imagePullPolicy: IfNotPresent name: tiny-cilium-master-188-1.k8s.tcinternal taints: null --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: 1.23.6 networking: dnsDomain: cili-cluster.tclocal serviceSubnet: 10.188.0.0/18 podSubnet: 10.188.64.0/18 scheduler: {} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs 初始化集群 此时我们再查看对应的配置文件中的镜像版本，就会发现已经变成了对应阿里云镜像源的版本\n# 查看一下对应的镜像版本，确定配置文件是否生效 $ kubeadm config images list --config kubeadm-cilium.conf registry.aliyuncs.com/google_containers/kube-apiserver:v1.23.6 registry.aliyuncs.com/google_containers/kube-controller-manager:v1.23.6 registry.aliyuncs.com/google_containers/kube-scheduler:v1.23.6 registry.aliyuncs.com/google_containers/kube-proxy:v1.23.6 registry.aliyuncs.com/google_containers/pause:3.6 registry.aliyuncs.com/google_containers/etcd:3.5.1-0 registry.aliyuncs.com/google_containers/coredns:v1.8.6 # 确认没问题之后我们直接拉取镜像 $ kubeadm config images pull --config kubeadm-cilium.conf [config/images] Pulled registry.aliyuncs.com/google_containers/kube-apiserver:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-controller-manager:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-scheduler:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-proxy:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/pause:3.6 [config/images] Pulled registry.aliyuncs.com/google_containers/etcd:3.5.1-0 [config/images] Pulled registry.aliyuncs.com/google_containers/coredns:v1.8.6 # 初始化 $ kubeadm init --config kubeadm-cilium.conf [init] Using Kubernetes version: v1.23.6 [preflight] Running pre-flight checks [preflight] Pulling images required for setting up a Kubernetes cluster [preflight] This might take a minute or two, depending on the speed of your internet connection [preflight] You can also perform this action in beforehand using \u0026#39;kubeadm config images pull\u0026#39; ...此处略去一堆输出... 当我们看到下面这个输出结果的时候，我们的集群就算是初始化成功了。\nYour Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf You should now deploy a pod network to the cluster. Run \u0026#34;kubectl apply -f [podnetwork].yaml\u0026#34; with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 10.31.188.1:6443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:fbe33f0dbda199b487a78948a4c693660d742d0dfc270bad2963a035b4971ade 配置kubeconfig 刚初始化成功之后，我们还没办法马上查看k8s集群信息，需要配置kubeconfig相关参数才能正常使用kubectl连接apiserver读取集群信息。\n# 对于非root用户，可以这样操作 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 如果是root用户，可以直接导入环境变量 export KUBECONFIG=/etc/kubernetes/admin.conf # 添加kubectl的自动补全功能 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 前面我们提到过kubectl不一定要安装在集群内，实际上只要是任何一台能连接到apiserver的机器上面都可以安装kubectl并且根据步骤配置kubeconfig，就可以使用kubectl命令行来管理对应的k8s集群。\n配置完成后，我们再执行相关命令就可以查看集群的信息了。\n$ kubectl cluster-info Kubernetes control plane is running at https://10.31.188.1:6443 CoreDNS is running at https://10.31.188.1:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39;. $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME tiny-cilium-master-188-1.k8s.tcinternal NotReady control-plane,master 84s v1.23.6 10.31.188.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 5.17.6-1.el7.elrepo.x86_64 docker://20.10.14 $ kubectl get pods -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system coredns-6d8c4cb4d-285cl 0/1 Pending 0 78s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system coredns-6d8c4cb4d-6zntv 0/1 Pending 0 78s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-tiny-cilium-master-188-1.k8s.tcinternal 1/1 Running 0 90s 10.31.188.1 tiny-cilium-master-188-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-tiny-cilium-master-188-1.k8s.tcinternal 1/1 Running 0 92s 10.31.188.1 tiny-cilium-master-188-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-tiny-cilium-master-188-1.k8s.tcinternal 1/1 Running 0 90s 10.31.188.1 tiny-cilium-master-188-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-m7q5n 1/1 Running 0 78s 10.31.188.1 tiny-cilium-master-188-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-tiny-cilium-master-188-1.k8s.tcinternal 1/1 Running 0 91s 10.31.188.1 tiny-cilium-master-188-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 添加worker节点 这时候我们还需要继续添加剩下的两个节点作为worker节点运行负载，直接在剩下的节点上面运行集群初始化成功时输出的命令就可以成功加入集群。\n$ kubeadm join 10.31.188.1:6443 --token abcdef.0123456789abcdef \\ \u0026gt; --discovery-token-ca-cert-hash sha256:fbe33f0dbda199b487a78948a4c693660d742d0dfc270bad2963a035b4971ade [preflight] Running pre-flight checks [preflight] Reading configuration from the cluster... [preflight] FYI: You can look at this config file with \u0026#39;kubectl -n kube-system get cm kubeadm-config -o yaml\u0026#39; [kubelet-start] Writing kubelet configuration to file \u0026#34;/var/lib/kubelet/config.yaml\u0026#34; [kubelet-start] Writing kubelet environment file with flags to file \u0026#34;/var/lib/kubelet/kubeadm-flags.env\u0026#34; [kubelet-start] Starting the kubelet [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap... This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run \u0026#39;kubectl get nodes\u0026#39; on the control-plane to see this node join the cluster. 如果不小心没保存初始化成功的输出信息也没有关系，我们可以使用kubectl工具查看或者生成token\n# 查看现有的token列表 $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS abcdef.0123456789abcdef 23h 2022-05-10T05:46:11Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token # 如果token已经失效，那就再创建一个新的token $ kubeadm token create xd468t.co8ye3su70bojo2k $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS abcdef.0123456789abcdef 23h 2022-05-10T05:46:11Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token xd468t.co8ye3su70bojo2k 23h 2022-05-10T05:58:40Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token # 如果找不到--discovery-token-ca-cert-hash参数，则可以在master节点上使用openssl工具来获取 $ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2\u0026gt;/dev/null | openssl dgst -sha256 -hex | sed \u0026#39;s/^.* //\u0026#39; 0d68339d3e5a045dc093470321f8f6334223e97f360542477c4f480bda34d72a 添加完成之后我们再查看集群的节点可以发现这时候已经多了两个node，但是此时节点的状态还是NotReady，接下来就需要部署CNI了。\n$ kubectl get nodes NAME STATUS ROLES AGE VERSION tiny-cilium-master-188-1.k8s.tcinternal NotReady control-plane,master 2m47s v1.23.6 tiny-cilium-worker-188-11.k8s.tcinternal NotReady \u0026lt;none\u0026gt; 41s v1.23.6 tiny-cilium-worker-188-12.k8s.tcinternal NotReady \u0026lt;none\u0026gt; 30s v1.23.6 安装CNI 安装cilium 快速安装的教程可以参考官网文档，基本的安装思路就是先下载cilium官方的cli工具，然后使用cli工具进行安装。\n这种安装方式的优势就是简单快捷，缺点就是缺少自定义参数配置的功能，只能使用官方原先设置的默认参数，比较适合快速初始化搭建可用环境用来学习和测试。\n# cilium的cli工具是一个二进制的可执行文件 $ curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz{,.sha256sum} $ sha256sum --check cilium-linux-amd64.tar.gz.sha256sum cilium-linux-amd64.tar.gz: OK $ sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin cilium # 使用该命令即可完成cilium的安装 $ cilium install ℹ️ using Cilium version \u0026#34;v1.11.3\u0026#34; 🔮 Auto-detected cluster name: kubernetes 🔮 Auto-detected IPAM mode: cluster-pool ℹ️ helm template --namespace kube-system cilium cilium/cilium --version 1.11.3 --set cluster.id=0,cluster.name=kubernetes,encryption.nodeEncryption=false,ipam.mode=cluster-pool,kubeProxyReplacement=disabled,operator.replicas=1,serviceAccounts.cilium.name=cilium,serviceAccounts.operator.name=cilium-operator ℹ️ Storing helm values file in kube-system/cilium-cli-helm-values Secret 🔑 Created CA in secret cilium-ca 🔑 Generating certificates for Hubble... 🚀 Creating Service accounts... 🚀 Creating Cluster roles... 🚀 Creating ConfigMap for Cilium version 1.11.3... 🚀 Creating Agent DaemonSet... level=warning msg=\u0026#34;spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[1].matchExpressions[0].key: beta.kubernetes.io/os is deprecated since v1.14; use \\\u0026#34;kubernetes.io/os\\\u0026#34; instead\u0026#34; subsys=klog level=warning msg=\u0026#34;spec.template.metadata.annotations[scheduler.alpha.kubernetes.io/critical-pod]: non-functional in v1.16+; use the \\\u0026#34;priorityClassName\\\u0026#34; field instead\u0026#34; subsys=klog 🚀 Creating Operator Deployment... ⌛ Waiting for Cilium to be installed and ready... ✅ Cilium was successfully installed! Run \u0026#39;cilium status\u0026#39; to view installation health # 查看cilium的状态 $ cilium status /¯¯\\ /¯¯\\__/¯¯\\ Cilium: OK \\__/¯¯\\__/ Operator: OK /¯¯\\__/¯¯\\ Hubble: disabled \\__/¯¯\\__/ ClusterMesh: disabled \\__/ DaemonSet cilium Desired: 3, Ready: 3/3, Available: 3/3 Deployment cilium-operator Desired: 1, Ready: 1/1, Available: 1/1 Containers: cilium-operator Running: 1 cilium Running: 3 Cluster Pods: 2/2 managed by Cilium Image versions cilium quay.io/cilium/cilium:v1.11.3@sha256:cb6aac121e348abd61a692c435a90a6e2ad3f25baa9915346be7b333de8a767f: 3 cilium-operator quay.io/cilium/operator-generic:v1.11.3@sha256:5b81db7a32cb7e2d00bb3cf332277ec2b3be239d9e94a8d979915f4e6648c787: 1 配置hubble # 我们先使用cilium-cli工具在k8s集群中部署hubble，只需要下面一条命令即可 $ cilium hubble enable 🔑 Found CA in secret cilium-ca ℹ️ helm template --namespace kube-system cilium cilium/cilium --version 1.11.3 --set cluster.id=0,cluster.name=kubernetes,encryption.nodeEncryption=false,hubble.enabled=true,hubble.relay.enabled=true,hubble.tls.ca.cert=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNGRENDQWJxZ0F3SUJBZ0lVSDRQcit1UU0xSXZtdWQvVlV3YWlycGllSEZBd0NnWUlLb1pJemowRUF3SXcKYURFTE1Ba0dBMVVFQmhNQ1ZWTXhGakFVQmdOVkJBZ1REVk5oYmlCR2NtRnVZMmx6WTI4eEN6QUpCZ05WQkFjVApBa05CTVE4d0RRWURWUVFLRXdaRGFXeHBkVzB4RHpBTkJnTlZCQXNUQmtOcGJHbDFiVEVTTUJBR0ExVUVBeE1KClEybHNhWFZ0SUVOQk1CNFhEVEl5TURVd09UQTVNREF3TUZvWERUSTNNRFV3T0RBNU1EQXdNRm93YURFTE1Ba0cKQTFVRUJoTUNWVk14RmpBVUJnTlZCQWdURFZOaGJpQkdjbUZ1WTJselkyOHhDekFKQmdOVkJBY1RBa05CTVE4dwpEUVlEVlFRS0V3WkRhV3hwZFcweER6QU5CZ05WQkFzVEJrTnBiR2wxYlRFU01CQUdBMVVFQXhNSlEybHNhWFZ0CklFTkJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUU3Z21EQ05WOERseEIxS3VYYzhEdndCeUoKWUxuSENZNjVDWUhBb3ZBY3FUM3drcitLVVNwelcyVjN0QW9IaFdZV0UyQ2lUNjNIOXZLV1ZRY3pHeXp1T0tOQwpNRUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGQmMrClNDb3F1Y0JBc09sdDBWaEVCbkwyYjEyNE1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRRDJsNWVqaDVLVTkySysKSHJJUXIweUwrL05pZ3NSUHRBblA5T3lDcHExbFJBSWdYeGY5a2t5N2xYU0pOYmpkREFjbnBrNlJFTFp2eEkzbQpKaG9JRkRlbER0dz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=,hubble.tls.ca.key=[--- REDACTED WHEN PRINTING TO TERMINAL (USE --redact-helm-certificate-keys=false TO PRINT) ---],ipam.mode=cluster-pool,kubeProxyReplacement=disabled,operator.replicas=1,serviceAccounts.cilium.name=cilium,serviceAccounts.operator.name=cilium-operator ✨ Patching ConfigMap cilium-config to enable Hubble... 🚀 Creating ConfigMap for Cilium version 1.11.3... ♻️ Restarted Cilium pods ⌛ Waiting for Cilium to become ready before deploying other Hubble component(s)... ✨ Generating certificates... 🔑 Generating certificates for Relay... ✨ Deploying Relay... ⌛ Waiting for Hubble to be installed... ℹ️ Storing helm values file in kube-system/cilium-cli-helm-values Secret ✅ Hubble was successfully enabled! # 安装hubble-cli工具，安装逻辑和cilium-cli的逻辑相似 $ export HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt) $ curl -L --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-amd64.tar.gz{,.sha256sum} $ sha256sum --check hubble-linux-amd64.tar.gz.sha256sum hubble-linux-amd64.tar.gz: OK $ sudo tar xzvfC hubble-linux-amd64.tar.gz /usr/local/bin hubble # 首先我们要开启hubble的api，使用cilium-cli开启转发 $ cilium hubble port-forward\u0026amp; [1] 15512 $ kubectl get svc -n kube-system NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hubble-relay ClusterIP 10.188.55.197 \u0026lt;none\u0026gt; 80/TCP 16h hubble-ui ClusterIP 10.188.17.78 \u0026lt;none\u0026gt; 80/TCP 16h kube-dns ClusterIP 10.188.0.10 \u0026lt;none\u0026gt; 53/UDP,53/TCP,9153/TCP 17h $ netstat -ntulp | grep 4245 tcp 0 0 0.0.0.0:4245 0.0.0.0:* LISTEN 15527/kubectl tcp6 0 0 :::4245 :::* LISTEN 15527/kubectl # 实际上执行的操作等同于下面这个命令 # kubectl port-forward -n kube-system svc/hubble-relay --address 0.0.0.0 --address :: 4245:80 # 测试和hubble-api的连通性 $ hubble status Healthcheck (via localhost:4245): Ok Current/Max Flows: 12,285/12,285 (100.00%) Flows/s: 28.58 Connected Nodes: 3/3 # 使用hubble命令查看数据的转发情况 $ hubble observe Handling connection for 4245 May 9 09:33:25.861: 10.0.1.47:44484 -\u0026gt; cilium-test/echo-same-node-5767b7b99d-xhzpb:8080 to-endpoint FORWARDED (TCP Flags: ACK, PSH) May 9 09:33:25.863: 10.0.1.47:44484 \u0026lt;- cilium-test/echo-same-node-5767b7b99d-xhzpb:8080 to-stack FORWARDED (TCP Flags: ACK, PSH) May 9 09:33:25.864: 10.0.1.47:44484 -\u0026gt; cilium-test/echo-same-node-5767b7b99d-xhzpb:8080 to-endpoint FORWARDED (TCP Flags: ACK, FIN) ...此处略去一堆输出... # 开启hubble ui组件 $ cilium hubble enable --ui 🔑 Found CA in secret cilium-ca ℹ️ helm template --namespace kube-system cilium cilium/cilium --version 1.11.3 --set cluster.id=0,cluster.name=kubernetes,encryption.nodeEncryption=false,hubble.enabled=true,hubble.relay.enabled=true,hubble.tls.ca.cert=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNGRENDQWJxZ0F3SUJBZ0lVSDRQcit1UU0xSXZtdWQvVlV3YWlycGllSEZBd0NnWUlLb1pJemowRUF3SXcKYURFTE1Ba0dBMVVFQmhNQ1ZWTXhGakFVQmdOVkJBZ1REVk5oYmlCR2NtRnVZMmx6WTI4eEN6QUpCZ05WQkFjVApBa05CTVE4d0RRWURWUVFLRXdaRGFXeHBkVzB4RHpBTkJnTlZCQXNUQmtOcGJHbDFiVEVTTUJBR0ExVUVBeE1KClEybHNhWFZ0SUVOQk1CNFhEVEl5TURVd09UQTVNREF3TUZvWERUSTNNRFV3T0RBNU1EQXdNRm93YURFTE1Ba0cKQTFVRUJoTUNWVk14RmpBVUJnTlZCQWdURFZOaGJpQkdjbUZ1WTJselkyOHhDekFKQmdOVkJBY1RBa05CTVE4dwpEUVlEVlFRS0V3WkRhV3hwZFcweER6QU5CZ05WQkFzVEJrTnBiR2wxYlRFU01CQUdBMVVFQXhNSlEybHNhWFZ0CklFTkJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUU3Z21EQ05WOERseEIxS3VYYzhEdndCeUoKWUxuSENZNjVDWUhBb3ZBY3FUM3drcitLVVNwelcyVjN0QW9IaFdZV0UyQ2lUNjNIOXZLV1ZRY3pHeXp1T0tOQwpNRUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGQmMrClNDb3F1Y0JBc09sdDBWaEVCbkwyYjEyNE1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRRDJsNWVqaDVLVTkySysKSHJJUXIweUwrL05pZ3NSUHRBblA5T3lDcHExbFJBSWdYeGY5a2t5N2xYU0pOYmpkREFjbnBrNlJFTFp2eEkzbQpKaG9JRkRlbER0dz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=,hubble.tls.ca.key=[--- REDACTED WHEN PRINTING TO TERMINAL (USE --redact-helm-certificate-keys=false TO PRINT) ---],hubble.ui.enabled=true,hubble.ui.securityContext.enabled=false,ipam.mode=cluster-pool,kubeProxyReplacement=disabled,operator.replicas=1,serviceAccounts.cilium.name=cilium,serviceAccounts.operator.name=cilium-operator ✨ Patching ConfigMap cilium-config to enable Hubble... 🚀 Creating ConfigMap for Cilium version 1.11.3... ♻️ Restarted Cilium pods ⌛ Waiting for Cilium to become ready before deploying other Hubble component(s)... ✅ Relay is already deployed ✨ Deploying Hubble UI and Hubble UI Backend... ⌛ Waiting for Hubble to be installed... ℹ️ Storing helm values file in kube-system/cilium-cli-helm-values Secret ✅ Hubble was successfully enabled! # 实际上这时候我们再查看k8s集群的状态可以看到部署了一个名为hubble-ui的deployment $ kubectl get deployment -n kube-system | grep hubble hubble-relay 1/1 1 1 17h hubble-ui 1/1 1 1 17h $ kubectl get svc -n kube-system | grep hubble hubble-relay ClusterIP 10.188.55.197 \u0026lt;none\u0026gt; 80/TCP 17h hubble-ui ClusterIP 10.188.17.78 \u0026lt;none\u0026gt; 80/TCP 17h # 将hubble-ui这个服务的80端口暴露到宿主机上面的12000端口上面 $ cilium hubble ui\u0026amp; [2] 5809 ℹ️ Opening \u0026#34;http://localhost:12000\u0026#34; in your browser... # 实际上执行的操作等同于下面这个命令 # kubectl port-forward -n kube-system svc/hubble-ui --address 0.0.0.0 --address :: 12000:80 访问k8s宿主机节点的IP+端口就可以看到hubble-ui的界面了\n最后所有的相关服务都部署完成之后，我们再检查一下整个cilium的状态\n$ cilium status /¯¯\\ /¯¯\\__/¯¯\\ Cilium: OK \\__/¯¯\\__/ Operator: OK /¯¯\\__/¯¯\\ Hubble: OK \\__/¯¯\\__/ ClusterMesh: disabled \\__/ Deployment cilium-operator Desired: 1, Ready: 1/1, Available: 1/1 Deployment hubble-relay Desired: 1, Ready: 1/1, Available: 1/1 Deployment hubble-ui Desired: 1, Ready: 1/1, Available: 1/1 DaemonSet cilium Desired: 3, Ready: 3/3, Available: 3/3 Containers: cilium Running: 3 cilium-operator Running: 1 hubble-relay Running: 1 hubble-ui Running: 1 Cluster Pods: 8/8 managed by Cilium Image versions cilium quay.io/cilium/cilium:v1.11.3@sha256:cb6aac121e348abd61a692c435a90a6e2ad3f25baa9915346be7b333de8a767f: 3 cilium-operator quay.io/cilium/operator-generic:v1.11.3@sha256:5b81db7a32cb7e2d00bb3cf332277ec2b3be239d9e94a8d979915f4e6648c787: 1 hubble-relay quay.io/cilium/hubble-relay:v1.11.3@sha256:7256ec111259a79b4f0e0f80ba4256ea23bd472e1fc3f0865975c2ed113ccb97: 1 hubble-ui quay.io/cilium/hubble-ui:v0.8.5@sha256:4eaca1ec1741043cfba6066a165b3bf251590cf4ac66371c4f63fbed2224ebb4: 1 hubble-ui quay.io/cilium/hubble-ui-backend:v0.8.5@sha256:2bce50cf6c32719d072706f7ceccad654bfa907b2745a496da99610776fe31ed: 1 hubble-ui docker.io/envoyproxy/envoy:v1.18.4@sha256:e5c2bb2870d0e59ce917a5100311813b4ede96ce4eb0c6bfa879e3fbe3e83935: 1 部署测试用例 集群部署完成之后我们在k8s集群中部署一个nginx测试一下是否能够正常工作。首先我们创建一个名为nginx-quic的命名空间（namespace），然后在这个命名空间内创建一个名为nginx-quic-deployment的deployment用来部署pod，最后再创建一个service用来暴露服务，这里我们先使用nodeport的方式暴露端口方便测试。\n$ cat nginx-quic.yaml apiVersion: v1 kind: Namespace metadata: name: nginx-quic --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-quic-deployment namespace: nginx-quic spec: selector: matchLabels: app: nginx-quic replicas: 4 template: metadata: labels: app: nginx-quic spec: containers: - name: nginx-quic image: tinychen777/nginx-quic:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-quic-service namespace: nginx-quic spec: externalTrafficPolicy: Cluster selector: app: nginx-quic ports: - protocol: TCP port: 8080 # match for service access port targetPort: 80 # match for pod access port nodePort: 30088 # match for external access port type: NodePort 部署完成后我们直接查看状态\n# 直接部署 $ kubectl apply -f nginx-quic.yaml namespace/nginx-quic created deployment.apps/nginx-quic-deployment created service/nginx-quic-service created # 查看deployment的运行状态 $ kubectl get deployment -o wide -n nginx-quic NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR nginx-quic-deployment 4/4 4 4 17h nginx-quic tinychen777/nginx-quic:latest app=nginx-quic # 查看service的运行状态 $ kubectl get service -o wide -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR nginx-quic-service NodePort 10.188.0.200 \u0026lt;none\u0026gt; 8080:30088/TCP 17h app=nginx-quic # 查看pod的运行状态 $ kubectl get pods -o wide -n nginx-quic NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-quic-deployment-696d959797-26dzh 1/1 Running 1 (17h ago) 17h 10.0.2.58 tiny-cilium-worker-188-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-696d959797-kw6bn 1/1 Running 1 (17h ago) 17h 10.0.2.207 tiny-cilium-worker-188-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-696d959797-mdw99 1/1 Running 1 (17h ago) 17h 10.0.1.247 tiny-cilium-worker-188-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-696d959797-x42zn 1/1 Running 1 (17h ago) 17h 10.0.1.60 tiny-cilium-worker-188-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # 查看IPVS规则 $ ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 172.17.0.1:30088 rr -\u0026gt; 10.0.1.60:80 Masq 1 0 0 -\u0026gt; 10.0.1.247:80 Masq 1 0 0 -\u0026gt; 10.0.2.58:80 Masq 1 0 0 -\u0026gt; 10.0.2.207:80 Masq 1 0 0 TCP 10.0.0.226:30088 rr -\u0026gt; 10.0.1.60:80 Masq 1 0 0 -\u0026gt; 10.0.1.247:80 Masq 1 0 0 -\u0026gt; 10.0.2.58:80 Masq 1 0 0 -\u0026gt; 10.0.2.207:80 Masq 1 0 0 TCP 10.31.188.1:30088 rr -\u0026gt; 10.0.1.60:80 Masq 1 0 0 -\u0026gt; 10.0.1.247:80 Masq 1 0 0 -\u0026gt; 10.0.2.58:80 Masq 1 0 0 -\u0026gt; 10.0.2.207:80 Masq 1 0 0 TCP 10.188.0.200:8080 rr -\u0026gt; 10.0.1.60:80 Masq 1 0 0 -\u0026gt; 10.0.1.247:80 Masq 1 0 0 -\u0026gt; 10.0.2.58:80 Masq 1 0 0 -\u0026gt; 10.0.2.207:80 Masq 1 0 0 最后我们进行测试，这个nginx-quic的镜像默认情况下会返回在nginx容器中获得的用户请求的IP和端口\n# 首先我们在集群内进行测试 # 直接访问pod $ curl 10.0.2.58:80 10.0.0.226:52312 # 直接访问service的ClusterIP，这时请求会被转发到pod中 $ curl 10.188.0.200:8080 10.0.0.226:36228 # 直接访问nodeport，这时请求会被转发到pod中，不会经过ClusterIP # 此时实际返回的IP要取决于被转发到的后端pod是否在当前的k8s节点上 $ curl 10.31.188.1:30088 10.0.0.226:38034 $ curl 10.31.188.11:30088 10.31.188.11:24371 $ curl 10.31.188.12:30088 10.31.188.12:56919 $ curl 10.31.188.12:30088 10.0.2.151:49222 # 接着我们在集群外进行测试 # 直接访问三个节点的nodeport，这时请求会被转发到pod中，不会经过ClusterIP # 此时实际返回的IP要取决于被转发到的后端pod是否在当前的k8s节点上 $ curl 10.31.188.11:30088 10.0.1.47:6318 $ curl 10.31.188.11:30088 10.31.188.11:63944 $ curl 10.31.188.12:30088 10.31.188.12:53882 $ curl 10.31.188.12:30088 10.0.2.151:6366 $ curl 10.31.188.12:30088 10.0.2.151:6368 $ curl 10.31.188.12:30088 10.31.188.12:48174 ","permalink":"http://localhost:9011/20220510-k8s-04-deploy-k8s-with-cilium/","summary":"\u003cp\u003e本文主要在centos7系统上基于\u003ccode\u003edocker\u003c/code\u003e和\u003ccode\u003ecilium\u003c/code\u003e组件部署\u003ccode\u003ev1.23.6\u003c/code\u003e版本的k8s原生集群，由于集群主要用于自己平时学习和测试使用，加上资源有限，暂不涉及高可用部署。\u003c/p\u003e\n\u003cp\u003e此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列04-kubeadm部署cilium网络的k8s集群"},{"content":"本文主要在centos7系统上基于docker和calico组件部署v1.23.6版本的k8s原生集群，由于集群主要用于自己平时学习和测试使用，加上资源有限，暂不涉及高可用部署。\n此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n准备工作 calico-集群节点信息 机器均为8C8G的虚拟机，硬盘为100G。\nIP Hostname 10.31.88.1 tiny-calico-master-88-1.k8s.tcinternal 10.31.88.11 tiny-calico-worker-88-11.k8s.tcinternal 10.31.88.12 tiny-calico-worker-88-12.k8s.tcinternal 10.88.64.0/18 podSubnet 10.88.0.0/18 serviceSubnet 检查mac和product_uuid 同一个k8s集群内的所有节点需要确保mac地址和product_uuid均唯一，开始集群初始化之前需要检查相关信息\n# 检查mac地址 ip link ifconfig -a # 检查product_uuid sudo cat /sys/class/dmi/id/product_uuid 配置ssh免密登录（可选） 如果k8s集群的节点有多个网卡，确保每个节点能通过正确的网卡互联访问\n# 在root用户下面生成一个公用的key，并配置可以使用该key免密登录 su root ssh-keygen cd /root/.ssh/ cat id_rsa.pub \u0026gt;\u0026gt; authorized_keys chmod 600 authorized_keys cat \u0026gt;\u0026gt; ~/.ssh/config \u0026lt;\u0026lt;EOF Host tiny-calico-master-88-1.k8s.tcinternal HostName 10.31.88.1 User root Port 22 IdentityFile ~/.ssh/id_rsa Host tiny-calico-worker-88-11.k8s.tcinternal HostName 10.31.88.11 User root Port 22 IdentityFile ~/.ssh/id_rsa Host tiny-calico-worker-88-12.k8s.tcinternal HostName 10.31.88.12 User root Port 22 IdentityFile ~/.ssh/id_rsa EOF 修改hosts文件 cat \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt;EOF 10.31.88.1 tiny-calico-master-88-1 tiny-calico-master-88-1.k8s.tcinternal 10.31.88.11 tiny-calico-worker-88-11 tiny-calico-worker-88-11.k8s.tcinternal 10.31.88.12 tiny-calico-worker-88-12 tiny-calico-worker-88-12.k8s.tcinternal EOF 关闭swap内存 # 使用命令直接关闭swap内存 swapoff -a # 修改fstab文件禁止开机自动挂载swap分区 sed -i \u0026#39;/swap / s/^\\(.*\\)$/#\\1/g\u0026#39; /etc/fstab 配置时间同步 这里可以根据自己的习惯选择ntp或者是chrony同步均可，同步的时间源服务器可以选择阿里云的ntp1.aliyun.com或者是国家时间中心的ntp.ntsc.ac.cn。\n使用ntp同步 # 使用yum安装ntpdate工具 yum install ntpdate -y # 使用国家时间中心的源同步时间 ntpdate ntp.ntsc.ac.cn # 最后查看一下时间 hwclock 使用chrony同步 # 使用yum安装chrony yum install chrony -y # 设置开机启动并开启chony并查看运行状态 systemctl enable chronyd.service systemctl start chronyd.service systemctl status chronyd.service # 当然也可以自定义时间服务器 vim /etc/chrony.conf # 修改前 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server 0.centos.pool.ntp.org iburst server 1.centos.pool.ntp.org iburst server 2.centos.pool.ntp.org iburst server 3.centos.pool.ntp.org iburst # 修改后 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server ntp.ntsc.ac.cn iburst # 重启服务使配置文件生效 systemctl restart chronyd.service # 查看chrony的ntp服务器状态 chronyc sourcestats -v chronyc sources -v 关闭selinux # 使用命令直接关闭 setenforce 0 # 也可以直接修改/etc/selinux/config文件 sed -i \u0026#39;s/^SELINUX=enforcing$/SELINUX=disabled/\u0026#39; /etc/selinux/config 配置防火墙 k8s集群之间通信和服务暴露需要使用较多端口，为了方便，直接禁用防火墙\n# centos7使用systemctl禁用默认的firewalld服务 systemctl disable firewalld.service 配置netfilter参数 这里主要是需要配置内核加载br_netfilter和iptables放行ipv6和ipv4的流量，确保集群内的容器能够正常通信。\ncat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter EOF cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sudo sysctl --system 关闭IPV6（可选） 虽然新版本的k8s已经支持双栈网络，但是本次的集群部署过程并不涉及IPv6网络的通信，因此关闭IPv6网络支持\n# 直接在内核中添加ipv6禁用参数 grubby --update-kernel=ALL --args=ipv6.disable=1 配置IPVS（可选） IPVS是专门设计用来应对负载均衡场景的组件，kube-proxy 中的 IPVS 实现通过减少对 iptables 的使用来增加可扩展性。在 iptables 输入链中不使用 PREROUTING，而是创建一个假的接口，叫做 kube-ipvs0，当k8s集群中的负载均衡配置变多的时候，IPVS能实现比iptables更高效的转发性能。\n注意在4.19之后的内核版本中使用nf_conntrack模块来替换了原有的nf_conntrack_ipv4模块\n(Notes: use nf_conntrack instead of nf_conntrack_ipv4 for Linux kernel 4.19 and later)\n# 在使用ipvs模式之前确保安装了ipset和ipvsadm sudo yum install ipset ipvsadm -y # 手动加载ipvs相关模块 modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack_ipv4 # 配置开机自动加载ipvs相关模块 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/ipvs.conf ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack_ipv4 EOF sudo sysctl --system # 最好重启一遍系统确定是否生效 $ lsmod | grep -e ip_vs -e nf_conntrack_ipv4 ip_vs_sh 12688 0 ip_vs_wrr 12697 0 ip_vs_rr 12600 0 ip_vs 145458 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr nf_conntrack_ipv4 15053 2 nf_defrag_ipv4 12729 1 nf_conntrack_ipv4 nf_conntrack 139264 7 ip_vs,nf_nat,nf_nat_ipv4,xt_conntrack,nf_nat_masquerade_ipv4,nf_conntrack_netlink,nf_conntrack_ipv4 libcrc32c 12644 4 xfs,ip_vs,nf_nat,nf_conntrack $ cut -f1 -d \u0026#34; \u0026#34; /proc/modules | grep -e ip_vs -e nf_conntrack_ipv4 ip_vs_sh ip_vs_wrr ip_vs_rr ip_vs nf_conntrack_ipv4 安装container runtime 安装docker 详细的官方文档可以参考这里，由于在刚发布的1.24版本中移除了docker-shim，因此安装的版本≥1.24的时候需要注意容器运行时的选择。这里我们安装的版本低于1.24，因此我们继续使用docker。\ndocker的具体安装可以参考我之前写的这篇文章，这里不做赘述。\n# 安装必要的依赖组件并且导入docker官方提供的yum源 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 我们直接安装最新版本的docker yum install docker-ce docker-ce-cli containerd.io 配置cgroup drivers CentOS7使用的是systemd来初始化系统并管理进程，初始化进程会生成并使用一个 root 控制组 (cgroup), 并充当 cgroup 管理器。 Systemd 与 cgroup 集成紧密，并将为每个 systemd 单元分配一个 cgroup。 我们也可以配置容器运行时和 kubelet 使用 cgroupfs。 连同 systemd 一起使用 cgroupfs 意味着将有两个不同的 cgroup 管理器。而当一个系统中同时存在cgroupfs和systemd两者时，容易变得不稳定，因此最好更改设置，令容器运行时和 kubelet 使用 systemd 作为 cgroup 驱动，以此使系统更为稳定。 对于 Docker, 需要设置 native.cgroupdriver=systemd 参数。\n参考官方的说明文档：\nhttps://kubernetes.io/docs/setup/production-environment/container-runtimes/#cgroup-drivers\n参考配置说明文档\nhttps://kubernetes.io/zh/docs/setup/production-environment/container-runtimes/#docker\nsudo mkdir /etc/docker cat \u0026lt;\u0026lt;EOF | sudo tee /etc/docker/daemon.json { \u0026#34;exec-opts\u0026#34;: [\u0026#34;native.cgroupdriver=systemd\u0026#34;], \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34; }, \u0026#34;storage-driver\u0026#34;: \u0026#34;overlay2\u0026#34; } EOF sudo systemctl enable docker sudo systemctl daemon-reload sudo systemctl restart docker # 最后检查一下Cgroup Driver是否为systemd $ docker info | grep systemd Cgroup Driver: systemd 关于kubelet的cgroup driver k8s官方有详细的文档介绍了如何设置kubelet的cgroup driver，需要特别注意的是，在1.22版本开始，如果没有手动设置kubelet的cgroup driver，那么默认会设置为systemd\nNote: In v1.22, if the user is not setting the cgroupDriver field under KubeletConfiguration, kubeadm will default it to systemd.\n一个比较简单的指定kubelet的cgroup driver的方法就是在kubeadm-config.yaml加入cgroupDriver字段\n# kubeadm-config.yaml kind: ClusterConfiguration apiVersion: kubeadm.k8s.io/v1beta3 kubernetesVersion: v1.21.0 --- kind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 cgroupDriver: systemd 我们可以直接查看configmaps来查看初始化之后集群的kubeadm-config配置。\n$ kubectl describe configmaps kubeadm-config -n kube-system Name: kubeadm-config Namespace: kube-system Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; Data ==== ClusterConfiguration: ---- apiServer: extraArgs: authorization-mode: Node,RBAC timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: v1.23.6 networking: dnsDomain: cali-cluster.tclocal serviceSubnet: 10.88.0.0/18 scheduler: {} BinaryData ==== Events: \u0026lt;none\u0026gt; 当然因为我们需要安装的版本高于1.22.0并且使用的就是systemd，因此可以不用再重复配置。\n安装kube三件套 对应的官方文档可以参考这里\nhttps://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl\nkube三件套就是kubeadm、kubelet 和 kubectl，三者的具体功能和作用如下：\nkubeadm：用来初始化集群的指令。 kubelet：在集群中的每个节点上用来启动 Pod 和容器等。 kubectl：用来与集群通信的命令行工具。 需要注意的是：\nkubeadm不会帮助我们管理kubelet和kubectl，其他两者也是一样的，也就是说这三者是相互独立的，并不存在谁管理谁的情况； kubelet的版本必须小于等于API-server的版本，否则容易出现兼容性的问题； kubectl并不是集群中的每个节点都需要安装，也并不是一定要安装在集群中的节点，可以单独安装在自己本地的机器环境上面，然后配合kubeconfig文件即可使用kubectl命令来远程管理对应的k8s集群； CentOS7的安装比较简单，我们直接使用官方提供的yum源即可。需要注意的是这里需要设置selinux的状态，但是前面我们已经关闭了selinux，因此这里略过这步。\n# 直接导入谷歌官方的yum源 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\\$basearch enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg exclude=kubelet kubeadm kubectl EOF # 当然如果连不上谷歌的源，可以考虑使用国内的阿里镜像源 cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF # 接下来直接安装三件套即可 sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes # 如果网络环境不好出现gpgcheck验证失败导致无法正常读取yum源，可以考虑关闭该yum源的repo_gpgcheck sed -i \u0026#39;s/repo_gpgcheck=1/repo_gpgcheck=0/g\u0026#39; /etc/yum.repos.d/kubernetes.repo # 或者在安装的时候禁用gpgcheck sudo yum install -y kubelet kubeadm kubectl --nogpgcheck --disableexcludes=kubernetes # 如果想要安装特定版本，可以使用这个命令查看相关版本的信息 sudo yum list --nogpgcheck kubelet kubeadm kubectl --showduplicates --disableexcludes=kubernetes # 这里我们为了保留使用docker-shim，因此我们按照1.24.0版本的前一个版本1.23.6 sudo yum install -y kubelet-1.23.6-0 kubeadm-1.23.6-0 kubectl-1.23.6-0 --nogpgcheck --disableexcludes=kubernetes # 安装完成后配置开机自启kubelet sudo systemctl enable --now kubelet 初始化集群 编写配置文件 在集群中所有节点都执行完上面的三点操作之后，我们就可以开始创建k8s集群了。因为我们这次不涉及高可用部署，因此初始化的时候直接在我们的目标master节点上面操作即可。\n# 我们先使用kubeadm命令查看一下主要的几个镜像版本 # 因为我们此前指定安装了旧的1.23.6版本，这里的apiserver镜像版本也会随之回滚 $ kubeadm config images list I0506 11:24:17.061315 16055 version.go:255] remote version is much newer: v1.24.0; falling back to: stable-1.23 k8s.gcr.io/kube-apiserver:v1.23.6 k8s.gcr.io/kube-controller-manager:v1.23.6 k8s.gcr.io/kube-scheduler:v1.23.6 k8s.gcr.io/kube-proxy:v1.23.6 k8s.gcr.io/pause:3.6 k8s.gcr.io/etcd:3.5.1-0 k8s.gcr.io/coredns/coredns:v1.8.6 # 为了方便编辑和管理，我们还是把初始化参数导出成配置文件 $ kubeadm config print init-defaults \u0026gt; kubeadm-calico.conf 考虑到大多数情况下国内的网络无法使用谷歌的k8s.gcr.io镜像源，我们可以直接在配置文件中修改imageRepository参数为阿里的镜像源 kubernetesVersion字段用来指定我们要安装的k8s版本 localAPIEndpoint参数需要修改为我们的master节点的IP和端口，初始化之后的k8s集群的apiserver地址就是这个 serviceSubnet和dnsDomain两个参数默认情况下可以不用修改，这里我按照自己的需求进行了变更 nodeRegistration里面的name参数修改为对应master节点的hostname 新增配置块使用ipvs，具体可以参考官方文档 apiVersion: kubeadm.k8s.io/v1beta3 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 10.31.88.1 bindPort: 6443 nodeRegistration: criSocket: /var/run/dockershim.sock imagePullPolicy: IfNotPresent name: tiny-calico-master-88-1.k8s.tcinternal taints: null --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: 1.23.6 networking: dnsDomain: cali-cluster.tclocal serviceSubnet: 10.88.0.0/18 scheduler: {} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs 初始化集群 此时我们再查看对应的配置文件中的镜像版本，就会发现已经变成了对应阿里云镜像源的版本\n# 查看一下对应的镜像版本，确定配置文件是否生效 $ kubeadm config images list --config kubeadm-calico.conf registry.aliyuncs.com/google_containers/kube-apiserver:v1.23.6 registry.aliyuncs.com/google_containers/kube-controller-manager:v1.23.6 registry.aliyuncs.com/google_containers/kube-scheduler:v1.23.6 registry.aliyuncs.com/google_containers/kube-proxy:v1.23.6 registry.aliyuncs.com/google_containers/pause:3.6 registry.aliyuncs.com/google_containers/etcd:3.5.1-0 registry.aliyuncs.com/google_containers/coredns:v1.8.6 # 确认没问题之后我们直接拉取镜像 $ kubeadm config images pull --config kubeadm-calico.conf [config/images] Pulled registry.aliyuncs.com/google_containers/kube-apiserver:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-controller-manager:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-scheduler:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-proxy:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/pause:3.6 [config/images] Pulled registry.aliyuncs.com/google_containers/etcd:3.5.1-0 [config/images] Pulled registry.aliyuncs.com/google_containers/coredns:v1.8.6 # 初始化 $ kubeadm init --config kubeadm-calico.conf [init] Using Kubernetes version: v1.23.6 [preflight] Running pre-flight checks [preflight] Pulling images required for setting up a Kubernetes cluster [preflight] This might take a minute or two, depending on the speed of your internet connection [preflight] You can also perform this action in beforehand using \u0026#39;kubeadm config images pull\u0026#39; ...此处略去一堆输出... 当我们看到下面这个输出结果的时候，我们的集群就算是初始化成功了。\nYour Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf You should now deploy a pod network to the cluster. Run \u0026#34;kubectl apply -f [podnetwork].yaml\u0026#34; with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 10.31.88.1:6443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:a4189d36d164a865be540d48fcd10ff13e2f90ed6e901201b6ea2baf96dae0ae 配置kubeconfig 刚初始化成功之后，我们还没办法马上查看k8s集群信息，需要配置kubeconfig相关参数才能正常使用kubectl连接apiserver读取集群信息。\n# 对于非root用户，可以这样操作 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 如果是root用户，可以直接导入环境变量 export KUBECONFIG=/etc/kubernetes/admin.conf # 添加kubectl的自动补全功能 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 前面我们提到过kubectl不一定要安装在集群内，实际上只要是任何一台能连接到apiserver的机器上面都可以安装kubectl并且根据步骤配置kubeconfig，就可以使用kubectl命令行来管理对应的k8s集群。\n配置完成后，我们再执行相关命令就可以查看集群的信息了。\n$ kubectl cluster-info Kubernetes control plane is running at https://10.31.88.1:6443 CoreDNS is running at https://10.31.88.1:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39; $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME tiny-calico-master-88-1.k8s.tcinternal NotReady control-plane,master 4m15s v1.23.6 10.31.88.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 docker://20.10.14 $ kubectl get pods -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system coredns-6d8c4cb4d-r8r9q 0/1 Pending 0 4m20s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system coredns-6d8c4cb4d-ztq6w 0/1 Pending 0 4m20s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-tiny-calico-master-88-1.k8s.tcinternal 1/1 Running 0 4m25s 10.31.88.1 tiny-calico-master-88-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-tiny-calico-master-88-1.k8s.tcinternal 1/1 Running 0 4m26s 10.31.88.1 tiny-calico-master-88-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-tiny-calico-master-88-1.k8s.tcinternal 1/1 Running 0 4m27s 10.31.88.1 tiny-calico-master-88-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-v6cg9 1/1 Running 0 4m20s 10.31.88.1 tiny-calico-master-88-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-tiny-calico-master-88-1.k8s.tcinternal 1/1 Running 0 4m25s 10.31.88.1 tiny-calico-master-88-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 添加worker节点 这时候我们还需要继续添加剩下的两个节点作为worker节点运行负载，直接在剩下的节点上面运行集群初始化成功时输出的命令就可以成功加入集群：\n$ kubeadm join 10.31.88.1:6443 --token abcdef.0123456789abcdef \\ \u0026gt; --discovery-token-ca-cert-hash sha256:a4189d36d164a865be540d48fcd10ff13e2f90ed6e901201b6ea2baf96dae0ae [preflight] Running pre-flight checks [preflight] Reading configuration from the cluster... [preflight] FYI: You can look at this config file with \u0026#39;kubectl -n kube-system get cm kubeadm-config -o yaml\u0026#39; [kubelet-start] Writing kubelet configuration to file \u0026#34;/var/lib/kubelet/config.yaml\u0026#34; [kubelet-start] Writing kubelet environment file with flags to file \u0026#34;/var/lib/kubelet/kubeadm-flags.env\u0026#34; [kubelet-start] Starting the kubelet [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap... This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run \u0026#39;kubectl get nodes\u0026#39; on the control-plane to see this node join the cluster. 如果不小心没保存初始化成功的输出信息也没有关系，我们可以使用kubectl工具查看或者生成token\n# 查看现有的token列表 $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS abcdef.0123456789abcdef 23h 2022-05-07T05:19:08Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token # 如果token已经失效，那就再创建一个新的token $ kubeadm token create e31cv1.lbtrzwp6mzon78ue $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS abcdef.0123456789abcdef 23h 2022-05-07T05:19:08Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token e31cv1.lbtrzwp6mzon78ue 23h 2022-05-07T05:51:40Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token # 如果找不到--discovery-token-ca-cert-hash参数，则可以在master节点上使用openssl工具来获取 $ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2\u0026gt;/dev/null | openssl dgst -sha256 -hex | sed \u0026#39;s/^.* //\u0026#39; a4189d36d164a865be540d48fcd10ff13e2f90ed6e901201b6ea2baf96dae0ae 添加完成之后我们再查看集群的节点可以发现这时候已经多了两个node，但是此时节点的状态还是NotReady，接下来就需要部署CNI了。\n$ kubectl get nodes NAME STATUS ROLES AGE VERSION tiny-calico-master-88-1.k8s.tcinternal NotReady control-plane,master 20m v1.23.6 tiny-calico-worker-88-11.k8s.tcinternal NotReady \u0026lt;none\u0026gt; 105s v1.23.6 tiny-calico-worker-88-12.k8s.tcinternal NotReady \u0026lt;none\u0026gt; 35s v1.23.6 安装CNI 编写manifest文件 calico的安装也比较简单，官方提供了多种安装方式，我们这里使用yaml（自定义manifests）进行安装，并且使用etcd作为datastore。\n# 我们先把官方的yaml模板下载下来，然后对关键字段逐个修改 curl https://projectcalico.docs.tigera.io/manifests/calico-etcd.yaml -O 针对calico-etcd.yaml文件，我们需要修改一些参数以适配我们的集群：\nCALICO_IPV4POOL_CIDR参数，配置的是pod的网段，这里我们使用此前计划好的10.88.64.0/18；CALICO_IPV4POOL_BLOCK_SIZE参数，配置的是分配子网的大小，默认是26 # The default IPv4 pool to create on startup if none exists. Pod IPs will be # chosen from this range. Changing this value after installation will have # no effect. This should fall within `--cluster-cidr`. - name: CALICO_IPV4POOL_CIDR value: \u0026#34;10.88.64.0/18\u0026#34; - name: CALICO_IPV4POOL_BLOCK_SIZE value: \u0026#34;26\u0026#34; CALICO_IPV4POOL_IPIP参数，控制是否启用ip-ip模式，默认情况下是Always，由于我们的节点都在同一个二层网络，这里修改为Never或者是CrossSubnet都可以。\n其中Never表示不启用ip-ip模式，而CrossSubnet则表示仅当跨子网的时候才启用ip-ip模式\n# Enable IPIP - name: CALICO_IPV4POOL_IPIP value: \u0026#34;Never\u0026#34; ConfigMap里面的etcd_endpoints变量配置etcd的连接端口和地址，为了安全我们这里开启TLS认证，当然如果不想配置证书的，也可以不使用TLS，然后这三个字段直接留空不做修改 kind: ConfigMap apiVersion: v1 metadata: name: calico-config namespace: kube-system data: # Configure this with the location of your etcd cluster. # etcd_endpoints: \u0026#34;http://\u0026lt;ETCD_IP\u0026gt;:\u0026lt;ETCD_PORT\u0026gt;\u0026#34; # If you\u0026#39;re using TLS enabled etcd uncomment the following. # You must also populate the Secret below with these files. # etcd_ca: \u0026#34;\u0026#34; # \u0026#34;/calico-secrets/etcd-ca\u0026#34; # etcd_cert: \u0026#34;\u0026#34; # \u0026#34;/calico-secrets/etcd-cert\u0026#34; # etcd_key: \u0026#34;\u0026#34; # \u0026#34;/calico-secrets/etcd-key\u0026#34; etcd_endpoints: \u0026#34;https://10.31.88.1:2379\u0026#34; etcd_ca: \u0026#34;/etc/kubernetes/pki/etcd/ca.crt\u0026#34; etcd_cert: \u0026#34;/etc/kubernetes/pki/etcd/server.crt\u0026#34; etcd_key: \u0026#34;/etc/kubernetes/pki/etcd/server.key\u0026#34; Secret里面的 name: calico-etcd-secrets下面的data字段，需要把上面的三个证书内容使用该命令cat \u0026lt;file\u0026gt; | base64 -w 0转成base64编码格式 --- # Source: calico/templates/calico-etcd-secrets.yaml # The following contains k8s Secrets for use with a TLS enabled etcd cluster. # For information on populating Secrets, see http://kubernetes.io/docs/user-guide/secrets/ apiVersion: v1 kind: Secret type: Opaque metadata: name: calico-etcd-secrets namespace: kube-system data: # Populate the following with etcd TLS configuration if desired, but leave blank if # not using TLS for etcd. # The keys below should be uncommented and the values populated with the base64 # encoded contents of each file that would be associated with the TLS data. # Example command for encoding a file contents: cat \u0026lt;file\u0026gt; | base64 -w 0 etcd-key: LS0tLS1CRUdJTi......tLS0tCg== etcd-cert: LS0tLS1CRUdJT......tLS0tLQo= etcd-ca: LS0tLS1CRUdJTiB......FLS0tLS0K 部署calico 修改完成之后我们直接部署即可\n$ kubectl apply -f calico-etcd.yaml secret/calico-etcd-secrets created configmap/calico-config created clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created clusterrole.rbac.authorization.k8s.io/calico-node created clusterrolebinding.rbac.authorization.k8s.io/calico-node created daemonset.apps/calico-node created serviceaccount/calico-node created deployment.apps/calico-kube-controllers created serviceaccount/calico-kube-controllers created Warning: policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget poddisruptionbudget.policy/calico-kube-controllers created # 查看pod是否正常运行 $ kubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system calico-kube-controllers-5c4bd49f9b-6b2gr 1/1 Running 5 (3m18s ago) 6m18s kube-system calico-node-bgsfs 1/1 Running 5 (2m55s ago) 6m18s kube-system calico-node-tr88g 1/1 Running 5 (3m19s ago) 6m18s kube-system calico-node-w59pc 1/1 Running 5 (2m36s ago) 6m18s kube-system coredns-6d8c4cb4d-r8r9q 1/1 Running 0 3h8m kube-system coredns-6d8c4cb4d-ztq6w 1/1 Running 0 3h8m kube-system etcd-tiny-calico-master-88-1.k8s.tcinternal 1/1 Running 0 3h8m kube-system kube-apiserver-tiny-calico-master-88-1.k8s.tcinternal 1/1 Running 0 3h8m kube-system kube-controller-manager-tiny-calico-master-88-1.k8s.tcinternal 1/1 Running 0 3h8m kube-system kube-proxy-n65sb 1/1 Running 0 169m kube-system kube-proxy-qmxhp 1/1 Running 0 168m kube-system kube-proxy-v6cg9 1/1 Running 0 3h8m kube-system kube-scheduler-tiny-calico-master-88-1.k8s.tcinternal 1/1 Running 0 3h8m # 查看calico-kube-controllers的pod日志是否有报错 $ kubectl logs -f calico-kube-controllers-5c4bd49f9b-6b2gr -n kube-system pod安装calicoctl calicoctl是用来查看管理calico的命令行工具，定位上有点类似于calico版本的kubectl，因为我们前面使用了etcd作为calico的datastore，这里直接选择在k8s集群中以pod的形式部署calicoctl的方式更加简单。\ncalicoctl的版本最好和部署的calico一致，这里均为v3.22.2 calicoctl的etcd配置最好和部署的calico一致，因为前面部署calico的时候etcd开启了TLS，因此这里我们也要修改yaml文件开启TLS # 为了方便后期管理，我们先把calicoctl.yaml下载到本地再进行部署 $ wget https://projectcalico.docs.tigera.io/manifests/calicoctl-etcd.yaml $ cat calicoctl-etcd.yaml # Calico Version v3.22.2 # https://projectcalico.docs.tigera.io/releases#v3.22.2 # This manifest includes the following component versions: # calico/ctl:v3.22.2 apiVersion: v1 kind: Pod metadata: name: calicoctl namespace: kube-system spec: nodeSelector: kubernetes.io/os: linux hostNetwork: true containers: - name: calicoctl image: calico/ctl:v3.22.2 command: - /calicoctl args: - version - --poll=1m env: - name: ETCD_ENDPOINTS valueFrom: configMapKeyRef: name: calico-config key: etcd_endpoints # If you\u0026#39;re using TLS enabled etcd uncomment the following. # Location of the CA certificate for etcd. - name: ETCD_CA_CERT_FILE valueFrom: configMapKeyRef: name: calico-config key: etcd_ca # Location of the client key for etcd. - name: ETCD_KEY_FILE valueFrom: configMapKeyRef: name: calico-config key: etcd_key # Location of the client certificate for etcd. - name: ETCD_CERT_FILE valueFrom: configMapKeyRef: name: calico-config key: etcd_cert volumeMounts: - mountPath: /calico-secrets name: etcd-certs volumes: # If you\u0026#39;re using TLS enabled etcd uncomment the following. - name: etcd-certs secret: secretName: calico-etcd-secrets 修改完成之后我们直接部署即可使用\n$ kubectl apply -f calicoctl-etcd.yaml pod/calicoctl created # 创建完成后我们查看calicoctl的运行状态 $ kubectl get pods -A | grep calicoctl kube-system calicoctl 1/1 Running 0 9s # 检验一下是否能够正常工作 $ kubectl exec -ti -n kube-system calicoctl -- /calicoctl get nodes NAME tiny-calico-master-88-1.k8s.tcinternal tiny-calico-worker-88-11.k8s.tcinternal tiny-calico-worker-88-12.k8s.tcinternal $ kubectl exec -ti -n kube-system calicoctl -- /calicoctl get profiles -o wide NAME LABELS projectcalico-default-allow kns.default pcns.kubernetes.io/metadata.name=default,pcns.projectcalico.org/name=default kns.kube-node-lease pcns.kubernetes.io/metadata.name=kube-node-lease,pcns.projectcalico.org/name=kube-node-lease kns.kube-public pcns.kubernetes.io/metadata.name=kube-public,pcns.projectcalico.org/name=kube-public kns.kube-system pcns.kubernetes.io/metadata.name=kube-system,pcns.projectcalico.org/name=kube-system ...此处略去一堆输出... # 查看ipam的分配情况 $ calicoctl ipam show +----------+---------------+-----------+------------+--------------+ | GROUPING | CIDR | IPS TOTAL | IPS IN USE | IPS FREE | +----------+---------------+-----------+------------+--------------+ | IP Pool | 10.88.64.0/18 | 16384 | 2 (0%) | 16382 (100%) | +----------+---------------+-----------+------------+--------------+ # 为了方便可以在bashrc中设置alias cat \u0026gt;\u0026gt; ~/.bashrc \u0026lt;\u0026lt;EOF alias calicoctl=\u0026#34;kubectl exec -i -n kube-system calicoctl -- /calicoctl\u0026#34; EOF 完整版本calicoctl命令可以参考官方文档。\nbinary安装calicoctl 使用pod方式部署calicoctl虽然简单，但是有个问题就是无法使用calicoctl node命令，这个命令需要访问部分宿主机的文件系统。因此这里我们再二进制部署一个calicoctl。\nNote that if you run calicoctl in a container, calicoctl node ... commands will not work (they need access to parts of the host filesystem).\n# 直接下线二进制文件即可使用 $ cd /usr/local/bin/ $ curl -L https://github.com/projectcalico/calico/releases/download/v3.22.2/calicoctl-linux-amd64 -o calicoctl $ chmod +x ./calicoctl 二进制的calicoctl会优先读取配置文件，当找不到配置文件的时候才会去读取环境变量，这里我们直接配置/etc/calico/calicoctl.cfg，注意etcd的证书直接和前面部署calico时使用的证书文件一致即可。\n# 配置calicoctl的配置文件 $ mkdir /etc/calico $ cat /etc/calico/calicoctl.cfg apiVersion: projectcalico.org/v3 kind: CalicoAPIConfig metadata: spec: datastoreType: etcdv3 etcdEndpoints: \u0026#34;https://10.31.88.1:2379\u0026#34; etcdCACert: | -----BEGIN CERTIFICATE----- MIIC9TCCAd2gAwIBAgIBADANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdldGNk LWNhMB4XDTIyMDUwNjA1MTg1OVoXDTMyMDUwMzA1MTg1OVowEjEQMA4GA1UEAxMH ZXRjZC1jYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANFFqq4Mk3DE 6UW581xnZPFrHqQWlGr/KptEywKH56Bp24OAnDIAkSz7KAMrJzL+OiVsj9YJV59F 9qH/YzU+bppctDnfk1yCuavkcXgLSd9O6EBhM2LkGtF9AdWMnFw9ui2jNhFC/QXj zCvq0I1c9o9gulbFmSHwIw2GLQd7ogO+PpfLsubRscJdKkCUWVFV0mb8opccmXoF vXynRX0VW3wpN+v66bD+HTdMSNK1JljfBngh9LAkibjUx7bMrHvu/GOalNCSWrtG lss/hhWkzwV7Y7AIXgvxxcmDdfswe5lUYLvW2CP4e+tXfB3i2wg10fErc8z63lix v9BWkIIalScCAwEAAaNWMFQwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB/wQFMAMB Af8wHQYDVR0OBBYEFH49PpnJYxze8aq0PVwgpY4Fo6djMBIGA1UdEQQLMAmCB2V0 Y2QtY2EwDQYJKoZIhvcNAQELBQADggEBAAGL6KwN80YEK6gZcL+7RI9bkMKk7UWW V48154CgN8w9GKvNTm4l0tZKvsWCnR61hiJtLQcG0S8HYHAvL1DBjOXw11bNilLy vaVM+wqOOIxPsXLU//F46z3V9z1uV0v/yLLlg320c0wtG+OLZZIn8O+yUhtOHM09 K0JSAF2/KhtNxhrc0owCTOzS+DKsb0w1SzQmS0t/tflyLfc3oJZ/2V4Tqd72j7iI cDBa36lGqtUBf8MXu+Xza0cdhy/f19AqkeM2fe+/DrbzR4zDVmZ7l4dqYGLbKHYo XaLn8bSToYQq4dlA/oAlyyH0ekB5v0DyYiHwlqgZgiu4qcR3Gw8azVk= -----END CERTIFICATE----- etcdCert: | -----BEGIN CERTIFICATE----- MIIDgzCCAmugAwIBAgIIePiBSOdMGwcwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE AxMHZXRjZC1jYTAeFw0yMjA1MDYwNTE4NTlaFw0yMzA1MDYwNTE4NTlaMDExLzAt BgNVBAMTJnRpbnktY2FsaWNvLW1hc3Rlci04OC0xLms4cy50Y2ludGVybmFsMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqZM/jBrdXLR3ctee7LVJhGSA 4usg/JQXGyOAd52OkkOLYwn3fvwqeo0Z0cX0q4mqaF0cnrPYc4eExX/3fJpF3Fxy D6vdpEZ/FrnzCAkibEYtK/UVhTKuV7n/VdbjFPGl8CpppuGVs6o+4NFZxffW7em0 8m/FK/7SDkV2qXCyG94kOaUCeDEgdBKE3cPCZQ4maFuwXi08bYs2CiTfbfa4dsT5 3yzaoQVX9BaBqE9IGmsHDFuxp1X8gkJXs+7wwHQX39o1oXmci6T4IVxVHA5GRbTv pCDG5Wye7QqKgnxO1KRF42FKs1Nif7UJ0iR35Ydpa7cat7Fr0M7l+rZLCDTJgwID AQABo4G9MIG6MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI KwYBBQUHAwIwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBR+PT6ZyWMc3vGqtD1c IKWOBaOnYzBaBgNVHREEUzBRgglsb2NhbGhvc3SCJnRpbnktY2FsaWNvLW1hc3Rl ci04OC0xLms4cy50Y2ludGVybmFshwQKH1gBhwR/AAABhxAAAAAAAAAAAAAAAAAA AAABMA0GCSqGSIb3DQEBCwUAA4IBAQC+pyH14/+US5Svz04Vi8QIduY/DVx1HOQq hfrIZKOZCH2iKU7fZ4o9QpQZh7D9B8hgpXM6dNuFpd98c0MVPr+LesShu4BHVjHl gPvUWEVB2XD5x51HqnMV2OkhMKooyAUIzI0P0YKN29SFEyJGD1XDu4UtqvBADqf7 COvAuqj4VbRgF/iQwNstjqZ47rSzvyp6rIwqFoHRP+Zi+8KL1qmozGjI3+H+TZFM Gv3b5DRx2pmfY+kGVLO5bjl3zxylRPjCDHaRlQUWiOYSWS8OHYRCBZuSLvW4tht0 JjWjUAh4hF8+3lyNrfx8moz7tfm5SG2q01pO1vjkhrhxhINAwaac -----END CERTIFICATE----- etcdKey: | -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAqZM/jBrdXLR3ctee7LVJhGSA4usg/JQXGyOAd52OkkOLYwn3 fvwqeo0Z0cX0q4mqaF0cnrPYc4eExX/3fJpF3FxyD6vdpEZ/FrnzCAkibEYtK/UV hTKuV7n/VdbjFPGl8CpppuGVs6o+4NFZxffW7em08m/FK/7SDkV2qXCyG94kOaUC eDEgdBKE3cPCZQ4maFuwXi08bYs2CiTfbfa4dsT53yzaoQVX9BaBqE9IGmsHDFux p1X8gkJXs+7wwHQX39o1oXmci6T4IVxVHA5GRbTvpCDG5Wye7QqKgnxO1KRF42FK s1Nif7UJ0iR35Ydpa7cat7Fr0M7l+rZLCDTJgwIDAQABAoIBAE1gMw7q8zbp4dc1 K/82eWU/ts/UGikmKaTofiYWboeu6ls2oQgAaCGjYLSnbw0Ws/sLAZQo3AtbOuoj ifoBKv9x71nXQjtDL5pfHtX71QkyvEniev9cMNE2vZudgeB8owsDT1ImfPiOJkLP Q/dhL2E/0qEM/xskGxUH/S0zjxHHfPZZsYODhkVPWc6Z+XEDll48fRCFn4/48FTN 9GbRvo7dv34EHmNYA20K4DMHbZUdrPqSZpKWzAPJXnDlgZbpvUeAYOJxqZHQtCm1 zbSOyM1Ql6K0Ayro0L5GAzap+0yGuk79OWiPnEsdPneVsATKG7dT7RZIL/INrOqQ 0wjUmQECgYEA02OHdT1K5Au6wtiTqKD99WweltnvFd4C/Z3dobEj8M8qN6uiKCca PievWahnxAlJEah3RiOgtarwA+0E/Jgsw99Qutp5BR/XdD3llTNczkPkg/RkWpve 2f/4DlZQrxuIem7UNLl+5BacfmF691DQQoX2RoIkvQxYJGTUNXvrSUkCgYEAzVyz mvN+dvSwzAlm0gkfVP5Ez3DFESUrWd0FR2v1HR6qHQy/dkgkkic6zRGCJtGeT5V7 N0kbVSHsz+wi6aQkFy0Sp0TbgZzjPhSwNtk+2JsBRvMp0CYczgrfyvWuAQ3gbXGc N8IkcZSSOv8TuigCnnYf2Xaz8LM50AivScnb6GsCgYEAyq4ScgnLpa3NawbnRPbf qRH6nl7lC01sBqn3mBHVSQ4JB4msF92uHsxEJ639mAvjIGgrvHdqnuT/7nOypVJv EXsr14ykHpKyLQUv/Idbw3V7RD3ufqYW3WS8/VorUEoQ6HsdQlRc4ur/L3ndwgWd OTtir6YW/aA5XuPCSGnBZekCgYB6VtlgW+Jg91BDnO41/d0+guN3ONUNa7kxpau5 aqTxHg11lNySmFPBBcHP3LhOa94FxyVKQDEaPEWZcDE0QuaFMALGxwyFYHM3zpdT dYQtAdp26/Fi4PGUBYJgpI9ubVffmyjXRr7zMvESWFbmNWOqBvDeWgrEP+EW/7V9 HdX11QKBgE1czchlibgQ/bhAl8BatKRr1X/UHvblWhmyApudOfFeGOILR6u/lWvY SS+Rg0y8nnZ4hTRSXbd/sSEsUJcSmoBc1TivWzl32eVuqe9CcrUZY0JSLtoj1KiP adRcCZtVDETXbW326Hvgz+MnqrIgzx+Zgy4tNtoAAbTv0q83j45I -----END RSA PRIVATE KEY----- 配置完成之后我们检查一下效果\n$ calicoctl node status Calico process is running. IPv4 BGP status +--------------+-------------------+-------+----------+-------------+ | PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO | +--------------+-------------------+-------+----------+-------------+ | 10.31.88.11 | node-to-node mesh | up | 08:26:30 | Established | | 10.31.88.12 | node-to-node mesh | up | 08:26:30 | Established | +--------------+-------------------+-------+----------+-------------+ IPv6 BGP status No IPv6 peers found. $ calicoctl get nodes NAME tiny-calico-master-88-1.k8s.tcinternal tiny-calico-worker-88-11.k8s.tcinternal tiny-calico-worker-88-12.k8s.tcinternal $ calicoctl ipam show +----------+---------------+-----------+------------+--------------+ | GROUPING | CIDR | IPS TOTAL | IPS IN USE | IPS FREE | +----------+---------------+-----------+------------+--------------+ | IP Pool | 10.88.64.0/18 | 16384 | 2 (0%) | 16382 (100%) | +----------+---------------+-----------+------------+--------------+ 部署测试用例 集群部署完成之后我们在k8s集群中部署一个nginx测试一下是否能够正常工作。首先我们创建一个名为nginx-quic的命名空间（namespace），然后在这个命名空间内创建一个名为nginx-quic-deployment的deployment用来部署pod，最后再创建一个service用来暴露服务，这里我们先使用nodeport的方式暴露端口方便测试。\n$ cat nginx-quic.yaml apiVersion: v1 kind: Namespace metadata: name: nginx-quic --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-quic-deployment namespace: nginx-quic spec: selector: matchLabels: app: nginx-quic replicas: 4 template: metadata: labels: app: nginx-quic spec: containers: - name: nginx-quic image: tinychen777/nginx-quic:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-quic-service namespace: nginx-quic spec: selector: app: nginx-quic ports: - protocol: TCP port: 8080 # match for service access port targetPort: 80 # match for pod access port nodePort: 30088 # match for external access port type: NodePort 部署完成后我们直接查看状态\n# 直接部署 $ kubectl apply -f nginx-quic.yaml namespace/nginx-quic created deployment.apps/nginx-quic-deployment created service/nginx-quic-service created # 查看deployment的运行状态 $ kubectl get deployment -o wide -n nginx-quic NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR nginx-quic-deployment 4/4 4 4 55s nginx-quic tinychen777/nginx-quic:latest app=nginx-quic # 查看service的运行状态 $ kubectl get service -o wide -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR nginx-quic-service NodePort 10.88.52.168 \u0026lt;none\u0026gt; 8080:30088/TCP 66s app=nginx-quic # 查看pod的运行状态 $ kubectl get pods -o wide -n nginx-quic NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-quic-deployment-7457f4d579-24q9z 1/1 Running 0 75s 10.88.120.72 tiny-calico-worker-88-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-7457f4d579-4svv9 1/1 Running 0 75s 10.88.84.68 tiny-calico-worker-88-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-7457f4d579-btrjj 1/1 Running 0 75s 10.88.120.71 tiny-calico-worker-88-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-7457f4d579-lvh6x 1/1 Running 0 75s 10.88.84.69 tiny-calico-worker-88-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # 查看IPVS规则 $ ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 172.17.0.1:30088 rr -\u0026gt; 10.88.84.68:80 Masq 1 0 0 -\u0026gt; 10.88.84.69:80 Masq 1 0 0 -\u0026gt; 10.88.120.71:80 Masq 1 0 0 -\u0026gt; 10.88.120.72:80 Masq 1 0 0 TCP 10.31.88.1:30088 rr -\u0026gt; 10.88.84.68:80 Masq 1 0 0 -\u0026gt; 10.88.84.69:80 Masq 1 0 0 -\u0026gt; 10.88.120.71:80 Masq 1 0 0 -\u0026gt; 10.88.120.72:80 Masq 1 0 0 TCP 10.88.52.168:8080 rr -\u0026gt; 10.88.84.68:80 Masq 1 0 0 -\u0026gt; 10.88.84.69:80 Masq 1 0 0 -\u0026gt; 10.88.120.71:80 Masq 1 0 0 -\u0026gt; 10.88.120.72:80 Masq 1 0 0 最后我们进行测试，这个nginx-quic的镜像默认情况下会返回在nginx容器中获得的用户请求的IP和端口\n# 首先我们在集群内进行测试 # 直接访问pod $ curl 10.88.84.68:80 10.31.88.1:34612 # 直接访问service的ClusterIP，这时请求会被转发到pod中 $ curl 10.88.52.168:8080 10.31.88.1:58978 # 直接访问nodeport，这时请求会被转发到pod中，不会经过ClusterIP $ curl 10.31.88.1:30088 10.31.88.1:56595 # 接着我们在集群外进行测试 # 直接访问三个节点的nodeport，这时请求会被转发到pod中，不会经过ClusterIP # 由于externalTrafficPolicy默认为Cluster，因此nginx拿到的IP就是我们访问的节点的IP，而非客户端IP $ curl 10.31.88.1:30088 10.31.88.1:27851 $ curl 10.31.88.11:30088 10.31.88.11:16540 $ curl 10.31.88.12:30088 10.31.88.12:5767 ","permalink":"http://localhost:9011/20220508-k8s-03-deploy-k8s-with-calico/","summary":"\u003cp\u003e本文主要在centos7系统上基于\u003ccode\u003edocker\u003c/code\u003e和\u003ccode\u003ecalico\u003c/code\u003e组件部署\u003ccode\u003ev1.23.6\u003c/code\u003e版本的k8s原生集群，由于集群主要用于自己平时学习和测试使用，加上资源有限，暂不涉及高可用部署。\u003c/p\u003e\n\u003cp\u003e此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列03-kubeadm部署calico网络的k8s集群"},{"content":"本文主要在centos7系统上基于docker和flannel组件部署v1.23.6版本的k8s原生集群，由于集群主要用于自己平时学习和测试使用，加上资源有限，暂不涉及高可用部署。\n此前写的一些关于k8s基础知识和集群搭建的一些方案，有需要的同学可以看一下。\n准备工作 flannel-集群节点信息 机器均为8C8G的虚拟机，硬盘为100G。\nIP Hostname 10.31.8.1 tiny-flannel-master-8-1.k8s.tcinternal 10.31.8.11 tiny-flannel-worker-8-11.k8s.tcinternal 10.31.8.12 tiny-flannel-worker-8-12.k8s.tcinternal 10.8.64.0/18 podSubnet 10.8.0.0/18 serviceSubnet 检查mac和product_uuid 同一个k8s集群内的所有节点需要确保mac地址和product_uuid均唯一，开始集群初始化之前需要检查相关信息\n# 检查mac地址 ip link ifconfig -a # 检查product_uuid sudo cat /sys/class/dmi/id/product_uuid 配置ssh免密登录（可选） 如果k8s集群的节点有多个网卡，确保每个节点能通过正确的网卡互联访问\n# 在root用户下面生成一个公用的key，并配置可以使用该key免密登录 su root ssh-keygen cd /root/.ssh/ cat id_rsa.pub \u0026gt;\u0026gt; authorized_keys chmod 600 authorized_keys cat \u0026gt;\u0026gt; ~/.ssh/config \u0026lt;\u0026lt;EOF Host tiny-flannel-master-8-1.k8s.tcinternal HostName 10.31.8.1 User root Port 22 IdentityFile ~/.ssh/id_rsa Host tiny-flannel-worker-8-11.k8s.tcinternal HostName 10.31.8.11 User root Port 22 IdentityFile ~/.ssh/id_rsa Host tiny-flannel-worker-8-12.k8s.tcinternal HostName 10.31.8.12 User root Port 22 IdentityFile ~/.ssh/id_rsa EOF 修改hosts文件 cat \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt;EOF 10.31.8.1 tiny-flannel-master-8-1 tiny-flannel-master-8-1.k8s.tcinternal 10.31.8.11 tiny-flannel-worker-8-11 tiny-flannel-worker-8-11.k8s.tcinternal 10.31.8.12 tiny-flannel-worker-8-12 tiny-flannel-worker-8-12.k8s.tcinternal EOF 关闭swap内存 # 使用命令直接关闭swap内存 swapoff -a # 修改fstab文件禁止开机自动挂载swap分区 sed -i \u0026#39;/swap / s/^\\(.*\\)$/#\\1/g\u0026#39; /etc/fstab 配置时间同步 这里可以根据自己的习惯选择ntp或者是chrony同步均可，同步的时间源服务器可以选择阿里云的ntp1.aliyun.com或者是国家时间中心的ntp.ntsc.ac.cn。\n使用ntp同步 # 使用yum安装ntpdate工具 yum install ntpdate -y # 使用国家时间中心的源同步时间 ntpdate ntp.ntsc.ac.cn # 最后查看一下时间 hwclock 使用chrony同步 # 使用yum安装chrony yum install chrony -y # 设置开机启动并开启chony并查看运行状态 systemctl enable chronyd.service systemctl start chronyd.service systemctl status chronyd.service # 当然也可以自定义时间服务器 vim /etc/chrony.conf # 修改前 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server 0.centos.pool.ntp.org iburst server 1.centos.pool.ntp.org iburst server 2.centos.pool.ntp.org iburst server 3.centos.pool.ntp.org iburst # 修改后 $ grep server /etc/chrony.conf # Use public servers from the pool.ntp.org project. server ntp.ntsc.ac.cn iburst # 重启服务使配置文件生效 systemctl restart chronyd.service # 查看chrony的ntp服务器状态 chronyc sourcestats -v chronyc sources -v 关闭selinux # 使用命令直接关闭 setenforce 0 # 也可以直接修改/etc/selinux/config文件 sed -i \u0026#39;s/^SELINUX=enforcing$/SELINUX=disabled/\u0026#39; /etc/selinux/config 配置防火墙 k8s集群之间通信和服务暴露需要使用较多端口，为了方便，直接禁用防火墙\n# centos7使用systemctl禁用默认的firewalld服务 systemctl disable firewalld.service 配置netfilter参数 这里主要是需要配置内核加载br_netfilter和iptables放行ipv6和ipv4的流量，确保集群内的容器能够正常通信。\ncat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter EOF cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sudo sysctl --system 关闭IPV6（可选） 虽然新版本的k8s已经支持双栈网络，但是本次的集群部署过程并不涉及IPv6网络的通信，因此关闭IPv6网络支持\n# 直接在内核中添加ipv6禁用参数 grubby --update-kernel=ALL --args=ipv6.disable=1 配置IPVS（可选） IPVS是专门设计用来应对负载均衡场景的组件，kube-proxy 中的 IPVS 实现通过减少对 iptables 的使用来增加可扩展性。在 iptables 输入链中不使用 PREROUTING，而是创建一个假的接口，叫做 kube-ipvs0，当k8s集群中的负载均衡配置变多的时候，IPVS能实现比iptables更高效的转发性能。\n注意在4.19之后的内核版本中使用nf_conntrack模块来替换了原有的nf_conntrack_ipv4模块\n(Notes: use nf_conntrack instead of nf_conntrack_ipv4 for Linux kernel 4.19 and later)\n# 在使用ipvs模式之前确保安装了ipset和ipvsadm sudo yum install ipset ipvsadm -y # 手动加载ipvs相关模块 modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack_ipv4 # 配置开机自动加载ipvs相关模块 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/ipvs.conf ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack_ipv4 EOF sudo sysctl --system # 最好重启一遍系统确定是否生效 $ lsmod | grep -e ip_vs -e nf_conntrack_ipv4 ip_vs_sh 12688 0 ip_vs_wrr 12697 0 ip_vs_rr 12600 0 ip_vs 145458 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr nf_conntrack_ipv4 15053 2 nf_defrag_ipv4 12729 1 nf_conntrack_ipv4 nf_conntrack 139264 7 ip_vs,nf_nat,nf_nat_ipv4,xt_conntrack,nf_nat_masquerade_ipv4,nf_conntrack_netlink,nf_conntrack_ipv4 libcrc32c 12644 4 xfs,ip_vs,nf_nat,nf_conntrack $ cut -f1 -d \u0026#34; \u0026#34; /proc/modules | grep -e ip_vs -e nf_conntrack_ipv4 ip_vs_sh ip_vs_wrr ip_vs_rr ip_vs nf_conntrack_ipv4 安装container runtime 安装docker 详细的官方文档可以参考这里，由于在刚发布的1.24版本中移除了docker-shim，因此安装的版本≥1.24的时候需要注意容器运行时的选择。这里我们安装的版本低于1.24，因此我们继续使用docker。\ndocker的具体安装可以参考我之前写的这篇文章，这里不做赘述。\n# 安装必要的依赖组件并且导入docker官方提供的yum源 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 我们直接安装最新版本的docker yum install docker-ce docker-ce-cli containerd.io 配置cgroup drivers CentOS7使用的是systemd来初始化系统并管理进程，初始化进程会生成并使用一个 root 控制组 (cgroup), 并充当 cgroup 管理器。 Systemd 与 cgroup 集成紧密，并将为每个 systemd 单元分配一个 cgroup。 我们也可以配置容器运行时和 kubelet 使用 cgroupfs。 连同 systemd 一起使用 cgroupfs 意味着将有两个不同的 cgroup 管理器。而当一个系统中同时存在cgroupfs和systemd两者时，容易变得不稳定，因此最好更改设置，令容器运行时和 kubelet 使用 systemd 作为 cgroup 驱动，以此使系统更为稳定。 对于 Docker, 需要设置 native.cgroupdriver=systemd 参数。\n参考官方的说明文档：\nhttps://kubernetes.io/docs/setup/production-environment/container-runtimes/#cgroup-drivers\n参考配置说明文档\nhttps://kubernetes.io/zh/docs/setup/production-environment/container-runtimes/#docker\nsudo mkdir /etc/docker cat \u0026lt;\u0026lt;EOF | sudo tee /etc/docker/daemon.json { \u0026#34;exec-opts\u0026#34;: [\u0026#34;native.cgroupdriver=systemd\u0026#34;], \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34; }, \u0026#34;storage-driver\u0026#34;: \u0026#34;overlay2\u0026#34; } EOF sudo systemctl enable docker sudo systemctl daemon-reload sudo systemctl restart docker # 最后检查一下Cgroup Driver是否为systemd $ docker info | grep systemd Cgroup Driver: systemd 关于kubelet的cgroup driver k8s官方有详细的文档介绍了如何设置kubelet的cgroup driver，需要特别注意的是，在1.22版本开始，如果没有手动设置kubelet的cgroup driver，那么默认会设置为systemd\nNote: In v1.22, if the user is not setting the cgroupDriver field under KubeletConfiguration, kubeadm will default it to systemd.\n一个比较简单的指定kubelet的cgroup driver的方法就是在kubeadm-config.yaml加入cgroupDriver字段\n# kubeadm-config.yaml kind: ClusterConfiguration apiVersion: kubeadm.k8s.io/v1beta3 kubernetesVersion: v1.21.0 --- kind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 cgroupDriver: systemd 我们可以直接查看configmaps来查看初始化之后集群的kubeadm-config配置。\n$ kubectl describe configmaps kubeadm-config -n kube-system Name: kubeadm-config Namespace: kube-system Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; Data ==== ClusterConfiguration: ---- apiServer: extraArgs: authorization-mode: Node,RBAC timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: v1.23.6 networking: dnsDomain: cali-cluster.tclocal serviceSubnet: 10.88.0.0/18 scheduler: {} BinaryData ==== Events: \u0026lt;none\u0026gt; 当然因为我们需要安装的版本高于1.22.0并且使用的就是systemd，因此可以不用再重复配置。\n安装kube三件套 对应的官方文档可以参考这里\nhttps://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl\nkube三件套就是kubeadm、kubelet 和 kubectl，三者的具体功能和作用如下：\nkubeadm：用来初始化集群的指令。 kubelet：在集群中的每个节点上用来启动 Pod 和容器等。 kubectl：用来与集群通信的命令行工具。 需要注意的是：\nkubeadm不会帮助我们管理kubelet和kubectl，其他两者也是一样的，也就是说这三者是相互独立的，并不存在谁管理谁的情况； kubelet的版本必须小于等于API-server的版本，否则容易出现兼容性的问题； kubectl并不是集群中的每个节点都需要安装，也并不是一定要安装在集群中的节点，可以单独安装在自己本地的机器环境上面，然后配合kubeconfig文件即可使用kubectl命令来远程管理对应的k8s集群； CentOS7的安装比较简单，我们直接使用官方提供的yum源即可。需要注意的是这里需要设置selinux的状态，但是前面我们已经关闭了selinux，因此这里略过这步。\n# 直接导入谷歌官方的yum源 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\\$basearch enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg exclude=kubelet kubeadm kubectl EOF # 当然如果连不上谷歌的源，可以考虑使用国内的阿里镜像源 cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF # 接下来直接安装三件套即可 sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes # 如果网络环境不好出现gpgcheck验证失败导致无法正常读取yum源，可以考虑关闭该yum源的repo_gpgcheck sed -i \u0026#39;s/repo_gpgcheck=1/repo_gpgcheck=0/g\u0026#39; /etc/yum.repos.d/kubernetes.repo # 或者在安装的时候禁用gpgcheck sudo yum install -y kubelet kubeadm kubectl --nogpgcheck --disableexcludes=kubernetes # 如果想要安装特定版本，可以使用这个命令查看相关版本的信息 sudo yum list --nogpgcheck kubelet kubeadm kubectl --showduplicates --disableexcludes=kubernetes # 这里我们为了保留使用docker-shim，因此我们按照1.24.0版本的前一个版本1.23.6 sudo yum install -y kubelet-1.23.6-0 kubeadm-1.23.6-0 kubectl-1.23.6-0 --nogpgcheck --disableexcludes=kubernetes # 安装完成后配置开机自启kubelet sudo systemctl enable --now kubelet 初始化集群 编写配置文件 在集群中所有节点都执行完上面的三点操作之后，我们就可以开始创建k8s集群了。因为我们这次不涉及高可用部署，因此初始化的时候直接在我们的目标master节点上面操作即可。\n# 我们先使用kubeadm命令查看一下主要的几个镜像版本 # 因为我们此前指定安装了旧的1.23.6版本，这里的apiserver镜像版本也会随之回滚 $ kubeadm config images list I0507 14:14:34.992275 20038 version.go:255] remote version is much newer: v1.24.0; falling back to: stable-1.23 k8s.gcr.io/kube-apiserver:v1.23.6 k8s.gcr.io/kube-controller-manager:v1.23.6 k8s.gcr.io/kube-scheduler:v1.23.6 k8s.gcr.io/kube-proxy:v1.23.6 k8s.gcr.io/pause:3.6 k8s.gcr.io/etcd:3.5.1-0 k8s.gcr.io/coredns/coredns:v1.8.6 # 为了方便编辑和管理，我们还是把初始化参数导出成配置文件 $ kubeadm config print init-defaults \u0026gt; kubeadm-flannel.conf 考虑到大多数情况下国内的网络无法使用谷歌的k8s.gcr.io镜像源，我们可以直接在配置文件中修改imageRepository参数为阿里的镜像源 kubernetesVersion字段用来指定我们要安装的k8s版本 localAPIEndpoint参数需要修改为我们的master节点的IP和端口，初始化之后的k8s集群的apiserver地址就是这个 serviceSubnet和dnsDomain两个参数默认情况下可以不用修改，这里我按照自己的需求进行了变更 nodeRegistration里面的name参数修改为对应master节点的hostname 新增配置块使用ipvs，具体可以参考官方文档 apiVersion: kubeadm.k8s.io/v1beta3 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 10.31.8.1 bindPort: 6443 nodeRegistration: criSocket: /var/run/dockershim.sock imagePullPolicy: IfNotPresent name: tiny-flannel-master-8-1.k8s.tcinternal taints: null --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: 1.23.6 networking: dnsDomain: flan-cluster.tclocal serviceSubnet: 10.8.0.0/18 podSubnet: 10.8.64.0/18 scheduler: {} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs 初始化集群 此时我们再查看对应的配置文件中的镜像版本，就会发现已经变成了对应阿里云镜像源的版本\n# 查看一下对应的镜像版本，确定配置文件是否生效 $ kubeadm config images list --config kubeadm-flannel.conf registry.aliyuncs.com/google_containers/kube-apiserver:v1.23.6 registry.aliyuncs.com/google_containers/kube-controller-manager:v1.23.6 registry.aliyuncs.com/google_containers/kube-scheduler:v1.23.6 registry.aliyuncs.com/google_containers/kube-proxy:v1.23.6 registry.aliyuncs.com/google_containers/pause:3.6 registry.aliyuncs.com/google_containers/etcd:3.5.1-0 registry.aliyuncs.com/google_containers/coredns:v1.8.6 # 确认没问题之后我们直接拉取镜像 $ kubeadm config images pull --config kubeadm-flannel.conf [config/images] Pulled registry.aliyuncs.com/google_containers/kube-apiserver:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-controller-manager:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-scheduler:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-proxy:v1.23.6 [config/images] Pulled registry.aliyuncs.com/google_containers/pause:3.6 [config/images] Pulled registry.aliyuncs.com/google_containers/etcd:3.5.1-0 [config/images] Pulled registry.aliyuncs.com/google_containers/coredns:v1.8.6 # 初始化 $ kubeadm init --config kubeadm-flannel.conf [init] Using Kubernetes version: v1.23.6 [preflight] Running pre-flight checks [preflight] Pulling images required for setting up a Kubernetes cluster [preflight] This might take a minute or two, depending on the speed of your internet connection [preflight] You can also perform this action in beforehand using \u0026#39;kubeadm config images pull\u0026#39; ...此处略去一堆输出... 当我们看到下面这个输出结果的时候，我们的集群就算是初始化成功了。\nYour Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf You should now deploy a pod network to the cluster. Run \u0026#34;kubectl apply -f [podnetwork].yaml\u0026#34; with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 10.31.8.1:6443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:d7160866920c0331731ad3c1c31a6e5b6c788b5682f86971cacaa940211db9ab 配置kubeconfig 刚初始化成功之后，我们还没办法马上查看k8s集群信息，需要配置kubeconfig相关参数才能正常使用kubectl连接apiserver读取集群信息。\n# 对于非root用户，可以这样操作 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 如果是root用户，可以直接导入环境变量 export KUBECONFIG=/etc/kubernetes/admin.conf # 添加kubectl的自动补全功能 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 前面我们提到过kubectl不一定要安装在集群内，实际上只要是任何一台能连接到apiserver的机器上面都可以安装kubectl并且根据步骤配置kubeconfig，就可以使用kubectl命令行来管理对应的k8s集群。\n配置完成后，我们再执行相关命令就可以查看集群的信息了。\n$ kubectl cluster-info Kubernetes control plane is running at https://10.31.8.1:6443 CoreDNS is running at https://10.31.8.1:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39;. $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME tiny-flannel-master-8-1.k8s.tcinternal NotReady control-plane,master 79s v1.23.6 10.31.8.1 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 docker://20.10.14 $ kubectl get pods -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system coredns-6d8c4cb4d-2clkj 0/1 Pending 0 86s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system coredns-6d8c4cb4d-8mznz 0/1 Pending 0 86s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system etcd-tiny-flannel-master-8-1.k8s.tcinternal 1/1 Running 0 91s 10.31.8.1 tiny-flannel-master-8-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-apiserver-tiny-flannel-master-8-1.k8s.tcinternal 1/1 Running 0 92s 10.31.8.1 tiny-flannel-master-8-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-controller-manager-tiny-flannel-master-8-1.k8s.tcinternal 1/1 Running 0 90s 10.31.8.1 tiny-flannel-master-8-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-proxy-dkvrn 1/1 Running 0 86s 10.31.8.1 tiny-flannel-master-8-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system kube-scheduler-tiny-flannel-master-8-1.k8s.tcinternal 1/1 Running 0 92s 10.31.8.1 tiny-flannel-master-8-1.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 添加worker节点 这时候我们还需要继续添加剩下的两个节点作为worker节点运行负载，直接在剩下的节点上面运行集群初始化成功时输出的命令就可以成功加入集群：\n$ kubeadm join 10.31.8.1:6443 --token abcdef.0123456789abcdef --discovery-token-ca-cert-hash sha256:d7160866920c0331731ad3c1c31a6e5b6c788b5682f86971cacaa940211db9ab [preflight] Running pre-flight checks [preflight] Reading configuration from the cluster... [preflight] FYI: You can look at this config file with \u0026#39;kubectl -n kube-system get cm kubeadm-config -o yaml\u0026#39; [kubelet-start] Writing kubelet configuration to file \u0026#34;/var/lib/kubelet/config.yaml\u0026#34; [kubelet-start] Writing kubelet environment file with flags to file \u0026#34;/var/lib/kubelet/kubeadm-flags.env\u0026#34; [kubelet-start] Starting the kubelet [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap... This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run \u0026#39;kubectl get nodes\u0026#39; on the control-plane to see this node join the cluster. 如果不小心没保存初始化成功的输出信息也没有关系，我们可以使用kubectl工具查看或者生成token\n# 查看现有的token列表 $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS abcdef.0123456789abcdef 23h 2022-05-08T06:27:34Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token # 如果token已经失效，那就再创建一个新的token $ kubeadm token create pyab3u.j1a9ld7vk03znbk8 $ kubeadm token list TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS abcdef.0123456789abcdef 23h 2022-05-08T06:27:34Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token pyab3u.j1a9ld7vk03znbk8 23h 2022-05-08T06:34:28Z authentication,signing \u0026lt;none\u0026gt; system:bootstrappers:kubeadm:default-node-token # 如果找不到--discovery-token-ca-cert-hash参数，则可以在master节点上使用openssl工具来获取 $ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2\u0026gt;/dev/null | openssl dgst -sha256 -hex | sed \u0026#39;s/^.* //\u0026#39; d6cdc5a3bc40cbb0ae85776eb4fcdc1854942e2dd394470ae0f2f97714dd9fb9 添加完成之后我们再查看集群的节点可以发现这时候已经多了两个node，但是此时节点的状态还是NotReady，接下来就需要部署CNI了。\n$ kubectl get nodes NAME STATUS ROLES AGE VERSION tiny-flannel-master-8-1.k8s.tcinternal NotReady control-plane,master 7m49s v1.23.6 tiny-flannel-worker-8-11.k8s.tcinternal NotReady \u0026lt;none\u0026gt; 2m58s v1.23.6 tiny-flannel-worker-8-12.k8s.tcinternal NotReady \u0026lt;none\u0026gt; 102s v1.23.6 安装CNI 编写manifest文件 flannel应该是众多开源的CNI插件中入门门槛最低的CNI之一了，部署简单，原理易懂，且相关的文档在网络上也非常丰富。\n# 我们先把官方的yaml模板下载下来，然后对关键字段逐个修改 $ wget https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml 针对kube-flannel.yml文件，我们需要修改一些参数以适配我们的集群：\nnet-conf.json参数，配置的是pod的网段，这里我们使用此前计划好的10.8.64.0/18 net-conf.json: | { \u0026#34;Network\u0026#34;: \u0026#34;10.8.64.0/18\u0026#34;, \u0026#34;Backend\u0026#34;: { \u0026#34;Type\u0026#34;: \u0026#34;vxlan\u0026#34; } } 部署flannel 修改完成之后我们直接部署即可\n$ kubectl apply -f kube-flannel.yml Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ podsecuritypolicy.policy/psp.flannel.unprivileged created clusterrole.rbac.authorization.k8s.io/flannel created clusterrolebinding.rbac.authorization.k8s.io/flannel created serviceaccount/flannel created configmap/kube-flannel-cfg created daemonset.apps/kube-flannel-ds created # 查看pod是否正常运行 $ kubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-6d8c4cb4d-np7q2 1/1 Running 0 14m kube-system coredns-6d8c4cb4d-z8f5b 1/1 Running 0 14m kube-system etcd-tiny-flannel-master-8-1.k8s.tcinternal 1/1 Running 0 14m kube-system kube-apiserver-tiny-flannel-master-8-1.k8s.tcinternal 1/1 Running 0 14m kube-system kube-controller-manager-tiny-flannel-master-8-1.k8s.tcinternal 1/1 Running 0 14m kube-system kube-flannel-ds-9fq4z 1/1 Running 0 12m kube-system kube-flannel-ds-ckstx 1/1 Running 0 7m18s kube-system kube-flannel-ds-qj55x 1/1 Running 0 8m25s kube-system kube-proxy-bncfl 1/1 Running 0 14m kube-system kube-proxy-lslcm 1/1 Running 0 7m18s kube-system kube-proxy-pmwhf 1/1 Running 0 8m25s kube-system kube-scheduler-tiny-flannel-master-8-1.k8s.tcinternal 1/1 Running 0 14m # 查看flannel的pod日志是否有报错 $ kubectl logs -f -l app=flannel -n kube-system 部署测试用例 集群部署完成之后我们在k8s集群中部署一个nginx测试一下是否能够正常工作。首先我们创建一个名为nginx-quic的命名空间（namespace），然后在这个命名空间内创建一个名为nginx-quic-deployment的deployment用来部署pod，最后再创建一个service用来暴露服务，这里我们先使用nodeport的方式暴露端口方便测试。\n$ cat nginx-quic.yaml apiVersion: v1 kind: Namespace metadata: name: nginx-quic --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-quic-deployment namespace: nginx-quic spec: selector: matchLabels: app: nginx-quic replicas: 2 template: metadata: labels: app: nginx-quic spec: containers: - name: nginx-quic image: tinychen777/nginx-quic:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-quic-service namespace: nginx-quic spec: selector: app: nginx-quic ports: - protocol: TCP port: 8080 # match for service access port targetPort: 80 # match for pod access port nodePort: 30088 # match for external access port type: NodePort 部署完成后我们直接查看状态\n# 直接部署 $ kubectl apply -f nginx-quic.yaml namespace/nginx-quic created deployment.apps/nginx-quic-deployment created service/nginx-quic-service created # 查看deployment的运行状态 $ kubectl get deployment -o wide -n nginx-quic NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR nginx-quic-deployment 2/2 2 2 48s nginx-quic tinychen777/nginx-quic:latest app=nginx-quic # 查看service的运行状态 $ kubectl get service -o wide -n nginx-quic NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR nginx-quic-service NodePort 10.8.4.218 \u0026lt;none\u0026gt; 8080:30088/TCP 62s app=nginx-quic # 查看pod的运行状态 $ kubectl get pods -o wide -n nginx-quic NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-quic-deployment-696d959797-jm8w5 1/1 Running 0 73s 10.8.66.2 tiny-flannel-worker-8-12.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-quic-deployment-696d959797-lwcqz 1/1 Running 0 73s 10.8.65.2 tiny-flannel-worker-8-11.k8s.tcinternal \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # 查看IPVS规则 $ ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 172.17.0.1:30088 rr -\u0026gt; 10.8.65.2:80 Masq 1 0 0 -\u0026gt; 10.8.66.2:80 Masq 1 0 0 TCP 10.8.4.218:8080 rr -\u0026gt; 10.8.65.2:80 Masq 1 0 0 -\u0026gt; 10.8.66.2:80 Masq 1 0 0 TCP 10.8.64.0:30088 rr -\u0026gt; 10.8.65.2:80 Masq 1 0 0 -\u0026gt; 10.8.66.2:80 Masq 1 0 0 TCP 10.8.64.1:30088 rr -\u0026gt; 10.8.65.2:80 Masq 1 0 0 -\u0026gt; 10.8.66.2:80 Masq 1 0 0 TCP 10.31.8.1:30088 rr -\u0026gt; 10.8.65.2:80 Masq 1 0 0 -\u0026gt; 10.8.66.2:80 Masq 1 0 0 最后我们进行测试，这个nginx-quic的镜像默认情况下会返回在nginx容器中获得的用户请求的IP和端口\n# 首先我们在集群内进行测试 # 直接访问pod，这时候显示的IP是master节点上面的flannel.1网卡的IP $ curl 10.8.66.2:80 10.8.64.0:38958 $ curl 10.8.65.2:80 10.8.64.0:46484 # 直接访问service的ClusterIP，这时请求会被转发到pod中 $ curl 10.8.4.218:8080 10.8.64.0:26305 # 直接访问nodeport，这时请求会被转发到pod中，不会经过ClusterIP $ curl 10.31.8.1:30088 10.8.64.0:6519 # 接着我们在集群外进行测试 # 直接访问三个节点的nodeport，这时请求会被转发到pod中，不会经过ClusterIP # 由于externalTrafficPolicy默认为Cluster，nginx拿到的IP就是我们访问的节点的flannel.1网卡的IP $ curl 10.31.8.1:30088 10.8.64.0:50688 $ curl 10.31.8.11:30088 10.8.65.1:41032 $ curl 10.31.8.12:30088 10.8.66.0:11422 ","permalink":"http://localhost:9011/20220507-k8s-02-deploy-k8s-with-flannel/","summary":"\u003cp\u003e本文主要在centos7系统上基于\u003ccode\u003edocker\u003c/code\u003e和\u003ccode\u003eflannel\u003c/code\u003e组件部署\u003ccode\u003ev1.23.6\u003c/code\u003e版本的k8s原生集群，由于集群主要用于自己平时学习和测试使用，加上资源有限，暂不涉及高可用部署。\u003c/p\u003e\n\u003cp\u003e此前写的一些关于k8s基础知识和集群搭建的一些\u003ca href=\"https://tinychen.com/tags/k8s/\"\u003e方案\u003c/a\u003e，有需要的同学可以看一下。\u003c/p\u003e","title":"k8s系列02-kubeadm部署flannel网络的k8s集群"},{"content":"本文主要用于介绍如何编译安装queryperf来对DNS服务器进行压测，以及CoreDNS常见的几种配置下的压测性能表现。\nqueryperf 编译安装 queryperf是bind9出品的一款测试dns服务器性能的工具，目前在9.12.4版本的bind源码中还存在，再往后的新版本就没看到有queryperf了。\n$ wget https://ftp.isc.org/isc/bind9/9.12.4/bind-9.12.4.tar.gz $ tar -zxvf bind-9.12.4.tar.gz $ cd bind-9.12.4/contrib/queryperf $ ./configure $ make $ file queryperf queryperf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped 常用操作 # 将编译好的二进制文件直接复制到系统的bin目录中即可全局操作 $ cp queryperf /usr/local/bin/ $ queryperf -h DNS Query Performance Testing Tool Version: $Id: queryperf.c,v 1.12 2007/09/05 07:36:04 marka Exp $ Usage: queryperf [-d datafile] [-s server_addr] [-p port] [-q num_queries] [-b bufsize] [-t timeout] [-n] [-l limit] [-f family] [-1] [-i interval] [-r arraysize] [-u unit] [-H histfile] [-T qps] [-e] [-D] [-R] [-c] [-v] [-h] -d specifies the input data file (default: stdin) -s sets the server to query (default: 127.0.0.1) -p sets the port on which to query the server (default: 53) -q specifies the maximum number of queries outstanding (default: 20) -t specifies the timeout for query completion in seconds (default: 5) -n causes configuration changes to be ignored -l specifies how a limit for how long to run tests in seconds (no default) -1 run through input only once (default: multiple iff limit given) -b set input/output buffer size in kilobytes (default: 32 k) -i specifies interval of intermediate outputs in seconds (default: 0=none) -f specify address family of DNS transport, inet or inet6 (default: any) -r set RTT statistics array size (default: 50000) -u set RTT statistics time unit in usec (default: 100) -H specifies RTT histogram data file (default: none) -T specify the target qps (default: 0=unspecified) -e enable EDNS 0 -D set the DNSSEC OK bit (implies EDNS) -R disable recursion -c print the number of packets with each rcode -v verbose: report the RCODE of each response on stdout -h print this usage 在压测之前需要我们自己准备压测的测试数据，格式为域名 查询类型，如：\ntinychen.com A tiny777.com A tinychen.com MX tiny777.com MX tinychen777.com AAAA 常用的操作命令有：\n# 对192.168.1.1进行压测，查询域名为文件query.domain.list的内容 queryperf -s 192.168.1.1 -d query.domain.list # 对192.168.1.1的5353端口进行压测，查询域名为文件query.domain.list的内容 queryperf -s 192.168.1.1 -p 5353 -d query.domain.list # 对192.168.1.1的5353端口进行压测，查询域名为文件query.domain.list的内容，压测压力为1000qps queryperf -s 192.168.1.1 -p 5353 -d query.domain.list -T 1000 # 对192.168.1.1的5353端口进行压测，查询域名为文件query.domain.list的内容，压测压力为1000qps，每次查询超时时间为3s queryperf -s 192.168.1.1 -p 5353 -d query.domain.list -T 1000 -t 3 压测数据 机器硬件配置 虚拟机为16核16G配置，宿主机为R730，配置是双路E5-2640 v4+82599万兆网卡\n物理机为R640，配置是双路银牌Silver 4114+128G内存+x710万兆万卡\n这里需要额外提一下，CoreDNS比较吃网卡和CPU，对于硬盘IO的要求并不算特别高（主要取决于写日志的量），对内存占用较低\nCoreDNS配置 CoreDNS配置的复杂程度会直接影响具体实例的性能表现，简单来说就是启用的插件越多，性能表现越差；比较影响性能表现的插件主要是日志相关的插件，如log插件开启全量日志记录，我们测试启用的插件主要有：log、errors、bind、reload、ready、prometheus、loadbalance、cache、acl、secondary、transfer、hosts、forward、import、file、kubernetes。\n数据汇总 机器配置 CoreDNS配置 性能表现 虚拟机 多插件配置+全量日志记录 30K qps 物理机 多插件配置+全量日志记录 55K qps 物理机 多插件配置+关闭log和error插件 70K qps 物理机 最少插件配置 75K qps Q\u0026amp;A 虚拟机和物理机数据差距并不算特别大？\n首先要明确：CoreDNS并没有完全吃满物理机上面的所有硬件配置，因此在物理机这里的性能表现瓶颈处于CoreDNS本身，而虚拟机的性能表现瓶颈才是硬件配置；\n其次要知道：我们的虚拟机相互之间的影响比较严重，30K qps的性能表现是同宿主机上面的其他虚拟机基本处于摸鱼状态，由于宿主机资源超售严重，一旦其他虚拟机利用率过高，必然会影响性能表现；而使用物理机则不用担心这类问题；\nlog插件对性能的影响？\nlog插件对性能的影响确实较大，但是仅限于全量写入查询日志的时候，如果对普通的查询日志的需求低于性能需求，可以考虑只配置部分特殊类型日志，如只记录错误日志log denial error；\n还有些同学可能会担心日志落盘速度慢是否会影响查询响应速度，实测在使用rsyslog记录日志的时候，所有查询请求均响应完之后一段时候内，rsyslog还在落盘相关日志，因此可以确定逻辑上并非是必须要等日志落盘结束后才响应请求；\nPrometheus插件采集到的数据和queryperf的数据不一致？\n如果压测的时间太短（几秒到几分钟不等），会导致高峰时间持续太短而Prometheus插件无法准确暴露数据，同时还要考虑Prometheus服务端的采集频率已经Grafana面板的显示设置等；最好的方式是进行一段时间较长的压测，如三十分钟以上；\n","permalink":"http://localhost:9011/20220221-dns-10-coredns-07-queryperf-performance-testing/","summary":"\u003cp\u003e本文主要用于介绍如何编译安装queryperf来对DNS服务器进行压测，以及CoreDNS常见的几种配置下的压测性能表现。\u003c/p\u003e","title":"CoreDNS篇7-性能压测"},{"content":"本文主要用于介绍CoreDNS实现递归服务器的几种方式以及在生产环境中遇到的一些问题和解决方案。\n在开始之前我们需要知道一些关于CoreDNS的基本知识：CoreDNS本身是没有能力作为一个递归查询的DNS服务器（Recursive DNS），但是它有着众多的插件，可以通过插件来实现对域名的递归查询和缓存等功能从而加速客户端的DNS查询性能。这里主要实现的插件有内部插件（Plugins）forward或外部插件（External Plugins）unbound。\nCoreDNS官方对External Plugins的描述为：\nOut of tree plugins for CoreDNS. A plugin listed here is not automatically endorsed by the CoreDNS team. Issues should be reported to owner(s) of the plugin.\n我们可以将CoreDNS的External Plugins简单理解为第三方插件，但是其中少部分会有官方的维护支持或者是直接默认内置在官方编译好的版本中。\nunbound 我们先来了解一下不依靠外部程序实现递归查询功能的unbound插件，unbound是一个非常优秀的DNS软件，专注于递归查询和缓存，但对于权威DNS服务器这方面的功能稍显不足，因此理论上将unbound和CoreDNS结合就可以很好的弥补两者的不足。\n编译安装 unbound插件的编译安装稍显麻烦，此前的文章中有详细介绍操作步骤和注意事项，需要特别注意的是编译安装了unbound插件的CoreDNS会从原来的静态二进制文件，变成了需要动态加载依赖库。因此如果需要提前编译然后大范围使用，最好保证编译环境的系统和最终的使用环境系统一致或全兼容。\n一些问题 unbound插件已经很长一段时间没有更新维护了，尽管它有Maintained by CoreDNS的标注，因此如果使用较新版本的go编译之后，在启动的时候会有报警。\nJan 18 02:29:16 coredns1 coredns: [WARNING] An external plugin (/home/gopath/pkg/mod/github.com/coredns/unbound@v0.0.7/setup.go line 63) is using the deprecated function Normalize. This will be removed in a future versions of CoreDNS. The plugin should be updated to use OriginsFromArgsOrServerBlock or NormalizeExact instead. 另外就是在运行的过程中会出现panic异常，一开始怀疑是和负载有关，后面测试发现当请求量极低（个位数qps）的时候也会出现此类异常。\n$ ./coredns -dns.port=53 -conf /home/coredns/corefile .:53 on 0.0.0.0 CoreDNS-1.8.3 linux/amd64, go1.16.4, 7b43d042-dirty [INFO] 127.0.0.1:46007 - 37929 \u0026#34;A IN baidu.com. udp 50 false 4096\u0026#34; NOERROR qr,rd,ra 484 0.614542s [INFO] 127.0.0.1:32946 - 43201 \u0026#34;A IN baidu.com. udp 50 false 4096\u0026#34; NOERROR qr,aa,rd,ra 484 0.0000971s [INFO] 127.0.0.1:38201 - 44652 \u0026#34;A IN baidu.com. udp 50 false 4096\u0026#34; NOERROR qr,aa,rd,ra 484 0.0001522s [INFO] 127.0.0.1:50863 - 63692 \u0026#34;A IN tinychen.com. udp 53 false 4096\u0026#34; NOERROR qr,rd,ra 58 0.3613896s [ERROR] Recovered from panic in server: \u0026#34;dns://0.0.0.0:53\u0026#34; [ERROR] Recovered from panic in server: \u0026#34;dns://0.0.0.0:53\u0026#34; [ERROR] Recovered from panic in server: \u0026#34;dns://0.0.0.0:53\u0026#34; 经过多次测试之后，我们发现针对unbound插件出现panic的情况和请求的频率无关，而是和请求的内容有关。简单来说就是：当请求的域名本身就不存在解析的时候，就会触发panic异常；当然还可以再进一步：当CoreDNS服务端本身无法和根域名服务器建立连接转发查询的时候，也会出现panic异常。\nforward 配置使用 forward插件主要的作用就是把DNS请求转发给上游的upstream服务器。forward插件本身并不支持任何的DNS解析功能，但是可以将相应的请求转发到递归服务器上，再结合cache插件做缓存，从而实现递归查询解析缓存的功能。\n.:53 { forward . 114.114.114.114 114.114.115.115 { health_check 5s } log errors ready prometheus cache { success 10240 600 60 denial 5120 60 5 } } forward对应的upstream机器可以根据自己的需求选择现有的公共DNS，如国内常见的114、谷歌的8888等免费DNS，或者选择自己使用unbound、bind9之类的DNS服务器单独搭建一个专门用来做递归查询的DNS服务。\n一些问题 Jan 18 05:29:16 coredns1 coredns: [ERROR] plugin/errors: 2 nonexist.test.tinychen.com. A: read udp 127.0.0.1:7522-\u0026gt;114.114.114.114:53: i/o timeout 当查询的域名解析记录不存在或者是网络问题不可达的时候，可能会触发upstream的超时时间限制，导致报错i/o timeout，可以考虑使用error插件的consolidate指令对这类报警进行统一处理并修改报警等级\nerrors { consolidate 5m \u0026#34;.* i/o timeout$\u0026#34; warning } ","permalink":"http://localhost:9011/20220220-dns-09-coredns-06-recursive-dns/","summary":"\u003cp\u003e本文主要用于介绍CoreDNS实现递归服务器的几种方式以及在生产环境中遇到的一些问题和解决方案。\u003c/p\u003e","title":"CoreDNS篇6-递归服务器"},{"content":"本文主要用于介绍CoreDNS用来记录日志的几种方式以及在生产环境中遇到的一些问题和解决方案。\nlog插件 coredns的日志输出并不如nginx那么完善（并不能在配置文件中指定输出的文件目录，但是可以指定日志的格式），默认情况下不论是log插件还是error插件都会把所有的相关日志输出到程序的standard output中。使用systemd来管理coredns之后，默认情况下基本就是由rsyslog和systemd-journald这两个服务来管理日志。\nlog插件配置 前面的文章里我们有简单的介绍过coredns的日志处理，对于log插件也是类似的操作。考虑到CentOS8已经结束支持，这里仅介绍CentOS7的配置方法，更高版本的systemd可以参考之前的文章。\n对于centos7等系统而言，是不支持在高版本的systemd中的unit文件中使用append和file两个参数的，并且对于这种方式输出的日志缺少了前面的时间和主机名等信息，相对而言还是修改rsyslog的方式要更加的可靠。那么在开启了rsyslog.service服务的情况下，日志就会输出到/var/log/messages文件中，或者可以使用journalctl -u coredns命令来查看全部的日志。\n如果想要将coredns的日志全部集中到一个文件进行统一管理，我们可以对负责管理systemd的日志的rsyslog服务的配置进行修改，然后再重启rsyslog服务即可生效。\n# 注意这段配置需要添加在其他日志配置规则之前 # 否则日志会在/var/log/messages和/home/coredns/logs/coredns.log都写入一份 $ vim /etc/rsyslog.conf if $programname == \u0026#39;coredns\u0026#39; then /home/coredns/logs/coredns.log \u0026amp; stop $ systemctl restart rsyslog.service 但是有两点需要额外注意：\nCoreDNS在开启日志记录之后的性能要差于不开启日志记录，这个很容易理解，因为记录日志需要耗费额外的资源，尤其是当QPS增大之后，记录日志组件的压力也会增大，需要注意两个服务对资源的抢占和分配是否合理；如果遇到写入日志占用大量资源的情况，可以考虑将其配置为只写入部分类型日志（如只写入错误日志） rsyslog和systemd-journald这两个服务默认情况下都会对日志的写入频率进行限制，当QPS增大之后，记录的日志会不完整；当然这个问题可以通过调整rsyslog的参数解决 rsyslog配置 下面具体介绍一下如何配置rsyslog和systemd-journald解除相关的日志限制。\n默认情况下我们查看systemd-journald.service的日志或者是查看/var/log/messages文件，如果看到类似下面的提示信息，就说明日志的输出频率太高被抑制了\n$ systemctl status systemd-journald.service ● systemd-journald.service - Journal Service Loaded: loaded (/usr/lib/systemd/system/systemd-journald.service; static; vendor preset: disabled) Active: active (running) since Wed 2021-12-01 09:30:03 CST; 3 weeks 6 days ago Docs: man:systemd-journald.service(8) man:journald.conf(5) Main PID: 709 (systemd-journal) Status: \u0026#34;Processing requests...\u0026#34; CGroup: /system.slice/systemd-journald.service └─709 /usr/lib/systemd/systemd-journald Dec 28 15:36:27 tiny-server systemd-journal[709]: Suppressed 13174 messages from /system.slice/coredns.service Dec 28 15:36:57 tiny-server systemd-journal[709]: Suppressed 13185 messages from /system.slice/coredns.service Dec 28 15:37:27 tiny-server systemd-journal[709]: Suppressed 11600 messages from /system.slice/coredns.service Dec 28 15:37:57 tiny-server systemd-journal[709]: Suppressed 13896 messages from /system.slice/coredns.service Dec 28 15:38:27 tiny-server systemd-journal[709]: Suppressed 13653 messages from /system.slice/coredns.service Dec 28 15:38:57 tiny-server systemd-journal[709]: Suppressed 16503 messages from /system.slice/coredns.service Dec 28 15:39:27 tiny-server systemd-journal[709]: Suppressed 13300 messages from /system.slice/coredns.service Dec 28 15:39:57 tiny-server systemd-journal[709]: Suppressed 10491 messages from /system.slice/coredns.service Dec 28 15:40:27 tiny-server systemd-journal[709]: Suppressed 12079 messages from /system.slice/coredns.service Dec 28 15:40:57 tiny-server systemd-journal[709]: Suppressed 17182 messages from /system.slice/coredns.service $ tail -f /var/log/messages Dec 28 15:43:55 tiny-server rsyslogd: imjournal: begin to drop messages due to rate-limiting 解决方案也比较简单，我们对这两个服务的配置参数进行调整，将限制日志写入频率和写入时间间隔都设为0（即不做限制），然后重启服务即可。\necho \u0026#34;RateLimitInterval=0\u0026#34; \u0026gt;\u0026gt; /etc/systemd/journald.conf echo \u0026#34;RateLimitBurst=0\u0026#34; \u0026gt;\u0026gt; /etc/systemd/journald.conf systemctl restart systemd-journald.service echo \u0026#34;\\$imjournalRatelimitInterval 0\u0026#34; \u0026gt;\u0026gt; /etc/rsyslog.conf echo \u0026#34;\\$imjournalRatelimitBurst 0\u0026#34; \u0026gt;\u0026gt; /etc/rsyslog.conf systemctl restart rsyslog.service 需要特别注意的是：上述两个参数对所有使用rsyslog的服务都会生效，如果有其他大量写入日志的服务，建议调整日志文件输出目录，同时关注/var/log等目录的硬盘空间情况。\ndnstap插件 CoreDNS的原生日志功能对于一个DNS服务器的绝大部分应用场景来说是足够使用的，如果有更进阶的需求，可以考虑使用dnstap插件来实现。\ndnstap是一个基于谷歌的缓冲区协议（Protocol buffers）实现的用于DNS的灵活的、结构化的、二进制日志格式记录软件，目前已经获得了bind9、unbound、coredns、knot等几乎所有主流dns软件的支持。dnstap目前支持二进制日志格式、text日志格式、yaml日志格式和json日志格式。\nCoreDNS官网对于dnstap插件的介绍比较简单，不过官网页面有比较详细的介绍。\ndnstap is a flexible, structured binary log format for DNS software. It uses Protocol Buffers to encode events that occur inside DNS software in an implementation-neutral format.\nCurrently dnstap can only encode wire-format DNS messages. It is planned to support additional types of DNS log information.\nProtocol buffers are Google\u0026rsquo;s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.\n缓冲区协议（Protocol buffers）是 Google 用于序列化结构数据的语言中立、平台中立、可扩展机制。可以将其和XML相对比，但Protocol buffers更小、更快、更简单。 使用者只需定义一次数据的结构化方式，然后就可以使用特殊生成的源代码轻松地将结构化数据写入和读取各种数据流，并使用各种语言。\n这里需要额外注意的是：\ndnstap和CoreDNS原生的log等日志记录插件可以同时使用 尽管CoreDNS默认内置dnstap插件，但是要使用dnstap还是需要我们自己手动安装dnstap，再到corefile中配置dnstap的socket路径或者通信端口才能使用 dnstap安装 dnstap官方在github上面开源了一个golang-dnstap的项目，可以使用go来快速安装dnstap\n# golang的安装配置比较简单，我们从https://go.dev/dl/直接下载最新的稳定版本即可。 wget https://go.dev/dl/go1.17.5.linux-amd64.tar.gz tar -zxvf go1.17.5.linux-amd64.tar.gz -C /usr/local # 修改系统默认的go文件 ln -s /usr/local/go/bin/go /usr/bin/go # 接下来的go环境变量同学们可以根据自己的实际需求进行配置。 # 对于我个人而言，我直接在/etc/profile中添加下面的配置然后source生效即可。 export GOROOT=/usr/local/go export GOBIN=$GOROOT/bin export PATH=$PATH:$GOBIN export GOPATH=/home/gopath 执行go version命令确定go安装无误之后就可以直接安装dnstap：\n# 接着就可以使用go命令来快速安装dnstap go get -u github.com/dnstap/golang-dnstap/dnstap # go版本高于v1.16建议使用下面这条指令 # 关于go get和go install的区别可以查看go的官方文档https://golang.org/doc/go-get-install-deprecation go install github.com/dnstap/golang-dnstap/dnstap@latest # 如果go包管理默认代理网址无法访问proxy.golang.org，换一个国内能访问的代理地址：https://goproxy.cn go env -w GOPROXY=https://goproxy.cn # 如果不确定是否安装成功，可以执行下面的命令查看安装信息进行确定 $ dnstap --help Usage: dnstap [OPTION]... -T value write dnstap payloads to tcp/ip address -U value write dnstap payloads to unix socket -a append to the given file, do not overwrite. valid only when outputting a text or YAML file. -j use verbose JSON output -l value read dnstap payloads from tcp/ip -q use quiet text output -r value read dnstap payloads from file -t duration I/O timeout for tcp/ip and unix domain sockets -u value read dnstap payloads from unix socket -w string write output to file -y use verbose YAML output Quiet text output format mnemonics: AQ: AUTH_QUERY AR: AUTH_RESPONSE RQ: RESOLVER_QUERY RR: RESOLVER_RESPONSE CQ: CLIENT_QUERY CR: CLIENT_RESPONSE FQ: FORWARDER_QUERY FR: FORWARDER_RESPONSE SQ: STUB_QUERY SR: STUB_RESPONSE TQ: TOOL_QUERY TR: TOOL_RESPONSE coredns配置 coredns中对dnstap的配置非常简单，可以选择配置socket通信或者是tcp通信，full参数则是可以记录更多的参数信息，如同样的一次查询，红框是开启了full参数，而绿框则是不开启，两者相差较大。\n# 使用tcp通信 dnstap tcp://127.0.0.1:6000 full # 使用socket通信 dnstap unix:///tmp/dnstap.sock full dnstap配置 dnstap的配置也比较简单，基本可以分为三个部分：\n指定通信方式：如-u /tmp/dnstap.sock或者-l 127.0.0.1:6000 指定日志文件：如-w /home/coredns/logs/dnstap.log 指定日志格式：默认为二进制格式，-q为text文本，-y为yaml格式，-j为json格式 其他参数：如-a追加写入内容到日志文件中而非覆盖原有的文件 # 使用socket通信，并以默认的二进制格式记录到/home/coredns/logs/dnstap.log文件中 dnstap -u /tmp/dnstap.sock -w /home/coredns/logs/dnstap.log # 此时无法直接查看日志文件中的内容 $ tail -f /home/coredns/logs/dnstap.log \u0026#34;\u0002\u0001\u0016protobuf:dnstap.Dnstap\u0026#34; # 使用socket通信，并以text文本格式追加记录到/home/coredns/logs/dnstap.log文件中 dnstap -u /tmp/dnstap.sock -w /home/coredns/logs/dnstap.log -q -a # 这里的coredns对tinychen.com的查询会forward出去， # 因此日志中会有CLIENT_QUERY CLIENT_RESPONSE FORWARDER_QUERY FORWARDER_RESPONSE四条记录 $ tail -f /home/coredns/logs/dnstap.log 16:14:39.329430 CQ 10.31.100.100 UDP 53b \u0026#34;tinychen.com.\u0026#34; IN A 16:14:39.329546 FQ 127.0.0.1 UDP 53b \u0026#34;tinychen.com.\u0026#34; IN A 16:14:39.330241 FR 127.0.0.1 UDP 69b \u0026#34;tinychen.com.\u0026#34; IN A 16:14:39.330441 CR 10.31.100.100 UDP 81b \u0026#34;tinychen.com.\u0026#34; IN A # 使用socket通信，并以yaml格式追加记录到/home/coredns/logs/dnstap.log文件中 dnstap -u /tmp/dnstap.sock -w /home/coredns/logs/dnstap.log -y -a # 这里的coredns对tinychen.com的查询会forward出去， # 因此日志中会有CLIENT_QUERY CLIENT_RESPONSE FORWARDER_QUERY FORWARDER_RESPONSE四条记录 $ tail -f /home/coredns/logs/dnstap.log type: MESSAGE message: type: CLIENT_QUERY query_time: !!timestamp 2022-01-12 08:23:25.211126656 socket_family: INET socket_protocol: UDP query_address: 10.31.100.100 query_port: 58562 query_message: | ;; opcode: QUERY, status: NOERROR, id: 30286 ;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1 ;; QUESTION SECTION: ;tinychen.com. IN A ;; ADDITIONAL SECTION: ;; OPT PSEUDOSECTION: ; EDNS: version 0; flags: ; udp: 4096 ; COOKIE: 22c8a1e55b27c1b9 --- type: MESSAGE message: type: FORWARDER_QUERY query_time: !!timestamp 2022-01-12 08:23:25.211208675 socket_family: INET socket_protocol: UDP query_address: 10.31.100.100 response_address: 127.0.0.1 query_port: 58562 response_port: 35353 query_message: | ;; opcode: QUERY, status: NOERROR, id: 30286 ;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1 ;; QUESTION SECTION: ;tinychen.com. IN A ;; ADDITIONAL SECTION: ;; OPT PSEUDOSECTION: ; EDNS: version 0; flags: do; udp: 2048 ; COOKIE: 22c8a1e55b27c1b9 --- type: MESSAGE message: type: FORWARDER_RESPONSE query_time: !!timestamp 2022-01-12 08:23:25.211208675 response_time: !!timestamp 2022-01-12 08:23:25.243335638 socket_family: INET socket_protocol: UDP query_address: 10.31.100.100 response_address: 127.0.0.1 query_port: 58562 response_port: 35353 response_message: | ;; opcode: QUERY, status: NOERROR, id: 30286 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; QUESTION SECTION: ;tinychen.com. IN A ;; ANSWER SECTION: tinychen.com. 589 IN A 1.12.217.55 ;; ADDITIONAL SECTION: ;; OPT PSEUDOSECTION: ; EDNS: version 0; flags: do; udp: 1232 --- type: MESSAGE message: type: CLIENT_RESPONSE query_time: !!timestamp 2022-01-12 08:23:25.211126656 response_time: !!timestamp 2022-01-12 08:23:25.243503207 socket_family: INET socket_protocol: UDP query_address: 10.31.100.100 query_port: 58562 response_message: | ;; opcode: QUERY, status: NOERROR, id: 30286 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; QUESTION SECTION: ;tinychen.com. IN A ;; ANSWER SECTION: tinychen.com. 589 IN A 1.12.217.55 ;; ADDITIONAL SECTION: ;; OPT PSEUDOSECTION: ; EDNS: version 0; flags: ; udp: 4096 ; COOKIE: 22c8a1e55b27c1b9 --- # 使用socket通信，并以yaml格式追加记录到/home/coredns/logs/dnstap.log文件中 dnstap -u /tmp/dnstap.sock -w /home/coredns/logs/dnstap.log -j -a # 这里的coredns对tinychen.com的查询会forward出去， # 因此日志中会有CLIENT_QUERY CLIENT_RESPONSE FORWARDER_QUERY FORWARDER_RESPONSE四条记录 $ tail -f /home/coredns/logs/dnstap.log {\u0026#34;type\u0026#34;:\u0026#34;MESSAGE\u0026#34;,\u0026#34;message\u0026#34;:{\u0026#34;type\u0026#34;:\u0026#34;CLIENT_QUERY\u0026#34;,\u0026#34;query_time\u0026#34;:\u0026#34;2022-01-12T08:36:41.745130199Z\u0026#34;,\u0026#34;socket_family\u0026#34;:\u0026#34;INET\u0026#34;,\u0026#34;socket_protocol\u0026#34;:\u0026#34;UDP\u0026#34;,\u0026#34;query_address\u0026#34;:\u0026#34;10.31.100.100\u0026#34;,\u0026#34;query_port\u0026#34;:58561,\u0026#34;query_message\u0026#34;:\u0026#34;;; opcode: QUERY, status: NOERROR, id: 5835\\n;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1\\n\\n;; QUESTION SECTION:\\n;tinychen.com.\\tIN\\t A\\n\\n;; ADDITIONAL SECTION:\\n\\n;; OPT PSEUDOSECTION:\\n; EDNS: version 0; flags: ; udp: 4096\\n; COOKIE: fd6716688b43710a\\n\u0026#34;}} {\u0026#34;type\u0026#34;:\u0026#34;MESSAGE\u0026#34;,\u0026#34;message\u0026#34;:{\u0026#34;type\u0026#34;:\u0026#34;FORWARDER_QUERY\u0026#34;,\u0026#34;query_time\u0026#34;:\u0026#34;2022-01-12T08:36:41.745243809Z\u0026#34;,\u0026#34;socket_family\u0026#34;:\u0026#34;INET\u0026#34;,\u0026#34;socket_protocol\u0026#34;:\u0026#34;UDP\u0026#34;,\u0026#34;query_address\u0026#34;:\u0026#34;10.31.100.100\u0026#34;,\u0026#34;response_address\u0026#34;:\u0026#34;127.0.0.1\u0026#34;,\u0026#34;query_port\u0026#34;:58561,\u0026#34;response_port\u0026#34;:35353,\u0026#34;query_message\u0026#34;:\u0026#34;;; opcode: QUERY, status: NOERROR, id: 5835\\n;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1\\n\\n;; QUESTION SECTION:\\n;tinychen.com.\\tIN\\t A\\n\\n;; ADDITIONAL SECTION:\\n\\n;; OPT PSEUDOSECTION:\\n; EDNS: version 0; flags: do; udp: 2048\\n; COOKIE: fd6716688b43710a\\n\u0026#34;}} {\u0026#34;type\u0026#34;:\u0026#34;MESSAGE\u0026#34;,\u0026#34;message\u0026#34;:{\u0026#34;type\u0026#34;:\u0026#34;FORWARDER_RESPONSE\u0026#34;,\u0026#34;query_time\u0026#34;:\u0026#34;2022-01-12T08:36:41.745243809Z\u0026#34;,\u0026#34;response_time\u0026#34;:\u0026#34;2022-01-12T08:36:41.83691354Z\u0026#34;,\u0026#34;socket_family\u0026#34;:\u0026#34;INET\u0026#34;,\u0026#34;socket_protocol\u0026#34;:\u0026#34;UDP\u0026#34;,\u0026#34;query_address\u0026#34;:\u0026#34;10.31.100.100\u0026#34;,\u0026#34;response_address\u0026#34;:\u0026#34;127.0.0.1\u0026#34;,\u0026#34;query_port\u0026#34;:58561,\u0026#34;response_port\u0026#34;:35353,\u0026#34;response_message\u0026#34;:\u0026#34;;; opcode: QUERY, status: SERVFAIL, id: 5835\\n;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1\\n\\n;; QUESTION SECTION:\\n;tinychen.com.\\tIN\\t A\\n\\n;; ADDITIONAL SECTION:\\n\\n;; OPT PSEUDOSECTION:\\n; EDNS: version 0; flags: do; udp: 1232\\n\u0026#34;}} {\u0026#34;type\u0026#34;:\u0026#34;MESSAGE\u0026#34;,\u0026#34;message\u0026#34;:{\u0026#34;type\u0026#34;:\u0026#34;CLIENT_RESPONSE\u0026#34;,\u0026#34;query_time\u0026#34;:\u0026#34;2022-01-12T08:36:41.745130199Z\u0026#34;,\u0026#34;response_time\u0026#34;:\u0026#34;2022-01-12T08:36:41.837144129Z\u0026#34;,\u0026#34;socket_family\u0026#34;:\u0026#34;INET\u0026#34;,\u0026#34;socket_protocol\u0026#34;:\u0026#34;UDP\u0026#34;,\u0026#34;query_address\u0026#34;:\u0026#34;10.31.100.100\u0026#34;,\u0026#34;query_port\u0026#34;:58561,\u0026#34;response_message\u0026#34;:\u0026#34;;; opcode: QUERY, status: SERVFAIL, id: 5835\\n;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1\\n\\n;; QUESTION SECTION:\\n;tinychen.com.\\tIN\\t A\\n\\n;; ADDITIONAL SECTION:\\n\\n;; OPT PSEUDOSECTION:\\n; EDNS: version 0; flags: ; udp: 4096\\n; COOKIE: fd6716688b43710a\\n\u0026#34;}} 其他问题 目前在使用dnstap的过程中发现了两个小问题：\ndnstap日志记录时间使用的是UTC时间记录，对于国内的用户，时间上就有八个小时的差距\ndnstap限制缓冲区的消息条数为10000条，超出限制的消息不会被写入dnstap中\nJan 13 20:41:56 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 196 Jan 13 20:41:56 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 680 Jan 13 20:41:58 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 1900 Jan 13 20:41:58 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 10118 Jan 13 20:41:59 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 1472 Jan 13 20:41:59 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 9430 Jan 13 20:42:02 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 3669 Jan 13 20:42:02 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 23351 Jan 13 20:42:06 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 5776 Jan 13 20:42:06 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 35511 Jan 13 20:42:17 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 13161 Jan 13 20:42:17 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 81395 Jan 13 20:42:41 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 29500 Jan 13 20:42:41 coredns2 coredns: [WARNING] plugin/dnstap: Dropped dnstap messages: 180877 如果想要突破这个限制，可以考虑在编译的时候修改源码\n# github地址 # https://github.com/coredns/coredns/blob/master/plugin/dnstap/io.go const ( tcpWriteBufSize = 1024 * 1024 // there is no good explanation for why this number has this value. queueSize = 10000 // idem. tcpTimeout = 4 * time.Second flushTimeout = 1 * time.Second ) errors插件 errors插件简介 CoreDNS官方有对errors插件比较详细的介绍和使用说明，简单来说errors插件就是专门用来记录错误信息日志的一个组件。这里需要注意的是：\n并不是不使用errors插件，日志中就不会输出[ERROR]级别的日志；其他的插件和CoreDNS本身也是可以输出[ERROR]级别的日志 errors插件默认情况下输出的日志级别都是[ERROR]，但是也可以通过consolidate命令进行定义 errors插件配置 errors的配置非常简单，位置和log插件一样\n.:53 { forward . 127.0.0.1:35353 log errors cache { success 10240 600 60 denial 5120 60 5 } } 开启前后的对比如下：\n# 不开启error插件 Jan 14 11:50:12 coredns2 coredns: [INFO] 10.31.53.2:36586 - 34882 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 1.001413555s # 开启error插件 Jan 14 11:50:33 coredns2 coredns: [INFO] 10.31.53.2:33796 - 62387 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 1.001076641s Jan 14 11:50:33 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:57591-\u0026gt;127.0.0.1:35353: read: connection refused 当然还可以有类似正则的配置方式可以简化日志输出，同时定义\nerrors { consolidate 30s \u0026#34;.* connection refused$\u0026#34; warning consolidate 5m \u0026#34;.* i/o timeout$\u0026#34; warning consolidate 30s \u0026#34;^Failed to .+\u0026#34; } 这样子可以把多条错误日志合并输出，结果如下：\n# 合并错误日志之前，每次错误日志都会单独输出，日志级别默认均为[ERROR] Jan 14 14:29:31 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:53697-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:32 coredns2 coredns: [INFO] 10.31.53.2:38422 - 21429 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000386185s Jan 14 14:29:32 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:34774-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:33 coredns2 coredns: [INFO] 10.31.53.2:44775 - 40543 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000418209s Jan 14 14:29:33 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:51575-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:33 coredns2 coredns: [INFO] 10.31.53.2:41858 - 30773 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000285244s Jan 14 14:29:33 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:54027-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:34 coredns2 coredns: [INFO] 10.31.53.2:56657 - 31457 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000280881s Jan 14 14:29:34 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:59189-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:34 coredns2 coredns: [INFO] 10.31.53.2:48445 - 29130 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000399419s Jan 14 14:29:34 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:35171-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:35 coredns2 coredns: [INFO] 10.31.53.2:42290 - 310 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000359849s Jan 14 14:29:35 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:40662-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:36 coredns2 coredns: [INFO] 10.31.53.2:33545 - 28529 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000456204s Jan 14 14:29:36 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:37439-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:36 coredns2 coredns: [INFO] 10.31.53.2:53167 - 18372 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000426039s Jan 14 14:29:36 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:45881-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:37 coredns2 coredns: [INFO] 10.31.53.2:44851 - 52388 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000264627s Jan 14 14:29:37 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:58678-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:37 coredns2 coredns: [INFO] 10.31.53.2:59500 - 19169 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000385832s Jan 14 14:29:37 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:36355-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:38 coredns2 coredns: [INFO] 10.31.53.2:48272 - 35987 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000359493s Jan 14 14:29:38 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:52055-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:39 coredns2 coredns: [INFO] 10.31.53.2:55974 - 48529 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.00035554s Jan 14 14:29:39 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:51253-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:39 coredns2 coredns: [INFO] 10.31.53.2:40670 - 43374 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.00024835s Jan 14 14:29:39 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:41919-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:40 coredns2 coredns: [INFO] 10.31.53.2:42067 - 780 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000257447s Jan 14 14:29:40 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:49359-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:40 coredns2 coredns: [INFO] 10.31.53.2:56758 - 62500 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000338204s Jan 14 14:29:40 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:44658-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:41 coredns2 coredns: [INFO] 10.31.53.2:36149 - 32955 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000400937s Jan 14 14:29:41 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:33750-\u0026gt;127.0.0.1:35353: read: connection refused Jan 14 14:29:41 coredns2 coredns: [INFO] 10.31.53.2:60504 - 49021 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000272807s Jan 14 14:29:41 coredns2 coredns: [ERROR] plugin/errors: 2 tinychen.com. A: read udp 127.0.0.1:41003-\u0026gt;127.0.0.1:35353: read: connection refused # 合并错误日志之后,合并输出30s内符合条件的错误日志总数，并且日志级别变为我们自定义的[WARNING] Jan 14 14:32:48 coredns2 coredns: [INFO] 10.31.53.2:60095 - 763 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000389099s Jan 14 14:33:10 coredns2 coredns: [INFO] 10.31.53.2:44256 - 21788 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000556023s Jan 14 14:33:11 coredns2 coredns: [INFO] 10.31.53.2:39654 - 18638 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000394553s Jan 14 14:33:11 coredns2 coredns: [INFO] 10.31.53.2:46340 - 23770 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000405404s Jan 14 14:33:12 coredns2 coredns: [INFO] 10.31.53.2:56168 - 59588 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000351365s Jan 14 14:33:13 coredns2 coredns: [INFO] 10.31.53.2:52337 - 28311 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.00033022s Jan 14 14:33:14 coredns2 coredns: [INFO] 10.31.53.2:55564 - 52773 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.0002756s Jan 14 14:33:14 coredns2 coredns: [INFO] 10.31.53.2:36233 - 21289 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000307114s Jan 14 14:33:15 coredns2 coredns: [INFO] 10.31.53.2:41822 - 41368 \u0026#34;A IN tinychen.com. udp 38 false 4096\u0026#34; - - 0 0.000379377s Jan 14 14:33:18 coredns2 coredns: [WARNING] plugin/errors: 9 errors like \u0026#39;.* connection refused$\u0026#39; occurred in last 30s ","permalink":"http://localhost:9011/20220114-dns-08-coredns-05-log/","summary":"\u003cp\u003e本文主要用于介绍CoreDNS用来记录日志的几种方式以及在生产环境中遇到的一些问题和解决方案。\u003c/p\u003e","title":"CoreDNS篇5-日志处理"},{"content":"本文主要介绍基于CentOS7.9系统部署DPVS的FullNAT模式在使用keepalived进行主备模式配置高可用集群在线上生产环境落地实践时遇到的一些问题和处理的思路。\n文中所有IP地址、主机名、MAC地址信息均已进行脱敏或魔改处理，客户端IP使用模拟器生成，但不影响阅读体验。\nkeepalived架构 单机架构图 为了方便理解我们可以把上面的架构图分为DPVS网络栈、Linux网络栈、RS集群和使用者（SA和Users）这四大部分。在Linux网络栈中的物理网卡使用eth表示，在DPVS网络栈中的物理网卡使用dpdk表示，DPVS网络栈中的网卡虚拟到Linux网络栈中则使用kni后缀表示，在两个网络栈中做了bonding的网卡都使用BOND表示。\n默认情况下，对于所有的kni网卡来说，它们的流量都会被DPVS程序劫持。\n网卡用途 keepalived双臂模式架构下，每台DPVS机器最少需要三组网卡，bonding可做可不做，不影响该架构图。上图为做了bonding4的网卡架构，因此网卡名称使用bond0、1、2来表示，只要理解清楚每一组网卡的作用，就能很容易理解图中的架构。\nbond0：bond0网卡主要用于运维人员管理机器以及keepalived程序对后端的RS节点进行探活\n只存在于Linux网络栈中的网卡，因为DPVS网络栈的网卡（包括其虚拟出的kni网卡）都是随着DPVS程序的存在而存在的，因此必须有一个独立于DPVS进程之外的网卡用于管理机器（机器信息监控报警，ssh登录操作等）。\nkeepalived程序对后端的RS节点探活的时候只能使用Linux网络栈，因此在上图的架构中，正好也是使用bond0网卡进行探活操作，如果有多个Linux网络栈的内网网卡，则根据Linux系统中的路由来判断（单张网卡的时候也是根据路由判断）。\nbond1.kni：bond1.kni在上述架构正常运行的时候是没有任何作用的\nbond1.kni作为DPVS中的bond1网卡在Linux网络栈中的虚拟网卡，在定位上和bond0是几乎完成重合的，因此最好将其关闭避免对bond0产生干扰。\n之所以不将其彻底删除，是因为当DPVS程序运行异常或者需要对bond1抓包的时候，可以将bond1的流量forward到bond1.kni进行操作。\nbond2.kni：bond2.kni主要用于刷新VIP的MAC地址\nkni网卡的mac地址和DPVS中的bond网卡的mac地址是一致的，由于我们常用的ping和arping等命令无法对DPVS中的网卡操作，因此当我们需要发送garp数据包或者是gna数据包来刷新IPv4或者IPv6的VIP地址在交换机中的MAC地址的时候，可以通过DPVS网卡对应的kni网卡来进行操作。\nbond1：业务流量网卡，主要用于加载LIP、与RS建立连接并转发请求\nlocal_address_group这个字段配置的LIP一般就是配置在bond1网卡。\nbond2：业务流量网卡，主要用于加载VIP、与客户端建立连接并转发请求\ndpdk_interface这个字段就是DPVS定制版的keepalived程序特有的字段，能够将VIP配置到dpvs网卡上。\n注意：keepalived主备节点之间的通信必须使用Linux网络栈内的网卡，在这个架构中可以是bond0或者是bond2.kni网卡\ndpdk网卡相关 原理分析 DPVS中的网卡命名是按照PCIe编号的顺序来命名的，使用dpdk-devbind工具我们可以看到网卡对应的PCIe编号和Linux网络栈中的网卡名称。\n如果Linux系统的网卡命名是使用eth*的命名方式并且在/etc/udev/rules.d/70-persistent-net.rules文件中固化了MAC地址和网卡名称的对应关系，那么就需要特别注意PCIe编号、DPVS网卡名、MAC地址、Linux网卡名四者之间的对应关系。\n尤其是当机器存在多个网段的网卡且做了bonding的时候，Linux网卡中的eth*和DPVS网卡中的dpdk*并不一定能一一对应，此时最好能修改相关配置并且让机房的同学调整网卡接线（当然直接在dpvs的配置文件中修改对应的网卡顺序也可以）。\n解决方案 下面的案例是一个仅供参考的比较不容易出问题的组合，eth网卡根据对应的PCIe编号升序进行命名，和dpdk网卡的命名规则一致，同时eth[0-3]为内网网卡，eth[4-5]为外网网卡，与本文的架构图对应，不容易出错。\n$ dpdk-devbind --status-dev net Network devices using DPDK-compatible driver ============================================ 0000:04:00.0 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; drv=igb_uio unused=ixgbe 0000:04:00.1 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; drv=igb_uio unused=ixgbe 0000:82:00.0 \u0026#39;Ethernet 10G 2P X520 Adapter 154d\u0026#39; drv=igb_uio unused=ixgbe 0000:82:00.1 \u0026#39;Ethernet 10G 2P X520 Adapter 154d\u0026#39; drv=igb_uio unused=ixgbe Network devices using kernel driver =================================== 0000:01:00.0 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth0 drv=ixgbe unused=igb_uio 0000:01:00.1 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth1 drv=ixgbe unused=igb_uio $ cat /etc/udev/rules.d/70-persistent-net.rules SUBSYSTEM==\u0026#34;net\u0026#34;, ACTION==\u0026#34;add\u0026#34;, DRIVERS==\u0026#34;?*\u0026#34;, ATTR{address}==\u0026#34;28:6e:45:c4:0e:48\u0026#34;, NAME=\u0026#34;eth0\u0026#34; SUBSYSTEM==\u0026#34;net\u0026#34;, ACTION==\u0026#34;add\u0026#34;, DRIVERS==\u0026#34;?*\u0026#34;, ATTR{address}==\u0026#34;28:6e:45:c4:0e:4a\u0026#34;, NAME=\u0026#34;eth1\u0026#34; SUBSYSTEM==\u0026#34;net\u0026#34;, ACTION==\u0026#34;add\u0026#34;, DRIVERS==\u0026#34;?*\u0026#34;, ATTR{address}==\u0026#34;38:e2:ba:1c:dd:74\u0026#34;, NAME=\u0026#34;eth2\u0026#34; SUBSYSTEM==\u0026#34;net\u0026#34;, ACTION==\u0026#34;add\u0026#34;, DRIVERS==\u0026#34;?*\u0026#34;, ATTR{address}==\u0026#34;38:e2:ba:1c:dd:76\u0026#34;, NAME=\u0026#34;eth3\u0026#34; SUBSYSTEM==\u0026#34;net\u0026#34;, ACTION==\u0026#34;add\u0026#34;, DRIVERS==\u0026#34;?*\u0026#34;, ATTR{address}==\u0026#34;b4:45:99:18:6c:5c\u0026#34;, NAME=\u0026#34;eth4\u0026#34; SUBSYSTEM==\u0026#34;net\u0026#34;, ACTION==\u0026#34;add\u0026#34;, DRIVERS==\u0026#34;?*\u0026#34;, ATTR{address}==\u0026#34;b4:45:99:18:6c:5e\u0026#34;, NAME=\u0026#34;eth5\u0026#34; $ dpip link -v show | grep -A4 dpdk 1: dpdk0: socket 0 mtu 1500 rx-queue 16 tx-queue 16 UP 10000 Mbps full-duplex auto-nego promisc addr 38:E2:BA:1C:DD:74 OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM pci_addr driver_name 0000:04:00:0 net_ixgbe -- 2: dpdk1: socket 0 mtu 1500 rx-queue 16 tx-queue 16 UP 10000 Mbps full-duplex auto-nego promisc addr 38:E2:BA:1C:DD:76 OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM pci_addr driver_name 0000:04:00:1 net_ixgbe -- 3: dpdk2: socket 0 mtu 1500 rx-queue 16 tx-queue 16 UP 10000 Mbps full-duplex auto-nego promisc addr B4:45:99:18:6C:5C OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM pci_addr driver_name 0000:82:00:0 net_ixgbe -- 4: dpdk3: socket 0 mtu 1500 rx-queue 16 tx-queue 16 UP 10000 Mbps full-duplex auto-nego promisc addr B4:45:99:18:6C:5E OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM pci_addr driver_name 0000:82:00:1 net_ixgbe 抓包排障 正常情况下，DPVS网络栈会劫持对应DPVS网卡的全部流量到DPVS网络栈中，因此我们使用tcpdump工具对相应的kni网卡进行抓包是没办法抓到相关的数据包的，比较方便的解决方案是使用dpip相关命令把dpvs网卡的流量forward到对应的kni网卡上，再对kni网卡进行抓包。\ndpip link set \u0026lt;port\u0026gt; forward2kni on # enable forward2kni on \u0026lt;port\u0026gt; dpip link set \u0026lt;port\u0026gt; forward2kni off # disable forward2kni on \u0026lt;port\u0026gt; 对于本文架构图中的dpvs节点，命令中的\u0026lt;port\u0026gt;一般为使用dpip命令查看到的bond1网卡和bond2网卡。\n也可以查看下面这个官方的参考链接：\nhttps://github.com/iqiyi/dpvs/blob/master/doc/tutorial.md#packet-capture-and-tcpdump\n注意：forward2kni操作非常影响性能，请不要在线上服务节点进行此操作！\nkni网卡相关 这里主要承接上面介绍kni网卡的作用以及相关的一些问题和解决思路\nkni网卡的作用 一般来说DPVS的网卡都会在Linux网络栈中虚拟一个对应的kni网卡，**考虑到默认情况下，对于所有的kni网卡来说，它们的流量都会被DPVS程序劫持。**在本文的架构中，kni网卡的主要作用还是辅助定位故障以及做少量补充工作。\n当dpvs网卡出现问题时，可以把流量forward到kni网卡进行DEBUG，当VIP出现问题的时候，可以用于刷新VIP的MAC地址 kni网卡本身也是一个虚拟网卡，只是所有流量都被DPVS劫持，可以在DPVS中配置路由放行特定的流量到kni网卡实现补充工作，如DPVS节点偶尔需要连接外网的时候可以通过bond2.kni放行该外网IP然后访问外网 kni网卡路由干扰 案例复现 在本图的架构中，bond1.kni和bond0网卡在定位上都是属于内网网卡，如果两个网卡都是同一个网段的话，就需要尤其注意内网流量进出网卡的情况。这里我们使用一台虚拟机进行示例：\n$ ip a ... 2: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 52:54:00:66:3b:08 brd ff:ff:ff:ff:ff:ff inet 10.31.100.2/16 brd 10.31.255.255 scope global noprefixroute eth0 valid_lft forever preferred_lft forever 3: eth1: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 52:54:00:47:37:3e brd ff:ff:ff:ff:ff:ff inet 10.31.100.22/16 brd 10.31.255.255 scope global noprefixroute eth1 valid_lft forever preferred_lft forever ... $ ip r ... 10.31.0.0/16 dev eth0 proto kernel scope link src 10.31.100.2 metric 100 10.31.0.0/16 dev eth1 proto kernel scope link src 10.31.100.22 metric 101 ... 上面的这台虚拟机有两个处于10.31.0.0/16网段的网卡，分别是eth0（10.31.100.2）和eth1（10.31.100.22），查看路由表的时候可以看到对10.31.0.0/16这个网段有两条路由分别指向eth0和eth1的IP，不同的是两者的metric。接下来我们做个测试：\n首先我们在10.31.100.1这台机器上面ping这台虚拟机的eth1（10.31.100.22），然后直接使用tcpdump进行抓包\n# 对eth1（10.31.100.22）网卡进行抓包的时候抓不到对应的icmp包 $ tcpdump -A -n -vv -i eth1 icmp tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes ^C 0 packets captured 0 packets received by filter 0 packets dropped by kernel # 接着我们对eth0（10.31.100.2）网卡进行抓包的时候发现能够抓到外部机器（10.31.100.1）对eth1（10.31.100.22）网卡的icmp数据包 $ tcpdump -A -n -vv -i eth0 icmp tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 16:29:44.789831 IP (tos 0x0, ttl 64, id 1197, offset 0, flags [DF], proto ICMP (1), length 84) 10.31.100.1 \u0026gt; 10.31.100.22: ICMP echo request, id 16846, seq 54, length 64 E..T..@.@.Y. .d. .d...VSA..6y2.a....yA...................... !\u0026#34;#$%\u0026amp;\u0026#39;()*+,-./01234567 16:29:44.789898 IP (tos 0x0, ttl 64, id 16187, offset 0, flags [none], proto ICMP (1), length 84) 10.31.100.22 \u0026gt; 10.31.100.1: ICMP echo reply, id 16846, seq 54, length 64 E..T?;..@._. .d. .d...^SA..6y2.a....yA...................... !\u0026#34;#$%\u0026amp;\u0026#39;()*+,-./01234567 16:29:45.813740 IP (tos 0x0, ttl 64, id 1891, offset 0, flags [DF], proto ICMP (1), length 84) 10.31.100.1 \u0026gt; 10.31.100.22: ICMP echo request, id 16846, seq 55, length 64 E..T.c@.@.V. .d. 原理分析 到这里我们就可以发现：尽管10.31.100.22是在eth1上面，但是实际上流量是经过eth0，也就是说eth1上面实际并没有流量。这也就很好地对应了路由表中10.31.100.2 metric 100要小于10.31.100.22 metric 101，符合metric越小优先级越高的原则。\n将上面的情况套用到bond0和bond1.kni网卡上，也会存在相似的问题，如果开启了IPv6网络，还需要考虑是否会有bond1.kni网卡在IPv6网络路由通告下发默认网关路由。这样一来就容易存在路由流量可能走bond0也可能走bond1.kni的问题，抛开两者物理网卡和虚拟网卡的性能差距先不谈，更重要的是：\n默认情况下bond1.kni网卡的流量都会被DPVS程序劫持，所以走bond1.kni网卡的请求都会不正常； 而恰好RS节点的探活是通过Linux网络栈实现的，如果这时候到RS节点的路由是走bond1.kni网卡，就会让keepalived误认为该后端RS节点处于不可用状态，从而将其weight降为0； 如果整个集群的RS都是如此，就会导致这个集群的VIP后无可用RS（weight均为0），最终的结果就是请求无法正常转发到RS导致服务彻底不可用。 解决思路 因此在这里最方便的一种解决方案就是直接关闭bond1.kni，直接禁用，仅当需要DEBUG的时候再启用，就可以有效地避免这类问题。\nkni网卡IP不通 原理分析 因为Linux网络栈中的kni网卡和DPVS网络栈中的网卡实际上对应的是一个物理网卡（或一组物理网卡），流经这个网卡的网络流量只能由一个网络栈处理。**默认情况下，对于所有的kni网卡来说，它们的流量都会被DPVS程序劫持。**这也就意味着bond2.kni网卡上的IP不仅是无法ping通，也无法进行其他的正常访问操作。但是DPVS程序支持针对特定的IP放行相关的流量到Linux网络栈中（通过kni_host路由实现），就可以实现该IP的正常访问。\n举个例子：一组x520网卡组bonding，在Linux网络栈中显示为bond2.kni，在DPVS网络中显示为bond2，而另外的bond0网卡则只是一个在linux网络栈中的bonding网卡，与DPVS无关。我们使用一些简单的命令来进行对比：\n使用ethtool工具查看，bond0网卡能正常获取网卡速率等信息，而kni网卡则完全无法获取任何有效信息，同时在DPVS网络栈中使用dpip命令则能够看到该bond2网卡非常详细的物理硬件信息。\n使用lspci -nnv命令查看两组网卡的详细信息，我们还可以看到bond0网卡使用的是Linux的网卡驱动ixgbe，而bond2网卡使用的是DPVS的PMDigb_uio。\n$ ethtool bond0 Settings for bond0: Supported ports: [ ] Supported link modes: Not reported Supported pause frame use: No Supports auto-negotiation: No Supported FEC modes: Not reported Advertised link modes: Not reported Advertised pause frame use: No Advertised auto-negotiation: No Advertised FEC modes: Not reported Speed: 20000Mb/s Duplex: Full Port: Other PHYAD: 0 Transceiver: internal Auto-negotiation: off Link detected: yes $ ethtool bond2.kni Settings for bond2.kni: No data available $ dpip -s -v link show bond2 3: bond2: socket 0 mtu 1500 rx-queue 16 tx-queue 16 UP 20000 Mbps full-duplex auto-nego addr 00:1C:34:EE:46:E4 ipackets opackets ibytes obytes 15451492 31306 6110603685 4922260 ierrors oerrors imissed rx_nombuf 0 0 0 0 mbuf-avail mbuf-inuse 1012315 36260 pci_addr driver_name net_bonding if_index min_rx_bufsize max_rx_pktlen max_mac_addrs 0 0 15872 16 max_rx_queues max_tx_queues max_hash_addrs max_vfs 127 63 0 0 max_vmdq_pools rx_ol_capa tx_ol_capa reta_size 0 0x1AE9F 0x2A03F 128 hash_key_size flowtype_rss_ol vmdq_que_base vmdq_que_num 0 0x38D34 0 0 rx_desc_max rx_desc_min rx_desc_align vmdq_pool_base 4096 0 1 0 tx_desc_max tx_desc_min tx_desc_align speed_capa 4096 0 1 0 Queue Configuration: rx0-tx0 cpu1-cpu1 rx1-tx1 cpu2-cpu2 rx2-tx2 cpu3-cpu3 rx3-tx3 cpu4-cpu4 rx4-tx4 cpu5-cpu5 rx5-tx5 cpu6-cpu6 rx6-tx6 cpu7-cpu7 rx7-tx7 cpu8-cpu8 rx8-tx8 cpu9-cpu9 rx9-tx9 cpu10-cpu10 rx10-tx10 cpu11-cpu11 rx11-tx11 cpu12-cpu12 rx12-tx12 cpu13-cpu13 rx13-tx13 cpu14-cpu14 rx14-tx14 cpu15-cpu15 rx15-tx15 cpu16-cpu16 HW mcast list: link 33:33:00:00:00:01 link 33:33:00:00:00:02 link 01:80:c2:00:00:0e link 01:80:c2:00:00:03 link 01:80:c2:00:00:00 link 01:00:5e:00:00:01 link 33:33:ff:bf:43:e4 $ lspci -nnv ... 01:00.0 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01) ... Kernel driver in use: ixgbe Kernel modules: ixgbe ... 81:00.0 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01) ... Kernel driver in use: igb_uio Kernel modules: ixgbe 解决思路 如果想要bond2.kni的网卡上面的IP相关操作正常，可以针对该IP添加kni_host路由，具体操作如下：\n# bond2.kni_ip可以替换为任意的一个IP dpip route add \u0026lt;bond2.kni_ip\u0026gt;/32 scope kni_host dev bond2 dpip route del \u0026lt;bond2.kni_ip\u0026gt;/32 scope kni_host dev bond2 # ipv6网络操作也是一样 dpip route -6 add \u0026lt;bond2.kni_ip\u0026gt;/128 scope kni_host dev bond2 dpip route -6 del \u0026lt;bond2.kni_ip\u0026gt;/128 scope kni_host dev bond2 注意：放行的时候一定要一个IP一个IP放行，掩码一定要是32或者是128，一次批量放行多个IP会非常影响性能！\nVIP能ping通但http请求异常 对于DPVS来说，ping请求和http请求的处理逻辑是完全不一样的。对于ping请求的icmp和icmpv6数据包，都是由DPVS网络栈本身来进行处理，并不会涉及到后面的RS节点。\n能ping通则说明DPVS程序工作正常，http请求异常则说明后端的RS节点状态异常，也有可能是LIP和RS之间的通信出现了问题导致数据包无法顺利到达。\n当然还有一种可能就是：LIP和RS之间的通信正常，但是用来RS探活的网卡和RS之间的通信异常导致keepalived进程误认为RS节点出现了问题从而将weight降为0。\n这种情况的一个常见案例就是IPv6网络下的DPVS节点和RS节点跨网段通信，而DPVS节点上面没有添加ipv6的跨网段路由。\nkeepalived相关 keepalived出现的问题主要可以分为两个方面：脑裂和主备切换。\n脑裂 一般来说，keepalived脑裂的最根本原因就是两台机器都以为自己是老大（master），造成这种情况的原因主要有两个：网络不通或者配置文件错误。这两种故障原因和排查思路在网上很多帖子都十分常见，此处不做赘述。比较少见的是一种由于交换机存在BUG，使得同一个vlan内出现两组不同的vrrp_instance使用相同的virtual_router_id引发的脑裂。\n交换机BUG导致脑裂 当使用组播通信的时候，对于部分有BUG的交换机，不同的vrrp_instance之间如果virtual_router_id一致也有可能会出现脑裂。注意这里说的virtual_router_id一致指的是仅仅virtual_router_id这一个参数一样，就算authentication配置不同的密码，也是会收到对方的组播包，但是只是会报错提示received an invalid passwd，并不会出现脑裂（因为这里是不同的vrrp_instance而不是同一个vrrp_instance内的不同节点）。\n一般来说virtual_router_id的范围是1-255，很明显这个变量设计之初就是假定一个vlan内的IP数量不要超过一个C，这样virtual_router_id就可以直接使用IP的最后一截。实际上如果vlan划分合理或者规划得当的话不太容易遇到这种问题，但是如果机房网络的vlan划分过大，又或者是机房网络质量差、交换机老旧的时候，就需要额外注意virtual_router_id冲突的问题。\n下面摘录一段keepalived官网的相关描述：\narbitrary unique number from 1 to 255 used to differentiate multiple instances of vrrpd running on the same network interface and address family (and hence same socket).\nNote: using the same virtual_router_id with the same address family on different interfaces has been known to cause problems with some network switches; if you are experiencing problems with using the same virtual_router_id on different interfaces, but the problems are resolved by not duplicating virtual_router_ids, the your network switches are probably not functioning correctly.\n解决思路 组播 keepalived主备节点之间的通信默认情况下是通过组播来进行的，组播的原理这里不赘述，默认情况下不论是IPv4还是IPv6都会使用一个组播地址，对于一些常见的如BGP、VRRP协议的数据包，RFC是有提前定义划分好相对应的组播地址供其使用，keepalived官方使用的组播地址遵循定义规范，具体如下：\nMulticast Group to use for IPv4 VRRP adverts Defaults to the RFC5798 IANA assigned VRRP multicast address 224.0.0.18 which You typically do not want to change. vrrp_mcast_group4 224.0.0.18\nMulticast Group to use for IPv6 VRRP adverts (default: ff02::12) vrrp_mcast_group6 ff02::12\n如果我们没办法确认节点所处网络中是否有使用了该virtual_router_id的vrrp_instance，可以尝试修改组播地址来避免冲突。\n单播 还有一种解决方案就是不使用组播，改为使用单播。单播不仅在网络通信质量上往往比组播更好，而且也很难出现virtual_router_id冲突的问题。同样的，如果keepalived集群之间出现因为主备节点之间组播通信质量差导致频繁出现主备切换，除了改善节点之间的通信网络质量之外，也可以尝试修改通信方式为单播。\n# ipv4网络配置单播 unicast_src_ip 192.168.229.1 unicast_peer { 192.168.229.2 } # ipv6网络配置单播 unicast_src_ip 2000::1 unicast_peer { 2000::2 } 上图中的unicast_src_ip是本机的IP，而unicast_peer则是对端的IP，注意这里的unicast_peer是可以有多个IP的（对应一主一备和一主多备或者多备抢占等情况）。\n单播虽然在稳定性上的表现更加优秀，但是相应的配置量也大大增加，需要运维同学在每一组vrrp_instance都增加对应的单播配置，并且主备节点之间的配置内容也不同（一般都是unicast_src_ip和unicast_peer对调）。并且单播相关配置一旦改错几乎就会发生脑裂，这就对配置管理检查和分发提出了更高的要求。\n主备切换 keepalived主备切换的时候容易出现的问题主要是当IP已经切换到了另一台机器，但是对应交换机上面的MAC地址表记录的VIP对应的MAC地址还没有更新。这种情况常见的解决方案就是使用arping（IPv4）操作或者是ping6（IPv6）来快速手动刷新MAC记录或者是配置keepalived来自动持续刷新MAC记录。\n手动刷新MAC 对于DPVS程序，刷新IPv4的VIP的MAC地址时，如果VIP和对应的kni网卡上的IP是同一个网段，则可以直接对kni网卡使用arping命令来刷新MAC地址（kni网卡和DPVS网卡的MAC地址一致）；但是IPv6网络并没有arp这个东西，刷新MAC记录需要使用ping6命令，而这在DPVS的kni网卡中是行不通的。个人建议使用python或go之类的能够网络编程的语言编写一个简单的程序实现发送gna数据包，然后在keepalived中配置脚本当进入master状态的时候就执行脚本刷新MAC地址，即可解决IPv6下VIP切换的MAC地址更新问题。\nkeepalived刷新MAC 还有一种方案就是配置keepalived，让keepalived自己发送garp和gna数据包，keepalived配置中有比较多的vrrp_garp*相关的配置可以调整发送garp和gna数据包的参数\n# 发送garp/gna数据包的间隔。这里是每10秒发送一轮 vrrp_garp_master_refresh 10 # 每次发送三个garp/gna数据包 vrrp_garp_master_refresh_repeat 3 # 每个garp数据包的发送间隔为0.001秒 vrrp_garp_interval 0.001 # 每个gna数据包的发送间隔为0.001秒 vrrp_gna_interval 0.001 不过使用keepalived配置还有一个问题：\nkeepalived发送garp/gna数据包的网卡是interface参数指定的网卡，也就是主备节点用来通信的网卡 keepalived发送的garp/gna数据包想要生效必须要是VIP所在的DPVS中的网卡或者是对应的kni网卡 因此如果想让keepalived来刷新VIP的MAC地址，需要将这个网卡修改为本文架构图中的bond2.kni网卡，也就是对应双臂网络架构模式下的外网网卡，同时如果使用单播通信的话还需要加上对应节点的kni_host路由以确保单播能正常通信。\n小结 以上两种方案各有优劣，需要结合内外网网络质量、使用单播还是多播、网络路由配置管理、keepalived文件管理等多个因素考虑。\n集群最大连接数 这部分主要是分析对比传统的LVS-DR模式和DPVS-FNAT模式下两者的最大TCP连接数性能限制瓶颈。\nLVS-DR模式 首先我们看一下传统的LVS-DR模式下的连接表\n$ ipvsadm -lnc | head | column -t IPVS connection entries pro expire state source virtual destination TCP 00:16 FIN_WAIT 44.73.152.152:54300 10.0.96.104:80 192.168.229.111:80 TCP 00:34 FIN_WAIT 225.155.149.221:55182 10.0.96.104:80 192.168.229.117:80 TCP 00:22 ESTABLISHED 99.251.37.22:53601 10.0.96.104:80 192.168.229.116:80 TCP 01:05 FIN_WAIT 107.111.180.141:15997 10.0.96.104:80 192.168.229.117:80 TCP 00:46 FIN_WAIT 44.108.145.205:57801 10.0.96.104:80 192.168.229.116:80 TCP 12:01 ESTABLISHED 236.231.219.215:36811 10.0.96.104:80 192.168.229.111:80 TCP 01:36 FIN_WAIT 91.90.162.249:52287 10.0.96.104:80 192.168.229.116:80 TCP 01:41 FIN_WAIT 85.35.41.0:44148 10.0.96.104:80 192.168.229.112:80 从上面我们可以看出DPVS的连接表和LVS的连接表基本上大同小异，DPVS多了一列CPU核心数和LIP信息，但是从原理上有着极大的区别。\n以下分析假定其他性能限制条件无瓶颈\n首先对于LVS-DR模式而言，我们知道Client是直接和RS建立连接的，LVS在此过程中是只做数据包转发的工作，不涉及建立连接这个步骤，那么影响连接数量的就是Protocol、CIP:Port、RIP:Port这五个变量，也就是常说的五元组。\nProtocol CIP:Port RIP:Port 考虑到Protocol不是TCP就是UDP，可以将其视为常量，也就是针对LVS-DR而言：真正影响TCP连接数的是CIP:Port（RIP:Port往往是固定的），但是由于CIP:Port理论上是可以足够多的，所以这个时候TCP连接数的最大限制往往是在RS上面，也就是RS的数量和RS的性能决定了整个LVS-DR集群的最大TCP连接数。\nDPVS-FNAT模式 接着我们使用ipvsadm -lnc命令来查看一下FNAT模式下的Client\u0026lt;--\u0026gt;DPVS\u0026lt;--\u0026gt;RS之间的连接情况：\n$ ipvsadm -lnc | head [1]tcp 90s TCP_EST 197.194.123.33:56058 10.0.96.216:443 192.168.228.1:41136 192.168.229.80:443 [1]tcp 7s TIME_WAIT 26.251.198.234:21164 10.0.96.216:80 192.168.228.1:44896 192.168.229.89:80 [1]tcp 7s TIME_WAIT 181.112.211.168:46863 10.0.96.216:80 192.168.228.1:62976 192.168.229.50:80 [1]tcp 90s TCP_EST 242.73.154.166:9611 10.0.96.216:443 192.168.228.1:29552 192.168.229.87:443 [1]tcp 3s TCP_CLOSE 173.137.182.178:53264 10.0.96.216:443 192.168.228.1:8512 192.168.229.87:443 [1]tcp 90s TCP_EST 14.53.6.35:23820 10.0.96.216:443 192.168.228.1:44000 192.168.229.50:443 [1]tcp 3s TCP_CLOSE 35.13.251.48:15348 10.0.96.216:443 192.168.228.1:16672 192.168.229.79:443 [1]tcp 90s TCP_EST 249.109.242.104:5566 10.0.96.216:443 192.168.228.1:10112 192.168.229.77:443 [1]tcp 3s TCP_CLOSE 20.145.41.157:6179 10.0.96.216:443 192.168.228.1:15136 192.168.229.86:443 [1]tcp 90s TCP_EST 123.34.92.153:15118 10.0.96.216:443 192.168.228.1:9232 192.168.229.87:443 $ ipvsadm -lnc | tail [16]tcp 90s TCP_EST 89.99.59.41:65197 10.0.96.216:443 192.168.228.1:7023 192.168.229.50:443 [16]tcp 3s TCP_CLOSE 185.97.221.45:18862 10.0.96.216:443 192.168.228.1:48159 192.168.229.50:443 [16]tcp 90s TCP_EST 108.240.236.85:64013 10.0.96.216:443 192.168.228.1:49199 192.168.229.50:443 [16]tcp 90s TCP_EST 85.173.18.255:53586 10.0.96.216:443 192.168.228.1:63007 192.168.229.87:443 [16]tcp 90s TCP_EST 182.123.32.10:5912 10.0.96.216:443 192.168.228.1:19263 192.168.229.77:443 [16]tcp 90s TCP_EST 135.35.212.181:51666 10.0.96.216:443 192.168.228.1:22223 192.168.229.88:443 [16]tcp 90s TCP_EST 134.210.227.47:29393 10.0.96.216:443 192.168.228.1:26975 192.168.229.90:443 [16]tcp 7s TIME_WAIT 110.140.221.121:54046 10.0.96.216:443 192.168.228.1:5967 192.168.229.84:443 [16]tcp 3s TCP_CLOSE 123.129.23.120:18550 10.0.96.216:443 192.168.228.1:7567 192.168.229.83:443 [16]tcp 90s TCP_EST 72.250.60.207:33043 10.0.96.216:443 192.168.228.1:53279 192.168.229.86:443 然后我们逐个分析这些字段的含义：\n[1]：这个数字表示的是CPU核心数，对应我们在dpvs.conf中配置的worker cpu的cpu_id，从这个字段可以看到每个DPVS进程的worker线程工作的负载情况\ntcp：tcp或者udp，对应这一条连接的类型，这个无需解释\n90s、30s、7s、3s：对应这一条连接的时间\nCLOSE_WAIT、FIN_WAIT、SYN_RECV、TCP_CLOSE、TCP_EST、TIME_WAIT：对应这一条tcp连接的状态\n最后这一组四个IP+Port的组合就是Client\u0026lt;--\u0026gt;DPVS\u0026lt;--\u0026gt;RS的对应关系：\nCIP:Port VIP:Port LIP:Port RIP:Port 那么对于DPVS-FNAT模式来说，加入了LIP之后变成了四组IP+Port的组合，再加上前面的cpu_id和Protocol就是影响连接数的十元组。\ncpu_id Protocol CIP:Port VIP:Port LIP:Port RIP:Port 开始分析之前我们需要知道上面的这四组IP+Port的组合实际上是分为两个四元组，即CIP:Port VIP:Port为一个四元组，LIP:Port RIP:Port为一个四元组，两个四元组之间为一一对应关系 首先我们还是排除掉Protocol、VIP:Port和RIP:Port，因为这三组五个变量基本也是固定的，可以视为常量 接着是CIP:Port理论上是可以足够多的，不会对我们的集群最大TCP连接数产生影响 然后是cpu_id，虽然一台机器最多可以有16个work cpu，但是并不意味着十元组的最大连接数=除cpu_id外的九元组的最大连接数*16，DPVS程序会把cpu_id根据LIP的端口号进行分配，从而尽可能地把负载均分到所有的CPU上面。所以这里的cpu_id和LIP的端口号也是一一对应的关系 最后是LIP:Port，我们知道一个IP可以使用的端口数量最多不超过65536个，由于RIP:Port是固定的，因此这个四元组的最大TCP连接数\u0026lt;=LIP数量*65536*RIP数量 又因为两个四元组之间为一一对应关系，cpu_id和LIP的端口号也是一一对应的关系，所以对于DPVS-FNAT模式来说，LIP的数量往往才是限制整个集群最大连接数的关键，如果集群有多连接数的需求，建议在规划之初就要预留足够数量的IP给LIP使用。\n这里顺便提一下，结合官方文档和实测，x520/82599、x710网卡在使用igb_uio这个PMD的时候，在ipv6网络下fdir不支持perfect模式，建议使用signature模式，但是注意这个模式下仅可使用一个LIP，会对集群的最大连接数有限制。\n官方文档链接可以点击这里查看。\nWe found there exists some NICs do not (fully) support Flow Control of IPv6 required by IPv6. For example, the rte_flow of 82599 10GE Controller (ixgbe PMD) relies on an old fashion flow type flow director (fdir), which doesn\u0026rsquo;t support IPv6 in its perfect mode, and support only one local IPv4 or IPv6 in its signature mode. DPVS supports the fdir mode config for compatibility.\n写在最后 DPVS确实在性能和功能方面都有着非常优秀的表现，也确实在落地初期会踩很多坑，建议多看文档，多查资料，多看源码，等到真正用起来之后也确实会给我们带来很多的惊喜和收获。最后顺便提一句，如果只想搭建一个小规模集群尝尝鲜，普通的IPv4网络和常见的x520网卡就足够了，当然有条件的同学可以尝试使用ECMP架构和一些比较好的网卡（如Mellanox）。\n","permalink":"http://localhost:9011/20211203-dpvs-fullnat-keepalived/","summary":"\u003cp\u003e本文主要介绍基于\u003ccode\u003eCentOS7.9\u003c/code\u003e系统部署DPVS的FullNAT模式在使用keepalived进行主备模式配置高可用集群在线上生产环境落地实践时遇到的一些问题和处理的思路。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e文中所有IP地址、主机名、MAC地址信息均已进行脱敏或魔改处理，客户端IP使用模拟器生成，但不影响阅读体验。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"DPVS-FullNAT模式keepalived篇"},{"content":"本文主要介绍基于CentOS7.9系统部署DPVS的FullNAT模式的各种部署方式和配置管理，包括IPv4-IPv4、bonding、IPv6-IPv6、IPv6-IPv4（NAT64）和keepalived模式这五种方案。\n以下的配置全部基于双臂模式，并且RS机器上面已经安装了DPVS相应的toa模块。我们先从单个网卡的IPv4简单配置开始，接着再做bonding配置，然后再进行IPv6简单配置，NAT64配置以及最后使用keepalived配置主备模式。\n本文中安装的DPVS版本为1.8-10，dpdk版本为18.11.2，详细安装过程已在之前的文章DPVS-FullNAT模式部署篇 - TinyChen\u0026rsquo;s Studio中叙述过，这里不做赘述。\nIPv4简单配置 架构图 首先是最简单的配置方式，直接使用ipvsadm的命令行操作来实现一个IPv4网络的FullNat模式，架构图如下：\n这里我们使用dpdk2网卡作为wan口，dpdk0网卡作为lan口\n配置过程 # 首先我们把VIP 10.0.96.204 加到dpdk2网卡（wan）上 $ dpip addr add 10.0.96.204/32 dev dpdk2 # 接着我们需要添加两条路由，分为是wan口网段的路由和到RS机器网段的路由 $ dpip route add 10.0.96.0/24 dev dpdk2 $ dpip route add 192.168.229.0/24 dev dpdk0 # 最好再加一条到网关的默认路由保证ICMP数据包的回包能跑通 $ dpip route add default via 10.0.96.254 dev dpdk2 # 使用RR算法建立转发规则 # add service \u0026lt;VIP:vport\u0026gt; to forwarding, scheduling mode is RR. # use ipvsadm --help for more info. $ ipvsadm -A -t 10.0.96.204:80 -s rr # 这里为了方便测试我们只添加一台RS # add two RS for service, forwarding mode is FNAT (-b) $ ipvsadm -a -t 10.0.96.204:80 -r 192.168.229.1 -b # 添加LocalIP到网络中，FNAT模式这里需要 # add at least one Local-IP (LIP) for FNAT on LAN interface $ ipvsadm --add-laddr -z 192.168.229.204 -t 10.0.96.204:80 -F dpdk0 # 然后我们查看一下效果 $ dpip route show inet 192.168.229.204/32 via 0.0.0.0 src 0.0.0.0 dev dpdk0 mtu 1500 tos 0 scope host metric 0 proto auto inet 10.0.96.204/32 via 0.0.0.0 src 0.0.0.0 dev dpdk2 mtu 1500 tos 0 scope host metric 0 proto auto inet 10.0.96.0/24 via 0.0.0.0 src 0.0.0.0 dev dpdk2 mtu 1500 tos 0 scope link metric 0 proto auto inet 192.168.229.0/24 via 0.0.0.0 src 0.0.0.0 dev dpdk0 mtu 1500 tos 0 scope link metric 0 proto auto inet 0.0.0.0/0 via 10.0.96.254 src 0.0.0.0 dev dpdk2 mtu 1500 tos 0 scope global metric 0 proto auto $ dpip addr show inet 10.0.96.204/32 scope global dpdk2 valid_lft forever preferred_lft forever inet 192.168.229.204/32 scope global dpdk0 valid_lft forever preferred_lft forever $ ipvsadm -ln IP Virtual Server version 0.0.0 (size=0) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.0.96.204:80 rr -\u0026gt; 192.168.229.1:80 FullNat 1 0 0 $ ipvsadm -G VIP:VPORT TOTAL SNAT_IP CONFLICTS CONNS 10.0.96.204:80 1 192.168.229.204 0 0 然后我们在RS上面启动一个nginx，设置返回IP和端口号，然后直接对VIP使用ping和curl命令进行测试：\n$ ping -c4 10.0.96.204 PING 10.0.96.204 (10.0.96.204) 56(84) bytes of data. 64 bytes from 10.0.96.204: icmp_seq=1 ttl=54 time=47.2 ms 64 bytes from 10.0.96.204: icmp_seq=2 ttl=54 time=48.10 ms 64 bytes from 10.0.96.204: icmp_seq=3 ttl=54 time=48.5 ms 64 bytes from 10.0.96.204: icmp_seq=4 ttl=54 time=48.5 ms --- 10.0.96.204 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 8ms rtt min/avg/max/mdev = 47.235/48.311/48.969/0.684 ms $ curl 10.0.96.204 Your IP and port is 172.16.0.1:62844 小结 该模式非常的简单，可以快速配置检验自己机器上的DPVS能否正常工作，不过由于是单点，往往较少使用。\nbonding配置 目前DPVS支持配置bonding4和bonding0，两者的配置基本相同，配置方式可以参考dpvs/conf/dpvs.conf.single-bond.sample这个文件。\n配置bonding模式的时候，不需要对slave网卡（如dpdk0等）指定kni_name这个参数，而是要在bonding中指定对应的kni_name，同时还要注意primary参数指定的网卡的MAC地址一般就是bonding网卡的MAC地址。\n! netif config netif_defs { \u0026lt;init\u0026gt; pktpool_size 1048575 \u0026lt;init\u0026gt; pktpool_cache 256 \u0026lt;init\u0026gt; device dpdk0 { rx { max_burst_size 32 queue_number 16 descriptor_number 1024 rss all } tx { max_burst_size 32 queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } ! mtu 1500 ! promisc_mode ! kni_name dpdk0.kni } \u0026lt;init\u0026gt; device dpdk1 { rx { max_burst_size 32 queue_number 16 descriptor_number 1024 rss all } tx { max_burst_size 32 queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } ! mtu 1500 ! promisc_mode ! kni_name dpdk1.kni } \u0026lt;init\u0026gt; device dpdk2 { rx { max_burst_size 32 queue_number 16 descriptor_number 1024 rss all } tx { max_burst_size 32 queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } ! mtu 1500 ! promisc_mode ! kni_name dpdk2.kni } \u0026lt;init\u0026gt; device dpdk3 { rx { max_burst_size 32 queue_number 16 descriptor_number 1024 rss all } tx { max_burst_size 32 queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } ! mtu 1500 ! promisc_mode ! kni_name dpdk3.kni } \u0026lt;init\u0026gt; bonding bond1 { mode 4 slave dpdk0 slave dpdk1 primary dpdk0 kni_name bond1.kni } \u0026lt;init\u0026gt; bonding bond2 { mode 4 slave dpdk2 slave dpdk3 primary dpdk2 kni_name bond2.kni } } 随后在配置每个worker-cpu的时候要注意port要选择对应的bond网卡\n\u0026lt;init\u0026gt; worker cpu1 { type slave cpu_id 1 port bond1 { rx_queue_ids 0 tx_queue_ids 0 } port bond2 { rx_queue_ids 0 tx_queue_ids 0 } } DPVS的bonding配置和在Linux中直接操作一样。bonding配置成功后只需要对生成的bonding网卡操作即可，使用dpip命令可以查看对应网卡的工作状态：如下面的网卡就工作在全双工、20000 Mbps速率的模式下，MTU为1500，并且在DPVS中配置了16个收发队列。\n$ dpip link show 5: bond1: socket 0 mtu 1500 rx-queue 15 tx-queue 16 UP 20000 Mbps full-duplex auto-nego addr AA:BB:CC:11:22:33 6: bond2: socket 0 mtu 1500 rx-queue 15 tx-queue 16 UP 20000 Mbps full-duplex auto-nego addr AA:BB:CC:12:34:56 IPv6简单配置 DPVS配置过程 IPv6的简单配置方法和IPv4一样，只是把对应的IPv4地址换成了IPv6地址，同时还需要额外注意一下IPv6地址指定端口的时候需要使用[]将IP地址括起来。\n# 添加VIP和相关路由 $ dpip addr add 2001::201/128 dev bond2 $ dpip route -6 add 2001::/64 dev bond2 $ dpip route -6 add 2407::/64 dev bond1 $ dpip route -6 add default via 2001::1 dev bond2 # 配置ipvsadm和RS $ ipvsadm -A -t [2001::201]:80 -s rr $ ipvsadm -a -t [2001::201]:80 -r [2407::1]:80 -b $ ipvsadm -a -t [2001::201]:80 -r [2407::2]:80 -b # 添加IPv6 LIP $ ipvsadm --add-laddr -z 2407::201 -t [2001::201]:80 -F bond1 $ dpip addr show inet6 2407::201/128 scope global bond1 valid_lft forever preferred_lft forever inet6 2001::201/128 scope global bond2 valid_lft forever preferred_lft forever $ dpip route -6 show inet6 2001::201/128 dev bond2 mtu 1500 scope host inet6 2407::201/128 dev bond1 mtu 1500 scope host inet6 2407::/64 dev bond1 mtu 1500 scope link inet6 2001::/64 dev bond2 mtu 1500 scope link inet6 default via 2001::1 dev bond2 mtu 1500 scope global $ ipvsadm -Ln IP Virtual Server version 0.0.0 (size=0) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP [2001::201]:80 rr -\u0026gt; [2407::1]:80 FullNat 1 0 0 -\u0026gt; [2407::2]:80 FullNat 1 0 0 效果检验 测试效果，确认RS上面的nginx能够正常返回用户端的真实IP和端口，则表明配置正常。\n$ curl -6 \u0026#34;http://\\[2001::201\\]\u0026#34; Your IP and port is [2408::1]:38383 NAT64配置 DPVS配置过程 架构图上和之前的两个并没太大的不同，只是IP略有差异\n# 添加VIP和相关路由 $ dpip addr add 2001::201/128 dev bond2 $ dpip route -6 add 2001::/64 dev bond2 $ dpip route add 192.168.229.0/23 dev bond1 $ dpip route -6 add default via 2001::1 dev bond2 # 配置ipvsadm和RS $ ipvsadm -A -t [2001::201]:80 -s rr $ ipvsadm -a -t [2001::201]:80 -r 192.168.229.1:80 -b $ ipvsadm -a -t [2001::201]:80 -r 192.168.229.2:80 -b # 添加IPv6 LIP $ ipvsadm --add-laddr -z 192.168.229.201 -t [2001::201]:80 -F bond1 $ dpip addr show inet6 2001::201/128 scope global bond2 valid_lft forever preferred_lft forever inet 192.168.229.201/32 scope global bond1 valid_lft forever preferred_lft forever $ dpip route show inet 192.168.229.201/32 via 0.0.0.0 src 0.0.0.0 dev bond1 mtu 1500 tos 0 scope host metric 0 proto auto inet 192.168.229.0/23 via 0.0.0.0 src 0.0.0.0 dev bond1 mtu 1500 tos 0 scope link metric 0 proto auto $ dpip route -6 show inet6 2001::201/128 dev bond2 mtu 1500 scope host inet6 2001::/64 dev bond2 mtu 1500 scope link inet6 default via 2001::1 dev bond2 mtu 1500 scope global $ ipvsadm -ln IP Virtual Server version 0.0.0 (size=0) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP [2001::201]:80 rr -\u0026gt; 192.168.229.1:80 FullNat 1 0 0 -\u0026gt; 192.168.229.2:80 FullNat 1 0 0 测试效果\n$ curl -6 \u0026#34;http://\\[2001::201\\]\u0026#34; Your IP and port is 192.168.229.201:1035 从上面的测试结果来看，即使是安装了TOA模块，也无法获取NAT64模式下客户端的源IP地址，所有的客户端IP和端口都会变成LIP转发过来的时候的IP和端口。如果是对源IP没需求的话可以忽略这个问题，如果有需求的话则需要更改RS上面的客户端程序，下面我们以nginx为例。\nNGINX支持NAT64 dpvs还为nginx提供了一个nat64的toa模块，当VIP为ipv6而RS为ipv4的时候，可以使用这个模块在nginx中获取用户真实的ipv6地址，需要我们在源码编译安装nginx之前先打上这个补丁。\n从官方的文件名我们可以看出应该是基于1.14.0版本制作的patch，首先我们使用旧版的1.14.0版本能够正常打上补丁，后续的编译安装也能正常进行\n$ pwd /home/nginx-1.14.0 $ ls auto CHANGES CHANGES.ru conf configure contrib html LICENSE man nginx-1.14.0-nat64-toa.patch README src $ cp /home/dpvs/kmod/toa/example_nat64/nginx/nginx-1.14.0-nat64-toa.patch ./ $ patch -p 1 \u0026lt; nginx-1.14.0-nat64-toa.patch patching file src/core/ngx_connection.h patching file src/core/ngx_inet.h patching file src/event/ngx_event_accept.c patching file src/http/ngx_http_variables.c 使用最新的nginx-1.21.1版本的时候会有报错\n$ cp /home/dpvs/kmod/toa/example_nat64/nginx/nginx-1.14.0-nat64-toa.patch ./ $ pwd /home/nginx-1.21.1 $ ls auto CHANGES CHANGES.ru conf configure contrib html LICENSE man nginx-1.14.0-nat64-toa.patch README src $ patch -p 1 \u0026lt; nginx-1.14.0-nat64-toa.patch patching file src/core/ngx_connection.h Hunk #1 FAILED at 144. 1 out of 1 hunk FAILED -- saving rejects to file src/core/ngx_connection.h.rej patching file src/core/ngx_inet.h Hunk #1 succeeded at 128 with fuzz 2 (offset 2 lines). patching file src/event/ngx_event_accept.c Hunk #1 succeeded at 17 (offset -5 lines). Hunk #2 succeeded at 30 (offset -5 lines). Hunk #3 succeeded at 172 (offset -5 lines). patching file src/http/ngx_http_variables.c Hunk #1 succeeded at 145 (offset 2 lines). Hunk #2 succeeded at 398 (offset 15 lines). Hunk #3 succeeded at 1311 (offset -11 lines). 仔细查看patch文件内容可以发现出现错误是因为1.21.1版本中对应部分移除了几行代码导致patch无法匹配，我们手动将那一行代码加上去\n随后就能正常编译安装了，完成之后我们可以在日志中加入$toa_remote_addr和$toa_remote_port这两个变量来获取NAT64模式下的客户端真实IP。\n效果检验 再次测试发现能够显示真正的客户端源IP地址和端口号。\n$ curl -6 \u0026#34;http://\\[2001::201\\]\u0026#34; Your remote_addr and remote_port is 192.168.229.201:1030 Your toa_remote_addr and toa_remote_port is [2408::1]:64920 # 同时在nginx的日志中也能看到对应的字段 toa_remote_addr=2408::1 | toa_remote_port=64920 | remote_addr=192.168.229.201 | remote_port=1030 keepalived配置 架构图 使用keepalived配置有两大好处：\nVIP、LIP、RS等配置参数可以固化在keepalived的配置文件中，无需每次都使用命令或脚本手动操作 keepalived可以使用VRRP协议配置主备模式（master-backup），避免了单点问题 官方的keepalived配置网络拓扑使用的是单臂模式，这里我们修改为双臂模式；同时需要注意DPVS使用的keepalived是修改过的版本，和原生版本的keepalived在配置语法和参数上稍有不同。\n和前面提到的一样，keepalived也支持IPv4-IPv4模式、IPv6-IPv6模式和NAT64模式（IPv6-IPv4）这三种模式，三者的不同只是在于路由的不同和keepalived的配置文件略有差异。\n配置kni网卡 keepalived的配置需要在正常的Linux网络栈（非DPVS实现的简单用户态网络栈）中有一个能进行正常网络通信的kni网卡。kni网卡的配置和普通网卡的配置是完全一样的，只需要将配置文件中的DEVICE改为对应的kni网卡即可。\nkni网卡的存在依赖于dpvs进程的存在，如果dpvs进程重启了，那么kni网卡不会跟着重启，而是处于DOWN状态直至我们手动将其启用（ifup）\n$ cat /etc/sysconfig/network-scripts/ifcfg-bond2.kni DEVICE=bond2.kni BOOTPROTO=static ONBOOT=yes IPADDR=10.0.96.200 NETMASK=255.255.255.0 GATEWAY=10.0.96.254 $ ip a 32: bond2.kni: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether a0:36:9f:f0:e4:c0 brd ff:ff:ff:ff:ff:ff inet 10.0.96.200/24 brd 10.0.96.255 scope global bond2.kni valid_lft forever preferred_lft forever inet6 fe80::a236:9fff:fef0:e4c0/64 scope link valid_lft forever preferred_lft forever 前面我们的dpvs.conf配置文件中会对每个dpdk网卡或者是bond网卡配置一个kni网卡（一般命名为dpdk0.kni或bond0.kni等），在前面的简单配置步骤中我们都是直接把VIP加到DPDK的网卡上，但是这样无法实现VIP的主备切换，因此这里我们需要将VIP交由keepalived程序控制。\n配置路由 keepalived模式下，对于双臂网络模式的FullNAT，我们需要加的路由一般来说可以直观地分为三大部分：VIP网段的路由，RS/LIP网段的路由，默认路由。\n# IPv4网络模式 $ dpip route add 10.0.96.0/24 dev bond2 # VIP网段的路由 $ dpip route add 192.168.229.0/23 dev bond1 # RS/LIP网段的路由 $ dpip route add default via 10.0.96.254 dev bond2 # 默认路由 # IPv6网络模式 $ dpip route -6 add 2001::/64 dev bond2 # VIP网段的路由 $ dpip route -6 add 2407::/64 dev bond1 # RS/LIP网段的路由 $ dpip route -6 add default via 2001::1 dev bond2 # 默认路由 # NAT64模式（IPv6-IPv4） # 此模式的区别在于RS要换为IPv4网段的路由 $ dpip route -6 add 2001::/64 dev bond2 # VIP网段的路由 $ dpip route add 192.168.229.0/23 dev bond1 # RS/LIP网段的路由 $ dpip route -6 add default via 2001::1 dev bond2 # 默认路由 配置keepalived 首先我们使用systemctl将keepalived管理起来，首先编写一个unit文件，配置中的路径要替换成DPVS定制版的keepalived二进制文件以及keepalived配置文件的路径（建议使用绝对路径）\n$ cat /usr/lib/systemd/system/keepalived.service [Unit] Description=DPVS modify version keepalived After=syslog.target network-online.target [Service] Type=forking PIDFile=/var/run/keepalived.pid KillMode=process ExecStart=/path/to/dpvs/bin/keepalived -f /etc/keepalived/keepalived.conf -D -d -S 0 ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target 随后我们修改keepalived的日志输出到指定的文件中方便我们定位问题\n# 对于系统使用rsyslog服务来管理日志的，可以修改 /etc/rsyslog.conf 加入下列的配置 local0.* /path/to/keepalived.log 最后我们重启相关的rsyslog日志服务和keepalived\n$ systemctl daemon-reload $ systemctl enable rsyslog.service $ systemctl restart rsyslog.service keepalived.conf 注意即使RS相同，NAT64模式和普通的IPv4模式也不能够在同一个vrrp_instance中同时定义IPv4地址和IPv6地址，因为两者使用的VRRP协议版本不同（VRRP和VRRP6）\n以下以NAT64和IPv4网络两种配置为例，截取部分重点配置\n! Configuration File for keepalived global_defs { router_id DPVS_TEST } # 配置LOCAL IP # 网卡使用DPDK的lan网段网卡 local_address_group laddr_g1 { 192.168.229.201 bond1 } # 配置IPv4模式的VIP vrrp_instance VI_1 { # 确定该VIP的状态为MASTER或者是BACKUP state MASTER # interface指定为Linux网络栈能识别到的、由dpvs虚拟出来的kni网卡 # keepalived模式需要确保该kni网卡处于up状态，此前的简单配置均不需要 interface bond2.kni # dpdk_interface指定为wan口网卡，即VIP所在的dpdk网卡 dpdk_interface bond2 # 虚拟路由ID，需要全局唯一 virtual_router_id 201 priority 100 advert_int 1 authentication { auth_type PASS auth_pass dpvstest } # 配置VIP，可以多个一组，但是不可IPv6和IPv4混用 virtual_ipaddress { 10.0.96.201 10.0.96.202 } } # 配置IPv6模式的VIP vrrp_instance VI_2 { state MASTER interface bond2.kni dpdk_interface bond2 virtual_router_id 202 priority 100 advert_int 1 authentication { auth_type PASS auth_pass dpvstest } virtual_ipaddress { 2001::201 2001::202 } } # 配置对应的VIP和RS virtual_server_group 10.0.96.201-80 { 10.0.96.201 80 10.0.96.202 80 } virtual_server group 10.0.96.201-80 { delay_loop 3 lb_algo rr lb_kind FNAT protocol TCP laddr_group_name laddr_g1 real_server 192.168.229.1 80 { weight 100 inhibit_on_failure TCP_CHECK { nb_sock_retry 2 connect_timeout 3 connect_port 80 } } real_server 192.168.229.2 80 { weight 100 inhibit_on_failure TCP_CHECK { nb_sock_retry 2 connect_timeout 3 connect_port 80 } } } virtual_server_group 10.0.96.201-80-6 { 2001::201 80 2001::202 80 } virtual_server group 10.0.96.201-80-6 { delay_loop 3 lb_algo rr lb_kind FNAT protocol TCP laddr_group_name laddr_g1 real_server 192.168.229.1 80 { weight 100 inhibit_on_failure TCP_CHECK { nb_sock_retry 2 connect_timeout 3 connect_port 80 } } real_server 192.168.229.2 80 { weight 100 inhibit_on_failure TCP_CHECK { nb_sock_retry 2 connect_timeout 3 connect_port 80 } } } keepalived启动之后，我们就可以使用dpip命令查看到各个定义的VIP，ipvsadm命令中应该可以看到各组RS状态正常 但是需要确保在keepalived运行过程中dpvs必须处于正常运行状态，并且配置文件中interface参数指定的kni网卡处于正常运行状态 每个网卡和网段的相关路由还是需要自己手动添加（IPv4、IPv6） # 检查配置的各个VIP和LIP是否生效 $ dpip addr show inet 10.0.96.202/32 scope global bond2 valid_lft forever preferred_lft forever inet6 2001::202/128 scope global bond2 valid_lft forever preferred_lft forever inet6 2001::201/128 scope global bond2 valid_lft forever preferred_lft forever inet 10.0.96.201/32 scope global bond2 valid_lft forever preferred_lft forever inet 192.168.229.201/32 scope global bond1 valid_lft forever preferred_lft forever # 检查各组RS服务是否正常 $ ipvsadm -ln IP Virtual Server version 0.0.0 (size=0) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.0.96.201:80 rr -\u0026gt; 192.168.229.1:80 FullNat 100 0 0 -\u0026gt; 192.168.229.2:80 FullNat 100 0 0 TCP 10.0.96.201:443 rr -\u0026gt; 192.168.229.1:443 FullNat 100 0 0 -\u0026gt; 192.168.229.2:443 FullNat 100 0 0 TCP 10.0.96.202:80 rr -\u0026gt; 192.168.229.1:80 FullNat 100 0 0 -\u0026gt; 192.168.229.2:80 FullNat 100 0 0 TCP 10.0.96.202:443 rr -\u0026gt; 192.168.229.1:443 FullNat 100 0 0 -\u0026gt; 192.168.229.2:443 FullNat 100 0 0 TCP [2001::201]:80 rr -\u0026gt; 192.168.229.1:80 FullNat 100 0 0 -\u0026gt; 192.168.229.2:80 FullNat 100 0 0 TCP [2001::201]:443 rr -\u0026gt; 192.168.229.1:443 FullNat 100 0 0 -\u0026gt; 192.168.229.2:443 FullNat 100 0 0 TCP [2001::202]:80 rr -\u0026gt; 192.168.229.1:80 FullNat 100 0 0 -\u0026gt; 192.168.229.2:80 FullNat 100 0 0 TCP [2001::202]:443 rr -\u0026gt; 192.168.229.1:443 FullNat 100 0 0 -\u0026gt; 192.168.229.2:443 FullNat 100 0 0 # 使用curl测试IPv6服务是否能够走通 $ curl -6 \u0026#34;http://\\[2001::202\\]\u0026#34; Your remote_addr and remote_port is 192.168.229.201:1034 Your toa_remote_addr and toa_remote_port is [2408::1]:9684 # 使用curl测试IPv4服务是否能够走通 $ curl 10.0.96.201 Your remote_addr and remote_port is 172.16.0.1:42254 Your toa_remote_addr and toa_remote_port is -:- 最后需要提醒的是，如果使用了NAT64模式，那么nginx是没办法直接获取真实的源端IP的，需要对XFF头进行设置，例如：\nproxy_set_header X-Forwarded-For \u0026#34;$real_remote_addr,$proxy_add_x_forwarded_for\u0026#34;; map $toa_remote_addr $real_remote_addr { default $toa_remote_addr; \u0026#39;-\u0026#39; $remote_addr; } map $toa_remote_port $real_remote_port { default $toa_remote_port; \u0026#39;-\u0026#39; $remote_port; } 总结 以上的多种配置中，基本上能在生产环境使用的最好就是keepalived的主备模式，此外还有一个需要交换机支持ECMP的多主模式这里因为条件有限暂时没有测试到，后面有条件了再补上。\n至于NAT64模式和IPv6-IPv6模式的选择，如果RS是nginx，那么两种模式的区别在于是在nginx上做兼容还是在RS上面配置IPv6网络，具体看实际的网络条件和运维管理工具来判断；如果RS是其他的第三方程序，不想对源代码进行太多的侵入变更，最好就是直接使用IPv6-IPv6模式。\n","permalink":"http://localhost:9011/20210810-dpvs-fullnat-management/","summary":"\u003cp\u003e本文主要介绍基于\u003ccode\u003eCentOS7.9\u003c/code\u003e系统部署DPVS的FullNAT模式的各种部署方式和配置管理，包括IPv4-IPv4、bonding、IPv6-IPv6、IPv6-IPv4（NAT64）和keepalived模式这五种方案。\u003c/p\u003e","title":"DPVS-FullNAT模式管理篇"},{"content":"本文主要介绍在CentOS7.9系统上部署DPVS的FullNAT模式和在RealServer上安装toa模块获取客户端的真实IP。\n此前的文章已经介绍过DPVS简介与部署以及DPDK在DPVS中的应用及原理分析，有需要的同学可以先补一下相关的内容。由于之前的文章中的部署步骤只介绍到了DPVS的部署，并没有涉及相关的各种负载均衡模式的配置，以及时间过去大半年之后，DPVS的版本和对应的DPDK版本都有所更新，因此这里再重新详细写一篇新的部署教程。\n本文中安装的DPVS版本为1.8-10，dpdk版本为18.11.2，和前文不同，安装步骤和操作也有差异。\n准备工作 在正式开始安装之后我们需要先对机器的硬件参数进行一些调整，DPVS官方对硬件有一定的要求（主要是因为底层使用的DPDK），dpdk官方给出了一份支持列表，虽然支持性列表上面的平台支持得很广泛，但是实际上兼容性和表现最好的似乎还是要Intel的硬件平台。\n硬件部分 硬件参数 机器型号： PowerEdge R630 CPU：两颗 Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz 内存：16G*8 DDR4-2400 MT/s（Configured in 2133 MT/s），每个CPU64G，共计128G 网卡1：Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01) 网卡2：Intel Corporation Ethernet 10G 2P X520 Adapter (rev 01) 系统：CentOS Linux release 7.9.2009 (Core) 内核：3.10.0-1160.36.2.el7.x86_64 BIOS设置 开始之前，先进入BIOS中关闭超线程和启用NUMA策略。其中DPVS是非常典型的CPU繁忙型应用（进程所在的CPU使用率一直都是100%），为了保证性能，建议关闭CPU的超线程设置。同时因为DPVS使用的是我们手动分配的大页内存，为了保证CPU亲和性，最好在BIOS中直接打开NUMA策略。\n网卡PCI ID 使用dpvs的PMD驱动接管网卡之后，如果网卡的数量较多，容易搞混，最好提前记录下对应的网卡名、MAC地址和PCI ID，避免后面操作的时候搞混。\n使用lspci命令可以查看对应网卡的PCI ID，接着我们可以查看/sys/class/net/这个目录下对应网卡名目录下的device文件，就能够得知网卡对应的PCI ID。最后就可以把网卡名-MAC地址-PCI ID三个参数串起来。\n$ lspci | grep -i net 01:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01) 01:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01) $ file /sys/class/net/eth0/device /sys/class/net/eth0/device: symbolic link to `../../../0000:01:00.0\u0026#39; 软件部分 系统软件 # 编译安装dpvs需要使用的工具以及查看CPU NUMA信息的工具 $ yum group install \u0026#34;Development Tools\u0026#34; $ yum install patch libnuma* numactl numactl-devel kernel-devel openssl* popt* libpcap-devel -y # 如果需要ipvsadm支持ipv6需要安装libnl3-devel $ yum install libnl libnl-devel libnl3 libnl3-devel -y # 注意kernel以及相应的kernel组件的版本需要和现在使用的kernel版本相对应 $ uname -r 3.10.0-1160.36.2.el7.x86_64 $ rpm -qa | grep kernel | grep \u0026#34;3.10.0-1160.36.2\u0026#34; kernel-3.10.0-1160.36.2.el7.x86_64 kernel-devel-3.10.0-1160.36.2.el7.x86_64 kernel-tools-libs-3.10.0-1160.36.2.el7.x86_64 kernel-debug-devel-3.10.0-1160.36.2.el7.x86_64 kernel-tools-3.10.0-1160.36.2.el7.x86_64 kernel-headers-3.10.0-1160.36.2.el7.x86_64 dpvs和dpdk # dpvs我们直接使用git从github拉取最新的版本 $ git clone https://github.com/iqiyi/dpvs.git # dpdk我们从官网下载18.11.2版本，放到dpvs目录下方便操作 $ cd dpvs/ $ wget https://fast.dpdk.org/rel/dpdk-18.11.2.tar.xz $ tar -Jxvf dpdk-18.11.2.tar.xz 完成上述步骤之后就可以开始下面的安装了。\n安装步骤 DPDK安装 安装dpdk-patch 在dpvs文件夹的patch目录下面有对应支持的dpdk版本的patch补丁，如果不清楚自己到底需要哪个补丁，官方的建议是全部安装\n$ ll dpvs/patch/dpdk-stable-18.11.2 total 44 -rw-r--r-- 1 root root 4185 Jul 22 12:47 0001-kni-use-netlink-event-for-multicast-driver-part.patch -rw-r--r-- 1 root root 1771 Jul 22 12:47 0002-net-support-variable-IP-header-len-for-checksum-API.patch -rw-r--r-- 1 root root 1130 Jul 22 12:47 0003-driver-kni-enable-flow_item-type-comparsion-in-flow_.patch -rw-r--r-- 1 root root 1706 Jul 22 12:47 0004-rm-rte_experimental-attribute-of-rte_memseg_walk.patch -rw-r--r-- 1 root root 16538 Jul 22 12:47 0005-enable-pdump-and-change-dpdk-pdump-tool-for-dpvs.patch -rw-r--r-- 1 root root 2189 Jul 22 12:47 0006-enable-dpdk-eal-memory-debug.patch 安装patch的操作也非常的简单\n# 我们首先把所有的patch复制到dpdk的根目录下面 $ cp dpvs/patch/dpdk-stable-18.11.2/*patch dpvs/dpdk-stable-18.11.2/ $ cd dpvs/dpdk-stable-18.11.2/ # 然后我们按照patch的文件名顺序依次进行安装 $ patch -p 1 \u0026lt; 0001-kni-use-netlink-event-for-multicast-driver-part.patch patching file kernel/linux/kni/kni_net.c $ patch -p 1 \u0026lt; 0002-net-support-variable-IP-header-len-for-checksum-API.patch patching file lib/librte_net/rte_ip.h $ patch -p 1 \u0026lt; 0003-driver-kni-enable-flow_item-type-comparsion-in-flow_.patch patching file drivers/net/mlx5/mlx5_flow.c $ patch -p 1 \u0026lt; 0004-rm-rte_experimental-attribute-of-rte_memseg_walk.patch patching file lib/librte_eal/common/eal_common_memory.c Hunk #1 succeeded at 606 (offset 5 lines). patching file lib/librte_eal/common/include/rte_memory.h $ patch -p 1 \u0026lt; 0005-enable-pdump-and-change-dpdk-pdump-tool-for-dpvs.patch patching file app/pdump/main.c patching file config/common_base patching file lib/librte_pdump/rte_pdump.c patching file lib/librte_pdump/rte_pdump.h $ patch -p 1 \u0026lt; 0006-enable-dpdk-eal-memory-debug.patch patching file config/common_base patching file lib/librte_eal/common/include/rte_malloc.h patching file lib/librte_eal/common/rte_malloc.c dpdk编译安装 $ cd dpvs/dpdk-stable-18.11.2 $ make config T=x86_64-native-linuxapp-gcc $ make # 出现Build complete [x86_64-native-linuxapp-gcc]的字样就说明make成功 $ export RTE_SDK=$PWD $ export RTE_TARGET=build 这里编译安装的过程中不会出现之前使用dpdk17.11.2版本出现的ndo_change_mtu问题\n配置hugepage 和其他的一般程序不同，dpvs使用的dpdk并不是从操作系统中索要内存，而是直接使用大页内存（hugepage），极大地提高了内存分配的效率。hugepage的配置比较简单，官方的配置过程中使用的是2MB的大页内存，这里的28672指的是分配了28672个2MB的大页内存，也就是一个node对应56GB的内存，一共分配了112GB的内存，这里的内存可以根据机器的大小来自行调整。但是如果小于1GB可能会导致启动报错。\n单个CPU的系统可以参考dpdk的官方文档\n# for NUMA machine $ echo 28672 \u0026gt; /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages $ echo 28672 \u0026gt; /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages $ mkdir /mnt/huge $ mount -t hugetlbfs nodev /mnt/huge # 需要开机自动挂载的话可以在 $ echo \u0026#34;nodev /mnt/huge hugetlbfs defaults 0 0\u0026#34; \u0026gt;\u0026gt; /etc/fstab # 配置完成后我们可以看到内存的使用率立马上升了 $ free -g\t# 配置前 total used free shared buff/cache available Mem: 125 1 122 0 1 123 $ free -g\t# 配置后 total used free shared buff/cache available Mem: 125 113 10 0 1 11 # 使用numactl查看内存状态也可以看到确实是两边的CPU内存各分配了56G $ numactl -H available: 2 nodes (0-1) node 0 cpus: 0 2 4 6 8 10 12 14 16 18 node 0 size: 64184 MB node 0 free: 4687 MB node 1 cpus: 1 3 5 7 9 11 13 15 17 19 node 1 size: 64494 MB node 1 free: 5759 MB node distances: node 0 1 0: 10 21 1: 21 10 配置ulimit 默认情况下系统的ulimit限制打开的文件描述符数量如果太小会影响dpvs正常运行，因此我们将其调大一些：\n$ ulimit -n 655350 $ echo \u0026#34;ulimit -n 655350\u0026#34; \u0026gt;\u0026gt; /etc/rc.local $ chmod a+x /etc/rc.local 挂载驱动模块 首先我们需要让系统挂载我们已经编译好的dpdk驱动（PMD驱动），然后再将网卡使用的默认驱动换为我们这里编译好的PMD驱动\n$ modprobe uio $ insmod /path/to/dpdk-stable-18.11.2/build/kmod/igb_uio.ko $ insmod /path/to/dpdk-stable-18.11.2/build/kmod/rte_kni.ko carrier=on 需要注意的是carrier参数是从DPDK v18.11版本开始新增的，默认值为off。我们需要在加载rte_kni.ko模块的时候带上carrier=on参数才能够使KNI设备工作正常。\n在dpdk-stable-18.11.2/usertools目录下有一些辅助我们安装使用dpdk的脚本，我们可以用它们来降低配置的复杂度，这里我们可以使用dpdk-devbind.py脚本来变更网卡的驱动\n# 首先我们关闭我们需要加载PMD驱动的网卡 $ ifdown eth{2,3,4,5} # 查看网卡状态，注意要特别关注网卡对应的PCI ID，下面只截取部分有用的输出结果 $ ./usertools/dpdk-devbind.py --status Network devices using kernel driver =================================== 0000:04:00.0 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth2 drv=ixgbe unused=igb_uio 0000:04:00.1 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth3 drv=ixgbe unused=igb_uio 0000:82:00.0 \u0026#39;Ethernet 10G 2P X520 Adapter 154d\u0026#39; if=eth4 drv=ixgbe unused=igb_uio 0000:82:00.1 \u0026#39;Ethernet 10G 2P X520 Adapter 154d\u0026#39; if=eth5 drv=ixgbe unused=igb_uio 从上面的输出结果我们可以看到目前的网卡使用的是ixgbe驱动，而我们的目标是让其使用igb_uio驱动。注意如果这个时候系统的网卡太多，前面我们记录下来的网卡名-MAC地址-PCI ID三个参数就可以派上用场了。\n# 对需要使用dpvs的网卡加载特定的驱动 $ ./usertools/dpdk-devbind.py -b igb_uio 0000:04:00.0 $ ./usertools/dpdk-devbind.py -b igb_uio 0000:04:00.1 $ ./usertools/dpdk-devbind.py -b igb_uio 0000:82:00.0 $ ./usertools/dpdk-devbind.py -b igb_uio 0000:82:00.1 # 再次检查是否加载成功，下面只截取部分有用的输出结果 $ ./usertools/dpdk-devbind.py --status Network devices using DPDK-compatible driver ============================================ 0000:04:00.0 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; drv=igb_uio unused=ixgbe 0000:04:00.1 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; drv=igb_uio unused=ixgbe 0000:82:00.0 \u0026#39;Ethernet 10G 2P X520 Adapter 154d\u0026#39; drv=igb_uio unused=ixgbe 0000:82:00.1 \u0026#39;Ethernet 10G 2P X520 Adapter 154d\u0026#39; drv=igb_uio unused=ixgbe DPVS安装 $ cd /path/to/dpdk-stable-18.11.2/ $ export RTE_SDK=$PWD $ cd /path/to/dpvs $ make $ make install # 查看bin目录下的二进制文件 $ ls /path/to/dpvs/bin/ dpip dpvs ipvsadm keepalived # 注意查看make过程中的提示信息，尤其是keepalived部分，如果出现下面的部分则表示IPVS支持IPv6 Keepalived configuration ------------------------ Keepalived version : 2.0.19 Compiler : gcc Preprocessor flags : -D_GNU_SOURCE -I/usr/include/libnl3 Compiler flags : -g -g -O2 -fPIE -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -O2 Linker flags : -pie -Wl,-z,relro -Wl,-z,now Extra Lib : -lm -lcrypto -lssl -lnl-genl-3 -lnl-3 Use IPVS Framework : Yes IPVS use libnl : Yes IPVS syncd attributes : No IPVS 64 bit stats : No # 为了方便管理可以将相关的操作命令软链接到/sbin下方便全局执行 $ ln -s /path/to/dpvs/bin/dpvs /sbin/dpvs $ ln -s /path/to/dpvs/bin/dpip /sbin/dpip $ ln -s /path/to/dpvs/bin/ipvsadm /sbin/ipvsadm $ ln -s /path/to/dpvs/bin/keepalived /sbin/keepalived # 检查dpvs相关命令能否正常工作,注意其他命令要在dpvs进程启动后才能正常使用 $ dpvs -v dpvs version: 1.8-10, build on 2021.07.26.15:34:26 配置dpvs.conf 在dpvs/conf目录下面有着各种配置方式的dpvs配置文件范例，同时在dpvs.conf.items文件中记录了所有的参数，建议同学们全部阅读一遍了解了基本语法之后再进行配置。默认的dpvs启动的配置文件的是/etc/dpvs.conf。\n这里简单摘几个部分出来说一下（!为注释符号）：\n日志的格式可以手动调成DEBUG并且修改日志输出的位置方便定位问题 global_defs { log_level DEBUG log_file /path/to/dpvs/logs/dpvs.log } 如果需要定义多个网卡，可以参考这个配置 netif_defs { \u0026lt;init\u0026gt; pktpool_size 1048575 \u0026lt;init\u0026gt; pktpool_cache 256 \u0026lt;init\u0026gt; device dpdk0 { rx { queue_number 16 descriptor_number 1024 rss all } tx { queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } kni_name dpdk0.kni } \u0026lt;init\u0026gt; device dpdk1 { rx { queue_number 16 descriptor_number 1024 rss all } tx { queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } kni_name dpdk1.kni } \u0026lt;init\u0026gt; device dpdk2 { rx { queue_number 16 descriptor_number 1024 rss all } tx { queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } kni_name dpdk2.kni } \u0026lt;init\u0026gt; device dpdk3 { rx { queue_number 16 descriptor_number 1024 rss all } tx { queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } kni_name dpdk3.kni } } 多个网卡的同一个收发队列共用同一个CPU \u0026lt;init\u0026gt; worker cpu1 { type slave cpu_id 1 port dpdk0 { rx_queue_ids 0 tx_queue_ids 0 } port dpdk1 { rx_queue_ids 0 tx_queue_ids 0 } port dpdk2 { rx_queue_ids 0 tx_queue_ids 0 } port dpdk3 { rx_queue_ids 0 tx_queue_ids 0 } } 如果需要单独指定某个CPU来处理ICMP数据包，可以在该worker的参数中添加icmp_redirect_core \u0026lt;init\u0026gt; worker cpu16 { type slave cpu_id 16 icmp_redirect_core port dpdk0 { rx_queue_ids 15 tx_queue_ids 15 } } DPVS进程启动后可以直接在Linux系统的网络配置文件中对相应的网卡进行配置，使用起来和其他的eth0之类的网卡是完全一样的。\n运行成功之后，使用dpip命令和正常的ip、ifconfig命令都能够看到对应的dpdk网卡，IPv4和IPv6网络都能够正常使用。下图只截取部分信息，IP和MAC信息已脱敏，IPv6信息已摘除。\n$ dpip link show 1: dpdk0: socket 0 mtu 1500 rx-queue 16 tx-queue 16 UP 10000 Mbps full-duplex auto-nego addr AA:BB:CC:23:33:33 OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM $ ip a 67: dpdk0.kni: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether AA:BB:CC:23:33:33 brd ff:ff:ff:ff:ff:ff inet 1.1.1.1/24 brd 1.1.1.255 scope global dpdk0.kni valid_lft forever preferred_lft forever $ ifconfig dpdk0.kni dpdk0.kni: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 1.1.1.1 netmask 255.255.254.0 broadcast 1.1.1.255 ether AA:BB:CC:23:33:33 txqueuelen 1000 (Ethernet) RX packets 1790 bytes 136602 (133.4 KiB) RX errors 0 dropped 52 overruns 0 frame 0 TX packets 115 bytes 24290 (23.7 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 配置FullNat 为了校验我们的DPVS能够正常工作，这里我们参考官方的配置文档，先配置一个最简单的双臂模式的FNAT。参考官方的架构图并修改其中的IP地址信息我们可以得到下面的简单架构图。\n该模式下不需要使用系统自带的ip、ifconfig等工具对DPVS虚拟出的kni网卡进行配置\n这里我们使用dpdk2网卡作为wan口，dpdk0网卡作为lan口\n# 首先我们把VIP 10.0.96.204 加到dpdk2网卡（wan）上 $ dpip addr add 10.0.96.204/32 dev dpdk2 # 接着我们需要添加两条路由，分为是wan口网段的路由和到RS机器网段的路由 $ dpip route add 10.0.96.0/24 dev dpdk2 $ dpip route add 192.168.229.0/24 dev dpdk0 # 最好再加一条到网关的默认路由保证ICMP数据包的回包能跑通 $ dpip route add default via 10.0.96.254 dev dpdk2 # 使用RR算法建立转发规则 # add service \u0026lt;VIP:vport\u0026gt; to forwarding, scheduling mode is RR. # use ipvsadm --help for more info. $ ipvsadm -A -t 10.0.96.204:80 -s rr # 这里为了方便测试我们只添加一台RS # add two RS for service, forwarding mode is FNAT (-b) $ ipvsadm -a -t 10.0.96.204:80 -r 192.168.229.1 -b # 添加LocalIP到网络中，FNAT模式这里需要 # add at least one Local-IP (LIP) for FNAT on LAN interface $ ipvsadm --add-laddr -z 192.168.229.204 -t 10.0.96.204:80 -F dpdk0 # 然后我们查看一下效果 $ dpip route show inet 192.168.229.204/32 via 0.0.0.0 src 0.0.0.0 dev dpdk0 mtu 1500 tos 0 scope host metric 0 proto auto inet 10.0.96.204/32 via 0.0.0.0 src 0.0.0.0 dev dpdk2 mtu 1500 tos 0 scope host metric 0 proto auto inet 10.0.96.0/24 via 0.0.0.0 src 0.0.0.0 dev dpdk2 mtu 1500 tos 0 scope link metric 0 proto auto inet 192.168.229.0/24 via 0.0.0.0 src 0.0.0.0 dev dpdk0 mtu 1500 tos 0 scope link metric 0 proto auto inet 0.0.0.0/0 via 10.0.96.254 src 0.0.0.0 dev dpdk2 mtu 1500 tos 0 scope global metric 0 proto auto $ dpip addr show inet 10.0.96.204/32 scope global dpdk2 valid_lft forever preferred_lft forever inet 192.168.229.204/32 scope global dpdk0 valid_lft forever preferred_lft forever $ ipvsadm -ln IP Virtual Server version 0.0.0 (size=0) Prot LocalAddress:Port Scheduler Flags -\u0026gt; RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.0.96.204:80 rr -\u0026gt; 192.168.229.1:80 FullNat 1 0 0 $ ipvsadm -G VIP:VPORT TOTAL SNAT_IP CONFLICTS CONNS 10.0.96.204:80 1 192.168.229.204 0 0 然后我们在RS上面启动一个nginx，设置返回IP和端口号，看看效果：\nserver { listen 80 default; location / { default_type text/plain; return 200 \u0026#34;Your IP and port is $remote_addr:$remote_port\\n\u0026#34;; } } 直接对VIP使用ping和curl命令进行测试：\n$ ping -c4 10.0.96.204 PING 10.0.96.204 (10.0.96.204) 56(84) bytes of data. 64 bytes from 10.0.96.204: icmp_seq=1 ttl=54 time=47.2 ms 64 bytes from 10.0.96.204: icmp_seq=2 ttl=54 time=48.10 ms 64 bytes from 10.0.96.204: icmp_seq=3 ttl=54 time=48.5 ms 64 bytes from 10.0.96.204: icmp_seq=4 ttl=54 time=48.5 ms --- 10.0.96.204 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 8ms rtt min/avg/max/mdev = 47.235/48.311/48.969/0.684 ms $ curl 10.0.96.204 Your IP and port is 192.168.229.204:1033 可以发现不管在什么机器上面都只会返回LIP的IP和端口号，如果需要获取用户的真实IP，那么就需要安装TOA模块\nRS安装TOA模块 目前开源社区提供toa模块的版本比较多，这里我们为了保证兼容性，直接使用dpvs官方提供的toa和uoa模块，根据他们的官方描述，他们的toa模块是从Alibaba TOA中剥离出来\nTOA source code is included into DPVS project(in directory kmod/toa) since v1.7 to support IPv6 and NAT64. It is derived from the Alibaba TOA. For IPv6 applications which need client\u0026rsquo;s real IP address, we suggest to use this TOA version.\n由于我们这里的RS机器和DPVS机器都是使用版本的CentOS7系统，因此我们可以直接在DPVS机器上面编译toa模块，再复制到各个RS机器上使用\n$ cd /path/to/dpvs/kmod/toa/ $ make 顺利编译完成之后会在当前目录下生成一个toa.ko模块文件，这就是我们需要的文件，直接使用insmod命令加载模块然后检查\n$ insmod toa.ko $ lsmod | grep toa toa 279641 0 确保开机加载模块，可以在rc.local文件中加入下面的指令\n/usr/sbin/insmod /path/to/toa.ko # for example： # /usr/sbin/insmod /home/dpvs/kmod/toa/toa.ko 除了toa模块之外，还有针对UDP协议的uoa模块，和上面的toa模块编译安装过程完全一致，这里不再赘述。\n在RS机器上面加载了toa模块后我们再次使用curl测试效果：\n$ curl 10.0.96.204 Your IP and port is 172.16.0.1:62844 至此，整个DPVS的FullNat模式就算是部署完成并且能够正常工作了。由于DPVS支持非常多的配置组合，后面会再专门写一篇关于IPv6、nat64、keepalived、bonding、Master/Backup模式的配置。\n","permalink":"http://localhost:9011/20210728-dpvs-fullnat-deploy/","summary":"\u003cp\u003e本文主要介绍在\u003ccode\u003eCentOS7.9\u003c/code\u003e系统上部署DPVS的FullNAT模式和在RealServer上安装toa模块获取客户端的真实IP。\u003c/p\u003e","title":"DPVS-FullNAT模式部署篇"},{"content":"本文主要介绍coredns的unbound插件进行编译安装的过程及常用的配置方法。\ncoredns官方的unbound文档unbound (coredns.io)，以及unbound插件的github地址coredns/unbound: CoreDNS plugin that performs recursive queries using libunbound (github.com)，此前已经介绍过coredns编译其他插件的方法，有需要的同学可以先回顾一下。\n此外，unbound插件虽然是coredns中的External Plugins，但是从详情页面中我们可以看到Maintained by CoreDNS: CoreDNS maintainers take care of this plugin.，说明这个插件是官方维护的，在稳定性可靠性以及后续更新维护上都有不错的保证，应该是可以放心使用的。\n配置环境 要使用CGO特性，需要安装C/C++构建工具链，在macOS和Linux下是要安装GCC，在windows下是需要安装MinGW工具。同时需要保证环境变量CGO_ENABLED被设置为1，这表示CGO是被启用的状态。在本地构建时CGO_ENABLED默认是启用的，当交叉构建时CGO默认是禁止的。比如要交叉构建ARM环境运行的Go程序，需要手工设置好C/C++交叉构建的工具链，同时开启CGO_ENABLED环境变量。\n以CentOS8为例，最好提前安装好gcc、unbound-devel和unbound-libs\n$ cat /etc/redhat-release CentOS Linux release 8.2.2004 (Core) $ go env | grep \u0026#34;CGO_ENABLED\u0026#34; CGO_ENABLED=\u0026#34;1\u0026#34; $ rpm -qa | grep gcc libgcc-8.4.1-1.el8.x86_64 gcc-8.4.1-1.el8.x86_64 gcc-c++-8.4.1-1.el8.x86_64 gcc-gdb-plugin-8.4.1-1.el8.x86_64 $ rpm -qa | grep unbound unbound-devel-1.7.3-15.el8.x86_64 unbound-libs-1.7.3-15.el8.x86_64 如果在使用go get命令获取unbound插件的时候遇到下面的这个问题，可以参考这个issue的解决方案：How to fix the issue: unbound.h: No such file or directory · Issue #3 · miekg/unbound (github.com)\n$ go get github.com/coredns/unbound # github.com/miekg/unbound ../gopath/pkg/mod/github.com/miekg/unbound@v0.0.0-20210309082708-dbeefb4cdb29/unbound.go:36:10: fatal error: unbound.h: No such file or directory 36 | #include \u0026lt;unbound.h\u0026gt; | ^~~~~~~~~~~ compilation terminated. 对于红帽系的Linux可以直接安装unbound-devel，debian系的解决方案类似，只是软件包名可能略有不同（libunbound-dev）。\nyum install -y unbound-devel 随后测试发现正常\n$ go get github.com/coredns/unbound go get: added github.com/coredns/unbound v0.0.7 go get: added github.com/miekg/unbound v0.0.0-20210309082708-dbeefb4cdb29 编译安装 虽然我们go的环境变量设置启用了CGO，但是coredns的Makefile文件默认是禁用的，因此需要将里面的CGO_ENABLED参数从默认的0改为1，从而才能启用CGO。\n$ grep \u0026#34;CGO_ENABLED\u0026#34; Makefile CGO_ENABLED:=1 随后进行编译安装，安装完成后查看当前目录下的coredns二进制文件是否包含unbound插件来确定是否顺利编译安装完成。\n$ echo \u0026#34;unbound:github.com/coredns/unbound\u0026#34; \u0026gt;\u0026gt; plugin.cfg $ go generate $ go build $ make CGO_ENABLED=1 go build -v -ldflags=\u0026#34;-s -w -X github.com/coredns/coredns/coremain.GitCommit=7b43d042-dirty\u0026#34; -o coredns $ ./coredns -plugins | grep unbound dns.unbound 对比是否编译安装了unbound插件的coredns，可以发现从原来的静态二进制文件，变成了需要动态加载依赖库。因此如果需要提前编译然后大范围使用，最好保证编译环境的系统和最终的使用环境系统一致或全兼容（本文的编译环境为CentOS8.2，使用环境为RockyLinux8.4）。\n$ file coredns.static coredns.static: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped $ file coredns coredns: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, stripped $ file /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2: symbolic link to ld-2.28.so $ file /lib64/ld-2.28.so /lib64/ld-2.28.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=04c0b62c6350fa6ec9158369de8b5b489e3d084b, not stripped 如果在运行的机器上面遇到下面的报错，则需要安装上面提到的unbound-devel。\n[root@coredns sbin]# ./coredns ./coredns: error while loading shared libraries: libunbound.so.2: cannot open shared object file: No such file or directory 配置使用 语法配置 unbound [FROM] { except IGNORED_NAMES... option NAME VALUE } FROM 指的是客户端请求需要解析的域名，例如blog.tinychen.com和tinychen.com这两个的FROM都是tinychen.com IGNORED_NAMES 和except搭配使用，指定不使用unbound的zone option 可以添加unbound本身支持的一些参数，具体可以查看unbound.conf的man文档或者直接查看官网的文档 prometheus监控 unbound插件提供了两个监控指标，只要对应的zone中启用了Prometheus插件，那么就可以同时启用这两个指标(其他插件的监控指标也一样)，它们分别是：\ncoredns_unbound_request_duration_seconds{server} - duration per query. coredns_unbound_response_rcode_count_total{server, rcode} - count of RCODEs. 这两个监控指标的数据格式和内容与coredns原生的coredns_dns_request_duration_seconds和coredns_dns_response_rcode_count_total一致，因此相关的监控图表只需要套用原有的进行简单修改后就能直接使用。\n范例 除了tinychen.com这个域名其他的都使用unbound，并开启DNS最小化查询功能（DNS Query Name Minimisation）\n. { unbound { except tinychen.com option qname-minimisation yes } log errors prometheus 0.0.0.0:9253 bind 0.0.0.0 cache { success 10240 600 60 denial 5120 60 5 } } ","permalink":"http://localhost:9011/20210713-dns-07-coredns-04-unbound/","summary":"\u003cp\u003e本文主要介绍coredns的unbound插件进行编译安装的过程及常用的配置方法。\u003c/p\u003e","title":"CoreDNS篇4-编译安装unbound"},{"content":"本文主要是对nginx官方limit_rate相关指令的用法解释和一些个人理解，limit_rate主要用于限制用户和服务器之间传输的字节数，最常用的场景可能就是下载/上传限速，在如今用户网速普遍大幅提升的情况下，对于一些文件传输、视频流媒体传输等服务还是有着一定的应用场景的。\n和我们前面提过的limit_req模块和limit_conn模块不一样的是，limit_rate并没有单独的一个模块，而是在ngx_http_core_module中，同时它的相关指令也比较少，只有limit_rate和limit_rate_after这两个指令。\nlimit_rate # 语法配置 Syntax:\tlimit_rate rate; Default:\tlimit_rate 0; Context:\thttp, server, location, if in location # 示例 limit_rate 4k; limit_rate的用法非常简单，后面跟随的rate就是具体限速的阈值 注意默认的单位是bytes/s，也就是每秒传输的字节数Bytes而不是比特数bits rate可以设置为变量，从而可以实现动态限速，后面我们再详细介绍 限速指令的生效范围是根据每个连接确定的，例如上面限定每个连接的速率为4k，也就是当客户端发起两个连接的时候，速率就可以变为8k limit_rate_after # 语法配置 Syntax:\tlimit_rate_after size; Default:\tlimit_rate_after 0; Context:\thttp, server, location, if in location This directive appeared in version 0.8.0. # 示例 location /flv/ { flv; limit_rate_after 500k; limit_rate 50k; } limit_rate_after允许在传输了一部分数据之后再进行限速，例如上面的配置中就是传输的前500kbyte数据不限速，500k之后再进行限速。比较常见的应用场景如分段下载限速，超过指定大小的部分再进行限速；又或者是流媒体视频网站一般为了保证用户体验而不会对第一个画面进行限速，确保其能够尽快加载出来，等用户开始观看视频之后，再把带宽限制在合理的范围内，从而降低因客户端网速过快导致提前加载过多内容带来的额外成本。\nproxy_limit_rate proxy_limit_rate的基本原理和用法与limit_rate几乎一样，唯一不同的是proxy_limit_rate是限制nginx和后端upstream服务器之间的连接速率而limit_rate限制的是nginx和客户端之间的连接速率。需要注意的是proxy_limit_rate需要开启了proxy_buffering这个指令才会生效。\nSyntax:\tproxy_limit_rate rate; Default:\tproxy_limit_rate 0; Context:\thttp, server, location This directive appeared in version 1.7.7. 动态限速 limit_rate的一大特点就是能够使用变量，这就意味着和map指令之类的进行组合就可以实现动态限速功能，这里只列几个简单的示范\n基于时间动态限速 这里引入了nginx内置的一个ssi模块，这个模块有两个比较有意思的时间变量：$date_local和$date_gmt，分别对应当前时间和GMT时间\nModule ngx_http_ssi_module (nginx.org)\nEmbedded Variables\nThe ngx_http_ssi_module module supports two embedded variables:\n$date_local\ncurrent time in the local time zone. The format is set by the config command with the timefmt parameter.\n$date_gmt\ncurrent time in GMT. The format is set by the config command with the timefmt parameter.\n这里使用变量和map指令组合的方式，利用正则表达式匹配不同的时间段，再结合map变量将不同时间段和不同的限速对应起来。\nmap $date_local $limit_rate_time { default 4K; ~(00:|01:|02:|03:|04:|05:|06:|07:).*:.* 16K; ~(08:|12:|13:|18:).*:.* 8K; ~(19:|20:|21:|22:|23:).*:.* 16K; } limit_rate $limit_rate_time 基于变量动态限速 有些服务可能会对不用的用户进行不同的限速，例如VIP用户的速度要更快一些等，例如下面可以针对不同的cookie进行限速\nmap $cookie_User $limit_rate_cookie { gold 64K; silver 32K; copper 16K; iron 8K; } limit_rate $limit_rate_cookie 当然还可以使用GeoIP模块等其他模块针对如不同的IP进行限速等操作，用法和配置基本上都是大同小异，这里就不再赘述。\n","permalink":"http://localhost:9011/20210712-nginx-12-triple-rate-limiting-limit-rate/","summary":"\u003cp\u003e本文主要是对nginx官方\u003ca href=\"http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate\"\u003elimit_rate\u003c/a\u003e相关指令的用法解释和一些个人理解，\u003ccode\u003elimit_rate\u003c/code\u003e主要用于限制用户和服务器之间传输的字节数，最常用的场景可能就是下载/上传限速，在如今用户网速普遍大幅提升的情况下，对于一些文件传输、视频流媒体传输等服务还是有着一定的应用场景的。\u003c/p\u003e","title":"nginx篇12-限速三剑客之limit_rate"},{"content":"本文主要是对nginx官方limit_conn相关模块的配置用法和一些个人理解，limit_conn主要用于限制用户的连接数，在如今多线程并发请求大量普及的情况下，对于一些特殊的场景还是有着一定的用处的。\n背景 目前来说在nginx上面我们常见的三种限速操作分别是：限制请求数(request)、限制连接数(connection)、限制响应速度(rate)，对应在nginx的模块相关指令分别是limit_req、limit_conn和limit_rate三个系列。limit_conn模块和limit_req模块类似，有着许多个指令组成一个大的模块，两个模块之间有很多指令的命名方式和用法也大同小异，对limit_req模块有兴趣的可以点这里查看之前的文章。\nngx_http_limit_conn_module 模块主要是用于根据特定的key来限制连接的数量，例如根据IP地址来限制连接数。需要注意的是并不是所有的连接都会被算入其中，只有当一个连接的整个请求头被读取并且已经被nginx服务器处理的时候才会算入限制中。这里我们重点介绍的还是limit_conn和limit_conn_zone这两个指令。\nlimit_conn_zone指令 # 语法配置 Syntax:\tlimit_conn_zone key zone=name:size; Default:\t— Context:\thttp # 示例 limit_conn_zone $binary_remote_addr zone=addr:10m; limit_conn_zone只能够在http块中使用\nkey就是用来判定连接数的变量，这个变量可以是文本、变量或它们的组合，例如我们可以使用IP地址+cookie等其他复杂的组合来更精确地限定范围\nname就是这个zone的命名，经过实测name需要全局唯一，不可以和其他的limit_conn_zone的相同，毕竟后面的limit_conn命令需要根据这个name来查找对应的zone进行相应限制规则的匹配\nsize定义了这个zone的大小，也就是nginx会在内存中开辟多大的空间来存储这个zone的相关信息，主要和前面定义的key的大小有关系，需要注意的是，当内存大小耗尽的时候，nginx会直接返回错误码limit_conn_status给后续的请求\nIf the zone storage is exhausted, the server will return the error to all further requests.\nlimit_conn指令 # 语法配置 Syntax:\tlimit_conn zone number; Default:\t— Context:\thttp, server, location # 示例 limit_conn_zone $binary_remote_addr zone=addr:10m; server { location /download/ { limit_conn addr 1; } limit_conn能在http 、server、 location三个块中使用，但是需要注意的是要搭配前面提及的limit_conn_zone limit_conn指令的变量只有zone和number两个 其中zone就是前面的limit_conn_zone中的name变量，也就是对应着全局唯一的zone，负责确定限制连接数的依据 其中number就是限制的连接数，zone和number组合就可以完成连接数的限定功能，注意这里的number必须使用数字而不能使用变量 对于开启了HTTP2的请求来说，每个并发请求都会被当作一个单独的连接\nIn HTTP/2 and SPDY, each concurrent request is considered a separate connection.\n其他指令 其他的一些指令用法相对简单，这里简单描述一下\nlimit_zone(已弃用) Syntax:\tlimit_zone name $variable size; Default:\t— Context:\thttp 生于1.1.8版本，卒于1.7.6版本\nlimit_conn_status Syntax:\tlimit_conn_status code; Default:\tlimit_conn_status 503; Context:\thttp, server, location This directive appeared in version 1.3.15. limit_conn_status这个指令是用来指定nginx回复那些被禁用的连接请求时的状态码，默认情况下是503（Service Unavailable 服务不可用），如果是一些有特殊需求的场景，可以手动调整为403之类的状态码，需要注意的是并不是所有的状态码都可以使用，nginx官方限定状态码必须在400到599之间。\nlimit_conn_dry_run Syntax:\tlimit_conn_dry_run on | off; Default:\tlimit_conn_dry_run off; Context:\thttp, server, location This directive appeared in version 1.17.6. dry_run模式的意义在于试运行而不对线上业务造成影响。设置为on之后，前面的limit_conn指令并不会真正生效，但是limit_conn_zone指令会生效，nginx会在内存中存储计算相关的数据。\nlimit_conn_log_level Syntax:\tlimit_conn_log_level info | notice | warn | error; Default:\tlimit_conn_log_level error; Context:\thttp, server, location This directive appeared in version 0.8.18. 用来调试的日志，从测试结果来看，会输出到error.log中而不是access.log，调成info的话会有较多的日志输出，需要额外注意硬盘容量等相关问题。\n","permalink":"http://localhost:9011/20210708-nginx-11-triple-rate-limiting-limit-conn/","summary":"\u003cp\u003e本文主要是对nginx官方\u003ca href=\"https://nginx.org/en/docs/http/ngx_http_limit_conn_module.html\"\u003elimit_conn\u003c/a\u003e相关模块的配置用法和一些个人理解，\u003ccode\u003elimit_conn\u003c/code\u003e主要用于限制用户的连接数，在如今多线程并发请求大量普及的情况下，对于一些特殊的场景还是有着一定的用处的。\u003c/p\u003e","title":"nginx篇11-限速三剑客之limit_conn"},{"content":"Rocky Linux — A community-driven effort to bring you enterprise-grade, production-ready Linux.\n背景介绍 这里摘录一段官网的简介，有兴趣的同学可以访问官网获取更多信息，官网提供了多种语言支持，在体验上还是相当不错的。\nRocky Linux 是一个社区化的企业级操作系统。其设计为的是与美国顶级企业 Linux 发行版实现 100％ Bug 级兼容，而原因是后者的下游合作伙伴转移了发展方向。目前社区正在集中力量发展有关设施。Rocky Linux 由 CentOS 项目的创始人 Gregory Kurtzer 领导。\nRocky Linux 从8.2版本开始内测，随后8.3版本开始RC，8.4版本开始GA，效率上还是相当高的。我们这里以8.4版本为例进行安装体验。值得顺便一提的是，除了常规的安装镜像，Rocky Linux 还提供了WSL相关的安装包，各种版本的支持都相当全面，目前看来确实给人感觉比较靠谱。\n安装体验 这里使用kvm虚拟机来进行安装体验。Rocky Linux 的整体安装过程和centos8并没有太大的区别，内置的一些软件也基本一致。除了一些关键的名称和logo、图片等进行了变更，更加契合Rocky Linux 本身的形象气质。\n常用软件 $ uname -r 4.18.0-305.3.1.el8_4.x86_64 $ lsb_release -a LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch Distributor ID: Rocky Description: Rocky Linux release 8.4 (Green Obsidian) Release: 8.4 Codename: GreenObsidian $ rpm -qa | grep glibc glibc-2.28-151.el8.x86_64 $ yum --version 4.4.2 Installed: dnf-0:4.4.2-11.el8.noarch at Mon 05 Jul 2021 07:46:22 AM GMT Built : infrastructure@rockylinux.org at Tue 25 May 2021 07:59:44 PM GMT Installed: rpm-0:4.14.3-14.el8_4.x86_64 at Mon 05 Jul 2021 08:22:57 AM GMT Built : infrastructure@rockylinux.org at Tue 29 Jun 2021 05:33:33 PM GMT $ dnf --version 4.4.2 Installed: dnf-0:4.4.2-11.el8.noarch at Mon 05 Jul 2021 07:46:22 AM GMT Built : infrastructure@rockylinux.org at Tue 25 May 2021 07:59:44 PM GMT Installed: rpm-0:4.14.3-14.el8_4.x86_64 at Mon 05 Jul 2021 08:22:57 AM GMT Built : infrastructure@rockylinux.org at Tue 29 Jun 2021 05:33:33 PM GMT $ rpm --version RPM version 4.14.3 yum源支持 使用centos8同样的epel、elrepo和docker源，发现都能够兼容安装并且正常使用。这也意味着使用国内的镜像源也是完全没有问题，这里使用的是中科大的镜像源，使用起来完全正常。\n$ ll total 72 -rw-r--r--. 1 root root 1919 Jul 5 16:38 docker-ce.repo -rw-r--r--. 1 root root 1906 Jun 16 2020 elrepo.repo -rw-r--r--. 1 root root 1177 Dec 6 2020 epel-modular.repo -rw-r--r--. 1 root root 1259 Dec 6 2020 epel-playground.repo -rw-r--r--. 1 root root 1114 Dec 6 2020 epel.repo -rw-r--r--. 1 root root 1276 Dec 6 2020 epel-testing-modular.repo -rw-r--r--. 1 root root 1213 Dec 6 2020 epel-testing.repo -rw-r--r--. 1 root root 700 Jun 19 22:20 Rocky-AppStream.repo -rw-r--r--. 1 root root 685 Jun 19 22:20 Rocky-BaseOS.repo -rw-r--r--. 1 root root 713 Jun 19 22:20 Rocky-Devel.repo -rw-r--r--. 1 root root 685 Jun 19 22:20 Rocky-Extras.repo -rw-r--r--. 1 root root 721 Jun 19 22:20 Rocky-HighAvailability.repo -rw-r--r--. 1 root root 680 Jun 19 22:20 Rocky-Media.repo -rw-r--r--. 1 root root 685 Jun 19 22:20 Rocky-Plus.repo -rw-r--r--. 1 root root 705 Jun 19 22:20 Rocky-PowerTools.repo -rw-r--r--. 1 root root 736 Jun 19 22:20 Rocky-ResilientStorage.repo -rw-r--r--. 1 root root 671 Jun 19 22:20 Rocky-RT.repo -rw-r--r--. 1 root root 2407 Jun 19 22:20 Rocky-Sources.repo epel源 elrepo源 docker源 尝试安装docker的时候报软件冲突错误：\n看起来是已经内置的podman、buildah和要安装的docker套件冲突了，卸载冲突的软件后再进行重新安装即可正常运行了\n默认源 我们查看一下Rocky Linux的默认源，跳转到对应的网址可以看到相对应的镜像\n$ cat Rocky-BaseOS.repo # Rocky-BaseOS.repo # # The mirrorlist system uses the connecting IP address of the client and the # update status of each mirror to pick current mirrors that are geographically # close to the client. You should use this for Rocky updates unless you are # manually picking other mirrors. # # If the mirrorlist does not work for you, you can try the commented out # baseurl line instead. [baseos] name=Rocky Linux $releasever - BaseOS mirrorlist=https://mirrors.rockylinux.org/mirrorlist?arch=$basearch\u0026amp;repo=BaseOS-$releasever #baseurl=http://dl.rockylinux.org/$contentdir/$releasever/BaseOS/$basearch/os/ gpgcheck=1 enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial 简单查看了一下发现国内的镜像源还不少，山东大学、重庆大学、上海交通大学等都有镜像源，目前来看满足正常使用是没有问题的。\n发展前景 Rocky Linux的目标就是为了接替CentOS的位置，成为开源社区的一个独立的稳定可靠的Linux发行版，同时会100%兼容RHEL，在RHEL的下游进行更新操作，同时创始人目前表示为了让Rocky Linux不再成为下一个CentOS（指被收购后丧失社区控制权从RHEL的下游转到上游）做了许多工作，除了拉了非常多厉害的赞助商之外，还专门成立了Rocky Enterprise Software Foundation (RESF) 基金会，同时制订了开源社区章程，还在规划更好的文档社区，一切看起来都非常的不错。\n尽管初来乍到，Rocky Linux 项目还是拥有相当高的人气和推动力。有报道称，在发布会后 72 小时内，由 Fastly 提供的 Tier0 镜像就迎来了将近 70000 次的下载量，此外 torrent 中子文件的下载量也有将大约 10000 次。\nSummarized, the Rocky Enterprise Software Foundation (RESF) is a Public Benefit Corporation (PBC) formed in Delaware (file number 4429978), backed by a board of advisors with access control policies that utilize the principle of least privilege and separation of duty to ensure that no action can be taken unilaterally (not even by the legal owner, Gregory Kurtzer). For more information, see our Organizational Structure.\n目前看来Rocky Linux的赞助商阵容还是相对不错的，CIQ作为创始赞助商（官网首页宣传上了Rocky Linux），45Drives作为存储赞助商，还有国外的云计算三巨头微软谷歌亚马逊也表示会把Rocky Linux系统加入到它们的云服务器系统镜像列表中。此外Rocky Linux 8.4 GA版本发布后的下载量也是相当不错，据种子文件的统计大概在72小时内达到了八万次左右，表明社区期盼值是相当高的。\n总而言之，如果能够按照目前这个势头发展下去，Rocky Linux确实未来可期。\n","permalink":"http://localhost:9011/20210705-rockylinux-quick-start-experience/","summary":"\u003cp\u003eRocky Linux — A community-driven effort to bring you enterprise-grade, production-ready Linux.\u003c/p\u003e","title":"RockyLinux简单安装上手体验"},{"content":"本文主要对coredns的prometheus监控进行介绍，以及对grafana中配置coredns的dashboard进行分享。\n背景 Prometheus插件作为coredns的Plugins，默认情况下是内置在coredns中，如果是自己编译安装的版本，需要注意在编译安装的时候的plugin.cfg文件中添加了prometheus:metrics，这样才能确保编译成功。\n# 首先我们检查一下运行的版本 $ ./coredns -plugins | grep prometheus dns.prometheus 简介 prometheus 插件主要用于暴露CoreDNS相关的监控数据，除了coredns本身外，其他支持prometheus的插件（如cache插件）在启用的时候也可以通过prometheus插件暴露出相关的监控信息，默认情况下暴露出的监控数据在localhost:9153，路径为/metrics，**配置文件中的每个server块只能使用一次prometheus。**下面是一些coredns自身相关的指标：\ncoredns_build_info{version, revision, goversion} - 关于 CoreDNS 本身的信息 coredns_panics_total{} - panics的总数 coredns_dns_requests_total{server, zone, proto, family, type} - 总查询次数 coredns_dns_request_duration_seconds{server, zone, type} - 处理每个查询的耗时 coredns_dns_request_size_bytes{server, zone, proto} - 请求的大小（以bytes为单位） coredns_dns_do_requests_total{server, zone} - 设置了 DO 位的查询（queries that have the DO bit set） coredns_dns_response_size_bytes{server, zone, proto} - 响应的大小（以bytes为单位） coredns_dns_responses_total{server, zone, rcode} - 每个zone的响应码和数量 coredns_plugin_enabled{server, zone, name} - 每个zone上面的各个插件是否被启用 需要注意的是上面频繁出现的几个标签(label)，这里额外做一些解释：\nzone：每个request/response相关的指标都会有一个zone的标签，也就是上述的大多数监控指标都是可以细化到每一个zone的。这对于需要具体统计相关数据和监控排查问题的时候是非常有用的 server：是用来标志正在处理这个对应请求的服务器，一般的格式为\u0026lt;scheme\u0026gt;://[\u0026lt;bind\u0026gt;]:\u0026lt;port\u0026gt;，默认情况下应该是dns://:53，如果使用了bind插件指定监听的IP，那么就可能是dns://127.0.0.53:53这个样子 proto：指代的就是传输的协议，一般就是udp或tcp family：指代的是传输的IP协议代数，(1 = IP (IP version 4), 2 = IP6 (IP version 6)) type：指代的是DNS查询的类型，这里被分为常见的如(A, AAAA, MX, SOA, CNAME, PTR, TXT, NS, SRV, DS, DNSKEY, RRSIG, NSEC, NSEC3, IXFR, AXFR and ANY) 和其他类型 “other” If monitoring is enabled, queries that do not enter the plugin chain are exported under the fake name “dropped” (without a closing dot - this is never a valid domain name).\n配置 coredns中想要启用prometheus插件，只需要在对应的zone中加上这一行配置即可，默认监听的是本机127.0.0.1的9153端口，当然也可以根据自己的需要更改监听的网卡和端口。\nSyntax prometheus [ADDRESS] 我们直接来看一段配置：\ntinychen.com:53 { forward tinychen.com 47.107.188.168 prometheus } google.com:53 { forward google.com 8.8.8.8 9.9.9.9 prometheus 192.168.100.100:9253 } example.org { file /home/coredns/conf/example.org } prometheus的生效范围是按照zone来划分的，在上面的这个配置中：\ntinychen.com:53这个域使用的是Prometheus的默认配置，那么此时的监听情况就是默认的127.0.0.1的9153端口，请求http://127.0.0.1:9153/metrics/这个地址就能够获取到tinychen.com:53这个域的监控信息 同理在http://192.168.100.100:9253/metrics/这个地址能够获取到google.com:53这个域的监控信息 example.org 这个域因为没有添加prometheus指令，所以在任何地址都不会暴露相关的监控信息 如果有多个zone，每个zone都有相同的基础配置，也可以使用import指令，如：\ntinychen.com:53 { forward tinychen.com 47.107.188.168 log whoami errors prometheus 192.168.100.100:9253 bind 192.168.100.100 cache { success 10240 600 60 denial 5120 60 5 } } google.com:53 { forward google.com 8.8.8.8 9.9.9.9 log whoami errors prometheus 192.168.100.100:9253 bind 192.168.100.100 cache { success 10240 600 60 denial 5120 60 5 } } example.org { file /home/coredns/conf/example.org log whoami errors prometheus 192.168.100.100:9253 bind 192.168.100.100 cache { success 10240 600 60 denial 5120 60 5 } } 可以简化成这样：\n(basesnip) { log whoami errors prometheus 192.168.100.100:9253 bind 192.168.100.100 cache { success 10240 600 60 denial 5120 60 5 } } tinychen.com:53 { forward tinychen.com 47.107.188.168 import basesnip } google.com:53 { forward google.com 8.8.8.8 9.9.9.9 import basesnip } example.org { file /home/coredns/conf/example.org import basesnip } grafana配置dashboard coredns原生支持的prometheus指标数量和丰富程度在众多DNS系统中可以说是首屈一指的，此外在grafana的官网上也有着众多现成的dashboard可用，并且由于绝大多数指标都是通用的，多个不同的dashboard之间的panel可以随意复制拖拽组合成新的dashboard并且不用担心兼容性问题。我们可以很容易的根据自己的实际需求配置对应的权威/递归/组合DNS相关的监控项。\n如上图我们可以看到能够监控出不同DNS类型的请求数量以及不同的zone各自的请求数量，还有其他的类似请求延迟、请求总数等等各项参数都能完善地监控起来。\n如上图我们能看到可以监控到不同的请求的传输层协议状态，缓存的大小状态和命中情况等各种信息。\n","permalink":"http://localhost:9011/20210623-dns-06-coredns-03-prometheus/","summary":"\u003cp\u003e本文主要对coredns的\u003cem\u003eprometheus\u003c/em\u003e监控进行介绍，以及对grafana中配置coredns的dashboard进行分享。\u003c/p\u003e","title":"CoreDNS篇3-接入prometheus监控"},{"content":"本文主要是对nginx官方limit_req相关模块的限速原理的解释和一些个人理解，主要参考的文章为Rate Limiting with NGINX and NGINX Plus和nginx的ngx_http_limit_req_module的详细说明。\n目前来说在nginx上面我们常见的三种限速操作分别是：限制请求数(request)、限制连接数(connection)、限制响应速度(rate)，对应在nginx的模块相关指令分别是limit_req、limit_conn和limit_rate三个系列。\n前言 限速（rate limiting）是NGINX中一个非常有用但是经常被误解且误用的功能特性。我们可以用它来限制在一段时间内的HTTP请求的数量，这些请求可以是如GET这样的简单请求又或者是用来填充登录表单的POST请求。\n限速还可以用于安全防护用途，例如限制密码撞库暴力破解等操作的频率，也可以通过把请求频率限制在一个正常范围来抵御DDoS攻击。不过更常见的使用情况是通过限制请求的数量来确保后端的upstream服务器不会在短时间内遭受到大量的流量访问从而导致服务异常。\n本文会尽量覆盖nginx中限速（rate limiting）的基本概念也相关知识同时会顺带尽可能多的提一下相关的进阶配置方法。限速（rate limiting）在付费版本的nginx（Nginx Plus）中也是同样可以使用的。\nNGINX Plus R16 及之后的版本支持“全局限速（global rate limiting）”，可以在一整个nginx集群中对某个用户或者连接进行限速状态的同步。原理基本上类似于openresty+redis的工作模式，将限速的状态存储到一个集群中，当需要限速操作的时候就去集群中读取相关参数，For details, see our blog and the NGINX Plus Admin Guide.\n工作原理 nginx中限速（rate limiting）的主要算法原理就是基于在计算机网络中当带宽是有限时十分常用的漏桶算法。基本原理就是：**以漏桶为例，水从顶部倒入，从底下漏出。**这里的几个概念分别是：\n漏桶对应我们服务器的带宽或者是处理请求的能力或者是一个队列 水表示客户端发送过来的请求 倒入的水则代表客户端发送给服务器但尚未进行处理的请求，此时请求仍在队列（在桶内） 漏出的水则代表从队列中出来即将发送给服务器端处理的请求，此时请求已经离开了队列（在桶外） 漏桶在一定程度上可以代表服务器的处理能力，请求根据**先进先出（FIFO）**调度算法等待处理。如果倒入水的速度小于漏水的速度，可以理解为服务器能够处理完所有的请求，此时整体服务表现正常。如果倒入水的速度大于漏水的速度，那么水桶内的水会不断增加直到最后溢出，这种情况下在水桶中的水可以理解为在队列中等待的请求，而溢出的水则表示直接被丢弃不处理的请求。\n基础配置 下面这里我们列举一个简单的nginx限速配置：\nlimit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; server { location /login/ { limit_req zone=mylimit; proxy_pass http://my_upstream; } } 这里首先使用了limit_req_zone指令定义了一个限速zone，名为mylimit，大小为10MB，对应的变量是$binary_remote_addr，限制的请求速率是每秒限制10个请求（10requests/secends），在login这个location中使用limit_req指令指定了限制的zone。接下来我们详细解析一下整个限速的过程：\n首先是limit_req_zone指令主要用于定义速度限制相关的参数，而limit_req指令则是用于启用定义的限速参数（如这里是在login中启用）\nlimit_req_zone 指令一般用于http块中，使其可以在多个相关server、location等contexts中使用，一般来说它需要定义下面三个必要参数：\nSyntax：limit_req_zone key zone=name:size rate=rate [sync];\nSyntax: limit_req zone=name [burst=number] [nodelay | delay=number];\nkey：定义用于限制请求的变量，在这个示例中使用的是NGINX的自带变量$binary_remote_addr，它的特点是使用二进制来表示IP地址，如123.183.224.65这个IP在$remote_addr中显示为123.183.224.65，在$binary_remote_addr表示为{\\xB7\\xE0A，因此$binary_remote_addr占用的空间要比$remote_addr更少。使用$binary_remote_addr则意味着将每个唯一的用户IP作为限制速率的判断依据。\nzone：定义用于存储前面定义的key变量和限制其访问请求频率rate变量的共享内存空间，将信息保存在共享内存中的好处是能够在多个worker进程中共享。存储空间的定义由两个部分组成：zone=后面的名称以及冒号后面的大小，如zone=mylimit:10m 就是一个名为mylimit的大小为10m的共享内存空间。以$binary_remote_addr 变量为例，它使用4 bytes来存储IPv4 地址或者是使用16 bytes来存储IPv6地址。存储状态始终在32位平台上占用64个字节，并在64位平台上占用128个字节。考虑到现在的服务器绝大多数都是64位的操作系统，1M的大小可以保留大约8192个128字节的状态。\n当存储空间耗尽的时候，如果需要记录新的值，那么就会通过LRU算法移除旧的变量来腾出空间，如果这样腾出来的空间还是不足以接纳新的记录值，那么nginx就会返回状态码503 (Service Temporarily Unavailable)。此外，为了防止内存耗尽，nginx每次创建一个新记录值的时候就会清理掉两个60秒内没被使用过的旧记录值。\nIf the zone storage is exhausted, the least recently used state is removed. If even after that a new state cannot be created, the request is terminated with an error.\nrate：设定允许的最大请求速率。上面的例子是每秒十个请求(10r/s)。nginx实现的是毫秒级别的控制粒度，10r/s对应的就是1r/100ms，这也就意味着在没有设置bursts的情况下，如果一个请求接受处理之后的100ms内出现第二个请求，那么它就会被拒绝处理。\nlimit_req_zone指令设置了速率限制和共享内存区域的参数，但它实际上并不限制请求速率。因此我们需要通过在contexts中使用limit_req指令来将其限制应用于特定location或server块。在上面的例子里，我们将请求速率限制在/login/这个location块中。因此现在每个唯一的 IP 地址被限制为每秒 10 个**/login/**请求，或者更准确地说，不能在前一个 URL 请求的 100 毫秒内发出对该 URL 的第二次请求。\n突发请求处理(Bursts) 上面的基础配置只能处理最简单的理想情况，但是如果服务器在100毫秒内收到了2个及以上的请求，那么在上面的配置中，nginx就会向第1个请求之后的所有客户端返回503代码。考虑到并发是程序的天然属性，大多数情况下都是同一时间内涌入大量的请求，因此这显然并不是我们想要的处理方案，我们想要的应该是尽可能“均匀平滑”地处理所有的请求而不是直接拒绝掉它们。因此在这种情况下我们可以使用burst参数设置突发阈值，允许并发情况的处理。\nlocation /login/ { limit_req zone=mylimit burst=20; proxy_pass http://my_upstream; } 上面这段配置中我们设置了burst=20，该配置定义了客户端可以超过区域指定速率的请求数（对于我们前面定义的mylimit区域，请求速率限制为每秒 10 个请求即每 100 毫秒 1 个）。在前一个请求之后 100 毫秒内到达的请求会被放入到队列中，这里我们将队列大小设置为 20。\n也就是说如果有22个请求同时发送过来，那么NGINX会马上把第1个请求根据相关规则转发给upstream服务器，然后把接下来的第2到21共计20个请求放入队列中，接着直接返回503代码给第22个请求，随后的2秒时间内，每100毫秒从队列中取出一个请求发送给upstream服务器进行处理。\n无延迟队列(Queueing with No Delay) 上面的方法虽然使得请求的流量变得**“均匀平滑”**，但是确很大程度上增加了响应时间，排在队列越后面的请求的等待时间越长，这就导致了它们的响应时间平白无故地增加了许多，过长的响应时间甚至可能会导致客户端认为请求异常或者直接导致请求超时。为了解决这种情况，我们可以在brust参数后面加上nodelay参数。\nlocation /login/ { limit_req zone=mylimit burst=20 nodelay; proxy_pass http://my_upstream; } 加上了nodelay参数之后，nginx的处理方式和上面基本相同，唯一的区别在于：当nginx接受了第2到21共计20个请求之后，不会把它们放入队列中，而是直接将它们转发给upstream服务器，同时标记队列中的这20个插槽(slot)为已使用，然后把剩下的全部请求都503拒绝掉，接着每过100毫秒再释放一个新的slot让新的请求进来。\nWith the nodelay parameter, NGINX still allocates slots in the queue according to the burst parameter and imposes the configured rate limit, but not by spacing out the forwarding of queued requests.\nInstead, when a request arrives “too soon”, NGINX forwards it immediately as long as there is a slot available for it in the queue. It marks that slot as “taken” and does not free it for use by another request until the appropriate time has passed (in our example, after 100ms).\n现在假设在第一组请求转发后 101 毫秒，另外 20 个请求同时到达。队列中只有 1 个插槽已被释放，因此 NGINX 转发 1 个请求给upstream服务器并以 status 503 拒绝其他 19 个请求。如果在 20 个新请求到达之前 已经过去了501毫秒而不是101毫秒，则有 5 个空闲槽，因此 NGINX 立即转发 5 个请求upstream服务器并拒绝剩余15 个请求。*这样最终的效果相当于每秒 10 个请求的速率限制，只不过没有了前面的“均匀平滑”*的特性，但是却有效降低了响应时间。**因此如果我们需要在不限制每个请求之间的时间间隔的情况下限制请求速率，可以考虑使用nodelay参数。\nNote: For most deployments, we recommend including the burst and nodelay parameters to the limit_req directive.\n两段限速(Two-Stage Rate Limiting) 原理解析 简单来说，所谓的分段限速就是允许客户端在刚开始的时候有一定的突发请求，后面再进入到平稳的限速中。\n我们可以在NGINX Plus R17或者是NGINX 1.15.7使用limit_req 指令和delay参数来实现两段限速，delay参数将nginx配置为允许突发请求以适应典型的 Web 浏览器请求模式，然后将额外的过度请求限制到一定程度，超过该点的额外过度请求将被拒绝。\n这里我们以5r/s的限制速率为例，一般来说网站通常每个页面有 4 到 6 个资源，并且永远不会超过 12 个资源。该配置允许最多 12 个请求的突发，其中前 8 个请求会被直接转发给upstream处理。在达到5r/s的请求限制之后，第6到第13个请求会被添加到延迟(delay)中，再之后的任何请求都会被拒绝。\nlimit_req_zone $binary_remote_addr zone=ip:10m rate=5r/s; server { listen 80; location / { limit_req zone=ip burst=12 delay=8; proxy_pass http://website; } } 下面假设有一个客户端不断地向我们的限速服务器发出请求，根据上面的配置，nginx的处理情况如下：\n这里可以看到，从burst队列中首先最开始的12个请求可以按照配置分为8+5 即最前面的8个请求会被直接发送给upstream处理，也就是在burst队列中的no delay部分，这里的8个和配置中的参数delay=8吻合 随后的5个请求也会被加入到burst队列中，这里的处理规则就不是按照前面的no delay部分的规则来处理，而是先按照设定的rate=5r/s来进行处理，接着同样是这一秒内的其他请求都会被返回503代码拒绝掉 再进入下一秒的时间，这里的请求就和之前设定的一样，全部按照rate=5r/s来进行处理，同样是这一秒内的其他请求都会被返回503代码拒绝掉 实测 这里我们使用jmeter来进行实测查看不同的配置对应的效果，这里我们尝试5秒内发送100次请求，然后查看测试结果，由于nginx的日志写入有延迟，和这里的精确到毫秒级别的测试会有误差，这里我们以秒为单位来查看日志。\n1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:45 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:46 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:47 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:48 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:49 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:50 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:50 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:50 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:50 +0800] | 503 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:50 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:50 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:50 +0800] | 200 | test.tiny777.com 1.1.1.1 | [16/Jun/2021:15:24:50 +0800] | 200 | test.tiny777.com 这里的效果就非常明显了，由于15:24:45内刚好只有12个请求，因此全部都能够正常处理，而之后的每一秒的请求处理都超过5个，但是由于我们限定了rate=5r/s，因此每秒只有5个成功的请求，其余均被503.\n","permalink":"http://localhost:9011/20210616-nginx-10-triple-rate-limiting-limit-req/","summary":"\u003cp\u003e本文主要是对nginx官方\u003ccode\u003elimit_req\u003c/code\u003e相关模块的限速原理的解释和一些个人理解，主要参考的文章为\u003ca href=\"https://www.nginx.com/blog/rate-limiting-nginx/\"\u003e\u003ccode\u003eRate Limiting with NGINX and NGINX Plus\u003c/code\u003e\u003c/a\u003e和nginx的\u003ca href=\"https://nginx.org/en/docs/http/ngx_http_limit_req_module.html\"\u003e\u003ccode\u003engx_http_limit_req_module\u003c/code\u003e\u003c/a\u003e的详细说明。\u003c/p\u003e","title":"nginx篇10-限速三剑客之limit_req"},{"content":"本文主要是对nginx官方的文章If is Evil... when used in location context的翻译和理解。\n简介 在location块中的if指令的有点问题，在某些情况下，if指令并不会按照我们预期的那般工作，而是有可能往完全不同的方向发展甚至可能会引起错误。因此最好的方法就是尽可能的不要使用if指令。\n在location块的if指令内100%安全的指令有：\nreturn rewrite …… last 除了上面的两个指令之外的任何操作都有可能导致不可预测的效果甚至可能是SIGSEGV错误（可能出现内存错误导致程序异常终止）。\n需要注意的是：if指令的执行结果是一致的，也就是说相同的两个请求操作，不可能会出现一个成功但是另一个失败的情况。这就意味着只要经过足够合适的测试以及我们对相应操作的足够了解，if指令是*“可以”*被使用的。这个建议对于其他的指令也是同样适用的。\n很多情况下我们是没办法避免使用if指令的，例如当我们需要判断某个变量是否等于某个值或者包含某个字段的时候，就会需要用到if指令：\nif ($request_method = POST ) { return 405; } if ($args ~ post=140){ rewrite ^ http://example.com/ permanent; } 替代方案 我们可以使用 try_files 指令、“return \u0026hellip;”指令或者“rewrite \u0026hellip; last”指令用来替代if指令。在某些条件允许的情况下，也可以把if指令移动到server块的层级，此时的if指令是可以安全使用的，因为这里只有其他的rewrite模块指令能被使用（原因下文会解释）。\n当然对于一些需要使用if指令来判断返回4xx和5xx之类的异常代码响应页面或者是操作也可以尝试使用return指令搭配error_page指令来保证安全，例如下面的这个例子：\nlocation / { error_page 418 = @other; recursive_error_pages on; if ($something) { return 418; } # some configuration ... } location @other { # some other configuration ... } 此外，在某些情况下使用一些嵌入的脚本模块（如lua，embedded perl, 或者是其他的ngx第三方模块）来完成一些较负责的逻辑操作和判断也不失为一个好主意。\n已知错误 下面列举了一些if指令不按照预期工作的的情况，请注意不要在生产环境上配置这些指令。\n# Here is collection of unexpectedly buggy configurations to show that # if inside location is evil. # only second header will be present in response # not really bug, just how it works location /only-one-if { set $true 1; if ($true) { add_header X-First 1; } if ($true) { add_header X-Second 2; } return 204; } # request will be sent to backend without uri changed # to \u0026#39;/\u0026#39; due to if location /proxy-pass-uri { proxy_pass http://127.0.0.1:8080/; set $true 1; if ($true) { # nothing } } # try_files wont work due to if location /if-try-files { try_files /file @fallback; set $true 1; if ($true) { # nothing } } # nginx will SIGSEGV location /crash { set $true 1; if ($true) { # fastcgi_pass here fastcgi_pass 127.0.0.1:9000; } if ($true) { # no handler here } } # alias with captures isn\u0026#39;t correcly inherited into implicit nested # location created by if location ~* ^/if-and-alias/(?\u0026lt;file\u0026gt;.*) { alias /tmp/$file; set $true 1; if ($true) { # nothing } } 如果发现了上面没有列出出来的情况，可以邮件到NGINX development mailing list.\n原因分析 if指令本质上其实是rewrite模块的一部分，rewrite模块本身设计就是用来“命令式”地执行操作**（这就是前面说的为什么if里面搭配return和rewrite指令会100%安全的原因）**。另一方面。nginx的配置通常来说是声明式的，但是出于某些原因，用户需要在if中添加某些非重写(non-rewrite)的指令（例如使用if判断http_origin之类的参数然后添加header），就有可能出现我们前面说的不正常工作的情况，尽管大部分情况下是能够正常工作的……\n目前看来唯一的解决方案就是完全禁用掉if中的所有非重写指令（non-rewrite directives），但是这会导致很多已有的配置无法正常工作（会极大地影响前向兼容性），因此一直没有处理这个问题。\n如仍使用 如果还是需要在location中使用if指令，请务必注意：\n明确if的工作原理和流程 事先进行足够的测试以确保能够正常工作 You were warned.\n","permalink":"http://localhost:9011/20210527-nginx-09-if-is-evil-in-location/","summary":"\u003cp\u003e本文主要是对nginx官方的文章\u003ca href=\"https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/\"\u003e\u003ccode\u003eIf is Evil... when used in location context\u003c/code\u003e\u003c/a\u003e的翻译和理解。\u003c/p\u003e","title":"nginx篇09-location中的if指令是魔鬼吧"},{"content":"本文主要对bind进行安装配置，并且搭配keepalived实现高可用。\n系统环境准备 这里我们使用的是centos7的操作系统，默认使用yum安装的情况下，bind的程序named会安装到/var/named目录下，注意保证分区的大小，当然也可以使用chroot包来修改目录，这里使用默认目录\n$ lsb_release -a LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch Distributor ID: CentOS Description: CentOS Linux release 7.9.2009 (Core) Release: 7.9.2009 Codename: Core bind安装 bind的安装非常简单，默认的centos系统的yum源中就包含了其安装包，不同的系统版本对应的bind版本略有差异但是不大。\n$ yum list | egrep ^bind.x86_64 bind.x86_64 32:9.11.20-5.el8_3.1 appstream $ cat /etc/redhat-release CentOS Linux release 8.3.2011 [root@tiny-cloud ~]# yum list | egrep ^bind.x86_64 bind.x86_64 32:9.11.4-26.P2.el7_9.5 updates [root@tiny-cloud ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) 一般我们只需要安装bind和bind-utils这两个包，前者是bind的主要程序named和控制工具rndc等，后者则是一些常用工具如dig命令等。\n$ yum install bind bind-utils 安装完成之后我们就会发现系统主要新增了/var/named、/etc/named和/etc/named*一系列文件\n$ ll /var/named/ total 28K drwxrwx--- 2 named named 4.0K Apr 29 22:05 data drwxrwx--- 2 named named 4.0K Apr 29 22:05 dynamic -rw-r----- 1 root named 2.3K Apr 5 2018 named.ca -rw-r----- 1 root named 152 Dec 15 2009 named.empty -rw-r----- 1 root named 152 Jun 21 2007 named.localhost -rw-r----- 1 root named 168 Dec 15 2009 named.loopback drwxrwx--- 2 named named 4.0K Apr 29 22:05 slaves $ ll /etc/named* -rw-r----- 1 root named 1.8K Apr 29 22:06 /etc/named.conf -rw-r--r-- 1 root named 3.9K Apr 29 22:06 /etc/named.iscdlv.key -rw-r----- 1 root named 931 Jun 21 2007 /etc/named.rfc1912.zones -rw-r--r-- 1 root named 1.9K Apr 13 2017 /etc/named.root.key /etc/named: total 0 同时我们可以使用systemd来对named服务进行管理：\n$ systemctl enable named.service Created symlink from /etc/systemd/system/multi-user.target.wants/named.service to /usr/lib/systemd/system/named.service. $ systemctl start named.service $ systemctl status named.service ● named.service - Berkeley Internet Name Domain (DNS) Loaded: loaded (/usr/lib/systemd/system/named.service; enabled; vendor preset: disabled) Active: active (running) since Thu 2021-05-20 16:26:36 CST; 10min ago Main PID: 28777 (named) Tasks: 4 Memory: 58.6M CGroup: /system.slice/named.service └─28777 /usr/sbin/named -u named -c /etc/named.conf May 20 16:31:49 tiny-cloud named[28777]: validating net/SOA: got insecure response; parent indicates it should be secure May 20 16:31:49 tiny-cloud named[28777]: no valid RRSIG resolving \u0026#39;edgekey.net/DS/IN\u0026#39;: 192.12.94.30#53 May 20 16:31:49 tiny-cloud named[28777]: validating net/SOA: got insecure response; parent indicates it should be secure May 20 16:31:49 tiny-cloud named[28777]: no valid RRSIG resolving \u0026#39;edgekey.net/DS/IN\u0026#39;: 192.31.80.30#53 May 20 16:31:49 tiny-cloud named[28777]: validating net/DNSKEY: got insecure response; parent indicates it should be secure May 20 16:31:49 tiny-cloud named[28777]: insecurity proof failed resolving \u0026#39;net/DNSKEY/IN\u0026#39;: 192.12.94.30#53 May 20 16:31:50 tiny-cloud named[28777]: validating net/DNSKEY: got insecure response; parent indicates it should be secure May 20 16:31:50 tiny-cloud named[28777]: insecurity proof failed resolving \u0026#39;net/DNSKEY/IN\u0026#39;: 192.31.80.30#53 May 20 16:31:51 tiny-cloud named[28777]: validating net/SOA: got insecure response; parent indicates it should be secure May 20 16:31:51 tiny-cloud named[28777]: no valid RRSIG resolving \u0026#39;akamaiedge.net/DS/IN\u0026#39;: 192.31.80.30#53 bind配置 IPv6相关配置 无论是centos7还是centos8系统使用yum进行安装的bind都已经默认开启了IPv6的支持，如果我们的系统暂时还不支持IPv6网络，则最好把配置文件中的IPv6相关的配置去除\nsed -i \u0026#39;s#listen-on-v6 port 53 { ::1; };#//listen-on-v6 port 53 { ::1; };#g\u0026#39; /etc/named.conf 同时如果是把bind当作递归查询服务器使用，默认情况下的bind是会自动启用了hint类型的解析\nzone \u0026#34;.\u0026#34; IN { type hint; file \u0026#34;named.ca\u0026#34;; }; 该配置会把所有匹配到这个zone的DNS查询请求转发到/var/named/named.ca文件中的13个根DNS服务器节点，为了减少不必要的干扰，我们可以把文件中的的AAAA记录注释掉。\n;; ADDITIONAL SECTION: a.root-servers.net. 518400 IN A 198.41.0.4 b.root-servers.net. 518400 IN A 199.9.14.201 c.root-servers.net. 518400 IN A 192.33.4.12 d.root-servers.net. 518400 IN A 199.7.91.13 e.root-servers.net. 518400 IN A 192.203.230.10 f.root-servers.net. 518400 IN A 192.5.5.241 g.root-servers.net. 518400 IN A 192.112.36.4 h.root-servers.net. 518400 IN A 198.97.190.53 i.root-servers.net. 518400 IN A 192.36.148.17 j.root-servers.net. 518400 IN A 192.58.128.30 k.root-servers.net. 518400 IN A 193.0.14.129 l.root-servers.net. 518400 IN A 199.7.83.42 m.root-servers.net. 518400 IN A 202.12.27.33 a.root-servers.net. 518400 IN AAAA 2001:503:ba3e::2:30 b.root-servers.net. 518400 IN AAAA 2001:500:200::b c.root-servers.net. 518400 IN AAAA 2001:500:2::c d.root-servers.net. 518400 IN AAAA 2001:500:2d::d e.root-servers.net. 518400 IN AAAA 2001:500:a8::e f.root-servers.net. 518400 IN AAAA 2001:500:2f::f g.root-servers.net. 518400 IN AAAA 2001:500:12::d0d h.root-servers.net. 518400 IN AAAA 2001:500:1::53 i.root-servers.net. 518400 IN AAAA 2001:7fe::53 j.root-servers.net. 518400 IN AAAA 2001:503:c27::2:30 k.root-servers.net. 518400 IN AAAA 2001:7fd::1 l.root-servers.net. 518400 IN AAAA 2001:500:9f::42 m.root-servers.net. 518400 IN AAAA 2001:dc3::35 named.conf配置rndc rndc可以简单视为是一个用来控制或者查看named系统的命令行工具。它不仅可以在服务器本地使用，还可以远程控制其他的bind服务器上面的named服务。但是在默认情况下的配置文件中并没有对其进行显示配置，所以named程序默认情况下只会监听本机127.0.0.1的953端口\n$ netstat -ntulp | grep named tcp 0 0 127.0.0.1:953 0.0.0.0:* LISTEN 12737/named tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 12737/named udp 0 0 127.0.0.1:53 0.0.0.0:* 12737/named 在named.conf文件中对rndc进行配置，主要涉及到三个选项：监听网卡（inet）、允许IP（allow）和认证使用的keys：\n这样子就是设为监听机器上的所有网卡并允许所有的来源IP进行访问，不过限制了需要使用keys\n# 这样子就是设为监听机器上的所有网卡并允许所有的来源IP进行访问，不过限制了需要使用keys controls { inet * allow { any; } keys { \u0026#34;rndc-key\u0026#34;; }; }; 这种限制了只能在本机上使用rndc\n# 这种限制了只能在本机上使用rndc controls { inet 127.0.0.1 allow { localhost; } keys { \u0026#34;rndc-key\u0026#34;; }; }; 这种限制了在局域网和本机上使用rndc，如果有多个inet需要指定监听则需要分开多条配置，并且可以添加port参数来指定端口\n# 这种限制了在局域网和本机上使用rndc，如果有多个inet需要指定监听则需要分开多条配置 controls { inet 127.0.0.1 allow { localhost; } keys { rndckey; }; inet 192.168.1.1 port 1953 allow { 192.168.1.1; 192.168.1.2; 192.168.1.0/24;} keys { rndckey; }; }; rndc-key配置 keys在这里的作用主要是鉴权，效果相当于ssh程序中的ssh-key，因此如果需要远程操作，需要保证客户端和服务端的keys一致。\nrndc-key只是keys这个参数的一个值，相当于一个key-value，这里只是用来指代，实际上换成tinycehn-key之类的其他名字也是没问题的，本质上它们都是一个HMAC-MD5的key，因此首先我们需要生成一个rndc-key。在bind9里面，一般情况下我们可以使用dnssec-keygen命令生成或者是rndc-confgen -a命令直接生成。这里个人推荐直接使用后者。\n$ rndc-confgen -a wrote key file \u0026#34;/etc/rndc.key\u0026#34; $ cat /etc/rndc.key key \u0026#34;rndc-key\u0026#34; { algorithm hmac-md5; secret \u0026#34;0JqEwIF4kihHEcAbgC4t1w==\u0026#34;; }; 使用dnssec-keygen命令的时候会生成一个key和一个private文件，private文件里面记录着详细信息，同时这样我们需要手动将格式修改为rndc支持的格式并放置在/etc/rndc.key文件中。\n$ dnssec-keygen -a hmac-md5 -b 256 -n HOST tinychen-key Ktinychen-key.+157+42293 $ ll total 8 -rw------- 1 root root 76 May 20 15:37 Ktinychen-key.+157+42293.key -rw------- 1 root root 185 May 20 15:37 Ktinychen-key.+157+42293.private $ cat Ktinychen-key.+157+42293.key tinychen-key. IN KEY 512 3 157 uEICksjZ53OlSXPpHrAmS1s/FhRy5g26+KUCtGqmflM= $ cat Ktinychen-key.+157+42293.private Private-key-format: v1.3 Algorithm: 157 (HMAC_MD5) Key: uEICksjZ53OlSXPpHrAmS1s/FhRy5g26+KUCtGqmflM= Bits: AAA= Created: 20210520073748 Publish: 20210520073748 Activate: 20210520073748 In this case, the is a HMAC-MD5 key. You can generate your own HMAC-MD5 keys with the following command:\ndnssec-keygen -a hmac-md5 -b -n HOST A key with at least a 256-bit length is good idea. The actual key that should be placed in the area can found in the .\nThe name of the key used in /etc/named.conf should be something other than key.\nrndc.conf配置 默认情况下并不会生成rndc.conf文件，因此我们需要自己手动生成一个。使用rndc-confgen命令可以快速地生成一个配置模板并且打印到终端窗口上（注意执行命令之后并不会生成rndc.conf文件，需要我们手动将生成的配置写入文件/etc/rndc.conf）\n$ rndc-confgen # Start of rndc.conf key \u0026#34;rndc-key\u0026#34; { algorithm hmac-md5; secret \u0026#34;h0Pmn9ueo1Uk9Cv6cpPE2w==\u0026#34;; }; options { default-key \u0026#34;rndc-key\u0026#34;; default-server 127.0.0.1; default-port 953; }; # End of rndc.conf # Use with the following in named.conf, adjusting the allow list as needed: # key \u0026#34;rndc-key\u0026#34; { # algorithm hmac-md5; # secret \u0026#34;h0Pmn9ueo1Uk9Cv6cpPE2w==\u0026#34;; # }; # # controls { # inet 127.0.0.1 port 953 # allow { 127.0.0.1; } keys { \u0026#34;rndc-key\u0026#34;; }; # }; # End of named.conf 需要注意的是，当同时存在rndc.conf文件和rndc.key文件的时候，默认是优先读取rndc.conf，如果我们需要指定不同的key来访问不同的server，可以在命令中指定参数，如：\n$ cat /etc/rndc.conf key \u0026#34;rndc-key\u0026#34; { algorithm hmac-md5; secret \u0026#34;h0Pmn9ueo1Uk9Cv6cpPE2w==\u0026#34;; }; key \u0026#34;rndc-conf-key\u0026#34; { algorithm hmac-md5; secret \u0026#34;uEICksjZ53OlSXPpHrAmS1s/FhRy5g26+KUCtGqmflM=\u0026#34;; }; options { default-key \u0026#34;rndc-key\u0026#34;; default-server 127.0.0.1; default-port 953; }; # -s参数用来指定不同的服务器，这里可以是域名也可以是IP # -k参数用来指定key，但是后面跟着的是一个类似rndc.key的文件 # -y参数也是用来指定key，但是后面跟着的参数需要是在rndc.conf文件中已经写明的key的key-value $ rndc -s localhost -k /etc/rndc.key status $ rndc -s 192.168.1.1 -y rndc-conf-key status keepalived安装 由于这里只需要支持IPv4网络的keepalived程序，因此我们可以直接使用yum源来安装keepalived，如果有较高的版本要求或者特殊需要，可以考虑自行下载源码编译安装。\n$ yum install keepalived 同时为了方便我们debug，这里再将keepalived的日志额外重定向到单独的目录文件中\n首先我们需要修改keepalived的默认启动参数\n$ sed -i \u0026#39;s#KEEPALIVED_OPTIONS=\u0026#34;-D\u0026#34;#KEEPALIVED_OPTIONS=\u0026#34;-D -d -S 0\u0026#34;#g\u0026#39; /etc/sysconfig/keepalived $ cat /etc/sysconfig/keepalived # Options for keepalived. See `keepalived --help\u0026#39; output and keepalived(8) and # keepalived.conf(5) man pages for a list of all options. Here are the most # common ones : # # --vrrp -P Only run with VRRP subsystem. # --check -C Only run with Health-checker subsystem. # --dont-release-vrrp -V Dont remove VRRP VIPs \u0026amp; VROUTEs on daemon stop. # --dont-release-ipvs -I Dont remove IPVS topology on daemon stop. # --dump-conf -d Dump the configuration data. # --log-detail -D Detailed log messages. # --log-facility -S 0-7 Set local syslog facility (default=LOG_DAEMON) # KEEPALIVED_OPTIONS=\u0026#34;-D -d -S 0\u0026#34; 修改syslog的配置，将日志输出到指定的目录\n# 对于系统使用rsyslog服务来管理日志的，可以修改 /etc/rsyslog.conf 加入下列的配置 local0.* /path/to/keepalived.log 最后重启rsyslog服务和keepalived服务即可\n$ systemctl enable rsyslog.service $ systemctl restart rsyslog.service bind主从安装 主从两个节点的安装理论上应该完全一致。需要注意的是：\n两个节点之间的rndc配置必须要保证能够互相远程访问 确保两者之间的配置文件能够保持同步（可以使用git或者rsync等方式） 确保两个节点的named.conf配置文件中的监听网卡和端口覆盖了本机IP和VIP 高可用过程分析 这里为了保证DNS节点的高可用，我们使用keepalived将两台机器分别作为主从节点，做一个简单的主从节点方式的高可用。\n正常情况下，主节点的priority为100，从节点的priority为90，此时VIP在主节点上，请求和流量从VIP进入，由主节点提供服务，从节点作为备用。\n当主节点出现异常时，VIP偏移到从节点，从节点进入MASTER状态，直到主节点本身的服务恢复正常再切换过去。\n而为了防止VIP偏移到对应的机器上的时候，named服务没有监听到对应的端口，我们在脚本中设置当keepalived进入notify_master时重启一下named服务，确保能够VIP的服务端口能够被正常监听。\n注意在主节点脚本中设置的检测方式是检测MASTER本身的IP上的rndc状态是否正常而不是VIP上面的rndc状态，而备节点也是检测本机的IP，确保本机的服务是正常的。\n我们以下面的配置为例进行分析，master节点的初始权重是100，backup节点的初始权重是90，两者的脚本都是检测本机的DNS服务是否正常，当不正常的时候weight -20。当master节点的DNS出现故障，weight从100降到80，此时VIP就会切换到backup节点，当master节点恢复正常的时候，weight上升回100，此时VIP就会重新回到master节点上面。\n三个节点的配置也是同理，只需要保证出问题的节点的weight，在出问题之后，减去的weight要低于所有节点中最低的weight即可。\n例如三个节点分别是100、90、80，则建议vrrp_script中的weight最少要-30，确保100的节点出了故障之后，要比正常的没出故障的80的节点weight更低。\n这里主要是为了避免当主节点本身DNS服务就有问题，而它的检测脚本又是检测VIP的rndc状态时，VIP切到从节点后可以正常提供服务，然后主节点脚本检测正常，将VIP抢了回来，但是这时候主节点自己是无法正常提供服务的，随后主节点检测到服务不正常，weight下降，备用节点又将VIP抢了过去，然后继续循环导致VIP不断偏移。\nkeepalived配置 keepalived的配置比较简单，注意这里的interval、interface、router_id、virtual_router_id等参数可以根据实际情况进行调整。\nkeepalived.conf（master） ! Configuration File for keepalived global_defs { router_id HA_DNS } vrrp_script chk_dns { # 定义检测的脚本和执行参数 script \u0026#34;/etc/keepalived/dns_status.sh check master机器的IP\u0026#34; # 定义脚本的执行时间间隔为2秒 interval 2\t# 执行失败的时候weight-20 weight -20 # 检测到脚本执行两次失败才算失败 fall 2 # 检测到脚本执行成功一次就算成功 rise 1 } vrrp_instance VIRT_DNS { state MASTER interface eth0 virtual_router_id 201 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 7777@tinychen } track_script { chk_dns } virtual_ipaddress { 需要配置的VIP(e.g. 1.1.1.1) dev VIP所在的网卡(e.g. eth0) } notify_master \u0026#34;/etc/keepalived/dns_status.sh notify master\u0026#34; notify_backup \u0026#34;/etc/keepalived/dns_status.sh notify backup\u0026#34; notify_fault \u0026#34;/etc/keepalived/dns_status.sh notify fault\u0026#34; } keepalived.conf（backup） ! Configuration File for keepalived global_defs { router_id HA_DNS } vrrp_script chk_dns { script \u0026#34;/etc/keepalived/dns_status.sh check backup主机的IP\u0026#34; # 定义脚本的执行时间间隔为2秒 interval 2\t# 执行失败的时候weight-20 weight -20 # 检测到脚本执行两次失败才算失败 fall 2 # 检测到脚本执行成功一次就算成功 rise 1 } vrrp_instance VIRT_DNS { state BACKUP interface eth0 virtual_router_id 201 priority 90 advert_int 1 authentication { auth_type PASS auth_pass 7777@tinychen } track_script { chk_dns } virtual_ipaddress { 需要配置的VIP(e.g. 1.1.1.1) dev VIP所在的网卡(e.g. eth0) } notify_master \u0026#34;/etc/keepalived/dns_status.sh notify master\u0026#34; notify_backup \u0026#34;/etc/keepalived/dns_status.sh notify backup\u0026#34; notify_fault \u0026#34;/etc/keepalived/dns_status.sh notify fault\u0026#34; } dns_status.sh 这里脚本的放置目录要和上面的keepalived配置文件中的目录保持一致\n# vim /etc/keepalived/dns_status.sh #!/bin/bash LOGFILE=\u0026#34;/home/named/keepalived/keepalived.log\u0026#34; case \u0026#34;$1\u0026#34; in check) ALIVE=$(/usr/sbin/rndc -s $2 status | grep \u0026#34;server is up and running\u0026#34;) if [ $? == 0 ]; then exit 0 else echo \u0026#34;---------------------------------------\u0026#34; \u0026gt;\u0026gt;$LOGFILE date \u0026#34;+%Y-%m-%d %H:%M:%S\u0026#34; \u0026gt;\u0026gt;$LOGFILE echo \u0026#34;Using rndc status check failed\u0026#34; \u0026gt;\u0026gt;$LOGFILE echo \u0026#34;---------------------------------------\u0026#34; \u0026gt;\u0026gt;$LOGFILE exit 1 fi ;; notify) echo \u0026#34;---------------------------------------\u0026#34; \u0026gt;\u0026gt;$LOGFILE date \u0026#34;+%Y-%m-%d %H:%M:%S\u0026#34; \u0026gt;\u0026gt;$LOGFILE echo \u0026#34;Enter status: $2\u0026#34; \u0026gt;\u0026gt;$LOGFILE if [ $2 == \u0026#34;master\u0026#34; ]; then echo \u0026#34;Begin running systemctl restart cmd \u0026#34; \u0026gt;\u0026gt;$LOGFILE systemctl restart named.service \u0026gt;\u0026gt;$LOGFILE 2\u0026gt;\u0026amp;1 echo \u0026#34;Running reload cmd done ! \u0026#34; \u0026gt;\u0026gt;$LOGFILE date \u0026#34;+%Y-%m-%d %H:%M:%S\u0026#34; \u0026gt;\u0026gt;$LOGFILE fi echo \u0026#34;---------------------------------------\u0026#34; \u0026gt;\u0026gt;$LOGFILE ;; *) echo \u0026#34;Usage: sh $0 check ipaddr | notify status\u0026#34; ;; esac 效果检测 最后我们关闭主节点上面的named服务，然后查看日志和从节点的状态确定VIP能否顺利切换，之后再重启主节点上面的named服务，保证服务能够正常运行即可。\n","permalink":"http://localhost:9011/20210520-dns-05-bind9-keepalived-ha-installation/","summary":"\u003cp\u003e本文主要对bind进行安装配置，并且搭配keepalived实现高可用。\u003c/p\u003e","title":"bind篇01-Bind+Keepalived安装高可用DNS集群"},{"content":"本文主要对coredns的源码进行编译安装，以及在编译安装的过程中加入一些External Plugins的方法。\n编译安装coredns golang环境准备 官方的github页面上提供了编译安装的相关指引，需要的可以点击这里跳转。\nFirst, make sure your golang version is 1.12 or higher as go mod support is needed. See here for go mod details.\n由于coredns是使用golang编写，因此对其进行编译安装之前需要先配置go环境。而在centos中使用yum安装的go版本较旧，我们直接去官网下载最新的版本进行解压即可。\nwget https://golang.org/dl/go1.16.4.linux-amd64.tar.gz tar -zxvf go1.16.4.linux-amd64.tar.gz -C /usr/local/ ln -s /usr/local/go/bin/go /usr/bin/go 接下来的go环境变量同学们可以根据自己的实际需求进行配置。对于我个人而言，我直接在/etc/profile中添加下面的配置然后source生效即可。\ncat \u0026gt;\u0026gt; /etc/profile \u0026lt;\u0026lt;EOF export GOROOT=/usr/local/go export GOBIN=$GOROOT/bin export PATH=$PATH:$GOBIN export GOPATH=/home/gopath EOF $ mkdir /home/gopath $ source /etc/profile $ go version go version go1.16.4 linux/amd64 go环境配置完成之后，我们还需要根据coredns的提示检查gomod是否正常，从go的GitHub文档中我们可以得知在1.16版本开始是默认启用并支持gomod的，所以这里我们无需额外配置。\nGo 1.16\nSee the Go 1.16 release notes for details.\nModule mode (GO111MODULE=on) is the default in all cases Commands no longer modify go.mod / go.sum by default (-mod=readonly) go install pkg@version is the recommended way to globally install packages / executables retract is available in go.mod 编译coredns 接下来我们直接开始进行编译，(在编译过程中会访问google.com,k8s.io等域名，需要注意保证网络正常)\ngit clone https://github.com/coredns/coredns cd coredns/ make 编译完成之后我们就会在当前目录下得到一个二进制文件\n$ file coredns coredns: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped $ ./coredns -version CoreDNS-1.8.3 linux/amd64, go1.16.4, 7b43d042 编译External Plugins 什么是External Plugins coredns官方对于插件的分类基本可以分为三种：Plugins、External Plugins和其他。其中Plugins一般都会被默认编译到coredns的预编译版本中，而External Plugins则不会。官方的文档对外部插件的定义有着明确的解释，主要要求大概是有用、高效、符合标准、文档齐全、通过测试等。\n如何编译插件 官方给出了一个详细的文档说明，编译插件基本可以分为修改源码和修改编译的配置文件这两种方式，这里我们采用简单高效的修改配置文件的方式进行测试。\n在我们前面下载的官方源码中，有一个plugin的目录，里面是各种插件的安装包，同时还有一个plugin.cfg的文件，里面列出了会编译到coredns中的插件，\n$ tail plugin.cfg secondary:secondary etcd:etcd loop:loop forward:forward grpc:grpc erratic:erratic whoami:whoami on:github.com/coredns/caddy/onevent sign:sign dump:github.com/miekg/dump 例如这里我们需要额外多添加一个dump插件到coredns中，只需要在plugin.cfg中加入插件的名称和地址\ndump:github.com/miekg/dump 对于在plugin目录下已经存在的插件，则可以直接写成plugin中的目录名：\nsign:sign 然后我们开始编译\n$ go get github.com/miekg/dump go: downloading github.com/miekg/dump v0.0.0-20201002053733-d877fdb82251 go get: added github.com/miekg/dump v0.0.0-20201002053733-d877fdb82251 $ go generate $ go build $ make 验证插件 接下来只要检验生成的coredns二进制文件中是否包含dump插件即可确认是否顺利编译完成：\n最后我们在配置文件中启动dump模块并进行测试，可以看到一条查询会出现两条日志，分别对应的是dump插件生成的日志和log插件生成的日志（带INFO）\n","permalink":"http://localhost:9011/20210516-dns-04-coredns-02-install-external-plugins/","summary":"\u003cp\u003e本文主要对coredns的源码进行编译安装，以及在编译安装的过程中加入一些External Plugins的方法。\u003c/p\u003e","title":"CoreDNS篇2-编译安装External Plugins"},{"content":"本文主要对coredns的原理和特性进行介绍，同时会对其二进制的安装方法进行尝试。\ncoredns简介 coredns是一个用go语言编写的开源的DNS服务，它的官网可以点击这里，github页面可以点击这里。需要额外注意的是，coredns是首批加入CNCF组织的云原生开源项目，并且作为已经在CNCF毕业的项目，coredns还是目前kubernetes中默认的dns服务。同时，由于coredns可以集成插件，它还能够实现服务发现的功能。\ncoredns和其他的诸如bind、knot、powerdns、unbound等DNS服务不同的是：coredns非常的灵活，并且几乎把所有的核心功能实现都外包给了插件。比如说如果你想要在coredns中加入Prometheus的监控支持，那么只需要安装对应的prometheus插件并且启用即可，因此官方也说coredns是由插件驱动的。\nCoreDNS is powered by plugins.\n对于coredns插件的定义，官网是这样表示的：插件是能够单独或者共同实现一个“DNS的功能（DNS function）”。\nPlugins can be stand-alone or work together to perform a “DNS function”.\nSo what’s a “DNS function”? For the purpose of CoreDNS, we define it as a piece of software that implements the CoreDNS Plugin API. The functionality implemented can wildly deviate. There are plugins that don’t themselves create a response, such as metrics or cache, but that add functionality. Then there are plugins that do generate a response. These can also do anything: There are plugins that communicate with Kubernetes to provide service discovery, plugins that read data from a file or a database.\ncoredns安装 和大多数的软件一样，coredns提供了源码编译、预编译包和docker镜像三种安装方式。这里我们使用预编译包的方式进行安装。coredns在github上面提供了各种版本的预编译包，我们只需要下载对应的硬件版本即可。\n解压对应的版本后可以得到一个二进制文件，直接执行就可以使用。\n$ ./coredns --help Usage of ./coredns: -conf string Corefile to load (default \u0026#34;Corefile\u0026#34;) -dns.port string Default port (default \u0026#34;53\u0026#34;) -pidfile string Path to write pid file -plugins List installed plugins -quiet Quiet mode (no initialization output) -version Show version 需要注意的是，对于预编译的版本，会内置全部官方认证的插件，也就是官网的插件页面列出来的全部插件\n$ ./coredns -plugins Server types: dns Caddyfile loaders: flag default Other plugins: dns.acl dns.any dns.auto dns.autopath dns.azure dns.bind dns.bufsize dns.cache dns.cancel dns.chaos dns.clouddns dns.debug dns.dns64 dns.dnssec dns.dnstap dns.erratic dns.errors dns.etcd dns.file dns.forward dns.grpc dns.health dns.hosts dns.k8s_external dns.kubernetes dns.loadbalance dns.local dns.log dns.loop dns.metadata dns.nsid dns.pprof dns.prometheus dns.ready dns.reload dns.rewrite dns.root dns.route53 dns.secondary dns.sign dns.template dns.tls dns.trace dns.transfer dns.whoami on coredns的运行也非常简单，直接运行二进制文件即可，默认情况下可以添加的参数不多，主要是指定配置文件，指定运行端口和设置quiet模式。\n$ ./coredns .:53 CoreDNS-1.8.3 linux/amd64, go1.16, 4293992 默认情况下会直接监听53端口，并且读取和自己在相同目录下的Corefile配置文件。但是在这种情况下，虽然coredns正常运行了，但是由于没有配置文件，是无法正常解析任何域名请求的。\n# 直接运行coredns让其监听30053端口 $ ./coredns -dns.port 30053 .:30053 CoreDNS-1.8.3 linux/amd64, go1.16, 4293992 [INFO] 127.0.0.1:47910 - 63992 \u0026#34;A IN tinychen.com. udp 53 false 4096\u0026#34; NOERROR qr,aa,rd 94 0.000162476s [INFO] 127.0.0.1:48764 - 26598 \u0026#34;A IN tinychen.com. udp 53 false 4096\u0026#34; NOERROR qr,aa,rd 94 0.000135895s # 使用dig命令进行测试，发现能够正常返回请求但是解析的结果不正确 $ dig tinychen.com @127.0.0.1 -p30053 ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.11.20-RedHat-9.11.20-5.el8_3.1 \u0026lt;\u0026lt;\u0026gt;\u0026gt; tinychen.com @127.0.0.1 -p30053 ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 26598 ;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 3 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ; COOKIE: 4429aa454c031afe (echoed) ;; QUESTION SECTION: ;tinychen.com. IN A ;; ADDITIONAL SECTION: tinychen.com. 0 IN A 127.0.0.1 _udp.tinychen.com. 0 IN SRV 0 0 48764 . ;; Query time: 0 msec ;; SERVER: 127.0.0.1#30053(127.0.0.1) ;; WHEN: Tue May 11 11:39:47 CST 2021 ;; MSG SIZE rcvd: 117 这里我们简单编写一个Corefile配置文件就能够先让coredns正常解析域名，这个配置文件的意识是对所有域的请求都forward到114DNS进行解析，并且记录正常的日志和错误的日志。\n$ cat Corefile . { forward . 114.114.114.114 223.5.5.5 log errors whoami } 然后我们再进行测试就发现coredns可以正常解析域名了：\n$ dig tinychen.com @127.0.0.1 -p30053 ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.11.20-RedHat-9.11.20-5.el8_3.1 \u0026lt;\u0026lt;\u0026gt;\u0026gt; tinychen.com @127.0.0.1 -p30053 ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 49732 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;tinychen.com. IN A ;; ANSWER SECTION: tinychen.com. 35 IN A 47.107.188.168 ;; Query time: 29 msec ;; SERVER: 127.0.0.1#30053(127.0.0.1) ;; WHEN: Tue May 11 14:02:41 CST 2021 ;; MSG SIZE rcvd: 69 $ ./coredns -dns.port 30053 .:30053 CoreDNS-1.8.3 linux/amd64, go1.16, 4293992 [INFO] 127.0.0.1:42293 - 51799 \u0026#34;A IN tinychen.com. udp 53 false 4096\u0026#34; NOERROR qr,rd,ra 58 0.244014828s systemd管理 coredns作为一个二进制执行文件，并没有向其他的如nginx、bind等服务提供种类繁多的进程控制（reload stop restart等等）选项，因此为了方便我们管理和在后台一直运行coredns，这里我们使用systemd对其进行管理，只需要编写一个systemd的unit文件即可：\n$ cat /usr/lib/systemd/system/coredns.service [Unit] Description=CoreDNS Documentation=https://coredns.io/manual/toc/ After=network.target [Service] # Type设置为notify时，服务会不断重启 # 关于type的设置，可以参考https://www.freedesktop.org/software/systemd/man/systemd.service.html#Options Type=simple User=root # 指定运行端口和读取的配置文件 ExecStart=/home/coredns/coredns -dns.port=53 -conf /home/coredns/Corefile Restart=on-failure [Install] WantedBy=multi-user.target 编写完成之后我们依次reload配置文件并且设置开机启动服务和开启服务，即可看到服务正常运行\n$ systemctl daemon-reload $ systemctl enable coredns.service $ systemctl start coredns.service $ systemctl status coredns.service ● coredns.service - CoreDNS Loaded: loaded (/usr/lib/systemd/system/coredns.service; enabled; vendor preset: disabled) Active: active (running) since Tue 2021-05-11 11:29:53 CST; 2h 37min ago Docs: https://coredns.io/manual/toc/ Main PID: 131287 (coredns) Tasks: 10 (limit: 49835) Memory: 27.3M CGroup: /system.slice/coredns.service └─131287 /home/coredns/coredns -dns.port=53 -conf /home/coredns/Corefile May 11 11:29:53 tiny-server systemd[1]: Started CoreDNS. coredns日志处理 coredns的日志输出并不如nginx那么完善（并不能在配置文件中指定输出的文件目录，但是可以指定日志的格式），默认情况下不论是log插件还是error插件都会把所有的相关日志输出到程序的standard output中。使用systemd来管理coredns之后，默认情况下基本就是由rsyslog和systemd-journald这两个服务来管理日志。\nStandardOutput 根据网上的参考资料我们可以得知较新版本的systemd是可以直接在systemd的unit文件里面配置StandardOutput和StandardError两个参数来将相关运行日志输出到指定的文件中。\n因此对于centos8等较新的系统，我们的unit文件可以这样编写：\n[Unit] Description=CoreDNS Documentation=https://coredns.io/manual/toc/ After=network.target # StartLimit这两个相关参数也是centos8等systemd版本较新的系统才支持的 StartLimitBurst=1 StartLimitIntervalSec=15s [Service] # Type设置为notify时，服务会不断重启 Type=simple User=root # 指定运行端口和读取的配置文件 ExecStart=/home/coredns/coredns -dns.port=53 -conf /home/coredns/Corefile # append类型可以在原有文件末尾继续追加内容，而file类型则是重新打开一个新文件 # 两者的区别类似于 echo \u0026gt;\u0026gt; 和 echo \u0026gt; StandardOutput=append:/home/coredns/logs/coredns.log StandardError=append:/home/coredns/logs/coredns_error.log Restart=on-failure [Install] WantedBy=multi-user.target 参考链接：systemd.exec (www.freedesktop.org)\nThe file:*path* option may be used to connect a specific file system object to standard output. The semantics are similar to the same option of StandardInput=, see above. If path refers to a regular file on the filesystem, it is opened (created if it doesn\u0026rsquo;t exist yet) for writing at the beginning of the file, but without truncating it. If standard input and output are directed to the same file path, it is opened only once, for reading as well as writing and duplicated. This is particularly useful when the specified path refers to an AF_UNIX socket in the file system, as in that case only a single stream connection is created for both input and output.\nappend:*path* is similar to file:*path* above, but it opens the file in append mode.\n修改完成之后我们再重启服务就可以看到日志已经被重定向输出到我们指定的文件中\n$ systemctl daemon-reload $ systemctl restart coredns.service rsyslog 对于centos7等系统而言，是不支持上面的append和file两个参数的，那么在开启了rsyslog.service服务的情况下，日志就会输出到/var/log/messages文件中，或者可以使用journalctl -u coredns命令来查看全部的日志。\n如果想要将coredns的日志全部集中到一个文件进行统一管理，我们可以对负责管理systemd的日志的rsyslog服务的配置进行修改：\n# vim /etc/rsyslog.conf if $programname == \u0026#39;coredns\u0026#39; then /home/coredns/logs/coredns.log \u0026amp; stop $ systemctl restart rsyslog.service 从上图我们可以看到两种方式打出来的日志稍微有些不同，对于StandardOutput这种方式输出的日志缺少了前面的时间和主机名等信息，相对而言还是修改rsyslog的方式要更加的可靠。\n","permalink":"http://localhost:9011/20210509-dns-03-coredns-01-introduction/","summary":"\u003cp\u003e本文主要对coredns的原理和特性进行介绍，同时会对其二进制的安装方法进行尝试。\u003c/p\u003e","title":"CoreDNS篇1-简介和安装"},{"content":"本文主要介绍如何在centos8中使用nmcli工具创建虚拟网桥并且添加物理网卡到虚拟网桥中，使其成为一个二层交换机。\n这个操作不仅适用于物理网卡设备上，还适用于各种虚拟机的虚拟网卡设备。\n目标是将红框内的三个网卡都组合成为一个二层的交换机，首先我们需要创建一个网桥设备，这个设备的功能就相当于我们平时使用的交换机\nnmcli connection add type bridge con-name switch ifname switch autoconnect yes 接着我们将需要的网卡添加到刚刚创建的名为switch的网桥中\nnmcli connection add type bridge-slave ifname enp0s20u3 master switch nmcli connection add type bridge-slave ifname enp0s20u4 master switch nmcli connection add type bridge-slave ifname enp6s0 master switch 完成添加之后我们需要对网桥和网卡的配置进行修改，编辑对应的配置文件修改启动选项和IP地址：/etc/sysconfig/network-scripts/ifcfg-*，把添加到switch网桥的所有网卡取消开机启动，避免和网桥的配置产生冲突：\nONBOOT=no 同时给网桥设置静态IP地址，配置的方法和部分相关配置如下\nBOOTPROTO=static ONBOOT=yes IPADDR=192.168.1.1 NETMASK=255.255.255.0 GATEWAY=192.168.1.253 DNS1=8.8.8.8 配置完成之后，这时的整个switch设备就相当于一个拥有三个RJ45接口的带有管理IP的二层交换机。我们在switch的网卡上面接上路由器，另一个接口接上我们的电脑，理论上这时候就能够正常获取路由器通过DHCP下发的IP地址：\n电脑网卡使用网线直接连接到Linux主机上已经添加到网桥中的网卡，查看状态可以直接获取到IPv4和IPv6的地址并且能够正确联网，这时候证明整个虚拟网桥设备是可以正常工作的。\n同样的操作我们还可以在KVM虚拟机上面实现，一般情况下如果我们给虚拟机直接添加宿主机网卡的桥接网卡，是可以实现虚拟机和宿主机处在相同网段的操作的。但是这个时候会出现宿主机没办法直接和虚拟机进行通信的情况。\n这时候就可以创建一个虚拟网桥，然后把宿主机的网卡添加到这个网桥内，然后虚拟机的网卡创建的时候选择桥接这个虚拟网桥即可。\n","permalink":"http://localhost:9011/20210408-nmcli-create-net-bridge/","summary":"\u003cp\u003e本文主要介绍如何在centos8中使用nmcli工具创建虚拟网桥并且添加物理网卡到虚拟网桥中，使其成为一个二层交换机。\u003c/p\u003e\n\u003cp\u003e这个操作不仅适用于物理网卡设备上，还适用于各种虚拟机的虚拟网卡设备。\u003c/p\u003e","title":"在centos中使用nmcli工具创建虚拟网桥"},{"content":"本文主要对openresty做入门简介以及初始化的安装配置介绍。\nOpenResty简介 了解过web服务器的同学肯定对nginx不陌生，nginx作为目前占有率第一的web服务器，本身作为静态web服务器、反向代理服务和四层、七层负载均衡器都有着非常优秀的表现。但是对于web服务器而言，nginx的很多功能都偏向于静态web应用，也就是说它的动态处理能力是有一定的缺失的。举个最简单的例子，nginx无法在配置中直接进行一个条件以上的逻辑判断，这对于一些架构设计来说有着较大的不便。OpenResty的诞生就是为了解决这个问题。\n官方对于OpenResty的介绍是：\nOpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台，其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。\nOpenResty® 通过汇聚各种设计精良的 Nginx 模块（主要由 OpenResty 团队自主开发），从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样，Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块，快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。\nOpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部，充分利用 Nginx 的非阻塞 I/O 模型，不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。\n个人简单的理解就是OpenResty=nginx+lua+第三方库，事实上nginx本身也可以通过编译的方式添加lua的支持，同时nginx也可以通过编译的方式添加第三方库的支持，OpenResty则是直接将其全部打包在一起。且不讨论OpenResty作为全能型web服务器是否能够满足所有的业务需求，就其突出的动态处理能力就能给我们的系统架构带来很大的灵活性和额外的多样化处理能力。\nyum安装 配置yum源 openresty官网有提供各种主流Linux发行版的预编译包，如果没有特殊需求，一般可以直接使用官方提供的安装包进行安装。\n下面我们以centos为例进行示范：\n# add the yum repo: wget https://openresty.org/package/centos/openresty.repo sudo mv openresty.repo /etc/yum.repos.d/ # update the yum index: sudo yum check-update rpm包介绍 openresty的yum源中除了openresty本身之外，还有一些其他的工具包和依赖包，我们可以这样查看所有可用的rpm包，对于这些rpm包的详细内容解说可以查看官网的文档。\nsudo yum --disablerepo=\u0026#34;*\u0026#34; --enablerepo=\u0026#34;openresty\u0026#34; list available 下面简单介绍几个重点的rpm包。\nopenresty 这是用于核心 OpenResty 服务的生产版本。也就是整个openresty中提供主要服务的核心功能文件。在使用rpm包安装的情况下，系统中的openresty指令会链接到/usr/bin/openresty，而/usr/bin/openresty实际上是/usr/local/openresty/nginx/sbin/nginx的软链接\n实际上/usr/local/openresty/nginx/sbin/nginx才是整个openresty的nginx可执行文件的本体，我们加上-V参数即可查看它包括编译模块在内的详细情况。之所以要将其软链接到/usr/bin/openresty，官方解释是为了避免和机器上已有的nginx发生冲突。\n从上面的编译参数中我们可以看到openresty使用了它们自己维护的openssl库、zlib库、pcre库和LuaJIT库，这样一来能够较好地控制各个库之间的版本以及功能更新和同步协调。\nopenresty-resty 这个包里面有 resty 命令行程序，这个工具包主要依赖于 resty-cli 这个项目，这个包依赖标准 的perl 包以及上面提到的 openresty 包才能正常工作。默认情况下它是指向/usr/bin/resty目录的一个环境变量，并且源文件在/usr/local/openresty/bin/resty\n个人认为这个工具包的主要作用就是方便我们进行一些简单的命令调试\n$ resty -e \u0026#39;ngx.say(\u0026#34;hello\u0026#34;)\u0026#39; hello openresty-doc 顾名思义这个rpm包最重要的功能就是提供文档查询，一般来说需要注意使用UTF-8编码的Terminal即可。和man命令有些类似，如果我们想要查询openresty中某个模块的相关文档，只需要使用restydoc命令即可。\n# 我们可以直接输入对应的模块命查看对应的文档 restydoc ngx_lua restydoc ngx_lua_upstream restydoc ngx_http_v2_module restydoc luajit # 也可以加入-s参数查看某条指令或某个部分的文档 restydoc -s content_by_lua restydoc -s proxy_pass openresty-openssl 这是openresty官方维护的 OpenSSL 库。为了节省开销，这个版本禁用了在构建中对于多线程的支持。此外，这个版本最重要的是OpenResty官方加入了他们自己的一些补丁包来支持一些openssl的最新特性，同时也做了一些优化使得比较旧的系统也能够支持最新版本的openssl。同理，不仅是openssl，openresty自己维护的pcre和zlib库也是有这种的目的。\n和nginx默认的rpm版本使用的还是较旧/稳定的openssl版本相比，openresty对于openssl版本的支持无疑是比较激进的。\n其他 更多详细的包介绍可以查看官方的英文文档，介绍的比较全面，一般来说就是主要的几个工具软件如openresty、openssl、pcre、zlib等等的主要生产版本、debug版本、asan版本、valgrind版本等，这里不做赘述。\nrpm包安装openresty 虽然上面介绍了很多个openresty相关的包，但是实际上安装的时候只需要安装openresty，其他的依赖关系会由yum自动处理。注意openresty-doc、openresty-resty等是可选安装的包。\n# add the yum repo: wget https://openresty.org/package/centos/openresty.repo sudo mv openresty.repo /etc/yum.repos.d/ # update the yum index: sudo yum check-update # use yum to install yum install openresty 安装完成之后会在默认的目录路径/usr/local/openresty/nginx下面生成对应的配置文件目录，我们将其和yum安装的nginx和编译安装的nginx对比可以发现几乎是一致的。\n# 使用yum安装的nginx $ pwd /etc/nginx $ ls conf.d fastcgi_params koi-utf koi-win mime.types modules nginx.conf scgi_params uwsgi_params win-utf # 源码编译安装的nginx $ pwd /home/nginx $ ls client_body_temp conf fastcgi_temp html logs sbin scgi_temp uwsgi_temp # 使用yum安装的openresty目录下的nginx $ pwd /usr/local/openresty/nginx $ ls client_body_temp conf fastcgi_temp html logs proxy_temp sbin scgi_temp uwsgi_temp 我们简单写一个nginx.conf配置文件用来测试即可，文件使用默认路径的/usr/local/openresty/nginx/conf/nginx.conf。\n$ openresty -T nginx: the configuration file /usr/local/openresty/nginx/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/openresty/nginx/conf/nginx.conf test is successful # configuration file /usr/local/openresty/nginx/conf/nginx.conf: user root; worker_processes 1; error_log logs/error.log; pid logs/nginx.pid; events { worker_connections 1024; } http { log_format main \u0026#39;$remote_addr | [$time_local] | $status | $scheme | \u0026#39; \u0026#39;$server_name | request= $request | request_uri= $request_uri | http_referer= $http_referer | \u0026#39; \u0026#39;UA= $http_user_agent | $request_time | $ssl_protocol | $ssl_cipher | \u0026#39; \u0026#39;remote_port= $remote_port | $request_id\u0026#39;; access_log logs/access.log main; server { listen 8080; location / { default_type text/html; content_by_lua_block { ngx.say(\u0026#34;\u0026lt;p\u0026gt;hello, world\u0026lt;/p\u0026gt;\u0026#34;) } } } } 分别使用curl和浏览器测试均正常\n$ curl 10.224.192.144:8080 \u0026lt;p\u0026gt;hello, world\u0026lt;/p\u0026gt; 日志中也能看到对应的操作\n$ tail -f ../logs/access.log 10.224.192.144 | [12/Mar/2021:14:42:25 +0800] | 200 | http | | request= GET / HTTP/1.1 | request_uri= / | http_referer= - | UA= curl/7.29.0 | 0.000 | - | - | remote_port= 34626 | 602882ad9b5cf910bea2f6d13ce4bf5e 10.228.18.249 | [12/Mar/2021:14:42:51 +0800] | 200 | http | | request= GET / HTTP/1.1 | request_uri= / | http_referer= - | UA= Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 | 0.000 | - | - | remote_port= 7148 | 5a91256294bf307ed62f48a06905dbc0 10.228.18.249 | [12/Mar/2021:14:42:51 +0800] | 200 | http | | request= GET /favicon.ico HTTP/1.1 | request_uri= /favicon.ico | http_referer= http://10.224.192.144:8080/ | UA= Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 | 0.000 | - | - | remote_port= 7148 | 372e7402db9f396950dae48b66a7e1ce 再查看端口监听和服务进程都正常\n那么就可以基本判定这次的安装是顺利安装成功了。\n源码编译安装 如果需要进行定制化，那么从源码开始进行编译安装就是我们的不二之选。首先我们去官网下载最新版本的openresty源码包并解压。\ncd /home wget https://openresty.org/download/openresty-1.19.3.1.tar.gz tar -zxvf openresty-1.19.3.1.tar.gz 编译环境 $ lsb_release -a LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch Distributor ID: CentOS Description: CentOS Linux release 7.9.2009 (Core) Release: 7.9.2009 Codename: Core 同时我们还需要准备编译使用的工具如编译器等，对于CentOS7，我们可以简单的安装整个开发工具包Development Tools。\nyum grouplist yum groupinstall \u0026#34;Development Tools\u0026#34; -y 准备openssl、pcre和zlib 和nginx一样，我们手动下载准备最新版本的openssl、pcre和zlib，注意这三个库只需要下载解压，并不需要提前进行安装。\n# 下载 wget https://www.openssl.org/source/openssl-1.1.1j.tar.gz wget https://ftp.pcre.org/pub/pcre/pcre-8.44.tar.gz wget https://zlib.net/zlib-1.2.11.tar.gz # 解压 tar -zxvf openssl-1.1.1j.tar.gz tar -zxvf pcre-8.44.tar.gz tar -zxvf zlib-1.2.11.tar.gz 编译安装 和nginx的编译安装一样，我们可以自定义编译安装的模块，这里可以查看openresty模块的官方说明。\ncd /home/openresty-1.19.3.1/ ./configure --prefix=/home/openresty --with-openssl=/home/openssl-1.1.1j --with-pcre=/home/pcre-8.44 --with-zlib=/home/zlib-1.2.11 --with-http_realip_module --with-http_stub_status_module --with-debug --with-http_ssl_module --with-http_v2_module gmake -j8 gmake install 最后在安装完成的时候我们可以看到会把编译的openresty目录下的/home/openresty/nginx/sbin/nginx软链接到/home/openresty/bin/openresty，逻辑和yum安装的openresty一样\n如果想要方便全局操作，可以创建一个软链接。\nln -s /home/openresty/bin/openresty /usr/local/bin/openresty 当然要是觉得这样子的目录不好操作，我们也可以在编译的时候多加一些参数，将常用的目录都手动指定，操作起来就和nginx几乎没有差别了，下面简单列举一个例子：\n./configure \\ --prefix=/home/openresty \\ --sbin-path=/home/openresty/bin/nginx \\ --conf-path=/home/openresty/conf/nginx.conf \\ --http-log-path=/home/openresty/logs/access.log \\ --error-log-path=/home/openresty/logs/error.log \\ --pid-path=/home/openresty/logs/nginx.pid \\ --lock-path=/home/openresty/logs/nginx.lock \\ --http-client-body-temp-path=/home/openresty/client_body_temp/ \\ --http-proxy-temp-path=/home/openresty/proxy_temp/ \\ --http-fastcgi-temp-path=/home/openresty/fastcgi_temp/ \\ --http-uwsgi-temp-path=/home/openresty/uwsgi_temp/ \\ --http-scgi-temp-path=/home/openresty/scgi_temp/ \\ --with-openssl=/home/openssl-1.1.1k \\ --with-pcre=/home/pcre-8.44 \\ --with-zlib=/home/zlib-1.2.11 \\ --with-http_realip_module \\ --with-http_stub_status_module \\ --with-debug \\ --with-http_ssl_module \\ --with-http_v2_module \\ --build=tinychen-build \\ --dry-run OpenResty操作 openresty的操作指令和nginx是完全一致的。具体如下：\n$ nginx -h nginx version: nginx/1.19.8 Usage: nginx [-?hvVtTq] [-s signal] [-p prefix] [-e filename] [-c filename] [-g directives] Options: -?,-h : this help -v : show version and exit -V : show version and configure options then exit -t : test configuration and exit -T : test configuration, dump it and exit -q : suppress non-error messages during configuration testing -s signal : send signal to a master process: stop, quit, reopen, reload -p prefix : set prefix path (default: /etc/nginx/) -e filename : set error log file (default: /var/log/nginx/error.log) -c filename : set configuration file (default: /etc/nginx/nginx.conf) -g directives : set global directives out of configuration file $ openresty -h nginx version: openresty/1.19.3.1 Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives] Options: -?,-h : this help -v : show version and exit -V : show version and configure options then exit -t : test configuration and exit -T : test configuration, dump it and exit -q : suppress non-error messages during configuration testing -s signal : send signal to a master process: stop, quit, reopen, reload -p prefix : set prefix path (default: /usr/local/openresty/nginx/) -c filename : set configuration file (default: conf/nginx.conf) -g directives : set global directives out of configuration file -h 打印帮助菜单 -v 小写的v是打印版本并退出 -V 大写的V是打印编译参数和版本信息并退出 -t 小写的t是测试配置文件是否有错误并退出 -T 大写的T是测试配置文件是否有误并且打印出生效的配置文件并退出，如果nginx.conf里面使用了include命令添加了其他的配置文件，也会一并拼接打印出来 -q 在测试期间不打印非错误信息，适合debug使用 -s 发送信号控制master进程，分别为stop（暴力停止）, quit（优雅停止）, reopen（重新打开日志文件）, reload（优雅重启） -p通过-p参数来指定实际的工作目录，例如我们有两个不同的测试项目，则可以通过openresty -p /path/to/app在启动的时候指定不同的工作目录，从而实现允许多个不同的 OpenResty 应用共享同一个 OpenResty 服务程序的效果 -c 通过-c参数来指定不同的配置文件而非只是默认路径下的配置文件 -g 在启动的时候设定配置文件之外的全局指令，例如openresty -g \u0026quot;pid /var/run/nginx.pid; worker_processes 8;\u0026quot; ","permalink":"http://localhost:9011/20210317-openresty-01-introduction-and-installation/","summary":"\u003cp\u003e本文主要对openresty做入门简介以及初始化的安装配置介绍。\u003c/p\u003e","title":"OpenResty篇01-入门简介和安装配置"},{"content":"本文主要介绍如何使用给nginx服务添加客户端证书认证从而实现双向加密。\n对于一般的https网站来说，实际上https所使用的证书是属于单向验证，即客户端单向验证服务器的安全性，而服务器端是没有对客户端的身份进行验证的。关于https的原理，可以查看这篇文章：《SSL/TLS、对称加密和非对称加密和TLSv1.3》\n如果自己部署了一些安全性较高的网站不希望被其他人随意访问，就可以尝试部署https的双向认证，对客户端也添加证书认证。本文将会使用openssl自签证书来完成最简单的一个https双向认证。\nopenssl自签证书 在开始之前我们新建一个目录专门用来存放这次生成证书相关的全部文件。openssl生成自签证书的命令非常简单，总结如下：\nopenssl genrsa -out root.key 1024 openssl req -new -out root.csr -key root.key openssl x509 -req -in root.csr -out root.crt -signkey root.key -CAcreateserial -days 3650 接下来我们开始逐个命令进行讲解，首先我们需要生成一个root.key文件\nroot.key文件生成之后，我们就可以根据key文件来生成一个记录证书信息的csr文件用于申请证书。csr文件需要记录相关的证书申请人的地址和邮件等信息，最后还可以设置一个密码，当需要使用这个csr文件来申请证书的适合就需要输入这个密码，否则无法使用，如果不想设置的话直接回车即可跳过\n生成了key和csr文件之后，我们就可以自己生成一个crt的证书文件，由于这里是测试，我们可以直接把时间设定为10年\n到这一步，我们已经生成了一个完整的自签CA证书，其中包含了私钥key文件，证书信息csr文件和公钥crt文件。\n生成p12证书 为了方便使用，我们需要把证书打包成p12格式的证书文件，然后在客户端的电脑上导入。同样的在生成p12文件的时候我们最好添加一个密码保护，这样就算证书泄露了也不会导致安全问题。\nopenssl pkcs12 -export -clcerts -in root.crt -inkey root.key -out root.p12 配置nginx 最后我们只需要在nginx中需要使用双向认证的部分添加以下配置然后重启即可生效\n# 客户端公钥证书 ssl_client_certificate /path/to/root.crt; # 开启客户端证书验证 ssl_verify_client on; 最后我们在windows或者mac上面导入p12文件之后，打开对应的网站就会有相应的提示了：\n如果不进行证书验证，则服务器会返回400错误。\n我们还可以使用curl来进行测试，如果不带上证书，会直接报400错误。\n# curl https://ip.tinychen.com \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;400 No required SSL certificate was sent\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;center\u0026gt;\u0026lt;h1\u0026gt;400 Bad Request\u0026lt;/h1\u0026gt;\u0026lt;/center\u0026gt; \u0026lt;center\u0026gt;No required SSL certificate was sent\u0026lt;/center\u0026gt; \u0026lt;hr\u0026gt;\u0026lt;center\u0026gt;nginx\u0026lt;/center\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 带上证书之后则访问正常\n$ curl --cert ./root.crt --key ./root.key https://ip.tinychen.com hello world 如果想要查看curl的详细过程，我们可以加上-v参数来查看。\n","permalink":"http://localhost:9011/20210304-nginx-08-ssl-client-certificate/","summary":"\u003cp\u003e本文主要介绍如何使用给nginx服务添加客户端证书认证从而实现双向加密。\u003c/p\u003e\n\u003cp\u003e对于一般的https网站来说，实际上https所使用的证书是属于单向验证，即客户端单向验证服务器的安全性，而服务器端是没有对客户端的身份进行验证的。关于https的原理，可以查看这篇文章：\u003ca href=\"https://tinychen.com/20200602-encryption-intro/\"\u003e《SSL/TLS、对称加密和非对称加密和TLSv1.3》\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e如果自己部署了一些安全性较高的网站不希望被其他人随意访问，就可以尝试部署https的双向认证，对客户端也添加证书认证。本文将会使用openssl自签证书来完成最简单的一个https双向认证。\u003c/p\u003e","title":"nginx篇08-添加客户端证书认证"},{"content":"本文主要是偏向运维角度来对tomcat进行入门介绍，重点讲解了tomcat的基本概念、基本配置和tomcat的I/O模型。是之前的tomcat篇的汇总整理之作。\nTomcat简介 在了解tomcat之前我们需要了解一些基本的概念。\nweb应用 所谓Web应用，就是指需要通过编程来创建的Web站点。Web应用中不仅包括普通的静态HTML文档，还包含大量可被Web服务器动态执行的程序。用户在Internet上看到的能开展业务的各种Web站点都可看作Web应用，例如，网上商店和网上银行都是Web应用。此外，公司内部基于Web的Intranet工作平台也是Web应用。\nWeb应用与传统的桌面应用程序相比，具有以下特点：\n以浏览器作为展示客户端界面的窗口。 客户端界面一律表现为网页形式，网页由HTML语言写成。 客户端与服务器端能进行和业务相关的动态交互。 能完成与桌面应用程序类似的功能。 使用浏览器—服务器架构（B/S），浏览器与服务器之间采用HTTP协议通信。 Web应用通过Web服务器来发布。 web应用的一大好处就是可以轻易地跨平台运行，不论是windows、mac、ios、android还是linux，只要安装了浏览器，一般都可以使用web应用，而浏览器在各个平台都是标配的软件，因此给web应用的普及提供了非常良好的条件。同样的，web应用使用的是B/S架构，即Browser/Server架构，主要的计算任务都交给Server端进行，因此都客户端的性能要求较低，同时也推动了服务端的负载均衡、高可用等技术的发展。\nContext：在tomcat中一般指web应用\nServlet **Servlet（Server Applet），全称Java Servlet。是用Java编写的服务器端程序。**其主要功能在于交互式地浏览和修改数据，生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口，广义的Servlet是指任何实现了这个Servlet接口的类别，一般情况下，我们说的Servlet为后者。\nServlet运行于支持Java的应用服务器中。从实现上讲，Servlet可以响应任何类型的请求，但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。也就是说Web服务器可以访问任意一个Web应用中所有实现Servlet接口的类。而Web应用中用于被Web服务器动态调用的程序代码位于Servlet接口的实现类中。既然servlet和java关系密切，那么servlet接口的标准制定毫无疑问也是由甲骨文公司来主导。\n**Servlet规范把能够发布和运行Java Web应用的Web服务器称为Servlet容器。**Servlet容器最主要的特征是动态执行Java Web应用中Servlet实现类的程序代码。由Apache开源软件组织创建的Tomcat是一个符合Servlet规范的优秀Servlet容器。\njsp JSP（全称JavaServer Pages）是由Sun Microsystems公司主导建立的一种动态网页技术标准。JSP是HttpServlet的扩展。JSP将Java代码和特定变动内容嵌入到静态的页面中，实现以静态页面为模板，动态生成其中的部分内容。JSP在首次被访问的时候被应用服务器转换为servlet，在以后的运行中，容器直接调用这个servlet，而不再访问JSP页面。JSP的实质仍然是servlet。\nTomcat Tomcat是在Oracle公司的JSWDK（JavaServer Web DevelopmentKit，是Oracle公司推出的小型Servlet/JSP调试工具）的基础上发展起来的一个优秀的Servlet容器，Tomcat本身完全用Java语言编写。作为一个开源软件，Tomcat除了运行稳定、可靠，并且效率高之外，还可以和目前大部分的主流Web服务器（如IIS、Apache、Nginx等）一起工作。\ntomcat的版本实际上比较复杂，目前有7、8、9、10四个版本并行发布，具体的各个版本的兼容信息我们可以通过官网查询。\nTomcat的目录结构 我们先来看一下tomcat8.5和tomcat9中的home目录中的文件：\n可以看到除掉一些说明文件之后，还有7个目录：\n目录名 用途 bin 存放用于启动及关闭的文件，以及其他一些脚本。其中，UNIX 系统专用的 *.sh 文件在功能上等同于 windows 系统专用的 *.bat 文件。因为 Win32 的命令行缺乏某些功能，所以又额外地加入了一些文件 conf 配置文件及相关的 DTD*（document type definition 文档类型定义，DTD文件一般和XML文件配合使用，主要是为了约束XML文件）*。其中最重要的文件是 server.xml，这是容器的主配置文件 lib 存放tomcat服务器自身和所有的web应用都可以访问的JAR文件 logs 日志文件的默认目录 temp 存放临时文件的默认目录 webapps 在tomcat上发布Java web应用的时候，默认把web应用的文件存放在这个目录 work tomcat的工作目录，tomcat把运行时生成的一些工作文件存放在这个目录，如默认情况下tomcat会把编译JSP生成的Servlet类文件存放在这里 实际上除了主目录里有lib目录，在webapps目录下的web应用中的WEB-INF目录下也存在一个lib目录：\n两者的区别在于：\n●　Tomcat主目录下的lib目录：存放的JAR文件不仅能被Tomcat访问，还能被所有在Tomcat中发布的Java Web应用访问 ●　webapps目录下的Java Web应用的lib目录：存放的JAR文件只能被当前Java Web应用访问\n既然有多个lib目录，那么肯定就有使用的优先顺序，Tomcat类加载器的目录加载优先顺序如下：\nTomcat的类加载器负责为Tomcat本身以及Java Web应用加载相关的类。假如Tomcat的类加载器要为一个Java Web应用加载一个类，类加载器会按照以下优先顺序到各个目录中去查找该类的.class文件，直到找到为止，如果所有目录中都不存在该类的.class文件，则会抛出异常：\n在Java Web应用的WEB-INF/classes目录下查找该类的.class文件 在Java Web应用的WEB-INF/lib目录下的JAR文件中查找该类的.class文件 在Tomcat的lib子目录下直接查找该类的.class文件 在Tomcat的lib子目录下的JAR文件中查找该类的.class文件 Tomcat安装配置 tomcat的配置安装需要先在系统上配置好jdk环境，这里我们使用centos7.7版本的Linux系统和jdk8版本。\n配置jdk8 我们首先到官网下载JDK8的安装包，这里我们选择tar.gz格式的压缩包下载，需要注意建议先使用浏览器下载再使用工具传输到Linux上，因为下载需要登录注册账号。\n接着我们解压将安装包解压到自己想要配置的jdk安装目录下，这里我们使用/home/目录\ntar -zxvf jdk-8u241-linux-x64.tar.gz -C /home/ 在/etc/profile中添加以下三个参数并导入\nJAVA_HOME=/home/jdk_1.8.0_241 CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin export JAVA_HOME CLASSPATH PATH 重新载入配置文件\nsource /etc/profile 检查配置是否生效，如不生效可以重启终端试试：\n[root@tiny-yun ~]# java -version java version \u0026#34;1.8.0_241\u0026#34; Java(TM) SE Runtime Environment (build 1.8.0_241-b07) Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode) 配置tomcat tomcat的安装配置和上面几乎一样，由于我们已经在/etc/profile中设定了全局的java环境变量，因此在tomcat中就不用再特殊配置，直接就会使用默认的全局变量。\n这里我们还是使用官网 提供的tar.gz压缩包来安装。\n# tomcat可以直接使用wget下载 wget https://downloads.apache.org/tomcat/tomcat-8/v8.5.53/bin/apache-tomcat-8.5.53.tar.gz # 解压到安装目录并重命名 tar -zxvf apache-tomcat-8.5.53.tar.gz /home/ cd /home mv apache-tomcat-8.5.53 tomcat-8.5.53 首先我们来看一下tomcat中的主要目录：\n/bin 存放用于启动及关闭的文件，以及其他一些脚本。其中，UNIX 系统专用的 *.sh 文件在功能上等同于 Windows 系统专用的 *.bat 文件。因为 Win32 的命令行缺乏某些功能，所以又额外地加入了一些文件。 /conf 配置文件及相关的 DTD。其中最重要的文件是 server.xml，这是容器的主配置文件。 /log 日志文件的默认目录。 /webapps 存放 Web 应用的相关文件。 接着我们进入tomcat目录下的bin目录就可以看到各种各样的脚本文件，主要分为bat和sh两类，其中bat主要是在windows系统上使用的，我们可以把它们删掉，接着我们执行一些version.sh这个脚本就可以看到版本信息。\n接下来我们来看一下和tomcat相关的几个变量：\nJRE_HOME\n这里我们可以看到JRE_HOME这个变量是之前设置了的JAVA_HOME环境变量。\n如果同时定义了JRE_HOME和JAVA_HOME这两个变量，那么使用的是JRE_HOME 如果只定义了JAVA_HOME，那么JRE_HOME变量值就是JAVA_HOME的变量值 如果两个变量都没定义，那么tomcat无法运行 前面我们提到过tomcat是使用Java编写的，这也就意味着它在运行的时候需要创建一个JVM虚拟机，所以如果没定义JAVA环境变量，tomcat是无法运行的\nCATALINA_HOME\ntomcat安装目录的根目录\nCATALINA_BASE\ntomcat实例运行的目录，默认情况下等于CATALINA_HOME，如果我们需要在一台机器上运行多个tomcat实例，可以设置多个CATALINA_BASE\nsetenv.sh\n这个脚本默认是不存在的，需要我们自己手动创建在bin目录下，在windows系统则应该是setenv.bat，我们在里面指定了JRE_HOME环境变量以及PID文件的位置，这样在运行的时候就能比较方便的定位到运行进程\n注意前面提到的CATALINA_HOME和CATALINA_BASE两个变量不能在这里设定，因为tomcat就是根据这两个变量来找到 setenv.sh的。\n$ cat setenv.sh JRE_HOME=/home/jdk1.8.0_241/jre CATALINA_PID=\u0026#34;$CATALINA_BASE/tomcat.pid\u0026#34; 这时候运行./catalina.sh start或者是./startup.sh文件就可以启动tomcat，注意要在防火墙中放行默认的8080端口。如果没有指定PID文件的位置，在关闭tomcat的时候可能会出现错误。此外，一般不建议使用root用户来运行tomcat。\nmanager应用 tomcat本身内置了两个web应用，专门用来管理tomcat，它们分别是host-manager（管理virtual host）和manager（管理web应用）。\nhttp://localhost:8080/host-manager/html http://localhost:8080/manager/html 在启动tomcat之后，我们访问上面的这两个网址可以发现被403了。因为我们还没有在配置文件中增加相关的用户，为了保证安全，这里的用户默认都是禁用的，我们需要自己创建。\n我们编辑tomcat目录下的conf子目录中的tomcat-users.xml，添加对应的配置即可：\n\u0026lt;!--admin对应的是host-manager的用户--\u0026gt; \u0026lt;!--allows access to the HTML GUI--\u0026gt; \u0026lt;role rolename=\u0026#34;admin-gui\u0026#34;/\u0026gt; \u0026lt;!--allows access to the text interface--\u0026gt; \u0026lt;role rolename=\u0026#34;admin‐script\u0026#34;/\u0026gt; \u0026lt;!--manager对应的是manager的用户--\u0026gt; \u0026lt;!--allows access to the HTML GUI and the status pages--\u0026gt; \u0026lt;role rolename=\u0026#34;manager-gui\u0026#34;/\u0026gt; \u0026lt;!--allows access to the text interface and the status pages--\u0026gt; \u0026lt;role rolename=\u0026#34;manager‐script\u0026#34;/\u0026gt; \u0026lt;!--allows access to the JMX proxy and the status pages--\u0026gt; \u0026lt;role rolename=\u0026#34;manager-jmx\u0026#34;/\u0026gt; \u0026lt;!--allows access to the status pages only--\u0026gt; \u0026lt;role rolename=\u0026#34;manager-status\u0026#34;/\u0026gt; \u0026lt;!--我们这里添加一个用户，然后定义角色即可--\u0026gt; \u0026lt;user username=\u0026#34;tinychen\u0026#34; password=\u0026#34;tinychen#321\u0026#34; roles=\u0026#34;admin‐gui,manager-gui\u0026#34;/\u0026gt; Users with the admin-gui role should not be granted the admin-script role.\n注意被授予admin-gui权限的用户不应该授予admin-script权限\nUsers with the manager-gui role should not be granted either the manager-script or manager-jmx roles.\n注意被授予manager-gui权限的用户不应该授予manager-script或manager-jmx权限\ntomcat9中默认是只允许部署tomcat的机器访问manger和host-manager的页面的，因此我们需要修改tomcat目录下对应的web应用的配置文件：\nvim /home/tomcat9/webapps/host-manager/META-INF/context.xml vim /home/tomcat9/webapps/manager/META-INF/context.xml 然后修改里面限制的IP地址为全部或者自己的IP地址即可。\n\u0026lt;Context antiResourceLocking=\u0026#34;false\u0026#34; privileged=\u0026#34;true\u0026#34; \u0026gt; \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.RemoteAddrValve\u0026#34; allow=\u0026#34;127\\.\\d+\\.\\d+\\.\\d+|::1|0:0:0:0:0:0:0:1\u0026#34; /\u0026gt; \u0026lt;Manager sessionAttributeValueClassNameFilter=\u0026#34;java\\.lang\\.(?:Boolean|Integer|Long|Number|String) |org\\.apache\\.catalina\\.filters\\.CsrfPreventionFilter\\$LruCache(?:\\$1)?|java\\.util\\.(?:Linked)?HashMap\u0026#34;/\u0026gt; \u0026lt;/Context\u0026gt; # 将allow参数改为 \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.RemoteAddrValve\u0026#34; allow=\u0026#34;^.*$\u0026#34; /\u0026gt; 然后我们就可以访问web界面来查看tomcat服务器的运行状态了。\nmanager的web界面 host-manager的web界面 Tomcat的守护进程（jsvc） 在Windows上，tomcat会默认注册成系统服务，这样设置启动和运行都方便很多，而在Linux上，我们需要借助jsvc来实现这一效果。\n什么是jsvc Commons Daemon（共享守护进程），原名JSVC，是一个属于Apache的Commons项目的Java库。守护程序提供了一种启动和停止正在运行服务器端应用程序的Java虚拟机（JVM）的便携式方法。守护程序包括两部分：用C编写的操作系统接口的原生库 ，以及提供用Java编写的Daemon API的库。\n**有两种使用Commons守护程序的方法：直接调用实现守护程序接口（interface）或调用为守护程序提供所需方法（method）的类（class）。**例如，Tomcat-4.1.x使用守护程序接口，而Tomcat-5.0.x提供了一个类，该类的方法直接由JSVC调用。\njsvc工作原理 jsvc使用了三个进程来工作：一个启动进程、一个控制进程、一个被控制进程。其中被控制进程一般来说就是java主线程（我们这里就是tomcat），如果JVM虚拟机崩溃了，那么控制进程会在下一分钟重启。因为jsvc是守护进程，所以它应该使用root用户来启动，同时我们可以使用-user参数来进行用户的降级（downgrade），即先使用root用户来创建进程，然后再降级到指定的非root用户而不丢失root用户的特殊权限，如监听1024以下的端口。\njsvc配置tomcat守护进程（daemon） tomcat的二进制安装包中的bin目录下就有jsvc的安装包，我们需要使用GCC编译器对其进行编译安装。同时在编译的时候我们需要指定jdk的路径，由于我们前面已经手动指定了，这里不需要再指定。如果没有，可以使用./configure --with-java=$JAVA_HOME来进行操作。\n# 首先我们进入tomcat的bin目录进行编译 cd $CATALINA_HOME/bin tar xvfz commons-daemon-native.tar.gz cd commons-daemon-1.2.2-native-src/unix ./configure make # 编译完成后，会在当前文件夹生成一个jsvc的文件，将它拷贝到tomcat的/bin/目录下 cp jsvc ../.. cd ../.. # 接着我们可以这样查看jsvc的帮助文档 ./jsvc -help 使用jsvc来启动tomcat，我们使用下面的参数来进行启动\n./jsvc \\ -user tomcat \\ -classpath $CATALINA_HOME/bin/bootstrap.jar:$CATALINA_HOME/bin/tomcat-juli.jar \\ -outfile $CATALINA_BASE/logs/catalina.out \\ -errfile $CATALINA_BASE/logs/catalina.err \\ -Dcatalina.home=$CATALINA_HOME \\ -Dcatalina.base=$CATALINA_BASE \\ -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \\ -Djava.util.logging.config.file=$CATALINA_BASE/conf/logging.properties \\ org.apache.catalina.startup.Bootstrap 注意看这时的用户和PID，上面的12839的用户为root，也就是我们前面说的控制进程，后面被12839进程控制的12840进程才是我们主要运行的tomcat进程，而这里的用户也符合我们使用-user参数指定的tomcat用户。如果我们不指定进程的PID文件位置，那么默认就会在/var/run目录下生成PID文件，我们可以看到这个jsvc.pid对应的正好是jsvc运行的三个进程中的被控制进程。\n如果需要关闭，我们可以使用下面的命令：\n./jsvc -stop org.apache.catalina.startup.Bootstrap stop # 还可以指定pid文件位置，如果前面没有使用默认的pid文件目录的话 ./jsvc -stop -pidfile /var/run/jsvc.pid org.apache.catalina.startup.Bootstrap stop 这个时候可能就会有同学发现，前面不是说jsvc主要有三个进程来工作的吗，怎么这里只有两个进程呢？\n我们在上面的启动命令的选项里面加入一个-wait 10的参数，然后启动之后迅速查看一下进程。\n一般情况下，启动进程在启动了控制进程之后就会结束，而当我们使用了-wait参数之后，启动进程会等待被控制进程启动好了之后向其发送一个\u0026quot;I am ready\u0026quot;信号，启动进程在收到信号之后就会结束。-wait 10表示等待时间为10秒，需要注意等待时间要是10的倍数。\n这时候可以看到存在三个jsvc相关的进程，等tomcat启动完之后再查看的时候我们就会发现最上面的19347号进程，也就是jsvc启动进程消失了。并且控制进程19350的父进程变成了1号进程。\n我们再进一步查看以下进程的关系：\n接着我们再来查看一下1号进程。可以发现，在centos7中的1号进程是systemd。\n接着我们可以总结以上的整个过程为下列步骤：\n系统启动，0号进程启动，0号通过fork()生成1号进程systemd； 1号进程systemd通过fork()创建进程sshd，这就是我们使用的ssh服务的进程； 用户使用ssh远程登录系统，sshd进程创建了对应的终端进程pts； 用户在终端输入指令，pts根据系统中指定的该用户使用的shell（此处为bash shell）来执行对应的操作，这里具体表现为根据我们输入的指令来创建jsvc的启动进程； jsvc启动进程创建jsvc控制进程，并根据启动参数决定是否在等待jsvc控制进程的\u0026quot;I am ready\u0026quot;信号再结束，同时jsvc启动进程在结束之前会把jsvc控制进程交给1号进程systemd来管理控制； jsvc控制进程创建jsvc被控制进程，也就是我们的主要进程tomcat，同时jsvc控制进程会监视jsvc被控制进程，如果它崩溃了，jsvc控制进程则会重启，确保其正常运行； 这里使用jsvc来启动tomcat的好处就是启动完成了之后即使我们的shell终端关闭了也不会影响它的运行，当然如果我们直接使用tomcat的bin目录下的启动脚本来进行启动然后再送入后台运行也是可以达到这样的效果。实际上我们还可以通过编写systemd的unit单元配置文件，将tomcat注册成系统服务。\ndaemon.sh 同样的，在tomcat的bin目录下，集成了一个daemon.sh的脚本，用来调用jsvc从而实现tomcat的守护进程。daemon.sh的实现原理还是jsvc，只不过在脚本中加入了大量的变量判断和环境配置文件读取等操作\n在官网上会建议我们直接把daemon.sh脚本复制到 /etc/init.d 目录下，就可以实现开机自动启动了。不过在CentOS7等使用了systemd的系统上，我个人更推荐使用systemd来管理。\nTomcat的守护进程（systemd+jsvc） 这里先放上archwiki和fedoraproject官网上面的链接作为参考资料：\nhttps://wiki.archlinux.org/index.php/Systemd\nhttps://docs.fedoraproject.org/en-US/quick-docs/understanding-and-administering-systemd/index.html\nsystemd简介 systemd 是 Linux 下一个与 SysV 和 LSB 初始化脚本兼容的系统和服务管理器，是 Linux 系统中最新的初始化系统（init），它主要的设计目标是克服 sysvinit 固有的缺点，提高系统的启动速度。systemd 和 ubuntu 的 upstart 是竞争对手，不过现在ubuntu也使用了systemd。\nsystemd 使用 socket 和 D-Bus 来开启服务，提供基于守护进程（daemon）的按需启动策略，保留了 Linux cgroups 的进程追踪功能，支持快照和系统状态恢复，维护挂载和自挂载点，实现了各服务间基于从属关系的一个更为精细的逻辑控制，拥有前卫的并行性能。systemd 无需经过任何修改便可以替代 sysvinit 。\n**systemd 开启和监督整个系统是基于 unit 的概念。**unit 是由一个与配置文件对应的名字和类型组成的(例如：avahi.service unit 有一个具有相同名字的配置文件，是守护进程 Avahi 的一个封装单元)。一个unit单元配置文件可以描述的内容有：系统服务（.service）、挂载点（.mount）、sockets（.sockets） 、系统设备（.device）、交换分区（.swap）、文件路径（.path）、启动目标（.target）、由 systemd 管理的计时器（.timer）。\nservice ：守护进程的启动、停止、重启和重载是此类 unit 中最为明显的几个类型。 socket ：此类 unit 封装系统和互联网中的一个 socket 。当下，systemd 支持流式、数据报和连续包的 AF_INET、AF_INET6、AF_UNIX socket 。也支持传统的 FIFO（先进先出） 传输模式。每一个 socket unit 都有一个相应的服务 unit 。相应的服务在第一个连接（connection）进入 socket 或 FIFO 时就会启动(例如：nscd.socket 在有新连接后便启动 nscd.service)。 device ：此类 unit 封装一个存在于 Linux 设备树中的设备。每一个使用 udev 规则标记的设备都将会在 systemd 中作为一个设备 unit 出现。udev 的属性设置可以作为配置设备 unit 依赖关系的配置源。 mount ：此类 unit 封装系统结构层次中的一个挂载点。 automount ：此类 unit 封装系统结构层次中的一个自挂载点。每一个自挂载 unit 对应一个已挂载的挂载 unit (需要在自挂载目录可以存取的情况下尽早挂载)。 target ：此类 unit 为其他 unit 进行逻辑分组。它们本身实际上并不做什么，只是引用其他 unit 而已。这样便可以对 unit 做一个统一的控制。(例如：multi-user.target 相当于在传统使用 SysV 的系统中运行级别5，即GUI图形化界面)；bluetooth.target 只有在蓝牙适配器可用的情况下才调用与蓝牙相关的服务，如：bluetooth 守护进程、obex 守护进程等） snapshot ：与 target unit 相似，快照本身不做什么，唯一的目的就是引用其他 unit 。 systemd的unit文件可以从多个地方加载，使用systemctl show --property=UnitPath 可以按优先级从低到高显示加载目录。\n主要的unit文件在下面的两个目录中：\n/usr/lib/systemd/system/ ：软件包安装的单元 /etc/systemd/system/ ：系统管理员安装的单元 systemd原理 这里我们重点分析一下systemd的并行操作性能以及service服务的配置单元。\n和前任的sysvinit的完全串行相比，systemd为了加速整个系统启动，实现了几乎所有的进程都并行启动（包括需要上下进程依赖的进程也并行启动）。想要实现这一点，主要需要解决三个方面的依赖问题：socket、D-Bus和文件系统。\nsocket 依赖(inetd) 绝大多数的服务依赖是套接字依赖。比如服务 A 通过一个套接字端口 S1 提供自己的服务，其他的服务如果需要服务 A，则需要连接 S1。因此如果服务 A 尚未启动，S1 就不存在，其他的服务就会得到启动错误。\n所以传统地，人们需要先启动服务 A，等待它进入就绪状态，再启动其他需要它的服务。\nsystemd 认为，只要我们预先把套接字端口S1建立好，那么其他所有的服务就可以同时启动而无需等待服务 A来创建套接字端口S1了。如果服务 A 尚未启动，那么其他进程向套接字端口S1发送的服务请求实际上会被 Linux 操作系统缓存，其他进程会在这个请求的地方等待（这里使用FIFO方式）。一旦服务A启动就绪，就可以立即处理缓存的请求，一切都开始正常运行。\n那么服务如何使用由 init 进程创建的套接字呢？\nLinux 操作系统有一个特性，当进程调用fork或者exec创建子进程之后，所有在父进程中被打开的文件句柄 (file descriptor) 都被子进程所继承。套接字也是一种文件句柄，进程A可以创建一个套接字，此后当进程 A调用 exec 启动一个新的子进程时，只要确保该套接字的close_on_exec标志位被清空，那么新的子进程就可以继承这个套接字。子进程看到的套接字和父进程创建的套接字是同一个系统套接字，就仿佛这个套接字是子进程自己创建的一样，没有任何区别。\n这个特性以前被一个叫做inetd的系统服务所利用。Inetd进程会负责监控一些常用套接字端口，比如 ssh，当该端口有连接请求时，inetd才启动telnetd进程，并把有连接的套接字传递给新的telnetd进程进行处理。这样，当系统没有 ssh 客户端连接时，就不需要启动 sshd 进程。Inetd 可以代理很多的网络服务，这样就可以节约很多的系统负载和内存资源，只有当有真正的连接请求时才启动相应服务，并把套接字传递给相应的服务进程。\n和 inetd 类似，systemd(1号进程)是所有其他进程的父进程，它可以先建立所有需要的套接字，然后在调用 exec 的时候将该套接字传递给新的服务进程，而新进程直接使用该套接字进行服务即可。\nD-Bus 依赖(bus activation) **D-Bus 是 desktop-bus 的简称，是一个低延迟、低开销、高可用性的进程间通信机制。**它越来越多地用于应用程序之间通信，也用于应用程序和操作系统内核之间的通信。很多现代的服务进程都使用D-Bus 取代套接字作为进程间通信机制，对外提供服务。\nLinux的 NetworkManager 服务就使用 D-Bus 和其他的应用程序或者服务进行交互：Linux上常见的邮件客户端软件 evolution 可以通过 D-Bus 从 NetworkManager 服务获取网络状态的改变，以便做出相应的处理。\nD-Bus 支持所谓\u0026quot;bus activation\u0026quot;功能。如果服务 A 需要使用服务 B 的 D-Bus 服务，而服务 B 并没有运行，则 D-Bus 可以在服务 A 请求服务 B 的 D-Bus 时自动启动服务 B。而服务 A 发出的请求会被 D-Bus 缓存，服务 A 会等待服务 B 启动就绪。利用这个特性，依赖 D-Bus 的服务就可以实现并行启动。\n文件系统依赖(automounter) 系统启动过程中，文件系统相关的活动是最耗时的，比如挂载文件系统，对文件系统进行磁盘检查（fsck），磁盘配额检查等都是非常耗时的操作。在等待这些工作完成的同时，系统处于空闲状态。那些想使用文件系统的服务似乎必须等待文件系统初始化完成才可以启动。但是 systemd 发现这种依赖也是可以避免的。\nsystemd 参考了 autofs 的设计思路，使得依赖文件系统的服务和文件系统本身初始化两者可以并行工作。autofs 可以监测到某个文件系统挂载点真正被访问到的时候才触发挂载操作，这是通过内核 automounter 模块的支持而实现的。systemd 集成了autofs的实现，对于系统中的挂载点，比如/home，当系统启动的时候，systemd 为其创建一个临时的自动挂载点。在这个时刻/home 真正的挂载设备尚未启动好，真正的挂载操作还没有执行，文件系统检测也还没有完成。可是那些依赖该目录的进程已经可以并发启动，他们的 open()操作被内建在 systemd 中的 autofs 捕获，将该 open()调用挂起（可中断睡眠状态）。然后等待真正的挂载操作完成，文件系统检测也完成后，systemd 将该自动挂载点替换为真正的挂载点，并让 open()调用返回。由此，实现了那些依赖于文件系统的服务和文件系统本身同时并发启动。\n对于/根目录的依赖实际上一定还是要串行执行，因为 systemd 自己也存放在/根目录之下，必须等待系统根目录挂载检查好。\n不过对于类似/home等挂载点，这种并发可以提高系统的启动速度，尤其是当/home是远程的 NFS 节点，或者是加密盘等，需要耗费较长的时间才可以准备就绪的情况下，因为并发启动，这段时间内，系统并不是完全无事可做，而是可以利用这段空余时间做更多的启动进程的事情，总的来说就缩短了系统启动时间。\n总结 从上面的三个办法我们可以看出，systemd让多个程序并行启动的解决思路就是先创建一个虚拟点，让各类需要依赖的服务先运行起来，最后再把虚拟点换成实际的服务使得能够正常运行。\nsystemd实现tomcat的daemon进程 我们在/usr/lib/systemd/system/目录下新建一个tomcat9.service文件，接下来我们可以使用systemctl命令来进行控制：\n使用 systemctl 控制单元时，通常需要使用unit文件的全名，包括扩展名（例如 sshd.service ）。但是有些unit可以在 systemctl 中使用简写方式。\n如果无扩展名，systemctl 默认把扩展名当作 .service 。例如 tomcat 和 tomcat.service 是等价的。\n挂载点会自动转化为相应的 .mount 单元。例如 /home 等价于 home.mount 。\n设备会自动转化为相应的 .device 单元，所以 /dev/sda1 等价于 dev-sda1.device 。\n使用daemon.sh 首先我们尝试在systemd中使用自带的脚本进行启动和关闭tomcat，这里我们先把startup.sh和shutdown.sh两个脚本给排除掉，虽然它们无法启动守护进程的缺陷可以使用systemd来进行弥补，但是还是无法使用jsvc，无法在特权端口和运行用户之间取得两全，我们直接使用daemon.sh来运行。\n需要注意的是，systemd并不会去读取我们先前在/etc/profile中设定的变量，因此我们直接把变量写进unit配置文件中。\n[Unit] Description=Apache Tomcat 9 [Service] User=tomcat Group=tomcat PIDFile=/var/run/tomcat.pid Environment=JAVA_HOME=/home/jdk8/ Environment=JRE_HOME=/home/jdk8/jre Environment=CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib Environment=CATALINA_HOME=/home/tomcat9 Environment=CATALINA_BASE=/home/tomcat9 Environment=CATALINA_TMPDIR=/home/tomcat9/temp ExecStart=/home/tomcat9/bin/daemon.sh start ExecStop=/home/tomcat9/bin/daemon.sh stop [Install] WantedBy=multi-user.target 添加了新的unit单元之后我们先systemctl daemon-reload重启一下daemon进程，再使用systemctl start tomcat9.service来启动服务，接着查看状态，发现无法正常运行，一启动进程就failed掉了，查看daemon脚本默认的日志文件（位于tomcat目录下的logs/catalina-daemon.out）我们发现返回了143错误。\nService exit with a return value of 143 网上搜索了一下，有个解决方案是把daemon.sh脚本中的wait参数时间从10调成240，在125行左右的位置：\n# Set the default service-start wait time if necessary test \u0026#34;.$SERVICE_START_WAIT_TIME\u0026#34; = . \u0026amp;\u0026amp; SERVICE_START_WAIT_TIME=10 wait参数调大之后，等待启动成功之后（这里用的主机配置很低，启动比较耗时）就可以正常访问了\n但是在四分钟（240s）之后我们再查看tomcat9.service就会发现，进程已经结束了，再次访问默认的8080端口也无法访问，查找进程也没有找到相关的进程。\n试图分析一波\n我们来根据上面的情况结合原理来试图分析一下：\n首先我们可以看到-wait参数时长调到240之后，bash shell进程的生命周期延长了，根据之前的jsvc工作原理部分我们可以知道-wait参数会影响jsvc的启动进程的生命周期，而从systemd输出的信息来看，有包括jsvc三个进程和bash shell进程在内共计四个进程，这和之前我们直接运行daemon.sh之后最终只有jsvc的两个进程（控制进程和被控制进程不同），且Main PID参数指向的是bash shell进程。\n于是乎我们大胆猜测一下：使用daemon.sh start命令启动tomcat，systemd会把启动daemon.sh的bash的PID作为整个service的PID来监控，而这个bash进程在启动了jsvc之后是会自行退出的，这也就导致了systemd认为service已经运行失败，从而清理掉了关联的进程，进而使得jsvc相关的tomcat进程也被清理掉了。而-wait参数时长调到240之后，bash shell进程的存活时间变长，我们就能在tomcat启动完成之后且bash shell进程结束之前访问到tomcat服务器。\n考虑到这种情况，我们可以试一下使用daemon.sh run来启动tomcat，因为在终端中使用run参数的时候会一直把log信息输出到终端，我猜测这个运行方式是和start不太一样的。\n把systemd的unit文件的启动参数改为run，同时将-wait参数时长调回默认的10，再次启动服务。\n这次我们可以看到systemd的Main PID对应为jsvc的主进程，tomcat服务也能一直正常的在后台运行。应该算是成功的使用systemd来管理jsvc启动的tomcat进程了。\n那么这两者的区别在哪里呢？接着我们打开daemon.sh这个脚本来查看一下两者的不同：\n从图中我们可以看到两者最大的不同就是使用run命令的时候是exec调用jsvc来启动tomcat并且使用了-nodetach参数。\nshell中的exec命令和直接调用不同，命令exec将并不启动新的shell，而是用要被执行命令替换当前的shell进程，并且将老进程的环境清理掉，而且exec命令后的其它命令将不再执行。\n也就是说，run命令使用exec调用了jsvc，是直接替代原来启动daemon.sh的bash shell进程，并且在这个exec命令执行完之后才会执行后面的exit命令。这样就可以让systemd的Main PID从bash shell进程顺理成章地变为jsvc的启动进程。\n那么我们知道，jsvc的启动进程在启动完jsvc控制进程之后还是会退出的，这个时候systemd还是会监听失败。而-nodetach参数的作用就是不脱离父进程而成为守护进程（ don\u0026rsquo;t detach from parent process and become a daemon），这样就能顺利地使得jsvc控制进程从它的父进程jsvc启动进程那里“得到”systemd的Main PID的位置，成为该service的主要进程。\n我们直接在终端中运行jsvc并加上-nodetach参数，可以看到即使是运行成功了之后也不会退出（控制进程继承了启动进程成为守护进程一直运行），而没加的情况下则是jsvc启动进程退出后就会退出。\n这里再放上systemd使用daemon.sh启动tomcat的整个unit文件的配置及注释：\n[Unit] Description=Apache Tomcat 9 # 对整个serive的描述，相当于备注，会出现在systemd的log中 After=network.target # 在network服务启动之后再启动 [Service] User=tomcat Group=tomcat # 运行该service的用户及用户组 PIDFile=/var/run/tomcat.pid # 该service的PID文件 Environment=JAVA_HOME=/home/jdk8/ Environment=JRE_HOME=/home/jdk8/jre Environment=CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib Environment=CATALINA_HOME=/home/tomcat9 Environment=CATALINA_BASE=/home/tomcat9 Environment=CATALINA_TMPDIR=/home/tomcat9/temp # 定义了运行时需要的变量 ExecStart=/home/tomcat9/bin/daemon.sh start ExecStop=/home/tomcat9/bin/daemon.sh stop # 对应systemd控制的start和stop命令 [Install] WantedBy=multi-user.target # 运行级别为第三级（带有网络的多用户模式） 直接使用jsvc 既然搞清楚了运行原理，我们也就可以跳过脚本直接在unit文件中定义各种参数：\n[Unit] Description=Apache Tomcat 9 After=network.target [Service] User=root Group=root # 这里使用root用户启动方便jsvc监听特权端口 # 后面可以在jsvc参数中使用-user降权到tomcat用户 PIDFile=/var/run/tomcat.pid Environment=JAVA_HOME=/home/jdk8/ Environment=JRE_HOME=/home/jdk8/jre Environment=CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib Environment=CATALINA_HOME=/home/tomcat9 Environment=CATALINA_BASE=/home/tomcat9 Environment=CATALINA_TMPDIR=/home/tomcat9/temp ExecStart=/home/tomcat9/bin/jsvc \\ -user tomcat \\ -nodetach \\ -java-home ${JAVA_HOME} \\ -pidfile ${CATALINA_BASE}/tomcat.pid \\ -classpath ${CATALINA_HOME}/bin/bootstrap.jar:${CATALINA_HOME}/bin/tomcat-juli.jar \\ -outfile ${CATALINA_BASE}/logs/catalina.out \\ -errfile ${CATALINA_BASE}/logs/catalina.err \\ -Dcatalina.home=${CATALINA_HOME} \\ -Dcatalina.base=${CATALINA_BASE} \\ -Djava.io.tmpdir=${CATALINA_TMPDIR} \\ -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \\ -Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \\ org.apache.catalina.startup.Bootstrap ExecStop=/home/tomcat9/bin/jsvc \\ -stop \\ -classpath ${CLASSPATH} \\ -Dcatalina.base=${CATALINA_BASE} \\ -Dcatalina.home=${CATALINA_HOME} \\ -pidfile ${CATALINA_BASE}/tomcat.pid \\ -Djava.io.tmpdir=${CATALINA_TMPDIR} \\ -Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \\ -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \\ org.apache.catalina.startup.Bootstrap [Install] WantedBy=multi-user.target 注意：ExecStart和ExecStop两个命令中的执行文件路径需要使用绝对路径\nTomcat的工作模式 Tomcat不仅可以单独运行，还可以与其他的Web服务器集成，作为其他Web服务器的进程内或进程外的servlet容器。集成的意义在于：对于不支持运行Java Servlet的其他Web服务器，可通过集成Tomcat来提供运行Servlet的功能。\nTomcat有三种工作模式：\n**第一种：Tomcat在一个Java虚拟机进程中独立运行，此时客户端直接和tomcat通信。**Tomcat可看作是能运行Servlet的独立Web服务器。Servlet容器组件作为Web服务器中的一部分而存在。这是Tomcat的默认工作模式。\n**第二种：Tomcat运行在其他Web服务器的进程中，Tomcat不直接和客户端通信，仅仅为其他Web服务器处理客户端访问Servlet的请求。**进程内的Servlet容器对于单进程、多线程的Web服务器非常合适，可以提供较高的运行速度，但缺乏伸缩性。\n在这种模式下，Tomcat分为Web服务器插件和Servlet容器组件两部分。如下图所示，Web服务器插件在其他Web服务器进程的内部地址空间启动一个Java虚拟机，Servlet容器组件在此Java虚拟机中运行。如有客户端发出调用Servlet的请求，Web服务器插件获得对此请求的控制并将它转发**（使用JNI通信机制）**给Servlet容器组件。\nJNI（Java Native Interface）指的是Java本地调用接口，通过这一接口，Java程序可以和采用其他语言编写的本地程序进行通信。\n第三种：Tomcat在一个Java虚拟机进程中独立运行，但是它不直接和客户端通信，仅仅为与它集成的其他Web服务器处理客户端访问Servlet的请求。\n在这种模式下，Tomcat分为Web服务器插件和Servlet容器组件两部分。如下图所示，Web服务器插件在其他Web服务器的外部地址空间启动一个JVM进程，Servlet容器组件在此JVM中运行。如有客户端发出调用Servlet的请求，Web服务器插件获得对此请求的控制并将它转发（采用IPC通信机制）给Servlet容器。\n进程外Servlet容器对客户请求的响应速度不如进程内Servlet容器，但进程外容器具有更好的伸缩性和稳定性。\nIPC（Inter-Process Communication，进程间通信）是两个进程之间进行通信的一种机制。\nTomcat的整体架构 我们先从tomcat的源码目录来分析一下tomcat的整体架构，前面我们配置jsvc运行tomcat的时候，我们知道tomcat中启动运行的最主要的类是org.apache.catalina.startup.Bootstrap，那么我们在tomcat的源码中的java目录下的org目录的apache目录可以找到主要的源码的相对应的类。\n图中的目录如果画成架构图，可以这样表示：\nTomcat 本质上就是一款Servlet 容器，因此catalina才是Tomcat的核心 ，其他模块都是为catalina提供支撑的。\ncoyote模块主要负责链接通信，Tomcat作为http服务器，需要从socket中获得HTTP数据流；而Tomcat作为容器，只能处理封装好的org.apache.coyote.Request，因此从socket到Request之间的转换就交给coyote来负责了。因此，连接socket和容器之间的重任就交给了Coyote。简单说就是coyote来处理底层的socket，并将http请求、响应等字节流层面的东西，包装成Request和Response两个类（这两个类是tomcat定义的，而非servlet中的ServletRequest和ServletResponse），供容器使用；同时，为了能让我们编写的servlet能够得到ServletRequest，tomcat使用了facade模式，将比较底层、低级的Request包装成为ServletRequest（这一过程通常发生在Wrapper容器一级） jasper模块提供JSP引擎，在jsp文件被初次访问的时候做出响应，将jsp页面翻译成servlet请求，然后调用java编译器对servlet进行编译得到class文件，再调用jvm来执行class文件生成应答，最后把应答发送回客户端。\nel全名为Expression Language，也叫JUEL，主要在Java Web应用中用于将表达式嵌入到web页面\nnaming提供JNDI 服务(Java Naming and Directory Interface,Java命名和目录接口)，为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口，由管理者将JNDI API映射为特定的命名服务和目录系统，使得Java应用程序可以和这些命名服务和目录服务之间进行交互。\njuli提供日志服务，JDK 所提供的默认 java.util.logging 实现功能太过局限，不能实现针对每一应用进行日志记录，因为配置是针对VM的。而juli通过自定义的 LogManager 能分辨运行在 Tomcat 上的不同 Web 应用（以及它们所用的不同的类加载器），还能针对每一应用进行私有的日志配置。\nTomcat的容器架构 Tomcat设计了4种容器，分别是Engine、Host、Context和Wrapper。这4种容器是父子关系， Tomcat通过一种分层的架构，使得Servlet容器具有很好的灵活性。\n如上图所示，我们可以看到：\n一台机器上可以通过设置不同的CATALINA_BASE来运行多个tomcat实例，即可以运行多个server 一个server中只有一个Engine，而Engine就是实现了servlet规范的引擎，这里就是Catalina 一个engine中可以包含多个host，即和apache、nginx等服务器相同，可以配置多个virtual host站点 一个host中可以包含多个context，即可以包含多个web应用 一个warpper表示一个Servlet，wrapper 作为容器中的最底层，不能包含子容器 Tomcat使用组合模式来管理这些容器，所有容器组件都实现了Container接口，因此组合模式可以使得用户对单容器对象（最底层的Wrapper）和组合容器对象（Context、Host或者Engine）的使用具有一致性。\nTomcat 服务器的配置主要集中于 tomcat/conf 下的 catalina.policy、 catalina.properties、context.xml、server.xml、tomcat-users.xml、web.xml 文件。\nTomcat的这一设计思想在其配置文件server.xml中得到了很好的诠释，server.xml 是tomcat 服务器的核心配置文件，包含了Tomcat的 Servlet 容器 （Catalina）的所有配置。下面我们先来了解一下server.xml文件中的一些主要配置。\nTomcat的connector简介 connector的工作原理 这里我们说的Tomcat中三种不同的I/O模型主要指的是其连接器（connector）的工作模型，对于tomcat而言，连接器一般指的是coyote，其工作原理大致如下图所示：\n连接器中的各个组件的作用如下：\nEndPoint EndPoint即Coyote通信端点，是通信监听的接口，是具体Socket接收和发送处理器，是对传输层（四层）的抽象，因此EndPoint用来实现TCP/IP协议的。Tomcat 并没有EndPoint接口，而是提供了一个抽象类AbstractEndpoint， 里面定义了两个内部类：Acceptor和SocketProcessor。Acceptor用于监听Socket连接请求。 SocketProcessor用于处理接收到的Socket请求，它实现Runnable接口，在Run方法里 调用协议处理组件Processor进行处理。为了提高处理能力，SocketProcessor被提交到线程池来执行，而这个线程池叫作执行器（Executor)。\nProcessor **Processor是coyote的协议处理接口 。**如果说EndPoint是用来实现TCP/IP协议的，那么 Processor用来实现HTTP协议，Processor接收来自EndPoint的Socket，读取字节流解析成Tomcat的Request和Response对象，并通过Adapter将其提交到容器处理， Processor是对应用层（七层）协议的抽象。\nProtocolHandler **ProtocolHandler是Coyote的协议接口，通过Endpoint和Processor ，实现对具体协议（HTTP或AJP）的处理。**Tomcat 按照协议和I/O 提供了6个实现类 ： AjpNioProtocol ， AjpAprProtocol， AjpNio2Protocol ， Http11NioProtocol ，Http11Nio2Protocol ， Http11AprProtocol。我们在配置tomcat/conf/server.xml 中的connecter块时 ， 至少要指定具体的ProtocolHandler , 当然也可以指定协议名称（如HTTP/1.1）。\nAdapter 由于协议不同，客户端发过来的请求信息也不尽相同，Tomcat定义了自己的Request类来存放这些请求信息。ProtocolHandler接口负责解析请求并生成Tomcat的Request类。 但是这个Request对象不是标准的ServletRequest，不能用来作为参数来调用容器。因此需要引入CoyoteAdapter，连接器调用CoyoteAdapter的Sevice方法，传入Tomcat的Request对象，CoyoteAdapter将Request转成ServletRequest，再调用容器的Service方法。\nconnector的几个重要参数 connectionTimeout The number of milliseconds this Connector will wait, after accepting a connection, for the request URI line to be presented. Use a value of -1 to indicate no (i.e. infinite) timeout. The default value is 60000 (i.e. 60 seconds) but note that the standard server.xml that ships with Tomcat sets this to 20000 (i.e. 20 seconds). Unless disableUploadTimeout is set to false, this timeout will also be used when reading the request body (if any).\n在connector和请求的客户端建立连接之后开始计时，当超过该值的时候就会超时，然后断开连接。使用值-1表示无超时，默认值为60000（即60秒），但Tomcat中的server.xml将此值设置为20000（即20秒）。\n除非disableUploadTimeout设置为false，否则在读取请求正文（如果有）时也会使用此超时。\nmaxThreads The maximum number of request processing threads to be created by this Connector, which therefore determines the maximum number of simultaneous requests that can be handled. If not specified, this attribute is set to 200. If an executor is associated with this connector, this attribute is ignored as the connector will execute tasks using the executor rather than an internal thread pool. Note that if an executor is configured any value set for this attribute will be recorded correctly but it will be reported (e.g. via JMX) as -1 to make clear that it is not used.\n最大线程数，大并发请求时，tomcat能创建来处理请求的最大线程数，超过则放入请求队列中进行排队，默认值为200。\nacceptCount The maximum queue length for incoming connection requests when all possible request processing threads are in use. Any requests received when the queue is full will be refused. The default value is 100.\n当最大线程数（maxThreads）被使用完时，可以放入请求队列排队个数，超过这个数返回connection refused（请求被拒绝），默认值为100；\nmaxConnections The maximum number of connections that the server will accept and process at any given time. When this number has been reached, the server will accept, but not process, one further connection. This additional connection be blocked until the number of connections being processed falls below maxConnections at which point the server will start accepting and processing new connections again. Note that once the limit has been reached, the operating system may still accept connections based on the acceptCount setting. The default value is 8192.For NIO/NIO2 only, setting the value to -1, will disable the maxConnections feature and connections will not be counted.\nTomcat在任意时刻接收和处理的最大连接数。当Tomcat接收的连接数达到maxConnections时，Acceptor线程不会读取accept队列中的连接；这时accept队列中的线程会一直阻塞着，直到Tomcat接收的连接数小于maxConnections。默认值为8192。\n对于NIO / NIO2，将该值设置为-1将禁用maxConnections功能，并且不计算连接数。\n图解 按照被处理的先后顺序我们可以把tomcat中的线程队列和以上四个参数使用该图进行表示\n当maxThreads + acceptCount \u0026lt; maxConnections的时候将不会有线程被阻塞 当阻塞的线程时间超过connectionTimeout还没得到返回值将返回连接超时 常见的服务器I/O模型 在开始了解Tomcat的I/O模型之前，我们需要先对服务器中常见的I/O模型进行简单介绍。\n阻塞I/O处理模型 单线程阻塞I/O模型 单线程阻塞I/O模型是最简单的一种服务器I/O模型，单线程即同时只能处理一个客户端的请求，阻塞即该线程会一直等待，直到处理完成为止。对于多个客户端访问，必须要等到前一个客户端访问结束才能进行下一个访问的处理，请求一个一个排队，只提供一问一答服务。\n如上图所示：这是一个同步阻塞服务器响应客户端访问的时间节点图。\n首先，服务器必须初始化一个套接字服务器，并绑定某个端口号并使之监听客户端的访问 接着，客户端1调用服务器的服务，服务器接收到请求后对其进行处理，处理完后写数据回客户端1，整个过程都是在一个线程里面完成的 最后，处理客户端2的请求并写数据回客户端2，期间就算客户端2在服务器处理完客户端1之前就进行请求，也要等服务器对客户端1响应完后才会对客户端2进行响应处理 **这种模型的特点在于单线程和阻塞I/O。**单线程即服务器端只有一个线程处理客户端的所有请求，客户端连接与服务器端的处理线程比是n:1，它无法同时处理多个连接，只能串行处理连接。而阻塞I/O是指服务器在读写数据时是阻塞的，读取客户端数据时要等待客户端发送数据并且把操作系统内核复制到用户进程中，这时才解除阻塞状态。写数据回客户端时要等待用户进程将数据写入内核并发送到客户端后才解除阻塞状态。这种阻塞带来了一个问题，服务器必须要等到客户端成功接收才能继续往下处理另外一个客户端的请求，在此期间线程将无法响应任何客户端请求。\n该模型的特点：它是最简单的服务器模型，整个运行过程都只有一个线程，只能支持同时处理一个客户端的请求(如果有多个客户端访问，就必须排队等待)，服务器系统资源消耗较小，但并发能力低，容错能力差。\n多线程阻塞I/O模型 多线程阻塞I/O模型在单线程阻塞I/O模型的基础上对其进行改进，加入多线程，提高并发能力，使其能够同时对多个客户端进行响应，多线程的核心就是利用多线程机制为每个客户端分配一个线程。\n如上图所示，服务器端开始监听客户端的访问，假如有两个客户端同时发送请求过来，服务器端在接收到客户端请求后分别创建两个线程对它们进行处理，每条线程负责一个客户端连接，直到响应完成。期间两个线程并发地为各自对应的客户端处理请求，包括读取客户端数据、处理客户端数据、写数据回客户端等操作。\n这种模型的I/O操作也是阻塞的，因为每个线程执行到读取或写入操作时都将进入阻塞状态，直到读取到客户端的数据或数据成功写入客户端后才解除阻塞状态。尽管I/O操作阻塞，但这种模式比单线程处理的性能明显高了，它不用等到第一个请求处理完才处理第二个，而是并发地处理客户端请求，客户端连接与服务器端处理线程的比例是1:1。\n多线程阻塞I/O模型的特点：支持对多个客户端并发响应，处理能力得到大幅提高，有较大的并发量，但服务器系统资源消耗量较大，而且如果线程数过多，多线程之间会产生较大的线程切换成本，同时拥有较复杂的结构。\n非阻塞I/O模型 非阻塞情况下的事件检测 在探讨单线程非阻塞I/O模型前必须要先了解非阻塞情况下套接字事件的检测机制，因为对于单线程非阻塞模型最重要的事情是检测哪些连接有感兴趣的事件发生。一般会有如下三种检测方式。\n此处“有感兴趣的事件发生”指的是需要进行读写数据等操作。\n应用程序遍历套接字的事件检测 当多个客户端向服务器请求时，服务器端会保存一个套接字连接列表中，应用层线程对套接字列表轮询尝试读取或写入。如果成功则进行处理，如果失败则下次继续。这样不管有多少个套接字连接，它们都可以被一个线程管理，这很好地利用了阻塞的时间，处理能力得到提升。\n但这种模型需要在应用程序中遍历所有的套接字列表，同时需要处理数据的拼接，连接空闲时可能也会占用较多CPU资源，不适合实际使用。\n内核遍历套接字的事件检测 这种方式将套接字的遍历工作交给了操作系统内核，把对套接字遍历的结果组织成一系列的事件列表并返回应用层处理。对于应用层，它们需要处理的对象就是这些事件，这是一种事件驱动的非阻塞方式。\n服务器端有多个客户端连接，应用层向内核请求读写事件列表。内核遍历所有套接字并生成对应的可读列表readList和可写列表writeList。readList和writeList则标明了每个套接字是否可读/可写。应用层遍历读写事件列表readList和writeList，做相应的读写操作。\n内核遍历套接字时已经不用在应用层对所有套接字进行遍历，将遍历工作下移到内核层，这种方式有助于提高检测效率。**然而，它需要将所有连接的可读事件列表和可写事件列表传到应用层，假如套接字连接数量变大，列表从内核复制到应用层也是不小的开销。**另外，当活跃连接较少时，内核与应用层之间存在很多无效的数据副本，因为它将活跃和不活跃的连接状态都复制到应用层中。\n内核基于回调的事件检测 通过遍历的方式检测套接字是否可读可写是一种效率比较低的方式，不管是在应用层中遍历还是在内核中遍历。所以需要另外一种机制来优化遍历的方式，那就是回调函数。内核中的套接字都对应一个回调函数，当客户端往套接字发送数据时，内核从网卡接收数据后就会调用回调函数，在回调函数中维护事件列表，应用层获取此事件列表即可得到所有感兴趣的事件。\n内核基于回调的事件检测方式有两种\n方式一： 第一种是用可读列表readList和可写列表writeList标记读写事件，套接字的数量与readList和writeList两个列表的长度一样。\n服务器端有多个客户端套接字连接\n当客户端发送数据过来时，内核从网卡复制数据成功后调用回调函数将readList/writeList对应的元素标记为可读/可写\n应用层发送请求读、写事件列表，内核返回包含了事件标识的readList和writeList事件列表，此时返回的两个列表内容大致如下\n套接字 readList 1 1 2 0 3 1 …… …… n …… 套接字 writeList 1 0 2 1 3 0 …… …… n …… 应用程序接着分表遍历读事件列表readList和写事件列表writeList，对置为1的元素对应的套接字进行读或写操作\n这样就避免了遍历套接字的操作，但仍然有大量无用的数据(状态为0的元素)从内核复制到应用层中。从上面的表格中我们可以看到实际上有用的数据只是在List中被标记为1的数据（意味着可读或可写），其他的数据并没有传送回去的必要。\n方式二： 服务器端有多个客户端套接字连接。\n应用层告诉内核每个套接字感兴趣的事件，这时候直接发送一个列表给内核\n套接字 操作 1 read 2 write 3 read …… …… n …… 接着，当客户端发送数据过来时，对应会有一个回调函数，内核从网卡复制数据成功后即调回调函数将套接字1作为可读事件event1加入到事件列表，同样地，内核发现网卡可写时就将套接字2作为可写事件event2添加到事件列表中\n应用层向内核请求读、写事件列表，内核将包含了event1和event2的事件列表返回应用层，此时的列表内容大致如下：\n套接字 可以进行的操作 1 read 2 write 注意这时不能进行读写操作的套接字是不会被记录到列表中返回给应用层的，这就大大地减少了数据的传输量。\n应用层通过遍历事件列表得知哪些套接字可以进行哪些操作，然后执行对应的操作。\n上面两种方式由操作系统内核维护客户端的所有连接并通过回调函数不断更新事件列表，而应用层线程只要遍历这些事件列表即可知道可读取或可写入的连接，进而对这些连接进行读写操作，极大提高了检测效率，自然处理能力也更强。\n单线程非阻塞I/O模型 单线程非阻塞I/O模型最重要的一个特点是，在调用读取或写入接口后立即返回，而不会进入阻塞状态。虽然只有一个线程，但是它通过把非阻塞读写操作与上面几种检测机制配合就可以实现对多个连接的及时处理，而不会因为某个连接的阻塞操作导致其他连接无法处理。在客户端连接大多数都保持活跃的情况下，这个线程会一直循环处理这些连接，它很好地利用了阻塞的时间，大大提高了这个线程的执行效率。\n单线程非阻塞I/O模型的主要优势体现在对多个连接的管理，一般在同时需要处理多个连接的发场景中会使用非阻塞NIO模式，此模型下只通过一个线程去维护和处理连接，这样大大提高了机器的效率。一般服务器端才会使用NIO模式，而对于客户端，出于方便及习惯，可使用阻塞模式的套接字进行通信。\n多线程非阻塞I/O模型 在多核的机器上可以通过多线程继续提高机器效率。最朴实、最自然的做法就是将客户端连接按组分配给若干线程，每个线程负责处理对应组内的连接。比如有4个客户端访问服务器，服务器将套接字1和套接字2交由线程1管理，而线程2则管理套接字3和套接字4，通过事件检测及非阻塞读写就可以让每个线程都能高效处理。\n多线程非阻塞I/O模式让服务器端处理能力得到很大提高，它充分利用机器的CPU，适合用于处理高并发的场景，但它也让程序更复杂，更容易出现问题（死锁、数据不一致等经典并发问题）。\nReactor模式 最经典的多线程非阻塞I/O模型方式是Reactor模式。首先看单线程下的Reactor，Reactor将服务器端的整个处理过程分成若干个事件，例如分为接收事件、读事件、写事件、执行事件等。Reactor通过事件检测机制将这些事件分发给不同处理器去处理。在整个过程中只要有待处理的事件存在，即可以让Reactor线程不断往下执行，而不会阻塞在某处，所以处理效率很高。\n基于单线程Reactor模型，根据实际使用场景，把它改进成多线程模式。常见的有两种方式：一种是在耗时的process处理器中引入多线程，如使用线程池；另一种是直接使用多个Reactor实例，每个Reactor实例对应一个线程。\nReactor模式的一种改进方式如下图所示。其整体结构基本上与单线程的Reactor类似，只是引入了一个线程池。由于对连接的接收、对数据的读取和对数据的写入等操作基本上都耗时较少，因此把它们都放到Reactor线程中处理。然而，对于逻辑处理可能比较耗时的工作，可以在process处理器中引入线程池，process处理器自己不执行任务，而是交给线程池，从而在Reactor线程中避免了耗时的操作。将耗时的操作转移到线程池中后，尽管Reactor只有一个线程，它也能保证Reactor的高效。\nReactor模式的另一种改进方式如下图所示。其中有多个Reactor实例，每个Reactor实例对应一个线程。因为接收事件是相对于服务器端而言的，所以客户端的连接接收工作统一由一个accept处理器负责，accept处理器会将接收的客户端连接均匀分配给所有Reactor实例，每个Reactor实例负责处理分配到该Reactor上的客户端连接，包括连接的读数据、写数据和逻辑处理。这就是多Reactor实例的原理。\nTomcat的三种主要I/O模型 Tomcat支持的I/O模型如下表（自8.5/9.0 版本起，Tomcat移除了对BIO的支持），在 8.0 之前 ， Tomcat 默认采用的I/O方式为 BIO ， 之后改为 NIO。 无论 NIO、NIO2 还是 APR， 在性能方面均优于以往的BIO。\nIO模型 描述 NIO 同步非阻塞I/O，采用Java NIO类库实现 NIO2 异步非阻塞I/O，采用JDK 7最新的NIO2类库实现 APR 采用Apache可移植运行库实现，是C/C++编写的本地库，需要单独安装APR库 在开始之前，我们先看一下tomcat官网给出的这三种I/O模型的工作参数的一个对比图：\n这里我们可以看到一般说的NIO、NIO2和APR使用的是非阻塞方式指的就是在读取请求报头和等待下一个请求的时候是使用的非阻塞方式。\n**Tomcat的NIO是基于I/O复用（同步I/O）来实现的，而NIO2是使用的异步I/O。**参考经典书籍《UNIX网络编程 卷1 套接字联网API》，两者的主要原理如下：\nI/O复用\nI/O复用（I/O multiplexing）可以调用select或poll，阻塞在这两个系统调用中的某一个之上，而不是阻塞在真正的I/O系统调用上。进程阻塞于select调用，等待数据报套接字变为可读。当select返回套接字可读这一条件时，进程调用recvfrom把所读数据报复制到应用进程缓冲区，尽管这里需要使用select和recvfrom两个系统调用，但是使用select的可以等待多个描述符就绪，即可以等待多个请求。\n异步IO\n异步I/O（asynchronous I/O）的工作机制是：告知内核启动某个操作，并让内核在整个操作（包括将数据从内核复制到应用程序的缓冲区）完成后通知应用程序。需要注意的是：异步I/O模型是由内核通知应用进程I/O操作何时完成。\n最后我们可以把上面的过程结合剩下没有提到的三种UNIX系统中的IO模型进行对比得到下图：\nNIO（New I/O APIs、同步非阻塞） **Tomcat中的NIO模型是使用的JAVA的NIO类库，其内部的IO实现是同步的（也就是在用户态和内核态之间的数据交换上是同步机制），采用基于selector实现的异步事件驱动机制（这里的异步指的是selector这个实现模型是使用的异步机制）。**而对于Java来说，非阻塞I/O的实现完全是基于操作系统内核的非阻塞I/O，它将操作系统的非阻塞I/O的差异屏蔽并提供统一的API，让我们不必关心操作系统。JDK会帮我们选择非阻塞I/O的实现方式。\n这里需要提一下同步异步和阻塞非阻塞的概念：\n同步和异步关注的是消息通信机制，同步异步指的是应用程序发起的调用请求和获得的返回值是否一起返回，如果一起返回就是同步，否则就是异步，异步可以通过回调函数等方式实现。\n阻塞和非阻塞关注的是程序在等待调用结果时的状态，应用程序发起调用请求之后不能干别的事情直到请求处理完成了就是阻塞，否则就是非阻塞。\n所以我个人认为，对于阻塞I/O谈同步异步是没有太大意义的，因为此时进程已经阻塞，想要去干别的事情必须得等请求处理完，而请求处理完必然会得到返回值。\n上面我们提到得内核基于回调得事件检测方式二就是典型的异步非阻塞I/O模型。\nNIO2（New I/O APIs 2、异步非阻塞、AIO） NIO2和前者相比的最大不同就在于引入了异步通道来实现异步IO操作，因此也叫AIO（Asynchronous I/O）。NIO.2 的异步通道 APIs 提供方便的、平台独立的执行异步操作的标准方法。这使得应用程序开发人员能够以更清晰的方式来编写程序，而不必定义自己的 Java 线程，此外，还可通过使用底层 OS 所支持的异步功能来提高性能。如同其他 Java API 一样，API 可利用的 OS 自有异步功能的数量取决于其对该平台的支持程度。\n异步通道提供支持连接、读取、以及写入之类非锁定操作的连接，并提供对已启动操作的控制机制。Java 7 中用于 Java Platform（NIO.2）的 More New I/O APIs，通过在 java.nio.channels 包中增加四个异步通道类，从而增强了 Java 1.4 中的 New I/O APIs（NIO），这些类在风格上与 NIO 通道 API 很相似。他们共享相同的方法与参数结构体，并且大多数对于 NIO 通道类可用的参数，对于新的异步版本仍然可用。主要区别在于新通道可使一些操作异步执行。\n异步通道 API 提供两种对已启动异步操作的监测与控制机制。第一种是通过返回一个 java.util.concurrent.Future 对象来实现，它将会建模一个挂起操作，并可用于查询其状态以及获取结果。第二种是通过传递给操作一个新类的对象，java.nio.channels.CompletionHandler，来完成，它会定义在操作完毕后所执行的处理程序方法。每个异步通道类为每个操作定义 API 副本，这样可采用任一机制。\nAPR **Apache可移植运行时（Apache Portable Runtime，APR）**是Apache HTTP服务器的支持库，最初，APR是作为Apache HTTP服务器的一部分而存在的，后来成为一个单独的项目。其他的应用程序可以使用APR来实现平台无关性（跨平台）。APR提供了一组映射到下层操作系统的API，如果操作系统不支持某个特定的功能，APR将提供一个模拟的实现。这样程序员使用APR编写真正可在不同平台上移植的程序。\nTomcat配置APR # 首先使用yum来安装apr yum install apr apr-devel # 进入tomcat目录下对tomcat-native进行解压 cd /home/tomcat9/bin/ tar -zxvf tomcat-native.tar.gz cd tomcat-native-1.2.23-src/native/ # 编译安装 ./configure make make install 顺利安装完成后会显示apr的lib库路径，一般都是/usr/local/apr/lib\n安装完成之后我们还需要修改环境变量和配置参数\n这里我们使用的是systemd调用jsvc来启动tomcat，所以我们直接在systemd对应的tomcat的unit文件中的ExecStart中添加一个路径参数-Djava.library.path=/usr/local/apr/lib指向apr库的路径：\nExecStart=/home/tomcat9/bin/jsvc \\ -user tomcat \\ -nodetach \\ -java-home ${JAVA_HOME} \\ -pidfile ${CATALINA_BASE}/tomcat.pid \\ -classpath ${CATALINA_HOME}/bin/bootstrap.jar:${CATALINA_HOME}/bin/tomcat-juli.jar \\ -outfile ${CATALINA_BASE}/logs/catalina.out \\ -errfile ${CATALINA_BASE}/logs/catalina.err \\ -Dcatalina.home=${CATALINA_HOME} \\ -Dcatalina.base=${CATALINA_BASE} \\ -Djava.io.tmpdir=${CATALINA_TMPDIR} \\ -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \\ -Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \\ -Djava.library.path=/usr/local/apr/lib \\ org.apache.catalina.startup.Bootstrap 然后我们在tomcat的home目录下的conf子目录中对server.xml文件进行修改\n把8080端口对应的配置修改成apr：（其他端口配置也类似）\n\u0026lt;Connector port=\u0026#34;8080\u0026#34; protocol=\u0026#34;org.apache.coyote.http11.Http11AprProtocol\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; redirectPort=\u0026#34;8443\u0026#34; /\u0026gt; 重启tomcat服务我们从tomcat的日志中就可以看到协议已经从默认的nio变成了apr。\n三者之间的区别： NIO NIO2 APR 实现 JAVA NIO库 JDK1.7 NIO2库 C IO模型 同步非阻塞 异步非阻塞 取决于系统 APR的重点在于使用C语言实现并且能够跨平台使用，它相当于将UNIX系统中的IO操作进行了一层封装使得编程开发更容易\nNIO性能是最差的这是毋庸置疑的，如果是考虑到高并发的情况，显然异步非阻塞I/O模式的NIO2和APR库在性能上更有优势，实际上NIO2的性能表现也和APR不相上下，但是NIO2要求Tomcat的版本要在8.0以上，而APR只需要5.5以上即可，但是APR需要额外配置库环境，相对于内置集成的NIO2来说APR这个操作比较麻烦，两者各有优劣。具体使用哪个还是需要结合实际业务需求和环境进行测试才能决定。\nserver.xml Tomcat中的大多数配置都会在server.xml文件中，server.xml的地位就好像nginx中的nginx.conf文件，因此我们想要学习配置tomcat的各类参数，最先开始学习的配置就是server.xml文件。\nserver.xml整体架构 首先我们需要知道server.xml中的xml代码块分类，tomcat官网将其主要分为四类：\nTop Level Elements：server块是整个配置文件的根元素，而service块代表与引擎关联的一组连接器（connector）。 Connectors ：表示外部客户端向特定服务发送请求和接收响应的接口（比如我们之前提到的coyote连接器以及对应的NIO等IO模式都是整个范畴内的概念）。 Containers：容器（Container）负责处理传入的请求并创建相应的响应。Engine处理对Service的所有请求，Host处理对特定virtual host的所有请求，而Context处理对特定Web应用程序的所有请求。 Nested Components：表示可以嵌套在Container元素内的元素。 注意一些元素可以嵌套在任何Container中，而另一些元素只能嵌套在Context中。 Top Level Elements Server块 Server块代表的是整个catalina servlet容器。因此，它必须是conf/server.xml配置文件中最外面的单个元素。它的属性代表了整个servlet容器的特征。Tomcat9中默认的配置文件中Server块内嵌的子元素为 Listener、GlobalNamingResources、Service（可以嵌套多个）。具体的每个属性参数我们可以查询官网，下面解释默认的参数配置。\n\u0026lt;Server port=\u0026#34;8005\u0026#34; shutdown=\u0026#34;SHUTDOWN\u0026#34;\u0026gt; \u0026lt;!-- port ： Tomcat监听的关闭服务器的端口 shutdown ： 关闭服务器的指令字符串 --\u0026gt; \u0026lt;!-- 以日志形式输出服务器、操作系统、JVM的版本信息 --\u0026gt; \u0026lt;Listener className=\u0026#34;org.apache.catalina.startup.VersionLoggerListener\u0026#34; /\u0026gt; \u0026lt;!-- 启动和停止APR。如果找不到APR库会输出日志但并不影响tomcat正常启动 --\u0026gt; \u0026lt;Listener className=\u0026#34;org.apache.catalina.core.AprLifecycleListener\u0026#34; SSLEngine=\u0026#34;off\u0026#34; /\u0026gt; \u0026lt;!-- 注意这里的SSLEngine默认是打开的（on） 如果启用了apr作为连接器的协议 但是只配置了http而没有配置https 则会报错 --\u0026gt; \u0026lt;!-- 用于避免JRE内存泄漏问题 --\u0026gt; \u0026lt;Listener className=\u0026#34;org.apache.catalina.core.JreMemoryLeakPreventionListener\u0026#34; /\u0026gt; \u0026lt;!-- 用户加载（服务器启动）和销毁（服务器停止）全局命名服务 --\u0026gt; \u0026lt;Listener className=\u0026#34;org.apache.catalina.mbeans.GlobalResourcesLifecycleListener\u0026#34; /\u0026gt; \u0026lt;!-- 用于在Context停止时重建Executor池中的线程， 以避免ThreadLocal相关的内 存泄漏 --\u0026gt; \u0026lt;Listener className=\u0026#34;org.apache.catalina.core.ThreadLocalLeakPreventionListener\u0026#34; /\u0026gt; \u0026lt;!-- GlobalNamingResources中定义了全局命名服务： --\u0026gt; \u0026lt;GlobalNamingResources\u0026gt; \u0026lt;Resource name=\u0026#34;UserDatabase\u0026#34; auth=\u0026#34;Container\u0026#34; type=\u0026#34;org.apache.catalina.UserDatabase\u0026#34; description=\u0026#34;User database that can be updated and saved\u0026#34; factory=\u0026#34;org.apache.catalina.users.MemoryUserDatabaseFactory\u0026#34; pathname=\u0026#34;conf/tomcat-users.xml\u0026#34; /\u0026gt; \u0026lt;!--这里定义的文件就是我们前面配置manager和host manager的用户的文件--\u0026gt; \u0026lt;/GlobalNamingResources\u0026gt; \u0026lt;Service\u0026gt; ... \u0026lt;/Service\u0026gt; \u0026lt;/Server\u0026gt; Service块 Service元素用于创建 Service 实例，默认使用 org.apache.catalina.core.StandardService。 默认情况下，Tomcat9中默认仅指定了Service的名称为Catalina。\n\u0026lt;Service name=\u0026#34;Catalina\u0026#34;\u0026gt; ... \u0026lt;/Service\u0026gt; Service 可以内嵌的元素为 ： Listener、Executor、Connector、Engine ，详细的参数可以点击这里查看官网\nListener 用于为Service 添加生命周期监听器 Executor 用于配置Service 共享线程池 Connector 用于配置 Service 包含的链接器 Engine 用于配置Service中连接器（connector）对应的Servlet 容器引擎 Executor executor表示可组件之间Tomcat中共享的线程池。默认情况下，Service并未添加共享线程池配置。executor实现了tomcat中的org.apache.catalina.Executor接口。 如果不配置共享线程池，那么Catalina 各组件在用到线程池时会独立创建。由于executor是Service元素的嵌套元素。为了使它能够被Connector使用，Executor元素必须出现在server.xml中的Connector元素之前。下面展示的是一个简单的executor的配置，具体的配置参数可以点这里查看官网：\n\u0026lt;Executor name=\u0026#34;tomcatThreadPool\u0026#34; namePrefix=\u0026#34;catalina‐exec‐\u0026#34; maxThreads=\u0026#34;200\u0026#34; minSpareThreads=\u0026#34;100\u0026#34; maxIdleTime=\u0026#34;60000\u0026#34; maxQueueSize=\u0026#34;Integer.MAX_VALUE\u0026#34; prestartminSpareThreads=\u0026#34;false\u0026#34; threadPriority=\u0026#34;5\u0026#34; className=\u0026#34;org.apache.catalina.core.StandardThreadExecutor\u0026#34;/\u0026gt; 属性 含义 name 线程池名称，用于Connector中指定。 namePrefix 所创建的每个线程的名称前缀，一个单独的线程名称为 namePrefix+threadNumber。 daemon 是否作为守护线程（类似于守护进程），默认为true maxThreads 线程池中最大线程数。 minSpareThreads 活跃线程数，也就是核心池线程数，这些线程不会被销毁，会一直存在。 maxIdleTime 线程空闲时间，超过该时间后，空闲线程会被销毁，默 认值为6000（1分钟），单位毫秒。 maxQueueSize 在被执行前最大线程排队数目，默认为int的最大值，也就是广义的无限。除非特殊情况，这个值不需要更改， 否则会有请求不会被处理的情况发生。 prestartminSpareThreads 启动线程池时是否启动 minSpareThreads部分线程。 默认值为false，即不启动。 threadPriority 线程池中线程优先级，默认值为5，值从1到10。 className 线程池实现类，未指定情况下，默认实现类为 org.apache.catalina.core.StandardThreadExecutor。 如果想使用自定义线程池首先需要实现 org.apache.catalina.Executor接口。 Connector Connector 用于创建链接器实例。默认情况下，server.xml 配置了两个链接器，一个支 持HTTP协议，一个支持AJP协议。因此大多数情况下，我们并不需要新增链接器配置， 只是根据需要对已有链接器进行优化。\n\u0026lt;Connector port=\u0026#34;8080\u0026#34; protocol=\u0026#34;HTTP/1.1\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; redirectPort=\u0026#34;8443\u0026#34; /\u0026gt; \u0026lt;Connector port=\u0026#34;8009\u0026#34; protocol=\u0026#34;AJP/1.3\u0026#34; redirectPort=\u0026#34;8443\u0026#34; /\u0026gt; port为监听的端口，如果设置为0，Tomcat将会随机选择一个可用的端口号给当前Connector 使用 protocol为Connector的协议，这里默认的是HTTP和AJP两种协议，后面可以指定对应协议的不同版本，默认情况下会检测本机是否配置了APR库，如果有并且 useAprConnector设置为true则会默认使用APR模式的IO协议，如果无则会使用NIO模式 connectionTimeOut ：Connector 接收链接后的等待超时时间，单位为毫秒。 -1表示永不超时 redirectPort：当前Connector 不支持SSL请求， 接收到了一个请求， 并且也符合 security-constraint 约束， 需要SSL传输，Catalina自动将请求重定向到指定的端口 executor ： 指定前面提到的共享线程池的名称，也可以通过maxThreads、minSpareThreads 等属性对该connector进行单独配置对应的内部线程池 URIEncoding : 用于指定编码URI的字符编码， Tomcat8.x和Tomcat9.x版本默认的编码为 UTF-8 , Tomcat7.x版本默认为ISO-8859-1 engine Engine 作为Servlet 引擎的顶级元素，内部可以嵌入： Cluster、Listener、Realm、 Valve和Host。\n\u0026lt;Engine name=\u0026#34;Catalina\u0026#34; defaultHost=\u0026#34;localhost\u0026#34;\u0026gt; …… \u0026lt;/Engine\u0026gt; name：用于指定Engine 的名称， 默认为Catalina defaultHost：默认使用的虚拟主机名称，当客户端请求访问的host无效时，会跳转到默认的host来处理请求 Host Host 元素用于配置一个虚拟主机，它支持以下嵌入元素：Alias、Cluster、Listener、 Valve、Realm、Context\n如果在Engine下配置Realm，那么此配置将在当前Engine下的所有Host中共享。 同样，如果在Host中配置Realm ，则在当前Host下的所有Context 中共享\nContext中的Realm优先级 \u0026gt; Host的Realm优先级 \u0026gt; Engine中的Realm优先级\n\u0026lt;Host name=\u0026#34;localhost\u0026#34; appBase=\u0026#34;webapps\u0026#34; unpackWARs=\u0026#34;true\u0026#34; autoDeploy=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.AccessLogValve\u0026#34; directory=\u0026#34;logs\u0026#34; prefix=\u0026#34;localhost_access_log\u0026#34; suffix=\u0026#34;.txt\u0026#34; pattern=\u0026#34;%h %l %u %t \u0026amp;quot;%r\u0026amp;quot; %s %b\u0026#34; /\u0026gt; \u0026lt;Alias\u0026gt;www.example.com\u0026lt;/Alias\u0026gt; \u0026lt;Alias\u0026gt;www.example2.com\u0026lt;/Alias\u0026gt; \u0026lt;/Host\u0026gt; 上面这一段Host的配置文件中还额外添加了Valve配置来实现自定义的日志记录。其中一些参数的详细信息和配置方式可以查看官网的说明 。\nThe shorthand pattern pattern=\u0026quot;common\u0026quot; corresponds to the Common Log Format defined by \u0026rsquo;%h %l %u %t \u0026ldquo;%r\u0026rdquo; %s %b\u0026rsquo;.\nname: 当前Host通用的网络名称，也就是常用的域名，如果有多个域名对应同一个Host的应用，我们可以设置一个或多个Alias来实现访问 appBase：当前Host应用对应的目录，当前Host上部署的Web应用均在该目录下（相对路径和绝对路径均可），默认为webapps unpackWARs：设置为true，Host在启动时会将appBase目录下war包解压为目 录。设置为false，Host将直接从war文件启动 autoDeploy： 控制tomcat是否在运行时定期检测并自动部署新增或变更的web应用 Context Context的完整配置官网文档，Context 用于配置一个Web应用，默认的配置如下。它支持的内嵌元素为：CookieProcessor，Loader，Manager，Realm，Resources，WatchedResource，JarScanner，Valve。\n\u0026lt;Host name=\u0026#34;localhost\u0026#34; appBase=\u0026#34;webapps\u0026#34; unpackWARs=\u0026#34;true\u0026#34; autoDeploy=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;Context docBase=\u0026#34;myAppDeploy\u0026#34; path=\u0026#34;/myApp\u0026#34;\u0026gt; .... \u0026lt;/Context\u0026gt; \u0026lt;/Host\u0026gt; docBase：Web应用目录或者War包的部署路径。可以是绝对路径，也可以是相对于该Context所属的Host中的appBase的相对路径。 path：Web应用的Context的访问路径。 假设tomcat的安装目录为/home/tomcat9，Host为默认的localhost， 则该web应用访问的根路径为： http://localhost:8080/myApp，对应的部署文件所存放的路径为：/home/tomcat9/webapps/myAppDeploy。\n","permalink":"http://localhost:9011/20210303-tomcat-start-guide/","summary":"\u003cp\u003e本文主要是偏向运维角度来对tomcat进行入门介绍，重点讲解了tomcat的基本概念、基本配置和tomcat的I/O模型。是之前的\u003ca href=\"https://tinychen.com/tags/tomcat/\"\u003etomcat篇\u003c/a\u003e的汇总整理之作。\u003c/p\u003e","title":"tomcat入门指北"},{"content":"本文主要介绍PowerDNS的主要特性和初始化安装的配置方法，侧重点是对复杂程度相对较高PowerDNS Authoritative Server进行介绍，同时会夹杂部分PowerDNS-Recursor的初始化安装和配置。\nPowerDNS简介 PowerDNS（PDNS）成立于20世纪90年代末，是开源DNS软件、服务和支持的主要供应商，它们提供的权威认证DNS服务器和递归认证DNS服务器都是100%开源的软件，同时也和红帽等开源方案提供商一样提供了付费的技术支持版本。同时官方表示为了避免和软件使用者出现竞争，他们只提供服务支持而不提供DNS托管服务。\nOur Authoritative Server, Recursor and dnsdist products are 100% open source. For the service provider market, OX also sells the PowerDNS Platform which builds on our Open Source products to deliver an integrated DNS solution with 24/7 support and includes features as parental control, malware filtering, automated attack mitigation, and long-term query logging \u0026amp; searching.\n熟悉DNS工作原理的同学可以大致地将DNS记录的查询分为两种：查询本地缓存和向上递归查询。和其他的如BIND、dnsmasq等将这些功能集成到一起的DNS软件不同，PowerDNS将其一分为二，分为了PowerDNS Authoritative Server和PowerDNS Recursor，分别对应这两种主要的需求，而我们常说的pdns指的就是PowerDNS Authoritative Server (后面简称PDNS Auth)，主要用途就是作为权威域名服务器，当然也可以作为普通的DNS服务器提供DNS查询功能。\n对于PowerDNS-Recursor，PowerDNS官网介绍其是一个内置脚本能力的高性能的DNS递归查询服务器，并且已经为一亿五千万个互联网连接提供支持。\nThe PowerDNS Recursor is a high-performance DNS recursor with built-in scripting capabilities. It is known to power the resolving needs of over 150 million internet connections.\nPowerDNS-Recursor(以下简称pdns-rec)的官方文档可以点击这里查看。官方指的内置脚本能力是指在4.0.0版本之后的配置文件里面添加了对lua脚本的支持。\nPowerDNS安装 PowerDNS Authoritative Server安装 这里我们还是使用经典的CentOS7系统进行安装测试，系统的相关版本和内核信息如下：\n$ lsb_release -a LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch Distributor ID: CentOS Description: CentOS Linux release 7.9.2009 (Core) Release: 7.9.2009 Codename: Core $ uname -r 3.10.0-1160.11.1.el7.x86_64 pdns对主流的操作系统都有着较好的支持，在centos上面可以直接通过repo仓库来安装，红帽系的Linux可以通过epel源，monshouwer提供的第三方源和powerdns官方源三种源来进行安装。\nOn RedHat based systems there are 3 options to install PowerDNS, from EPEL, the repository from Kees Monshouwer or from the PowerDNS repositories:\n使用epel源来进行安装的话可能会导致无法安装最新版本\n如果网络条件允许的话，最好的办法是直接通过官方的repo源来进行安装，如果使用的是master源，则可以安装到最新的测试版本：\nyum install epel-release yum-plugin-priorities curl -o /etc/yum.repos.d/powerdns-auth-master.repo https://repo.powerdns.com/repo-files/centos-auth-master.repo yum install pdns yum install pdns-backend-$backend 这里我们使用最新的稳定版4.4版本进行安装，backend这里我们选择pdns-backend-mysql\nyum install epel-release yum-plugin-priorities curl -o /etc/yum.repos.d/powerdns-auth-44.repo https://repo.powerdns.com/repo-files/centos-auth-44.repo yum install pdns yum install pdns-backend-$backend 请注意对于某些软件包源，bind backend作为基本pdns软件包的一部分提供，并且没有单独的pdns-backend-bind软件包。\nPowerDNS-Recursor安装 这里我们还是使用经典的CentOS7系统进行安装测试，系统的相关版本和内核信息如下：\n[root@tiny-cloud /home]# lsb_release -a LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch Distributor ID: CentOS Description: CentOS Linux release 7.9.2009 (Core) Release: 7.9.2009 Codename: Core [root@tiny-cloud /home]# uname -r 3.10.0-1160.24.1.el7.x86_64 pdns对主流的操作系统都有着较好的支持，在红帽系和CentOS系相关的发行版本上面可以之间通过epel源来进行安装，不过使用epel源来进行安装的话可能会导致无法安装最新版本。\nOn Red Hat, CentOS and related distributions, ensure that EPEL is available. To install the PowerDNS Recursor, run yum install pdns-recursor as root.\n如果网络条件允许的话，最好的办法是直接通过官方的repo源来进行安装，如果使用的是master分支的repo源，则可以安装到最新的测试版本。官方表示master存储库对应的是他们在github上面正在开发的master分支。\nyum install epel-release yum-plugin-priorities \u0026amp;\u0026amp; curl -o /etc/yum.repos.d/powerdns-rec-master.repo https://repo.powerdns.com/repo-files/centos-rec-master.repo \u0026amp;\u0026amp; yum install pdns-recursor 当然我们也可以通过官方提供的不同版本yum源来安装对应版本的pdns，不同的版本分支对应的支持时间也是不一样的，官方表示在对应版本的生命周期结束之后，对应的仓库也不会再提供支持，pdns-rec的EOL信息可以点击这里查看。由于目前4.5.x版本还处于rc阶段，因此这里我们还是安装最新的稳定版本4.4.x版本。\nyum install epel-release yum-plugin-priorities \u0026amp;\u0026amp; curl -o /etc/yum.repos.d/powerdns-rec-44.repo https://repo.powerdns.com/repo-files/centos-rec-44.repo \u0026amp;\u0026amp; yum install pdns-recursor pdns-auth的mysql安装配置 安装mysql pdns对于mysql的版本和安装方式并没有什么特殊的要求，个人推荐版本在5.7+或者8.0+都可以，这里使用yum安装8.0版本的mysql。\n最新版的mysql的repo文件我们可以直接前往官网下载：https://dev.mysql.com/downloads/repo/yum/\n如果需要使用5.7的版本可以到这里下载\nwget http://repo.mysql.com/mysql57-community-release-el7-9.noarch.rpm $ rpm -ivh mysql80-community-release-el7-3.noarch.rpm $ yum update $ yum install mysql-server $ mysqladmin --version mysqladmin Ver 8.0.23 for Linux on x86_64 (MySQL Community Server - GPL) # MYSQL8的初始密码可以在log中查看 $ grep \u0026#39;temporary password\u0026#39; /var/log/mysqld.log $ mysql -u root -p 创建用户 接下来需要进行基本的数据库操作，给pdns创建对应的数据库和用户并简单设置相关权限：\n-- 修改密码 ALTER user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;你的新密码\u0026#39;; -- 注意这里的\u0026#39;localhost\u0026#39;也有可能是别的参数，具体可以通过下面这条命令来进行查询： select user, host, authentication_string, plugin from mysql.user; -- 创建一个mysql的用户名为powerdns，只能本机登录 -- 创建一个mysql的数据库名为powerdns，并允许powerdns用户访问 CREATE USER \u0026#39;powerdns\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;你的新密码\u0026#39;; CREATE DATABASE powerdns; GRANT ALL ON powerdns.* TO \u0026#39;powerdns\u0026#39;@\u0026#39;localhost\u0026#39;; FLUSH PRIVILEGES; -- 对于MYSQL8需要额外指定加密方式避免ERROR 2059 (HY000)的问题 ALTER USER \u0026#39;powerdns\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED WITH mysql_native_password BY \u0026#39;你的新密码\u0026#39;; 创建数据表 创建数据表的操作完全按照官方的文档进行，有特殊需要的也可以根据实际情况进行修改：\nCREATE TABLE domains ( id INT AUTO_INCREMENT, name VARCHAR(255) NOT NULL, master VARCHAR(128) DEFAULT NULL, last_check INT DEFAULT NULL, type VARCHAR(6) NOT NULL, notified_serial INT UNSIGNED DEFAULT NULL, account VARCHAR(40) CHARACTER SET \u0026#39;utf8\u0026#39; DEFAULT NULL, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE UNIQUE INDEX name_index ON domains(name); CREATE TABLE records ( id BIGINT AUTO_INCREMENT, domain_id INT DEFAULT NULL, name VARCHAR(255) DEFAULT NULL, type VARCHAR(10) DEFAULT NULL, content VARCHAR(64000) DEFAULT NULL, ttl INT DEFAULT NULL, prio INT DEFAULT NULL, disabled TINYINT(1) DEFAULT 0, ordername VARCHAR(255) BINARY DEFAULT NULL, auth TINYINT(1) DEFAULT 1, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE INDEX nametype_index ON records(name,type); CREATE INDEX domain_id ON records(domain_id); CREATE INDEX ordername ON records (ordername); CREATE TABLE supermasters ( ip VARCHAR(64) NOT NULL, nameserver VARCHAR(255) NOT NULL, account VARCHAR(40) CHARACTER SET \u0026#39;utf8\u0026#39; NOT NULL, PRIMARY KEY (ip, nameserver) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE TABLE comments ( id INT AUTO_INCREMENT, domain_id INT NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(10) NOT NULL, modified_at INT NOT NULL, account VARCHAR(40) CHARACTER SET \u0026#39;utf8\u0026#39; DEFAULT NULL, comment TEXT CHARACTER SET \u0026#39;utf8\u0026#39; NOT NULL, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE INDEX comments_name_type_idx ON comments (name, type); CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); CREATE TABLE domainmetadata ( id INT AUTO_INCREMENT, domain_id INT NOT NULL, kind VARCHAR(32), content TEXT, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind); CREATE TABLE cryptokeys ( id INT AUTO_INCREMENT, domain_id INT NOT NULL, flags INT NOT NULL, active BOOL, published BOOL DEFAULT 1, content TEXT, PRIMARY KEY(id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE INDEX domainidindex ON cryptokeys(domain_id); CREATE TABLE tsigkeys ( id INT AUTO_INCREMENT, name VARCHAR(255), algorithm VARCHAR(50), secret VARCHAR(255), PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); pdns配置mysql mysql 相关配置 # gmysql-host # 需要连接的mysql的IP地址，和gmysql-socket变量互斥 # gmysql-port # 需要连接的mysql的端口号，默认是3306 # gmysql-socket # 需要连接的mysql的UNIX socket地址，和gmysql-host互斥 # gmysql-dbname # 需要连接的数据库，默认：powerdns # gmysql-user # 连接数据库的用户名，默认：powerdns # gmysql-group # 连接数据库的组，默认：client # gmysql-password # 连接数据库的用户的密码 # gmysql-dnssec # 是否启用dnssec功能，默认：no # gmysql-innodb-read-committed # 使用InnoDB的READ-COMMITTED事务隔离，默认：yes # gmysql-ssl # 是否开启SSL支持，默认：no # gmysql-timeout # 尝试读取数据库的超时时间，0为禁用，默认：10 # gmysql-thread-cleanup # 对于一些老旧版本的MySQL/MariaDB（比如RHEL7内置的版本）会出现内存泄露的问题，除非应用程序明确向该库报告每个线程的结束。启用gmysql-thread-cleanup告诉PowerDNS每当线程结束时就调用mysql_thread_end（）。 # 只有当确定自己需要开启这个功能的时候再开启，详情可以查看https://github.com/PowerDNS/pdns/issues/6231. pdns.conf配置 $ cat /etc/pdns/pdns.conf api=yes api-key=你的API-KEY config-dir=/etc/pdns write-pid=yes daemon=no guardian=no launch=gmysql gmysql-host=localhost gmysql-port=3306 gmysql-dbname=你的数据库名 gmysql-user=你的用户名 gmysql-password=你的密码 log-dns-details=yes log-dns-queries=yes log-timestamp=yes loglevel=9 logging-facility=0 log-timestamp=yes setgid=root setuid=root webserver=yes webserver-address=192.168.100.100 webserver-loglevel=detailed webserver-port=8081 # webserver-allow-from指定允许访问webserver和API的IP白名单，多个IP可以使用英文逗号隔开 webserver-allow-from=192.168.100.0/24 # pdns服务监听的地址，多个IP可以使用英文逗号隔开 local-address=192.168.100.100 query-local-address=192.168.100.100 pdns-rec配置 pdns-rec的配置文件除了默认文件命名和少数特殊的配置项外，其他的绝大部分配置都和pdns-auth一致，这里不作赘述。\n[root@tiny-cloud /etc/pdns-recursor]# realpath recursor.conf /etc/pdns-recursor/recursor.conf pdns日志处理 官网的相关文档可以点击这里查看，debug阶段我们把日志级别调到了最高的9，为了避免错过重要信息，我们把日志按照不同的级别分别写入不同的文件中。\n修改centos对应的rsyslog配置文件并且重启服务\n# mkdir -p /etc/pdns/logs # cat /etc/rsyslog.conf | grep pdns local0.info /etc/pdns/logs/pdns.info.log local0.warn /etc/pdns/logs/pdns.warn.log local0.err /etc/pdns/logs/pdns.err.log systemctl restart rsyslog.service 修改pdns的systemd的unit文件，将里面的禁用syslog参数去掉，同时将其他多余的控制选项也一并去除，统一将各类参数设置集中到pdns.conf文件中，方便后期的管理和运维。\nvim /usr/lib/systemd/system/pdns.service # 将原来的启动参数全部替换掉 # ExecStart=/usr/sbin/pdns_server --socket-dir=%t/pdns --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no # 替换为 ExecStart=/usr/sbin/pdns_server --socket-dir=%t/pdns systemctl daemon-reload pdns-auth的API请求 PDNS提供了API功能，请求的时候需要注意正确携带配置中的api-key，否则会无法返回正确的结果，而是显示401 Unauthorized错误。\n$ curl -v http://192.168.100.100:8081/api/v1/servers * Trying 192.168.100.100... * TCP_NODELAY set * Connected to 192.168.100.100 (192.168.100.100) port 8081 (#0) \u0026gt; GET /api/v1/servers HTTP/1.1 \u0026gt; Host: 192.168.100.100:8081 \u0026gt; User-Agent: curl/7.61.1 \u0026gt; Accept: */* \u0026gt; \u0026lt; HTTP/1.1 401 Unauthorized \u0026lt; Connection: close \u0026lt; Content-Length: 12 \u0026lt; Content-Type: text/plain; charset=utf-8 \u0026lt; Server: PowerDNS/4.4.0 \u0026lt; Www-Authenticate: X-API-Key realm=\u0026#34;PowerDNS\u0026#34; \u0026lt; * Closing connection 0 Unauthorized 请求正确的情况下会返回json格式的信息。\n$ curl -v -H \u0026#39;X-API-Key: 配置中的api-key\u0026#39; http://192.168.100.100:8081/api/v1/servers * Trying 192.168.100.100... * TCP_NODELAY set * Connected to 192.168.100.100 (192.168.100.100) port 8081 (#0) \u0026gt; GET /api/v1/servers HTTP/1.1 \u0026gt; Host: 192.168.100.100:8081 \u0026gt; User-Agent: curl/7.61.1 \u0026gt; Accept: */* \u0026gt; X-API-Key: 配置中的api-key \u0026gt; \u0026lt; HTTP/1.1 200 OK \u0026lt; Access-Control-Allow-Origin: * \u0026lt; Connection: close \u0026lt; Content-Length: 249 \u0026lt; Content-Security-Policy: default-src \u0026#39;self\u0026#39;; style-src \u0026#39;self\u0026#39; \u0026#39;unsafe-inline\u0026#39; \u0026lt; Content-Type: application/json \u0026lt; Server: PowerDNS/4.4.0 \u0026lt; X-Content-Type-Options: nosniff \u0026lt; X-Frame-Options: deny \u0026lt; X-Permitted-Cross-Domain-Policies: none \u0026lt; X-Xss-Protection: 1; mode=block \u0026lt; * Closing connection 0 [{\u0026#34;config_url\u0026#34;: \u0026#34;/api/v1/servers/localhost/config{/config_setting}\u0026#34;, \u0026#34;daemon_type\u0026#34;: \u0026#34;authoritative\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;localhost\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;Server\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;/api/v1/servers/localhost\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;4.4.0\u0026#34;, \u0026#34;zones_url\u0026#34;: \u0026#34;/api/v1/servers/localhost/zones{/zone}\u0026#34;}] DNS解析 对于pdns-rec而言，是单纯的一个递归查询器（Recursor），会根据设置的缓存时间来缓存向上查询到的DNS记录。\n理论上PDNS Auth只会查询自己已有的DNS记录，如果不存在则会直接返回空，而不是继续向上递归查询。这里我们使用轻量型的DNS服务器dnsmasq作为对比，两者都没有手动添加任何的DNS解析记录。\n从上面的测试结果我们可以看出pdns auth只会返回自己的数据库中存在的记录。于是我们手动添加记录到pdns中再进行查询。\n这里我们使用pdnsutil工具来简单测试，首先我们简单的创建一个关于example.org的zone，然后我们再创建关于example.org的一条A记录和MX记录，接着使用dig命令来进行测试：\n$ pdnsutil create-zone example.org ns1.example.com Feb 24 16:54:48 gmysql Connection successful. Connected to database \u0026#39;powerdns\u0026#39; on \u0026#39;localhost\u0026#39;. Feb 24 16:54:48 gmysql Connection successful. Connected to database \u0026#39;powerdns\u0026#39; on \u0026#39;localhost\u0026#39;. Creating empty zone \u0026#39;example.org\u0026#39; Feb 24 16:54:48 No serial for \u0026#39;example.org\u0026#39; found - zone is missing? Also adding one NS record $ pdnsutil list-all-zones Feb 24 16:54:59 gmysql Connection successful. Connected to database \u0026#39;powerdns\u0026#39; on \u0026#39;localhost\u0026#39;. Feb 24 16:54:59 gmysql Connection successful. Connected to database \u0026#39;powerdns\u0026#39; on \u0026#39;localhost\u0026#39;. tinychen.com example.org $ pdnsutil add-record example.org \u0026#39;\u0026#39; MX \u0026#39;25 mail.example.org\u0026#39; Feb 24 16:55:36 gmysql Connection successful. Connected to database \u0026#39;powerdns\u0026#39; on \u0026#39;localhost\u0026#39;. Feb 24 16:55:36 gmysql Connection successful. Connected to database \u0026#39;powerdns\u0026#39; on \u0026#39;localhost\u0026#39;. New rrset: example.org. 3600 IN MX 25 mail.example.org $ pdnsutil add-record example.org. www A 192.168.100.100 Feb 24 16:56:09 gmysql Connection successful. Connected to database \u0026#39;powerdns\u0026#39; on \u0026#39;localhost\u0026#39;. Feb 24 16:56:09 gmysql Connection successful. Connected to database \u0026#39;powerdns\u0026#39; on \u0026#39;localhost\u0026#39;. New rrset: www.example.org. 3600 IN A 192.168.100.100 同样的我们对tinychen.com进行相同的操作，可以看到这时已经能够解析出对应的IP了。\n从上图中我们可以看到对应的tinychen.com域名解析出来的记录是我们手动设定的IP值。\n","permalink":"http://localhost:9011/20210224-dns-02-pdns01-pdns-auth-and-rec-introduction/","summary":"\u003cp\u003e本文主要介绍PowerDNS的主要特性和初始化安装的配置方法，侧重点是对复杂程度相对较高\u003ccode\u003ePowerDNS Authoritative Server\u003c/code\u003e进行介绍，同时会夹杂部分\u003ccode\u003ePowerDNS-Recursor\u003c/code\u003e的初始化安装和配置。\u003c/p\u003e","title":"PowerDNS篇1-简介和安装"},{"content":"作为 CNCF 中最成功的开源项目之一，Prometheus 已经成为了云原生监控的代名词，被广泛应用在 Kubernetes和OpenShift 等项目中，同时有很多第三方解决方案也会集成 Prometheus。随着 Kubernetes 在容器调度和管理上确定领头羊的地位，Prometheus 也成为Kubernetes容器监控的标配。\n考虑到k8s系统的复杂性和上手难度较高，本文将从最简单最基础的部分开始循序渐进，主要介绍如何在CentOS8系统上直接使用二进制文件和systemd进行Prometheus server的单机版本部署，本文只涉及到最基础的Prometheus server、node_exporter和Grafana三个模块，暂未涉及Pushgateway和Alertmanager两个模块。\n一般来说，Prometheus server是用于作为服务端来存储各类exporter在被监控节点上面采集的数据，而Grafana则负责将Prometheus server上的数据可视化，因此Prometheus server和Grafana不一定要部署在同一台机器上面，只需要部署两者的机器能够互相通信即可，同理，各类exporter应该部署在需要被监控的节点机器上。这里由于机器数量有限且只作为测试，会将三者都部署在同一台机器上。\nPrometheus最初起源于SoundCloud构建的监控系统和告警工具，是Google BorgMon监控系统的开源版本。它基于Go语言且自带时序型数据库，这也就意味着DevOps工程师在部署Prometheus监控系统的时候无需再额外搭建数据库，当然如果有更高的需求也可以自行搭建其他的时序性数据库（TSDB）。\nPrometheus的基本原理是通过 HTTP周期性抓取被监控组件的状态，任意组件只要提供对应的 HTTP 接口并且符合 Prometheus 定义的数据格式，就可以接入Prometheus监控。此外，使用Grafana 的Prometheus的数据可视化程度非常高，告警规则也十分多样，还支持传统服务器监控以及云原生监控，可以称为是监控系统中的“瑞士军刀”。\n部署prometheus 我们去官网选择合适的二进制版本进行下载并解压\nwget https://github.com/prometheus/prometheus/releases/download/v2.23.0/prometheus-2.23.0.linux-amd64.tar.gz tar -zxvf prometheus-2.23.0.linux-amd64.tar.gz 为了方便管理，我们需要创建一个单独的prometheus用户来运行prometheus，同时要保证安全，创建的用户只用来运行prometheus而不能使用shell登录。\ngroupadd prometheus useradd -g prometheus -s /sbin/nologin prometheus chown -R prometheus.prometheus /home/prometheus 为了方便管理，我们可以配置使用systemd对进程进行管理\nvim /usr/lib/systemd/system/prometheus.service [Unit] Description=Prometheus Documentation=https://prometheus.io/ After=network.target [Service] # Type设置为notify时，服务会不断重启 Type=simple User=prometheus # --storage.tsdb.path是可选项，默认数据目录在运行目录的./dada目录中 ExecStart=/home/prometheus/prometheus --config.file=/home/prometheus/prometheus.yml --storage.tsdb.path=/home/prometheus Restart=on-failure [Install] WantedBy=multi-user.target chown prometheus.prometheus /usr/lib/systemd/system/prometheus.service systemctl daemon-reload systemctl enable prometheus.service systemctl start prometheus.service systemctl status prometheus.service 如无意外应该已经可以正常运行了，同时访问服务器的9090端口可以查看到简单的UI界面\n部署node_exporter 我们去官网选择合适的二进制版本进行下载并解压\nwget https://github.com/prometheus/node_exporter/releases/download/v1.0.1/node_exporter-1.0.1.linux-amd64.tar.gz tar -zxvf node_exporter-1.0.1.linux-amd64.tar.gz mv node_exporter-1.0.1.linux-amd64 node_exporter chown -R prometheus.prometheus /home/node_exporter 同样的我们使用systemd进行管理\nvim /usr/lib/systemd/system/node_exporter.service [Unit] Description=Prometheus-node_exporter Documentation=https://prometheus.io/ After=network.target # 注意下面这两项参数是CentOS8的systemd才支持的 StartLimitBurst=1 StartLimitIntervalSec=15s [Service] Type=simple User=prometheus ExecStart=/home/node_exporter/node_exporter Restart=on-failure [Install] WantedBy=multi-user.target chown prometheus.prometheus /usr/lib/systemd/system/node_exporter.service systemctl daemon-reload systemctl enable node_exporter.service systemctl start node_exporter.service systemctl status node_exporter.service 如果顺利的话，node_exporter应该就正常运行在默认的9100端口了，再查看9090端口的prometheus的web页面种的Status里面的Targets应该就能够看到已经运行起来的监控了。\n接着我们需要修改Prometheus的配置文件/home/prometheus/prometheus.yml，添加对应的job，使其监听对应IP的9100端口获取信息并重启Prometheus。\nscrape_configs: - job_name: \u0026#39;prometheus\u0026#39; static_configs: - targets: [\u0026#39;192.168.100.100:9090\u0026#39;] - job_name: \u0026#39;tiny-server\u0026#39; static_configs: - targets: [\u0026#39;192.168.100.100:9100\u0026#39;] 部署grafana 官网给出了多种版本系统的安装教程，我们这里使用的是CentOS，可以直接下载对应的RPM包然后直接安装，对应的操作如下\nwget https://dl.grafana.com/oss/release/grafana-7.3.6-1.x86_64.rpm sudo yum install grafana-7.3.6-1.x86_64.rpm systemctl daemon-reload systemctl enable grafana-server.service systemctl start grafana-server.service systemctl status grafana-server.service 对应的配置文件位于/etc/grafana/grafana.ini，如果有需要可以进行更改，然后重启服务。\n默认情况下我们直接访问对应服务器的3000端口，然后使用初始的用户名和密码admin/admin进行登录并且修改初始密码。\n随后我们开始添加数据源\n接下来在配置中填入相应的网址和端口即可，一般如果没有进行特殊修改，使用默认的配置即可读取到相应的数据源\n前面我们已经部署了node_exporter，它会采集相应的数据到部署的机器的9100端口，我们这里再到Grafana中添加对应的dashboard就可以看到对应的图表数据了。Grafana官方提供的dashboard非常多，对应node_exporter的可以进行搜索下载，然后再上传到Grafana中，当然如果网络正常的话，我们也可以直接在导入面板中输入对应的ID编号就可以直接导入，由于我们的版本足够新，所以这里我们使用中文版本的node_exporter的dashboard，可以点击这里查看详情。\n部分效果展示如下：\n","permalink":"http://localhost:9011/20210105-prometheus-01-binary-install/","summary":"\u003cp\u003e作为 CNCF 中最成功的开源项目之一，\u003ccode\u003ePrometheus\u003c/code\u003e 已经成为了云原生监控的代名词，被广泛应用在 \u003ccode\u003eKubernetes\u003c/code\u003e和\u003ccode\u003eOpenShift\u003c/code\u003e 等项目中，同时有很多第三方解决方案也会集成 \u003ccode\u003ePrometheus\u003c/code\u003e。随着 \u003ccode\u003eKubernetes\u003c/code\u003e 在容器调度和管理上确定领头羊的地位，\u003ccode\u003ePrometheus\u003c/code\u003e 也成为\u003ccode\u003eKubernetes\u003c/code\u003e容器监控的标配。\u003c/p\u003e\n\u003cp\u003e考虑到k8s系统的复杂性和上手难度较高，本文将从最简单最基础的部分开始循序渐进，主要介绍如何在CentOS8系统上直接使用二进制文件和\u003ccode\u003esystemd\u003c/code\u003e进行\u003ccode\u003ePrometheus server\u003c/code\u003e的单机版本部署，本文只涉及到最基础的\u003ccode\u003ePrometheus server\u003c/code\u003e、\u003ccode\u003enode_exporter\u003c/code\u003e和\u003ccode\u003eGrafana\u003c/code\u003e三个模块，暂未涉及\u003ccode\u003ePushgateway\u003c/code\u003e和\u003ccode\u003eAlertmanager\u003c/code\u003e两个模块。\u003c/p\u003e\n\u003cp\u003e一般来说，\u003ccode\u003ePrometheus server\u003c/code\u003e是用于作为服务端来存储各类\u003ccode\u003eexporter\u003c/code\u003e在被监控节点上面采集的数据，而\u003ccode\u003eGrafana\u003c/code\u003e则负责将\u003ccode\u003ePrometheus server\u003c/code\u003e上的数据可视化，因此\u003ccode\u003ePrometheus server\u003c/code\u003e和\u003ccode\u003eGrafana\u003c/code\u003e不一定要部署在同一台机器上面，只需要部署两者的机器能够互相通信即可，同理，各类\u003ccode\u003eexporter\u003c/code\u003e应该部署在需要被监控的节点机器上。这里由于机器数量有限且只作为测试，会将三者都部署在同一台机器上。\u003c/p\u003e","title":"Prometheus系列01-单机版二进制部署"},{"content":"本文主要讲解在centos8系统中安装fail2ban服务来提供ssh服务的安全性。\n本文参考链接。\nfail2ban用来保护ssh的原理非常简单，主要就是通过检测ssh的日志，记录下频繁登录失败的IP，然后使用iptables来直接禁用掉这个IP对应的请求即可实现ssh的防暴力破解。\n安装fail2ban centos中可以直接启用epel源来直接进行安装\nyum install epel-release yum install fail2ban 配置fail2ban fail2ban的主要配置目录位于/etc/fail2ban\n$ cat jail.local [DEFAULT] # 以空格分隔的列表，可以是 IP 地址、CIDR 前缀或者 DNS 主机名 # 用于指定哪些地址可以忽略 fail2ban 防御 ignoreip = 192.168.0.0/24 # 客户端主机被禁止的时长（秒） bantime = 8640000 # 客户端主机被禁止前允许失败的次数 maxretry = 3 # 查找失败次数的时长（秒） findtime = 600 mta = sendmail [ssh-iptables] enabled = true filter = sshd action = iptables[name=SSH, port=ssh, protocol=tcp] sendmail-whois[name=SSH-Fail2ban, dest=example@mail.com, sender=fail2ban@email.com] # Red Hat 系的发行版 logpath = /var/log/secure # ssh 服务的最大尝试次数 maxretry = 3 重启服务 $ sudo systemctl restart fail2ban $ sudo systemctl status fail2ban ● fail2ban.service - Fail2Ban Service Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; disabled; vendor preset: disabled) Active: active (running) since Tue 2020-12-22 16:01:45 +08; 5s ago Docs: man:fail2ban(1) Process: 46536 ExecStartPre=/bin/mkdir -p /run/fail2ban (code=exited, status=0/SUCCESS) Main PID: 46539 (f2b/server) Tasks: 5 (limit: 408286) Memory: 13.4M CGroup: /system.slice/fail2ban.service └─46539 /usr/bin/python3.6 -s /usr/bin/fail2ban-server -xf start Dec 22 16:01:45 tiny-server systemd[1]: Starting Fail2Ban Service... Dec 22 16:01:45 tiny-server systemd[1]: Started Fail2Ban Service. Dec 22 16:01:45 tiny-server fail2ban-server[46539]: Server ready # 测试是否正常运行 $ sudo fail2ban-client ping Server replied: pong # 添加服务开机启动 $ sudo systemctl enable fail2ban Created symlink /etc/systemd/system/multi-user.target.wants/fail2ban.service → /usr/lib/systemd/system/fail2ban.service. # 查看fail2ban的日志 $ tail -f /var/log/fail2ban.log # 查看fail2ban状态 $ fail2ban-client status Status |- Number of jail: 1 `- Jail list: ssh-iptables $ fail2ban-client status ssh-iptables Status for the jail: ssh-iptables |- Filter | |- Currently failed: 0 | |- Total failed: 0 | `- File list: /var/log/secure `- Actions |- Currently banned: 0 |- Total banned: 0 `- Banned IP list: # 查看iptables的禁用情况 sudo iptables --list -n # 解禁一个特定IP fail2ban-client set ssh-iptables unbanip 192.168.1.8 ","permalink":"http://localhost:9011/20201222-centos8-install-fail2ban/","summary":"\u003cp\u003e本文主要讲解在\u003ccode\u003ecentos8\u003c/code\u003e系统中安装\u003ccode\u003efail2ban\u003c/code\u003e服务来提供ssh服务的安全性。\u003c/p\u003e\n\u003cp\u003e本文\u003ca href=\"https://www.xmodulo.com/how-to-protect-ssh-server-from-brute-force-attacks-using-fail2ban.html\"\u003e参考链接\u003c/a\u003e。\u003c/p\u003e","title":"给ssh服务添加fail2ban安全认证"},{"content":"本文主要讲解在ubuntu20.04系统安装后需要进行的一些初始化事项。\n安装ssh服务 # ubuntu默认情况下需要自己安装ssh服务 sudo apt install openssl-server 更换镜像源（清华） 参考的官方链接，Ubuntu 的软件源配置文件是 /etc/apt/sources.list。将系统自带的该文件做个备份，将该文件替换为下面内容，即可使用 TUNA 的软件源镜像。\n# 默认注释了源码镜像以提高 apt update 速度，如有需要可自行取消注释 deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse # 预发布软件源，不建议启用 # deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse 防火墙 # 禁用防火墙 tinychen@tiny-server:~$ sudo ufw disable Firewall stopped and disabled on system startup 禁用IPv6 echo \u0026#34;net.ipv6.conf.all.disable_ipv6=1\u0026#34; \u0026gt;\u0026gt; /etc/sysctl.conf echo \u0026#34;net.ipv6.conf.default.disable_ipv6=1\u0026#34; \u0026gt;\u0026gt; /etc/sysctl.conf echo \u0026#34;net.ipv6.conf.lo.disable_ipv6=1\u0026#34; \u0026gt;\u0026gt; /etc/sysctl.conf sudo sysctl -p 安装nvidia显卡驱动 # 查看显卡型号和推荐安装的驱动 tinychen@tiny-server:~$ ubuntu-drivers devices == /sys/devices/pci0000:00/0000:00:1c.7/0000:08:00.0 == modalias : pci:v000010DEd00000A66sv00001B0Asd00009060bc03sc00i00 vendor : NVIDIA Corporation model : GT218 [GeForce 310] driver : nvidia-340 - distro non-free recommended driver : xserver-xorg-video-nouveau - distro free builtin # 自动安装默认推荐的驱动 tinychen@tiny-server:~$ sudo ubuntu-drivers autoinstall [sudo] password for tinychen: ","permalink":"http://localhost:9011/20201220-ubuntu2004-init/","summary":"\u003cp\u003e本文主要讲解在\u003ccode\u003eubuntu20.04\u003c/code\u003e系统安装后需要进行的一些初始化事项。\u003c/p\u003e","title":"ubuntu20.04初始化的部分操作"},{"content":"本文主要讲解在centos7系统中添加谷歌验证器来开启ssh的双重认证从而提高ssh服务在使用密码登录的时候的安全性。\n本文参考链接。\nLinux上安装google验证器 首先我们需要启用epel仓库\nyum install epel-release 然后我们需要安装相关依赖库\nyum install pam-devel 接着直接安装google验证器\n# 查看版本信息 yum list google-authenticator # 直接安装 yum install google-authenticator 安装完成之后进行初始化配置，直接运行下面命令开始初始化配置\ngoogle-authenticator 初始化过程中生成二维码需要通过手机端的google身份验证器这个软件来扫描然后就可以添加到一个30s变化一次的动态密码；\n另外还有一个生成的emergency scratch codes记得自己小心保存起来，这个是当手机丢失的时候用来紧急登录的；\n其他的选项根据自己的需求进行选择，不知道的话就默认选择y即可。\n给ssh启用google验证 echo \u0026#34;auth required pam_google_authenticator.so\u0026#34; \u0026gt;\u0026gt; /etc/pam.d/sshd 修改ssh的配置文件/etc/ssh/sshd_config，将下列参数调整为yes\nChallengeResponseAuthentication yes 最后我们重启ssh服务即可\nsystemctl restart sshd 效果 重启之后需要再次使用密码进行ssh登录的时候就需要先输入前面的谷歌验证码才能再输入密码登录。\nVerification code: Password: ","permalink":"http://localhost:9011/20201203-ssh-add-google-authenticator/","summary":"\u003cp\u003e本文主要讲解在centos7系统中添加谷歌验证器来开启ssh的双重认证从而提高ssh服务在使用密码登录的时候的安全性。\u003c/p\u003e","title":"给ssh服务添加谷歌双重认证"},{"content":"本文主要介绍如何给KVM虚拟机添加ttyS0终端实现宿主机的console操作。\n一般来说直接创建的虚拟机是没办法在宿主机上直接通过console命令来操作，在使用console命令的时候会卡在下列界面\n# 添加到securetty中允许登录 grep ttyS0 /etc/securetty || echo \u0026#34;ttyS0\u0026#34; \u0026gt;\u0026gt; /etc/securetty # 使用grubby在内核参数中添加ttyS0 grubby --update-kernel=ALL --args=console=ttyS0 # 添加到inittab中实现开机初始化tty grep ttyS0 /etc/inittab || echo \u0026#34;S0:12345:respawn:/sbin/agetty ttyS0 115200\u0026#34; \u0026gt;\u0026gt; /etc/inittab # 重启生效 reboot /etc/inittab文件中的配置按照以下格式进行书写\nid：runlevels：action：process id：它是每个登记项的标识符，用于唯一标识每个登记项，不能重复 runlevels：系统的运行级别，表示process的action要在哪个级别下运行，该段中可以定义多个运行级别，各级别之间直接写不用分隔符；如果为空，表示在所有的运行级别运行 action：表示对应登记项的process在一定条件下所要执行的动作 process：表示启动哪个程序或脚本或执行哪个命令等 之后再次尝试就可以正常操作了\n","permalink":"http://localhost:9011/20201129-add-kvm-console-ttys0/","summary":"\u003cp\u003e本文主要介绍如何给KVM虚拟机添加ttyS0终端实现宿主机的console操作。\u003c/p\u003e","title":"对KVM虚拟机添加ttyS0实现console操作"},{"content":"本文主要介绍如何使用github上的开源项目advcpmv来实现Linux中的cp和mv命令的进度条。\n由于cp和mv命令都是属于coreutils工具包下的，因此我们的主要操作就是在编译coreutils的时候加入补丁从而实现进度条功能\n# 注意尽量不要使用root用户操作 $ pwd /home/tinychen # 下载coreutils $ wget http://ftp.gnu.org/gnu/coreutils/coreutils-8.32.tar.xz $ tar -xJf coreutils-8.32.tar.xz $ cd coreutils-8.32/ # 下载github上的补丁 $ wget https://raw.githubusercontent.com/jarun/advcpmv/master/advcpmv-0.8-8.32.patch # 打补丁，实现进度条显示 $ patch -p1 -i advcpmv-0.8-8.32.patch patching file src/copy.c patching file src/copy.h patching file src/cp.c patching file src/mv.c # 编译安装 $ ./configure $ make # 将打补丁生成的cp和mv命令的二进制文件复制到bin目录下 $ sudo cp src/cp /usr/local/bin/cp $ sudo cp src/mv /usr/local/bin/mv 接着我们只需要在使用cp和mv命令的时候加上-g参数就可以显示进度条了，为了方便起见我们可以在.bashrc文件中设置alias\nalias cp=\u0026#39;cp -ig\u0026#39; alias mv=\u0026#39;mv -ig\u0026#39; 试一下实际效果，复制一个大文件夹的时候可以显示总进度和当前文件复制进度，以及在复制完成之后还可以显示复制的过程中的平均速度。\n$ cp -r /samba/Elements02/kvm/* /kvm/ 0 files copied so far... 1.6 GiB / 67.2 GiB [===\u0026gt; ] 2.4 % Copying at 178.4 MiB/s (about 0h 6m 45s remaining) /samba/Elements02/kvm/iso/CentOS-6.10-x86_64-bin-DVD1.iso 1.6 GiB / 3.7 GiB [========================================================================\u0026gt; ] 43.4 % ","permalink":"http://localhost:9011/20201128-add-progess-bar-in-cp-mv/","summary":"\u003cp\u003e本文主要介绍如何使用github上的开源项目\u003ca href=\"https://github.com/jarun/advcpmv\"\u003eadvcpmv\u003c/a\u003e来实现Linux中的\u003cstrong\u003ecp\u003c/strong\u003e和\u003cstrong\u003emv\u003c/strong\u003e命令的进度条。\u003c/p\u003e","title":"给cp和mv命令添加进度条"},{"content":"本文主要介绍如何使用acme.sh工具来申请let\u0026rsquo;s encrypt的泛域名证书。\n安装acme.sh 安装acme.sh之前我们需要先安装必要的工具和依赖\nyum install socat curl -y 接着我们安装acme.sh，过程比较简单，只需要执行下列操作即可自动安装。对于安装的用户，官方声称可以使用root用户或者普通用户，这里我们使用root用户进行操作。\ncurl https://get.acme.sh | sh 安装的过程比较简单，会在目录下创建一个隐藏目录，所有的相关文件都会存放在这里，同时还会创建一个crontab来定时执行任务检测证书。\n$ pwd /root $ curl https://get.acme.sh | sh % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 775 0 775 0 0 963 0 --:--:-- --:--:-- --:--:-- 962 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 201k 100 201k 0 0 47150 0 0:00:04 0:00:04 --:--:-- 47156 [Fri Nov 27 09:50:57 CST 2020] Installing from online archive. [Fri Nov 27 09:50:57 CST 2020] Downloading https://github.com/acmesh-official/acme.sh/archive/master.tar.gz [Fri Nov 27 09:51:00 CST 2020] Extracting master.tar.gz [Fri Nov 27 09:51:00 CST 2020] Installing to /root/.acme.sh [Fri Nov 27 09:51:00 CST 2020] Installed to /root/.acme.sh/acme.sh [Fri Nov 27 09:51:00 CST 2020] Installing alias to \u0026#39;/root/.bashrc\u0026#39; [Fri Nov 27 09:51:00 CST 2020] OK, Close and reopen your terminal to start using acme.sh [Fri Nov 27 09:51:00 CST 2020] Installing alias to \u0026#39;/root/.cshrc\u0026#39; [Fri Nov 27 09:51:00 CST 2020] Installing alias to \u0026#39;/root/.tcshrc\u0026#39; [Fri Nov 27 09:51:00 CST 2020] Installing cron job 21 0 * * * \u0026#34;/root/.acme.sh\u0026#34;/acme.sh --cron --home \u0026#34;/root/.acme.sh\u0026#34; \u0026gt; /dev/null [Fri Nov 27 09:51:00 CST 2020] Good, bash is found, so change the shebang to use bash as preferred. [Fri Nov 27 09:51:01 CST 2020] OK [Fri Nov 27 09:51:01 CST 2020] Install success! $ crontab -l 21 0 * * * \u0026#34;/root/.acme.sh\u0026#34;/acme.sh --cron --home \u0026#34;/root/.acme.sh\u0026#34; \u0026gt; /dev/null $ ll -A | grep acme drwx------ 5 root root 4.0K Nov 27 09:51 .acme.sh 配置阿里云DNS解析 由于泛域名证书申请的时候需要使用DNS解析作为认证，为了保证自动申请证书成功，我们需要申请一个Access_Key来进行操作，这里以阿里云为例\n[root@aliyun /root/.acme.sh/dnsapi]# pwd dns_ali.sh /root/.acme.sh/dnsapi 在里面找到Ali_Key和Ali_Secret两个字段，将申请到的阿里云access_key填入里面，注意在阿里云中创建了子用户之后，需要授予对应的权限才能对DNS解析进行操作\n生成证书 我们以阿里云DNS申请泛域名证书为例，执行下列命令则可以生成对应的证书\n./acme.sh --issue --dns dns_ali -d tinychen.com -d *.tinychen.com 对于ecc证书，我们只需要在后面加上--keylength ec-256\n./acme.sh --issue --dns dns_ali -d tinychen.com -d *.tinychen.com --keylength ec-256 对于生成的证书，证书链和公钥是分开的，在nginx中，为了保证证书的完整性，我们一般使用带有证书链的公钥，也就是对应这里生成的fullchain.cer文件就包含了公钥和证书链，我们可以直接使用。\n","permalink":"http://localhost:9011/20201127-use-acme-letsencrypt-ecc-certs/","summary":"\u003cp\u003e本文主要介绍如何使用\u003ca href=\"https://github.com/acmesh-official/acme.sh\"\u003eacme.sh\u003c/a\u003e工具来申请let\u0026rsquo;s encrypt的泛域名证书。\u003c/p\u003e","title":"使用acme.sh工具申请let's encrypt的泛域名证书"},{"content":"本文主要介绍什么是k8s以及k8s的基本架构和相关基础概念。\nKubernetes 一词源于希腊语，意为“舵手”或“飞行员”，作为一个可移植、可扩展的开源平台，k8s可以使用声明式配置来管理编排容器服务并且提高自动化水平和效率。同时，得益于庞大且仍不断在增长的生态系统支撑，k8s拥有海量可用的周边服务、工具和生态支持。\nkubernetes官网上给出的定义如下：Kubernetes是用于自动部署，扩展和管理容器化应用程序的开源系统。（Kubernetes, also known as K8s, is an open-source system for automating deployment, scaling, and management of containerized applications.）它将组成应用程序的容器组合成逻辑单元（pod），以便于管理和服务发现。Kubernetes 源自Google 15 年生产环境的运维经验，同时凝聚了社区的最佳创意和实践。\n软件部署的迭代进程 一般来说，我们可以把软件部分分为三个阶段：传统部署、虚拟化部署、容器化部署。\n注意容器化虽然也属于虚拟化的一种，但是这里所指的虚拟化部署指的是通过KVM等虚拟化方式创建的虚拟机来部署应用。\n传统部署\n在传统部署时代，所有的应用都运行在同一台物理机上面。这给资源的隔离带来了很大的困扰，不同的应用会因为争夺系统资源而降低性能表现，也有可能会出现一个应用占用了大部分资源而另一个应用无资源可用的情况。同时，运维人员维护如此多的运行不同应用的物理机也是相当麻烦的。\n虚拟化部署\n在虚拟化部署时代，通过在操作系统中加入了Hypervisor，以及CPU等硬件的更新迭代支持，可以在一台物理机上面通过虚拟化的方式创建多台虚拟机Virtual Machines (VMs)。每台虚拟机都是一个独立的操作系统，拥有自己的文件系统和各种资源以及虚拟化的硬件。运维人员只需要维护各种虚拟机镜像镜像即可。\n容器化部署\n前面我们说过容器化严格来说也是虚拟化的一种，只不过容器化更进一步，通过容器运行时（Container Runtime），可以让不同的容器共享底层宿主机的操作系统，因为容器化被视为是一种轻量的虚拟化技术。同时，和虚拟机一样，容器也有自己的文件系统、CPU、内存、进程空间等资源。而且由于容器和底层的操作系统分离，一个容器可以运行在不同操作系统和云环境上。容器化技术的一些特点如下：\n解耦了应用的创建和部署过程：对比虚拟机，可以提供更高的易用性和效率\n持续开发、集成、部署（CI/CD）：得益于容器镜像的不变性，容器化技术可以高效快速地频繁创建高质量的容器镜像用于部署和回滚\n开发和运维分离：创建应用容器镜像和部署应用的时空连续性被打断，创建之后不需要立即部署，而是使用容器作为中介将其保存起来，从而使得应用和底层的基础架构解耦\n出色的可观察性：不仅仅是系统层面的指标信息，包括应用的健康状态和其他变量也能够展示出来\n环境一致性：开发、测试和生产环境只要使用同样的镜像，就可以保证环境的一致性\n跨云和操作系统：容器可以运行在各种云环境和不同的操作系统上\n以应用为中心进行管理：容器的抽象层级上升到了应用级别，因此可以从应用的逻辑资源层面进行调度分配和管理\n松散耦合、分布式、弹性、解放的微服务：应用程序被分解成较小的独立部分， 并且可以动态部署和管理，而不是在一台大型的物理机或虚拟机上整体运行\n资源隔离：可预测的应用程序性能\n资源利用：高效率和高密度\nk8s的优势 容器无疑是一种运行和管理应用的良方。在生产环境中，我们需要保证每一个容器服务都正常运行，如果某个容器宕掉了，需要重启一个新的容器来进行替补。如果上述的操作能够被某个系统自动实现呢？这时候k8s的优势就展现出来了。k8s提供了一个弹性的分布式系统框架，它照顾到了应用扩容、故障转移和部署模式等多个方面。k8s的一些主要特性有：\n服务发现和负载均衡：k8s可以通过DNS或者是IP地址来暴露容器中的服务。如果某个容器的流量/请求特别高，k8s能够主动实现负载均衡来降低该容器的流量/请求从而保证容器的稳定运行\n存储编排：k8s允许我们根据自己的实际需求和选择来挂载存储系统，支持但不限于本地存储、云存储等各种方式\n自动部署和回滚：我们可以使用k8s来声明已经部署的容器的期望状态，它可以以受控的速率将实际状态切换到期望状态。例如我们可以声明某个容器的运行数量的期望值为10，k8s会自动将容器的数量调整到该期望状态；我们还可以使用自动化 Kubernetes 来为部署创建新容器， 删除现有容器并将它们的所有资源用于新容器\n自动装箱计算：我们可以在k8s中指定每个容器所需 CPU 和内存（RAM）。 当容器指定了资源请求时，k8s会根据每个node节点的状态和每个容器所需要的资源来进行智能调度，确保资源的最大化利用\n自我修复\nk8s能够自动重启运行失败的容器、替换容器、杀死不响应 用户定义的运行状况检查的容器，并且在准备好服务之前不将其通告给客户端，从而保证客户端的请求不会被分发到有问题的容器中\n密钥与配置管理\nk8s允许我们存储和管理如密码、OAuth 令牌和 ssh 密钥等敏感信息，我们可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置，也不需要在配置中暴露密钥等敏感信息\nk8s不是什么 官方一直在强调这个观点：**k8s并不是一个传统的包罗万象的PaaS平台，**它的设计思想是提供最核心的基础框架和必要的核心功能，而在其他的选择上尽可能保证多样的兼容性和灵活性。尽管因为操作在容器级别层面而不是硬件层面，使得k8s提供了一些如部署、扩容、负载均衡等和PaaS平台类似的特性，但是对于日志、存储、报警、监控、CI/CD等其他诸多方面，k8s选择了将选择权交给使用者，这也是构建k8s丰富的生态中的重要一环。\nk8s不限制支持的应用类型：包括无状态（nginx等）、有状态（数据库等）和数据处理（AI、大数据、深度学习等）各种各样的应用类型，基本上能在容器中运行的应用都能在k8s中运行，而事实上绝大多数应用都能在容器中运行，因此绝大多数应用都能在k8s中运行\n不负责部署源代码、不构建程序、没有CI/CD：k8s并不涉及这些部分，使用者可以根据自己的偏好来选择合适的解决方案\n不提供应用级别的服务：不提供中间件、数据库、数据存储集群等作为内置服务，但是它们都能够很好地运行在k8s中，也能通过各种方式将服务暴露出去使用\n不提供日志、监控和报警等解决方案，但是提供了一些概念和指标数据等用于收集导出机制\n不提供或不要求配置语言/系统（例如 jsonnet），它提供了可以由任意形式的声明性规范所构成的声明性 API\n不提供也不采用任何全面的机器配置、维护、管理或自我修复系统\n此外，Kubernetes 不仅仅是一个编排系统，实际上它消除了编排的需要。 编排的技术定义是执行已定义的工作流程：首先执行 A，然后执行 B，再执行 C。 相比之下，Kubernetes 包含一组独立的、可组合的控制过程， 这些过程连续地将当前状态驱动到所提供的所需状态。 如何从 A 到 C 的方式无关紧要，也不需要集中控制，这使得系统更易于使用 且功能更强大、系统更健壮、更为弹性和可扩展。\nk8s基本架构 我们先来看看官方给出的基本架构图。下图只包含了非常基础的k8s架构组件。我们先从这里开始。\n首先上图灰色区域表示的是一整个k8s集群，整个k8s的架构其实和我们常见的master-worker模型非常相似，只不过由于这是分布式的集群，要更复杂一些。\nControl Plane 首先我们看到蓝色框内的Control Plane，这个是整个集群的控制平面，相当于是master进程的加强版。k8s中的Control Plane一般都会运行在Master节点上面。在默认情况下，Master节点并不会运行应用工作负载，所有的应用工作负载都交由Node节点负责。\n控制平面中的Master节点主要运行控制平面的各种组件，它们主要的作用就是维持整个k8s集群的正常工作、存储集群的相关信息，同时为集群提供故障转移、负载均衡、任务调度和高可用等功能。对于Master节点一般有多个用于保证高可用，而控制平面中的各个组件均以容器的Pod形式运行在Master节点中，大部分的组件需要在每个Master节点上都运行，少数如DNS服务等组件则只需要保证足够数量的高可用即可。\nkube-apiserver k8s集群的控制平面的核心是API服务器，而API服务器主要就是由kube-apiserver组件实现的，它被设计为可水平扩展，即通过部署不同数量的实例来进行缩放从而适应不同的流量。API服务器作为整个k8s控制平面的前端，负责提供 HTTP API，以供用户、集群中的不同部分组件和集群外部组件相互通信。\n并且kubernetes api在设计的时候是遵循REST思想的，我们可以通过kubeadm或者kubectl这类的CLI工具来操控API从而控制整个k8s集群，也可以通过其他的Web UI来进行操控。\nkube-scheduler 主节点上的组件，该组件监视那些新创建的未指定运行节点的 Pod，并选择节点让 Pod 在上面运行。\n调度决策考虑的因素包括单个 Pod 和 Pod 集合的资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰和最后时限。\nkube-controller-manager 在主节点上运行 控制器 的组件。\n从逻辑上讲，每个控制器都是一个单独的进程， 但是为了降低复杂性，它们都被编译到同一个可执行文件，并在一个进程中运行。\n这些控制器包括:\n节点控制器（Node Controller）: 负责在节点出现故障时进行通知和响应。 副本控制器（Replication Controller）: 负责为系统中的每个副本控制器对象维护正确数量的 Pod。 端点控制器（Endpoints Controller）: 填充端点(Endpoints)对象(即加入 Service 与 Pod)。 服务帐户和令牌控制器（Service Account \u0026amp; Token Controllers）: 为新的命名空间创建默认帐户和 API 访问令牌. etcd etcd 是兼具一致性和高可用性的键值key-value数据库，可以作为保存 Kubernetes 所有集群数据的后台数据库，负责保存Kubernetes Cluster的配置信息和各种资源的状态信息，当数据发生变化时，etcd 会快速地通知Kubernetes相关组件。\nKubernetes 集群的 etcd 数据库通常需要有个备份计划。此外还有一种k8s集群部署的高可用方案是将etcd数据库从容器中抽离出来，单独作为一个高可用数据库部署，从而为k8s提供稳定可靠的高可用数据库存储。\ncloud-controller-manager cloud-controller-manager 仅运行特定于云平台的控制回路。 如果你在自己的环境中运行 Kubernetes，或者在本地计算机中运行学习环境， 所部署的环境中不需要云控制器管理器。因此可以将其理解为云服务厂商专用的kube-controller-manager，这里不作赘述。\nDNS DNS虽然在k8s的官方文档被划分为插件部分，但是从k8s的原理中我们不难看出其重要性，因此几乎所有 Kubernetes 集群都应该有集群 DNS， 因为很多示例都需要 DNS 服务。\n集群 DNS 是一个 DNS 服务器，和环境中的其他 DNS 服务器一起工作，它为 Kubernetes 服务提供 DNS 记录。Kubernetes 启动的容器自动将此 DNS 服务器包含在其 DNS 搜索列表中。\nk8s默认使用的是coreDNS组件。\nWorker Node Worker Node的概念是和前面的Control Plane相对立的，集群中的节点基本都可以分为控制节点（Control Plane/master）和工作节点(Worker Node/worker)两大类。一般来说集群中的主要工作负载都是运行在Worker Node上面的，Master节点默认情况下不参与工作负载，但是可以手动设置为允许参与工作负载。\nPod Pod是k8s集群中最小的工作单元，和docker里面的单个运行的容器不同，Pod中可以包含多个容器，它们共享相同的计算、网络和存储等资源（相当于在一台机器上运行多个应用）\nkubelet kubelet是k8s集群中的每个节点上（包括master节点）都会运行的代理。 它能够保证容器都运行在 Pod 中。kubelet 只会管理由 Kubernetes 创建的容器。\nkubelet 接收一组通过各类机制提供给它的 PodSpecs，确保这些 PodSpecs 中描述的容器处于运行状态且健康。 当Scheduler确定在某个Node上运行Pod后，会将Pod的具体配置信息（image、volume等）发送给该节点的kubelet，kubelet根据这些信息创建和运行容器，并向Master报告运行状态。\nkube-proxy kube-proxy 是集群中每个节点上运行的网络代理， kube-proxy通过维护主机上的网络规则并执行连接转发，实现了Kubernetes服务抽象。\nservice在逻辑上代表了后端的多个Pod，外界通过service访问Pod。service接收到的请求就是通过kube-proxy转发到Pod上的，kube-proxy服务负责将访问service的TCP/UDP数据流转发到后端的容器。如果有多个副本，kube-proxy会实现负载均衡。\n","permalink":"http://localhost:9011/20201120-k8s-01-what-is-kubernetes/","summary":"\u003cp\u003e本文主要介绍什么是k8s以及k8s的基本架构和相关基础概念。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eKubernetes\u003c/strong\u003e 一词源于希腊语，意为“舵手”或“飞行员”，作为一个可移植、可扩展的开源平台，k8s可以使用声明式配置来管理编排容器服务并且提高自动化水平和效率。同时，得益于庞大且仍不断在增长的生态系统支撑，k8s拥有海量可用的周边服务、工具和生态支持。\u003c/p\u003e","title":"k8s系列01-什么是kubernetes？"},{"content":"grubby是一个用于更新和显示有关各种体系结构特定的引导程序的配置文件信息的命令行工具。 它主要设计用于安装新内核并需要查找有关当前引导环境的信息的脚本，同时也可以对启动内核的各项信息参数进行修改。\n本文主要介绍如何在centos8中使用grubby工具来对系统的内核启动参数和启动顺序进行调整。\n使用yum或者dnf可以直接安装grubby工具。\n$ yum install grubby Last metadata expiration check: 1:29:38 ago on Wed 18 Nov 2020 09:44:26 AM +08. Package grubby-8.40-38.el8.x86_64 is already installed. Dependencies resolved. Nothing to do. Complete! 查看当前的默认启动内核：\n$ grubby --default-kernel /boot/vmlinuz-4.18.0-193.28.1.el8_2.x86_64 查看系统安装的全部内核：\n$ grubby --info=ALL index=0 kernel=\u0026#34;/boot/vmlinuz-5.9.1-1.el8.elrepo.x86_64\u0026#34; args=\u0026#34;ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap net.ifnames=0 rhgb quiet intel_iommu=on $tuned_params\u0026#34; root=\u0026#34;/dev/mapper/cl-root\u0026#34; initrd=\u0026#34;/boot/initramfs-5.9.1-1.el8.elrepo.x86_64.img $tuned_initrd\u0026#34; title=\u0026#34;Red Hat Enterprise Linux (5.9.1-1.el8.elrepo.x86_64) 8.2 (Ootpa)\u0026#34; id=\u0026#34;12ab47b22fef4c02bcdc88b340d5f706-5.9.1-1.el8.elrepo.x86_64\u0026#34; index=1 kernel=\u0026#34;/boot/vmlinuz-4.18.0-193.28.1.el8_2.x86_64\u0026#34; args=\u0026#34;ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap net.ifnames=0 rhgb quiet intel_iommu=on $tuned_params\u0026#34; root=\u0026#34;/dev/mapper/cl-root\u0026#34; initrd=\u0026#34;/boot/initramfs-4.18.0-193.28.1.el8_2.x86_64.img $tuned_initrd\u0026#34; title=\u0026#34;CentOS Linux (4.18.0-193.28.1.el8_2.x86_64) 8 (Core)\u0026#34; id=\u0026#34;12ab47b22fef4c02bcdc88b340d5f706-4.18.0-193.28.1.el8_2.x86_64\u0026#34; index=2 kernel=\u0026#34;/boot/vmlinuz-4.18.0-193.19.1.el8_2.x86_64\u0026#34; args=\u0026#34;ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap net.ifnames=0 rhgb quiet intel_iommu=on $tuned_params\u0026#34; root=\u0026#34;/dev/mapper/cl-root\u0026#34; initrd=\u0026#34;/boot/initramfs-4.18.0-193.19.1.el8_2.x86_64.img $tuned_initrd\u0026#34; title=\u0026#34;CentOS Linux (4.18.0-193.19.1.el8_2.x86_64) 8 (Core)\u0026#34; id=\u0026#34;12ab47b22fef4c02bcdc88b340d5f706-4.18.0-193.19.1.el8_2.x86_64\u0026#34; index=3 kernel=\u0026#34;/boot/vmlinuz-4.18.0-193.el8.x86_64\u0026#34; args=\u0026#34;ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap net.ifnames=0 rhgb quiet intel_iommu=on $tuned_params\u0026#34; root=\u0026#34;/dev/mapper/cl-root\u0026#34; initrd=\u0026#34;/boot/initramfs-4.18.0-193.el8.x86_64.img $tuned_initrd\u0026#34; title=\u0026#34;CentOS Linux (4.18.0-193.el8.x86_64) 8 (Core)\u0026#34; id=\u0026#34;12ab47b22fef4c02bcdc88b340d5f706-4.18.0-193.el8.x86_64\u0026#34; index=4 kernel=\u0026#34;/boot/vmlinuz-0-rescue-12ab47b22fef4c02bcdc88b340d5f706\u0026#34; args=\u0026#34;ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap net.ifnames=0 rhgb quiet intel_iommu=on\u0026#34; root=\u0026#34;/dev/mapper/cl-root\u0026#34; initrd=\u0026#34;/boot/initramfs-0-rescue-12ab47b22fef4c02bcdc88b340d5f706.img\u0026#34; title=\u0026#34;CentOS Linux (0-rescue-12ab47b22fef4c02bcdc88b340d5f706) 8 (Core)\u0026#34; id=\u0026#34;12ab47b22fef4c02bcdc88b340d5f706-0-rescue\u0026#34; 设置新的默认启动内核：\n# 使用路径来指定内核，可以使用--set-default=kernel-path $ grubby --set-default=/boot/vmlinuz-5.9.1-1.el8.elrepo.x86_64 The default is /boot/loader/entries/12ab47b22fef4c02bcdc88b340d5f706-5.9.1-1.el8.elrepo.x86_64.conf with index 0 and kernel /boot/vmlinuz-5.9.1-1.el8.elrepo.x86_64 $ grubby --default-kernel /boot/vmlinuz-5.9.1-1.el8.elrepo.x86_64 # 使用index来指定内核，则使用--set-default-index=entry-index $ grubby --set-default-index=1 The default is /boot/loader/entries/12ab47b22fef4c02bcdc88b340d5f706-4.18.0-193.28.1.el8_2.x86_64.conf with index 1 and kernel /boot/vmlinuz-4.18.0-193.28.1.el8_2.x86_64 $ grubby --default-kernel /boot/vmlinuz-4.18.0-193.28.1.el8_2.x86_64 添加/删除内核启动参数：\n# 对所有的内核都删除某个参数 $ grubby --update-kernel=ALL --remove-args=intel_iommu=on # 对所有的内核都添加某个参数 $ grubby --update-kernel=ALL --args=intel_iommu=on # 对某个的内核添加启动参数 $ grubby --update-kernel=/boot/vmlinuz-5.9.1-1.el8.elrepo.x86_64 --args=intel_iommu=on 查看特定内核的具体信息：\n$ grubby --info=/boot/vmlinuz-5.9.1-1.el8.elrepo.x86_64 index=0 kernel=\u0026#34;/boot/vmlinuz-5.9.1-1.el8.elrepo.x86_64\u0026#34; args=\u0026#34;ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap net.ifnames=0 rhgb quiet intel_iommu=on $tuned_params\u0026#34; root=\u0026#34;/dev/mapper/cl-root\u0026#34; initrd=\u0026#34;/boot/initramfs-5.9.1-1.el8.elrepo.x86_64.img $tuned_initrd\u0026#34; title=\u0026#34;Red Hat Enterprise Linux (5.9.1-1.el8.elrepo.x86_64) 8.2 (Ootpa)\u0026#34; id=\u0026#34;12ab47b22fef4c02bcdc88b340d5f706-5.9.1-1.el8.elrepo.x86_64\u0026#34; ","permalink":"http://localhost:9011/20201118-centos8-use-grubby-modify-kernel/","summary":"\u003cp\u003egrubby是一个用于更新和显示有关各种体系结构特定的引导程序的配置文件信息的命令行工具。 它主要设计用于安装新内核并需要查找有关当前引导环境的信息的脚本，同时也可以对启动内核的各项信息参数进行修改。\u003c/p\u003e\n\u003cp\u003e本文主要介绍如何在centos8中使用grubby工具来对系统的内核启动参数和启动顺序进行调整。\u003c/p\u003e","title":"centos8使用grubby修改内核启动参数"},{"content":"本文主要介绍在linux系统下对ssh的配置文件进行修改从而自定义ssh登录显示的banner横幅提示语。\n在ssh的配置文件中找到banner字段，如没有则手动添加，然后指定一个文件，文件的内容就是到时候登录显示的内容\n$ grep Banner /etc/ssh/sshd_config #Banner none Banner /etc/ssh/my_banner 对于centos8而言，除了自定义的banner，/etc/motd和/etc/motd.d/*也会在ssh登录的时候显示。\n对于banner的自定义，可以使用各种字符工具，例如figlet、cowsay等等，都可以使用yum直接安装\n$ figlet tiny-server \u0026gt;\u0026gt; /etc/ssh/my_banner $ cat /etc/redhat-release | cowsay -f turtle \u0026gt;\u0026gt; /etc/ssh/my_banner 最后效果\n[/root]# ssh tiny-server _ _ | |_(_)_ __ _ _ ___ ___ _ ____ _____ _ __ | __| | \u0026#39;_ \\| | | |_____/ __|/ _ \\ \u0026#39;__\\ \\ / / _ \\ \u0026#39;__| | |_| | | | | |_| |_____\\__ \\ __/ | \\ V / __/ | \\__|_|_| |_|\\__, | |___/\\___|_| \\_/ \\___|_| |___/ --------------------------------------- \u0026lt; CentOS Linux release 8.2.2004 (Core) \u0026gt; --------------------------------------- \\ ___-------___ \\ _-~~ ~~-_ \\ _-~ /~-_ /^\\__/^\\ /~ \\ / \\ /| O|| O| / \\_______________/ \\ | |___||__| / / \\ \\ | \\ / / \\ \\ | (_______) /______/ \\_________ \\ | / / \\ / \\ \\ \\^\\\\ \\ / \\ / \\ || \\______________/ _-_ //\\__// \\ ||------_-~~-_ ------------- \\ --/~ ~\\ || __/ ~-----||====/~ |==================| |/~~~~~ (_(__/ ./ / \\_\\ \\. (_(___/ \\_____)_) Last login: Wed Nov 18 14:50:10 2020 from 0.0.0.0 [root@tiny-server ~]# ","permalink":"http://localhost:9011/20201116-linux-modify-ssh-login-banner/","summary":"\u003cp\u003e本文主要介绍在linux系统下对ssh的配置文件进行修改从而自定义ssh登录显示的banner横幅提示语。\u003c/p\u003e","title":"linux中设置ssh登录时显示的banner"},{"content":"上一篇文章中我们已经介绍了DPVS的特点和部署方式，本文主要是用于介绍DPVS是如何实现前面所说的特点，或者说是如何提高性能的。\n下图是爱奇艺的DPVS开发团队给出的DPVS在提高性能方面的操作，我们这里换一个角度，自底向上，从底层的CPU、内存、网卡来看这些操作是如何实现的。\n（本文涉及到较多的计算机组织架构和操作系统原理的知识点，由于篇幅原因没办法一一详解，因此有一定的理解门槛，如果有看不懂的知识点可以在文章下面留言，有机会我会写一些文章详细介绍一下相关内容）\n这里需要额外解释一下Share Nothing和Batching。\n**Shared nothing架构（Shared Nothing Architecture，SNA）这里的Share Nothing指的是一种设计模式而不是某种具体的技术，这种架构设计的思想是通过牺牲整体的横向扩展能力来提升纵向性能。**作为一种分布式计算架构，它的每一个节点（ node）都是独立、自给的，而且整个系统中没有单点竞争，没有资源的竞争就不需要加锁，也不需要上下文切换。\nBatching在这里指的批处理，主要还是依靠DPDK的SIMD（Single Instruction Multiple Data，单指令流多数据流）编码思想以及处理器本身的SIMD指令集来实现。SIMD是一种采用一个控制器来控制多个处理器，同时对一组数据（又称“数据向量”）中的每一个分别执行相同的操作从而实现空间上的并行性的技术。在微处理器中，单指令流多数据流技术则是一个控制器控制多个平行的处理微元，例如Intel的MMX或SSE，以及AMD的3D Now!指令集。\nFrom NIC drivers PMD （Poll Mode Driver） 使用top命令查看系统资源占用，我们会发现在dpvs中对网卡收发队列进行了一一绑定的CPU核心占用率会一直维持在100%，并且是用户态的100%占用，并且无论是否有流量在所属的dpdk网卡上经过，都会一直维持在100%的状态。这是dpdk的一个特性，称之为**PMD（Poll Mode Driver）**。前面在安装dpdk的时候，我们需要使用dpdk给对应的网卡来安装特定的网卡驱动，比如我们这里用于测试使用的82599网卡使用的是UIO驱动，这些都是属于PMD驱动。目前DPDK支持1G、10G、40G以及半虚拟化的virtio网卡的PMD驱动。\n对于运行着dpdk的机器，我们查看内核模块和对应的dpdk网卡驱动，可以发现uio模块和PMD驱动的踪迹，其中04:00.1是dpdk的网卡，而04:00.0则是普通状态下的网卡\n# lsmod | grep uio igb_uio 13414 1 uio 19338 3 igb_uio 04:00.0 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01) Subsystem: Intel Corporation Ethernet Server Adapter X520-2 [8086:000c] Kernel driver in use: ixgbe Kernel modules: ixgbe 04:00.1 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01) Subsystem: Intel Corporation Ethernet Server Adapter X520-2 [8086:000c] Kernel driver in use: igb_uio Kernel modules: ixgbe PMD驱动包含了各种API，并且提供了在用户态运行的BSD驱动，用于配置网卡和网卡的收发队列。此外，PWD驱动最大的优势就是无需任何中断操作就可以直接访问网卡队列中的RX/TX描述符（除了网卡的链接状态变化），借助这个优势，就可以绕过内核和同样运行在用户态的应用程序快速地进行数据传输。我们可以通过下图来对比传统的应用程序和网卡通信的方式以及使用PMD驱动后和网卡通信的方式。\n传统模式下，应用程序如nginx等运行在用户态（User Space），网卡（NIC）属于硬件设备，是归属于内核管理，网卡驱动运行在内核态中（Kernel Space）。那么当应用程序需要和网卡交换数据的时候，需要进行一轮用户态到内核态之间的切换，再经过内核中的TCP/IP协议栈，才能和网卡驱动通信，而网卡驱动和网卡之间的通信是通过硬件中断的方式来实现的，也就是传统模式下存在着用户态/内核态切换和两个非常耗时耗资源的操作。\nDPDK的处理方式非常简单粗暴，首先将网卡驱动从内核态移动到用户态运行，这样应用程序和PMD网卡驱动之间进行数据交换就不需要进行内核态/用户态的切换，避免了上下文切换，且由于都是在用户态，不需要将数据拷贝到内核态，也实现了零拷贝zero copy，同时还绕过了内核中的TCP/IP网络栈，极大地缩短APP到Driver之间的传输时间。\n当然这种绕过系统内核的做法最直接的代价就是驱动程序必须一直通过轮询poll的操作来保证能够及时接收到网卡的信息和数据，导致对应的cpu核心会一直处于100%的占用状态。同时这个时候网卡已经不再归于系统内核管理，常规的ip和ifconfig等命令已经没办法查看网卡的详细信息，需要使用dpip工具来对网卡进行管理。\n这里就是dpvs宣称的内核旁路（kernel bypass）、轮询（polling）、零拷贝（zero copy）等特性的实现原理。\n注意这种PMD驱动并不适用于一般的应用场景，因为需要有专门的CPU给PMD一直做轮询操作，对应的CPU就会一直占用100%，可能严重影响了其他任务的运行。\n当网络处于空闲状态的时候，CPU占用100%的问题会带来额外的不必要的功耗，因此dpdk还推出了一个interrupt dpdk模式，即当网卡中没有数据包处理的时候进入类似睡眠模式的状态，然后改为传统的中断方式通知，这个时候被100%占用的核心利用率就会降低，可以和其他的进程共享，但是DPDK仍然拥有高优先级，而当有数据包进来的时候还是可以优先处理。\nFrom CPU NUMA Awareness 1.从系统架构来看，目前的商用服务器大体可以分为三类\n对称多处理器结构(SMP：Symmetric Multi-Processor) 非一致存储访问结构(NUMA：Non-Uniform Memory Access) 海量并行处理结构(MPP：Massive Parallel Processing) 2.共享存储型多处理机有两种模型\n均匀存储器存取（Uniform Memory Access，简称UMA）模型 非均匀存储器存取（Nonuniform Memory Access，简称NUMA）模型 前面我们在安装dpvs的时候，特别需要注意的就是需要在BIOS中开启NUMA，这里主要涉及到的就是对于多路服务器（往往多颗CPU在一块主板上）的内存调度优化问题。以我们的测试服务器为例来进行举例说明：这台普通的R630服务器一共有两颗CPU，128G内存，其中内存是均匀分布在两颗CPU上的，即每颗CPU的内存总线都对应连接着64G的内存。那么NUMA打开和关闭的区别在哪里呢？\n对于关闭NUMA的机器，在Linux系统中查看的时候只有一个NUMA节点，系统会以为只有一颗CPU，那么这128G内存就都是这一颗CPU的，这样的好处是在应用程序可以通过操作系统跨NUMA节点调度另外一颗CPU的内存，虽然一颗CPU只有64G内存的，但是在这个CPU上面运行的程序可以调用128G的内存，实在不够了再调用SWAP内存。对于一般的应用程序来说，即使是使用了另外一颗CPU的内存会带来较高的延时，但是性能也远比swap内存强多了。\n关于是否开启处理器的超线程技术，主要分析如下：\n超线程（Hyper-Threading）在一个处理器中提供两个逻辑执行线程，逻辑线程共享流水线、执行单元和缓存。**该技术的本质是复用单处理器中的超标量流水线的多路执行单元，降低多路执行单元中因指令依赖造成的执行单元闲置。**对于每个逻辑线程，拥有完整独立的寄存器集合和本地中断逻辑，从软件的角度，与单线程物理核并没有差异。例如，8核心的处理器使用超线程技术之后，可以得到16个逻辑线程。采用超线程，在单核上可以同时进行多线程处理，使整体性能得到一定程度提升。但由于其毕竟是共享执行单元的，对IPC（每周期执行指令数）越高的应用，带来的帮助越有限。DPDK是一种I/O集中的负载，对于这类负载，IPC相对不是特别高，所以超线程技术会有一定程度的帮助。\n对于打开NUMA的机器，在本身的CPU内存用尽的情况下，不会去跨NUMA节点调度另外一颗CPU的内存，而是直接调用swap内存。对于DPDK程序来说，这样的好处就是尽可能地降低内存的延迟，提高性能表现。由于DPDK广泛的使用了大页内存（HugePage），可以有效地控制内存的超用问题，不会涉及到swap内存的调用。\nRSS（Receive Side Scaling） 同样的除了内存之外，问题还存在于网卡设备上。一般来说网卡设备是通过PCIe总线和CPU连接，不同CPU之间跨NUMA节点调用PCIe设备带来的性能损耗实际上并没有内存那么大，因为PCIe总线并没有内存总线距离CPU那么“近”，而且两者的带宽也不是在一个数量级上。\n还是前面用到的那张网卡，我们使用lspci命令查看pci设备信息的时候我们可以看到对应的NIC设备的信息（截取部分）：\n这里可以看到5GT/s对应的是PCIe2.0，x8则表示带宽，这里使用的是一个PCIe2.0x8的双口万兆网卡82599ES\nGT/s是PCIe设备用来表示带宽的单位，具体对应速度转换可以查看下面的这个表格\nNUMA node表示这块PCIe网卡插在的插槽是哪一颗CPU对应的PCIe总线\n04:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01) Subsystem: Intel Corporation Ethernet Server Adapter X520-2 NUMA node: 0 LnkCap: Port #0, Speed 5GT/s, Width x8, ASPM L0s, Exit Latency L0s \u0026lt;1us, L1 \u0026lt;8us ClockPM- Surprise- LLActRep- BwNot- ASPMOptComp- Kernel driver in use: igb_uio Kernel modules: ixgbe RSS（Receive Side Scaling）功能的实现其实和网卡以及CPU都有关系，但是这里放到CPU的部分来分析，主要是需要前面提及的技术作为支撑。关于RSS的一些说明我们可以参考Linux内核的官网文档，此外微软的官方文档也有不错的图文解释。\nRSS，接收方缩放（直译起来略显晦涩生硬），使用搜索引擎的时候建议搜全名Receive Side Scaling，搜缩写RSS容易搜到Really Simple Syndication，中文译作简易信息聚合，也称聚合内容，是一种消息来源格式规范，用以聚合经常发布更新资料的网站，例如博客文章、新闻、音频或视频的网摘。\n现在的网卡基本都支持多队列(multi-queue)技术，而队列是分为收和发两种的。在接收数据包的时候，网卡可以把数据包发往不同的消息队列（一般通过哈希算法），不同的消息队列对应不同的CPU，这样就可以做到并行处理数据。典型的RSS配置是：如果设备支持足够的队列，则每个CPU有一个接收队列（一般多网卡机器上网卡的总队列数会超过CPU核心数，因此CPU核心数和网卡队列数一一对应即可），否则，每个内存域（如一个NUMA节点）至少有一个接收队列。\n基于这些特性，DPVS可以在配置中设定worker进程、CPU核心和网卡收发队列的一一绑定，最好的情况下就是全部都是在一颗物理CPU上，这样带来的性能提升最明显。\n这是DPVS宣传的NUMA Awareness和RX steering\u0026amp;CPU的技术原理。\nFrom memory Huge page大页内存 DPDK广泛使用了大页内存（2M或者1G）机制（对于DPVS来说，主要使用的是2MB的大页内存），以Linux系统为例，1G的大页一般不能在系统加载后动态分配，所以一般会在内核加载的时候设置好需要用到的大页。例如，增加内核启动参数default_hugepagesz=1G hugepagesz=1G hugepages=8来配置好8个1G的大页。在Linux系统上，可以通过命令cat /proc/meminfo来查看系统加载后的内存状况和大页内存的分配状况。\n# cat /proc/meminfo | grep Huge AnonHugePages: 243712 kB HugePages_Total: 16384 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB 注意上面的AnonHugePages和我们这里要讲的Hugepages不是一类东西，建议忽略。同时Hugepages不会备注大小，上面的Hugepagesize表明使用的是2MB的大页内存，而HugePages_Total表明有16384个这么大的大页内存，所以这里一共是32GB的大页内存。\n在继续后面的内容之前我们需要了解一下TLB是什么。\n首先，我们知道MMU(Memory Management Unit)的作用是把虚拟内存地址转换成物理内存地址。虚拟地址和物理地址的映射关系存储在页表中，而现在页表又是分级的。64位系统一般都是3~5级。常见的配置是4级页表，就以4级页表为例说明。分别是PGD、PUD、PMD、PTE四级页表。\n在硬件上会有一个叫做页表基地址寄存器，它存储PGD页表的首地址。MMU就是根据页表基地址寄存器从PGD页表一路查到PTE，最终找到物理地址(PTE页表中存储物理地址)。四级页表查找过程需要四次内存访问。延时可想而知，非常影响性能。（下图来自知乎）\nTLB的本质其实就是一块高速缓存。数据cache缓存地址(虚拟地址或者物理地址)和数据。**TLB缓存虚拟地址和其映射的物理地址。**TLB根据虚拟地址查找cache，它没得选，只能根据虚拟地址查找。所以TLB是一个虚拟高速缓存。硬件存在TLB后，虚拟地址到物理地址的转换过程发生了变化。虚拟地址首先发往TLB确认是否命中cache，如果cache hit直接可以得到物理地址。否则，一级一级查找页表获取物理地址。并将虚拟地址和物理地址的映射关系缓存到TLB中。\n默认下Linux系统的内存采用4KB为一页，页越小且内存越大则页表的开销越大，页表的内存占用也越大。CPU使用的TLB（Translation Lookaside Buffer）制作成本很高，所以一般大小并不大，因此一般就只能存放几百到上千个页表项。如果进程要使用64G内存，则64G/4KB=16000000（一千六百万）页，每页在页表项中占用16000000*4B=62MB。如果用HugePage采用2MB作为一页，只需64G/2MB=2000，数量不在同个级别。\n而DPDK采用HugePage，在×86-64下支持2MB、1GB的页大小，几何级的降低了页表项的大小，从而减少TLB-Miss，并提供了内存池（Mempool）、MBuf、无锁环（Ring）、Bitmap等基础库。\n而且对于普通应用程序而言，根本不会调用到大页内存，且大页内存分配之后立刻就会在系统的可用内存中划去，不管dpdk程序是否使用，都不会被其他程序占用，在只运行了dpdk程序的机器上，划分出的大页内存区域就是该dpdk程序独享的VIP区。\n这里就是DPVS宣传的mempool和HugePage特性的实现原理。\n总结 稍加深入分析之后我们不难发现DPVS的高性能实现基本都是依靠特定硬件和DPDK来实现的，那么DPVS的优势在哪里呢？\n实际上DPDK作为一套开发套件，本身并不是特别的完善，由于它的PMD驱动绕过了内核的网络栈，因此想要实现DPDK+LVS的核心难度在于如何在用户态实现一套轻量级的网络协议栈（L4LB不需要完整的网络栈），这也是DPVS的最核心难点之一。\n","permalink":"http://localhost:9011/20201112-dpdk-in-dpvs-principle-analysis/","summary":"\u003cp\u003e\u003ca href=\"https://tinychen.com/20201103-dpvs-deploy-in-centos7/\"\u003e上一篇文章\u003c/a\u003e中我们已经介绍了DPVS的特点和部署方式，本文主要是用于介绍DPVS是如何实现前面所说的特点，或者说是如何提高性能的。\u003c/p\u003e\n\u003cp\u003e下图是爱奇艺的DPVS开发团队给出的DPVS在提高性能方面的操作，我们这里换一个角度，自底向上，从底层的CPU、内存、网卡来看这些操作是如何实现的。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e（本文涉及到较多的计算机组织架构和操作系统原理的知识点，由于篇幅原因没办法一一详解，因此有一定的理解门槛，如果有看不懂的知识点可以在文章下面留言，有机会我会写一些文章详细介绍一下相关内容）\u003c/p\u003e\n\u003c/blockquote\u003e","title":"DPDK在DPVS中的应用及原理分析"},{"content":"对DPVS进行简单的介绍和在centos7的物理机上部署dpvs负载均衡系统。\nDPVS简介 DPVS架构 DPVS是一个基于DPDK的高性能四层负载均衡器（Layer-4 load balancer），DPVS的名字来源于DPDK+LVS，注意这里的LVS是阿里巴巴改进版的LVS。下图是爱奇艺官方给出的一个DPVS架构以及主要特点：\n用户态实现 DPVS主要的任务都是在用户态完成的，可以极大地提高效率。*官方声称DPVS的包处理速度，1个工作线程可以达到 2.3Mpps，6个工作线程可以达到万兆网卡小包的转发线速（约 12Mpps)。*这主要是因为DPVS绕过了内核复杂的协议栈，并采用轮询的方式收发数据包，避免了锁、内核中断、上下文切换、内核态和用户态数据拷贝产生的性能开销。\n实际上四层负载均衡并不需要完整的协议栈，但是需要基本的网络组件，以便完成和周围设备的交互（ARP/NS/NA）、确定分组走向 （Route）、回应 Ping 请求、健全性检查（分组完整性，Checksum校验）、以及 IP 地址管理等基本工作。使用 DPDK 提高了收发包性能，但也绕过了内核协议栈，DPVS 依赖的协议栈需要自己实现。\nMaster/Worker模型 这一点和nginx一样，使用M/S模型，Master 处理控制平面，比如参数配置、统计获取等；Worker 实现核心负载均衡、调度、数据转发功能。\n另外，DPVS 使用多线程模型，每个线程绑定到一个 CPU 物理核心上，并且禁止这些 CPU 被调度。这些 CPU 只运行 DPVS 的 Master 或者某个 Worker，以此避免上下文切换，别的进程不会被调度到这些 CPU，Worker 也不会迁移到其他 CPU 造成缓存失效。\n网卡队列/CPU绑定 现在的服务器网卡绝大多数都是多队列网卡，支持多个队列同时收发数据，让不同的 CPU 处理不同的网卡队列的流量，分摊工作量，DPVS将其和CPU进行绑定，利用DPDK 的 API 实现一个网卡的一个收发队列对应一个CPU核心和一个Worker进程，实现一一对应和绑定，从而实现了处理能力随CPU核心、网卡队列数的增加而线性增长，并且很好地实现了并行处理和线性扩展。\n关键数据无锁化 **内核性能问题的一大原因就是资源共享和锁。**所以，被频繁访问的关键数据需要尽可能的实现无锁化，其中一个方法是将数据做到 per-cpu 化，即每个CPU核心只处理自己本地的数据，不需要访问其他CPU的数据，这样就可以避免加锁。对于DPVS而言，连接表，邻居表，路由表等频繁修改或者频繁查找的数据，都做到了 per-cpu 化。但是在具体 per-cpu 的实现上，连接表和邻居表、路由表两者的实现方式并不相同。\n连接表在高并发的情况下会被频繁的CRUD。DPVS中每个CPU核心维护的是不相同的连接表，不同的网络数据流（TCP/UDP/ICMP）按照 N 元组被定向到不同的CPU核心，在此特定的CPU核心上创建、查找、转发、销毁。同一个数据流的包，只会出现在某个CPU核心上，不会落到其他的CPU核心上。这样就可以做到不同的CPU核心只维护自己本地的表，无需加锁。\n对于邻居表和路由表这种每个CPU核心都要使用的全局级别的操作系统数据，默认情况下是使用”全局表+锁保护“的方式。DPVS通过让每个CPU核心有同样的视图，也就是每个CPU核心需要维护同样的表，从而做到了per-cpu。对于这两个表，虽然在具体实现上有小的差别（路由表是直接传递信息，邻居是克隆数据并传递分组给别的 CPU），但是本质上都是通过跨CPU通信来实现的跨CPU无锁同步，从而将表的变化同步到每个CPU，最后实现了无锁化。\n跨CPU无锁通信 上面的关键数据无锁化和这一点实际上是殊途同归的。首先，虽然采用了关键数据 per-cpu等优化，但跨CPU还是需要通信的，比如:\nMaster 获取各个 Worker 的各种统计信息 Master 将路由、黑名单等配置同步到各个 Worker Master 将来自DPVS的KNI网卡的数据发送到 Worker（只有 Worker 能操作DPDK网卡接口来发送数据） 既然需要通信，就不能存在互相影响、相互等待的情况，因为那会影响性能。DPVS的无锁通信还是主要依靠DPDK提供的无锁rte_ring库实现的，从底层保证通信是无锁的，并且我们在此之上封装一层消息机制来支持一对一，一对多，同步或异步的消息。\n丰富的功能 从转发模式上看：DPVS 支持 DirectRouting（DR）、NAT、Tunnel、Full-NAT、SNAT五种转发模式，可以灵活适配各种网络应用场景\n从协议支持上看：DPVS 支持 IPv4和 IPv6 协议、且最新版本增加了 NAT64的转发功能，实现了用户从 IPv6网络访问 IPv4服务\n从设备支持上看：DPVS支持主流的硬件网卡设备，同时还支持了Bonding（mode 0 and 4 ）, VLAN, kni, ipip/GRE等虚拟设备\n从管理工具上看：可以使用包括 ipvsadm、keepalived、dpip等工具对DPVS进行配置和管理，也支持使用进行 quagga 集群化部署\n小结 从上面列出的几个DPVS的主要特点我们不难发现，DPVS的主要设计思路就是通过减少各种切换和避免加锁来提高性能，具体的实现上则主要依赖了DPDK的许多功能特性以及使用了常用的几个开源负载均衡软件（ipvsadm、keepalived、dpip等），结合用户态的轻量级网络协议栈（只保留了四层负载均衡所必须的），就实现了超强性能的四层负载均衡系统。\n机器配置 DPVS由于引入了DPDK套件作为底层的支撑，因此想要最大化发挥它的性能，需要对硬件有一定的要求，dpdk官方给出了一份支持列表，虽然支持性列表上面的平台支持得很广泛，但是实际上兼容性和表现最好的似乎还是要Intel的硬件平台。网卡的兼容性方面，主流的Intel网卡几乎都支持，需要注意的是不同型号的网卡在flow-director功能和网卡的收发数据包支持的队列数可能会有不同。\n机器参数 CPU：两颗 Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz\n内存：16G*8 DDR4-2400 MT/s，每个CPU64G，共计128G\n网卡：两张双口的Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)\n系统：Red Hat Enterprise Linux Server release 7.6 (Maipo)\n内核：3.10.0-1127.19.1.el7.x86_64\n关闭超线程和启用NUMA策略 关闭超线程最好的办法是在BIOS中找到相关的超线程设置并且将其禁用，而NUMA策略也是一样，最好在BIOS中直接打开。\n打开超线程技术的时候我们可以看到Thread(s) per core是2，也就是每个物理核心对应有2个逻辑核心，而Core(s) per socket表示每个socket有10个物理核心（一般一个CPU对应一个socket），Socket(s)表示当前服务器有两个CPU，也就是常说的双路。\n$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 40 On-line CPU(s) list: 0-39 Thread(s) per core: 2 Core(s) per socket: 10 Socket(s): 2 NUMA node(s): 2 Vendor ID: GenuineIntel CPU family: 6 Model: 79 Model name: Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz Stepping: 1 CPU MHz: 2669.567 CPU max MHz: 3100.0000 CPU min MHz: 1200.0000 BogoMIPS: 4399.75 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 25600K NUMA node0 CPU(s): 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38 NUMA node1 CPU(s): 1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39 # 当硬件的超线程设置打开的时候，显示为1，表明硬件启用了超线程 $ cat /sys/devices/system/cpu/smt/active 1 # 当然较高版本的内核也可以通过smt.control参数来从操作系统层面控制超线程技术 $ cat /sys/devices/system/cpu/smt/control on 关闭超线程技术之后的时候我们可以看到Thread(s) per core是1，也就是每个物理核心对应有1个逻辑核心。这时候CPU(s)的数值和CPU的物理核心数值应该相等。\n$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 20 On-line CPU(s) list: 0-19 Thread(s) per core: 1 Core(s) per socket: 10 Socket(s): 2 NUMA node(s): 2 Vendor ID: GenuineIntel CPU family: 6 Model: 79 Model name: Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz Stepping: 1 CPU MHz: 1200.036 CPU max MHz: 3100.0000 CPU min MHz: 1200.0000 BogoMIPS: 4400.07 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 25600K NUMA node0 CPU(s): 0,2,4,6,8,10,12,14,16,18 NUMA node1 CPU(s): 1,3,5,7,9,11,13,15,17,19 $ cat /sys/devices/system/cpu/smt/active 0 使用numastat命令可以看到有两个node，表明已经打开NUMA策略，同样的还可以使用lscpu命令查看NUMA node(s)的数量。\n$ numastat node0 node1 numa_hit 654623151 11334196 numa_miss 0 0 numa_foreign 0 0 interleave_hit 34599 34359 local_node 654619810 11281809 other_node 3341 52387 安装 开始之前我们需要使用yum安装一些编译安装的时候需要使用的工具和软件\n$ yum group install \u0026#34;Development Tools\u0026#34; $ yum install patch libnuma* numactl numactl-devel kernel-devel openssl* popt* -y # 2021-0722更新，centos7.9系统需要安装 $ yum install libpcap-devel -y # 2021-0722更新，需要支持ipv6需要安装libnl3-devel # *** WARNING - this build will not support IPVS with IPv6. Please install libnl/libnl-3 dev libraries to support IPv6 with IPVS. $ yum install libnl3-devel -y # 注意kernel以及相应的kernel组件的版本需要和现在使用的kernel版本相对应 $ rpm -qa | grep kernel | grep \u0026#34;3.10.0-1127.19.1\u0026#34; | sort kernel-3.10.0-1127.19.1.el7.x86_64 kernel-debug-devel-3.10.0-1127.19.1.el7.x86_64 kernel-devel-3.10.0-1127.19.1.el7.x86_64 kernel-headers-3.10.0-1127.19.1.el7.x86_64 kernel-tools-3.10.0-1127.19.1.el7.x86_64 kernel-tools-libs-3.10.0-1127.19.1.el7.x86_64 $ uname -r 3.10.0-1127.19.1.el7.x86_64 这时候需要机器能够连接外网（互联网），直接从github将dpvs项目clone下来，同时还需要下载特定版本的dpdk，无法联网的机器也可以直接下载之后传输到服务器中，后续的安装过程并不需要联网\n# 首先我们从GitHub上面把dpvs整个项目clone下来 $ cd /home/ $ git clone https://github.com/iqiyi/dpvs.git $ cd /home/dpvs # 然后我们下载特定版本的dpdk并解压 $ wget https://fast.dpdk.org/rel/dpdk-17.11.2.tar.xz $ tar vxf dpdk-17.11.2.tar.xz 接下来需要对dpdk进行打补丁，注意这里的补丁并不是必须的。\n$ cd /home/dpvs $ cp patch/dpdk-stable-17.11.2/*.patch dpdk-stable-17.11.2/ $ cd dpdk-stable-17.11.2/ # 0001号补丁主要是用于在kni网卡上开启硬件多播功能，比如在kni设备上启动ospfd $ patch -p 1 \u0026lt; 0001-kni-use-netlink-event-for-multicast-driver-part.patch # patching file lib/librte_eal/linuxapp/kni/kni_net.c # 0002号补丁主要是使用dpvs的UOA模块的时候需要用到 $ patch -p 1 \u0026lt; 0002-net-support-variable-IP-header-len-for-checksum-API.patch # patching file lib/librte_net/rte_ip.h 编译dpdk $ cd /home/dpvs/dpdk-stable-17.11.2 $ make config T=x86_64-native-linuxapp-gcc # Configuration done using x86_64-native-linuxapp-gcc $ make 如果出现下面的问题\n我们需要手动更改文件，解决方案参考这里：\n用find / -name netdevice.h 查找内核中的头文件，找到struct net_device_ops 中的 ndo_change_mtu，\n会看到ndo_change_mtu被替换成对应版本的ndo_change_mtu_rhXX,比如 ndo_change_mtu_rh75 将 /kni_net.c:704:2 中 ndo_change_mtu 用 ndo_change_mtu_rh75 替换试试？\n查看对应内核中的文件\n$ find / -name netdevice.h /usr/include/linux/netdevice.h /usr/src/kernels/3.10.0-1127.19.1.el7.x86_64.debug/include/linux/netdevice.h /usr/src/kernels/3.10.0-1127.19.1.el7.x86_64.debug/include/uapi/linux/netdevice.h /usr/src/kernels/3.10.0-1127.19.1.el7.x86_64/include/linux/netdevice.h /usr/src/kernels/3.10.0-1127.19.1.el7.x86_64/include/uapi/linux/netdevice.h $ grep ndo_change_mtu /usr/src/kernels/3.10.0-1127.19.1.el7.x86_64/include/linux/netdevice.h * int (*ndo_change_mtu)(struct net_device *dev, int new_mtu); int (*ndo_change_mtu)(struct net_device *dev, * int (*ndo_change_mtu)(struct net_device *dev, int new_mtu); RH_KABI_RENAME(int (*ndo_change_mtu), int (*ndo_change_mtu_rh74))(struct net_device *dev, * .ndo_change_mtu_rh74 handler is *not* provided. * both .extended.ndo_change_mtu() as well as .ndo_change_mtu_rh74() are $ grep ndo_change_mtu /home/dpvs/dpdk-stable-17.11.2/lib/librte_eal/linuxapp/kni/kni_net.c .ndo_change_mtu = kni_net_change_mtu, 或者对于CentOS7.5之后的版本可以直接执行\nsed -i \u0026#39;s/ndo_change_mtu/ndo_change_mtu_rh74/g\u0026#39; /home/dpvs/dpdk-stable-17.11.2/lib/librte_eal/linuxapp/kni/kni_net.c 然后我们重新执行make操作\n$ make clean $ make # 出现下面字段说明make成功 # Build complete [x86_64-native-linuxapp-gcc] # 接着设置变量 $ export RTE_SDK=$PWD 配置hugepage 和其他的一般程序不同，dpvs使用的dpdk并不是从操作系统中索要内存，而是直接使用大页内存（hugepage），极大地提高了内存分配的效率。\n官方的配置过程中使用的是2MB的大页内存，这里的8192指的是分配了8192个2MB的大页内存，也就是一个node对应16GB的内存，一共分配了32GB的内存，这里的内存可以根据机器的大小来自行调整。但是如果小于1GB可能会导致启动报错。\n# for NUMA machine $ echo 8192 \u0026gt; /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages $ echo 8192 \u0026gt; /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages $ mkdir /mnt/huge $ mount -t hugetlbfs nodev /mnt/huge # 需要开机自动挂载的话可以在 $ echo \u0026#34;nodev /mnt/huge hugetlbfs defaults 0 0\u0026#34; \u0026gt;\u0026gt; /etc/fstab 挂载驱动模块 $ modprobe uio $ cd /home/dpvs/dpdk-stable-17.11.2 $ insmod /home/dpvs/dpdk-stable-17.11.2/build/kmod/igb_uio.ko $ insmod /home/dpvs/dpdk-stable-17.11.2/build/kmod/rte_kni.ko 使用脚本查看目前机器和dpvs兼容的设备，这里我们只截取部分重点内容\n$ ./usertools/dpdk-devbind.py --status $ ./usertools/dpdk-devbind.py --status Network devices using DPDK-compatible driver ============================================ \u0026lt;none\u0026gt; Network devices using kernel driver =================================== 0000:01:00.0 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth0 drv=ixgbe unused=igb_uio 0000:01:00.1 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth1 drv=ixgbe unused=igb_uio 0000:04:00.0 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth4 drv=ixgbe unused=igb_uio *Active* 0000:04:00.1 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth5 drv=ixgbe unused=igb_uio *Active* 0000:08:00.0 \u0026#39;I350 Gigabit Network Connection 1521\u0026#39; if=eth2 drv=igb unused=igb_uio 0000:08:00.1 \u0026#39;I350 Gigabit Network Connection 1521\u0026#39; if=eth3 drv=igb unused=igb_uio # 对需要使用dpvs的网卡加载特定的驱动 $ ifconfig eth5 down $ ./usertools/dpdk-devbind.py -b igb_uio 0000:04:00.1 # 再次检查是否加载成功 $ ./usertools/dpdk-devbind.py --status Network devices using DPDK-compatible driver ============================================ 0000:04:00.1 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; drv=igb_uio unused=ixgbe Network devices using kernel driver =================================== 0000:01:00.0 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth0 drv=ixgbe unused=igb_uio 0000:01:00.1 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth1 drv=ixgbe unused=igb_uio 0000:04:00.0 \u0026#39;82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb\u0026#39; if=eth4 drv=ixgbe unused=igb_uio *Active* 0000:08:00.0 \u0026#39;I350 Gigabit Network Connection 1521\u0026#39; if=eth2 drv=igb unused=igb_uio 0000:08:00.1 \u0026#39;I350 Gigabit Network Connection 1521\u0026#39; if=eth3 drv=igb unused=igb_uio 编译安装dpvs $ cd /home/dpvs/dpdk-stable-17.11.2/ $ export RTE_SDK=$PWD $ cd /home/dpvs/ $ make $ make install $ cd bin/ $ ls dpip dpvs ipvsadm keepalived $ cp conf/dpvs.conf.single-nic.sample /etc/dpvs.conf $ cd /home/dpvs/bin/ $ ./dpvs \u0026amp; # 如果安装成功并且成功运行了，执行命令就可以看到 $ ./dpip link show 1: dpdk0: socket 0 mtu 1500 rx-queue 8 tx-queue 8 UP 10000 Mbps full-duplex auto-nego addr 00:1B:21:BE:EA:C2 OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM 为了方便管理可以将相关的操作命令软链接到/sbin下方便全局执行\nln -s /home/dpvs/bin/dpvs /sbin/dpvs ln -s /home/dpvs/bin/dpip /sbin/dpip ln -s /home/dpvs/bin/ipvsadm /sbin/ipvsadm ln -s /home/dpvs/bin/keepalived /sbin/keepalived ","permalink":"http://localhost:9011/20201103-dpvs-deploy-in-centos7/","summary":"\u003cp\u003e对DPVS进行简单的介绍和在centos7的物理机上部署dpvs负载均衡系统。\u003c/p\u003e","title":"DPVS简介与部署"},{"content":"本文主要介绍将centos7/8中引入的新网卡命名方式修改为传统的ethx命名并将命名和mac地址进行持久化绑定的方法\n默认情况下的网卡名称会根据网卡的型号和连接方式，如USB网卡、PCIe网卡等各种方式不同而显示出不同的名称，例如这里就显示为eno1：\n[root@tiny-server network-scripts]# nmcli connection show NAME UUID TYPE DEVICE eno1 03043370-9378-4a22-9396-b8f7c83142d5 ethernet eno1 virbr0 821ce0c2-56cb-42ce-93c5-4933b9b56841 bridge virbr0 修改内核参数并重新生成grub引导文件，然后重新启动系统\n#在内核启动参数中的rhgb前添加net.ifnames=0 sed -i \u0026#39;s/rhgb/net.ifnames=0 \u0026amp;/\u0026#39; /etc/default/grub # 修改前 GRUB_CMDLINE_LINUX=\u0026#34;crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet\u0026#34; # 修改后 GRUB_CMDLINE_LINUX=\u0026#34;crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap net.ifnames=0 rhgb quiet\u0026#34; # 重新生成引导文件，注意BIOS引导和EFI引导的启动文件目录会不一样 grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg 或者也可以直接使用grubby工具\ngrubby --update-kernel=ALL --args=net.ifnames=0 重启系统之后原来的网卡已经失效无法使用，此时无法正常连接网络，因此我们需要使用nmcli工具新建网卡\n# 其中con-name为新建的网卡名称，而后面的ifname为已有的硬件网卡名称 # nmcli connection add type ethernet con-name eth0 ifname eth0 # 新建后查看如下 $ nmcli connection show NAME UUID TYPE DEVICE eth1 363bd12e-34aa-435d-83db-2e966c50854b ethernet eth1 修改为传统的ethx命名方式之后，对应的ethx和物理网卡并没有一一对应的强绑定关系，也就是说我们对eth1网络进行配置变更之后，可能机器上面新增了一张网卡，或者是原来的网卡的pcie插槽位置变更了，都可能会导致eth1网卡对应的物理网卡发生变化，解决这个问题的最好方法就是将名字和mac地址进行绑定。\n# 修改下面的这个配置文件，如果没有就新建一个，然后写入对应的mac地址和网卡名字，最后重启机器即可 [root@tiny-dpvs ~]# cat /etc/udev/rules.d/70-persistent-net.rules SUBSYSTEM==\u0026#34;net\u0026#34;, ACTION==\u0026#34;add\u0026#34;, DRIVERS==\u0026#34;?*\u0026#34;, ATTR{address}==\u0026#34;11:22:33:44:55:66\u0026#34;, NAME=\u0026#34;eth0\u0026#34; SUBSYSTEM==\u0026#34;net\u0026#34;, ACTION==\u0026#34;add\u0026#34;, DRIVERS==\u0026#34;?*\u0026#34;, ATTR{address}==\u0026#34;11:22:33:44:55:77\u0026#34;, NAME=\u0026#34;eth1\u0026#34; SUBSYSTEM==\u0026#34;net\u0026#34;, ACTION==\u0026#34;add\u0026#34;, DRIVERS==\u0026#34;?*\u0026#34;, ATTR{address}==\u0026#34;11:22:33:44:55:88\u0026#34;, NAME=\u0026#34;eth2\u0026#34; ","permalink":"http://localhost:9011/20201024-centos8-modify-eth-name/","summary":"\u003cp\u003e本文主要介绍将centos7/8中引入的新网卡命名方式修改为传统的ethx命名并将命名和mac地址进行持久化绑定的方法\u003c/p\u003e","title":"centos7/8修改网卡名称并持久化配置"},{"content":"本文主要记录使用wsl2长时间开启后占用过多系统内存的解决办法。\n前言 前几天笔记本一直没有关机，后面发现系统内存已经被占满了，我的笔记本扣掉核显内存占用之后可用容量大概还有15G多一点，用任务管理器里面查看发现是Vmmem这个进程占用了大量的内存，这个就是wsl对应的虚拟机进程。\n查询了相关资料发现这并不是个例，wsl2本身的机制似乎会不断地拿宿主机的内存来给自己当cache使用，并且拿了还不还，之前使用台式机的时候有关机的习惯因此并不会太明显，现在使用笔记本长时间不关机就触发了这个问题。目前来说主要有以下几个解决方案：\n重启wsl2 使用.wslconfig文件限制资源 调整内核参数定期释放cache内存 重启wsl2 如果我们直接关闭wsl2的窗口并不会关闭该wsl2的虚拟机，它依旧会在后台运行，需要关闭wsl2的话我们可以打开powershell，在里面使用wsl --shutdown命令就可以关闭全部的wsl2虚拟机了。之后再随意打开一个wsl2的窗口就会再次开启虚拟机。\n这种方式释放内存最彻底，效果和物理机内存不足卡死然后直接重启是一样的。\n限制wsl2内存使用 这个解决方案来自github，简单来说就是创建一个%UserProfile%\\.wslconfig文件来限制wsl使用的内存总量。比如说我在Windows中使用的用户是tinychen，那么我就在C:\\Users\\tinychen中创建了一个.wslconfig文件，在里面加入以下内容来限制wsl2的内存总大小：\n[wsl2] processors=8 memory=8GB swap=8GB localhostForwarding=true 注意修改完成之后需要重启wsl2才能生效。更多详细的配置可以查看官方文档。\n定期释放cache内存 Linux内核中有一个参数/proc/sys/vm/drop_caches，是可以用来手动释放Linux中的cache缓存，如果发现wsl2的cache过大影响到宿主机正常运行了，可以手动执行以下命令来释放cache：\necho 3 \u0026gt; /proc/sys/vm/drop_caches 当然也可以设置成定时任务，每隔一段时间释放一次。\n","permalink":"http://localhost:9011/20200924-wsl2-limit-memory/","summary":"\u003cp\u003e本文主要记录使用wsl2长时间开启后占用过多系统内存的解决办法。\u003c/p\u003e","title":"限制wsl2占用过多内存"},{"content":"本文主要记录了一些Linux系统下使用ssh指令的技巧和ssh的config文件简化ssh操作的过程。\n设置权限 一般来说，每个用户都会在对应的家目录下生成一个.ssh文件夹，如tinychen用户则为/home/tinychen/.ssh，而root用户则为/root/.ssh。如果是手动创建的.ssh文件夹，还需要注意权限问题。一般来说.ssh文件夹的权限为700，私钥为600，公钥为644。\n[/home/tinychen]# ls -lA | grep ssh drwx------ 2 tinychen tinychen 4096 Sep 11 15:48 .ssh [/home/tinychen/.ssh]# ls -l total 8 -rw------- 1 tinychen tinychen 2610 Sep 11 15:48 id_rsa -rw-r--r-- 1 tinychen tinychen 575 Sep 11 15:48 id_rsa.pub 指定密钥 对于密钥而言，一个用户可能会有多个密钥，如果需要在建立ssh连接的时候需要指定某个密钥可以使用-i参数\nssh -i /home/tinychen/.ssh/id_rsa tinychen@192.168.1.1 指定端口 如果ssh服务器的端口不是默认的22端口，则在连接的时候需要使用-p参数手动指定端口\nssh -p 23333 tinychen@192.168.1.1 多路复用 多路复用这个功能并不算是罕见的新功能，对于ssh也可以实现ssh连接的多路复用，即在和一台主机建立ssh连接之后，再次与这台主机建立连接的时候直接复用已有的ssh连接，就不需要再重复进行密码验证等各种操作，这种操作在ssh里面称之为ControlMaster。配置的方法很简单，我们只需要在每个用户对应的~/.ssh/目录下面新建一个config文件，再添加对应的配置：\nHost * # Host变量主要用于控制哪些主机会使用下面的配置 ControlMaster auto # ControlMaster变量用于控制是否开启ssh的多路复用功能 ControlPersist 600s # ControlPersist变量主要用于控制保持连接的时间，这里600s即为600秒（10分钟） ControlPath ~/.ssh/%r@%h:%p.socket # ControlPath变量用于存放复用连接所产生的socket文件的路径和命名方式 代理转发 ssh中的代理转发功能，名为agent forwarding，主要作用如下：\n假设当前有两台服务器A和B，你从自己的电脑远程登录到服务器A，这时候想直接在服务器A上面登录到服务器B，但是服务器A上面没有能够登录到服务器B的key，此时只要开启代理转发功能，将自己电脑的key跟随ssh过程一起发送到服务器A，就可以从服务器A登录到服务器B了。\n如何验证自己的配置是否生效呢？我们可以通过ssh-add -L命令来查看当前主机所拥有的key。\nHost * ForwardAgent yes 保持连接 有些ssh服务端为了安全，在一定的时间内没有接收到来自客户端的操作，就会主动断开连接。想要延长这个时间我们可以设置客户端每隔一段时间就向服务器发送数据包来保持连接：\nServerAliveInterval 60 ServerAliveCountMax 60 同样地，我们也可以在服务器端添加相关的配置，一般都是在/etc/ssh/sshd_config文件中\nClientAliveInterval 60 ClientAliveCountMax 60 其中ServerAliveInterval和ClientAliveInterval表示间隔时间，每隔60s发送一次；ServerAliveCountMax和ClientAliveCountMax则表示发送的最大次数，最多发送60次。即在服务器端和客户端之间没有操作的时候重复发送60次，一共60次*60秒共计1小时后断开连接。\n","permalink":"http://localhost:9011/20200912-ssh-config-introduction/","summary":"\u003cp\u003e本文主要记录了一些Linux系统下使用ssh指令的技巧和ssh的config文件简化ssh操作的过程。\u003c/p\u003e","title":"ssh常用配置"},{"content":"本文主要记录各个Windows NT版本对应的Windows版本。原维基百科链接可以点击这里查看。\nWindows NT，新技术视窗操作系统（Windows New Technology）的简称，是美国微软公司1993年推出的纯32位操作系统核心。其基于OS/2 NT的基础构造。OS/2是由微软和IBM联合研制，分为微软的Microsoft OS/2 NT与IBM的IBM OS/2。由于双方在协作后来不欢而散，IBM继续向市场提供先前的OS/2版本；而微软则把OS/2 NT改名为Windows NT，并在1988年11月开始了对于“WinNT”（即第一代的Windows NT 3.1）的产品研发。在研发初期，Windows NT曾一度被认为将会是原先OS/2的3.0版本，但面世之后的Windows NT是一种纯32位操作系统，采用NT核心技术。后期更新的Windows NT支持32与64位两种版本。\nNT版本 市场名称 版本 发行日期 NT 3.1 Windows NT 3.1 Workstation（仅被命名为“Windows NT”）、Advanced Server 1993年7月27日 NT 3.5 Windows NT 3.5 Workstation、Advanced Server 1994年9月5日 NT 3.51 Windows NT 3.51 Workstation、服务器版 1995年5月30日 NT 4.0 Windows NT 4.0 Workstation、服务器版、Server Enterprise Edition, Terminal Server、Embedded 1996年7月29日 NT 5.0 Windows 2000 专业版、服务器版、Advanced Server、DataCenter Server 2000年2月17日 NT 5.1 Windows XP 家庭版、专业版、Media Center、Tablet PC、入门版、Embedded、N 2001年10月25日 NT 5.2 Windows XP 64-bit Edition Version 2003（For IA64）、专业版 x64 Edition（For AMD64） IA64: 2003年3月28日x64: 2005年4月25日 Windows Server 2003 Standard、企业版、DataCenter、Web、Small Business Server 2003年4月24日 Windows Server 2003 R2 Standard、企业版、DataCenter、Web、Small Business Server 2005年12月6日 NT 6.0 Windows Vista 入门版、家庭普通版、家庭高级版、商业版、企业版、旗舰版 2007年1月30日 Windows Server 2008 Foundation、Standard、企业版、Datacenter、Web Server、HPC Server、Itanium-Based Systems 2008年8月27日 6001（RTM）6002（SP2） NT 6.1 Windows 7 入门版、家庭普通版、家庭高级版、专业版、企业版、旗舰版 2009年10月22日 Windows Server 2008 R2 Foundation、Standard、企业版、Datacenter、Web Server、HPC Server、Itanium-Based Systems 2009年10月22日 7600（RTM）7601（SP1） NT 6.2 Windows 8 Windows 8、专业版、企业版、Windows RT 2012年10月26日 Windows Phone 8 不适用 2012年10月30日 9200 Windows Server 2012 Foundation、Essentials、Standard、Datacenter 2012年9月4日 9200 NT 6.3 Windows 8.1 Windows 8.1、专业版、企业版、Windows RT 8.1 2013年10月18日 Windows Server 2012 R2 Essentials、Standard、Datacenter 2013年10月18日 9600 NT 10.0 Windows 10 个人使用：家庭版（包括S模式的家庭版）[7]、专业版（包括S模式的专业版）[7]、专业工作站版商用：企业版、教育版、专业教育版（包括S模式的专业教育版）[7]移动设备：移动版、移动企业版物联网设备：Windows 10 IoT 2015年7月29日 Windows Server 2016 Essentials、Standard、Datacenter 2016年9月26日 14393（RS1） 16299（RS3） 17134（RS4） Windows Server 2019 Essentials、Standard、Datacenter 2018年10月2日 17763 ","permalink":"http://localhost:9011/20200907-windows-nt-version/","summary":"\u003cp\u003e本文主要记录各个Windows NT版本对应的Windows版本。原维基百科链接可以点击\u003ca href=\"https://en.wikipedia.org/wiki/Windows_NT\"\u003e这里\u003c/a\u003e查看。\u003c/p\u003e","title":"Windows NT对应版本"},{"content":"本文主要介绍在xrags命令的一些参数和常见的使用方法。\n使用Linux命令的时候，我们经常使用管道符号|来进行命令之间的输入输出的传递，例如\ncat example.txt | grep uuid | sort 这一条命令就可以将example.txt这个文件的内容全部打印到屏幕上，然后使用grep来查找带有uuid字符的行，接着使用sort命令来进行排序，当然我们也可以直接这样：\ngrep uuid example.txt | sort 两者的效果是一样的，但是对于很多命令来说管道符号|却不一定能用\n例如我们需要查找所有进程中带有nginx关键字的进程并且将其全部kill掉，我们可以这样获取到对应的进程ID信息\n# ps -ef | grep nginx | grep -v grep | awk \u0026#39;{print $2}\u0026#39; 12245 29913 理论上我们只需要将对应的PID全部kill掉就可以完成操作，但是却不能直接使用管道符号，因为kill命令没办法接收这样的输入信息\n# 这样操作是不行的 # ps -ef | grep nginx | grep -v grep | awk \u0026#39;{print $2}\u0026#39; | kill -9 这个时候我们就可以使用xargs命令来进行操作，xargs命令会将前面命令的输入结果逐个传递给后面的kill命令\nps -ef | grep nginx | grep -v grep | awk \u0026#39;{print $2}\u0026#39; | xargs kill -9 接着我们来查看一下xargs命令的使用说明：\n[/root]# xargs --help Usage: xargs [OPTION]... COMMAND [INITIAL-ARGS]... Run COMMAND with arguments INITIAL-ARGS and more arguments read from input. Mandatory and optional arguments to long options are also mandatory or optional for the corresponding short option. -0, --null items are separated by a null, not whitespace; disables quote and backslash processing and logical EOF processing -a, --arg-file=FILE read arguments from FILE, not standard input -d, --delimiter=CHARACTER items in input stream are separated by CHARACTER, not by whitespace; disables quote and backslash processing and logical EOF processing -E END set logical EOF string; if END occurs as a line of input, the rest of the input is ignored (ignored if -0 or -d was specified) -e, --eof[=END] equivalent to -E END if END is specified; otherwise, there is no end-of-file string -I R same as --replace=R -i, --replace[=R] replace R in INITIAL-ARGS with names read from standard input; if R is unspecified, assume {} -L, --max-lines=MAX-LINES use at most MAX-LINES non-blank input lines per command line -l[MAX-LINES] similar to -L but defaults to at most one non- blank input line if MAX-LINES is not specified -n, --max-args=MAX-ARGS use at most MAX-ARGS arguments per command line -P, --max-procs=MAX-PROCS run at most MAX-PROCS processes at a time -p, --interactive prompt before running commands --process-slot-var=VAR set environment variable VAR in child processes -r, --no-run-if-empty if there are no arguments, then do not run COMMAND; if this option is not given, COMMAND will be run at least once -s, --max-chars=MAX-CHARS limit length of command line to MAX-CHARS --show-limits show limits on command-line length -t, --verbose print commands before executing them -x, --exit exit if the size (see -s) is exceeded --help display this help and exit --version output version information and exit Report bugs to \u0026lt;bug-findutils@gnu.org\u0026gt;. 这里着重说明几个参数\n参数 作用 -t 在执行对应的命令之前先将其打印出来 -a 从指定的文件中读取变量运行命令而不是标准的输入 -i 使用{}可以替换对应的变量 对于-i参数的使用可能不太能理解，我们举例进行说明：\n假设我们需要对当前目录下的所有txt文件进行查找，对于文件中含有tinychen关键字的文件名全部添加一个own的后缀\ngrep tinychen *.log 2\u0026gt;/dev/null | awk -F : \u0026#39;{print $1}\u0026#39; | sort | uniq | xargs -t -i mv {} {}.own 对于上面的这个命令中使用了-i参数之后，会将前面读取到的输入与{}相关联，对于使用像mv这样的需要输入不止一个变量的命令的时候特别有用。\n再举个例子，假设我们需要查找当前目录下面的txt文件，将所有带有192.168.192.168字段的行注释掉\ncat *.txt 2\u0026gt;/dev/null | grep 192.168.192.168 | xargs -t -i sed -i \u0026#39;s/{}/#{}/g\u0026#39; *.txt ","permalink":"http://localhost:9011/20200903-linux-xargs-command-introduction/","summary":"\u003cp\u003e本文主要介绍在xrags命令的一些参数和常见的使用方法。\u003c/p\u003e","title":"xrags命令的常见用法"},{"content":"本文主要介绍Megacli工具的常用命令\nMegaCli 是LSI公司官方提供的SCSI卡管理工具软件，后来经过一系列的收购合并等操作，现在的LSI属于博通公司旗下的产品，因此如果需要下载MegaCli的话可以前往博通官网下载。\nMegaCli工具一般在/opt/MegaRAID/MegaCli/MegaCli64，当然也可以直接使用MegaCli，一般来说两者是一样的\n/opt/MegaRAID/MegaCli/MegaCli64 -LDInfo -Lall -aALL # 查看所有阵列卡的所有阵列逻辑卷的所有信息 /opt/MegaRAID/MegaCli/MegaCli64 -LDInfo -Lall -a0 | grep -i \u0026#34;Virtual Disk\u0026#34; # 查看第1张阵列卡上一共有多少个raid阵列组（默认从0开始计数） /opt/MegaRAID/MegaCli/MegaCli64 -LDInfo -L0 -a0 # 查看第1张阵列卡上的第0个raid阵列组的信息（默认从0开始计数） /opt/MegaRAID/MegaCli/MegaCli64 -LDPdInfo -aAll # 查看所有阵列卡的所有阵列逻辑卷的所有信息（包括阵列逻辑卷对应的物理硬盘） /opt/MegaRAID/MegaCli/MegaCli64 -AdpAllInfo -aALL # 显示所有阵列卡信息,可以查看机器上一共有多少张阵列卡以及每张卡的详细信息 /opt/MegaRAID/MegaCli/MegaCli64 -PDList -aALL # 查看所有阵列卡上的所有物理硬盘信息，包括型号、接口、SN码、部分SMART信息等 /opt/MegaRAID/MegaCli/MegaCli64 -CfgLdAdd -r5 [32:2,32:3,32:4] WB Direct -Hsp[32:4] -a0 # 在第1张阵列卡上创建一个 raid5 阵列，由物理盘 1,2,3 构成，该阵列的热备盘是物理盘 4 # 注意这里的32:2指的是使用eID和slot ID对一个阵列卡下的硬盘进行定位（默认从0开始计数） /opt/MegaRAID/MegaCli/MegaCli64 -CfgLdAdd -r5 [32:2,32:3,32:4] WB Direct -a0 # 同上，创建一个 raid5 阵列，但是不指定热备盘 /opt/MegaRAID/MegaCli/MegaCli64 -LDRecon -Start -r5 -Add -PhysDrv[32:5] -L1 -a0 # 在线添加物理硬盘到某个阵列中 /opt/MegaRAID/MegaCli/MegaCli64 -CfgLdDel -L1 -a0 # 删除第1张阵列卡上的第2个raid阵列（默认从0开始计数） /opt/MegaRAID/MegaCli/MegaCli64 -LDInit -ShowProg -LALL -aALL # 阵列创建完后，会有一个初始化同步块的过程，可以看看其进度 /opt/MegaRAID/MegaCli/MegaCli64 -LDInit -ProgDsply -LALL -aALL # 同上，但是以动态可视化文字界面显示进度信息 /opt/MegaRAID/MegaCli/MegaCli64 -LDBI -ShowProg -LALL -aALL # 查看阵列后台初始化进度 /opt/MegaRAID/MegaCli/MegaCli64 -LDBI -ProgDsply -LALL -aALL # 同上，以动态可视化文字界面显示 /opt/MegaRAID/MegaCli/MegaCli64 -PDHSP -Set [-EnclAffinity] [-nonRevertible] -PhysDrv[32:7] -a0 # 指定第1张阵列卡的第8块盘作为全局热备 （默认从0开始计数） /opt/MegaRAID/MegaCli/MegaCli64 -PDHSP -Set [-Dedicated [-Array1]] [-EnclAffinity] [-nonRevertible] -PhysDrv[32:7] -a0 # 同上，指定为某个阵列的专用热备 /opt/MegaRAID/MegaCli/MegaCli64 -PDHSP -Rmv -PhysDrv[32:7] -a0 # 删除全局热备盘 /opt/MegaRAID/MegaCli/MegaCli64 -PDOffline -PhysDrv [32:2] -a0 # 将某块物理盘下线（offline） /opt/MegaRAID/MegaCli/MegaCli64 -PDOnline -PhysDrv [32:2] -a0 # 将某块物理盘下线（online） /opt/MegaRAID/MegaCli/MegaCli64 -PDRbld -ShowProg -PhysDrv [32:2] -a0 # 换盘后查看物理磁盘重建进度 /opt/MegaRAID/MegaCli/MegaCli64 -PDRbld -ProgDsply -PhysDrv [32:2] -a0 # 以动态可视化界面查看物理磁盘重建进度 /opt/MegaRAID/MegaCli/MegaCli64 -FwTermLog -Dsply -aALL # 查看raid卡日志，注意日志的输出量可能较大，阵列卡对应的组建阵列等操作的详细信息都可以在日志中查看 /opt/MegaRAID/MegaCli/MegaCli64 -cfgdsply -aALL # 显示Raid卡型号，Raid设置，Disk相关信息 /opt/MegaRAID/MegaCli/MegaCli64 -adpCount # 显示适配器个数，只能查看机器上一共有多少张阵列卡，不能查看详细信息 /opt/MegaRAID/MegaCli/MegaCli64 -AdpGetTime –aALL # 显示适配器时间 /opt/MegaRAID/MegaCli/MegaCli64 -AdpBbuCmd -aAll # 查看BBU的详细信息 /opt/MegaRAID/MegaCli/MegaCli64 -AdpBbuCmd -GetBbuStatus - aALL |grep \u0026#39;Charger Status\u0026#39; # 查看BBU电池的充电状态 /opt/MegaRAID/MegaCli/MegaCli64 -AdpBbuCmd -GetBbuStatus -aALL # 显示BBU状态信息 /opt/MegaRAID/MegaCli/MegaCli64 -AdpBbuCmd -GetBbuCapacityInfo -aALL # 显示BBU容量信息 /opt/MegaRAID/MegaCli/ MegaCli64 -AdpBbuCmd -GetBbuDesignInfo -aALL # 显示BBU设计参数 /opt/MegaRAID/MegaCli/MegaCli64 -AdpBbuCmd -GetBbuProperties -aALL # 显示当前BBU属性 ","permalink":"http://localhost:9011/20200902-megacli-command-introduction/","summary":"\u003cp\u003e本文主要介绍Megacli工具的常用命令\u003c/p\u003e","title":"Megacli常用命令整理"},{"content":"本文主要介绍使用nginx-quic和boringssl项目来对服务器进行升级支持HTTP/3协议。\n背景介绍 nginx官方从1.19版本开始，新建立了一个分支，专门用来对QUIC进行支持，官网的链接点这里。注意该项目还处于早期的alpha版本，非常不建议用于生产环境。\nThe code is at an early alpha level of quality and should not be used in production.\nnginx-quic的安装包可以在下面这里找到，由于还处于开发阶段，项目更新得非常快。\nhttps://hg.nginx.org/nginx-quic/shortlog/quic 从官网的readme文件我们可以了解到，截止到2020年8月21日，nginx-quic项目目前只支持h3-27、h3-28、h3-29三个版本，再早期的草案版本并不支持，不过目前更新的速度非常快，变动也很大。\nCurrently we support IETF-QUIC draft-27, draft-28, draft-29. Earlier drafts are NOT supported as they have incompatible wire format.\n考虑到浏览器的兼容性，接下来我们主要基于h3-27版本进行测试。\n同时需要注意的是openssl官方版本目前并不支持quic协议，官方于2020年2月给出的原因是目前quic并不稳定，还有很多版本需要迭代确定，它们需要优先把精力放在openssl3.0版本的开发上，等到openssl3.0版本开发完成了再来进行quic的支持。\nSo in conclusion; QUIC is on our minds, but it will not be included in the OpenSSL 3.0 release. We expect more tangible action to happen after we’ve released OpenSSL 3.0.\n所以我们这里需要用到谷歌自己使用的openssl分支版本boringssl，这是谷歌对应的开源的自用的openssl版本，谷歌官方表示boringssl这个项目虽然开源了，但是并不建议大家在生产环境上广泛使用，因为它是根据谷歌自身的需求进行调整的，有些API可能并不稳定。\n后续更新：2021年5月份，RFC 9000正式发布了HTTP3-QUIC。\nrfc9000 (ietf.org)\n编译安装nginx-quic 这里我们使用了CentOS7来进行编译安装\n[root@tiny-centos7 nginx-quic-quic]# lsb_release -a LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch Distributor ID: CentOS Description: CentOS Linux release 7.9.2009 (Core) Release: 7.9.2009 Codename: Core 首先我们在系统中使用yum安装基本的编译工具，注意此次使用的很多软件工具和依赖都是需要较新版本的，因此后面会进行大量的编译安装，请确保机器拥有足够的性能（可能需要编译gcc）和良好的网络。主要需要使用到的工具软件的安装步骤如下：\nyum安装依赖 需要注意的是这里安装所需要的软件很多，这里并不能全部列出来，但是除了列出来的软件对新版本有要求外，别的都可以直接使用yum进行安装，当然也可以自己根据需要编译安装新版本。\nsudo yum group install \u0026#34;Development Tools\u0026#34; 需要注意的是网上有部分教程会让我们去安装build-essential，而这个套件在centos中是不存在的，我们直接安装Development Tools套件即可\n编译安装cmake cmake的版本必须要在3.0以上，cmake可以到这里下载https://cmake.org/download/。\nwget https://github.com/Kitware/CMake/releases/download/v3.18.2/cmake-3.18.2.tar.gz tar -zxvf cmake-3.18.2.tar.gz ./bootstrap gmake make make install cmake --version 编译安装perl perl的安装包可以从这里www.cpan.org/src下载。perl的版本需要尽可能新，否则可能会出现问题，这里使用的是5.32.0的稳定版本（截止2020-08-21）\nwget https://www.cpan.org/src/5.0/perl-5.32.0.tar.gz tar -zxvf perl-5.32.0.tar.gz cd perl-5.32.0/ ./Configure -des -Dprefix=/usr/local/perl make -j8 make test make install # 注意最后检查一下系统默认的perl是否为我们新安装的perl perl -v # 如果不是则需要进行修改 # 查看默认的perl which perl | xargs file # 替换新安装的perl和原来的perl mv /usr/bin/perl /usr/bin/perl.5.16.3 # 需要注意新安装的perl目录要根据前面编译的时候指定的目录来确定 ln -s /usr/local/perl/bin/perl /usr/bin/perl # 再次检查 perl -v 安装golang golang的安装配置比较简单，我们从https://golang.org/dl直接下载最新的稳定版本即可。\nwget https://golang.org/dl/go1.15.linux-amd64.tar.gz tar -zxvf go1.15.linux-amd64.tar.gz -C /usr/local # 修改系统默认的go文件 ln -s /usr/local/go/bin/go /usr/bin/go 接下来的go环境变量同学们可以根据自己的实际需求进行配置。对于我个人而言，我直接在/etc/profile中添加下面的配置然后source生效即可。\nexport GOROOT=/usr/local/go export GOBIN=$GOROOT/bin export PATH=$PATH:$GOBIN export GOPATH=/home/gopath 编译安装gcc CentOS7默认的gcc的版本太旧了，编译boringssl的时候会报错，我们手动编译安装新版本的gcc，这里使用的是10.2.0的版本。gcc的版本可以在这里下载http://ftp.gnu.org/gnu/gcc/。\nwget http://ftp.gnu.org/gnu/gcc/gcc-10.2.0/gcc-10.2.0.tar.gz tar -zxvf gcc-10.2.0.tar.gz cd gcc-10.2.0/ # 解压完成后需要下载四个依赖，我们执行脚本即可直接下载，服务器网络不好的同学也可以手动下载 ./contrib/download_prerequisites # 这里需要对c和c++进行支持，为了节省时间禁用掉了交叉编译 ./configure -enable-checking=release -enable-languages=c,c++ -disable-multilib # 接下来这里需要很久 make -j8 make install # 安装完成之后我们需要替换原来的cc文件和c++文件，确保它们的版本都是最新的版本 # 一般来说原来系统的cc文件和c++文件都在/usr/bin/目录下，而我们编译安装的cc文件和c++文件在/usr/local/bin/ cd /usr/bin/ mv /usr/bin/cc /usr/bin/cc.4.8.5 mv /usr/bin/c++ /usr/bin/c++.4.8.5 ln -s /usr/local/bin/gcc /usr/bin/cc ln -s /usr/local/bin/c++ /usr/bin/c++ # 最后检查版本 /usr/bin/cc -v /usr/bin/c++ -v gcc下载的依赖内容如下，有需要的同学自取\n$ ./contrib/download_prerequisites 2020-08-21 21:28:24 URL:http://gcc.gnu.org/pub/gcc/infrastructure/gmp-6.1.0.tar.bz2 [2383840/2383840] -\u0026gt; \u0026#34;./gmp-6.1.0.tar.bz2\u0026#34; [1] 2020-08-21 21:28:26 URL:http://gcc.gnu.org/pub/gcc/infrastructure/mpfr-3.1.4.tar.bz2 [1279284/1279284] -\u0026gt; \u0026#34;./mpfr-3.1.4.tar.bz2\u0026#34; [1] 2020-08-21 21:28:28 URL:http://gcc.gnu.org/pub/gcc/infrastructure/mpc-1.0.3.tar.gz [669925/669925] -\u0026gt; \u0026#34;./mpc-1.0.3.tar.gz\u0026#34; [1] 2020-08-21 21:28:30 URL:http://gcc.gnu.org/pub/gcc/infrastructure/isl-0.18.tar.bz2 [1658291/1658291] -\u0026gt; \u0026#34;./isl-0.18.tar.bz2\u0026#34; [1] gmp-6.1.0.tar.bz2: OK mpfr-3.1.4.tar.bz2: OK mpc-1.0.3.tar.gz: OK isl-0.18.tar.bz2: OK All prerequisites downloaded successfully. 编译安装boringssl 谷歌官方建议我们使用ninja来编译安装boringssl，因此我们需要先安装一个ninja。\nwget https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-linux.zip unzip ninja-linux.zip cp -r ninja /usr/bin/ which ninja # boringssl 需要 libunwind-generic 这个module的支持 yum install libunwind-devel libunwind # 注意这里的git仓库很大，大概在250MB左右，请确保编译安装服务器的网络良好 git clone https://github.com/google/boringssl.git cd boringssl/ # 建立一个专门用于编译的文件夹 mkdir build cd build cmake -GNinja .. ninja 注意在执行cmake这一步的时候正常情况下检测到的gcc文件和perl库版本应该是我们之前编译安装好的新版本，如果不对的话需要再次检查\n# cmake -GNinja .. -- The C compiler identification is GNU 10.2.0 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: /usr/bin/cc - skipped -- Detecting C compile features -- Detecting C compile features - done -- The CXX compiler identification is GNU 10.2.0 -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Found Perl: /usr/bin/perl (found version \u0026#34;5.32.0\u0026#34;) -- Checking for module \u0026#39;libunwind-generic\u0026#39; -- Checking for module \u0026#39;libunwind-generic\u0026#39; -- Found libunwind-generic, version 1.2 -- The ASM compiler identification is GNU -- Found assembler: /usr/bin/cc -- Configuring done -- Generating done -- Build files have been written to: /root/boringssl/build 编译nginx 剩下的nginx的编译安装步骤就和正常的nginx编译安装一致，这里不再赘述，模块可以根据自己的需求进行安装，开启HTTP/3模块需要使用--with-http_v3_module --with-cc-opt=\u0026quot;-I../boringssl-master/include\u0026quot; --with-ld-opt=\u0026quot;-L../boringssl-master/build/ssl -L../boringssl-master/build/crypto\u0026quot;参数。\ncd nginx-quic/ ./auto/configure --prefix=/home/nginx --with-http_ssl_module --with-http_v2_module --with-debug --with-http_v3_module --with-cc-opt=\u0026#34;-I../boringssl/include\u0026#34; --with-ld-opt=\u0026#34;-L../boringssl/build/ssl -L../boringssl/build/crypto\u0026#34; --with-http_v3_module --with-http_quic_module --with-stream_quic_module --build=ngx-quic-tinychen20211103 make make install 安装完成后我们检测nginx的参数：\n[root@tiny-centos7 nginx-quic-quic]# /home/nginx/sbin/nginx -V nginx version: nginx/1.21.3 (ngx-quic-tinychen20211103) built by gcc 11.2.0 (GCC) built with OpenSSL 1.1.1 (compatible; BoringSSL) (running with BoringSSL) TLS SNI support enabled configure arguments: --prefix=/home/nginx --with-http_ssl_module --with-http_v2_module --with-debug --with-http_v3_module --with-cc-opt=-I../boringssl/include --with-ld-opt=\u0026#39;-L../boringssl/build/ssl -L../boringssl/build/crypto\u0026#39; --with-http_v3_module --with-http_quic_module --with-stream_quic_module --build=ngx-quic-tinychen20211103 配置nginx-quic 由于HTTP/3需要使用udp协议端口，请注意开放对应的防火墙 http2监听的是443的tcp端口，而http3监听的是udp端口 nginx中添加了$http3和$quic变量，可以添加到日志中，这样就可以看到是否使用了HTTP/3来进行访问了 如果有多个server_name，在不指定IP的情况下，只需要在任意一个配置了listen 443 http3 quic reuseport;那么其他所有server_name都会开启HTTP3，并且不需要再添加该配置否则会报错（不知道后续会不会把报错去掉），如果需要部分server_name开启HTTP/3，请指定监听IP。 http3增加了http3_max_field_size、http3_max_table_capacity、http3_max_blocked_streams、http3_max_concurrent_pushes、 http3_push、http3_push_preload这六个变量来控制http3的性能 server { listen 443 ssl http2; listen 443 http3 quic reuseport; # UDP listener for QUIC+HTTP/3 server_name tinychen.com www.tinychen.com; add_header Strict-Transport-Security \u0026#34;max-age=63072000; includeSubDomains; preload\u0026#34;; resolver 127.0.0.1; ssl_protocols TLSv1.2 TLSv1.3; ssl_ecdh_curve X25519:P-256:P-384; ssl_ciphers TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:EECDH+CHACHA20:EECDH+AES128; # 该选项用于开启address validation，但是会和0-RTT冲突 #quic_retry on; # 开启 TLS 1.3 0-RTT ssl_early_data on; # 添加 Early-Data 头告知后端, 防止重放攻击 proxy_set_header Early-Data $ssl_early_data; # 参考nginx官方目前支持的http3版本，我们添加对应的header add_header Alt-Svc \u0026#39;h3-27=\u0026#34;:443\u0026#34;; h3-28=\u0026#34;:443\u0026#34;; h3-29=\u0026#34;:443\u0026#34;; ma=86400; quic=\u0026#34;:443\u0026#34;\u0026#39;; ...... } server { listen 443 ssl http2; listen 443 http3 quic reuseport; # UDP listener for QUIC+HTTP/3 server_name tinychen.com; #ssl_stapling on; ssl_stapling_verify on; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:1m; ssl_verify_depth 10; ssl_session_timeout 30m; ssl_protocols TLSv1.2 TLSv1.3; ssl_ecdh_curve X25519:P-256:P-384; ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256; # 添加HSTS配置 add_header Strict-Transport-Security \u0026#34;max-age=63072000; includeSubDomains; preload\u0026#34;; # 添加header告知浏览器支持http3，注意这里不再需要像之前的草稿版本http3那样指定如h3-27 h3-28 h3-29等版本 add_header Alt-Svc \u0026#39;h3=\u0026#34;:443\u0026#34;; ma=86400\u0026#39;; # 开启 TLS 1.3 0-RTT ssl_early_data on; # 添加 Early-Data 头告知后端, 防止重放攻击 proxy_set_header Early-Data $ssl_early_data; ...... location ~ ^/ { root html/; index index.html index.htm; http2_push_preload on; http3_push_preload on; } } 修改配置运行之后应该能看到udp的443端口被nginx进程监听\n[root@tiny-cloud /home/nginx]# netstat -ntulp | grep 443 tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 9730/nginx: master udp 0 0 0.0.0.0:443 0.0.0.0:* 9730/nginx: master 测试 目前的主流浏览器只有Firefox和Chrome支持了HTTP/3协议。并且两者支持程度并不高，Firefox是默认禁用，Chrome是默认开启部分，因此配置起来有点麻烦。\nHTTP/3 CHECK http3check.net网站提供了网站的HTTP/3支持检测\nFirefox开启HTTP/3 对于firefox要求版本在75+，我们直接使用最新版本进行测试：\n在firefox中开启http3比较简单，我们直接在地址栏中输入about:config,然后搜索http3，将network.http.http3.enabled设置为true，接着重启浏览器。\n再次访问tinychen.com就可以看到使用了HTTP/3协议进行访问，在后台的日志中也能看到对应的请求使用了quic和h3-27。（日志中的IP地址已做脱敏处理）。\n0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET / HTTP/3 | - | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /css/main.css HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /lib/font-awesome/css/font-awesome.min.css HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /lib/pace/pace-theme-material.min.css HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /lib/pace/pace.min.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /lib/canvas-nest/canvas-nest.min.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /lib/anime.min.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /lib/velocity/velocity.min.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /lib/velocity/velocity.ui.min.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /js/utils.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /js/motion.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /js/schemes/pisces.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /js/next-boot.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /js/local-search.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.011 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:14:40:47 +0800] | 200 | tinychen.com | GET /lib/font-awesome/fonts/fontawesome-webfont.woff2?v=4.7.0 HTTP/3 | https://tinychen.com/lib/font-awesome/css/font-awesome.min.css | Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 | 0.014 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 Chrome开启HTTP/3 nginx的官网提示说Chrome需要83+版本才支持HTTP/3，实测在部分网站（如google.com）确实可以使用HTTP/3的某些草案版本，但是Chrome目前来说默认还是不支持h3-27协议，我们需要手动开启。对于windows系统而言，我们使用命令行+指定参数的方式开启h3-27的支持：\nPS C:\\Program Files (x86)\\Google\\Chrome\\Application\u0026gt; .\\chrome.exe --enable-quic --quic-version=h3-27 PS C:\\Program Files (x86)\\Google\\Chrome\\Application\u0026gt; .\\chrome.exe --enable-quic --quic-version=h3-27 --origin-to-force-quic-on=tinychen.com:443 上面使用了powershell来启动chrome，为了保证效果，还使用了--origin-to-force-quic-on来强行指定域名和端口。\n本次测试的chrome版本如下：\n测试效果如下：\n除了本次的测试网站tinychen.com外，我们还可以看到谷歌的大部分网站也都开启了HTTP/3。\n同样的我们在后台日志中也能看到对应的访问日志（日志中的IP地址已做脱敏处理）：\n0.0.0.0 | [24/Aug/2020:15:01:26 +0800] | 200 | tinychen.com | GET / HTTP/3 | - | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /css/main.css HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /lib/font-awesome/css/font-awesome.min.css HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /lib/pace/pace-theme-material.min.css HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /lib/pace/pace.min.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /lib/canvas-nest/canvas-nest.min.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /lib/anime.min.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /lib/velocity/velocity.min.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /lib/velocity/velocity.ui.min.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /js/utils.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /js/motion.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /js/schemes/pisces.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /js/next-boot.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /js/local-search.js HTTP/3 | https://tinychen.com/ | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.000 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 0.0.0.0 | [24/Aug/2020:15:01:27 +0800] | 200 | tinychen.com | GET /lib/font-awesome/fonts/fontawesome-webfont.woff2?v=4.7.0 HTTP/3 | https://tinychen.com/lib/font-awesome/css/font-awesome.min.css | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 | 0.015 | TLSv1.3 | TLS_AES_128_GCM_SHA256 | quic | h3-27 edge开启HTTP/3 2021年11月3日后续更新，使用Microsoft EdgeVersion 95.0.1020.40 (Official build) (64-bit)进行测试发现默认已经支持http3。\n","permalink":"http://localhost:9011/20200821-nginx-quic-compile-to-implement-http3/","summary":"\u003cp\u003e本文主要介绍使用nginx-quic和boringssl项目来对服务器进行升级支持HTTP/3协议。\u003c/p\u003e","title":"使用nginx-quic支持HTTP/3"},{"content":"本文主要介绍了windows更新引起的wsl access is denied的问题解决方案。\n这几天使用wsl的时候突然出现了拒绝访问的情况，只有在使用管理员权限打开powershell然后再打开wsl才能顺利切换到wsl，上网查询了一下发现是windows更新的一个补丁（KB4565503）引起的。\n再查了一下更新记录发现确实最近几天安装了这个系统更新。然后果断将其卸载，然后关闭系统更新，接着重启，之后问题解决。\n检测之后已经能够正常工作。\n","permalink":"http://localhost:9011/20200718-wsl-access-is-denied/","summary":"\u003cp\u003e本文主要介绍了windows更新引起的wsl access is denied的问题解决方案。\u003c/p\u003e","title":"wsl access is denied"},{"content":"本文主要记录了一些常见的HTTP状态码，内容参考维基百科。\n标准扩展码 1xx Informational 信息化 100 Continue 继续\n101 Switching Protocols 交换协议\n102 Processing 处理\n2xx Success 成功 200 OK\n201 Created 创建\n202 Accepted 已接受\n203 Non-Authoritative Information 非授权信息\n204 No Content 无内容\n205 Reset Content 重置内容\n206 Partial Content 部分内容\n207 Multi-Status 多状态\n208 Already Reported 已报告\n226 IMIM Used 使用的\n3xx Redirection 重定向 300 Multiple Choices 多种选择\n301 Moved Permanently 永久移动\n302 Found 发现\n303 See Other 查看其它\n304 Not Modified 未修改\n305 Use Proxy 使用代理\n306 Switch Proxy 开关代理\n307 Temporary Redirect 临时重定向\n308 Permanent Redirect 永久重定向\n4xx Client Error 客户端错误 400 Bad Request 错误的请求\n401 Unauthorized 未授权\n402 Payment Required 需要付费\n403Forbidden 拒绝访问\n404 Not Found 未找到\n405 Method Not Allowed 不允许的方法\n406 Not Acceptable 不可接受\n407 Proxy Authentication Required 代理服务器需要身份验证\n408 Request Timeout 请求超时\n409 Conflict 冲突\n410 Gone 完成\n411 Length Required 需要长度\n412 Precondition Failed 前提条件失败\n413 Payload Too Large 负载过大\n414 URI Too Long 太长\n415 Unsupported Media Type 不支持的媒体类型\n416 Range Not Satisfiable 的范围不合适\n417 Expectation Failed 预期失败\n418 I\u0026rsquo;m a teapot 我是一个茶壶\n421 Misdirected Request 误导请求\n422 Unprocessable Entity 无法处理的实体\n423 Locked 锁定\n424 Failed Dependency 失败的依赖\n426 Upgrade Required 升级所需\n428 Precondition Required 所需的先决条件\n429 Too Many Requests 太多的请求\n431 Request Header Fields Too Large 请求头字段太大\n451 Unavailable For Legal Reasons 不可出于法律原因\n5xx Server Error 服务器错误 500 Internal Server Error 内部服务器错误\n501 Not Implemented 未执行\n502 Bad Gateway 错误的网关\n503 Service Unavailable 服务不可用\n504 Gateway Timeout 网关超时\n505 HTTP Version Not Supported 不支持HTTP版本\n506 Variant Also Negotiates 变体也进行协商\n507 Insufficient Storage 存储空间不足\n508 Loop Detected 检测到循环\n510 Not Extended 不延长\n511 Network Authentication Required 网络需要身份验证\n非官方扩展码 103 Checkpoint 检查点 420 Method Failure (Spring Framework) 故障的方法（Spring框架） 420 Enhance Your Calm (Twitter) 增强您的平静（微博） 450 Blocked by Windows Parental Controls (Microsoft) 被Windows阻止家长控制（微软） 498 Invalid Token (Esri) 无效的令牌（ESRI的） 499 Token Required (Esri) 令牌必需（ESRI的） 499 Request has been forbidden by antivirus 请求已被禁止反病毒 509 Bandwidth Limit Exceeded (Apache Web Server/cPanel) 超出带宽限制（Apache的Web服务器/的cPanel） 530 Site is frozen 网站被冻结 互联网信息服务扩展状态码 440 Login Timeout 登录超时\n449 Retry With 重新发送带\n451 Redirect 重定向\nNGINX 扩展状态码 444 No Response 没有响应\n495 SSL Certificate Error 证书错误\n496 SSL Certificate Required证书要求\n497 HTTP Request Sent to HTTPS Port 发送到HTTPS端口请求\n499 Client Closed Request 客户端请求关闭\n","permalink":"http://localhost:9011/20200717-http-code-introduction/","summary":"\u003cp\u003e本文主要记录了一些常见的HTTP状态码，内容参考\u003ca href=\"https://en.wikipedia.org/wiki/List_of_HTTP_status_codes\"\u003e维基百科\u003c/a\u003e。\u003c/p\u003e","title":"常见的HTTP状态码"},{"content":"本文主要介绍使用正则表达式匹配ipv4地址。\n粗犷匹配 比较粗犷的匹配方法，直接匹配四组使用了.进行分隔的数字\n\\d+\\.{3}\\d+ 但是这样容易把不是ip的也匹配进去，比如说8888.888.88.8这样的也是符合规则的，所以想要尽可能的准确，我们就要进行精细的匹配。\n精准匹配所有ip 首先我们要确定ipv4地址的范围是0.0.0.0到255.255.255.255。然后就使用正则表达式逐个字进行匹配。\n首先要匹配0-255这256个数字，由于正则表达式在这里不能直接使用数值大小进行匹配，并且需要尽可能地精确控制数值范围，所以我们将其分为0-9、10-99、100-199、200-249、250-255一共五个部分：\n?:\t# 非获取匹配，只匹配但是不获取 (?:1[0-9][0-9]\\.) # 100-199 (?:2[0-4][0-9]\\.) # 200-249 (?:25[0-5]\\.) # 250-255 (?:[1-9][0-9]\\.) # 10-99 (?:[0-9]\\.) # 0-9 注意这五个分组都是或|关系,前面三个部分都是0-255加上一个点.，最后的是没有点的.，所以前面的执行三次匹配，最后再加上没有点.的一次，正好就能匹配所有的IP地址\n(?:(?:1[0-9][0-9]\\.)|(?:2[0-4][0-9]\\.)|(?:25[0-5]\\.)|(?:[1-9][0-9]\\.)|(?:[0-9]\\.)){3}(?:(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5])|(?:[1-9][0-9])|(?:[0-9])) 匹配局域网IP地址 局域网ip地址分为三个大的网段，分别是10.x.x.x、172.16.0.0—172.31.255.254和192.168.x.x，具体匹配思路和上面一样\nx.x.x 10(?:(?:\\.1[0-9][0-9])|(?:\\.2[0-4][0-9])|(?:\\.25[0-5])|(?:\\.[1-9][0-9])|(?:\\.[0-9])){3} 0—172.31.255.254 172(?:\\.(?:1[6-9])|(?:2[0-9])|(?:3[0-1]))(?:(?:\\.1[0-9][0-9])|(?:\\.2[0-4][0-9])|(?:\\.25[0-5])|(?:\\.[1-9][0-9])|(?:\\.[0-9])){2} x.x 192\\.168(?:(?:\\.1[0-9][0-9])|(?:\\.2[0-4][0-9])|(?:\\.25[0-5])|(?:\\.[1-9][0-9])|(?:\\.[0-9])){2} 匹配所有局域网IP地址 (?:(?:10(?:(?:\\.1[0-9][0-9])|(?:\\.2[0-4][0-9])|(?:\\.25[0-5])|(?:\\.[1-9][0-9])|(?:\\.[0-9])))|(?:172(?:\\.(?:1[6-9])|(?:2[0-9])|(?:3[0-1])))|(?:192\\.168))(?:(?:\\.1[0-9][0-9])|(?:\\.2[0-4][0-9])|(?:\\.25[0-5])|(?:\\.[1-9][0-9])|(?:\\.[0-9])){2} ","permalink":"http://localhost:9011/20200714-ipv4-address-re-match/","summary":"\u003cp\u003e本文主要介绍使用正则表达式匹配ipv4地址。\u003c/p\u003e","title":"匹配ipv4地址的正则表达式"},{"content":"本文主要介绍使用gpg来对下载的文件进行完整性校验\n以在nginx官网下载nginx为例，我们可以获取到nginx的压缩包和一个对应的asc格式的gpg签名文件。\n[root@TINY-DESKTOP /mnt/c/Users/tinychen/Downloads/http2]# ll nginx* -rwxrwxrwx 1 root root 1047223 Jul 13 13:11 nginx-1.19.1.tar.gz -rwxrwxrwx 1 root root 455 Jul 13 13:52 nginx-1.19.1.tar.gz.asc 使用gpg命令需要先安装gnupg工具\nsudo yum install gnupg 一般来说是使用gpg --verify来校验文件，一般来说可能会出现没有公钥的情况，这个时候可以根据显示的RSA key来导入公钥，然后再进行验证即可。\n# gpg --verify gpg签名文件 待校验文件 # gpg --receive-keys RSA_key ","permalink":"http://localhost:9011/20200713-gpg-check-file/","summary":"\u003cp\u003e本文主要介绍使用gpg来对下载的文件进行完整性校验\u003c/p\u003e","title":"使用gpg来对下载的文件进行校验"},{"content":"本文主要介绍在bash中开启显示完整路径。\n只需要修改对应的环境变量，然后重新登录即可实现：\nsudo echo \u0026#34;export PS1=\u0026#39;[\\u@\\h \\$PWD]# \u0026#39;\u0026#34; \u0026gt;\u0026gt; /etc/profile \u0026#39;\\[\\e]2;\\u@\\H:\\w\\a\\]\\n($LOGNAME@$HOSTNAME) \\t\\n[$PWD]: \u0026#39; # 注意在$符号之后一般会留有一个空格，主要是为了输入命令的时候可以和前面的路径信息隔离开来 \\u 显示当前用户账号 \\v BASH的版本信息 \\H 完整的主机名称。例如：hostname为aliyun.alibaba.clound，则这个名称就是aliyun.alibaba.clound \\h 仅取主机的第一个名字，例如：hostname为aliyun.alibaba.clound，则这个名称就是aliyun \\W 只显示当前路径最后一个目录 \\w 显示当前绝对路径（当前用户目录会以 ~代替） \\t 显示时间为24小时格式,如：14:19:34 \\T 显示时间为12小时格式,如：02:19:34 \\A 显示时间为24小时格式但是不带秒,如：14:19 \\d 代表日期，格式为weekday month date，例如：\u0026#34;Mon Aug1\u0026#34; $PWD 显示当前全路径 \\# 显示命令行提示符号，一般使用\u0026#39;$\u0026#39;或者\u0026#39;#\u0026#39; sudo echo \u0026#34;export PS1=\u0026#39;[\\u@\\h \\w]# \u0026#39;\u0026#34; \u0026gt;\u0026gt; /etc/profile # 如果想要在用户家目录的时候显示为~则可以将$PWD换为\\w $ hostname aliyun.alibaba.clound $ tail -1 /etc/profile export PS1=\u0026#39;[\\u@\\h $PWD]# \u0026#39; [root@aliyun.alibaba.clound /root]# hostname aliyun.alibaba.clound [root@aliyun.alibaba.clound /root]# tail -1 /etc/profile export PS1=\u0026#39;[\\u@\\H $PWD]# \u0026#39; ","permalink":"http://localhost:9011/20200701-bash-show-pwd/","summary":"\u003cp\u003e本文主要介绍在bash中开启显示完整路径。\u003c/p\u003e","title":"在bash中开启显示完整路径"},{"content":"本文主要介绍在RHEL/CentOS/6/7/8中如何修改主机的hostname。\nCentOS6/RHEL6 对于6系的红帽Linux，修改hostname较为麻烦，如果只是需要临时修改hostname，只需要使用hostname命令即可：\nhostname your-new-temp-hostname 如果需要永久修改hostname，则需要修改两个地方：\n首先是/etc/hosts文件中，需要添加IP和hostname的映射关系，如：\n127.0.0.1\tyour-new-perm-hostname 然后在/etc/sysconfig/network文件中修改对应的HOSTNAME=参数为新的hostname\nHOSTNAME=your-new-perm-hostname 注意两处地方需要同时修改，否则会报错，最后需要重启系统才会生效。\nCentOS7、8/RHEL7、8 对于7系及之后的红帽Linux，只需要直接修改/etc/hostname文件然后重启就可以完成永久修改\n如果不想重启，可以使用新的hostnamectl工具来进行永久修改\nhostnamectl set-hostname your-new-perm-hostname 使用脚本自动执行 #!/bin/bash # Determine whether executor is root or not if [ $(whoami) != \u0026#34;root\u0026#34; ]; then echo \u0026#34;please exec this shell script with sudo or in root mode\u0026#34; exit 1 fi # Determine if there is a new hostname if [ -z \u0026#34;$1\u0026#34; ]; then echo \u0026#34;please input the new hostname\u0026#34; exit 1 fi # get the release version version=$(cat /etc/redhat-release | awk -F \u0026#39;release\u0026#39; \u0026#39;{print $2}\u0026#39; | cut -c -2) if [ $version -ne 6 ] \u0026amp;\u0026amp; [ $version -ne 7 ] \u0026amp;\u0026amp; [ $version -ne 8 ]; then echo \u0026#34;This script do not suit your system, Bye!\u0026#34; exit 1 fi echo \u0026#34;your hostname will be change to $1\u0026#34; if [ $version == 6 ]; then # get hostname from /etc/sysconfig/network hostnameCurrent=$(cat /etc/sysconfig/network | grep HOSTNAME | awk -F \u0026#39;=\u0026#39; \u0026#39;{ print $NF }\u0026#39;) # echo \u0026#34;your hostname now is $hostnameCurrent\u0026#34; # modify the tmp hostname hostname $1 # Determine if there is a field about $hostnameCurrent in /etc/hosts # if yes, use awk to replace it # if no, echo a new line aboout new hostname cat /etc/hosts | grep $hostnameCurrent if [ $? -ne 0 ]; then echo \u0026#34;127.0.0.1 $1\u0026#34; \u0026gt;\u0026gt;/etc/hosts else sed -i \u0026#34;s/$hostnameCurrent/$1/g\u0026#34; /etc/hosts fi # modify the hostname in /etc/sysconfig/network sed -i \u0026#34;s/$hostnameCurrent/$1/g\u0026#34; /etc/sysconfig/network else if [ $version == 7 ] || [ $version == 8 ]; then hostnamectl set-hostname $1 fi fi echo \u0026#34;Hostname modification is done !\u0026#34; echo \u0026#34;A restart might be better for apply the change\u0026#34; 将上述的代码保存为脚本再加上需要修改的hostname直接执行就可以了。\n","permalink":"http://localhost:9011/20200616-centos-modify-hostname/","summary":"\u003cp\u003e本文主要介绍在RHEL/CentOS/6/7/8中如何修改主机的hostname。\u003c/p\u003e","title":"RHEL/CentOS修改hostname"},{"content":"本文主要介绍了HTTP2相对于HTTP1.1的一些改进和新特性。\nHTTP1.1的不足 HTTP协议采用“request-response”模式，当使用普通模式，即非KeepAlive模式时，每个请求/应答客户和服务器都要新建一个连接，完成之后立即断开连接（HTTP协议为无连接的协议）。对于一个复杂的页面，一般都会有多个资源需要获取。如果是每个资源都单独建立一个TCP连接去获取，每次都需要进行三次握手建立连接来通信，这是十分耗费资源的。\n随后出现了Keep-Alive，Keep-Alive解决的核心问题是一定时间内，同一域名多次请求数据，只建立一次HTTP请求，其他请求可复用每一次建立的连接通道，以达到提高请求效率的问题。这里面所说的一定时间一般是可以自行配置的。当使用Keep-Alive模式（又称持久连接、连接重用）时，Keep-Alive功能使客户端到服务器端的连接持续有效，当出现对服务器的后继请求时，Keep-Alive功能避免了建立或者重新建立连接。\n在HTTP1.1中是默认开启了Keep-Alive，虽然解决了多次连接的问题，但是依然有两个比较严重的问题：\n串行传输：在单通道传输的前提下，假设要传输10个文件，那么只能依次逐个传输，传输完第一个再传输第二个，以此类推； 连接数问题：HTTP/1.1 虽然默认开启keep-alive可以复用一部分连接，但域名分片等情况下仍然需要建立多个connection，耗费资源，给服务器带来性能压力。 作为 HTTP/1.x 的连接，请求是序列化的，哪怕本来是无序的，在没有足够庞大可用的带宽时，也无从优化。一个解决方案是，浏览器为每个域名建立多个连接，以实现并发请求。曾经默认的连接数量为 2 到 3 个，现在比较常用的并发连接数已经增加到 6 条。如果尝试大于这个数字，就有触发服务器 DoS 保护的风险。如果服务器端想要更快速的响应网站或应用程序的应答，它可以迫使客户端建立更多的连接。例如，不要在同一个域名下获取所有资源，假设有个域名是 www.example.com，我们可以把它拆分成好几个域名：www1.example.com、www2.example.com、www3.example.com。所有这些域名都指向同一台服务器，浏览器会同时为每个域名建立 6 条连接(在我们这个例子中，连接数会达到 18 条)。这一技术被称作域名分片。\n**HTTP1.1中的数据传输是基于文本来进行传输的，这就需要保证在传输的过程中必须是按照文本原有的顺序进行传输。**按照顺序传输的一大弊端就是没办法进行并行传输，例如一句Hello就只能按照字母顺序逐个传输，如果并行传输，字母到达的先后顺序是不一定和在文本中的顺序一样的，而字母本身又没有顺序标号，就没有办法对其进行排序，很有可能就会导致内容错乱，Hello就可能变成leloH。\nHTTP2的特性 二进制分帧 http2采用二进制格式传输数据，而非 HTTP 1.x 的文本格式，二进制协议解析起来更高效。 HTTP / 1 的请求和响应报文，都是由起始行，首部和实体正文（可选）组成，各部分之间以文本换行符分隔。HTTP/2 将请求和响应数据分割为更小的帧，并且它们采用二进制编码。HTTP/2 中，同域名下所有通信都在单个连接上完成，该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送，而消息又由一个或多个帧组成。多个帧之间可以乱序发送，根据帧首部的流标识可以重新组装。\n流：流是连接中的一个虚拟信道，可以承载双向的消息；每个流都有一个唯一的整数标识符（1、2…N）； 消息：是指逻辑上的 HTTP 消息，比如请求、响应等，由一或多个帧组成。 帧：HTTP 2.0 通信的最小单位，每个帧包含帧首部，至少也会标识出当前帧所属的流，承载着特定类型的数据，如 HTTP 首部、负荷，等等 HTTP/2引入的二进制数据帧和流的概念，其中帧对数据进行顺序标识，这样浏览器收到数据之后，就可以按照序列对数据进行合并，而不会出现合并后数据错乱的情况。同样是因为有了序列，服务器就可以并行的传输数据，这就是流所做的事情。\n我们可以从下面的两张图来对其进行了解：\n多路复用 （multiplexing） 在HTTP/2 协议中， Connection 和 Keep-Alive 是被忽略的；主要使用了多路复用来进行连接管理。在 HTTP/2 中，有了二进制分帧之后，HTTP /2 不再依赖 TCP 连接去实现多流并行了，在 HTTP/2 中\n同域名下所有通信都在单个连接上完成 单个连接可以承载任意数量的双向数据流 数据流以消息的形式发送，而消息又由一个或多个帧组成，多个帧之间可以乱序发送，因为根据帧首部的流标识可以重新组装。 这一特性，使性能有了极大提升：\n同个域名只需要占用一个 TCP 连接，使用一个连接并行发送多个请求和响应,消除了因多个 TCP 连接而带来的延时和内存消耗 并行交错地发送多个请求和响应，两两之间互不影响 在 HTTP/2 中，每个请求都可以带一个 31bit 的优先值，0 表示最高优先级， 数值越大优先级越低。有了这个优先值，客户端和服务器就可以在处理不同的流时采取不同的策略，以最优的方式发送流、消息和帧。 使用浏览器的开发者模式查看相关资源的加载情况，可以看到对应的http2的资源在加载的时候会使用多路复用，客户端可以同时请求多个资源；而下方的http1.1则在请求资源的时候出现了等待的情况。\n首部压缩（header compression） 在同一个HTTP页面中，许多资源的Header是高度相似的，但是在HTTP2之前都是不会对其进行压缩的，这使得在多次传输中白白浪费了资源来进行重复无谓的操作。\n在HTTP中，首部字段是一个键值对，所有的首部字段组成首部字段列表。在HTTP/1.x中，首部字段都被表示为字符串，一行一行的首部字段字符串组成首部字段列表。而在HTTP/2的首部压缩HPACK算法中，则有着不同的表示方法。\nHPACK算法的具体细节可以参考RFC7541\nHPACK算法表示的对象，主要有原始数据类型的整型值和字符串，头部字段，以及头部字段列表。\n头部压缩需要在支持 HTTP/2 的浏览器和服务端之间：\n**维护一份相同的静态字典（Static Table），包含常见的头部名称，以及特别常见的头部名称与值的组合。**其主要作用有两个：对于完全匹配的头部键值对，例如 :method: GET，可以直接使用一个字符表示；对于头部名称可以匹配的键值对，例如 cookie: xxxxxxx，可以将名称cookie使用一个字符表示。 **维护一份相同的动态字典（Dynamic Table），可以动态地添加内容。**对于cookie: xxxxxxx 这样的内容，可以将其添加到动态字典中，之后整个键值对只使用字段中替代的字符来表示即可。需要注意的是，动态字典上下文有关，需要为每个 HTTP/2 连接维护不同的字典。 支持基于静态哈夫曼码表的哈夫曼编码（Huffman Coding）； 使用字典可以极大地提升压缩效果，其中静态字典在首次请求中就可以使用。对于静态、动态字典中不存在的内容，还可以使用哈夫曼编码来减小体积。HTTP/2 使用了一份静态哈夫曼码表，也需要内置在客户端和服务端之中。\n下面的表格截取了部分静态字典的内容：\n服务器推送（server push） Server Push 即服务端能通过 push 的方式将客户端需要的内容预先推送过去，也叫“cache push”。这个新特性是有些颠覆了传统的HTTP模式的，因为传统的HTTP模式是客户端请求（request）资源，然后服务器才会返回（response）对应的资源，而Server Push的方式是服务器主动将资源推送到客户端。\n可以想象以下情况，某些资源客户端是一定会请求的，这时就可以采取服务端 push 的技术，提前给客户端推送必要的资源，这样就可以相对减少一点延迟时间。例如服务端可以主动把 JS 和 CSS 文件推送给客户端，而不需要客户端解析 HTML 时再发送这些请求。\n服务端可以主动推送，客户端也有权利选择是否接收。如果服务端推送的资源已经被浏览器缓存过，浏览器可以通过发送 RST_STREAM 帧来拒收。实际上服务器应该避免推送已经被客户端缓存了的资源，一般可以通过判断请求中的缓存相关字段来确定是否需要推送。主动推送也遵守同源策略，换句话说，服务器不能随便将第三方资源推送给客户端，而必须是经过双方确认才行。\n还可以使用server push可以提前将需要加载的资源推送到客户端，这样还可以有效提升页面的加载速度：\n从上面两张图片的对比中我们可以看到一个大小为50KB左右的main.css文件在使用了服务器推送功能之后的加载时间从原来的73.98ms变为19.54ms。\nHTTP2协商机制 由于目前并不是所有的客户端和服务器都支持HTTP2，因此在建立HTTP连接的过程中必然会存在一个协商的过程，具体的协商流程如下：\nHTTP2的不足 HTTP2在我个人看来在协议的设计方面应该已经是发挥了目前的全部性能，除非有什么惊人的压缩算法之类的出现，否则很难再有极大的改进（实际上HTTP2对比HTTP1.1在实际应用上的性能提升也并不算特别大）。但是由于HTTP协议的传输层还是使用的TCP，这就导致了可能会出现TCP的一些问题。\n例如在多路复用的情况下， 一般来说同一域名下只需要使用一个 TCP 连接。但当这个连接中出现了丢包的情况，TCP的特性会需要重新传输，也就是需要把所有的请求都重新传输一次，那就会导致 HTTP/2 的表现情况反倒不如 HTTP/1.1 了。因为在出现丢包的情况下，整个 TCP 都要开始等待重传，也就导致了后面的所有数据都被阻塞了。但是对于 HTTP/1.1 来说，可以开启多个 TCP 连接，出现这种情况反到只会影响其中一个连接，剩余的 TCP 连接还可以正常传输数据。这种情况和队头堵塞情况有些类似，但是HTTP2的这种情况一般会出现在网络较差的情况下，也就是说HTTP2会有一种快的更快，慢的更慢的倾向。\nHead-Of-Line Blocking（HOLB）：导致带宽无法被充分利用，以及后续健康请求被阻塞。HOLB 是指一系列包（package）因为第一个包被阻塞；当页面中需要请求很多资源的时候，HOLB（队头阻塞）会导致在达到最大请求数量时，剩余的资源需要等待其他资源请求完成后才能发起请求。\n","permalink":"http://localhost:9011/20200608-http2-introduction/","summary":"\u003cp\u003e本文主要介绍了HTTP2相对于HTTP1.1的一些改进和新特性。\u003c/p\u003e","title":"HTTP2的特性解析"},{"content":"本文主要对对称加密和非对称加密的原理以及过程进行分析，同时还会简单介绍一下TLS/SSL的一些相关内容，并且对比TLSv1.2和TLSv1.3的不同。\nSSL和TLS的历史 其实早期的互联网协议基本都是不加密进行传输的，如HTTP、FTP、telnet.等协议的\n传输层安全性协议（英语：Transport Layer Security，缩写：TLS）及其前身安全套接层（英语：Secure Sockets Layer，缩写：SSL）的历史进程如下表所示：\n协议 发布时间 状态 SSL 1.0 未公布 未公布 SSL 2.0 1995年 已于2011年弃用 SSL 3.0 1996年 已于2015年弃用 TLS 1.0 1999年 已于2020年弃用 TLS 1.1 2006年 已于2020年弃用 TLS 1.2 2008年 TLS 1.3 2018年 TLS 1.0 于1999年发布为RFC 2246 TLS 1.1 于2006年作为RFC 4346发布 TLS 1.2 于2008年发布为RFC 5246 TLS 1.3 于2018年8月作为建议标准在RFC 8446发布 SSL（Secure Sockets Layer）是网景公司（Netscape）设计的主要用于Web的安全传输协议，这种协议在Web上获得了广泛的应用。SSL1.0没有被公开发布过，1995 网景公司发布SSL2.0，但是由于SSL2.0有严重的安全漏洞，因此1996年又发布了SSL3.0。\n但是在2014年10月，Google发布在SSL 3.0中发现设计缺陷，建议禁用此一协议。攻击者可以向TLS发送虚假错误提示，然后将安全连接强行降级到过时且不安全的SSL 3.0，然后就可以利用其中的设计漏洞窃取敏感信息。Google在自己公司相关产品中陆续禁止回溯兼容，强制使用TLS协议。Mozilla也在11月25日发布的Firefox 34中彻底禁用了SSL 3.0。微软同样发出了安全通告。**这就是SSL3.0在2015年被弃用的原因。**但是由于SSL存在的时间太长了，人们以及习惯用SSL这个名词来指代加密的安全传输协议，因此我们要知道现在说的SSL绝大多数都是说的TLS加密。\n众所周知当年的浏览器大战微软战胜了网景，而后网景将SSL协议的管理权交给了标准化组织IETF（Internet Engineering Task Force）。1999年，IETF在SSL3.0的基础上进行发布了TLS协议的1.0版本，需要注意的是TLS1.0版本和SSL3.0版本的区别很小，并且TLS1.0是可以降级到SSL3.0来使用的，之所以换名字主要是为了避免一些版权和法律的问题。这也就导致了后来谷歌禁止TLS回溯兼容SSL协议从而避免安全事故的发送。注意其实所有TLS版本在2011年3月发布的RFC 6176中删除了对SSL2.0的兼容，这样TLS会话将永远无法协商使用的SSL 2.0以避免安全问题。但是还是可以降级协商到SSL3.0的。\nRFC 6176的原文摘要如下： This document requires that when Transport Layer Security (TLS) clients and servers establish connections, they never negotiate the use of Secure Sockets Layer (SSL) version 2.0. This document updates the backward compatibility sections found in the Transport Layer Security (TLS). TLS 1.1在 RFC 4346 中定义，于2006年4月发表。TLS 1.2在 RFC 5246 中定义，于2008年8月发表。TLS 1.3在 RFC 8446 中定义，于2018年8月发表。实际上现代的浏览器已经基本不使用 SSL，使用的都是 TLS，而目前主流使用的加密协议版本是TLS1.2和TLS1.3。\nSSL/TLS属于哪一层 这个问题十分有意思，从前面的发展历史中我们不难知道，TLS可以视为是SSL的高级版本（主要体现在更加安全上），而从TLS的名字（传输层安全性协议）就会觉得它应该是传输层的协议，当然这可能就望文生义了，实际上在网上有不少的文章在讨论TLS/SSL属于应用层还是传输层，实际上的情况要更为复杂一些，我们先来搞清楚在不同的网络模型中对于不同层的划分。\n首先我们需要知道一般说的七层协议指的是在OSI模型协议，而在TCP/IP模型中网络被划分为四层，我们直接来看下面的示意图：\n原始版本的OSI模型划分得太细，TCP/IP模型又划分得太粗，于是人们把两者结合，将OSI模型中的5、6、7三层统一为应用层，就得到了一个升级版的五层网络模型。\n首先我们对SSL/TLS的作用进行分析：SSL/TLS最初是为了给HTTP协议加密使用，也就是HTTPS协议，通常来说我们可以认为HTTP+SSL/TLS=HTTPS，而实际上现在我们的很多其他应用层协议都可以使用SSL/TLS，比如SSH、FTPS、POP3S、IMAPS等等。再以HTTPS为例，一个HTTPS建立连接需要经过TCP握手建立连接这一步骤的，也就是说HTTPS还是基于TCP的，而TCP属于传输层这是毫无争论的。也就是说从划分最细的OSI七层参考模型来看，SSL/TLS应该是在传输层和应用层之间。\n实际上从SSL/TLS的功能来分析：\nSSL Record Protocol（SSL记录协议），它建立在可靠的传输协议（如TCP）之上，SSL/TLS使用了双向字节流传输（全双工），为高层协议提供数据封装、压缩、加密等基本功能的支持，从功能上看这应该是OSI的L6（表示层） SSL Handshake Protocol（SSL握手协议）：它建立在SSL记录协议之上，用于在实际的数据传输开始前，通讯双方进行身份认证、协商加密算法、交换加密密钥等，从功能上看这应该是OSI的L5（会话层） 首先，SSL协议分为SSL握手协议和SSL记录协议。记录协议工作在TCP之上，握手协议工作在记录协议之上。\n而与之相对应的七层结构中，传输层之上是会话层，会话层之上是表示层。\n一、会话层负责建立和位置会话，很明显SSL握手协议就是干这个事的。\n二、表示层对统一传输方式，并对数据进行加密之类的前置处理。这个应该是SSL记录协议要做的事情。\n所以如果真要说对应关系，应该是SSL握手协议对应会话层， SSL记录协议对应表示层，但是这又与SSL握手协议在SSL记录协议之上相违背。\n那么我们就可以得出结论：**OSI七层模型并不适用于SSL/TLS协议，这个人为设计的理论参考模型并不能完美地套用在每一个网络协议上，**可能这也是OSI模型被弃用的原因之一吧。\n那么对应五层的网络模型呢？由于OSI模型中的L5、L6、L7都合并成了应用层，所以SSL/TLS应该是属于传输层和应用层了。\n对称加密和非对称加密 讲到加密，必然需要理解加密算法，而加密算法一般来说可以分为对称加密和非对称加密两种。\n这里的对称和非对称是针对加密和解密这两个操作而言的，一般来说是消息发送方发送消息时需要加密，消息接收方在接收消息后需要进行解密。如果加密和解密用的密钥是相同的，则是对称加密；如果不同则是非对称加密。\n对称加密 **对称加密算法的特点是算法公开、计算量小、加密速度快、加密效率高。**常见的对称加密算法有AES、DES等。\n对称加密最大的问题在于密钥的传输：因为如果信息的发送方和接收方是通过网络来进行通信的，而在网络中使用明文通信是不安全的，想要安全通信必须使用密钥加密，同时要保证密钥只有通信双方知道，但是在传输密钥之前双方并没有一个安全可靠双方都知道的密钥。如果最开始的密钥传输过程使用明文，就可能会被别有用心的人截获密钥，之后的加密就毫无意义。最保险的方法就是线下传输密钥然后再线上通信，可以参考谍战片中的特务舍生取义护送密码本，但是这在互联网时代显然不靠谱。\n非对称加密 这时候非对称加密就出现了，非对称加密最大的特点就是把密钥进行分离，将其分成公钥和私钥两个部分，常见的非对称加密算法主要有 RSA 、 DSA 、ECC等。\n顾名思义，公钥是可以用在互联网中随意传播的，而私钥则是需要自己小心保存避免泄露的。消息的发送方只需要知道消息接受方的公钥，即可将明文通过公钥加密然后通过网络传输给消息接收方。消息接收方收到密文后，通过非对称加密算法，使用自己的私钥进行解密，即可获取消息内容。\n这里面有几个点需要额外关注一下：\n公钥是所有人都可以获取的，因此想要给接收方发送消息只需要获取公钥即可，所以公钥可以用明文直接传输，因为即使是在传输过程中泄露了公钥，由于解密只能使用私钥，因此整个数据传输也还是安全的 在通信过程中一般两边都涉及到消息的发送和接收，因此在通信过程中一般会有两套密钥对 可以根据私钥生成公钥，反之不行 消息发送方是无法对发送出去的密文解密的，它只能读取自己保存的明文来了解之前发送过的消息 数字证书 **在通信的过程中，我们使用公钥加密，私钥解密，因为私钥是自己才有的，而传输的信息是不安全的可能被别人截获的，但是只要对其进行加密，然后保证自己才能解密，就可以认为传输信息是安全的。**这就好比使用了一个很安全的保险箱来存放重要资料再快递到别的地方去，只要保证只有自己能够解锁保险箱，那么运输过程中保险箱会被谁接触到都不重要，只要保险箱送到目的地就可以了。\n即便是非对称加密，也存在一个公钥传输的问题。基本上存在着两种方案，一种是直接把公钥放到网上，然后让需要使用的用户去下载，另一种就是在通信传输过程中，由服务器直接发送给客户端。这两种方法都存在一个问题就是无法保证公钥传输的安全性，虽然公钥是可以给任何人知道的，但是在通信过程中使用的公钥必须是通信双方的公钥，否则如果出现中间人劫持了通信并且将公钥替换为中间人自己的公钥，那么中间人就可以获取到通信内容。\n这个时候就需要数字证书了，基于非对称加密公私钥分离的特性，我们就可以对公钥进行单独操作，用于数字证书，也叫数字认证（digital certificate），即相当于现实生活中的签名，用于证实身份。\n数字证书是部署HTTPS认证的网站的必需品，我们在访问一个网站的时候，一般点击浏览器地址栏旁边的小锁就可以看到这时候正在使用的数字证书：\n点击进去就可以看到相关的证书信息。证书中包含着十分多的信息，首先最重要的当然是对应的域名和公钥，其他的还有证书的生效时间，使用的加密算法、签名算法等各种相关信息。\n签发证书的机构被称为 CA（ Certificate Authority），理论上每个人都可以成为CA，因为每个人都可以自己签发证书，但是只有极少数的权威CA颁发的证书才会被承认，这几大权威CA的称为ROOT CA，他们的证书一般都会内置在操作系统中，浏览器默认是信任这些ROOT CA的证书的，而这些ROOT CA下属还有其他的CA，这些下属的CA可以为各种网站颁发证书，根据层层信任的原则，浏览器也会信任这些CA下发的证书，最终就保证了通信中公钥传输的安全。\n早期的证书是需要收费的，但是到了近几年加密通信的需求增加，很多网站的运营者并没有那么多钱来购买证书（证书过期了续费也是要钱的），这时候就出现了以Encryption Everywhere、 Let’s Encrypt等为首的CA开始大量普及免费的数字证书，如今国内的很多云厂商也提供了各种免费的数字证书，从而很好的推动了加密通信的发展。不过这些免费的数字证书在安全性上并没有企业级的收费证书那么高，大多数都只是DV证书，如果对安全性有很高的追求，还是建议购买收费的证书。\n一般来说数字证书可以按照安全程度分为以下三类：\nEV：**EV证书(Extended Validation Certificate)**是一种根据一系列特定标准颁发的X.509电子证书，根据要求，在颁发证书之前，证书颁发机构(CA)必须验证申请者的身份。不同机构根据证书标准发行的扩展验证证书并无太大差异，但是有时候根据一些具体的要求，特定机构发行的证书可以被特定的软件识别 OV：OV证书(Organization Validation SSL)，指需要验证网站所有单位的真实身份的标准型SSL证书，此类证书不仅能够起到网站信息加密的作用，而且能向用户证明网站的真实身份 DV：DV证书(Domain Validation SSL)，指需要验证域名的有效性。该类证书只提供基本的加密保障，不能提供域名所有者的信息 TLS加密的握手过程 TLS本身是一个混合加密系统，也就是说它使用了对称加密和非对称加密两种方式，**首先是使用非对称加密来传输在这次会话过程中生成的用于生成对称加密的密钥（ pre-master key），结合明文传输的随机数和算法生成堆成加密的密钥之后再使用对称加密进行通信。**这样通信的原因是因为非对称加密虽然很安全，但是效率实在是太低了（比对称加密慢几个数量级），因此只用来传输对称加密的密钥，之后就使用效率更高的对称加密来通信。\nTLS支持多种密钥交换算法（key exchange algorithms） 和加密算法（ciphersuites），不同的客户端和服务器之间支持的也各不相同，因此在加密通信之间就需要进行协商，**客户端和服务端需要协商清楚使用何种算法，使用何种加密方式，使用什么密钥等等问题，这一个过程称为握手过程（handshake）。**就好像TCP连接在建立前需要进行三次握手一样，所有的TLS通信在开始之前都需要进行握手（handshake）。当客户端和服务器完成TCP三次握手建立TCP连接之后，就开始进行TLS的握手过程，具体的流程如下：\n首先由客户端发送Client Hello 消息到服务器，消息中主要包含了客户端支持的ciphersuites， TLS 版本信息和客户端随机数。注意此时是明文传输 服务器接收到消息后，返回自己支持的ciphersuites， TLS 版本，自己的数字证书和服务器端生成的随机数。注意此时是明文传输 客户端开始验证数字证书，可能会不断往上追溯 CA、CA 的 CA、CA 的 CA 的 CA，直到一个授信的 CA。验证完证书之后生成一个新的pre-master key，再使用证书中的公钥来对pre-master key进行加密，然后发送给服务器。注意此时是非对称加密传输 服务器接收到客户端发送过来的非对称加密的密文，使用自己的私钥进行解密，获得了pre-master key。注意此时是非对称加密传输 到这里为止，服务器和客户端都有三组数字，**分别是客户端的随机数、服务器的随机数和pre-master key。**其中由于客户端的随机数和服务器的随机数都是使用明文传输，所以这两个数字是有被暴露的风险的，但是由于pre-master key是使用非对称加密传输，十分安全，所以将这三者结合，使用之前协商好的特定的算法就可以生成一个密钥，这个密钥称为shared secert。也就是之后用来对称加密的密钥。 客户端在计算出对称加密的密钥之后，使用该密钥进行对称加密通信，告知服务器之后都使用该密钥进行对称加密。注意此时是对称加密传输 服务器接收到密文后，使用之前计算出的密钥来进行对称解密，解密成功之后，再使用该密钥进行对称加密通信。告知客户端密钥确认无误，可以使用该密钥进行通信。注意此时是对称加密传输 至此，整个TLS的握手过程完整，之后就可以开始对称加密的通信了。 全过程如下图所示：\n在RFC5246文档中我们也可以看到对应的简单图示\n整体流程和上面的基本相同，都是需要进行两个RTT操作。\nTLS1.2的问题 纵观整个SSL/TLS协议的发展史，我们可以发现整个SSL/TLS协议就是不断地填坑的一个过程，不断地对旧版本的协议中的各种漏洞进行修补迭代更新，然后发布新的版本，直到TLSv1.2版本才算是一个不错的可用的加密协议版本。即便如此，对应TLSv1.2来说还是有着太多的历史包袱和兼容性的问题，尽管在功能实现上的漏洞可以通过补丁来进行修补，但是在协议设计之初就存在的问题是没有办法修复的，只能推倒重来，于是就出现了后面的TLSv1.3。这里我们先了解一下TLSv1.2版本中的一些主要的问题：\n安全问题 作为一个提供安全通信的协议，安全问题是首要的也是致命的问题。TLS发展到1.2以来，已经被很多机构和学者曝出有各种各样的安全漏洞，包括密钥交换算法（key exchange algorithms）、加密套件（ciphersuites）和数字签名（digital signatures）各个方面都存在安全问题，很多都是由于历史原因兼容问题而遗留下来的问题。\n还有一些则是设计协议本身就存在的问题如TLS重新协议（renegotiation）可以让心怀不轨的人将高版本的TLS协议重新协商降级到低版本的不安全的协议然后进行攻击。又或者是SNI的不加密问题，TLS1.2及之前的协议都不对SNI进行加密，这也存在了很大的风险。\n性能问题 互联网上一直存在着加密传输对性能有很大损耗的说法，实际上了解了上面的TLSv1.2握手过程之后，我们可以知道加密传输对性能确实有损耗，但是远没有到很多人鼓吹的那么严重的程度。而且在后面也加入了很多诸如OCSP、HSTS等技术来提高其性能表现，但是即便如此，整个TLSv1.2的握手过程也需要2-RTT，也就是在客户端和服务器之间来回两次才能顺利建立TLS传输，这还是在一切都进行顺利的情况下。\nTLS1.3的改进 TLSv1.3是TLS协议更新中变化非常大的一个版本，加入了许多新的特性和性能优化，并且不完全前向兼容，因此也有些人认为应该称为TLSv2.0，不过最后还是命名为TLSv1.3。\n针对TLSv1.2中存在的安全和性能问题，TLSv1.3在设计的时候就放弃了前向兼容性，不再对之前的版本进行兼容，同时禁用了大量不安全的算法，使用了少量安全的算法来设计协议，这样的好处就是可以简化握手过程中的操作，使得握手过程从2-RTT变为1-RTT，同时有效提高安全性和性能。\nTLS1.3和TLS1.2的主要不同 部分新的密码套件（ciphersuite）只能在TLSv1.3中工作，并且TLSv1.3不支持之前在TLSv1.2前用的旧的密码套件ciphersuites。也就是说如果需要使用TLSv1.3就必须要添加新的只能在TLSv1.3中使用的密码套件 新的密码套件（ciphersuites）和之前的密码套件定义不同，并不需要指定对应的证书类型(e.g. RSA, DSA, ECDSA) 或者是密钥交换机制 (e.g. DHE or ECHDE) TLSv1.3不再支持DSA证书 TLS1.3中不再支持重新协商（Renegotiation），即不可能像TLSv1.2之前那样通过重新协商来回退到更早的更不安全的版本 TLS1.3中更多的握手过程都被加密了（Server Hello之后都会进行加密） TLSv1.3支持更多的的消息类型，即对自定义的扩展API和认证传输有更好的扩展性 客户端在TLS握手阶段发送ClientHello数据包的时候需要提供支持的密码套件（ciphersuite）和密钥共享（key_share）从而提高速度，如果client发送的keyshare类型是server不支持，那就不是1-RTT。 sessions会话在TLS握手完成之后才会建立，所以在session和TLS握手之间可能会有空隙（即不是连续的） TLS1.3中的密钥交换算法 TLS 1.3的核心宗旨是简单性。在新版本中，除去了Diffie-Hellman（DH）密钥交换以外的所有密钥交换算法。TLS 1.3还定义了一组经过测试的DH参数，无需与服务器协商参数。由于只有一个密钥交换算法（具有内置参数）和少数支持的密码，因此设置TLS 1.3通道所需的绝对带宽比早期版本要少得多。\n我们来看DH算法交换密钥的步骤。假设客户端和服务器双方需要传递密钥，他们之间可以这么做：\n客户端首选选择一个素数p，例如509，底数g，任选，例如5，随机数a，例如123，然后计算A=g^a mod p，结果是215，然后，客户端发送p＝509，g=5，A=215给服务器； 服务器收到后，也选择一个随机数b，例如，456，然后计算B=g^b mod p，结果是181，服务器再同时计算s=A^b mod p，结果是121； 服务器把计算的B=181发给客户端，客户端计算s＝B^a mod p的余数，计算结果与服务器算出的结果一样，都是121。 所以最终双方协商出的密钥s是121。注意到这个密钥s并没有在网络上传输。而通过网络传输的p，g，A和B是无法推算出s的，因为实际算法选择的素数是非常大的。所以，更确切地说，DH算法是一个密钥协商算法，双方最终协商出一个共同的密钥，而这个密钥不会通过网络传输。\nTLS1.3握手过程 整个流程的目的和TLS 1.2是相似的，TLS握手过程就是为了让双方能够得到一个安全的可用于对称加密的密钥。和之前不一样的就是，无非就是客户端提前把所有的公钥计算了一遍，发给server，server再挑选。\nwireshark抓包 使用wireshark对TLSv1.3握手过程进行抓包，未解密的情况如下图。我们可以看到在Server Hello阶段之后的数据就已经被加密了，无法查看具体的数据情况，均显示为Application Data\n对其进行解密操作之后就可以看到其中的数据情况，其中的Encrypted Extensions就是对SNI部分进行了加密。\n解密前的TLSv1.2握手过程，可以看到直到Change Cipher Spec阶段都是没有进行加密的。\n解密后的TLSv1.2握手过程，我们可以看到被加密的部分也就是Encrypted Handshake Message实际上就是Finished消息，用于检验对称加密的密钥是可以正常工作的。\n","permalink":"http://localhost:9011/20200602-encryption-intro/","summary":"\u003cp\u003e本文主要对对称加密和非对称加密的原理以及过程进行分析，同时还会简单介绍一下TLS/SSL的一些相关内容，并且对比TLSv1.2和TLSv1.3的不同。\u003c/p\u003e","title":"SSL/TLS、对称加密和非对称加密和TLSv1.3"},{"content":"本文主要介绍如何使用编译的方式升级openssl库和nginx用于支持HTTP2和TLSv1.3并且介绍了一些简单的提高nginx安全性的配置。\n编译安装openssl 考虑到Linux系统中有许多组件都需要使用openssl库，而现在默认使用的openssl库绝大多数都没到达能够支持TLS1.3的openssl1.1.1版本以上，因此个人建议不要直接修改系统已有的默认openssl库而是另外使用一个新目录来编译安装新版本的openssl。\n具体的支持信息可以查看openssl官网的TLSv1.3部分。\n解压下载的稳定版openssl1.1.1g进行编译安装，注意使用--prefix=指定安装目录\n# 下载 wget https://www.openssl.org/source/openssl-1.1.1g.tar.gz # 解压 tar -zxvf openssl-1.1.1g.tar.gz # 配置、编译 ./config --prefix=/home/openssl make # 检查是否出错 make test # 安装 make install 安装完成后检查链接库是否正常，将缺少的文件直接软链接到系统的/usr/lib64目录下\nln -s /home/openssl/lib/libssl.so.1.1 /usr/lib64/libssl.so.1.1 ln -s /home/openssl/lib/libcrypto.so.1.1 /usr/lib64/libcrypto.so.1.1 最后检查系统使用的openssl的版本是否为新版以及系统的库指向是否正确\n编译安装nginx nginx的TLS1.3和HTTP2 想要在nginx中开启TLS1.3的支持，只需要使用支持TLS1.3的openssl库来进行编译即可。\n查看nginx的版本更新说明我们可以知道nginx从1.9.5版本开始支持HTTP2：\n从1.13版本开始支持TLS1.3\n指定openssl目录进行nginx编译安装 nginx的编译安装此前已经介绍过了，有需要的同学可以点击这里回顾。\nwget http://nginx.org/download/nginx-1.18.0.tar.gz 这次我们使用的是截止2020年5月19号最新的稳定版本nginx1.18.0，一些注意的事项如下：\n--with-http_v2_module和--with-http_ssl_module用于支持HTTP2和ssl加密， --with-openssl=用于指定openssl库的安装目录 --with-openssl-opt=enable-tls1_3用于开启openssl库的tls1.3支持，但是现在的新版本已经默认开启，无需额外添加这个参数 剩下的就和常规的编译安装无异，下面是此次编译的参数：\n./configure --prefix=/home/nginx \\ --sbin-path=/home/nginx/sbin/nginx \\ --with-openssl-opt=enable-tls1_3 \\ --with-openssl=/home/openssl \\ --conf-path=/home/nginx/nginx.conf \\ --error-log-path=/home/nginx/logs/error.log \\ --http-log-path=/home/nginx/logs/access.log \\ --pid-path=/home/nginx/nginx.pid \\ --lock-path=/home/nginx/nginx.lock \\ --http-client-body-temp-path=/home/nginx/cache/client_temp \\ --http-proxy-temp-path=/home/nginx/cache/proxy_temp \\ --http-fastcgi-temp-path=/home/nginx/cache/fastcgi_temp \\ --http-uwsgi-temp-path=/home/nginx/cache/uwsgi_temp \\ --http-scgi-temp-path=/home/nginx/cache/scgi_temp \\ --user=nginx \\ --group=nginx \\ --with-compat \\ --with-file-aio \\ --with-threads \\ --with-http_addition_module \\ --with-http_auth_request_module \\ --with-http_dav_module \\ --with-http_flv_module \\ --with-http_gunzip_module \\ --with-http_gzip_static_module \\ --with-http_mp4_module \\ --with-http_random_index_module \\ --with-http_realip_module \\ --with-http_secure_link_module \\ --with-http_slice_module \\ --with-http_ssl_module \\ --with-http_stub_status_module \\ --with-http_sub_module \\ --with-http_v2_module \\ --with-stream 编译的时候出现报错：\n修改nginx源码目录中auto/lib/openssl/conf中的openssl路径参数，在40行左右的位置对应四个参数中的.openssl去掉，修改后的内容如下：\n39 CORE_INCS=\u0026#34;$CORE_INCS $OPENSSL/include\u0026#34; 40 CORE_DEPS=\u0026#34;$CORE_DEPS $OPENSSL/include/openssl/ssl.h\u0026#34; 41 CORE_LIBS=\u0026#34;$CORE_LIBS $OPENSSL/lib/libssl.a\u0026#34; 42 CORE_LIBS=\u0026#34;$CORE_LIBS $OPENSSL/lib/libcrypto.a\u0026#34; 然后重新编译安装。\nnginx配置修改 安装完成之后将原来的配置文件和html文件全部迁移到新的nginx目录下并进行相应的修改就可以正常的启用nginx了。如果此前的nginx是直接使用yum安装并且使用systemd进行守护进程的控制，我们可以将对应的systemd unit文件中的目录进行修改，一般来说只需要修改相关路径的参数即可：\n首先我们停止服务，然后修改文件，接着重启服务即可：\n# 停止nginx systemctl stop nginx # 修改配置文件 vim /usr/lib/systemd/system/nginx.service [Unit] Description=nginx - high performance web server Documentation=http://nginx.org/en/docs/ After=network-online.target remote-fs.target nss-lookup.target Wants=network-online.target [Service] Type=forking PIDFile=/home/nginx/nginx.pid ExecStart=/home/nginx/sbin/nginx -c /home/nginx/nginx.conf ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s TERM $MAINPID [Install] WantedBy=multi-user.target # 重启daemon进程 systemctl daemon-reload 如果还想使用全局命令nginx的话，可以先把原来的yum安装的移除再将编译安装的新版本指向系统目录\n#使用which nginx查看nginx的指向 $ which nginx /usr/sbin/nginx $ mv /usr/sbin/nginx /usr/sbin/nginx.bak $ ln -s /home/nginx/sbin/nginx /usr/sbin/nginx 最后我们查看nginx版本信息：\n配置http2和tls1.3 nginx配置 nginx中开启http2和tls1.3十分简单，这里配置如下：\nserver{ listen 80; server_name tinychen.com www.tinychen.com; if ($server_port = 80){ return 301 https://$host$request_uri; } } server{ listen 443 ssl http2 default_server; server_name tinychen.com www.tinychen.com; add_header Strict-Transport-Security \u0026#34;max-age=63072000; includeSubDomains; preload\u0026#34;; ssl_ciphers TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES; #ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256; ssl_protocols TLSv1.2 TLSv1.3; ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate certs/tinychen.com.pem; ssl_prefer_server_ciphers on; ssl_certificate certs/tinychen.com.pem; ssl_certificate_key certs/tinychen.com.key; ssl_session_cache shared:SSL:1m; ssl_verify_depth 10; ssl_session_timeout 30m; location / { root html/; index index.html index.htm; } } 由于http2默认需要使用加密，因此直接在nginx对应的ssl监听端口上加上http2字段即可 TLS1.3则只需要在ssl_protocols指令中加上TLSv1.3 ssl_ciphers这里的配置采用了比较激进的配置，由于前面协议只启用了TLSv1.2和TLSv1.3，因此这里对应的ssl_ciphers也直接弃用了大量旧的和弱的加密套件 add_header Strict-Transport-Security \u0026quot;max-age=63072000; includeSubDomains; preload\u0026quot;;这个字段头用于开启HSTS避免在进行301跳转的时候被中间人攻击，添加了preload字段还需要在HSTS的官网添加自己的网址，注意HSTS不应该添加在HTTP请求的网站上，所以上面将80端口和443端口分为两个server块 ssl_stapling on用于开启OCSP（Online Certificate Status Protocol），可以减缓网络和客户端资源负担 ssl_stapling_verify on用于开启OCSP Stapling（OCSP装订），这是TLS证书状态查询扩展，服务器在TLS握手时发送事先缓存的OCSP响应，用户只要验证该响应的时效性而不用再向数字证书认证机构(CA)发送请求，可以加快握手速度 检测 使用myssl进行检测，可以看到很顺利地显示已经支持了HTTP2和TLS1.3协议。\n同时可以看到由于关闭了TLS1.2以下的加密协议支持并且强制启用了https之后很多旧浏览器已经不再支持了：\n在不考虑旧版浏览器的兼容之后可以很轻松获得A+评分并且上榜：\n同样的我们可以使用ssllab来进行测试，测试结果也是大同小异：\n主流网站测试 下表是截止2020年6月6日使用Chrome（83.0.4103.97（正式版本））对一些主要的网站首页进行的简单测试\n简称 网站 HTTP TLS 谷歌 google.com/ H2+quic/46 TLS1.3 苹果 apple.com.cn/ H2 TLS1.3 GitHub github.com/ H2 TLS1.3 维基百科 en.wikipedia.org/ H2 TLS1.3 k8s kubernetes.io/ H2 TLS1.3 nginx商业版 nginx.com/ H2 TLS1.3 阿里CDN *.alicdn.com H2 TLS1.3 163首页 163.com/ H2+HTTP1.1 TLS1.3 ssllab ssllabs.com/index.html HTTP1.1 TLS1.3 微软 microsoft.com/zh-cn/ H2 TLS1.2 docker docker.com/ H2 TLS1.2 微信 weixin.qq.com/ H2 TLS1.2 严选 you.163.com/ H2 TLS1.2 淘宝 taobao.com/ H2 TLS1.2 天猫 tmall.com/ H2 TLS1.2 阿里巴巴 alibaba.com/ H2 TLS1.2 csdn csdn.net/ H2 TLS1.2 腾讯云 cloud.tencent.com/ H2 TLS1.2 亚马逊 amazon.com/ H2+HTTP1.1 TLS1.2 QQ qq.com/ H2+HTTP1.1 TLS1.2 B站 bilibili.com/ H2+HTTP1.1 TLS1.2 百度 baidu.com/ H2+HTTP1.1 TLS1.2 今日头条 toutiao.com/ H2+HTTP1.1 TLS1.2 小米 mi.com/ H2+HTTP1.1 TLS1.2 拼多多 pinduoduo.com/ H2+HTTP1.1 TLS1.2 美团 meituan.com/ HTTP1.1+H2(极少) TLS1.2 网易企业邮 qiye.163.com/ HTTP1.1 TLS1.2 126邮箱 126.com/ HTTP1.1 TLS1.2 myssl myssl.com/ HTTP1.1 TLS1.2 openssl openssl.org/ HTTP1.1 TLS1.2 nginx开源版 nginx.org/ HTTP1.1 TLS1.2 华为 huawei.com/cn/ HTTP1.1 TLS1.2 ","permalink":"http://localhost:9011/20200519-nginx-07-enable-http2-tls13/","summary":"\u003cp\u003e本文主要介绍如何使用编译的方式升级openssl库和nginx用于支持HTTP2和TLSv1.3并且介绍了一些简单的提高nginx安全性的配置。\u003c/p\u003e","title":"nginx篇07-启用HTTP2和TLSv1.3"},{"content":"本文主要使用Wireshark对邮件客户端使用IMAP协议接收邮件的过程进行抓包分析并使用telnet命令进行简单操作。\nIMAP简介 IMAP和POP3两个协议基本上是目前支持和使用最广泛的邮件接收协议，IMAP和POP3相比有着许多优点，参考wiki和后面的抓包结果进行分析，这里列出一些后面抓包的时候可以验证的优点：\n支持连接和断开两种操作模式\n和POP3协议在接收完邮件之后就和服务器断开连接不同，IMAP协议可以一直和服务器保持连接从而使得接收新邮件的延迟大大降低。\n支持多个客户同时连接到一个邮箱\nPOP3协议假定邮箱当前的连接是唯一的连接。相反，IMAP4协议允许多个用户同时访问邮箱同时提供一种机制让客户能够感知其他当前连接到这个邮箱的用户所做的操作。\n支持在服务器保留消息状态信息\n通过使用在IMAP4协议中定义的标志客户端可以跟踪消息状态，例如邮件是否被读取，回复，或者删除。这些标识存储在服务器，所以多个客户在不同时间访问一个邮箱可以感知其他用户所做的操作。\n支持在服务器上访问多个邮箱\nIMAP4客户端可以在服务器上创建，重命名，或删除邮箱（通常以文件夹形式显现给用户）。支持多个邮箱还允许服务器提供对于共享和公共文件夹的访问。\n支持访问消息中的MIME部分和部分获取。\n几乎所有的Internet邮件都是以MIME格式传输的。MIME允许消息包含一个树型结构，这个树型结构的叶子节点都是单一内容类型而非叶子节点都是多块类型的组合。IMAP4协议允许客户端获取任何独立的MIME部分和获取信息的一部分或者全部。这些机制使得用户无需下载附件就可以浏览消息内容或者在获取内容的同时浏览。\n抓包分析 MUA中的设置如下，同样不使用加密协议方便分析数据。\n配置完成开始抓包之后发现邮箱大师有多线程并发收件的操作，这也算是利用了IMAP协议的能够允许多个客户端连接到同一个服务器的特点，但是显然不利于我们分析串行模式下单个连接收件的完整过程，不过也可以对比多个连接之间的差异。\n由于建立IMAP通信连接必须要登录，所以我们只需要查看报文就可以知道一共并行发起了多少个连接，在这次的报文中一共发现了有三个LOGIN的请求报文，因此可以判断应该一共先后发起了三个连接，其中第三个连接是在第一个连接结束之后发起的，具体分析如下：\n可以看到最开始几乎同时发起了两个IMAP连接：\n第一个连接 我们先来对第一个连接进行分析：\n首先我们可以确定IMAP的传输层协议也是使用的TCP协议，同样这里略去TCP三握四挥的分析，直接看IMAP相关部分的报文：\n客户端与IMAP服务器在TCP三次握手之后建立连接\nIMAP服务器返回OK信息，并说明自身邮件系统的类型为coremail\n客户端发送CAPABILITY命令查询可用的命令，这个和POP3中的CAPA命令功能相同\nIMAP服务器返回可以执行的命令\n客户端发送ID命令，附带了MUA和OS的相关信息\nIMAP服务器返回ID字符串，同样附带了邮件服务器的相关信息\n客户端发送LOGIN命令进行登录，双引号内的为邮箱账号，后面加一个空格然后紧跟着的是密码\nIMAP服务器返回OK指令提示登录成功\n客户端发送LIST命令查询该账号内的邮件信息，但是可能命令的格式不对，并没有查询到任何有用的信息\nIMAP服务器返回查询结果为空：\n如果把查询命令换为LIST \u0026quot;\u0026quot; \u0026quot;*\u0026quot;则可以查询到该账号的所有邮箱文件夹：\n客户端发送NOOP指令，与之前的POP3协议类似，NOOP指令的作用应该是用于保持连接，默认相当于无操作，但是在此次连接还没使用过SELECT命令并且是第一次发送NOOP指令的时候，IMAP服务器会返回该账号下所有目录的邮件总数\nIMAP服务器返回OK指令并且返回了邮件总数、\n客户端使用SELECT指令，并且选中了INBOX文件夹（一般对应收件箱），相当于在数据库中选中了一个数据表\n然后连接就莫名没了\n第二个连接 第二次连接的数据包较长，我们截取部分客户端发送的请求：\n对比第一次连接，主要有以下不同点：\n使用了XLIST \u0026quot;\u0026quot; \u0026quot;*\u0026quot;查询到了邮件账号下的所有文件夹（收件箱、发件箱、草稿箱、垃圾箱、垃圾邮件等） 依次使用SELECT命令和UID SEARCH UID 命令来对每个文件夹进行操作，进而获取到该账号对应的文件夹下的所有邮件的总数和对应的UID 第三个连接 从wireshark上对数据包标记的序号来看，第三个连接是在第一个连接结束之后才发起的。对应的客户端主要操作如下：\n上面的内容重复操作较多，主要就是对每个文件夹都进行SELECT，然后获取里面的邮件具体内容，主要的核心操作有以下两个\nUID FETCH 1557156839:1557156846 (UID FLAGS RFC822.SIZE BODYSTRUCTURE INTERNALDATE BODY.PEEK[HEADER.FIELDS (Date Subject From Sender Reply-To To Cc Bcc Message-ID References In-Reply-To X-MailMaster-ShowOneRcpt X-CUSTOM-MAIL-MASTER-SENT-ID Disposition-Notification-To X-CM-CTRLMSGS)]) UID FETCH 1557156844 BODY.PEEK[1] 上面两个请求命令中的1557156844就是在第一个连接中使用UID SEARCH命令查询到的邮件的UID，这里使用了UID FETCH命令来获取邮件的对应内容。由于前面我们提到邮件的格式是符合MIME标准的，而IMAP协议又是允许MUA下载符合MIME标准的部分邮件内容，因此这两条UID命令就是用于获取对应的邮件的特定部分的内容。\n小结 在换了另一个测试账号进行同样的抓包操作之后我发现两次的数据请求操作几乎是一模一样的，都是有三个连接，操作也和上面相同。由此可以分析IMAP协议的可操作性要比POP3强很多，因此在具体的功能实现上对于不同的MUA而言也有不同。\ntelnet操作 同样的我们也可以telnet到邮件服务器的143端口来进行命令操作：\n[root@www coremail]# telnet localhost 143 Trying 127.0.0.1... Connected to localhost. Escape character is \u0026#39;^]\u0026#39;. * OK Coremail System IMap Server Ready(126com[c92b4e18679ada4069d0bde6e2528ad1]) C1 LOGIN \u0026#34;test02@coremail.cn\u0026#34; password C1 OK LOGIN completed C2 LIST \u0026#34;\u0026#34; \u0026#34;\u0026#34; * LIST (\\Noselect) \u0026#34;/\u0026#34; \u0026#34;\u0026#34; C2 OK LIST Completed C3 LIST \u0026#34;\u0026#34; \u0026#34;*\u0026#34; * LIST () \u0026#34;/\u0026#34; \u0026#34;INBOX\u0026#34; * LIST (\\Drafts) \u0026#34;/\u0026#34; \u0026#34;Drafts\u0026#34; * LIST (\\Sent) \u0026#34;/\u0026#34; \u0026#34;Sent Items\u0026#34; * LIST (\\Trash) \u0026#34;/\u0026#34; \u0026#34;Trash\u0026#34; * LIST (\\Junk) \u0026#34;/\u0026#34; \u0026#34;Junk E-mail\u0026#34; * LIST () \u0026#34;/\u0026#34; \u0026#34;Virus Items\u0026#34; C3 OK LIST Completed C4 SELECT INBOX * 8 EXISTS * 0 RECENT * OK [UIDVALIDITY 1] UIDs valid * FLAGS (\\Answered \\Seen \\Deleted \\Draft \\Flagged) * OK [PERMANENTFLAGS (\\Answered \\Seen \\Deleted \\Draft \\Flagged)] Limited C4 OK [READ-WRITE] SELECT completed C5 UID SEARCH 1:* * SEARCH 1557156839 1557156840 1557156841 1557156842 1557156843 1557156844 1557156845 1557156846 C5 OK SEARCH completed C6 UID FETCH 1557156839 C6 BAD Parse command error C7 UID FETCH 1557156839 FULL * 1 FETCH (UID 1557156839 INTERNALDATE \u0026#34; 6-May-2019 23:33:59 +0800\u0026#34; FLAGS (\\Seen) ENVELOPE (\u0026#34;Mon, 6 May 2019 23:33:59 +0800 (GMT+08:00)\u0026#34; \u0026#34;=?UTF-8?B?5qyi6L+O5L2/55SoQ29yZW1haWznlLXlrZDpgq7ku7bns7vnu58vV2VsY29tZSB0byB0aGUgQ29yZW1haWwgZS1tYWlsIHN5c3RlbQ==?=\u0026#34; ((NIL NIL \u0026#34;postmaster\u0026#34; \u0026#34;coremail.cn\u0026#34;)) ((NIL NIL \u0026#34;postmaster\u0026#34; \u0026#34;coremail.cn\u0026#34;)) ((NIL NIL \u0026#34;postmaster\u0026#34; \u0026#34;coremail.cn\u0026#34;)) ((NIL NIL \u0026#34;test02\u0026#34; \u0026#34;coremail.cn\u0026#34;)) NIL NIL NIL \u0026#34;\u0026lt;1106604853.1.1557156839490@www.example.com\u0026gt;\u0026#34;) BODY ((\u0026#34;text\u0026#34; \u0026#34;html\u0026#34; (\u0026#34;charset\u0026#34; \u0026#34;UTF-8\u0026#34;) NIL NIL \u0026#34;quoted-printable\u0026#34; 7274 152) \u0026#34;related\u0026#34;) RFC822.SIZE 7959) C7 OK Fetch completed C8 UID FETCH 1557156846 FULL * 8 FETCH (UID 1557156846 INTERNALDATE \u0026#34; 8-May-2019 09:43:11 +0800\u0026#34; FLAGS (\\Seen) ENVELOPE (\u0026#34;Wed, 8 May 2019 09:43:11 +0800 (CST)\u0026#34; \u0026#34;=?UTF-8?B?dGVsbmV0IHRlc3QgbWFpbCBBdXRoZW50aWNhdGVk?=\u0026#34; ((\u0026#34;=?UTF-8?B?InRlc3QwMSI=?=\u0026#34; NIL \u0026#34;test01\u0026#34; \u0026#34;coremail.cn\u0026#34;)) ((\u0026#34;=?UTF-8?B?InRlc3QwMSI=?=\u0026#34; NIL \u0026#34;test01\u0026#34; \u0026#34;coremail.cn\u0026#34;)) ((\u0026#34;=?UTF-8?B?InRlc3QwMSI=?=\u0026#34; NIL \u0026#34;test01\u0026#34; \u0026#34;coremail.cn\u0026#34;)) ((\u0026#34;=?UTF-8?B?InRlc3QwMiI=?=\u0026#34; NIL \u0026#34;test02\u0026#34; \u0026#34;coremail.cn\u0026#34;)) NIL NIL NIL \u0026#34;\u0026lt;5CD2342F.000006.02460@coremail.cn\u0026gt;\u0026#34;) BODY (\u0026#34;TEXT\u0026#34; \u0026#34;PLAIN\u0026#34; NIL NIL NIL \u0026#34;7BIT\u0026#34; 0 0) RFC822.SIZE 656) C8 OK Fetch completed C9 UID FETCH 1557156844 FULL * 6 FETCH (UID 1557156844 INTERNALDATE \u0026#34; 8-May-2019 16:59:38 +0800\u0026#34; FLAGS (\\Seen) ENVELOPE (\u0026#34;Wed, 8 May 2019 16:59:38 +0800 (CST)\u0026#34; \u0026#34;=?UTF-8?B?dGVsbmV0IHRlc3QgbWFpbA==?=\u0026#34; ((\u0026#34;=?UTF-8?B?InRlc3QwMSI=?=\u0026#34; NIL \u0026#34;test01\u0026#34; \u0026#34;coremail.cn\u0026#34;)) ((\u0026#34;=?UTF-8?B?InRlc3QwMSI=?=\u0026#34; NIL \u0026#34;test01\u0026#34; \u0026#34;coremail.cn\u0026#34;)) ((\u0026#34;=?UTF-8?B?InRlc3QwMSI=?=\u0026#34; NIL \u0026#34;test01\u0026#34; \u0026#34;coremail.cn\u0026#34;)) ((\u0026#34;=?UTF-8?B?InRlc3QwMiI=?=\u0026#34; NIL \u0026#34;test02\u0026#34; \u0026#34;coremail.cn\u0026#34;)) NIL NIL NIL \u0026#34;\u0026lt;5CD29A7A.000004.02460@coremail.cn\u0026gt;\u0026#34;) BODY (\u0026#34;TEXT\u0026#34; \u0026#34;PLAIN\u0026#34; NIL NIL NIL \u0026#34;7BIT\u0026#34; 0 0) RFC822.SIZE 642) C9 OK Fetch completed ","permalink":"http://localhost:9011/20200513-wireshark-analysis-imap/","summary":"\u003cp\u003e本文主要使用Wireshark对邮件客户端使用IMAP协议接收邮件的过程进行抓包分析并使用telnet命令进行简单操作。\u003c/p\u003e","title":"Wireshark对IMAP抓包分析"},{"content":"本文主要用于记录在Windows上使用WSL2和centos系统打造一个免费高效的shell终端的配置过程以及界面优化过程。\n选型分析 之前一直都是使用termius和windows来进行ssh管理，但是最近termius要过期了（没钱续费），不用盗版（版权意识），ssh的Agent Forwarding功能是个人刚需且为termius的付费版才有的功能，所以开始研究windows上免费的ssh客户端管理工具或者是方式。\n（mac系统用户可以跳过这篇文章）首先考虑到了mac，因为mac系统是类unix系统，在命令行终端操作上要比windows好太多，但是没钱买mac，所以pass\n然后如果直接使用linux系统的话虽然shell的问题很容易解决，但是其他的办公软件无法使用或者很难用；如果用虚拟机跑linux，启动麻烦，耗费性能，文件管理也十分麻烦；所以pass\n还有就是各种windows上的各种免费第三方工具，如powershell、GitBash、cmder等等，能实现一定的Linux命令和shell功能，但是相比原生linux还是功能残缺。\n**但是如果只要管理各种ssh客户端的话很多免费工具已经足够优秀了，**而笔者本次除了管理ssh之外还想要实现一定的shell功能，因此最后锁定Windows10上面的2004版本的wsl2工具，用它来运行一个linux系统，虽然是残缺的Linux，但是在shell功能和ssh功能上和直接运行Linux并无差异。唯一需要注意的就是windows的版本要比较新。\nWSL 2 is only available in Windows 10 builds 18917 or higher\n安装wsl windows官网的安装教程比较详细，并且wsl的安装启用比较简单，我们可以直接使用管理员模式的powershell通过命令行来启用：\ndism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart 命令顺利执行完后需要重启，这时候输入wsl --help就可以看到wsl已经安装成功。\n安装centos Windows的应用商店中有一些不错的linux发行版，包括很多同学都很喜欢的ubuntu，但是个人比较熟悉使用centos，而应用商店中的centos是要收费的，不过好在github上面有CENTOS官方开源的安装包，我们这里使用github上的安装包进行安装。\n如果使用应用商店中的发行版直接点击安装即可。随后便可以跳过下面的centos的安装部分。\n首先我们去centos的GitHub页面下载对应的安装包：\n注意这里要切换分支进行下载，以centos7-x86.64为例，这里对应的就是最新的centos7.8系统，我们只需要下载对应的tar.xz压缩包即可。\nwget https://github.com/CentOS/sig-cloud-instance-images/blob/CentOS-7-x86_64/docker/centos-7-x86_64-docker.tar.xz 接着我们以管理员身份打开一个powershell窗口：\n# 安装 Chocolatey Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString(\u0026#39;https://chocolatey.org/install.ps1\u0026#39;)) # 安装 LxRunOffline choco install lxrunoffline 注意这里安装完成之后需要重启powershell来进行下一步的安装\nLxRunOffline install -n 自定义系统名称 -d 安装目录路径 -f tar.xz安装包路径 # 注意windows系统命令行中的文件路径和linux系统差别很大 # 比如我这里的安装命令就是 LxRunOffline.exe install -n centos -d D:/centos -f .\\centos-7-x86_64-docker.tar.xz # 将centos安装到D盘的centos文件夹下，并且命名为centos 接下来就可以使用下述两种方式尝试启动\nLxRunOffline run -n 自定义系统名称 wsl -d 自定义系统名称 升级centos为wsl2 windows官网教程\n# 列出已经安装的wsl的信息 wsl -l -v # 将对应的wsl设为wsl2，注意\u0026lt;Distro\u0026gt;要和上面查询到的信息一致 wsl --set-version \u0026lt;Distro\u0026gt; 2 # 设置默认使用的发行版 wsl -s \u0026lt;Distro\u0026gt; 配置windows terminal 使用windows的cmd或者powershell都可以直接输入wsl命令进入到wsl系统中进行操作，但是由于这两者的界面比较丑，因此我们这里使用可以定制更多参数的windows terminal来进行替换。\n不过windows terminal目前来说属于比较轻量级的产品，如果需要更多的功能可以考虑一下其他的软件，这里额外推荐一个免费的全平台终端terminus，注意比收费的termius要多一个字母n，也是github上面的一个开源项目，免费高效且内置多种主题和少量还可以的插件。\n安装windows terminal windows terminal直接使用win10自带的应用商店即可搜索下载安装。\n安装powerline/fonts powerline/fonts是github上面的一个项目，涵盖了较多的字体，windows本身内置的字体可能在shell中显示不太美观，所以这里我们需要在windows系统中和wsl中都安装字体。\n首先在wsl中使用git拉取项目并且安装\ngit clone https://github.com/powerline/fonts.git --depth=1 cd fonts ./install.sh 在Windows中也需要安装该字体，以UbuntuMono为例，同样是下载之后进入到对应的文件夹中手动安装所有的ttf字体即可：\n（注意尽量以管理员身份为本机所有用户安装）\n配置iTerm2-Color-Schemes 主题的效果我们可以在官网进行预览，配置参数我们可以在对应的GitHub中找到。需要注意的是要找到对应的windows terminal目录中的配置。\n配置setting.json setting.json的参数非常多，我们可以根据自己的习惯设定显示界面和快捷键等操作，并且还可以添加背景图片，自定义不同shell的图标icon和主题等用以区分不同的shell避免误操作。\n// To view the default settings, hold \u0026#34;alt\u0026#34; while clicking on the \u0026#34;Settings\u0026#34; button. // For documentation on these settings, see: https://aka.ms/terminal-documentation { \u0026#34;$schema\u0026#34;: \u0026#34;https://aka.ms/terminal-profiles-schema\u0026#34;, \u0026#34;defaultProfile\u0026#34;: \u0026#34;{aabae64a-7cf7-5add-b5d6-744e54ab56d6}\u0026#34;, //设定默认启动使用的shell。这里使用后面对应的guid \u0026#34;initialRows\u0026#34;: 40, //初始化窗口的行数 \u0026#34;initialCols\u0026#34;: 150,//初始化窗口的列数 \u0026#34;alwaysShowTabs\u0026#34;: true, \u0026#34;showTerminalTitleInTitlebar\u0026#34;: true, \u0026#34;profiles\u0026#34;: { \u0026#34;defaults\u0026#34;: { // Put settings here that you want to apply to all profiles }, \u0026#34;list\u0026#34;: [ { \u0026#34;guid\u0026#34;: \u0026#34;{07b52e3e-de2c-5db4-bd2d-ba144ed6c273}\u0026#34;, \u0026#34;hidden\u0026#34;: false, \u0026#34;name\u0026#34;: \u0026#34;Ubuntu-20.04\u0026#34;, \u0026#34;source\u0026#34;: \u0026#34;Windows.Terminal.Wsl\u0026#34;, \u0026#34;fontFace\u0026#34;: \u0026#34;Fira Mono for Powerline\u0026#34;, \u0026#34;fontSize\u0026#34;: 12, \u0026#34;colorScheme\u0026#34;: \u0026#34;Ubuntu\u0026#34;, //这里的color要和后面的schemes中的一致 \u0026#34;useAcrylic\u0026#34; : false, // 是否启用窗口透明度，效果类似毛玻璃特效 \u0026#34;acrylicOpacity\u0026#34; : 0.6 // 窗口透明度 } { \u0026#34;guid\u0026#34;: \u0026#34;{aabae64a-7cf7-5add-b5d6-744e54ab56d6}\u0026#34;, \u0026#34;hidden\u0026#34;: false, \u0026#34;name\u0026#34;: \u0026#34;centos\u0026#34;, \u0026#34;source\u0026#34;: \u0026#34;Windows.Terminal.Wsl\u0026#34;, \u0026#34;fontFace\u0026#34;: \u0026#34;Fira Mono for Powerline\u0026#34;, \u0026#34;fontSize\u0026#34;: 12, \u0026#34;colorScheme\u0026#34;: \u0026#34;Atom\u0026#34;, \u0026#34;useAcrylic\u0026#34; : true, // 窗口透明度 \u0026#34;acrylicOpacity\u0026#34; : 0.8 // 窗口透明度 }, { // Make changes here to the cmd.exe profile \u0026#34;guid\u0026#34;: \u0026#34;{0caa0dad-35be-5f56-a8ff-afceeeaa6101}\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;cmd\u0026#34;, \u0026#34;commandline\u0026#34;: \u0026#34;cmd.exe\u0026#34;, \u0026#34;hidden\u0026#34;: false, \u0026#34;fontSize\u0026#34;: 12, \u0026#34;colorScheme\u0026#34;: \u0026#34;MaterialDark\u0026#34;, \u0026#34;useAcrylic\u0026#34; : false, // 窗口透明度 \u0026#34;acrylicOpacity\u0026#34; : 0.5 // 窗口透明度 } ] }, // Add custom color schemes to this array \u0026#34;schemes\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;Atom\u0026#34;, \u0026#34;black\u0026#34;: \u0026#34;#000000\u0026#34;, \u0026#34;red\u0026#34;: \u0026#34;#fd5ff1\u0026#34;, \u0026#34;green\u0026#34;: \u0026#34;#87c38a\u0026#34;, \u0026#34;yellow\u0026#34;: \u0026#34;#ffd7b1\u0026#34;, \u0026#34;blue\u0026#34;: \u0026#34;#85befd\u0026#34;, \u0026#34;purple\u0026#34;: \u0026#34;#b9b6fc\u0026#34;, \u0026#34;cyan\u0026#34;: \u0026#34;#85befd\u0026#34;, \u0026#34;white\u0026#34;: \u0026#34;#e0e0e0\u0026#34;, \u0026#34;brightBlack\u0026#34;: \u0026#34;#000000\u0026#34;, \u0026#34;brightRed\u0026#34;: \u0026#34;#fd5ff1\u0026#34;, \u0026#34;brightGreen\u0026#34;: \u0026#34;#94fa36\u0026#34;, \u0026#34;brightYellow\u0026#34;: \u0026#34;#f5ffa8\u0026#34;, \u0026#34;brightBlue\u0026#34;: \u0026#34;#96cbfe\u0026#34;, \u0026#34;brightPurple\u0026#34;: \u0026#34;#b9b6fc\u0026#34;, \u0026#34;brightCyan\u0026#34;: \u0026#34;#85befd\u0026#34;, \u0026#34;brightWhite\u0026#34;: \u0026#34;#e0e0e0\u0026#34;, \u0026#34;background\u0026#34;: \u0026#34;#161719\u0026#34;, \u0026#34;foreground\u0026#34;: \u0026#34;#c5c8c6\u0026#34; } ], \u0026#34;keybindings\u0026#34;: [//这里可以设置快捷键 { \u0026#34;command\u0026#34; : \u0026#34;copy\u0026#34;, \u0026#34;keys\u0026#34; : [ \u0026#34;ctrl+c\u0026#34; ] }, { \u0026#34;command\u0026#34; : \u0026#34;paste\u0026#34;, \u0026#34;keys\u0026#34; : [ \u0026#34;ctrl+v\u0026#34; ] } ] } ssh_config 最后我们再搭配ssh自带的ssh_config来添加各种自定义ssh参数，如果需要在这上面开启ssh的Agent Forwarding功能，个人习惯是单独创建一个脚本并且在bash中设置alias快捷操作。\n# 脚本内容示例 # 开启ssh Agent Forwarding eval `ssh-agent` # 将要使用的ssh key添加到ssh-add中 # 如果后面不指明文件则使用默认的~/.ssh/id_rsa # 也可以一次添加多个 ssh-add ~/.ssh/id_rsa ssh user@host 然后在bashrc文件中添加alias即可快速操作，对应的ssh_config参数可以使用man ssh_config命令来查看。\n这里贴出几个常用的参数：\nHost example.host.com # ssh远程客户端的别名，可以直接通过ssh example.host.com来进行连接 HostName 192.168.1.1 # ssh远程客户端的ip地址 User root # ssh远程的用户 Port 22 # ssh的端口 IdentityFile ~/.ssh/id_rsa # ssh的认证密钥 ForwardAgent yes # 是否开启Agent Forwarding，默认不开启 AddKeysToAgent yes # 是否自动将 key 加入到 ssh-agent，值可以为 no(default)/confirm/ask/yes # 如果是 yes，key 和密码都将读取文件并以加入到 agent ，就像 ssh-add。其他分别是询问、确认、不加入的意思。添加到 ssh-agent 意味着将私钥和密码交给它管理，让它来进行身份认证。 效果展示 wsl2使用的应该是windows定制的内核（目前我的系统内核是4.19.84-microsoft-standard），同时无法使用systemd工具，但是作为terminal来使用已经是绰绰有余了，命令的丰富程度也要远胜于powershell、gitbash和其他的第三方命令行，安装一些常用命令的方便程度也要远胜于cygwin等。windows系统的硬盘也会直接挂载在wsl中的/mnt目录下，可以直接使用Linux的sed、awk、paste等命令来对windows下的文本进行操作。\n注意在默认情况下会把系统本身的硬盘也挂载到/mnt目录下，注意不要误操作rm命令\n","permalink":"http://localhost:9011/20200512-windows-terminal-wsl2-centos-installation/","summary":"\u003cp\u003e本文主要用于记录在Windows上使用WSL2和centos系统打造一个免费高效的shell终端的配置过程以及界面优化过程。\u003c/p\u003e","title":"Windows Terminal + WSL2 + CENTOS 配置Windows命令终端"},{"content":"本文主要使用Wireshark对邮件客户端使用POP3协议收取邮件的过程进行抓包分析并使用telnet命令进行简单操作。\nPOP3简介 邮局协议（英语：Post Office Protocol，缩写：POP）属于TCP/IP协议族中的一员，由RFC 1939定义。此协议主要用于支持使用客户端远程管理在服务器上的电子邮件。最新版本为POP3，全名“Post Office Protocol - Version 3”，而提供了SSL加密的POP3协议被称为POP3S。\nPOP支持离线邮件处理。其具体过程是：发件人将邮件发送到服务器上，收件人客户端使用MUA以连接服务器，并下载所有未阅读的电子邮件。这种离线访问模式是一种存储转发服务，将邮件从邮件服务器端送到个人客户端上，可以是PC或者手机等多种设备。旧版的POP3协议在邮件被下载后，会删除掉邮件服务器上的邮件。改进的POP3协议可以在配置中选择可以“只下载邮件，服务器端并不删除”，也是目前POP3的主流操作方式。\n抓包环境 抓包工具还是使用的wireshark，测试的协议是POP3，默认端口110，同样地为了方便分析数据没有使用TLS/SSL加密，但是结果都问题不大。\n测试的账号登录之后自动同步了收件箱中已有的5封邮件，然后我又给测试账号发了一封邮件，因此抓包的时候MUA客户端上应该是只有本地已缓存的5封已读邮件和服务器端尚未下载的1封未读邮件，这样比较符合实际的情况。\n开启wireshark监听对应的网卡，设定Filter为邮件服务器的IP并且设定协议为pop基本就能抓取到需要的数据包，然后我们再Follow这条POP数据流对应的TCP stream就可以看到下面的完整信息。\n毫无意外地可以看到TCP的三握四挥，这里不作赘述。\nPOP3数据包分析 首先我们可以看到在客户端和服务端TCP三次握手建立连接后，服务端发送报文给客户端告知顺利和POP3服务器建立连接：\n接下来客户端发送了一个CAPA命令：\nwiki里面有列出常用的POP3命令，但是却并没有CAPA这条命令，顺着下面的参考文档查了一下，在RFC的文档RFC2449中找到了比较详细的定义：\nSection 3 describes the CAPA response using [ABNF]. When a capability response describes an optional command, the SHOULD be identical to the command keyword. CAPA response tags are case-insensitive.\n​ CAPA\n​ Arguments: ​ none\n​ Restrictions: ​ none\n​ Discussion: ​ An -ERR response indicates the capability command is not ​ implemented and the client will have to probe for ​ capabilities as before.\n​ An +OK response is followed by a list of capabilities, one ​ per line. Each capability name MAY be followed by a single ​ space and a space-separated list of parameters. Each ​ capability line is limited to 512 octets (including the ​ CRLF). The capability list is terminated by a line ​ containing a termination octet (\u0026quot;.\u0026quot;) and a CRLF pair.\n​ Possible Responses: ​ +OK -ERR\n也就是说客户端发送CAPA命令主要是用于获取POP3服务端可以执行的命令，然后POP3服务器果然就返回了对应的+OK response和Capability list follows\n接下来客户端开始传输账号密码用于登录，可以看到在登录成功之后，POP3服务端返回了消息提示有6封邮件（6 messages）和邮件的总大小（25568 bytes），然后客户端发送UTF8命令指定编码方式，收到服务端返回的确认消息后再发送STAT命令来请求服务器发回关于邮箱的统计资料（此处为邮件总数和总字节数）。\n紧接着客户端继续发送UIDL命令，POP3服务器端返回邮件的唯一标识符，POP3会话的每个标识符都将是唯一的，并且是全局始终唯一。即同一封邮件在每次通信的时候的标识符都是不变的，这样就有利于MUA将本地存储的邮件和服务器端的邮件进行对比从而知道哪些邮件还没有被下载到本地。\n获取邮件标识符后，客户端会发送LIST命令查询邮件数量和每个邮件的大小，这里我们可以看到邮件的总数量和总大小是和上面的STAT命令查询的一致的。\n前面我们说过在抓包之前MUA中是存着5封邮件并且第6封邮件是未读的，因此这时客户端就会发送RETR请求获取第6封邮件的信息：\n服务器首先返回一条信息表示OK，并且说明这封邮件的大小是1199个字节，然后开始传输整封邮件的内容，整个邮件内容包含了标准的一些邮件信息（主题、正文、收发件人、时间等）和一些对应的邮件系统的专属信息。\n最后邮件以.结束传输，客户端发送QUIT请求，服务端返回OK结束本次传输。\ntelnet操作 和之前的SMTP一样，我们也可以使用telnet命令对服务器的110端口进行操作：\n[root@www coremail]# telnet localhost 110 Trying 127.0.0.1... Connected to localhost. Escape character is \u0026#39;^]\u0026#39;. +OK Welcome to coremail Mail Pop3 Server (126coms[c92b4e18679ada4069d0bde6e2528ad1s]) CAPA +OK Capability list follows TOP USER PIPELINING UIDL LANG UTF8 SASL PLAIN STLS . USER test01@coremail.cn +OK core mail PASS password +OK 6 message(s) [25568 byte(s)] UTF8 +OK UTF-8 OK STAT +OK 6 25568 UIDL +OK 6 25568 1 1tbiAQACE10Y3LsAAAAAsy 2 1tbiAQACE10Y3LsAAAACsw 3 1tbiAQACE10Y3LsAAAADsx 4 1tbiAQACE10Y3LsAAAAEs2 5 1tbiAQACE10Y3LsAAAAFs3 6 1tbiAQACE10Y3LsAAAAGs0 . LIST +OK 6 25568 1 7959 2 1199 3 6445 4 6469 5 2297 6 1199 . 到这里的操作是和之前一样的，实际上我们还可以使用RETR命令查看任意一封邮件的内容：\nRETR 5 +OK 2297 octets Received: from TINYDESKTOP (unknown [10.228.12.149]) by www.example.com (Coremail) with SMTP id AQAAfwBXc7qLVrZeWAAAAA--.16S2; Sat, 09 May 2020 15:06:51 +0800 (CST) From: \u0026#34;Microsoft Outlook\u0026#34; \u0026lt;test01@coremail.cn\u0026gt; Sender: test01@coremail.cn To: \u0026lt;test01@coremail.cn\u0026gt; Subject: =?utf-8?B?TWljcm9zb2Z0IE91dGxvb2sg5rWL6K+V5raI5oGv?= MIME-Version: 1.0 Content-Type: multipart/alternative; boundary=\u0026#34;----=_NextPart_000_0000_01D62613.778669A0\u0026#34; X-CM-TRANSID:AQAAfwBXc7qLVrZeWAAAAA--.16S2 Message-Id:\u0026lt;5EB6568B.000001.04520@coremail.cn\u0026gt; X-Coremail-Antispam: 1UD129KBjDUn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73 VFW2AGmfu7bjvjm3AaLaJ3UjIYCTnIWjp_UUUYK7AC8VAFwI0_Jr0_Gr1l1xkIjI8I6I8E 6xAIw20EY4v20xvaj40_Wr0E3s1l1IIY67AEw4v_Jr0_Jr4l8cAvFVAK0II2c7xJM28Cjx kF64kEwVA0rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVWUJVWUCwA2z4x0Y4vE2Ix0cI8I cVCY1x0267AKxVWUJVW8JwA2z4x0Y4vEx4A2jsIE14v26r1j6r4UM28EF7xvwVC2z280aV CY1x0267AKxVWUJVW8JwAac4AC62xK8xCEY4vEwIxC4wAS0I0E0xvYzxvE52x082IY62kv 0487Mc02F40E4c8EcI0Er2xKeI8DMcIj6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67 AKxVWUJVW8JwAm72CE4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IY64vIr41lF7I21c0EjII2zVCS 5cI20VAGYxC7M4xvF2IEb7IF0Fy264kE64k0F24lw4CEF2IF47xS0VAv8wCF04k20xvY0x 0EwIxGrwCF04k20xvE0xIIj40Ec7CjxwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02 F40E14v26r106r1rMI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Jr0_Jr ylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVWUJVWUCwCI42IY6xIIjxv20xvEc7Cj xVAFwI0_Jr0_Gr1lIxAIcVCF04k26cxKx2IYs7xG6rW3Jr0E3s1lIxAIcVC2z280aVAFwI 0_Jr0_Gr1lIxAIcVC2z280aVCY1x0267AKxVWUJVW8JbIYCTnIWIevJa73UjIFyTuYvjfU jiiSDUUUU Date: Sat, 9 May 2020 15:06:51 +0800 (CST) X-CM-SenderInfo: hwhv3imr6f02phpdxzgofq/ This is a multipart message in MIME format. ------=_NextPart_000_0000_01D62613.778669A0 Content-Type: text/plain; charset=\u0026#34;utf-8\u0026#34; Content-Transfer-Encoding: base64 6L+Z5piv5Zyo5rWL6K+V5L2g55qE5biQ5oi36K6+572u5pe2IE1pY3Jvc29mdCBPdXRsb29rIOiH quWKqOWPkemAgeeahOeUteWtkOmCruS7tuOAgg0K ------=_NextPart_000_0000_01D62613.778669A0 Content-Type: text/html; charset=\u0026#34;utf-8\u0026#34; Content-Transfer-Encoding: base64 PGh0bWw+PGJvZHk+PHA+6L+Z5piv5Zyo5rWL6K+V5L2g55qE5biQ5oi36K6+572u5pe2IE1pY3Jv c29mdCBPdXRsb29rIOiHquWKqOWPkemAgeeahOeUteWtkOmCruS7tuOAgg0KPC9wPjwvYm9keT48 L2h0bWw+ ------=_NextPart_000_0000_01D62613.778669A0-- . RETR 6 +OK 1199 octets Received: by ajax-webmail-www.example.com (Coremail) ; Sat, 9 May 2020 15:35:01 +0800 (GMT+08:00) X-Originating-IP: [10.228.12.149] Date: Sat, 9 May 2020 15:35:01 +0800 (GMT+08:00) X-CM-HeaderCharset: UTF-8 From: test02@coremail.cn To: test01@coremail.cn Subject: awesome pop X-Priority: 3 X-Mailer: Coremail Webmail Server Version XT5.0.8a build 20190308(983496cf) Copyright (c) 2002-2020 www.mailtech.cn 126com Content-Type: multipart/alternative; boundary=\u0026#34;----=_Part_5_1271853342.1589009701828\u0026#34; MIME-Version: 1.0 Message-ID: \u0026lt;4d02717c.1.171f85bdbc6.Coremail.test02@coremail.cn\u0026gt; X-Coremail-Locale: zh_CN X-CM-TRANSID:AQAAfwAXM7olXbZeXAAAAA--.0W X-CM-SenderInfo: hwhv3ims6f02phpdxzgofq/1tbiAQADCV0Y3LsAAwAIsi X-Coremail-Antispam: 1Ur529EdanIXcx71UUUUU7IcSsGvfJ3GIAIbVAYFVCjjxCrMI AIbVAFxVCF77xC64kEw24lV2xY67C26IkvcIIF6IxKo4kEV4DvcSsGvfC2KfnxnUU== ------=_Part_5_1271853342.1589009701828 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit This is for pop test! ------=_Part_5_1271853342.1589009701828 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit This is for pop test! ------=_Part_5_1271853342.1589009701828-- . RETR 4 +OK 6469 octets Received: from TINY-DESKTOP (unknown [10.228.12.149]) by www.example.com (Coremail) with SMTP id AQAAfwCngFa5lNFcYQAAAA--.16S2; Tue, 07 May 2019 22:22:50 +0800 (CST) Date: Thu, 7 May 2020 14:22:46 +0800 From: test02 \u0026lt;test02@coremail.cn\u0026gt; To: =?utf-8?Q?test01=40coremail.cn?= \u0026lt;test01@coremail.cn\u0026gt; Message-ID: \u0026lt;305443E1-9258-4260-AA18-1A5CDFBD60EE@coremail.cn\u0026gt; Subject: smtp test X-Mailer: MailMasterPC/4.14.1.1004 (Windows 10 RS5) X-CUSTOM-MAIL-MASTER-SENT-ID: 710FAD56-B1F1-48B7-B72F-543A5E51C5F2 MIME-Version: 1.0 Content-Type: text/html; charset=\u0026#34;utf-8\u0026#34; Content-Transfer-Encoding: base64 X-CM-TRANSID:AQAAfwCngFa5lNFcYQAAAA--.16S2 X-Coremail-Antispam: 1UD129KBjDUn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73 VFW2AGmfu7bjvjm3AaLaJ3UjIYCTnIWjp_UUUol7kC6x804xWl14x267AKxVWUJVW8JwAF c2x0x2IEx4CE42xK8VAvwI8IcIk0rVWUJVWUGwAFIxvE14AKwVWUJVWUGwA2jI8I6cxK62 vIxIIY0VWkZVCq3wA2ocxC64kIII0Yj41l84x0c7CEw4AK67xGY2AK021l84ACjcxK6xII jxv20xvE14v26r1j6r1xM28EF7xvwVC0I7IYx2IY6xkF7I0E14v26r1j6r4UM28EF7xvwV C2z280aVAFwI0_Jr0_Gr1l84ACjcxK6I8E87Iv6xkF7I0E14v26r1j6r4UM2vYz4IE04k2 4VAvwVAKI4IrM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVAqjxCE14ACF2xKxwAqx4 xG6xAIxVCFxsxG0wAqx4xG6I80eVA0xI0YY7vIx2IE14AGzxvEb7x7Mc02F40Ex7xS62Iq YxC26I8Yz20kMcIj6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67AKxVWUJVW8JwAm72 CE4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lF7I21c0EjII2zVCS5cI20VAGYxC7 M4xvF2IEb7IF0Fy264kE64k0F24lFcxC0VAqx4xG64AKrs4lw4CE7480Y4vE14AKx2xKxV C2ax8xMxAIw28IcxkI7VAKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAF wI0_JrI_JrWlx2IqxVCjr7xvwVAFwI0_Jr0_Jr4lx4CE17CEb7AF67AKxVWUJVWUXwCIc4 0Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r1j6r1xMIIF0xvE2Ix0cI8IcVCY1x0267AK xVWUJVW8JwCI42IY6xAIw20EY4v20xvaj40_Wr1j6rW3Jr1lIxAIcVC2z280aVAFwI0_Jr 0_Gr1lIxAIcVC2z280aVCY1x0267AKxVWUJVW8JwCE64xvF2IEb7IF0Fy7YxBIdaVFxhVj vjDU0xZFpf9x0zEzBTiUUUUU= X-CM-SenderInfo: hwhv3ims6f02phpdxzgofq/ PGh0bWw+DQo8aGVhZD4NCiAgICA8bWV0YSBodHRwLWVxdWl2PSdDb250ZW50LVR5cGUnIGNvbnRl bnQ9J3RleHQvaHRtbDsgY2hhcnNldD1VVEYtOCc+DQo8L2hlYWQ+DQo8Ym9keT4NCjxzdHlsZT4N CiAgICBmb250ew0KICAgICAgICBsaW5lLWhlaWdodDogMS42Ow0KICAgIH0NCiAgICB1bCxvbHsN CiAgICAgICAgcGFkZGluZy1sZWZ0OiAyMHB4Ow0KICAgICAgICBsaXN0LXN0eWxlLXBvc2l0aW9u OiBpbnNpZGU7DQogICAgfQ0KPC9zdHlsZT4NCjxkaXYgc3R5bGUgPSAnZm9udC1mYW1pbHk65b6u 6L2v6ZuF6buRLFZlcmRhbmEsJnF1b3Q7TWljcm9zb2Z0IFlhaGVpJnF1b3Q7LFNpbVN1bixzYW5z LXNlcmlmO2ZvbnQtc2l6ZToxNHB4OyBsaW5lLWhlaWdodDoxLjY7Jz4NCiAgICA8ZGl2ID48L2Rp dj48ZGl2PgogICAgPGRpdj4KICAgICAgICA8c3Bhbj5zbXRwIHRlc3QKICAgICAgICA8L3NwYW4+ PC9kaXY+CiAgICA8ZGl2PgogICAgICAgIDxzcGFuPgogICAgICAgICAgICA8YnI+CiAgICAgICAg PC9zcGFuPgogICAgPC9kaXY+CiAgICA8ZGl2IGlkPSJudGVzLXBjbWFjLXNpZ25hdHVyZSIgc3R5 bGU9ImZvbnQtZmFtaWx5Oiflvq7ova/pm4Xpu5EnIj4KICAgICAKICAgIDxkaXYgc3R5bGU9ImZv bnQtc2l6ZToxNHB4OyBwYWRkaW5nOiAwOyAgbWFyZ2luOjA7bGluZS1oZWlnaHQ6MTRweDsiPgog ICAgICAgIDxkaXYgc3R5bGU9InBhZGRpbmctYm90dG9tOjZweDttYXJnaW4tYm90dG9tOjEwcHg7 Ym9yZGVyLWJvdHRvbToxcHggc29saWQgI2U2ZTZlNjtkaXNwbGF5OmlubGluZS1ibG9jazsiPgog ICAgICAgICAgICAgICAgICAgIDxhIGhyZWY9Imh0dHBzOi8vbWFhcy5tYWlsLjE2My5jb20vZGFz aGktd2ViLWV4dGVuZC9odG1sL3Byb1NpZ25hdHVyZS5odG1sP2Z0bElkPTEmYW1wO25hbWU9dGVz dDAyJmFtcDt1aWQ9dGVzdDAyJTQwY29yZW1haWwuY24mYW1wO2ljb25Vcmw9aHR0cHMlM0ElMkYl MkZtYWlsLW9ubGluZS5ub3Nkbi4xMjcubmV0JTJGcWl5ZWxvZ28lMkZkZWZhdWx0QXZhdGFyLnBu ZyZhbXA7aXRlbXM9JTVCJTIydGVzdDAyJTQwY29yZW1haWwuY24lMjIlNUQiIHN0eWxlPSJkaXNw bGF5OmJsb2NrO2JhY2tncm91bmQ6I2ZmZjsgbWF4LXdpZHRoOiA0MDBweDsgX3dpZHRoOiA0MDBw eDtwYWRkaW5nOjE1cHggMCAxMHB4IDA7dGV4dC1kZWNvcmF0aW9uOiBub25lOyBvdXRsaW5lOm5v bmU7LXdlYmtpdC10YXAtaGlnaGxpZ2h0LWNvbG9yOnRyYW5zcGFyZW50Oy13ZWJraXQtdGV4dC1z aXplLWFkanVzdDpub25lICFpbXBvcnRhbnQ7dGV4dC1zaXplLWFkanVzdDpub25lICFpbXBvcnRh bnQ7Ij4KICAgICAgICAgICAgPHRhYmxlIGNlbGxwYWRkaW5nPSIwIiBzdHlsZT0id2lkdGg6IDEw MCU7IG1heC13aWR0aDogMTAwJTsgdGFibGUtbGF5b3V0OiBmaXhlZDsgYm9yZGVyLWNvbGxhcHNl OiBjb2xsYXBzZTtjb2xvcjogIzliOWVhMTtmb250LXNpemU6IDE0cHg7bGluZS1oZWlnaHQ6MS4z Oy13ZWJraXQtdGV4dC1zaXplLWFkanVzdDpub25lICFpbXBvcnRhbnQ7dGV4dC1zaXplLWFkanVz dDpub25lICFpbXBvcnRhbnQ7Ij4KICAgICAgICAgICAgICAgIDx0Ym9keSBzdHlsZT0iZm9udC1m YW1pbHk6ICdQaW5nRmFuZyBTQycsICdIaXJhZ2lubyBTYW5zIEdCJywnV2VuUXVhbllpIE1pY3Jv IEhlaScsICdNaWNyb3NvZnQgWWFoZWknLCAn5b6u6L2v6ZuF6buRJywgdmVyZGFuYSAhaW1wb3J0 YW50OyB3b3JkLXdyYXA6YnJlYWstd29yZDsgd29yZC1icmVhazpicmVhay1hbGw7LXdlYmtpdC10 ZXh0LXNpemUtYWRqdXN0Om5vbmUgIWltcG9ydGFudDt0ZXh0LXNpemUtYWRqdXN0Om5vbmUgIWlt cG9ydGFudDsiPgogICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAg ICAgICAgIDx0ZCB3aWR0aD0iMzgiIHN0eWxlPSJwYWRkaW5nOjA7IGJveC1zaXppbmc6IGJvcmRl ci1ib3g7IHdpZHRoOiAzOHB4OyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGlt ZyB3aWR0aD0iMzgiIGhlaWdodD0iMzgiIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjptaWRkbGU7IHdp ZHRoOiAzOHB4OyBoZWlnaHQ6IDM4cHg7IGJvcmRlci1yYWRpdXM6NTAlOyIgc3JjPSJodHRwczov L21haWwtb25saW5lLm5vc2RuLjEyNy5uZXQvcWl5ZWxvZ28vZGVmYXVsdEF2YXRhci5wbmciPgog ICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAg ICAgIDx0ZCBzdHlsZT0icGFkZGluZzogMCAwIDAgMTBweDsgY29sb3I6ICMzMTM1M2I7Ij4KICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IHN0eWxlPSJmb250LXNpemU6IDE2cHg7 Zm9udC13ZWlnaHQ6Ym9sZDsgd2lkdGg6MTAwJTsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgb3ZlcmZs b3c6aGlkZGVuO3RleHQtb3ZlcmZsb3c6IGVsbGlwc2lzOyI+dGVzdDAyPC9kaXY+CiAgICAgICAg ICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAg ICAgICAgICAgICAgICAgICAgIDx0ciB3aWR0aD0iMTAwJSIgc3R5bGU9ImZvbnQtc2l6ZTogMTRw eCAhaW1wb3J0YW50OyB3aWR0aDogMTAwJTsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAg PHRkIGNvbHNwYW49IjIiIHN0eWxlPSJwYWRkaW5nOjEwcHggMCAwIDA7IGZvbnQtc2l6ZToxNHB4 ICFpbXBvcnRhbnQ7IHdpZHRoOiAxMDAlOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgIDxkaXYgc3R5bGU9IndpZHRoOiAxMDAlO2ZvbnQtc2l6ZTogMTRweCAhaW1wb3J0YW50 O3dvcmQtd3JhcDpicmVhay13b3JkO3dvcmQtYnJlYWs6YnJlYWstYWxsOyI+dGVzdDAyQGNvcmVt YWlsLmNuPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAg ICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAg PC90YWJsZT4KICAgICAgICA8L2E+CiAgICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KICAgIDxkaXYg c3R5bGU9ImZvbnQtc2l6ZToxMnB4O2NvbG9yOiNiNWI5YmQ7bGluZS1oZWlnaHQ6MThweDsiPgog ICAgICAgIDxzcGFuPuetvuWQjeeUsTwvc3Bhbj4KICAgICAgICA8YSBzdHlsZT0idGV4dC1kZWNv cmF0aW9uOiBub25lO2NvbG9yOiM0MTk2ZmY7cGFkZGluZzowIDVweDsiIGhyZWY9Imh0dHBzOi8v bWFpbC4xNjMuY29tL2Rhc2hpL2RscHJvLmh0bWw/ZnJvbT1tYWlsODEiPue9keaYk+mCrueuseWk p+W4iDwvYT4KICAgICAgICA8c3Bhbj7lrprliLY8L3NwYW4+CiAgICA8L2Rpdj4KIDwvZGl2Pgo8 L2Rpdj48IS0t8J+YgC0tPg0KPC9kaXY+DQo8L2JvZHk+DQo8L2h0bWw+ . 发送NOOP命令并无实际作用，主要是用于和服务器保持数据连接不要中断。\nNOOP +OK core mail 我们还可以使用DELE命令来删除指定的邮件，删除后使用LIST查看效果：\nDELE 4 +OK core mail LIST +OK 5 19099 1 7959 2 1199 3 6445 5 2297 6 1199 . 还可以使用RSET命令来进行撤销删除的操作并且使用LIST命令查看效果\nRSET +OK core mail LIST +OK 6 25568 1 7959 2 1199 3 6445 4 6469 5 2297 6 1199 . 最后使用QUIT命令断开连接退出系统：\nQUIT +OK core mail Connection closed by foreign host. ","permalink":"http://localhost:9011/20200511-wireshark-analysis-pop3/","summary":"\u003cp\u003e本文主要使用Wireshark对邮件客户端使用POP3协议收取邮件的过程进行抓包分析并使用telnet命令进行简单操作。\u003c/p\u003e","title":"Wireshark对pop3抓包分析"},{"content":"最近在学习SMTP的时候发现SMTP在使用加密传输的时候涉及到465和587两个端口，网上对两者之间的区别众说纷纭，后来查到了RFC官方文档中对于这个争论较久的问题的定义和详细说明，这里做转载和翻译用于记录。\nRFC8314原文 我们查看RFC8314官方文档中的相关叙述，和该问题相关的主要是3.3、4.2、5.5和7.3这四个部分，由于本文只讨论两个端口的作用和历史缘由，因此涉及到加密过程和原理的4.2、5.5两个部分不在这里提及，其余部分的原文内容如下：\n3.3. Implicit TLS for SMTP Submission\nWhen a TCP connection is established for the \u0026ldquo;submissions\u0026rdquo; service (default port 465), a TLS handshake begins immediately. Clients MUST implement the certificate validation mechanism described in [RFC7817]. Once the TLS session is established, Message Submission protocol data [RFC6409] is exchanged as TLS application data for the remainder of the TCP connection. (Note: The \u0026ldquo;submissions\u0026rdquo; service name is defined in Section 7.3 of this document and follows the usual convention that the name of a service layered on top of Implicit TLS consists of the name of the service as used without TLS, with an \u0026ldquo;s\u0026rdquo; appended.)\nThe STARTTLS mechanism on port 587 is relatively widely deployed due to the situation with port 465 (discussed in Section 7.3). This differs from IMAP and POP services where Implicit TLS is more widely deployed on servers than STARTTLS. It is desirable to migrate core protocols used by MUA software to Implicit TLS over time, for consistency as well as for the additional reasons discussed in Appendix A. However, to maximize the use of encryption for submission, it is desirable to support both mechanisms for Message Submission over TLS for a transition period of several years. As a result, clients and servers SHOULD implement both STARTTLS on port 587 and Implicit TLS on port 465 for this transition period. Note that there is no significant difference between the security properties of STARTTLS on port 587 and Implicit TLS on port 465 if the implementations are correct and if both the client and the server are configured to require successful negotiation of TLS prior to Message Submission.\nNote that the \u0026ldquo;submissions\u0026rdquo; port provides access to a Message Submission Agent (MSA) as defined in [RFC6409], so requirements and recommendations for MSAs in that document, including the requirement to implement SMTP AUTH [RFC4954] and the requirements of Email Submission Operations [RFC5068], also apply to the submissions port.\nSee Sections 5.5 and 4.2 for additional information on client certificate authentication. See Section 7.3 for port registration information.\n7.3. Submissions Port Registration\nIANA has assigned an alternate usage of TCP port 465 in addition to the current assignment using the following template [RFC6335]:\n​ Service Name: submissions ​ Transport Protocol: TCP ​ Assignee: IESG iesg@ietf.org ​ Contact: IETF Chair chair@ietf.org ​ Description: Message Submission over TLS protocol ​ Reference: RFC 8314 ​ Port Number: 465\nThis is a one-time procedural exception to the rules in [RFC6335]. This requires explicit IESG approval and does not set a precedent. Note: Since the purpose of this alternate usage assignment is to align with widespread existing practice and there is no known usage of UDP port 465 for Message Submission over TLS, IANA has not assigned an alternate usage of UDP port 465.\nHistorically, port 465 was briefly registered as the \u0026ldquo;smtps\u0026rdquo; port. This registration made no sense, as the SMTP transport MX infrastructure has no way to specify a port, so port 25 is always used. As a result, the registration was revoked and was subsequently reassigned to a different service. In hindsight, the \u0026ldquo;smtps\u0026rdquo; registration should have been renamed or reserved rather than revoked. Unfortunately, some widely deployed mail software interpreted \u0026ldquo;smtps\u0026rdquo; as \u0026ldquo;submissions\u0026rdquo; [RFC6409] and used that port for email submission by default when an end user requested security during account setup. If a new port is assigned for the submissions service, either (a) email software will continue with unregistered use of port 465 (leaving the port registry inaccurate relative to\nde facto practice and wasting a well-known port) or (b) confusion between the de facto and registered ports will cause harmful interoperability problems that will deter the use of TLS for Message Submission. The authors of this document believe that both of these outcomes are less desirable than a \u0026ldquo;wart\u0026rdquo; in the registry documenting real-world usage of a port for two purposes. Although STARTTLS on port 587 has been deployed, it has not replaced the deployed use of Implicit TLS submission on port 465.\n个人理解 将上面的几段原文阅读整合之后，个人的理解如下：\n首先要说明原文中多次出现的submission的意思实际上是指客户端使用SMTP协议来对服务端进行数据传输，下面提及的SMTPS等价于原文的TLS submission。\n当年IANA为TCP的465端口注册了用途，用于SMTP的TLS加密传输，且没有指定UDP的465端口用途，这就是465端口在历史上被用为SMTPS端口的由来。\n为什么说是历史上呢，因为这个注册在不久之后就被撤销了，也就是说这个注册没用了，465端口要被回收拿去给其他的服务用了。而撤销的原因是*“这种注册没有意义，因为SMTP传输MX基础结构无法指定端口，因此始终使用端口25。（This registration made no sense, as the SMTP transport MX infrastructure has no way to specify a port, so port 25 is always used. ）”*\n但是后来又觉得当时应该把这个465的SMTPS（隐式TLS）端口保留或者是重命名而不是撤销，因为已经有许多邮件服务软件使用了465端口作为SMTPS（隐式TLS）的传输端口，如果为SMTPS（隐式TLS）服务分配了新端口，则已经使用被注销的465端口作为SMTPS服务端口的电子邮件软件相当于是使用了一个和实际SMTPS（隐式TLS）端口不匹配的端口，并且实际使用端口和理论注册端口的不同也有可能导致各种问题。\n因此尽管已在端口587上部署了STARTTLS，但它尚未取代在端口465上部署的SMTPS（隐式TLS）的使用。\n随着时间的推移，出于一致性以及其他原因，需要将MUA软件使用的核心协议迁移到隐式TLS。但是，为了最大程度地使用加密来进行提交，需要在几年的过渡期内支持两种通过TLS进行消息提交的机制。因此，在此过渡期间，客户端和服务器应在端口587上实施STARTTLS，并在端口465上实施隐式TLS。请注意，如果实施正确且客户端和服务器都配置为要求在消息提交之前成功协商TLS，则端口587上的STARTTLS和端口465上的隐式TLS的安全属性之间没有显着差异。\nA mail user agent (MUA) is a program that allows you to receive and send e-mail messages; it\u0026rsquo;s usually just called an e-mail program.\nMUA软件即指我们平时使用的集收发读写邮件于一体的邮件客户端软件。\n简而言之，465端口最开始被注册用于SMTPS，随后被撤销，但是因为已经被用了，现在又恢复了，并且还多增加了一个587端口用于STARTTLS加密传输，并且在配置正确的前提下两者一样安全。目前的主要任务是把邮件从明文传输迁移到加密传输，在迁移的过渡期间应当支持587端口的STARTTLS和465端口的隐式TLS。\n","permalink":"http://localhost:9011/20200508-rfc8314-465-587-part-translation/","summary":"\u003cp\u003e最近在学习SMTP的时候发现SMTP在使用加密传输的时候涉及到465和587两个端口，网上对两者之间的区别众说纷纭，后来查到了RFC官方文档中对于这个争论较久的问题的定义和详细说明，这里做转载和翻译用于记录。\u003c/p\u003e","title":"RFC8314文档中对465端口和587端口的阐述"},{"content":"本文主要使用Wireshark对邮件客户端使用SMTP协议发送邮件的过程进行抓包分析并使用telnet命令进行简单操作。\nSMTP简介 简单邮件传输协议（英语：Simple Mail Transfer Protocol，缩写：SMTP）是一个在互联网上传输电子邮件的标准。\nSMTP是一个相对简单的基于文本的协议。在其之上指定了一条消息的一个或多个接收者（在大多数情况下被确认是存在的），然后消息文本会被传输。可以很简单地通过telnet程序来测试一个SMTP服务器。SMTP使用TCP端口25。要为一个给定的域名决定一个SMTP服务器，需要使用DNS的MX记录。\n无论使用POP3还是IMAP4来获取消息，客户端均使用SMTP协议来发送消息。邮件客户端可能是POP客户端或者IMAP客户端，但都会使用SMTP。\nWireshark抓包分析 由于大多数人平时接触到的程序除了邮件客户端在发件的时候会使用SMTP协议之外，其余的几乎不会用到，因此相关的抓包分析非常简单，只需要在Wireshark的Filter中设定抓取的协议为SMTP即可顺利过滤出绝大多数的包，如果担心过滤效果不佳可以再加上邮件服务器的IP地址。\n点击开始抓包之后，我们使用配置了SMTP的邮件客户端来发送一封邮件即可抓取到全部的数据包。\n220 注意这里为了方便分析数据包内容并没有使用加密协议，抓取到的一个完整的SMTP发送过程如下：\n需要注意的是这里筛掉了TCP协议，因此最开始的TCP三次握手建立连接在这里是看不到的，因此在图中的第一个包就是SMTP的服务器端发送给客户端的数据包：\n响应代码220表示连接建立成功，后面的Anti-spam表明是该邮件系统的反垃圾邮件模块，即猜测在这里就有反垃圾邮件模块来抵挡垃圾邮件的攻击。\nEHLO/HELO 服务端返回220代码之后，客户端继续发送请求，首先是发送EHLO命令：\n一般来说客户端和SMTP服务端建立连接之后就需要发送EHLO或者是HELO命令，后面附带的参数是，即相当于客户端的主机域名或者是主机名，这一步的主要作用是声明身份，EHLO/HELO命令相当于是HELLO命令，两者之间的主要区别是EHLO带身份验证而HELO不带身份验证，因此EHLO要更加安全\n在服务器上使用telnet命令可以很直接的看到两者之间的区别，EHLO会返回身份验证方式而HELO命令则直接返回250 OK代码。\nAUTH 服务端接收到客户端的EHLO请求之后，返回了一个250代码并且附带了支持的身份验证方式：\n客户端使用AUTH命令进行身份验证：\n身份验证成功后会返回235的成功代码： 到这里就完成了和SMTP服务器建立连接和身份验证的步骤。\nMAIL FROM 接下来客户端发送MAIL FROM命令声明邮件的发件人：\n服务器返回250代码确定操作成功：\nRCPT TO 然后客户端发送RCPT TO命令声明邮件的收件人：\n服务器返回250代码确定操作成功：\nDATA 客户端使用DATA命令，告知服务器要开始传输邮件的正文内容：\n服务端返回354代码，告知邮件的内容结束以\u0026lt;CR\u0026gt;\u0026lt;LF\u0026gt;.\u0026lt;CR\u0026gt;\u0026lt;LF\u0026gt;为标记：\nwindows中的换行符标记为CRLF而Linux中的为LF，\u0026lt;CR\u0026gt;\u0026lt;LF\u0026gt;.\u0026lt;CR\u0026gt;\u0026lt;LF\u0026gt;即表明当服务器收到单独一行的.即完成本次邮件正文传输。\n客户端接收到254代码后，开始传输邮件内容：\n我们可以看到在上面的数据包中包含了SMTP和IMF两个部分，因为抓包发送的邮件内容都是文本，所以直接使用IMF协议就可以传输，而SMTP协议中的报文内容则是DATA命令的终止标志.\nQUIT 在客户端发送完邮件内容之后，还会接着发送一个QUIT命令来表示结束这次的SMTP传输：\n服务器在接受到数据之后会返回250代码表示接受成功并且再返回221代码表示结束本次SMTP传输。\ntelnet测试SMTP 由于SMTP协议十分简单且没有加密，所以我们使用telnet命令连接到邮件服务器也可以执行同样的操作：\n# 使用telnet和服务器建立连接 telnet localhost 25 # 这里根据邮件系统的不同选择EHLO或者是HELO HELO TINY-DESKTOP EHLO TINY-DESKTOP # 使用EHLO还可以进行身份验证 # 使用AUTH命令进行身份验证和登录 # 需要注意要将账号密码转码成base64编码的文本才可以成功登录 AUTH LOGIN # 转码前的账号test01@coremail.cn dGVzdDAxQGNvcmVtYWlsLmNu # 转码前的密码password01 cGFzc3dvcmQwMQ== # 确定发件人 MAIL FROM: \u0026lt;test01@coremail.cn\u0026gt; # 确定收件人 RCPT TO: \u0026lt;test02@coremail.cn\u0026gt; # 开始传输正文 DATA Subject: telnet test mail From:\u0026#34;test01\u0026#34;\u0026lt;test01@coremail.cn\u0026gt; To:\u0026#34;test02\u0026#34;\u0026lt;test02@coremail.cn\u0026gt; Hello, This is a smtp test via telnet. Goodbye. . # 结束传输并退出 QUIT 如图中所示使用AUTH命令登录认证成功之后是会收到服务器返回的235代码的。我们操作完成之后查看邮箱确定收到了测试的邮件则说明操作成功。\n在使用AUTH认证的情况如下：\n实际上返回的334代码后面的dXNlcm5hbWU6和UGFzc3dvcmQ6就是base64编码的username和Password。\n由于明文传输的不安全性，现在单纯的SMTP已经几乎没有使用了，使用的较多的都是它的加密版本SMTPS（465端口，SSL加密，不推荐使用）和STARTTLS（587端口，TLS加密，推荐使用）。\n","permalink":"http://localhost:9011/20200507-wireshark-analysis-smtp/","summary":"\u003cp\u003e本文主要使用Wireshark对邮件客户端使用SMTP协议发送邮件的过程进行抓包分析并使用telnet命令进行简单操作。\u003c/p\u003e","title":"Wireshark对SMTP抓包分析"},{"content":"本文主要包括LVS DR模式在IPv4网络和IPv6网络下配置的一些差异对比。\nLVS DR via IPv4 首先这里我们使用三台主机配置lvs集群，对应的系统和内核版本如下\n[root@lvs81 ~]# lsb_release -a LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch Distributor ID: CentOS Description: CentOS release 6.10 (Final) Release: 6.10 Codename: Final [root@lvs81 ~]# uname -r 2.6.32-754.28.1.el6.x86_64 对应的IP地址如下\n192.168.100.80 lvsipv4 192.168.100.81 lvs81 192.168.100.82 lvs82 192.168.100.83 lvs83 240e:c331:dead:beef::3c80 lvsipv6 240e:c331:dead:beef::3c81 lvs81 240e:c331:dead:beef::3c82 lvs82 240e:c331:dead:beef::3c83 lvs83 其中lvs81对应为LB，lvs82和lvs83为RS，RS上部署nginx分别监听本机对应ipv4和ipv6地址的80端口用于测试。\nLB配置 此处略去ipvs模块的加载和ipvsadm的安装。\nifconfig eth1:1 192.168.100.80 broadcast 192.168.100.81 netmask 255.255.255.255 up ipvsadm -A -t 192.168.100.80:80 -s rr ipvsadm -a -t 192.168.100.80:80 -r 192.168.100.82 -g ipvsadm -a -t 192.168.100.80:80 -r 192.168.100.83 -g 修改ip_vs模块对应的哈希表的大小\necho \u0026#39;options ip_vs conn_tab_bits=20\u0026#39; \u0026gt;/etc/modprobe.d/ipvs.conf RS配置 ifconfig lo:1 192.168.100.80 broadcast 192.168.100.80 netmask 255.255.255.255 up 同时需要修改内核参数，修改ARP请求对应的响应限制\n$ cat /etc/sysctl.conf | grep arp net.ipv4.conf.all.arp_ignore=1 net.ipv4.conf.lo.arp_ignore=1 net.ipv4.conf.all.arp_announce=2 net.ipv4.conf.lo.arp_announce=2 测试 ipv6和ipv4的一些差别 ipv6中没有广播地址的概念\n在IPv4中广泛的使用单播、广播、组播的方式。而在IPv6的应用环境中，使用单播，组播、任意播的新方式。\nipv6中没有ARP协议，使用了升级版本的邻居发现协议（NDR）协议\n邻居发现的这些功能主要通过邻居发现协议报文实现，邻居发现协议分组装载在ICMPv6分组内部。邻居发现协议中定义了五种ICMPv6分组类型，它们的名称和作用如下。\n路由器请求（RS，Router Solicitation）报文 当主机的接口开始工作时，主机会发送路由器请求消息，请求可能存在的路由器答复，即是为了探寻与自己相连的路由器的情况。\n路由器通告（RA，Router Advertisement）报文 路由器通告由路由器周期性地发送，也可作为收到的路由器请求（来自主机）的响应发送出去。每个路由器通告中还可能包含前缀信息、链路配置和IPv6协议参数等信息。路由器通告宣告着路由器的存在和一些自身配置。\n邻居请求（NS，Neighbor Solicitation）报文 节点可以发送邻居请求用以解析另一个节点的链路层地址和验证另一个节点的可达性。邻居请求还用来验证一条特定链路上的地址是否是唯一的：当节点上产生了一个新地址时，会向整个链路上的其它节点发送邻居请求，询问该地址是否已经被占用。如果该地址已经被占用，那么占用该地址的节点会回复邻居通告，否则多次检测后没有收到回复，则该新地址生效。\n邻居通告（NA，Neighbor Advertisement）报文 节点可以发送邻居通告来响应邻居请求分组，它还会发送未经请求的邻居通告，将节点的链路层地址变化通知其它节点。\n重定向（Redirect）报文 路由器通过重定向报文通知主机，对于一条特定的路由，如果不是最佳路由，则通知主机最佳路由及下一跳。\n实际上在IPv6协议过程中使用NDP协议根据IP查找MAC的过程主要使用的是NS和NA两种报文，它们相当于ARP协议中的ARP Request和ARP Reply。\nipv6的地址分类 ipv6的地址表示 IPv6地址的长度是IPv4（32位）的4倍，达到了128位，复杂程度大大提升，表达上使用了8组不区分大小写的16进制数来表示，每组由4个16进制数组成。IPv6地址一般使用“零缩法”来表示，主要限制如下：\n16位地址块中的前导0可以省略，如果16位全为0，可以只写一个0 当IPv6地址中有多个连续的，值为0的16位地址块时，可以用1个双冒号转换这 些连续的0，但双冒号在一个IPv6地址中只能出现一次，也就是在一个IPv6地址只 能用一个双冒号转换一个连续的、值为0的16位地址块 不能用双冒号转换属于16位地址块中一部分的0，即使是地址块中的最后一个16 进制数0 ipv6地址类型 IPv6协议主要定义了三种地址类型：单播地址（Unicast Address）、组播地址 （Multicast Address）和任播地址（Anycast Address）。相比IPv4而言取消了广播地址，新增了任意播，而IPv4中的广播功能在IPv6中主要通过组播实现。由于两者的组播标准并无过多的改动，因此IPv6中的组播地址其实与IPv4中的组播地址是类似的。\n单播地址：用来唯一标识一个接口，类似于IPv4中的单播地址。发送到单播地址 的数据报文将被传送给此地址所标识的一个接口。 组播地址：用来标识一组接口（通常这组接口属于不同的节点），类似于IPv4中的组播地址。发送到组播地址的数据报文被传送给此地址所标识的所有接口。 任播地址：用来标识一组接口（通常这组接口属于不同的节点）。发送到任播地 址的数据报文被传送给此地址所标识的一组接口中距离源节点最近（根据使用的路 由协议进行度量）的一个接口。 整个IPv6单播地址包括以下五个类型：全局单播地址、链路本地地址、站点本地地址、特殊地址、兼容性地址。由于篇幅有限，这里重点讲一下和LVS要用到的全局单播地址和链路本地地址。\n全局单播地址等同于IPv4中的公网地址，可以在IPv6 Internet上进行全局路由和访问。\n在IPv6中，本地单播地址就是指本地网络使用的单播地址，也就是IPV4地址中经常 所说的局域网专用地址。本地单播地址又有两种，分别是链路本地地址和站点本地 地址。每个接口上至少要有一个链路本地单播地址，另外还可分配任何类型（单 播、任播和组播）或范围的IPv6地址。\n链路本地地址仅用于单个链路（注意，这里的“链路”就相当于IPv4中的子网）， 不能在不同子网中路由。结点使用链路本地地址与同一个链路上的相邻结点进行通 信。\n链路本地地址等效于169.254.0.0/16网段的自动专用IP寻址（APIPA）IPv4地址 （在运行Windows系统的计算机上自动配置）。邻居发现过程要求使用链路本地地 址，该地址始终自动配置（也就是无须手工配置），即使所有其他单播地址都不存 在也是如此。\n从图中可以看出，链路本地地址始终以“1111111010”（FE80）开头。后面紧跟着 的54位均为0，最后的64位是用来标识接口，称为接口ID。在IPv6单播地址中的接 口ID用来标识一个链路上的接口，不能在同一个链路上为不同结点分配相同的接口 ID。所以对于链路本地地址的前缀始终是FE80：/64。IPv6路由器永远不会将链路 本地通信转发出该链路。\nipv6的NDP协议 要理解IPv6的组播，首先需要明白三个关键点：\n任何节点都能够成为一个多播组成员也叫做组播组成员\n源节点可以发送数据包到多播组\n在一个多播组的节点都能收到发往该组播组的数据\n在IPv4环境的的ARP地址解析协议是使用目标地址为广播（255.255.255.255或者FFFF.FFFF.FFFF）将MAC地址请求消息发送到整个以太网链路上的所有主机，即便是LB（lvs81）主机请求RS（lvs82）主机的MAC，RS（lvs82）主机与RS（lvs83）主机是同样会收到这个请求广播，从性能与效率上讲这明显不科学。\n所以在IPv6的环境中放弃了广播的的方式，而是采用组播方式将MAC地址的解析请求，以点对点的形式直接组播到lvs82主机的请求节点组播地址。而不再将请求消息发送到无关的lvs83主机，所以IPv6的节点请求的确是高效率去替代IPv4的ARP协议。总而言之就是使用点到点的方式去代替广播。\n那么对于IPv6来说，发送组播数据包的时候，也是不知道目标IP主机的MAC地址的，这时候就要用到IPv6中对应的组播IP地址和组播MAC地址了：\n从上图中我们可以看到，组播的IPv6地址是根据目标IPv6地址生成的，组播IPv6地址会取目标IPv6地址的低24位（2进制）来加上固定的前缀FF02::1:FF（16进制）从而生成对应的组播地址，而组播的MAC地址则是根据组播的IPv6地址的低32位（2进制）再加上固定前缀3333（16进制）生成。\n由于在同一个局域网中，IPv6地址低24位相同的概率十分小，因此组播包发送出去之后，接受该组播包的客户端几乎只有一个，就无限接近于一对一的单播效果，从而有效地避免了ARP协议使用广播方式的诸多问题。\nLVS DR via IPv6 同样还是使用上面的三台机器，由于IPv6中没有ARP的困扰，所以在配置的时候反而要比IPv4更加简单，我们只需要直接添加ip即可：\nIPv6模式下还使用了elrepo库中的lt版本的4.4.219-1内核进行测试，两者结果一致。均可直接正常工作。\nLB配置 不需要对内核参数进行任何修改，确认启用了ipv6网络即可\nifconfig eth2 inet6 add 240e:c331:dead:beef::3c80/128 ipvsadm -A -t [240e:c331:dead:beef::3c80]:80 -s rr ipvsadm -a -t [240e:c331:dead:beef::3c80]:80 -r [240e:c331:dead:beef::3c82]:80 -g ipvsadm -a -t [240e:c331:dead:beef::3c80]:80 -r [240e:c331:dead:beef::3c83]:80 -g RS配置 不需要对内核参数进行任何修改，确认启用了ipv6网络即可\nifconfig lo inet6 add 240e:c331:dead:beef::3c80/128 测试 使用客户端进行访问，可以看到返回的是ipv6的结果，并且产生了预设的轮询效果。\n查看ipv6网络中的邻居表，可以看到对应的两个RS状态为可达（REACHABLE）,并且对应的MAC地址为RIP的ipv6地址所在的网卡。此外和IPv4中不同的是，对应的两台RS上面的Link-local地址在邻居表中的状态也显示为可达（REACHABLE）。\n查看LB上面的ipvsadm中的连接状态，我们可以看到连接已经建立并且确实转发到了后端的RS上。\n","permalink":"http://localhost:9011/20200505-lvs-deploy-dr-mode-in-ipv6/","summary":"\u003cp\u003e本文主要包括LVS DR模式在IPv4网络和IPv6网络下配置的一些差异对比。\u003c/p\u003e","title":"IPv6网络配置LVS的DR模式"},{"content":"本文主要包括tomcat服务器的web应用部署和管理，以及server.xml的主要配置。\nmanager应用 tomcat本身内置了两个web应用，专门用来管理tomcat，它们分别是host-manager（管理virtual host）和manager（管理web应用）。\nhttp://localhost:8080/host-manager/html http://localhost:8080/manager/html 在启动tomcat之后，我们访问上面的这两个网址可以发现被403了。因为我们还没有在配置文件中增加相关的用户，为了保证安全，这里的用户默认都是禁用的，我们需要自己创建。\n我们编辑tomcat目录下的conf子目录中的tomcat-users.xml，添加对应的配置即可：\n\u0026lt;!--admin对应的是host-manager的用户--\u0026gt; \u0026lt;!--allows access to the HTML GUI--\u0026gt; \u0026lt;role rolename=\u0026#34;admin-gui\u0026#34;/\u0026gt; \u0026lt;!--allows access to the text interface--\u0026gt; \u0026lt;role rolename=\u0026#34;admin‐script\u0026#34;/\u0026gt; \u0026lt;!--manager对应的是manager的用户--\u0026gt; \u0026lt;!--allows access to the HTML GUI and the status pages--\u0026gt; \u0026lt;role rolename=\u0026#34;manager-gui\u0026#34;/\u0026gt; \u0026lt;!--allows access to the text interface and the status pages--\u0026gt; \u0026lt;role rolename=\u0026#34;manager‐script\u0026#34;/\u0026gt; \u0026lt;!--allows access to the JMX proxy and the status pages--\u0026gt; \u0026lt;role rolename=\u0026#34;manager-jmx\u0026#34;/\u0026gt; \u0026lt;!--allows access to the status pages only--\u0026gt; \u0026lt;role rolename=\u0026#34;manager-status\u0026#34;/\u0026gt; \u0026lt;!--我们这里添加一个用户，然后定义角色即可--\u0026gt; \u0026lt;user username=\u0026#34;tinychen\u0026#34; password=\u0026#34;tinychen#321\u0026#34; roles=\u0026#34;admin‐gui,manager-gui\u0026#34;/\u0026gt; Users with the admin-gui role should not be granted the admin-script role.\n注意被授予admin-gui权限的用户不应该授予admin-script权限\nUsers with the manager-gui role should not be granted either the manager-script or manager-jmx roles.\n注意被授予manager-gui权限的用户不应该授予manager-script或manager-jmx权限\ntomcat9中默认是只允许部署tomcat的机器访问manger和host-manager的页面的，因此我们需要修改tomcat目录下对应的web应用的配置文件：\nvim /home/tomcat9/webapps/host-manager/META-INF/context.xml vim /home/tomcat9/webapps/manager/META-INF/context.xml 然后修改里面限制的IP地址为全部或者自己的IP地址即可。\n\u0026lt;Context antiResourceLocking=\u0026#34;false\u0026#34; privileged=\u0026#34;true\u0026#34; \u0026gt; \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.RemoteAddrValve\u0026#34; allow=\u0026#34;127\\.\\d+\\.\\d+\\.\\d+|::1|0:0:0:0:0:0:0:1\u0026#34; /\u0026gt; \u0026lt;Manager sessionAttributeValueClassNameFilter=\u0026#34;java\\.lang\\.(?:Boolean|Integer|Long|Number|String) |org\\.apache\\.catalina\\.filters\\.CsrfPreventionFilter\\$LruCache(?:\\$1)?|java\\.util\\.(?:Linked)?HashMap\u0026#34;/\u0026gt; \u0026lt;/Context\u0026gt; # 将allow参数改为 \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.RemoteAddrValve\u0026#34; allow=\u0026#34;^.*$\u0026#34; /\u0026gt; 然后我们就可以访问web界面来查看tomcat服务器的运行状态了。\nmanager的web界面 host-manager的web界面 Tomcat的容器架构 Tomcat设计了4种容器，分别是Engine、Host、Context和Wrapper。这4种容器是父子关系， Tomcat通过一种分层的架构，使得Servlet容器具有很好的灵活性。\n如上图所示，我们可以看到：\n一台机器上可以通过设置不同的CATALINA_BASE来运行多个tomcat实例，即可以运行多个server 一个server中只有一个Engine，而Engine就是实现了servlet规范的引擎，这里就是Catalina 一个engine中可以包含多个host，即和apache、nginx等服务器相同，可以配置多个virtual host站点 一个host中可以包含多个context，即可以包含多个web应用 一个warpper表示一个Servlet，wrapper 作为容器中的最底层，不能包含子容器 Tomcat使用组合模式来管理这些容器，所有容器组件都实现了Container接口，因此组合模式可以使得用户对单容器对象（最底层的Wrapper）和组合容器对象（Context、Host或者Engine）的使用具有一致性。\nTomcat 服务器的配置主要集中于 tomcat/conf 下的 catalina.policy、 catalina.properties、context.xml、server.xml、tomcat-users.xml、web.xml 文件。\nTomcat的这一设计思想在其配置文件server.xml中得到了很好的诠释，server.xml 是tomcat 服务器的核心配置文件，包含了Tomcat的 Servlet 容器 （Catalina）的所有配置。下面我们先来了解一下server.xml文件中的一些主要配置。\nserver.xml server.xml整体架构 首先我们需要知道server.xml中的xml代码块分类，tomcat官网将其主要分为四类：\nTop Level Elements：server块是整个配置文件的根元素，而service块代表与引擎关联的一组连接器（connector）。 Connectors ：表示外部客户端向特定服务发送请求和接收响应的接口（比如我们之前提到的coyote连接器以及对应的NIO等IO模式都是整个范畴内的概念）。 Containers：容器（Container）负责处理传入的请求并创建相应的响应。Engine处理对Service的所有请求，Host处理对特定virtual host的所有请求，而Context处理对特定Web应用程序的所有请求。 Nested Components：表示可以嵌套在Container元素内的元素。 注意一些元素可以嵌套在任何Container中，而另一些元素只能嵌套在Context中。 Top Level Elements Server块 Server块代表的是整个catalina servlet容器。因此，它必须是conf/server.xml配置文件中最外面的单个元素。它的属性代表了整个servlet容器的特征。Tomcat9中默认的配置文件中Server块内嵌的子元素为 Listener、GlobalNamingResources、Service（可以嵌套多个）。具体的每个属性参数我们可以查询官网，下面解释默认的参数配置。\n\u0026lt;Server port=\u0026#34;8005\u0026#34; shutdown=\u0026#34;SHUTDOWN\u0026#34;\u0026gt; \u0026lt;!-- port ： Tomcat监听的关闭服务器的端口 shutdown ： 关闭服务器的指令字符串 --\u0026gt; \u0026lt;!-- 以日志形式输出服务器、操作系统、JVM的版本信息 --\u0026gt; \u0026lt;Listener className=\u0026#34;org.apache.catalina.startup.VersionLoggerListener\u0026#34; /\u0026gt; \u0026lt;!-- 启动和停止APR。如果找不到APR库会输出日志但并不影响tomcat正常启动 --\u0026gt; \u0026lt;Listener className=\u0026#34;org.apache.catalina.core.AprLifecycleListener\u0026#34; SSLEngine=\u0026#34;off\u0026#34; /\u0026gt; \u0026lt;!-- 注意这里的SSLEngine默认是打开的（on） 如果启用了apr作为连接器的协议 但是只配置了http而没有配置https 则会报错 --\u0026gt; \u0026lt;!-- 用于避免JRE内存泄漏问题 --\u0026gt; \u0026lt;Listener className=\u0026#34;org.apache.catalina.core.JreMemoryLeakPreventionListener\u0026#34; /\u0026gt; \u0026lt;!-- 用户加载（服务器启动）和销毁（服务器停止）全局命名服务 --\u0026gt; \u0026lt;Listener className=\u0026#34;org.apache.catalina.mbeans.GlobalResourcesLifecycleListener\u0026#34; /\u0026gt; \u0026lt;!-- 用于在Context停止时重建Executor池中的线程， 以避免ThreadLocal相关的内 存泄漏 --\u0026gt; \u0026lt;Listener className=\u0026#34;org.apache.catalina.core.ThreadLocalLeakPreventionListener\u0026#34; /\u0026gt; \u0026lt;!-- GlobalNamingResources中定义了全局命名服务： --\u0026gt; \u0026lt;GlobalNamingResources\u0026gt; \u0026lt;Resource name=\u0026#34;UserDatabase\u0026#34; auth=\u0026#34;Container\u0026#34; type=\u0026#34;org.apache.catalina.UserDatabase\u0026#34; description=\u0026#34;User database that can be updated and saved\u0026#34; factory=\u0026#34;org.apache.catalina.users.MemoryUserDatabaseFactory\u0026#34; pathname=\u0026#34;conf/tomcat-users.xml\u0026#34; /\u0026gt; \u0026lt;!--这里定义的文件就是我们前面配置manager和host manager的用户的文件--\u0026gt; \u0026lt;/GlobalNamingResources\u0026gt; \u0026lt;Service\u0026gt; ... \u0026lt;/Service\u0026gt; \u0026lt;/Server\u0026gt; Service块 Service元素用于创建 Service 实例，默认使用 org.apache.catalina.core.StandardService。 默认情况下，Tomcat9中默认仅指定了Service的名称为Catalina。\n\u0026lt;Service name=\u0026#34;Catalina\u0026#34;\u0026gt; ... \u0026lt;/Service\u0026gt; Service 可以内嵌的元素为 ： Listener、Executor、Connector、Engine ，详细的参数可以点击这里查看官网\nListener 用于为Service 添加生命周期监听器 Executor 用于配置Service 共享线程池 Connector 用于配置 Service 包含的链接器 Engine 用于配置Service中连接器（connector）对应的Servlet 容器引擎 Executor executor表示可组件之间Tomcat中共享的线程池。默认情况下，Service并未添加共享线程池配置。executor实现了tomcat中的org.apache.catalina.Executor接口。 如果不配置共享线程池，那么Catalina 各组件在用到线程池时会独立创建。由于executor是Service元素的嵌套元素。为了使它能够被Connector使用，Executor元素必须出现在server.xml中的Connector元素之前。下面展示的是一个简单的executor的配置，具体的配置参数可以点这里查看官网：\n\u0026lt;Executor name=\u0026#34;tomcatThreadPool\u0026#34; namePrefix=\u0026#34;catalina‐exec‐\u0026#34; maxThreads=\u0026#34;200\u0026#34; minSpareThreads=\u0026#34;100\u0026#34; maxIdleTime=\u0026#34;60000\u0026#34; maxQueueSize=\u0026#34;Integer.MAX_VALUE\u0026#34; prestartminSpareThreads=\u0026#34;false\u0026#34; threadPriority=\u0026#34;5\u0026#34; className=\u0026#34;org.apache.catalina.core.StandardThreadExecutor\u0026#34;/\u0026gt; 属性 含义 name 线程池名称，用于Connector中指定。 namePrefix 所创建的每个线程的名称前缀，一个单独的线程名称为 namePrefix+threadNumber。 daemon 是否作为守护线程（类似于守护进程），默认为true maxThreads 线程池中最大线程数。 minSpareThreads 活跃线程数，也就是核心池线程数，这些线程不会被销毁，会一直存在。 maxIdleTime 线程空闲时间，超过该时间后，空闲线程会被销毁，默 认值为6000（1分钟），单位毫秒。 maxQueueSize 在被执行前最大线程排队数目，默认为int的最大值，也就是广义的无限。除非特殊情况，这个值不需要更改， 否则会有请求不会被处理的情况发生。 prestartminSpareThreads 启动线程池时是否启动 minSpareThreads部分线程。 默认值为false，即不启动。 threadPriority 线程池中线程优先级，默认值为5，值从1到10。 className 线程池实现类，未指定情况下，默认实现类为 org.apache.catalina.core.StandardThreadExecutor。 如果想使用自定义线程池首先需要实现 org.apache.catalina.Executor接口。 Connector Connector 用于创建链接器实例。默认情况下，server.xml 配置了两个链接器，一个支 持HTTP协议，一个支持AJP协议。因此大多数情况下，我们并不需要新增链接器配置， 只是根据需要对已有链接器进行优化。\n\u0026lt;Connector port=\u0026#34;8080\u0026#34; protocol=\u0026#34;HTTP/1.1\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; redirectPort=\u0026#34;8443\u0026#34; /\u0026gt; \u0026lt;Connector port=\u0026#34;8009\u0026#34; protocol=\u0026#34;AJP/1.3\u0026#34; redirectPort=\u0026#34;8443\u0026#34; /\u0026gt; port为监听的端口，如果设置为0，Tomcat将会随机选择一个可用的端口号给当前Connector 使用 protocol为Connector的协议，这里默认的是HTTP和AJP两种协议，后面可以指定对应协议的不同版本，默认情况下会检测本机是否配置了APR库，如果有并且 useAprConnector设置为true则会默认使用APR模式的IO协议，如果无则会使用NIO模式 connectionTimeOut ：Connector 接收链接后的等待超时时间，单位为毫秒。 -1表示永不超时 redirectPort：当前Connector 不支持SSL请求， 接收到了一个请求， 并且也符合 security-constraint 约束， 需要SSL传输，Catalina自动将请求重定向到指定的端口 executor ： 指定前面提到的共享线程池的名称，也可以通过maxThreads、minSpareThreads 等属性对该connector进行单独配置对应的内部线程池 URIEncoding : 用于指定编码URI的字符编码， Tomcat8.x和Tomcat9.x版本默认的编码为 UTF-8 , Tomcat7.x版本默认为ISO-8859-1 engine Engine 作为Servlet 引擎的顶级元素，内部可以嵌入： Cluster、Listener、Realm、 Valve和Host。\n\u0026lt;Engine name=\u0026#34;Catalina\u0026#34; defaultHost=\u0026#34;localhost\u0026#34;\u0026gt; …… \u0026lt;/Engine\u0026gt; name：用于指定Engine 的名称， 默认为Catalina defaultHost：默认使用的虚拟主机名称，当客户端请求访问的host无效时，会跳转到默认的host来处理请求 Host Host 元素用于配置一个虚拟主机，它支持以下嵌入元素：Alias、Cluster、Listener、 Valve、Realm、Context\n如果在Engine下配置Realm，那么此配置将在当前Engine下的所有Host中共享。 同样，如果在Host中配置Realm ，则在当前Host下的所有Context 中共享\nContext中的Realm优先级 \u0026gt; Host的Realm优先级 \u0026gt; Engine中的Realm优先级\n\u0026lt;Host name=\u0026#34;localhost\u0026#34; appBase=\u0026#34;webapps\u0026#34; unpackWARs=\u0026#34;true\u0026#34; autoDeploy=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.AccessLogValve\u0026#34; directory=\u0026#34;logs\u0026#34; prefix=\u0026#34;localhost_access_log\u0026#34; suffix=\u0026#34;.txt\u0026#34; pattern=\u0026#34;%h %l %u %t \u0026amp;quot;%r\u0026amp;quot; %s %b\u0026#34; /\u0026gt; \u0026lt;Alias\u0026gt;www.example.com\u0026lt;/Alias\u0026gt; \u0026lt;Alias\u0026gt;www.example2.com\u0026lt;/Alias\u0026gt; \u0026lt;/Host\u0026gt; 上面这一段Host的配置文件中还额外添加了Valve配置来实现自定义的日志记录。其中一些参数的详细信息和配置方式可以查看官网的说明 。\nThe shorthand pattern pattern=\u0026quot;common\u0026quot; corresponds to the Common Log Format defined by \u0026rsquo;%h %l %u %t \u0026ldquo;%r\u0026rdquo; %s %b\u0026rsquo;.\nname: 当前Host通用的网络名称，也就是常用的域名，如果有多个域名对应同一个Host的应用，我们可以设置一个或多个Alias来实现访问 appBase：当前Host应用对应的目录，当前Host上部署的Web应用均在该目录下（相对路径和绝对路径均可），默认为webapps unpackWARs：设置为true，Host在启动时会将appBase目录下war包解压为目 录。设置为false，Host将直接从war文件启动 autoDeploy： 控制tomcat是否在运行时定期检测并自动部署新增或变更的web应用 Context Context的完整配置官网文档，Context 用于配置一个Web应用，默认的配置如下。它支持的内嵌元素为：CookieProcessor，Loader，Manager，Realm，Resources，WatchedResource，JarScanner，Valve。\n\u0026lt;Host name=\u0026#34;localhost\u0026#34; appBase=\u0026#34;webapps\u0026#34; unpackWARs=\u0026#34;true\u0026#34; autoDeploy=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;Context docBase=\u0026#34;myAppDeploy\u0026#34; path=\u0026#34;/myApp\u0026#34;\u0026gt; .... \u0026lt;/Context\u0026gt; \u0026lt;/Host\u0026gt; docBase：Web应用目录或者War包的部署路径。可以是绝对路径，也可以是相对于该Context所属的Host中的appBase的相对路径。 path：Web应用的Context的访问路径。 假设tomcat的安装目录为/home/tomcat9，Host为默认的localhost， 则该web应用访问的根路径为： http://localhost:8080/myApp，对应的部署文件所存放的路径为：/home/tomcat9/webapps/myAppDeploy。\n","permalink":"http://localhost:9011/20200504-tomcat-04-deploy-manager-serverxml/","summary":"\u003cp\u003e本文主要包括tomcat服务器的web应用部署和管理，以及\u003ccode\u003eserver.xml\u003c/code\u003e的主要配置。\u003c/p\u003e","title":"Tomcat篇04-部署和管理"},{"content":"本文主要包括LVS三种模式的基本工作原理介绍和可能涉及的ARP问题原理。\nLVS简介 LVS起源 LVS是Linux Virtual Server的简称，也叫Linux虚拟服务器, 也就是现在常说的四层负载均衡。 它是一个由章文嵩博士发起的自由软件项目 。现在LVS已经是 Linux标准内核的一部分，在Linux2.4内核以前，使用LVS时必须要重新编译内核以支持LVS功能模块，但是从Linux2.4内核以后，已经完全内置了LVS的各个功能模块，无需给内核打任何补丁，可以直接使用LVS提供的各种功能。\nLVS常用名词概念 loadbalance：Load Balancer，负载均衡器，运行LVS负责负载均衡的服务器 DS：Director Server，指的是前端负载均衡器节点，也就是运行LVS的服务器； RS：Real Server，后端真实的工作服务器； VIP：Virtual Server IP，向外部直接面向用户请求，作为用户请求的目标的IP地址，一般也是DS的外部IP地址； DIP：Director Server IP，主要用于和内部主机通讯的IP地址，一般也是DS的内部IP地址； RIP：Real Server IP，后端服务器的IP地址； CIP：Client IP，访问客户端的IP地址； LVS基本原理 一般说的LVS集群指的是对于客户端来说的一个大型快速可靠高可用的服务器集群。LVS的核心是在LVS director上的Linux内核中的ip_vs内核模块\n对于LVS集群中的Director Server（以下简称DS）来说：\nLVS属于四层负载均衡，DS属于四层交换，它在网络中显示为路由器，其规则与普通路由器略有不同； DS接受从客户端发送过来的请求，并且从后端（backend）的真实服务器RS中挑选一个用来处理请求； RS可以提供正常互联网服务中的任何服务，因为LVS是四层转发，兼容性很好，对业务无侵入性； RS可以在客户端无感知的情况下添加或删除到LVS集群中，因此允许后端的RS出现宕机、升级、弹性伸缩。 在调度器的实现技术中，IP负载均衡技术是效率最高的。在已有的IP负载均衡技术中有通过网络地址转换（Network Address Translation）将一组服务器构成一个高性能的、高可用的虚拟服务器，我们称之为VS/NAT技术（Virtual Server via Network Address Translation），在分析VS/NAT的缺点和网络服务的非对称性的基础上，LVS提出通过IP隧道实现虚拟服务器的方法VS/TUN （Virtual Server via IP Tunneling），和通过直接路由实现虚拟服务器的方法VS/DR（Virtual Server via Direct Routing），它们可以极大地提高系统的伸缩性。所以，IPVS软件实现了这三种IP负载均衡技术，它们的大致原理如下\nLVS NAT模式 NAT模式的主要实现原理是通过网络地址转换，LB重写请求报文的目标地址(包括IP和MAC)，根据预设的调度算法，将请求分派给后端的RS；RS的响应报文通过LB返回时，报文的源地址被重写，修改为LB的MAC和IP，再返回给客户，完成整个负载调度过程。\n当用户访问服务器群集提供的服务时，发往虚拟服务器IP（VIP/LB的外部IP地址）的数据包将到达LB LB检查数据包的目标地址和端口号。如果符合在LVS的规则表中定义添加的服务，则根据调度算法从后端群集中选择一个RS，并将该连接添加到记录已建立连接的哈希表中 然后，将数据包的目标地址和端口重写为所选RS的地址和端口，然后将数据包转发到RS 当传入的数据包符合在LVS的规则表中定义添加的服务并且可以在哈希表中找到所选的RS时，该数据包将被重写并直接转发到所选的RS 当RS处理完请求之后，会把回复数据包返回给LB，此时LB会将数据包的源地址和端口重写为虚拟服务的源地址和端口，然后发送给客户端 连接终止或超时后，连接记录将在哈希表中删除 LVS的NAT模式需要开启LB的内核中的ip_forward功能\necho 1 \u0026gt; /proc/sys/net/ipv4/ip_forward LVS via IP Tunneling(TUN) 采用NAT技术时，虽然对于客户端来说整个服务器集群中的LVS负载均衡过程是无感的（因为对于客户端来说请求包发送的目标IP和响应包返回的源IP都没有改变），但是由于请求和响应报文都必须经过LB进行重写，当客户请求越来越多时，LB的处理能力将成为整个集群中的瓶颈。\n为了解决这个问题，LB把请求报文通过IP隧道转发至RS，而RS将响应直接返回给客户端，所以LB只需要处理请求报文。由于一般网络服务应答的数据包要比请求数据包大许多，采用 VS/TUN技术后，集群系统的最大吞吐量可以大大提高。\n客户端请求LVS集群提供的服务，数据包发送到VIP LB检查数据包的目的地址和端口，如果符合在LVS的规则表中定义添加的服务，则根据调度算法从后端群集中选择一个RS，并将该连接添加到记录已建立连接的哈希表中 LB对请求的数据包进行封装，在VIP外面再封装一层目标RS的IP地址，然后将它发送到对应的RS上 当传入的数据包符合在LVS的规则表中定义添加的服务并且可以在哈希表中找到所选的RS时，该数据包将被直接封装IP并转发到所选的RS RS接收到数据包后，对其进行解封并且处理请求，然后将响应数据包直接发送到客户端 连接终止或超时后，连接记录将在哈希表中删除 注意在这种模式下的RS可以是在物理位置上分离的服务器（如可以分布在不同地区的机房），只要拥有在任意网络中的任意真实IP即可（相对VIP而言）。此外，此时的RS需要支持IP封装协议并且需要和LB直接配置好IP隧道，同时VIP需要配置到非ARP响应的网卡设备上。\n需要LB和RS上面的服务使用的端口必须保持一致，因此在添加配置规则的时候无需指定RS的端口。\nNote that the services running on the real servers must run on the same port as virtual service, so it is not necessary to specify the service port on the real servers.\nLVS via Direct Routing(DR) VS/DR通过改写请求报文的MAC地址，将请求发送到RS，而RS将响应直接返回给客户。和VS/TUN技术一样，VS/DR技术可极大地提高集群系统的伸缩性。这种方法没有IP隧道的开销，对集群中的RS也没有必须支持IP隧道协议的要求，但是因为使用的是MAC地址进行二层转发，所以要求LB和RS都有一块网卡连在同一物理网段上。\n客户端请求LVS集群提供的服务，数据包发送到VIP LB检查数据包的目的地址和端口，如果符合在LVS的规则表中定义添加的服务，则根据调度算法从后端群集中选择一个RS，并将该连接添加到记录已建立连接的哈希表中 LB对请求的数据包进行封装，在VIP外面再封装一层目标RS的MAC地址，然后将它发送到对应的RS上 当传入的数据包符合在LVS的规则表中定义添加的服务并且可以在哈希表中找到所选的RS时，该数据包将被直接封装IP并转发到所选的RS 由于RS的本地lo接口上面绑定了VIP，且这时MAC地址是RS自身的MAC地址，所以RS接收到数据包后会处理请求，然后将响应数据包直接发送到客户端 连接终止或超时后，连接记录将在哈希表中删除 由于LB只是简单地对数据包的MAC地址更改为RS的MAC地址并且将其重新发送到局域网中，所以要求LB和RS必须要在同一个局域网中，这样才能直接利用MAC来进行二层传输。\n注意DR模式也同样不支持指定RS的服务端口，因此LB和RS的端口也必须保持一致。\nARP in LVS 细心观察上面的DR模式，我们会发现：\nLB把数据包发送给RS的时候只修改了MAC，尽管在交换机上会根据MAC直接把包发送给RS，但是RS在接受到数据包之后还是会检查数据包的目的IP和端口，此时数据包的目的IP依旧是VIP。所以这就是为什么需要在RS的网卡上面也绑定VIP的原因。（一般绑定在loopback接口）\n那么当LB和RS都绑定了VIP的时候，问题又来了：\n当客户端的请求数据包传到LVS集群所在的网关的时候，它是不知道LB的MAC地址的，因此需要通过ARP协议来进行查询，也就是在局域网中发送ARP请求，看谁会响应，响应的就是要发送的MAC地址。而这个时候，由于DR模式下的LB和RS都在同一个局域网中且都绑定了VIP，那么它们就都会响应这个ARP请求。这样一来客户端的数据包就不一定会发送到LB上面，也就不一定会触发整个负载均衡效果。\n因此这种情况下一般都会对RS上面的网卡接口的ARP请求设置进行修改：\necho \u0026#34;1\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/lo/arp_ignore echo \u0026#34;2\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/lo/arp_announce echo \u0026#34;1\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/all/arp_ignore echo \u0026#34;2\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/all/arp_announce 我们看一下kernel的文档中对这两个参数的相关描述\narp_ignore的意义\narp_ignore - INTEGER\nDefine different modes for sending replies in response to received ARP requests that resolve local target IP addresses:\n0 - (default): reply for any local target IP address, configured on any interface\n1 - reply only if the target IP address is local address configured on the incoming interface\n2 - reply only if the target IP address is local address configured on the incoming interface and both with the sender\u0026rsquo;s IP address are part from same subnet on this interface\n3 - do not reply for local addresses configured with scope host, only resolutions for global and link addresses are replied\n4-7 - reserved\n8 - do not reply for all local addresses The max value from conf/{all,interface}/arp_ignore is used when ARP request is received on the {interface}\narp_ignore设置为0时，无论收到的ARP请求的IP是否是eth上面的IP，只要在宿主机上有网卡有对应的IP，就会发送ARP应答。\narp_ignore设置为1即意味着对应的网卡在收到了目标IP不是自己的网卡的IP的数据包的ARP请求时不会进行回应，而在DR模式中，对应的VIP是绑定在lo接口上的，而lo接口并不是物理网卡，实际上数据包都是从物理网卡eth上进来，因此这时就不会对目标IP是VIP的数据包进行ARP回应。使得访问能够顺利地到达LB上面，再从LB上面进行负载均衡。\narp_annouce的意义\narp_announce - INTEGER\nDefine different restriction levels for announcing the local source IP address from IP packets in ARP requests sent on interface:\n0 - (default) Use any local address, configured on any interface 1 - Try to avoid local addresses that are not in the target\u0026rsquo;s subnet for this interface. This mode is useful when target hosts reachable via this interface require the source IP address in ARP requests to be part of their logical network configured on the receiving interface. When we generate the request we will check all our subnets that include the target IP and will preserve the source address if it is from such subnet. If there is no such subnet we select source address according to the rules for level 2. 2 - Always use the best local address for this target. In this mode we ignore the source address in the IP packet and try to select local address that we prefer for talks with the target host. Such local address is selected by looking for primary IP addresses on all our subnets on the outgoing interface that include the target IP address. If no suitable local address is found we select the first local address we have on the outgoing interface or on all other interfaces, with the hope we will receive reply for our request and even sometimes no matter the source IP address we announce. The max value from conf/{all,interface}/arp_announce is used. 每个机器或者交换机中都有一张ARP表，ARP表的作用就是用于记录IP地址和MAC地址的对应关系。当收到一个ARP表中没有记录的IP地址的ARP请求，就会在本机的ARP表中新增对应的IP和MAC记录；当收到一个已知IP地址（arp表中已有记录的地址）的arp请求，则会根据arp请求中的源MAC刷新自己的arp表。\n如果arp_announce参数配置为0，则网卡在发送arp请求时，可能选择的源IP地址并不是该网卡自身的IP地址，这时候收到该arp请求的其他节点或者交换机上的arp表中记录的该网卡IP和MAC的对应关系就不正确。\n所以DR模式下要求arp_announce参数要求配置为2。\n在这个模式下会忽略IP数据包中的源地址并且尝试选择能与目标地址主机通信的本机地址 首先就是查找本机所有的出口网卡上的IP地址所属的子网里面包含了目标的IP地址的IP 如果没有上述的合适的IP地址，那么就会选择出口网卡的第一个IP地址或者是在所有的网卡中最有可能能够接收到请求的IP地址 由于在DR模式中RS和LB的物理网卡是处于同一个局域网中的，所以会直接解析到对应的物理网卡的MAC地址。\n","permalink":"http://localhost:9011/20200427-lvs-principle-introduction/","summary":"\u003cp\u003e本文主要包括LVS三种模式的基本工作原理介绍和可能涉及的ARP问题原理。\u003c/p\u003e","title":"LVS三种模式的工作原理"},{"content":"NoSQL基本概念简介，常见的NoSQL数据库类型介绍以及MongoDB、Memcached和Redis的简单概念以及特性介绍。\n什么是NoSQL NoSQL(Not only SQL)是对不同于传统的关系数据库的数据库管理系统的统称，即广义地来说可以把所有不是关系型数据库的数据库统称为NoSQL。\nNoSQL 数据库专门构建用于特定的数据模型，并且具有灵活的架构来构建现代应用程序。NoSQL 数据库使用各种数据模型来访问和管理数据。这些类型的数据库专门针对需要大数据量、低延迟和灵活数据模型的应用程序进行了优化，这是通过放宽其他数据库的某些数据一致性限制来实现的。\n数十年来，用于应用程序开发的主要数据模型是由关系数据库（如 Oracle、DB2、SQL Server、MySQL 和 PostgreSQL）使用的关系数据模型。直到 21 世纪中后期，才开始大规模采用和使用其他数据模型。为了对这些新类别的数据库和数据模型进行区分和分类，创造了术语“NoSQL”。通常术语“NoSQL”与“非关系”可互换使用。\nNoSQL的常见类型 键值数据库 键值：键值数据库是高度可分区的，并且允许以其他类型的数据库无法实现的规模进行水平扩展。诸如游戏、广告技术和 IoT 等使用案例本身特别适合键值数据模型。Amazon DynamoDB 旨在为任意规模的工作负载提供一致且低于 10 毫秒的延迟。这种一致的性能是为何使用 Snapchat Stories 功能的主要原因，该功能包含移至 DynamoDB 的 Snapchat 的最大存储写入工作负载。\n键值数据库是一种非关系数据库，它使用简单的键值方法来存储数据。键值数据库将数据存储为键值对集合，其中键作为唯一标识符。键和值都可以是从简单对象到复杂复合对象的任何内容。键值数据库是高度可分区的，并且允许以其他类型的数据库无法实现的规模进行水平扩展。\n内存数据库 内存：游戏和广告技术应用程序具有排行榜、会话存储和实时分析等使用案例，它们需要微秒响应时间并且可能随时出现大规模的流量高峰。\n文档数据库 文档：在应用程序代码中，数据通常表示为对象或 JSON 文档，因为对开发人员而言它是高效和直观的数据模型。文档数据库让开发人员可以使用他们在其应用程序代码中使用的相同文档模型格式，更轻松地在数据库中存储和查询数据。文档和文档数据库的灵活、半结构化和层级性质允许它们随应用程序的需求而变化。文档模型可以很好地与目录、用户配置文件和内容管理系统配合使用，其中每个文档都是唯一的，并会随时间而变化。\n文档数据库是一种非关系数据库，旨在将数据作为类 JSON 文档存储和查询。文档数据库让开发人员可以使用他们在其应用程序代码中使用的相同文档模型格式，更轻松地在数据库中存储和查询数据。文档和文档数据库的灵活、半结构化和层级性质允许它们随应用程序的需求而变化。文档模型可以很好地与目录、用户配置文件和内容管理系统等使用案例配合使用，其中每个文档都是唯一的，并会随时间而变化。文档数据库支持灵活的索引、强大的临时查询和文档集合分析。\n图形数据库 图形：图形数据库旨在轻松构建和运行与高度连接的数据集一起使用的应用程序。图形数据库的典型使用案例包括社交网络、推荐引擎、欺诈检测和知识图形。热门图形数据库包括 Neo4j 和 Giraph。图形数据库专门用于存储和导航关系。关系是图形数据库中的一等公民，图形数据库的大部分价值都源自于这些关系。图形数据库使用节点来存储数据实体，并使用边缘来存储实体之间的关系。边缘始终有一个开始节点、结束节点、类型和方向，并且边缘可以描述父子关系、操作、所有权等。一个节点可以拥有的关系的数量和类型没有限制。\n图形数据库中的图形可依据具体的边缘类型进行遍历，或者也可对整个图形进行遍历。在图形数据库中，遍历联结或关系非常快，因为节点之间的关系不是在查询时计算的，而是留存在数据库中。在社交网络、推荐引擎和欺诈检测等使用案例中，您需要在数据之间创建关系并快速查询这些关系，此时，图形数据库更具优势。\n搜索数据库 搜索：许多应用程序输出日志以帮助开发人员解决问题。搜索引擎数据库是一种非关系数据库，专用于数据内容的搜索。搜索引擎数据库使用索引对数据之间的相似特征进行分类，并增强搜索功能。搜索引擎数据库经过优化，可处理可能是长数据，半结构数据或非结构数据的数据，并且它们通常提供专门的方法，例如全文搜索，复杂的搜索表达式和搜索结果排名。\n关系型数据库和NoSQL 关系数据库 NoSQL 数据库 最佳工作负载 关系数据库专为事务性和高度一致的联机事务处理 (OLTP) 应用程序而设计，并且适用于联机分析处理 (OLAP)。 NoSQL 数据库适用于包括低延迟应用程序在内的多种数据访问模式。NoSQL 搜索数据库设计用于对半结构化数据进行分析。 数据模型 关系模型可将数据标准化为由行和列组成的表。采用一种架构来严格定义表、行、列、索引、各个表之间的关系及其他数据库元素。数据库在表之间的关系中强制实施引用完整性。 NoSQL 数据库提供了各种数据模型，如键值、文档和图形，这些模型针对性能和规模进行了优化。 ACID 属性 关系数据库提供原子性、一致性、隔离性和持久性 (ACID) 属性： 原子性要求事务完全执行或根本不执行。一致性要求事务提交之后，数据必须符合数据库架构。隔离性要求并发事务彼此分开执行。持久性要求能够从意外系统故障或断电情况中恢复到上一个已知状态。 NoSQL 数据库通常通过放宽关系数据库的一些 ACID 属性来进行权衡，以获得可以水平扩展的更灵活的数据模型。这将使 NoSQL 数据库成为高吞吐量、低延迟使用案例的绝佳选择，这些使用案例需要水平扩展超出单个实例的限制。 性能 性能通常取决于磁盘子系统。要获得最佳性能，通常需要优化查询、索引和表结构。 性能通常由底层硬件集群大小、网络延迟以及调用应用程序来决定。 扩展 关系数据库通常通过增加硬件的计算能力进行纵向扩展或通过为只读工作负载添加副本进行横向扩展。 NoSQL 数据库通常是可分区的，因为访问模式可以通过使用分布式体系结构进行横向扩展来提高吞吐量，从而以接近无限的规模提供一致的性能。 API 存储和检索数据的请求通过使用符合结构化查询语言 (SQL) 的查询来传达。这些查询由关系数据库解析和执行。 借助基于对象的 API，应用开发人员可以轻松存储和检索数据结构。通过分区键，应用程序可以查找键值对、列集或包含序列化应用程序对象和属性的半结构化文档。 分布式系统的CAP原理 在理论计算机科学中，CAP定理（CAP theorem），又被称作布鲁尔定理（Brewer\u0026rsquo;s theorem），它指出对于一个分布式计算系统来说，不可能同时满足以下三点：\n一致性（Consistency） （等同于所有节点访问同一份最新的数据副本） 可用性（Availability）（每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据） 分区容错性（Partition tolerance）（以实际效果而言，分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性，就意味着发生了分区的情况，必须就当前操作在C和A之间做出选择。） 根据定理，分布式系统只能满足三项中的两项而不可能满足全部三项。理解CAP理论的最简单方式是想象两个节点分处分区两侧。允许至少一个节点更新状态会导致数据不一致，即丧失了C性质。如果为了保证数据一致性，将分区一侧的节点设置为不可用，那么又丧失了A性质。除非两个节点可以互相通信，才能既保证C又保证A，这又会导致丧失P性质。\nNoSQL的BASE原则 BASE：Basically Available, Soft-state, Eventually Consistent。 由 Eric Brewer 定义。BASE是NoSQL数据库通常对可用性及一致性的弱要求原则:\nBasically Availble \u0026ndash;基本可用 Soft-state \u0026ndash;软状态/柔性事务。 \u0026ldquo;Soft state\u0026rdquo; 可以理解为\u0026quot;无连接\u0026quot;的, 而 \u0026ldquo;Hard state\u0026rdquo; 是\u0026quot;面向连接\u0026quot;的 Eventual Consistency \u0026ndash; 最终一致性， 也是是 ACID 的最终目的。 BASE模型是传统ACID模型的反面，不同于ACID，BASE强调牺牲高一致性，从而获得可用性，数据允许在一段时间内的不一致，只要保证最终一致就可以了。\nMongoDB MongoDB 是由C++语言编写的基于分布式文件存储的开源数据库系统（document database）。MongoDB数据库中的记录称为文档（document），是一种由字段和值（field and value）成对组成的key-value键值型数据结构，格式上和常用的json格式类似，字段的值可以包括其他文档，数组和文档数组。\n{ name: \u0026#34;sue\u0026#34;, age: 26, status: \u0026#34;A\u0026#34;, groups: [\u0026#34;news\u0026#34;,\u0026#34;sports\u0026#34;] } 使用文档的主要优势在于可以支持许多编程语言的原生数据类型，避免不必要的join操作以及动态的schema模式可以流畅地支持多态类型。\nDynamic schema supports fluent polymorphism。\nMongoDB将文档存储到集合（collections）中，集合与关系型数据库中的数据表类似。除了集合之外，MongoDB还支持只读视图（3.4）和按需实例化视图（4.2）。\n除了最主要的文档特性外，MongoDB还具有以下特性：\nHigh Performance MongoDB提供了高性能的数据持久化存储功能，主要是\n通过支持嵌入式的数据模型来减少数据库系统的I/O操作 索引支持更快的查询，并且可以包括来自嵌入式文档和数组的键 Rich Query Language MongoDB提供了丰富的查询语句用于支持CRUD等操作，除了常规的查询语句还支持如\nData Aggregation（数据聚合）、Text Search 、 Geospatial Queries和mapping等操作\n聚合操作（Data Aggregation）处理数据记录并返回计算结果。聚合操作将来自多个文档的值组合在一起，并且可以对分组的数据执行各种操作以返回单个结果。MongoDB提供了三种执行聚合的方式：聚合管道，map-reduce函数和单一目的聚合方法。 MongoDB支持使用文本索引和$text运算符执行字符串内容的文本搜索的查询操作。 MongoDB还支持地理空间位置的查询操作 High Availability MongoDB的复制工具（称为副本集 ）提供自动故障转移和数据冗余功能。副本集是一组维护相同数据集的MongoDB服务器，可提供冗余并提高数据可用性.\nHorizontal Scalability MongoDB的核心功能之一就是提供水平扩展能力。\n分片将数据分布在一组计算机上。 从3.4开始，MongoDB支持基于分片键创建数据区域。在均衡的集群中，MongoDB仅将区域覆盖的读写定向到区域内的那些分片。 Support for Multiple Storage Engines MongoDB支持多种存储引擎，如WiredTiger和In-Memory 。此外，MongoDB还提供了存储引擎的API插件供第三方开发者开发存储引擎。\nMemcached 自由和开放源代码，高性能，分布式内存对象缓存系统，本质上是通用的，但旨在通过减轻数据库负载来加速动态Web应用程序。Memcached是一个内存中的键值存储，用于存储来自数据库调用，API调用或页面渲染结果的任意数据（字符串，对象）。Memcached简单但功能强大。其简单的设计可促进快速部署，易于开发，并解决了大型数据缓存面临的许多问题。它的API适用于大多数流行语言。Memcached对其特点介绍主要有以下几点：\nSimple Key/Value Store Memcached的服务端并不在乎用户的数据具体是怎么样的，每一个项目/Item（相当于MySQL中的行）是由key、过期时间、标记/flags（可选）和原始数据组成。Memcached并不知道数据的数据结构，因此用户端必须要上传预序列化的数据。\nLogic Half in Client, Half in Server 一个完整的memcached过程实现是需要客户端和服务端共同完成的，也就是说有部分操作在客户端完成而另一部分在服务端完成。客户端负责选择哪一个服务器来进行读写和无法与服务器建立通信连接的时候该如何操作；服务器负责存储和拉取item，同时还负责内存的释放和复用等工作。\nServers are Disconnected From Each Other Memcached集群之间的服务器互相并不知道对方的存在，他们不交流、不同步、不广播、不复制，往集群中添加服务器就可以直接增加整个集群的可用内存。当客户端删除或者覆盖了记录该客户端的缓存数据的服务器的时候，缓存就会失效。\nO(1) 所有的命令实现起来都很快并且对加锁十分友好，这为所有用例提供了近乎确定的查询速度。官方表示对于比较慢的机器每次查询都在1ms以下，而高端服务器可以实现每秒百万的吞吐。\nQueries on slow machines should run in well under 1ms. High end servers can serve millions of keys per second in throughput.\nForgetting is a Feature Memcached默认使用LRU算法和懒惰回收机制。items会在指定的时间后失效，但不是失效后就马上把item从内存中删除，而是当内存不足需要新的内存来建立item时再去查找已经过期的item将其删除并释放内存。当没有过期的item且当内存不足时，Memcached会清除内存中尚未过期但是很久没有被使用的数据来释放内存从而保留那些被频繁访问的数据\nCache Invalidation Memcached使用哈希算法，因此客户端不是直接向所有可用主机广播更改，而是根据哈希表的记录直接访问保存有待失效数据的服务器。\nRedis Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.\nRedis是一个基于BSD开源协议的，内存中的数据结构存储系统，可以用于数据库、缓存和消息中间件。它支持多种类型的数据结构，如 字符串（strings）， 散列（hashes）， 列表（lists）， 集合（sets）， 有序集合（sorted sets） 与范围查询， bitmaps， hyperloglogs 和 地理空间（geospatial） 索引半径查询。 Redis 内置了复制（replication），LUA脚本（Lua scripting）， LRU驱动事件（LRU eviction），事务（transactions） 和不同级别的数据持久化（persistence）， 并通过 Redis哨兵（Sentinel）和redis集群的自动分区提供高可用性。\nRedis vs Memcached 缓存中间件 Memcache 和 Redis 的区别\nMemcached Redis 数据类型 支持简单数据类型 数据类型丰富 数据持久化存储 N Y 主从 N Y 分片 N Y Redis的特点 redis 完全基于内存，绝大部分请求是纯粹的内存操作，执行效率高。\nredis 使用单进程单线程模型的（K，V）数据库，将数据存储在内存中，存取均不会受到硬盘 IO 的限制，因此其执行速度极快。\n另外单线程也能处理高并发请求，还可以避免频繁上下文切换和锁的竞争，如果想要多核运行也可以启动多个实例。\n当然了，单线程也会有它的缺点，也是Redis的噩梦：**阻塞。**如果执行一个命令过长，那么会造成其他命令的阻塞，对于Redis是十分致命的，所以Redis是面向快速执行场景的数据库。\n数据结构简单，对数据操作也简单，Redis 不使用表，不会强制用户对各个关系进行关联，不会有复杂的关系限制，其存储结构就是键值对，类似于 HashMap，HashMap 最大的优点就是存取的时间复杂度为 O(1)。\nredis 使用I/O 多路复用模型，属于非阻塞 IO。Redis使用epoll作为I/O多路复用技术的实现，再加上Redis自身的事件处理模型将epoll的read、write、close等都转换成事件，不在网络I/O上浪费过多的时间。实现对多个FD读写的监控，提高性能。\n","permalink":"http://localhost:9011/20200424-nosql-base-introduction/","summary":"\u003cp\u003eNoSQL基本概念简介，常见的NoSQL数据库类型介绍以及MongoDB、Memcached和Redis的简单概念以及特性介绍。\u003c/p\u003e","title":"NoSQL数据库简介"},{"content":"本文主要包括DNS的简单介绍，DNS查询的原理介绍和DNS负载均衡应用的简单介绍。\nDNS简介 什么是DNS 首先我们来了解DNS是什么。下面引用维基百科的解释：\n域名系统（英语：Domain Name System，缩写：DNS）是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库，能够使人更方便地访问互联网。DNS使用TCP和UDP端口53。当前，对于每一级域名长度的限制是63个字符，域名总长度则不能超过253个字符。\n我们知道计算机网络中的通信实际上是需要通过IP来进行的，但是让使用者记住那么多复杂无规律的IP地址是不现实的，因此人们发明了域名，使用者只需要记住域名，然后通过域名即可访问对应的网站，而DNS就是负责将我们平时使用的域名如163.com、google.com等解析成对应的IP地址，然后让客户端和该IP地址进行通信的这样一个系统。\n上面的维基百科说DNS是一个分布式的数据库其实也是合理的，因为DNS主要的功能就是存储着各类域名和对应的IP地址，DNS请求可以理解成查询数据库的操作。而因为DNS需要给全球所有上网的用户频繁使用，因此它必须是分布式（地域广）、高并发（请求多）、高可用（重要性高）的一个系统。\nDNS解析类型 DNS的解析类型其实也有很多种，我们比较常接触到的就是A类和CNAME，当然现在IPv6开始普及了也就有AAAA类（相当于IPv6版的A类）。\nA-将域名指向一个IPV4地址\n比如将wikipedia.org这个域名解析到103.102.166.224这个IPv4地址；\nCNAME-将域名指向另外一个域名\n比如将wikipedia.org这个域名解析到wikipedia.org.cn\nAAAA-将域名指向一个IPV6地址\n比如将wikipedia.org这个域名解析到2001:df2:e500:ed1a::1这个IPv6地址；\nNS-将子域名指定其他DNS服务器解析\n比如你在阿里云购买的域名，但是想要使用网易云来提供DNS解析服务，那就添加一个NS记录，将记录值设置成网易云的DNS服务器域名；\nMX-将域名指向邮件服务器地址\n设置邮箱时，让邮箱能收到邮件，就需要添加 MX 记录。MX全称为mail exchanger，用于电子邮件系统发邮件时根据收信人的地址后缀来定位邮件服务器。例如，当有人发邮件给wikipediauser@wikipedia.org时，系统将对wikipedia.org进行DNS中的MX记录解析。如果MX记录存在，系统就根据MX记录的优先级，将邮件转发到与该MX相应的邮件服务器上。\nTXT-文本长度限制一般为255，通常做SPF记录（反垃圾邮件）\nSPF是 Sender Policy Framework 的缩写，一种以IP地址认证电子邮件发件人身份的技术。接收邮件方会首先检查域名的SPF记录，来确定发件人的IP地址是否被包含在SPF记录里面，如果在，就认为是一封正确的邮件，否则会认为是一封伪造的邮件进行退回。\nCAA-CA证书颁发机构授权校验\nCAA(Certificate Authority Authorization)，即证书颁发机构授权。是一项新的可以添加到DNS记录中的额外字段,通过DNS机制创建CAA资源记录，可以限定域名颁发的证书和CA（证书颁发机构）之间的联系。未经授权的第三方尝试通过其他CA注册获取用于该域名的SSL/TLS证书将被拒绝。\n域名设置 CAA 记录，使网站所有者，可授权指定CA机构为自己的域名颁发证书，以防止HTTPS证书错误签发，从而提高网站安全性。\n其他的还有诸如SRV、显性/隐性URL等解析，具体可以查看对应的域名供应商提供的DNS解析服务说明文档。\nDNS查询过程 DNS服务器类型 DNS服务器一般分三种，根DNS服务器，顶级DNS服务器，权威DNS服务器。\n根DNS服务器是最高层次的DNS服务器，全球共有13套，它并不提供直接的DNS域名解析服务，而是负责将对应的顶级域名DNS服务器的地址返回给查询的客户端\n顶级域名（top level domain，简写为 TLD），即对应图中第二层的顶级DNS服务器负责的顶级域名，同样的，顶级域名服务器也不提供直接的DNS域名解析服务，而是负责将对应的权威域名服务器返回给查询的客户端\n权威DNS服务器为图中的第三层，这时候权威DNS服务器会返回对应域名的IP地址，客户端拿到了IP地址就可以进行访问了。\n那么图中的第四层example.wikipedia.org为wikipedia.org的子域名，也可以叫做二级域名，第五层www.example.wikipedia.org是第四层example.wikipedia.org的子域名，也可以叫做三级域名，以此类推。\nDNS请求过程 接下来我们以访问维基百科(wikipedia.org)为例，对应下图进行解析：\n首先客户端发送请求需要访问wikipedia.org，然后第一步是访问本地的DNS缓存； 本地的DNS缓存会读取系统下对应的hosts文件，也就是Linux下的/etc/hosts或Windows下的C:\\Windows\\System32\\drivers\\etc\\hsots，如果这里面定义了wikipedia.org这个域名对应的IP地址，则直接访问这个IP，就没有后面什么事儿了； 如果本地的缓存文件没有记录，那么客户端就会发送请求到本地DNS服务器，一般来说会是ISP默认提供的DNS服务器，当然我们也可以手动指定成第三方的DNS服务器。本地DNS服务器查询服务器内的记录，如果有则直接返回记录给客户端； 如果本地DNS服务器没有记录，那么就会访问根域名服务器，询问wikipedia.org对应的顶级域名服务器的地址； 根域名服务器根据请求判断出是访问.org域名的请求，就会给客户端返回.org的顶级域名服务器地址； 本地DNS服务器获取到地址之后，发送请求给.org的顶级域名服务器，询问wikipedia.org对应的权威域名服务器的地址； .org的顶级域名服务器给客户端返回了wikipedia.org的权威域名服务器的地址； 本地DNS服务器获取到地址之后，发送请求给wikipedia.org的权威域名服务器，询问wikipedia.org的IP地址； wikipedia.org的权威域名服务器给本地DNS服务器返回了wikipedia.org的IP地址； 本地DNS服务器给客户端返回了wikipedia.org的IP地址，客户端和这个IP建立连接，开始传输数据，该次DNS请求结束。 实际上上面的是最长的DNS查询情况，因为一般情况下我们的DNS本地服务器就会存着对应的DNS缓存记录，这样在DNS查询的时候直接就可以返回本地的缓存给客户端，从而避免了后面的递归查询。\n那么要是我们修改了域名的DNS解析记录呢？比如wikipedia.org的IP地址从103.102.166.224修改成103.102.166.225，这就涉及到解析生效的时间问题，也就是所谓的TTL，解析生效时间取决于本地DNS缓存的解析记录的TTL到期时间，一般默认为10分钟。例如解析记录设置的TTL值为 10 分钟，则理论上全球解析生效时间需要10分钟；解析记录设置的TTL值为60秒，则理论上全球解析生效时间需要60秒。\nDNS的用途 我们已经知道DNS最基本也是最重要的作用就是进行域名和IP地址之间对应关系的记录和查询，除此之外，DNS还可以用作负载均衡。\n首先我们要知道，域名和IP并不是一对一的关系，而是多对多的关系。也就是说一个IP可以绑定多个域名，一个域名也可以解析到多个IP。有了这个特性，我们就可以利用DNS来实现负载均衡。\n注意负载均衡并非只有DNS这一种方式\n内部负载均衡（SLB） 例如，一个应用要访问数据库，在这个应用里面应该配置这个数据库的IP地址，还是应该配置这个数据库的域名呢？显然应该配置域名，因为一旦这个数据库，因为某种原因，换到了另外一台机器上，而如果有多个应用都配置了这台数据库的话，一换IP地址，就需要将这些应用全部修改一遍。但是如果配置了域名，则只要在DNS服务器里，将域名映射为新的IP地址，这个工作就完成了，大大简化了运维。\n在这个基础上，我们可以再进一步。例如，某个应用要访问另外一个应用，如果配置另外一个应用的IP地址，那么这个访问就是一对一的。但是当被访问的应用撑不住的时候，我们其实可以部署多个。但是，访问它的应用，如何在多个之间进行负载均衡？只要配置成为域名就可以了。在域名解析的时候，我们只要配置策略，这次返回第一个IP，下次返回第二个IP，就可以实现负载均衡了。\n全局负载均衡（GSLB） 还是刚刚上面的那个图，我们加入了GSLB1和GSLB2在权威域名服务器后面，权威服务器之前的访问过程和上面提到的DNS请求过程一样，这里我们还是以访问维基百科(wikipedia.org)为例。\n我们先对维基百科(wikipedia.org)做一个CNAME解析，让它解析到glsb.wikipedia.org这个GSLB1服务器； 客户端请求到权威域名服务器之后，获得了一个glsb.wikipedia.org的地址，然后客户端就访问glsb.wikipedia.org这个服务器，注意这个时候glsb.wikipedia.org应该是充当了权威域名服务器的角色； glsb.wikipedia.org接受到请求之后，可以根据来源的IP判断是哪个地区的请求，假设是中国，那就给它返回一个cn.glsb.wikipedia.org的GSLB2的服务器地址； GSLB2根据接受到的请求，返回一个离客户端最近服务器的IP地址，从而实现了GSLB的效果。 GSLB和我们常说的LVS、NGINX负载均衡等方式的一个比较大的不同就是在覆盖范围特别广的时候使用效果会更好一些。比如在中国的服务器业务，要扩展到美国，假设业务的访问量翻倍，要增加一倍的机器，可以选择在原有的集群上增加一倍的机器，或者在美国再部署一套同样的服务器，然后配置DNS的GSLB服务器来实现。显然后者的效果会更好，因为美国的用户访问中国的服务器在速度上肯定是没有访问美国的服务器快。\n这里只是打一个不太恰当的比喻，实际上访问量翻倍肯定没这么简单。但是在这种跨地域的负载均衡策略上，DNS的GSLB是一个不错的选择。\n","permalink":"http://localhost:9011/20200417-dns-01-dns-theory-01-dns-introduction/","summary":"\u003cp\u003e本文主要包括DNS的简单介绍，DNS查询的原理介绍和DNS负载均衡应用的简单介绍。\u003c/p\u003e","title":"DNS原理篇01-DNS原理介绍"},{"content":"本文主要介绍了iptables的基本工作原理和四表五链等基本概念以及NAT的工作原理。\niptables简介 我们先来看一下netfilter官网对iptables的描述：\niptables is the userspace command line program used to configure the Linux 2.4.x and later packet filtering ruleset. It is targeted towards system administrators.\nSince Network Address Translation is also configured from the packet filter ruleset, iptables is used for this, too.\nThe iptables package also includes ip6tables. ip6tables is used for configuring the IPv6 packet filter.\n也就是说iptables实际上只是位于用户空间的一个面向系统管理员的Linux防火墙的管理工具而已，而真正实现防火墙功能的是netfilter，它是Linux内核中实现包过滤的内核模块，iptables对应在内核中的模块应该是ip_tables，我们查看系统内核中ip_tables的信息的时候可以看到ip_tables.ko这个模块是在netfilter这个目录下的。\n实际上除了iptables还有如nftables、firewalld等防火墙工具都是在用户空间（用户层）对相应的内核空间中对应的netfilter相关的模块进行操作的工具。\niptables的四表五链 iptables流程图 首先我们来看一下下面的这张图了解一下iptables中的表和链的概念。图中使用箭头展示了用户访问使用了iptables的机器的过程，其中按照箭头的顺序我们就可以将其梳理为一条大的带有分支的链条，在每个需要进行操作的模块处都标有名称和相应的括号，括号内的就是iptables的四表，而每个模块都可以视为一个链。\nCentOS7中的input链中还有nat表，但是在CentOS6中并没有。\n之所以叫做链就是因为在访问该链的时候会按照每个链对应的表依次进行查询匹配执行的操作，如PREROUTING链对应的就是(raw-\u0026gt;mangle-\u0026gt;nat)，每个表按照优先级顺序进行连接，每个表中还可能有多个规则，因此最后看起来就像链一样，因此称为链。而iptables的表中存储的就是对应的规则和需要执行的操作，这里以路由器为例查看其中iptables的filter表：\n注意每一个链对应的表都是不完全一样的，表和链之间是多对多的对应关系。但是不管一个链对应多少个表，它的表都是按照下面的优先顺序来进行查找匹配的。\n表的处理优先级：raw\u0026gt;mangle\u0026gt;nat\u0026gt;filter。\n四表 iptables的四个表iptable_filter，iptable_mangle，iptable_nat，iptable_raw，默认表是filter（没有指定表的时候就是filter表）。\nfilter 表：用来对数据包进行过滤，具体的规则要求决定如何处理一个数据包。\n对应的内核模块为：iptable_filter，其表内包括三个链：input、forward、output;\nnat 表：nat 全称：network address translation 网络地址转换，主要用来修改数据包的 IP 地址、端口号信息。\n对应的内核模块为：iptable_nat，其表内包括三个链：prerouting、postrouting、output;\nmangle 表：主要用来修改数据包的服务类型，生存周期，为数据包设置标记，实现流量整形、策略路由等。\n对应的内核模块为：iptable_mangle，其表内包括五个链：prerouting、postrouting、input、output、forward;\nraw 表：主要用来决定是否对数据包进行状态跟踪。\n对应的内核模块为：iptable_raw，其表内包括两个链：output、prerouting;\nraw表只使用在PREROUTING链和OUTPUT链上,因为优先级最高，从而可以对收到的数据包在系统进行ip_conntrack（连接跟踪）前进行处理。一但用户使用了raw表,在某个链上，raw表处理完后，将跳过NAT表和ip_conntrack处理，即不再做地址转换和数据包的链接跟踪处理了。RAW表可以应用在那些不需要做nat的情况下，以提高性能。\n五链 iptables的五个链PREROUTING，INPUT，FORWARD，OUTPUT，POSTROUTING。\ninput 链：当收到访问防火墙本机地址的数据包时，将应用此链中的规则； output 链：当防火墙本机向外发送数据包时，将应用此链中的规则； forward 链：当收到需要通过防火中转发给其他地址的数据包时，将应用此链中的规则，注意如果需要实现forward转发需要开启Linux内核中的ip_forward功能； prerouting 链：在对数据包做路由选择之前，将应用此链中的规则； postrouting 链：在对数据包做路由选择之后，将应用此链中的规则； iptables的常见情况 下面我们利用上面的流程图来对几种常见的情况解析：关键点在于发往iptables主机的数据包的目的地址是否是iptables主机本机。如果是，那我们就可以理解为常见的开启了iptables防火墙的网站服务器主机；如果不是，那就是走ip_forward进行转发，比如我们常见的NAT路由器的NAT服务和策略路由等。如下图为开启了ip_forward功能的openwrt路由器。\nNAT工作原理 接下来介绍一些NAT(Network Address Translation，网络地址转换)的基本知识，众所周知，IPv4的公网IP地址已经枯竭，但是需要接入互联网的设备还在不断增加，这其中NAT就发挥了很大的作用（此处不讨论IPv6）。NAT服务器提供了一组私有的IP地址池（10.0.0.0/8、172.16.0.0/12、192.168.0.0/16），使得连接该NAT服务器的设备能够获得一个私有的IP地址（也称局域网IP/内网IP），当设备需要连接互联网的时候，NAT服务器将该设备的私有IP转换成可以在互联网上路由的公网IP（全球唯一）。NAT的实现方式有很多种，这里我们主要介绍三种：静态NAT、动态NAT和网络地址端口转换（NAPT）。\nBNAT 静态NAT：LVS的官方文档中也称为(N-to-N mapping) ，前面的N指的是局域网中需要联网的设备数量，后面的N指的是该NAT服务器所拥有的公网IP的数量。既然数量相等，那么就可以实现静态转换，即一个设备对应一个公网IP，这时候的NAT服务器只需要维护一张静态的NAT映射转换表。\n内网IP 外网IP 192.168.1.55 219.152.168.222 192.168.1.59 219.152.168.223 192.168.1.155 219.152.168.224 动态NAT：LVS的官方文档中也称为(M-to-N mapping) ，注意这时候的M\u0026gt;N，也就是说局域网中需要联网的设备数量多于NAT服务器拥有的公网IP数量，这时候就需要由NAT服务器来实现动态的转换，这样每个内网设备访问公网的时候使用的公网IP就不一定是同一个IP。\n在一些家用路由器中，DMZ是指一部所有端口都暴露在外部网络的内部网络主机，除此以外的端口都被转发。严格来说这不是真正的DMZ，因为该主机仍能访问内部网络，并非独立于内部网络之外的。但真正的DMZ是不允许访问内部网络的，DMZ和内部网络是分开的。这种 DMZ主机并没有真正DMZ所拥有的子网划分的安全优势，其常常以一种简单的方法将所有端口转发到另外的防火墙或NAT设备上。\nNAPT 以上的这两种都属于基本网络地址转换（Basic NAT），这种转换在技术上比较简单，仅支持地址转换，不支持端口映射，这也就带来了另一个问题就是资源的浪费。我们知道一个IP实际上可以对应多个端口，而我们访问应用实际上是通过IP地址+端口号的形式来访问的，即客户端访问的时候发送请求到服务器端应用程序监听的端口即可实现访问。那么NAPT就是在这基础上的扩展延申，它在IP地址的基础上加上了端口号，支持了端口映射的功能。\nNAPT：NAPT实际上还可以分为**源地址转换（SNAT）和目的地址转换（DNAT）**两种。注意这个源地址和目的地址是针对NAT服务器而言，我们通过下面一张图来说明： 首先我们这里有一个客户端，上面运行着一个浏览器，假设它使用的是5566端口，它需要访问14.25.23.47这个Web服务器的HTTPS服务的443端口，它在访问的时候需要经过局域网出口的这个路由器网关（同时也是NAT服务器），路由器对它进行一个NAPT的源地址转换（SNAT），这个时候客户端的请求经过NAT服务器之后变成了222.17.23.45:7788这个IP端口对Web服务器的443端口进行访问。注意在这个过程中，目标服务器（Web服务器）的IP和端口是一直没有改变的。\n接下来在Web服务器接收到请求之后，需要返回数据给发送请求的设备，注意这时候web服务器返回数据的指向IP应该是刚刚NAT服务器发送请求的227.17.23.45:7788这个IP端口，这时候路由器网关再进行一次NAPT的目标地址转换（DNAT），目标的IP端口就是最开始发送请求的192.168.1.77:5566这个端口。\n实际上对于大多数人来说日常接触到最多的就是路由器做的SNAT和DNAT操作，它们一般成对出现用于解决公网IP资源不足的问题，需要注意的是NAT是可以进行嵌套操作的，即NAT下面的网络设备还可以继续做NAT，只要做NAT的网段不和上层的NAT的网段相同即可。\niptables配置 在了解清楚iptables的工作原理和每个表以及链的作用之后，我们就可以根据其特点进行针对性的配置。\niptables 的基本语法命令格式\niptables [-t 表名] 管理选项 [链名] [匹配条件] [-j 控制类型] 表名、链名：指定iptables命令所操作的表和链，未指定表名时将默认使用filter表； 管理选项：表示iptables规则的操作方式，比如：插入、增加、删除、查看等； 匹配条件：指定要处理的数据包的特征，不符合指定条件的数据包不处理； 控制类型：指数据包的处理方式，比如：允许accept、拒绝reject、丢弃drop、日志LOG等； iptables 命令的常用管理选项 -A:在指定链的末尾添加一条新的规则 -D:删除指定链中的某一条规则，可删除指定序号或具体内容 -I:在指定链中插入一条新规则，未指定序号时默认作为第一条规则 -R:修改、替换指定链中的某一条规则，可指定规则序号或具体内容 -L:列出指定链中所有的规则，未指定链名，则列出表中的所有链 -F:清空指定链中所有的规则，未指定链名，则清空表中的所有链 -P:设置指定链的默认策略 -n:使用数字形式显示输出结果 -v:查看规则列表时显示详细的信息 -h:查看命令帮助信息 --line-numbers:查看规则列表时，同时显示规则在链中的顺序号 在添加规则之前我们先开启iptables的独立log功能，对于centos6，我们可以执行以下操作：\necho \u0026#34;kern.* /var/log/iptables.log\u0026#34; \u0026gt;\u0026gt; /etc/rsyslog.conf # 记录所有级别的日志到指定目录中 service rsyslog restart # 重启rsyslog服务使配置生效 接着我们开始添加一条比较有针对性的规则：\niptables -A INPUT -j LOG --log-prefix \u0026#34;*** INPUT ***\u0026#34; --log-level debug iptables -t filter -A INPUT -p tcp -s 192.168.100.100 --dport 80 -j REJECT 上述两条命令第一条增加了一条记录日志的规则，对于INPUT链中的所有操作都记录到日志中，添加日志前缀*** INPUT ***并设定日志级别为debug\n第二条是在INPUT链的filter表中插入一条规则，限定对192.168.100.100这个IP使用tcp协议访问本机的目的端口80端口的时候拒绝掉数据包。\n接着我们尝试访问发现无法正常显示页面。\n再查看日志发现iptables日志中多了很多请求被拒绝的记录。\n再清空所有的规则，此时可以正常访问，且不再继续记录日志，因为记录日志的那条规则也被我们清空掉了。\n","permalink":"http://localhost:9011/20200414-iptables-principle-introduction/","summary":"\u003cp\u003e本文主要介绍了iptables的基本工作原理和四表五链等基本概念以及NAT的工作原理。\u003c/p\u003e","title":"iptables的四表五链与NAT工作原理"},{"content":"使用ceph-deploy部署ceph集群并配置ceph-dashboard。\n准备工作 关闭selinux（所有节点） setenforce 0 sed -i \u0026#39;s/^SELINUX=enforcing$/SELINUX=disabled/\u0026#39; /etc/selinux/config # 也可以直接修改/etc/selinux/config文件 配置ntp时间同步（所有节点） 具体的操作可以参考之前的文章。\n安装epel源 sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 导入repo仓库 cat \u0026lt;\u0026lt; EOM \u0026gt; /etc/yum.repos.d/ceph.repo [ceph-noarch] name=Ceph noarch packages baseurl=https://download.ceph.com/rpm-{ceph-stable-release}/el7/noarch enabled=1 gpgcheck=1 type=rpm-md gpgkey=https://download.ceph.com/keys/release.asc EOM 注意这里的{ceph-stable-release}要换成对应的版本号，如我这里就是：\ncat \u0026lt;\u0026lt; EOM \u0026gt; /etc/yum.repos.d/ceph.repo [ceph-noarch] name=Ceph noarch packages baseurl=https://download.ceph.com/rpm-octopus/el7/noarch enabled=1 gpgcheck=1 type=rpm-md gpgkey=https://download.ceph.com/keys/release.asc EOM 然后更新yum repo\nyum clean all yum repolist 安装python工具 sudo yum install python-setuptools 安装ceph-deploy sudo yum update sudo yum install ceph-deploy 注意这里的版本为2.0.1要比epel中的1.5.x要新一些，后面的repo也应该是刚刚导入的ceph repo\n创建ceph部署用户（所有节点） 这里我们需要创建一个用户专门用来给ceph-deploy部署，使用ceph-deploy部署的时候只需要加上--username选项即可指定用户，需要注意的是：\n不建议使用root 不能使用ceph为用户名，因为后面的部署中需要用到该用户名，如果系统中已存在该用户则会先删除掉该用户，然后就会导致部署失败 该用户需要具备超级用户权限（sudo），并且不需要输入密码使用sudo权限 所有的节点均需要创建该用户 该用户需要在ceph集群中的所有机器之间免密ssh登录 创建新用户\nsudo useradd -d /home/{username} -m {username} sudo passwd {username} 配置sudo权限并设置免密\necho \u0026#34;{username} ALL = (root) NOPASSWD:ALL\u0026#34; | sudo tee /etc/sudoers.d/{username} sudo chmod 0440 /etc/sudoers.d/{username} 如果我们的节点已经设置了ssh免密登录，可以直接把免密登录用户的ssh文件夹复制到新建的用户目录下，这里以root用户为例。{username}请替换成需要新建的用户名。\nsudo cp -R /root/.ssh/ /home/{username}/ sudo chown {username}:{username} /home/{username}/.ssh/ -R 编辑deploy节点的ssh文件\n我们可以通过编辑deploy节点的ssh配置文件来指定登录到ceph其他节点的用户：\nHost node1 Hostname node1 User {username} Host node2 Hostname node2 User {username} Host node3 Hostname node3 User {username} 在我这里替换成\nHost ceph71 Hostname ceph71 User cephDeploy Host ceph72 Hostname ceph72 User cephDeploy Host ceph73 Hostname ceph73 User cephDeploy 防火墙中放行端口 sudo firewall-cmd --zone=public --add-service=ceph-mon --permanent # on monitors sudo firewall-cmd --zone=public --add-service=ceph --permanent # on OSDs and MDSs sudo firewall-cmd --reload 也可以直接关闭或禁用防火墙\n安装yum插件 sudo yum install yum-plugin-priorities Ensure that your package manager has priority/preferences packages installed and enabled. On CentOS, you may need to install EPEL. On RHEL, you may need to enable optional repositories.\n部署ceph 创建部署目录 $ mkdir my-cluster $ cd my-cluster $ pwd /home/cephDeploy/my-cluster 由于部署过程中会生成许多文件，这里我们专门创建一个目录用于存放。\n初始化mon节点 $ ceph-deploy new ceph71 [ceph_deploy.conf][DEBUG ] found configuration file at: /home/cephDeploy/.cephdeploy.conf [ceph_deploy.cli][INFO ] Invoked (2.0.1): /usr/bin/ceph-deploy new ceph71 [ceph_deploy.cli][INFO ] ceph-deploy options: [ceph_deploy.cli][INFO ] username : None [ceph_deploy.cli][INFO ] func : \u0026lt;function new at 0x7f7f15a43e60\u0026gt; [ceph_deploy.cli][INFO ] verbose : False [ceph_deploy.cli][INFO ] overwrite_conf : False [ceph_deploy.cli][INFO ] quiet : False [ceph_deploy.cli][INFO ] cd_conf : \u0026lt;ceph_deploy.conf.cephdeploy.Conf instance at 0x7f7f151ba950\u0026gt; [ceph_deploy.cli][INFO ] cluster : ceph [ceph_deploy.cli][INFO ] ssh_copykey : True [ceph_deploy.cli][INFO ] mon : [\u0026#39;ceph71\u0026#39;] [ceph_deploy.cli][INFO ] public_network : None [ceph_deploy.cli][INFO ] ceph_conf : None [ceph_deploy.cli][INFO ] cluster_network : None [ceph_deploy.cli][INFO ] default_release : False [ceph_deploy.cli][INFO ] fsid : None [ceph_deploy.new][DEBUG ] Creating new cluster named ceph [ceph_deploy.new][INFO ] making sure passwordless SSH succeeds [ceph71][DEBUG ] connection detected need for sudo [ceph71][DEBUG ] connected to host: ceph71 [ceph71][DEBUG ] detect platform information from remote host [ceph71][DEBUG ] detect machine type [ceph71][DEBUG ] find the location of an executable [ceph71][INFO ] Running command: sudo /usr/sbin/ip link show [ceph71][INFO ] Running command: sudo /usr/sbin/ip addr show [ceph71][DEBUG ] IP addresses found: [u\u0026#39;240e:f8:a903:2455:5054:ff:fe99:871\u0026#39;, u\u0026#39;192.168.122.71\u0026#39;, u\u0026#39;192.168.100.71\u0026#39;] [ceph_deploy.new][DEBUG ] Resolving host ceph71 [ceph_deploy.new][DEBUG ] Monitor ceph71 at 192.168.122.71 [ceph_deploy.new][DEBUG ] Monitor initial members are [\u0026#39;ceph71\u0026#39;] [ceph_deploy.new][DEBUG ] Monitor addrs are [\u0026#39;192.168.122.71\u0026#39;] [ceph_deploy.new][DEBUG ] Creating a random mon key... [ceph_deploy.new][DEBUG ] Writing monitor keyring to ceph.mon.keyring... [ceph_deploy.new][DEBUG ] Writing initial config to ceph.conf... 指定网卡 由于我们这里的虚拟机每台都有两个网卡，因此我们需要指定ceph集群用于通信的网卡所在的网段\n安装ceph 在所有的节点上都安装ceph\nceph-deploy install ceph71 ceph72 ceph73 初始化mon\nceph-deploy mon create-initial 顺利执行后会在当前目录下生成一系列相关的密钥文件\n使用ceph-deploy复制配置文件和密钥\nUse ceph-deploy to copy the configuration file and admin key to your admin node and your Ceph Nodes so that you can use the ceph CLI without having to specify the monitor address and ceph.client.admin.keyring each time you execute a command.\nceph-deploy admin ceph71 ceph72 ceph73 部署manager ceph-deploy mgr create ceph71 添加OSD 这里我们添加三个节点上面的共计6个硬盘到ceph集群中作为osd\nceph-deploy osd create --data /dev/vdb ceph71 ceph-deploy osd create --data /dev/vdb ceph72 ceph-deploy osd create --data /dev/vdb ceph73 ceph-deploy osd create --data /dev/vdc ceph71 ceph-deploy osd create --data /dev/vdc ceph72 ceph-deploy osd create --data /dev/vdc ceph73 检测结果 # 查看ceph集群状态 sudo ceph health sudo ceph -s 配置dashboard 详细的官网部署文档链接：https://docs.ceph.com/docs/master/mgr/dashboard/\n启用dashboard ceph mgr module enable dashboard 禁用ssl加密 ceph config set mgr mgr/dashboard/ssl false 重启ceph-dashboard ceph mgr module disable dashboard ceph mgr module enable dashboard 配置IP和端口 $ ceph config set mgr mgr/dashboard/$name/server_addr $IP $ ceph config set mgr mgr/dashboard/$name/server_port $PORT $ ceph config set mgr mgr/dashboard/$name/ssl_server_port $PORT 到我这里是\nceph config set mgr mgr/dashboard/ceph71/server_addr 192.168.100.71 ceph config set mgr mgr/dashboard/ceph71/server_port 8080 ceph config set mgr mgr/dashboard/ceph71/ssl_server_port 8443 创建dashboard用户 ceph dashboard ac-user-create \u0026lt;username\u0026gt; \u0026lt;password\u0026gt; administrator 到我这里是\n[root@ceph71 my-cluster]# ceph dashboard set-login-credentials tinychen tinychen 登录后可以看到首页\n最后可以看到的界面如上。\n","permalink":"http://localhost:9011/20200410-ceph-02-ceph-deploy-deploy-ceph/","summary":"\u003cp\u003e使用ceph-deploy部署ceph集群并配置ceph-dashboard。\u003c/p\u003e","title":"Ceph系列02-使用ceph-deploy部署ceph集群"},{"content":"本文主要包括Tomcat9的NIO、NIO2、APR三种I/O模型的工作原理以及使用Jmeter对其进行持续压力测试。\nconnector的工作原理 这里我们说的Tomcat中三种不同的I/O模型主要指的是其连接器（connector）的工作模型，对于tomcat而言，连接器一般指的是coyote，其工作原理大致如下图所示：\n连接器中的各个组件的作用如下：\nEndPoint EndPoint即Coyote通信端点，是通信监听的接口，是具体Socket接收和发送处理器，是对传输层（四层）的抽象，因此EndPoint用来实现TCP/IP协议的。Tomcat 并没有EndPoint接口，而是提供了一个抽象类AbstractEndpoint， 里面定义了两个内部类：Acceptor和SocketProcessor。Acceptor用于监听Socket连接请求。 SocketProcessor用于处理接收到的Socket请求，它实现Runnable接口，在Run方法里 调用协议处理组件Processor进行处理。为了提高处理能力，SocketProcessor被提交到线程池来执行，而这个线程池叫作执行器（Executor)。\nProcessor **Processor是coyote的协议处理接口 。**如果说EndPoint是用来实现TCP/IP协议的，那么 Processor用来实现HTTP协议，Processor接收来自EndPoint的Socket，读取字节流解析成Tomcat的Request和Response对象，并通过Adapter将其提交到容器处理， Processor是对应用层（七层）协议的抽象。\nProtocolHandler **ProtocolHandler是Coyote的协议接口，通过Endpoint和Processor ，实现对具体协议（HTTP或AJP）的处理。**Tomcat 按照协议和I/O 提供了6个实现类 ： AjpNioProtocol ， AjpAprProtocol， AjpNio2Protocol ， Http11NioProtocol ，Http11Nio2Protocol ， Http11AprProtocol。我们在配置tomcat/conf/server.xml 中的connecter块时 ， 至少要指定具体的ProtocolHandler , 当然也可以指定协议名称（如HTTP/1.1）。\nAdapter 由于协议不同，客户端发过来的请求信息也不尽相同，Tomcat定义了自己的Request类来存放这些请求信息。ProtocolHandler接口负责解析请求并生成Tomcat的Request类。 但是这个Request对象不是标准的ServletRequest，不能用来作为参数来调用容器。因此需要引入CoyoteAdapter，连接器调用CoyoteAdapter的Sevice方法，传入Tomcat的Request对象，CoyoteAdapter将Request转成ServletRequest，再调用容器的Service方法。\n三种I/O模型原理 在开始之前，我们先看一下tomcat官网给出的这三种I/O模型的工作参数的一个对比图：\n这里我们可以看到一般说的NIO、NIO2和APR使用的是非阻塞方式指的就是在读取请求报头和等待下一个请求的时候是使用的非阻塞方式。\nTomcat的NIO是基于I/O复用（同步I/O）来实现的，而NIO2是使用的异步I/O。参考经典书籍《UNIX网络编程 卷1 套接字联网API》，两者的主要原理如下：\nI/O复用（NIO） I/O复用（I/O multiplexing）可以调用select或poll，阻塞在这两个系统调用中的某一个之上，而不是阻塞在真正的I/O系统调用上。进程阻塞于select调用，等待数据报套接字变为可读。当select返回套接字可读这一条件时，进程调用recvfrom把所读数据报复制到应用进程缓冲区，尽管这里需要使用select和recvfrom两个系统调用，但是使用select的可以等待多个描述符就绪，即可以等待多个请求。\n异步IO（NIO2） 异步I/O（asynchronous I/O）的工作机制是：告知内核启动某个操作，并让内核在整个操作（包括将数据从内核复制到应用程序的缓冲区）完成后通知应用程序。需要注意的是：异步I/O模型是由内核通知应用进程I/O操作何时完成。\n最后我们可以把上面的过程结合剩下没有提到的三种UNIX系统中的IO模型进行对比得到下图：\nNIO、NIO2和APR的区别 NIO NIO2 APR 实现 JAVA NIO库 JDK1.7 NIO2库 C IO模型 同步非阻塞 异步非阻塞 取决于系统 APR的重点在于使用C语言实现并且能够跨平台使用，它相当于将UNIX系统中的IO操作进行了一层封装使得编程开发更容易\nconnector的几个重要参数 connectionTimeout The number of milliseconds this Connector will wait, after accepting a connection, for the request URI line to be presented. Use a value of -1 to indicate no (i.e. infinite) timeout. The default value is 60000 (i.e. 60 seconds) but note that the standard server.xml that ships with Tomcat sets this to 20000 (i.e. 20 seconds). Unless disableUploadTimeout is set to false, this timeout will also be used when reading the request body (if any).\n在connector和请求的客户端建立连接之后开始计时，当超过该值的时候就会超时，然后断开连接。使用值-1表示无超时，默认值为60000（即60秒），但Tomcat中的server.xml将此值设置为20000（即20秒）。\n除非disableUploadTimeout设置为false，否则在读取请求正文（如果有）时也会使用此超时。\nmaxThreads The maximum number of request processing threads to be created by this Connector, which therefore determines the maximum number of simultaneous requests that can be handled. If not specified, this attribute is set to 200. If an executor is associated with this connector, this attribute is ignored as the connector will execute tasks using the executor rather than an internal thread pool. Note that if an executor is configured any value set for this attribute will be recorded correctly but it will be reported (e.g. via JMX) as -1 to make clear that it is not used.\n最大线程数，大并发请求时，tomcat能创建来处理请求的最大线程数，超过则放入请求队列中进行排队，默认值为200。\nacceptCount The maximum queue length for incoming connection requests when all possible request processing threads are in use. Any requests received when the queue is full will be refused. The default value is 100.\n当最大线程数（maxThreads）被使用完时，可以放入请求队列排队个数，超过这个数返回connection refused（请求被拒绝），默认值为100；\nmaxConnections The maximum number of connections that the server will accept and process at any given time. When this number has been reached, the server will accept, but not process, one further connection. This additional connection be blocked until the number of connections being processed falls below maxConnections at which point the server will start accepting and processing new connections again. Note that once the limit has been reached, the operating system may still accept connections based on the acceptCount setting. The default value is 8192.For NIO/NIO2 only, setting the value to -1, will disable the maxConnections feature and connections will not be counted.\nTomcat在任意时刻接收和处理的最大连接数。当Tomcat接收的连接数达到maxConnections时，Acceptor线程不会读取accept队列中的连接；这时accept队列中的线程会一直阻塞着，直到Tomcat接收的连接数小于maxConnections。默认值为8192。\n对于NIO / NIO2，将该值设置为-1将禁用maxConnections功能，并且不计算连接数。\n图解 按照被处理的先后顺序我们可以把tomcat中的线程队列和以上四个参数使用该图进行表示\n当maxThreads + acceptCount \u0026lt; maxConnections的时候将不会有线程被阻塞 当阻塞的线程时间超过connectionTimeout还没得到返回值将返回连接超时 配置测试环境 配置connector 首先我们需要在tomcat中配置三个connector，分别对应三种I/O模型：\n\u0026lt;Connector port=\u0026#34;8080\u0026#34; protocol=\u0026#34;org.apache.coyote.http11.Http11AprProtocol\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; redirectPort=\u0026#34;8443\u0026#34; acceptCount=\u0026#34;20000\u0026#34; maxThreads=\u0026#34;16\u0026#34; maxConnections=\u0026#34;22000\u0026#34;/\u0026gt; \u0026lt;Connector port=\u0026#34;8081\u0026#34; protocol=\u0026#34;org.apache.coyote.http11.Http11Nio2Protocol\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; redirectPort=\u0026#34;8444\u0026#34; acceptCount=\u0026#34;20000\u0026#34; maxThreads=\u0026#34;200\u0026#34; maxConnections=\u0026#34;22000\u0026#34;/\u0026gt; \u0026lt;Connector port=\u0026#34;8082\u0026#34; protocol=\u0026#34;org.apache.coyote.http11.Http11NioProtocol\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; redirectPort=\u0026#34;8445\u0026#34; acceptCount=\u0026#34;20000\u0026#34; maxThreads=\u0026#34;16\u0026#34; maxConnections=\u0026#34;22000\u0026#34;/\u0026gt; 配置jmeter 测试环境 jmeter是apache旗下的一款开源的使用JAVA编写的服务器压力测试软件，我们从官网下载源码包，分别部署在windows和Linux系统上，因为windows系统的硬件配置太差了，没办法进行高并发的压力测试，所以windows平台只进行jmeter的测试文件jmx的配置，配置完成后再使用Linux测试机来进行压力测试。(注意jmeter版本需要保持一致)\n使用jmeter进行测试的机器系统和内核版本为：\n[root@www ~]# lsb_release -a LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch Distributor ID: RedHatEnterpriseServer Description: Red Hat Enterprise Linux Server release 6.9 (Santiago) Release: 6.9 Codename: Santiago [root@www ~]# uname -r 2.6.32-696.el6.x86_64 安装tomcat9的服务器系统和内核版本为：\n[root@tmpsys conf]# lsb_release -a LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch Distributor ID: n/a Description: NAME=\u0026#34;Red Hat Enterprise Linux Server\u0026#34; Release: n/a Codename: n/a [root@tmpsys conf]# uname -r 3.10.0-1062.18.1.el7.x86_64 配置jmeter jmeter使用前需要配置JDK和系统环境变量（JDK配置这里不再赘述），我们在/etc/profile中导入相关变量并使用source命令保证生效。\nexport JMETER_HOME=/home/jmeter export CLASSPATH=$JMETER_HOME/lib/ext/ApacheJMeter_core.jar:$JMETER_HOME/lib/jorphan.jar:$CLASSPATH export PATH=$JMETER_HOME/bin:$PATH 配置成功后应该可以看到如下输出\n编辑JMX文件 JMX的文件配置不算复杂，最重要的是测试的时间和并发线程数量\n这里我们使用持续压力测试模式，设置循环次数为永远，然后设置持续时间为300秒即5分钟，设置线程数为200并且ramp-up时间为1s即每秒200并发数，如果ramp-up时间为10s即每秒200÷10=20并发数，以此类推。对应到jmx文件中的xml文件块为：\n\u0026lt;ThreadGroup guiclass=\u0026#34;ThreadGroupGui\u0026#34; testclass=\u0026#34;ThreadGroup\u0026#34; testname=\u0026#34;线程组\u0026#34; enabled=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;stringProp name=\u0026#34;ThreadGroup.on_sample_error\u0026#34;\u0026gt;continue\u0026lt;/stringProp\u0026gt; \u0026lt;elementProp name=\u0026#34;ThreadGroup.main_controller\u0026#34; elementType=\u0026#34;LoopController\u0026#34; guiclass=\u0026#34;LoopControlPanel\u0026#34; testclass=\u0026#34;LoopController\u0026#34; testname=\u0026#34;循环控制器\u0026#34; enabled=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;boolProp name=\u0026#34;LoopController.continue_forever\u0026#34;\u0026gt;false\u0026lt;/boolProp\u0026gt; \u0026lt;intProp name=\u0026#34;LoopController.loops\u0026#34;\u0026gt;-1\u0026lt;/intProp\u0026gt; \u0026lt;/elementProp\u0026gt; \u0026lt;stringProp name=\u0026#34;ThreadGroup.num_threads\u0026#34;\u0026gt;200\u0026lt;/stringProp\u0026gt; \u0026lt;stringProp name=\u0026#34;ThreadGroup.ramp_time\u0026#34;\u0026gt;1\u0026lt;/stringProp\u0026gt; \u0026lt;boolProp name=\u0026#34;ThreadGroup.scheduler\u0026#34;\u0026gt;true\u0026lt;/boolProp\u0026gt; \u0026lt;stringProp name=\u0026#34;ThreadGroup.duration\u0026#34;\u0026gt;300\u0026lt;/stringProp\u0026gt; \u0026lt;stringProp name=\u0026#34;ThreadGroup.delay\u0026#34;\u0026gt;\u0026lt;/stringProp\u0026gt; \u0026lt;boolProp name=\u0026#34;ThreadGroup.same_user_on_next_iteration\u0026#34;\u0026gt;true\u0026lt;/boolProp\u0026gt; \u0026lt;/ThreadGroup\u0026gt; 测试类型 这里我们分别测试五分钟持续压测情况下200、400、600、800、1000的并发情况，测试的页面为tomcat的默认首页，tomcat自带的examples中的/examples/servlets/nonblocking/bytecounter.html和/examples/servlets/nonblocking/numberwriter。可以看到后面的两个example都是使用非阻塞的方式进行编写的sevlet。三者的主要操作如下：\ntomcat首页几乎相当于一个静态页面，属于简单的网页请求操作，应用程序发送请求到内核，内核从IO从读取相应文件并返回； numberwriter是生成返回一串很长的数字，应用程序发送请求到内核并接收从内核生成返回的较大的数据； bytecounter需要上传一个文件然后再计算字数（这里使用了一个大小约30KB的markdown文件作为测试），需要进行IO传输和CPU计算再从内核返回一个简单的数值到应用程序； tomcat9启动参数 此处我们使用的依旧是systemd调用jsvc启动tomcat，启动参数如下:\nExecStart=/home/tomcat9/bin/jsvc \\ -user tomcat \\ -nodetach \\ -java-home ${JAVA_HOME} \\ -Xms4096m \\ -Xmx8192m \\ -XX:NewRatio=3 \\ -XX:SurvivorRatio=4 \\ -pidfile ${CATALINA_BASE}/tomcat.pid \\ -classpath ${CATALINA_HOME}/bin/bootstrap.jar:${CATALINA_HOME}/bin/tomcat-juli.jar \\ -outfile ${CATALINA_BASE}/logs/catalina.out \\ -errfile ${CATALINA_BASE}/logs/catalina.err \\ -Dcatalina.home=${CATALINA_HOME} \\ -Dcatalina.base=${CATALINA_BASE} \\ -Djava.io.tmpdir=${CATALINA_TMPDIR} \\ -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \\ -Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \\ -Djava.library.path=/usr/local/apr/lib \\ org.apache.catalina.startup.Bootstrap 测试结果 tomcat首页测试结果 对于简单的请求，三种模式的所有表现数据都几乎一样，基本不存在测试误差范围外的差距。\nnumberwriter测试结果 到了numberwrite这一种返回较长数据的请求，NIO2模型的错误率要比其他两者低得多，到了1200并发的时候apr模型和NIO模型的错误率都已经超过了六成，个人认为此时的响应时间不具有参考性。\nwordcount测试结果 和之前的numberwrite一样，同样是牺牲了响应时间而降低了错误率。\n","permalink":"http://localhost:9011/20200409-tomcat-03-jmeter-nio-nio2-apr/","summary":"\u003cp\u003e本文主要包括Tomcat9的NIO、NIO2、APR三种I/O模型的工作原理以及使用Jmeter对其进行持续压力测试。\u003c/p\u003e","title":"Tomcat篇03-使用Jmeter对Tomcat9的三种IO模型进行持续压力测试"},{"content":"使用qemu-kvm在CentOS8上通过GUI创建虚拟机。\n安装系统组件 首先我们需要安装epel源和相应的虚拟机工具\nyum install epel-release -y yum install qemu-kvm qemu-img libvirt virt-manager virt-top.x86_64 virtio-win.noarch virt-viewer.x86_64 virt-install.noarch -y virt-install libvirt #安装libvirt会将libvirt-client作为依赖进行安装，libvirt-client即libvirt的客户端，最重要的功能之一就是就在宿主机关机时可以通知虚拟机也关机，使虚拟机系统正常关机，而不是被强制关机，造成数据丢失 ipxe-roms-qemu #虚拟机iPXE的启动固件，支持虚拟机从网络启动 qemu-kvm #KVM在用户空间运行的程序 virt-manager #基于 Libvirt 的图像化虚拟机管理软件 libvirt #用于管理虚拟机，它提供了一套虚拟机操作API virt-viewer #显示虚拟机的控制台console virt-top #类似于top命令，查看虚拟机的资源使用情况 virt-what #在虚拟机内部执行，查看虚拟机运行的虚拟化平台 qemu-img #用于操作虚拟机硬盘镜像的创建、查看和格式转化 一些主要组件的功能介绍如下：\nvirt-install\nDescription : Package includes several command line utilities, including virt-install : (build and install new VMs) and virt-clone (clone an existing virtual : machine).\nvirt-who\nDescription : Agent that collects information about virtual guests present in the system and : report them to the subscription manager.\nvirt-top\nDescription : virt-top is a \u0026rsquo;top(1)\u0026rsquo;-like utility for showing stats of virtualized : domains. Many keys and command line options are the same as for : ordinary \u0026rsquo;top\u0026rsquo;. : : It uses libvirt so it is capable of showing stats across a variety of : different virtualization systems.\nvirt-viewer\nDescription : Virtual Machine Viewer provides a graphical console client for connecting : to virtual machines. It uses the GTK-VNC or SPICE-GTK widgets to provide : the display, and libvirt for looking up VNC/SPICE server details.\nvirt-manager\nDescription : Virtual Machine Manager provides a graphical tool for administering virtual : machines for KVM, Xen, and LXC. Start, stop, add or remove virtual devices, : connect to a graphical or serial console, and see resource usage statistics : for existing VMs on local or remote machines. Uses libvirt as the backend : management API.\nlibvirt\nDescription : Libvirt is a C toolkit to interact with the virtualization capabilities : of recent versions of Linux (and other OSes). The main package includes : the libvirtd server exporting the virtualization support.\nqemu-kvm\nDescription : qemu-kvm is an open source virtualizer that provides hardware : emulation for the KVM hypervisor. qemu-kvm acts as a virtual : machine monitor together with the KVM kernel modules, and emulates the : hardware for a full system such as a PC and its associated peripherals.\n创建虚拟机 配置虚拟机存储目录 这里我们直接开启宿主机Linux系统上面的GUI模式并且使用xrdp远程到桌面，然后使用virt-manager来进行GUI模式的创建虚拟机。\n这里需要注意的是因为我们使用的是ISO镜像文件来进行虚拟机系统的安装，因此必须需要一个GUI界面来进行最开始的系统安装，等待系统安装完成之后，我们可以直接使用命令行操作来克隆虚拟机，就不用每次都打开GUI模式安装系统这么麻烦了。\n接着我们创建一个目录专门用来存放虚拟机相关的镜像和文件，同时我们还需要注意权限的问题：\nsudo mkdir -p /kvm/iso sudo chown tinychen:tinychen /kvm -R 接着我们把相应的系统安装镜像复制进去（对应的系统安装镜像文件均可直接在对应的官网下载），然后打开Virtual Machine Manager（以下简称VMM）来创建虚拟机\n首先我们在Edit→Preferences里面开启XML文件编辑功能，XML模式可以最大程度的控制虚拟机的所有配置，一些特殊的配置或者组件操作无法通过VMM的GUI界面完成的我们可以直接编辑对应的XML文件块来实现。\n创建虚拟机 直接点击加号或者上面的File→New Virtual Machine然后选择使用本地媒体创建虚拟机（这里我们使用刚刚复制进去的ISO镜像文件）\n接着我们可以看到有四种创建虚拟机的方式：\n从本地的媒体资源安装（iso文件或者CD光盘） 通过网络协议获取安装的镜像文件然后进行安装 通过PXE进行安装 导入已有的虚拟机硬盘镜像文件 我们选择第一种方式进行新建虚拟机操作。\n由于是第一次创建虚拟机，我们需要先在VMM中添加一个池（pool），把先前创建的存放iso镜像文件的目录作为一个池添加到VMM中。\n添加完成之后我们就可以看到里面的镜像文件，这里我们选择CentOS7的版本进行安装。\n选择镜像之后VMM会自动检测镜像文件内包含的操作系统。\n接下来是设定CPU和内存，注意这里的内存单位是MB，而CPU的单位是个，即并不是我们常说的CPU核心数量或者是超线程的线程数量，而是虚拟设定的多少个CPU，这里选择4即为4个CPU而不是一个4核CPU，这个选项我们后面需要再进行修改。\n接下来便是进行虚拟机的硬盘参数设置，我们这里选择手动创建一个硬盘。\n和之前一样我们再创建一个池专门用来存放虚拟机的系统镜像文件。\n接着在对应的池中新建一个qcow2磁盘文件。\n这里我们可以看到硬盘的格式主要有raw和qcow2两种。qcow2 镜像格式是 QEMU 模拟器支持的一种磁盘镜像。它也是可以用一个文件的形式来表示一块固定大小的块设备磁盘。与普通的 raw 格式的镜像相比，有以下特性：\n更小的空间占用，即使文件系统不支持空洞(holes)； 支持写时拷贝（COW, copy-on-write），镜像文件只反映底层磁盘的变化； 支持快照（snapshot），镜像文件能够包含多个快照的历史； 可选择基于 zlib 的压缩方式 可以选择 AES 加密 qcow2最大的特点就是可以节省硬盘空间，只有在虚拟机实际占用了磁盘空间时，其文件才会增长。比如这里我们创建了一个40G的硬盘镜像文件，但是实际占用并没有40G，只有在虚拟机实际使用了硬盘空间之后，qcow2格式的镜像文件才会增大。\n最后需要设置的就是虚拟机的网卡，这里我们要勾选Customize configuration before install，在系统开始安装之前进行一些参数的设置。\n配置虚拟机其他参数 首先我们看到Overview中的Chipset和Firmware两个参数，它们分别相当于物理机电脑主板的芯片组和固件模式（BIOS或UEFI），如果有新版本的需求（如虚拟机KVM直通GPU）可以选择UEFI（OVMF）来进行启动，需要注意VMM中的快照（snapshot）功能不支持UEFI（OVMF）固件的虚拟机，因此这里我们选择默认的Q35和BIOS即可。\n接下来我们需要对前面设定的CPU参数进行更改。这里我们编辑对应的XML文件将CPU模式修改为host-passthrough以便将CPU的全部特性传递到虚拟机中，同时修改CPU个数为1，核心数为4，每个核心的线程数为1。\n\u0026lt;cpu mode=\u0026#34;host-passthrough\u0026#34;\u0026gt; \u0026lt;topology sockets=\u0026#34;1\u0026#34; cores=\u0026#34;4\u0026#34; threads=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;/cpu\u0026gt; 设定安装系统的ISO为只读，保证ISO文件不会被修改，同时设置数据总线方式为SATA保证最好的兼容性。\n配置两个网卡，分别为桥接宿主机的网卡和NAT模式，其中桥接网卡使用e1000e型号保证最好的兼容性，NAT模式的网卡使用VirtIO型号可以达到最大100G的传输速度。\n其他还会默认添加一些常用的组件，我们根据自己的需求进行修改，这里我只保留了常用的USB重定向设备，声卡、网卡、串行总线等基本设备，如果不清楚具体需求，建议保留。\n最后整个完整的xml文件如下：\n\u0026lt;domain type=\u0026#34;kvm\u0026#34;\u0026gt; \u0026lt;name\u0026gt;centos7_demo\u0026lt;/name\u0026gt; \u0026lt;uuid\u0026gt;2e80a698-c4f9-464b-bb3e-28c4987cbe9a\u0026lt;/uuid\u0026gt; \u0026lt;metadata\u0026gt; \u0026lt;libosinfo:libosinfo xmlns:libosinfo=\u0026#34;http://libosinfo.org/xmlns/libvirt/domain/1.0\u0026#34;\u0026gt; \u0026lt;libosinfo:os id=\u0026#34;http://centos.org/centos/7.0\u0026#34;/\u0026gt; \u0026lt;/libosinfo:libosinfo\u0026gt; \u0026lt;/metadata\u0026gt; \u0026lt;memory\u0026gt;8388608\u0026lt;/memory\u0026gt; \u0026lt;currentMemory\u0026gt;8388608\u0026lt;/currentMemory\u0026gt; \u0026lt;vcpu current=\u0026#34;4\u0026#34;\u0026gt;4\u0026lt;/vcpu\u0026gt; \u0026lt;os\u0026gt; \u0026lt;type arch=\u0026#34;x86_64\u0026#34; machine=\u0026#34;q35\u0026#34;\u0026gt;hvm\u0026lt;/type\u0026gt; \u0026lt;boot dev=\u0026#34;hd\u0026#34;/\u0026gt; \u0026lt;/os\u0026gt; \u0026lt;features\u0026gt; \u0026lt;acpi/\u0026gt; \u0026lt;apic/\u0026gt; \u0026lt;vmport state=\u0026#34;off\u0026#34;/\u0026gt; \u0026lt;/features\u0026gt; \u0026lt;cpu mode=\u0026#34;host-passthrough\u0026#34;\u0026gt; \u0026lt;topology sockets=\u0026#34;1\u0026#34; cores=\u0026#34;4\u0026#34; threads=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;/cpu\u0026gt; \u0026lt;clock offset=\u0026#34;utc\u0026#34;\u0026gt; \u0026lt;timer name=\u0026#34;rtc\u0026#34; tickpolicy=\u0026#34;catchup\u0026#34;/\u0026gt; \u0026lt;timer name=\u0026#34;pit\u0026#34; tickpolicy=\u0026#34;delay\u0026#34;/\u0026gt; \u0026lt;timer name=\u0026#34;hpet\u0026#34; present=\u0026#34;no\u0026#34;/\u0026gt; \u0026lt;/clock\u0026gt; \u0026lt;pm\u0026gt; \u0026lt;suspend-to-mem enabled=\u0026#34;no\u0026#34;/\u0026gt; \u0026lt;suspend-to-disk enabled=\u0026#34;no\u0026#34;/\u0026gt; \u0026lt;/pm\u0026gt; \u0026lt;devices\u0026gt; \u0026lt;emulator\u0026gt;/usr/libexec/qemu-kvm\u0026lt;/emulator\u0026gt; \u0026lt;disk type=\u0026#34;file\u0026#34; device=\u0026#34;disk\u0026#34;\u0026gt; \u0026lt;driver name=\u0026#34;qemu\u0026#34; type=\u0026#34;qcow2\u0026#34;/\u0026gt; \u0026lt;source file=\u0026#34;/kvm/vm/centos7_demo.qcow2\u0026#34;/\u0026gt; \u0026lt;target dev=\u0026#34;vda\u0026#34; bus=\u0026#34;virtio\u0026#34;/\u0026gt; \u0026lt;/disk\u0026gt; \u0026lt;disk type=\u0026#34;file\u0026#34; device=\u0026#34;cdrom\u0026#34;\u0026gt; \u0026lt;driver name=\u0026#34;qemu\u0026#34; type=\u0026#34;raw\u0026#34;/\u0026gt; \u0026lt;source file=\u0026#34;/kvm/iso/centos7.7.iso\u0026#34;/\u0026gt; \u0026lt;target dev=\u0026#34;sda\u0026#34; bus=\u0026#34;sata\u0026#34;/\u0026gt; \u0026lt;readonly/\u0026gt; \u0026lt;/disk\u0026gt; \u0026lt;interface type=\u0026#34;network\u0026#34;\u0026gt; \u0026lt;source network=\u0026#34;default\u0026#34;/\u0026gt; \u0026lt;mac address=\u0026#34;52:54:00:83:cc:6d\u0026#34;/\u0026gt; \u0026lt;model type=\u0026#34;virtio\u0026#34;/\u0026gt; \u0026lt;/interface\u0026gt; \u0026lt;console type=\u0026#34;pty\u0026#34;/\u0026gt; \u0026lt;channel type=\u0026#34;unix\u0026#34;\u0026gt; \u0026lt;source mode=\u0026#34;bind\u0026#34;/\u0026gt; \u0026lt;target type=\u0026#34;virtio\u0026#34; name=\u0026#34;org.qemu.guest_agent.0\u0026#34;/\u0026gt; \u0026lt;/channel\u0026gt; \u0026lt;channel type=\u0026#34;spicevmc\u0026#34;\u0026gt; \u0026lt;target type=\u0026#34;virtio\u0026#34; name=\u0026#34;com.redhat.spice.0\u0026#34;/\u0026gt; \u0026lt;/channel\u0026gt; \u0026lt;input type=\u0026#34;tablet\u0026#34; bus=\u0026#34;usb\u0026#34;/\u0026gt; \u0026lt;graphics type=\u0026#34;spice\u0026#34; port=\u0026#34;-1\u0026#34; tlsPort=\u0026#34;-1\u0026#34; autoport=\u0026#34;yes\u0026#34;\u0026gt; \u0026lt;image compression=\u0026#34;off\u0026#34;/\u0026gt; \u0026lt;/graphics\u0026gt; \u0026lt;video\u0026gt; \u0026lt;model type=\u0026#34;qxl\u0026#34;/\u0026gt; \u0026lt;/video\u0026gt; \u0026lt;memballoon model=\u0026#34;virtio\u0026#34;/\u0026gt; \u0026lt;rng model=\u0026#34;virtio\u0026#34;\u0026gt; \u0026lt;backend model=\u0026#34;random\u0026#34;\u0026gt;/dev/urandom\u0026lt;/backend\u0026gt; \u0026lt;/rng\u0026gt; \u0026lt;interface type=\u0026#34;direct\u0026#34;\u0026gt; \u0026lt;source dev=\u0026#34;enp6s0\u0026#34; mode=\u0026#34;bridge\u0026#34;/\u0026gt; \u0026lt;mac address=\u0026#34;52:54:00:31:ad:1d\u0026#34;/\u0026gt; \u0026lt;model type=\u0026#34;e1000e\u0026#34;/\u0026gt; \u0026lt;/interface\u0026gt; \u0026lt;/devices\u0026gt; \u0026lt;/domain\u0026gt; 4开始安装 接下来的安装就和正常的物理机安装操作系统一样，这里我们就不再赘述了，由于是CentOS的虚拟机，为了节约系统资源，我选择了最小化安装（Minimal Install）、关闭了KDUMP功能，并且使用LVM来设定分区以便后期扩容\n最后可以通过virsh命令来查看虚拟机的状态\n如果需要开启虚拟机只需要使用virsh start 虚拟机名即可。\n","permalink":"http://localhost:9011/20200405-centos-create-kvm-vm/","summary":"\u003cp\u003e使用qemu-kvm在CentOS8上通过GUI创建虚拟机。\u003c/p\u003e","title":"CentOS创建KVM虚拟机"},{"content":"本文主要包括tomcat服务器的目录结构、工作模式、整体架构、I/O模型以及NIO、NIO2、APR三者的对比介绍。\nTomcat的目录结构 我们先来看一下tomcat8.5和tomcat9中的home目录中的文件：\n可以看到除掉一些说明文件之后，还有7个目录：\n目录名 用途 bin 存放用于启动及关闭的文件，以及其他一些脚本。其中，UNIX 系统专用的 *.sh 文件在功能上等同于 windows 系统专用的 *.bat 文件。因为 Win32 的命令行缺乏某些功能，所以又额外地加入了一些文件 conf 配置文件及相关的 DTD*（document type definition 文档类型定义，DTD文件一般和XML文件配合使用，主要是为了约束XML文件）*。其中最重要的文件是 server.xml，这是容器的主配置文件 lib 存放tomcat服务器自身和所有的web应用都可以访问的JAR文件 logs 日志文件的默认目录 temp 存放临时文件的默认目录 webapps 在tomcat上发布Java web应用的时候，默认把web应用的文件存放在这个目录 work tomcat的工作目录，tomcat把运行时生成的一些工作文件存放在这个目录，如默认情况下tomcat会把编译JSP生成的Servlet类文件存放在这里 实际上除了主目录里有lib目录，在webapps目录下的web应用中的WEB-INF目录下也存在一个lib目录：\n两者的区别在于：\n●　Tomcat主目录下的lib目录：存放的JAR文件不仅能被Tomcat访问，还能被所有在Tomcat中发布的Java Web应用访问 ●　webapps目录下的Java Web应用的lib目录：存放的JAR文件只能被当前Java Web应用访问\n既然有多个lib目录，那么肯定就有使用的优先顺序，Tomcat类加载器的目录加载优先顺序如下：\nTomcat的类加载器负责为Tomcat本身以及Java Web应用加载相关的类。假如Tomcat的类加载器要为一个Java Web应用加载一个类，类加载器会按照以下优先顺序到各个目录中去查找该类的.class文件，直到找到为止，如果所有目录中都不存在该类的.class文件，则会抛出异常：\n在Java Web应用的WEB-INF/classes目录下查找该类的.class文件 在Java Web应用的WEB-INF/lib目录下的JAR文件中查找该类的.class文件 在Tomcat的lib子目录下直接查找该类的.class文件 在Tomcat的lib子目录下的JAR文件中查找该类的.class文件 Tomcat的工作模式 Tomcat不仅可以单独运行，还可以与其他的Web服务器集成，作为其他Web服务器的进程内或进程外的servlet容器。集成的意义在于：对于不支持运行Java Servlet的其他Web服务器，可通过集成Tomcat来提供运行Servlet的功能。\nTomcat有三种工作模式：\n第一种：Tomcat在一个Java虚拟机进程中独立运行，此时客户端直接和tomcat通信。Tomcat可看作是能运行Servlet的独立Web服务器。Servlet容器组件作为Web服务器中的一部分而存在。这是Tomcat的默认工作模式。 第二种：Tomcat运行在其他Web服务器的进程中，Tomcat不直接和客户端通信，仅仅为其他Web服务器处理客户端访问Servlet的请求。进程内的Servlet容器对于单进程、多线程的Web服务器非常合适，可以提供较高的运行速度，但缺乏伸缩性。\n在这种模式下，Tomcat分为Web服务器插件和Servlet容器组件两部分。如下图所示，Web服务器插件在其他Web服务器进程的内部地址空间启动一个Java虚拟机，Servlet容器组件在此Java虚拟机中运行。如有客户端发出调用Servlet的请求，Web服务器插件获得对此请求的控制并将它转发**（使用JNI通信机制）**给Servlet容器组件。\nJNI（Java Native Interface）指的是Java本地调用接口，通过这一接口，Java程序可以和采用其他语言编写的本地程序进行通信。\n第三种：Tomcat在一个Java虚拟机进程中独立运行，但是它不直接和客户端通信，仅仅为与它集成的其他Web服务器处理客户端访问Servlet的请求。\n在这种模式下，Tomcat分为Web服务器插件和Servlet容器组件两部分。如下图所示，Web服务器插件在其他Web服务器的外部地址空间启动一个JVM进程，Servlet容器组件在此JVM中运行。如有客户端发出调用Servlet的请求，Web服务器插件获得对此请求的控制并将它转发（采用IPC通信机制）给Servlet容器。\n进程外Servlet容器对客户请求的响应速度不如进程内Servlet容器，但进程外容器具有更好的伸缩性和稳定性。\nIPC（Inter-Process Communication，进程间通信）是两个进程之间进行通信的一种机制。\nTomcat的整体架构 我们先从tomcat的源码目录来分析一下tomcat的整体架构，前面我们配置jsvc运行tomcat的时候，我们知道tomcat中启动运行的最主要的类是org.apache.catalina.startup.Bootstrap，那么我们在tomcat的源码中的java目录下的org目录的apache目录可以找到主要的源码的相对应的类。\n图中的目录如果画成架构图，可以这样表示：\nTomcat 本质上就是一款Servlet 容器，因此catalina才是Tomcat的核心 ，其他模块都是为catalina提供支撑的。\ncoyote模块主要负责链接通信，Tomcat作为http服务器，需要从socket中获得HTTP数据流；而Tomcat作为容器，只能处理封装好的org.apache.coyote.Request，因此从socket到Request之间的转换就交给coyote来负责了。因此，连接socket和容器之间的重任就交给了Coyote。简单说就是coyote来处理底层的socket，并将http请求、响应等字节流层面的东西，包装成Request和Response两个类（这两个类是tomcat定义的，而非servlet中的ServletRequest和ServletResponse），供容器使用；同时，为了能让我们编写的servlet能够得到ServletRequest，tomcat使用了facade模式，将比较底层、低级的Request包装成为ServletRequest（这一过程通常发生在Wrapper容器一级） jasper模块提供JSP引擎，在jsp文件被初次访问的时候做出响应，将jsp页面翻译成servlet请求，然后调用java编译器对servlet进行编译得到class文件，再调用jvm来执行class文件生成应答，最后把应答发送回客户端。\nel全名为Expression Language，也叫JUEL，主要在Java Web应用中用于将表达式嵌入到web页面\nnaming提供JNDI 服务(Java Naming and Directory Interface,Java命名和目录接口)，为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口，由管理者将JNDI API映射为特定的命名服务和目录系统，使得Java应用程序可以和这些命名服务和目录服务之间进行交互。\njuli提供日志服务，JDK 所提供的默认 java.util.logging 实现功能太过局限，不能实现针对每一应用进行日志记录，因为配置是针对VM的。而juli通过自定义的 LogManager 能分辨运行在 Tomcat 上的不同 Web 应用（以及它们所用的不同的类加载器），还能针对每一应用进行私有的日志配置。\nTomcat的I/O模型 阻塞I/O处理模型 单线程阻塞I/O模型 单线程阻塞I/O模型是最简单的一种服务器I/O模型，单线程即同时只能处理一个客户端的请求，阻塞即该线程会一直等待，直到处理完成为止。对于多个客户端访问，必须要等到前一个客户端访问结束才能进行下一个访问的处理，请求一个一个排队，只提供一问一答服务。\n如上图所示：这是一个同步阻塞服务器响应客户端访问的时间节点图。\n首先，服务器必须初始化一个套接字服务器，并绑定某个端口号并使之监听客户端的访问 接着，客户端1调用服务器的服务，服务器接收到请求后对其进行处理，处理完后写数据回客户端1，整个过程都是在一个线程里面完成的 最后，处理客户端2的请求并写数据回客户端2，期间就算客户端2在服务器处理完客户端1之前就进行请求，也要等服务器对客户端1响应完后才会对客户端2进行响应处理 **这种模型的特点在于单线程和阻塞I/O。**单线程即服务器端只有一个线程处理客户端的所有请求，客户端连接与服务器端的处理线程比是n:1，它无法同时处理多个连接，只能串行处理连接。而阻塞I/O是指服务器在读写数据时是阻塞的，读取客户端数据时要等待客户端发送数据并且把操作系统内核复制到用户进程中，这时才解除阻塞状态。写数据回客户端时要等待用户进程将数据写入内核并发送到客户端后才解除阻塞状态。这种阻塞带来了一个问题，服务器必须要等到客户端成功接收才能继续往下处理另外一个客户端的请求，在此期间线程将无法响应任何客户端请求。\n该模型的特点：它是最简单的服务器模型，整个运行过程都只有一个线程，只能支持同时处理一个客户端的请求(如果有多个客户端访问，就必须排队等待)，服务器系统资源消耗较小，但并发能力低，容错能力差。\n多线程阻塞I/O模型 多线程阻塞I/O模型在单线程阻塞I/O模型的基础上对其进行改进，加入多线程，提高并发能力，使其能够同时对多个客户端进行响应，多线程的核心就是利用多线程机制为每个客户端分配一个线程。\n如上图所示，服务器端开始监听客户端的访问，假如有两个客户端同时发送请求过来，服务器端在接收到客户端请求后分别创建两个线程对它们进行处理，每条线程负责一个客户端连接，直到响应完成。期间两个线程并发地为各自对应的客户端处理请求，包括读取客户端数据、处理客户端数据、写数据回客户端等操作。\n这种模型的I/O操作也是阻塞的，因为每个线程执行到读取或写入操作时都将进入阻塞状态，直到读取到客户端的数据或数据成功写入客户端后才解除阻塞状态。尽管I/O操作阻塞，但这种模式比单线程处理的性能明显高了，它不用等到第一个请求处理完才处理第二个，而是并发地处理客户端请求，客户端连接与服务器端处理线程的比例是1:1。\n多线程阻塞I/O模型的特点：支持对多个客户端并发响应，处理能力得到大幅提高，有较大的并发量，但服务器系统资源消耗量较大，而且如果线程数过多，多线程之间会产生较大的线程切换成本，同时拥有较复杂的结构。\n非阻塞I/O模型 非阻塞情况下的事件检测 在探讨单线程非阻塞I/O模型前必须要先了解非阻塞情况下套接字事件的检测机制，因为对于单线程非阻塞模型最重要的事情是检测哪些连接有感兴趣的事件发生。一般会有如下三种检测方式。\n此处“有感兴趣的事件发生”指的是需要进行读写数据等操作。\n应用程序遍历套接字的事件检测 当多个客户端向服务器请求时，服务器端会保存一个套接字连接列表中，应用层线程对套接字列表轮询尝试读取或写入。如果成功则进行处理，如果失败则下次继续。这样不管有多少个套接字连接，它们都可以被一个线程管理，这很好地利用了阻塞的时间，处理能力得到提升。\n但这种模型需要在应用程序中遍历所有的套接字列表，同时需要处理数据的拼接，连接空闲时可能也会占用较多CPU资源，不适合实际使用。\n内核遍历套接字的事件检测 这种方式将套接字的遍历工作交给了操作系统内核，把对套接字遍历的结果组织成一系列的事件列表并返回应用层处理。对于应用层，它们需要处理的对象就是这些事件，这是一种事件驱动的非阻塞方式。\n服务器端有多个客户端连接，应用层向内核请求读写事件列表。内核遍历所有套接字并生成对应的可读列表readList和可写列表writeList。readList和writeList则标明了每个套接字是否可读/可写。应用层遍历读写事件列表readList和writeList，做相应的读写操作。\n内核遍历套接字时已经不用在应用层对所有套接字进行遍历，将遍历工作下移到内核层，这种方式有助于提高检测效率。**然而，它需要将所有连接的可读事件列表和可写事件列表传到应用层，假如套接字连接数量变大，列表从内核复制到应用层也是不小的开销。**另外，当活跃连接较少时，内核与应用层之间存在很多无效的数据副本，因为它将活跃和不活跃的连接状态都复制到应用层中。\n内核基于回调的事件检测 通过遍历的方式检测套接字是否可读可写是一种效率比较低的方式，不管是在应用层中遍历还是在内核中遍历。所以需要另外一种机制来优化遍历的方式，那就是回调函数。内核中的套接字都对应一个回调函数，当客户端往套接字发送数据时，内核从网卡接收数据后就会调用回调函数，在回调函数中维护事件列表，应用层获取此事件列表即可得到所有感兴趣的事件。\n内核基于回调的事件检测方式有两种\n方式一： 第一种是用可读列表readList和可写列表writeList标记读写事件，套接字的数量与readList和writeList两个列表的长度一样。\n服务器端有多个客户端套接字连接\n当客户端发送数据过来时，内核从网卡复制数据成功后调用回调函数将readList/writeList对应的元素标记为可读/可写\n应用层发送请求读、写事件列表，内核返回包含了事件标识的readList和writeList事件列表，此时返回的两个列表内容大致如下\n套接字 readList 1 1 2 0 3 1 …… …… n …… 套接字 writeList 1 0 2 1 3 0 …… …… n …… 应用程序接着分表遍历读事件列表readList和写事件列表writeList，对置为1的元素对应的套接字进行读或写操作\n这样就避免了遍历套接字的操作，但仍然有大量无用的数据(状态为0的元素)从内核复制到应用层中。从上面的表格中我们可以看到实际上有用的数据只是在List中被标记为1的数据（意味着可读或可写），其他的数据并没有传送回去的必要。\n方式二： 服务器端有多个客户端套接字连接。\n应用层告诉内核每个套接字感兴趣的事件，这时候直接发送一个列表给内核\n套接字 操作 1 read 2 write 3 read …… …… n …… 接着，当客户端发送数据过来时，对应会有一个回调函数，内核从网卡复制数据成功后即调回调函数将套接字1作为可读事件event1加入到事件列表，同样地，内核发现网卡可写时就将套接字2作为可写事件event2添加到事件列表中\n应用层向内核请求读、写事件列表，内核将包含了event1和event2的事件列表返回应用层，此时的列表内容大致如下：\n套接字 可以进行的操作 1 read 2 write 注意这时不能进行读写操作的套接字是不会被记录到列表中返回给应用层的，这就大大地减少了数据的传输量。\n应用层通过遍历事件列表得知哪些套接字可以进行哪些操作，然后执行对应的操作。\n上面两种方式由操作系统内核维护客户端的所有连接并通过回调函数不断更新事件列表，而应用层线程只要遍历这些事件列表即可知道可读取或可写入的连接，进而对这些连接进行读写操作，极大提高了检测效率，自然处理能力也更强。\n单线程非阻塞I/O模型 单线程非阻塞I/O模型最重要的一个特点是，在调用读取或写入接口后立即返回，而不会进入阻塞状态。虽然只有一个线程，但是它通过把非阻塞读写操作与上面几种检测机制配合就可以实现对多个连接的及时处理，而不会因为某个连接的阻塞操作导致其他连接无法处理。在客户端连接大多数都保持活跃的情况下，这个线程会一直循环处理这些连接，它很好地利用了阻塞的时间，大大提高了这个线程的执行效率。\n单线程非阻塞I/O模型的主要优势体现在对多个连接的管理，一般在同时需要处理多个连接的发场景中会使用非阻塞NIO模式，此模型下只通过一个线程去维护和处理连接，这样大大提高了机器的效率。一般服务器端才会使用NIO模式，而对于客户端，出于方便及习惯，可使用阻塞模式的套接字进行通信。\n多线程非阻塞I/O模型 在多核的机器上可以通过多线程继续提高机器效率。最朴实、最自然的做法就是将客户端连接按组分配给若干线程，每个线程负责处理对应组内的连接。比如有4个客户端访问服务器，服务器将套接字1和套接字2交由线程1管理，而线程2则管理套接字3和套接字4，通过事件检测及非阻塞读写就可以让每个线程都能高效处理。\n多线程非阻塞I/O模式让服务器端处理能力得到很大提高，它充分利用机器的CPU，适合用于处理高并发的场景，但它也让程序更复杂，更容易出现问题（死锁、数据不一致等经典并发问题）。\nReactor模式 最经典的多线程非阻塞I/O模型方式是Reactor模式。首先看单线程下的Reactor，Reactor将服务器端的整个处理过程分成若干个事件，例如分为接收事件、读事件、写事件、执行事件等。Reactor通过事件检测机制将这些事件分发给不同处理器去处理。在整个过程中只要有待处理的事件存在，即可以让Reactor线程不断往下执行，而不会阻塞在某处，所以处理效率很高。\n基于单线程Reactor模型，根据实际使用场景，把它改进成多线程模式。常见的有两种方式：一种是在耗时的process处理器中引入多线程，如使用线程池；另一种是直接使用多个Reactor实例，每个Reactor实例对应一个线程。\nReactor模式的一种改进方式如下图所示。其整体结构基本上与单线程的Reactor类似，只是引入了一个线程池。由于对连接的接收、对数据的读取和对数据的写入等操作基本上都耗时较少，因此把它们都放到Reactor线程中处理。然而，对于逻辑处理可能比较耗时的工作，可以在process处理器中引入线程池，process处理器自己不执行任务，而是交给线程池，从而在Reactor线程中避免了耗时的操作。将耗时的操作转移到线程池中后，尽管Reactor只有一个线程，它也能保证Reactor的高效。\nReactor模式的另一种改进方式如下图所示。其中有多个Reactor实例，每个Reactor实例对应一个线程。因为接收事件是相对于服务器端而言的，所以客户端的连接接收工作统一由一个accept处理器负责，accept处理器会将接收的客户端连接均匀分配给所有Reactor实例，每个Reactor实例负责处理分配到该Reactor上的客户端连接，包括连接的读数据、写数据和逻辑处理。这就是多Reactor实例的原理。\nTomcat的I/O模型 Tomcat支持的I/O模型如下表（自8.5/9.0 版本起，Tomcat移除了对BIO的支持），在 8.0 之前 ， Tomcat 默认采用的I/O方式为 BIO ， 之后改为 NIO。 无论 NIO、NIO2 还是 APR， 在性能方面均优于以往的BIO。\nIO模型 描述 NIO 同步非阻塞I/O，采用Java NIO类库实现 NIO2 异步非阻塞I/O，采用JDK 7最新的NIO2类库实现 APR 采用Apache可移植运行库实现，是C/C++编写的本地库，需要单独安装APR库 NIO（New I/O APIs、同步非阻塞） **Tomcat中的NIO模型是使用的JAVA的NIO类库，其内部的IO实现是同步的（也就是在用户态和内核态之间的数据交换上是同步机制），采用基于selector实现的异步事件驱动机制（这里的异步指的是selector这个实现模型是使用的异步机制）。**而对于Java来说，非阻塞I/O的实现完全是基于操作系统内核的非阻塞I/O，它将操作系统的非阻塞I/O的差异屏蔽并提供统一的API，让我们不必关心操作系统。JDK会帮我们选择非阻塞I/O的实现方式。\n这里需要提一下同步异步和阻塞非阻塞的概念：\n同步和异步关注的是消息通信机制，同步异步指的是应用程序发起的调用请求和获得的返回值是否一起返回，如果一起返回就是同步，否则就是异步，异步可以通过回调函数等方式实现。\n阻塞和非阻塞关注的是程序在等待调用结果时的状态，应用程序发起调用请求之后不能干别的事情直到请求处理完成了就是阻塞，否则就是非阻塞。\n所以我个人认为，对于阻塞I/O谈同步异步是没有太大意义的，因为此时进程已经阻塞，想要去干别的事情必须得等请求处理完，而请求处理完必然会得到返回值。\n上面我们提到得内核基于回调得事件检测方式二就是典型的异步非阻塞I/O模型。\nNIO2（New I/O APIs 2、异步非阻塞、AIO） NIO2和前者相比的最大不同就在于引入了异步通道来实现异步IO操作，因此也叫AIO（Asynchronous I/O）。NIO.2 的异步通道 APIs 提供方便的、平台独立的执行异步操作的标准方法。这使得应用程序开发人员能够以更清晰的方式来编写程序，而不必定义自己的 Java 线程，此外，还可通过使用底层 OS 所支持的异步功能来提高性能。如同其他 Java API 一样，API 可利用的 OS 自有异步功能的数量取决于其对该平台的支持程度。\n异步通道提供支持连接、读取、以及写入之类非锁定操作的连接，并提供对已启动操作的控制机制。Java 7 中用于 Java Platform（NIO.2）的 More New I/O APIs，通过在 java.nio.channels 包中增加四个异步通道类，从而增强了 Java 1.4 中的 New I/O APIs（NIO），这些类在风格上与 NIO 通道 API 很相似。他们共享相同的方法与参数结构体，并且大多数对于 NIO 通道类可用的参数，对于新的异步版本仍然可用。主要区别在于新通道可使一些操作异步执行。\n异步通道 API 提供两种对已启动异步操作的监测与控制机制。第一种是通过返回一个 java.util.concurrent.Future 对象来实现，它将会建模一个挂起操作，并可用于查询其状态以及获取结果。第二种是通过传递给操作一个新类的对象，java.nio.channels.CompletionHandler，来完成，它会定义在操作完毕后所执行的处理程序方法。每个异步通道类为每个操作定义 API 副本，这样可采用任一机制。\nAPR **Apache可移植运行时（Apache Portable Runtime，APR）**是Apache HTTP服务器的支持库，最初，APR是作为Apache HTTP服务器的一部分而存在的，后来成为一个单独的项目。其他的应用程序可以使用APR来实现平台无关性（跨平台）。APR提供了一组映射到下层操作系统的API，如果操作系统不支持某个特定的功能，APR将提供一个模拟的实现。这样程序员使用APR编写真正可在不同平台上移植的程序。\nTomcat配置APR # 手动编译安装最新版本的apr cd /home wget https://dlcdn.apache.org//apr/apr-1.7.0.tar.gz yum group install \u0026#34; Development Tools \u0026#34; -y tar -zxvf apr-1.7.0.tar.gz /home/apr-1.7.0 ./buildconf ./configure \u0026amp;\u0026amp; make \u0026amp;\u0026amp; make install # 也可以使用yum来安装apr yum install apr apr-devel # 进入tomcat目录下对tomcat-native进行解压 cd /home/tomcat9/bin/ tar -zxvf tomcat-native.tar.gz cd tomcat-native-1.2.23-src/native/ # 编译安装 ./configure make make install 顺利安装完成后会显示apr的lib库路径，一般都是/usr/local/apr/lib\n安装完成之后我们还需要修改环境变量和配置参数\n这里我们使用的是systemd调用jsvc来启动tomcat，所以我们直接在systemd对应的tomcat的unit文件中的ExecStart中添加一个路径参数-Djava.library.path=/usr/local/apr/lib指向apr库的路径：\nExecStart=/home/tomcat9/bin/jsvc \\ -user tomcat \\ -nodetach \\ -java-home ${JAVA_HOME} \\ -pidfile ${CATALINA_BASE}/tomcat.pid \\ -classpath ${CATALINA_HOME}/bin/bootstrap.jar:${CATALINA_HOME}/bin/tomcat-juli.jar \\ -outfile ${CATALINA_BASE}/logs/catalina.out \\ -errfile ${CATALINA_BASE}/logs/catalina.err \\ -Dcatalina.home=${CATALINA_HOME} \\ -Dcatalina.base=${CATALINA_BASE} \\ -Djava.io.tmpdir=${CATALINA_TMPDIR} \\ -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \\ -Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \\ -Djava.library.path=/usr/local/apr/lib \\ org.apache.catalina.startup.Bootstrap 然后我们在tomcat的home目录下的conf子目录中对server.xml文件进行修改\n把8080端口对应的配置修改成apr：（其他端口配置也类似）\n\u0026lt;Connector port=\u0026#34;8080\u0026#34; protocol=\u0026#34;org.apache.coyote.http11.Http11AprProtocol\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; redirectPort=\u0026#34;8443\u0026#34; /\u0026gt; 重启tomcat服务我们从tomcat的日志中就可以看到协议已经从默认的NIO变成了apr。\n三者之间的区别： NIO性能是最差的这是毋庸置疑的，如果是考虑到高并发的情况，显然异步非阻塞I/O模式的NIO2和APR库在性能上更有优势，实际上NIO2的性能表现也和APR不相上下，但是NIO2要求Tomcat的版本要在8.0以上，而APR只需要5.5以上即可，但是APR需要额外配置库环境，相对于内置集成的NIO2来说APR这个操作比较麻烦，两者各有优劣。具体使用哪个还是需要结合实际业务需求和环境进行测试才能决定。\n","permalink":"http://localhost:9011/20200402-tomcat-02-structure-io-model/","summary":"\u003cp\u003e本文主要包括tomcat服务器的目录结构、工作模式、整体架构、I/O模型以及NIO、NIO2、APR三者的对比介绍。\u003c/p\u003e","title":"Tomcat篇02-整体架构和I/O模型"},{"content":"为微软windows系统自带的RDP远程桌面配置权威CA签发的证书，使得可以安全加密远程。\n首先我们需要确定可以通过公网IP:端口号的方式来远程访问自己的windows主机，如果只是在内网远程，windows本身自己签发的证书便足够了，但是如果暴露在公网上还是使用权威CA签发的可信证书比较安全。\n如果家里的宽带有公网IP的可以使用DDNS+端口转发的方式暴露出远程桌面端口号，没有公网IP则可以考虑FRP内网穿透等方式。\n接下来我们要准备一个域名和一张域名相关的证书，这里我使用的是阿里云购买的万网域名，证书也是使用的阿里云上申请的免费证书。下载证书一般来说可以得到一个pem文件和一个key文件。\n首先我们使用openssl来生成p12文件，命令格式如下。如果没有安装openssl，可以先安装一个git，然后打开git bash即可使用openssl。\nopenssl pkcs12 -export -clcerts -in [your_domain_crt.pem] -inkey [your_domain_key.key] -out [your_domain.p12] 首先我们按win+r然后输入mmc打开管理台：\n然后在文件里面添加管理单元，选择证书，注意这里账户要选择计算机账户\n然后在个人这里右键选择导入证书，接着按照默认设置导入我们刚刚生成的p12证书：\n接着我们需要修改该证书的属性使得远程桌面能够使用它\n然后找到我们刚刚导入的证书，点击 右键 ，选择 所有任务-管理私钥 ，然后把读取 权限分配给 NETWORK SERVICE\n成功之后我们使用证书对应的域名进行远程的时候就不会再弹出证书不安全的提醒了，如果不是使用域名进行连接而是使用IP或者局域网名的话还是会提醒证书不安全。\n默认情况下是会使用windows自带的自签证书进行连接。\n","permalink":"http://localhost:9011/20200331-mstsc-deploy-tls-cert/","summary":"\u003cp\u003e为微软windows系统自带的RDP远程桌面配置权威CA签发的证书，使得可以安全加密远程。\u003c/p\u003e","title":"windows系统RDP远程桌面配置可信证书"},{"content":"本文主要包括tomcat服务器的主要概念介绍、在systemd上的tomcat守护进程的配置、jsvc的原理介绍和systemd的并发实现原理介绍。\nTomcat简介 在了解tomcat之前我们需要了解一些基本的概念。\nweb应用 所谓Web应用，就是指需要通过编程来创建的Web站点。Web应用中不仅包括普通的静态HTML文档，还包含大量可被Web服务器动态执行的程序。用户在Internet上看到的能开展业务的各种Web站点都可看作Web应用，例如，网上商店和网上银行都是Web应用。此外，公司内部基于Web的Intranet工作平台也是Web应用。\nWeb应用与传统的桌面应用程序相比，具有以下特点：\n以浏览器作为展示客户端界面的窗口。 客户端界面一律表现为网页形式，网页由HTML语言写成。 客户端与服务器端能进行和业务相关的动态交互。 能完成与桌面应用程序类似的功能。 使用浏览器—服务器架构（B/S），浏览器与服务器之间采用HTTP协议通信。 Web应用通过Web服务器来发布。 web应用的一大好处就是可以轻易地跨平台运行，不论是windows、mac、ios、android还是linux，只要安装了浏览器，一般都可以使用web应用，而浏览器在各个平台都是标配的软件，因此给web应用的普及提供了非常良好的条件。同样的，web应用使用的是B/S架构，即Browser/Server架构，主要的计算任务都交给Server端进行，因此都客户端的性能要求较低，同时也推动了服务端的负载均衡、高可用等技术的发展。\nContext：在tomcat中一般指web应用\nServlet **Servlet（Server Applet），全称Java Servlet。是用Java编写的服务器端程序。**其主要功能在于交互式地浏览和修改数据，生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口，广义的Servlet是指任何实现了这个Servlet接口的类别，一般情况下，我们说的Servlet为后者。\nServlet运行于支持Java的应用服务器中。从实现上讲，Servlet可以响应任何类型的请求，但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。也就是说Web服务器可以访问任意一个Web应用中所有实现Servlet接口的类。而Web应用中用于被Web服务器动态调用的程序代码位于Servlet接口的实现类中。既然servlet和java关系密切，那么servlet接口的标准制定毫无疑问也是由甲骨文公司来主导。\n**Servlet规范把能够发布和运行Java Web应用的Web服务器称为Servlet容器。**Servlet容器最主要的特征是动态执行Java Web应用中Servlet实现类的程序代码。由Apache开源软件组织创建的Tomcat是一个符合Servlet规范的优秀Servlet容器。\njsp JSP（全称JavaServer Pages）是由Sun Microsystems公司主导建立的一种动态网页技术标准。JSP是HttpServlet的扩展。JSP将Java代码和特定变动内容嵌入到静态的页面中，实现以静态页面为模板，动态生成其中的部分内容。JSP在首次被访问的时候被应用服务器转换为servlet，在以后的运行中，容器直接调用这个servlet，而不再访问JSP页面。JSP的实质仍然是servlet。\nTomcat Tomcat 是在Oracle公司的JSWDK（JavaServer Web DevelopmentKit，是Oracle公司推出的小型Servlet/JSP调试工具）的基础上发展起来的一个优秀的Servlet容器，Tomcat本身完全用Java语言编写。作为一个开源软件，Tomcat除了运行稳定、可靠，并且效率高之外，还可以和目前大部分的主流Web服务器（如IIS、Apache、Nginx等）一起工作。\ntomcat的版本实际上比较复杂，目前有7、8、9、10四个版本并行发布，具体的各个版本的兼容信息我们可以通过官网查询。\nTomcat安装配置 tomcat的配置安装需要先在系统上配置好jdk环境，这里我们使用centos7.7版本的Linux系统和jdk8版本。\n配置jdk8 我们首先到官网下载JDK8的安装包，这里我们选择tar.gz格式的压缩包下载，需要注意建议先使用浏览器下载再使用工具传输到Linux上，因为下载需要登录注册账号。\n接着我们解压将安装包解压到自己想要配置的jdk安装目录下，这里我们使用/home/目录\ntar -zxvf jdk-8u241-linux-x64.tar.gz -C /home/ 在/etc/profile中添加以下三个参数并导入\nJAVA_HOME=/home/jdk_1.8.0_241 CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin export JAVA_HOME CLASSPATH PATH 重新载入配置文件\nsource /etc/profile 检查配置是否生效，如不生效可以重启终端试试：\n[root@tiny-yun ~]# java -version java version \u0026#34;1.8.0_241\u0026#34; Java(TM) SE Runtime Environment (build 1.8.0_241-b07) Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode) 配置tomcat tomcat的安装配置和上面几乎一样，由于我们已经在/etc/profile中设定了全局的java环境变量，因此在tomcat中就不用再特殊配置，直接就会使用默认的全局变量。\n这里我们还是使用官网 提供的tar.gz压缩包来安装。\n# tomcat可以直接使用wget下载 wget https://downloads.apache.org/tomcat/tomcat-8/v8.5.53/bin/apache-tomcat-8.5.53.tar.gz # 解压到安装目录并重命名 tar -zxvf apache-tomcat-8.5.53.tar.gz /home/ cd /home mv apache-tomcat-8.5.53 tomcat-8.5.53 tomcat目录 首先我们来看一下tomcat中的主要目录：\n/bin 存放用于启动及关闭的文件，以及其他一些脚本。其中，UNIX 系统专用的 *.sh 文件在功能上等同于 Windows 系统专用的 *.bat 文件。因为 Win32 的命令行缺乏某些功能，所以又额外地加入了一些文件。 /conf 配置文件及相关的 DTD。其中最重要的文件是 server.xml，这是容器的主配置文件。 /log 日志文件的默认目录。 /webapps 存放 Web 应用的相关文件。 接着我们进入tomcat目录下的bin目录就可以看到各种各样的脚本文件，主要分为bat和sh两类，其中bat主要是在windows系统上使用的，我们可以把它们删掉，接着我们执行一些version.sh这个脚本就可以看到版本信息。\n接下来我们来看一下和tomcat相关的几个变量：\nJRE_HOME\n这里我们可以看到JRE_HOME这个变量是之前设置了的JAVA_HOME环境变量。\n如果同时定义了JRE_HOME和JAVA_HOME这两个变量，那么使用的是JRE_HOME 如果只定义了JAVA_HOME，那么JRE_HOME变量值就是JAVA_HOME的变量值 如果两个变量都没定义，那么tomcat无法运行 前面我们提到过tomcat是使用Java编写的，这也就意味着它在运行的时候需要创建一个JVM虚拟机，所以如果没定义JAVA环境变量，tomcat是无法运行的\nCATALINA_HOME tomcat安装目录的根目录\nCATALINA_BASE tomcat实例运行的目录，默认情况下等于CATALINA_HOME，如果我们需要在一台机器上运行多个tomcat实例，可以设置多个CATALINA_BASE\nsetenv.sh 这个脚本默认是不存在的，需要我们自己手动创建在bin目录下，在windows系统则应该是setenv.bat，我们在里面指定了JRE_HOME环境变量以及PID文件的位置，这样在运行的时候就能比较方便的定位到运行进程\n注意前面提到的CATALINA_HOME和CATALINA_BASE两个变量不能在这里设定，因为tomcat就是根据这两个变量来找到 setenv.sh的。\n$ cat setenv.sh JRE_HOME=/home/jdk1.8.0_241/jre CATALINA_PID=\u0026#34;$CATALINA_BASE/tomcat.pid\u0026#34; 这时候运行./catalina.sh start或者是./startup.sh文件就可以启动tomcat，注意要在防火墙中放行默认的8080端口。如果没有指定PID文件的位置，在关闭tomcat的时候可能会出现错误。此外，一般不建议使用root用户来运行tomcat。\n个人感觉使用catalina.sh加参数的方式来控制tomcat进程要更加灵活强大一些。\n$ ./catalina.sh -h Using CATALINA_BASE: /home/tomcat-8.5.53 Using CATALINA_HOME: /home/tomcat-8.5.53 Using CATALINA_TMPDIR: /home/tomcat-8.5.53/temp Using JRE_HOME: /home/jdk1.8.0_241/jre Using CLASSPATH: /home/tomcat-8.5.53/bin/bootstrap.jar:/home/tomcat-8.5.53/bin/tomcat-juli.jar Using CATALINA_PID: /home/tomcat-8.5.53/tomcat.pid Usage: catalina.sh ( commands ... ) commands: debug Start Catalina in a debugger debug -security Debug Catalina with a security manager jpda start Start Catalina under JPDA debugger run Start Catalina in the current window run -security Start in the current window with security manager start Start Catalina in a separate window start -security Start in a separate window with security manager stop Stop Catalina, waiting up to 5 seconds for the process to end stop n Stop Catalina, waiting up to n seconds for the process to end stop -force Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running stop n -force Stop Catalina, wait up to n seconds and then use kill -KILL if still running configtest Run a basic syntax check on server.xml - check exit code for result version What version of tomcat are you running? Note: Waiting for the process to end and use of the -force option require that $CATALINA_PID is defined jsvc配置daemon（守护进程） 在Windows上，tomcat会默认注册成系统服务，这样设置启动和运行都方便很多，而在Linux上，我们需要借助jsvc来实现这一效果。\n什么是jsvc Commons Daemon（共享守护进程），原名JSVC，是一个属于Apache的Commons项目的Java库。守护程序提供了一种启动和停止正在运行服务器端应用程序的Java虚拟机（JVM）的便携式方法。守护程序包括两部分：用C编写的操作系统接口的原生库 ，以及提供用Java编写的Daemon API的库。\n**有两种使用Commons守护程序的方法：直接调用实现守护程序接口（interface）或调用为守护程序提供所需方法（method）的类（class）。**例如，Tomcat-4.1.x使用守护程序接口，而Tomcat-5.0.x提供了一个类，该类的方法直接由JSVC调用。\njsvc工作原理 jsvc使用了三个进程来工作：一个启动进程、一个控制进程、一个被控制进程。其中被控制进程一般来说就是java主线程（我们这里就是tomcat），如果JVM虚拟机崩溃了，那么控制进程会在下一分钟重启。因为jsvc是守护进程，所以它应该使用root用户来启动，同时我们可以使用-user参数来进行用户的降级（downgrade），即先使用root用户来创建进程，然后再降级到指定的非root用户而不丢失root用户的特殊权限，如监听1024以下的端口。\njsvc配置tomcat守护进程（daemon） tomcat的二进制安装包中的bin目录下就有jsvc的安装包，我们需要使用GCC编译器对其进行编译安装。同时在编译的时候我们需要指定jdk的路径，由于我们前面已经手动指定了，这里不需要再指定。如果没有，可以使用./configure --with-java=$JAVA_HOME来进行操作。\n# 首先我们进入tomcat的bin目录进行编译 cd $CATALINA_HOME/bin tar xvfz commons-daemon-native.tar.gz cd commons-daemon-1.2.2-native-src/unix ./configure make # 编译完成后，会在当前文件夹生成一个jsvc的文件，将它拷贝到tomcat的/bin/目录下 cp jsvc ../.. cd ../.. # 接着我们可以这样查看jsvc的帮助文档 ./jsvc -help 使用jsvc来启动tomcat，我们使用下面的参数来进行启动\n./jsvc \\ -user tomcat \\ -classpath $CATALINA_HOME/bin/bootstrap.jar:$CATALINA_HOME/bin/tomcat-juli.jar \\ -outfile $CATALINA_BASE/logs/catalina.out \\ -errfile $CATALINA_BASE/logs/catalina.err \\ -Dcatalina.home=$CATALINA_HOME \\ -Dcatalina.base=$CATALINA_BASE \\ -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \\ -Djava.util.logging.config.file=$CATALINA_BASE/conf/logging.properties \\ org.apache.catalina.startup.Bootstrap 注意看这时的用户和PID，上面的12839的用户为root，也就是我们前面说的控制进程，后面被12839进程控制的12840进程才是我们主要运行的tomcat进程，而这里的用户也符合我们使用-user参数指定的tomcat用户。如果我们不指定进程的PID文件位置，那么默认就会在/var/run目录下生成PID文件，我们可以看到这个jsvc.pid对应的正好是jsvc运行的三个进程中的被控制进程。\n如果需要关闭，我们可以使用下面的命令：\n./jsvc -stop org.apache.catalina.startup.Bootstrap stop # 还可以指定pid文件位置，如果前面没有使用默认的pid文件目录的话 ./jsvc -stop -pidfile /var/run/jsvc.pid org.apache.catalina.startup.Bootstrap stop 这个时候可能就会有同学发现，前面不是说jsvc主要有三个进程来工作的吗，怎么这里只有两个进程呢？\n我们在上面的启动命令的选项里面加入一个-wait 10的参数，然后启动之后迅速查看一下进程。\n一般情况下，启动进程在启动了控制进程之后就会结束，而当我们使用了-wait参数之后，启动进程会等待被控制进程启动好了之后向其发送一个\u0026quot;I am ready\u0026quot;信号，启动进程在收到信号之后就会结束。-wait 10表示等待时间为10秒，需要注意等待时间要是10的倍数。\n这时候可以看到存在三个jsvc相关的进程，等tomcat启动完之后再查看的时候我们就会发现最上面的19347号进程，也就是jsvc启动进程消失了。并且控制进程19350的父进程变成了1号进程。\n我们再进一步查看以下进程的关系：\n接着我们再来查看一下1号进程。可以发现，在centos7中的1号进程是systemd。\n接着我们可以总结以上的整个过程为下列步骤：\n系统启动，0号进程启动，0号通过fork()生成1号进程systemd； 1号进程systemd通过fork()创建进程sshd，这就是我们使用的ssh服务的进程； 用户使用ssh远程登录系统，sshd进程创建了对应的终端进程pts； 用户在终端输入指令，pts根据系统中指定的该用户使用的shell（此处为bash shell）来执行对应的操作，这里具体表现为根据我们输入的指令来创建jsvc的启动进程； jsvc启动进程创建jsvc控制进程，并根据启动参数决定是否在等待jsvc控制进程的\u0026quot;I am ready\u0026quot;信号再结束，同时jsvc启动进程在结束之前会把jsvc控制进程交给1号进程systemd来管理控制； jsvc控制进程创建jsvc被控制进程，也就是我们的主要进程tomcat，同时jsvc控制进程会监视jsvc被控制进程，如果它崩溃了，jsvc控制进程则会重启，确保其正常运行； 这里使用jsvc来启动tomcat的好处就是启动完成了之后即使我们的shell终端关闭了也不会影响它的运行，当然如果我们直接使用tomcat的bin目录下的启动脚本来进行启动然后再送入后台运行也是可以达到这样的效果。实际上我们还可以通过编写systemd的unit单元配置文件，将tomcat注册成系统服务。\ndaemon.sh 同样的，在tomcat的bin目录下，集成了一个daemon.sh的脚本，用来调用jsvc从而实现tomcat的守护进程。daemon.sh的实现原理还是jsvc，只不过在脚本中加入了大量的变量判断和环境配置文件读取等操作\n在官网上会建议我们直接把daemon.sh脚本复制到 /etc/init.d 目录下，就可以实现开机自动启动了。不过在CentOS7等使用了systemd的系统上，我个人更推荐使用systemd来管理。\nsystemd配置 这里先放上archwiki和fedoraproject官网上面的链接作为参考资料：\nhttps://wiki.archlinux.org/index.php/Systemd\nhttps://docs.fedoraproject.org/en-US/quick-docs/understanding-and-administering-systemd/index.html\nsystemd简介 systemd 是 Linux 下一个与 SysV 和 LSB 初始化脚本兼容的系统和服务管理器，是 Linux 系统中最新的初始化系统（init），它主要的设计目标是克服 sysvinit 固有的缺点，提高系统的启动速度。systemd 和 ubuntu 的 upstart 是竞争对手，不过现在ubuntu也使用了systemd。\nsystemd 使用 socket 和 D-Bus 来开启服务，提供基于守护进程（daemon）的按需启动策略，保留了 Linux cgroups 的进程追踪功能，支持快照和系统状态恢复，维护挂载和自挂载点，实现了各服务间基于从属关系的一个更为精细的逻辑控制，拥有前卫的并行性能。systemd 无需经过任何修改便可以替代 sysvinit 。\n**systemd 开启和监督整个系统是基于 unit 的概念。**unit 是由一个与配置文件对应的名字和类型组成的(例如：avahi.service unit 有一个具有相同名字的配置文件，是守护进程 Avahi 的一个封装单元)。一个unit单元配置文件可以描述的内容有：系统服务（.service）、挂载点（.mount）、sockets（.sockets） 、系统设备（.device）、交换分区（.swap）、文件路径（.path）、启动目标（.target）、由 systemd 管理的计时器（.timer）。\nservice ：守护进程的启动、停止、重启和重载是此类 unit 中最为明显的几个类型。 socket ：此类 unit 封装系统和互联网中的一个 socket 。当下，systemd 支持流式、数据报和连续包的 AF_INET、AF_INET6、AF_UNIX socket 。也支持传统的 FIFO（先进先出） 传输模式。每一个 socket unit 都有一个相应的服务 unit 。相应的服务在第一个连接（connection）进入 socket 或 FIFO 时就会启动(例如：nscd.socket 在有新连接后便启动 nscd.service)。 device ：此类 unit 封装一个存在于 Linux 设备树中的设备。每一个使用 udev 规则标记的设备都将会在 systemd 中作为一个设备 unit 出现。udev 的属性设置可以作为配置设备 unit 依赖关系的配置源。 mount ：此类 unit 封装系统结构层次中的一个挂载点。 automount ：此类 unit 封装系统结构层次中的一个自挂载点。每一个自挂载 unit 对应一个已挂载的挂载 unit (需要在自挂载目录可以存取的情况下尽早挂载)。 target ：此类 unit 为其他 unit 进行逻辑分组。它们本身实际上并不做什么，只是引用其他 unit 而已。这样便可以对 unit 做一个统一的控制。(例如：multi-user.target 相当于在传统使用 SysV 的系统中运行级别5，即GUI图形化界面)；bluetooth.target 只有在蓝牙适配器可用的情况下才调用与蓝牙相关的服务，如：bluetooth 守护进程、obex 守护进程等） snapshot ：与 target unit 相似，快照本身不做什么，唯一的目的就是引用其他 unit 。 systemd的unit文件可以从多个地方加载，使用systemctl show --property=UnitPath 可以按优先级从低到高显示加载目录。\n主要的unit文件在下面的两个目录中：\n/usr/lib/systemd/system/ ：软件包安装的单元 /etc/systemd/system/ ：系统管理员安装的单元 systemd原理 这里我们重点分析一下systemd的并行操作性能以及service服务的配置单元。\n和前任的sysvinit的完全串行相比，systemd为了加速整个系统启动，实现了几乎所有的进程都并行启动（包括需要上下进程依赖的进程也并行启动）。想要实现这一点，主要需要解决三个方面的依赖问题：socket、D-Bus和文件系统。\nsocket 依赖(inetd) 绝大多数的服务依赖是套接字依赖。比如服务 A 通过一个套接字端口 S1 提供自己的服务，其他的服务如果需要服务 A，则需要连接 S1。因此如果服务 A 尚未启动，S1 就不存在，其他的服务就会得到启动错误。\n所以传统地，人们需要先启动服务 A，等待它进入就绪状态，再启动其他需要它的服务。\nsystemd 认为，只要我们预先把套接字端口S1建立好，那么其他所有的服务就可以同时启动而无需等待服务 A来创建套接字端口S1了。如果服务 A 尚未启动，那么其他进程向套接字端口S1发送的服务请求实际上会被 Linux 操作系统缓存，其他进程会在这个请求的地方等待（这里使用FIFO方式）。一旦服务A启动就绪，就可以立即处理缓存的请求，一切都开始正常运行。\n那么服务如何使用由 init 进程创建的套接字呢？\nLinux 操作系统有一个特性，当进程调用fork或者exec创建子进程之后，所有在父进程中被打开的文件句柄 (file descriptor) 都被子进程所继承。套接字也是一种文件句柄，进程A可以创建一个套接字，此后当进程 A调用 exec 启动一个新的子进程时，只要确保该套接字的close_on_exec标志位被清空，那么新的子进程就可以继承这个套接字。子进程看到的套接字和父进程创建的套接字是同一个系统套接字，就仿佛这个套接字是子进程自己创建的一样，没有任何区别。\n这个特性以前被一个叫做inetd的系统服务所利用。Inetd进程会负责监控一些常用套接字端口，比如 ssh，当该端口有连接请求时，inetd才启动telnetd进程，并把有连接的套接字传递给新的telnetd进程进行处理。这样，当系统没有 ssh 客户端连接时，就不需要启动 sshd 进程。Inetd 可以代理很多的网络服务，这样就可以节约很多的系统负载和内存资源，只有当有真正的连接请求时才启动相应服务，并把套接字传递给相应的服务进程。\n和 inetd 类似，systemd(1号进程)是所有其他进程的父进程，它可以先建立所有需要的套接字，然后在调用 exec 的时候将该套接字传递给新的服务进程，而新进程直接使用该套接字进行服务即可。\nD-Bus 依赖(bus activation) **D-Bus 是 desktop-bus 的简称，是一个低延迟、低开销、高可用性的进程间通信机制。**它越来越多地用于应用程序之间通信，也用于应用程序和操作系统内核之间的通信。很多现代的服务进程都使用D-Bus 取代套接字作为进程间通信机制，对外提供服务。\nLinux的 NetworkManager 服务就使用 D-Bus 和其他的应用程序或者服务进行交互：Linux上常见的邮件客户端软件 evolution 可以通过 D-Bus 从 NetworkManager 服务获取网络状态的改变，以便做出相应的处理。\nD-Bus 支持所谓\u0026quot;bus activation\u0026quot;功能。如果服务 A 需要使用服务 B 的 D-Bus 服务，而服务 B 并没有运行，则 D-Bus 可以在服务 A 请求服务 B 的 D-Bus 时自动启动服务 B。而服务 A 发出的请求会被 D-Bus 缓存，服务 A 会等待服务 B 启动就绪。利用这个特性，依赖 D-Bus 的服务就可以实现并行启动。\n文件系统依赖(automounter) 系统启动过程中，文件系统相关的活动是最耗时的，比如挂载文件系统，对文件系统进行磁盘检查（fsck），磁盘配额检查等都是非常耗时的操作。在等待这些工作完成的同时，系统处于空闲状态。那些想使用文件系统的服务似乎必须等待文件系统初始化完成才可以启动。但是 systemd 发现这种依赖也是可以避免的。\nsystemd 参考了 autofs 的设计思路，使得依赖文件系统的服务和文件系统本身初始化两者可以并行工作。autofs 可以监测到某个文件系统挂载点真正被访问到的时候才触发挂载操作，这是通过内核 automounter 模块的支持而实现的。systemd 集成了autofs的实现，对于系统中的挂载点，比如/home，当系统启动的时候，systemd 为其创建一个临时的自动挂载点。在这个时刻/home 真正的挂载设备尚未启动好，真正的挂载操作还没有执行，文件系统检测也还没有完成。可是那些依赖该目录的进程已经可以并发启动，他们的 open()操作被内建在 systemd 中的 autofs 捕获，将该 open()调用挂起（可中断睡眠状态）。然后等待真正的挂载操作完成，文件系统检测也完成后，systemd 将该自动挂载点替换为真正的挂载点，并让 open()调用返回。由此，实现了那些依赖于文件系统的服务和文件系统本身同时并发启动。\n对于/根目录的依赖实际上一定还是要串行执行，因为 systemd 自己也存放在/根目录之下，必须等待系统根目录挂载检查好。\n不过对于类似/home等挂载点，这种并发可以提高系统的启动速度，尤其是当/home是远程的 NFS 节点，或者是加密盘等，需要耗费较长的时间才可以准备就绪的情况下，因为并发启动，这段时间内，系统并不是完全无事可做，而是可以利用这段空余时间做更多的启动进程的事情，总的来说就缩短了系统启动时间。\n总结 从上面的三个办法我们可以看出，systemd让多个程序并行启动的解决思路就是先创建一个虚拟点，让各类需要依赖的服务先运行起来，最后再把虚拟点换成实际的服务使得能够正常运行。\nsystemd实现tomcat的daemon进程 我们在/usr/lib/systemd/system/目录下新建一个tomcat9.service文件，接下来我们可以使用systemctl命令来进行控制：\n使用 systemctl 控制单元时，通常需要使用unit文件的全名，包括扩展名（例如 sshd.service ）。但是有些unit可以在 systemctl 中使用简写方式。\n如果无扩展名，systemctl 默认把扩展名当作 .service 。例如 tomcat 和 tomcat.service 是等价的。\n挂载点会自动转化为相应的 .mount 单元。例如 /home 等价于 home.mount 。\n设备会自动转化为相应的 .device 单元，所以 /dev/sda1 等价于 dev-sda1.device 。\n使用daemon.sh 首先我们尝试在systemd中使用自带的脚本进行启动和关闭tomcat，这里我们先把startup.sh和shutdown.sh两个脚本给排除掉，虽然它们无法启动守护进程的缺陷可以使用systemd来进行弥补，但是还是无法使用jsvc，无法在特权端口和运行用户之间取得两全，我们直接使用daemon.sh来运行。\n需要注意的是，systemd并不会去读取我们先前在/etc/profile中设定的变量，因此我们直接把变量写进unit配置文件中。\n[Unit] Description=Apache Tomcat 9 [Service] User=tomcat Group=tomcat PIDFile=/var/run/tomcat.pid Environment=JAVA_HOME=/home/jdk8/ Environment=JRE_HOME=/home/jdk8/jre Environment=CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib Environment=CATALINA_HOME=/home/tomcat9 Environment=CATALINA_BASE=/home/tomcat9 Environment=CATALINA_TMPDIR=/home/tomcat9/temp ExecStart=/home/tomcat9/bin/daemon.sh start ExecStop=/home/tomcat9/bin/daemon.sh stop [Install] WantedBy=multi-user.target 添加了新的unit单元之后我们先systemctl daemon-reload重启一下daemon进程，再使用systemctl start tomcat9.service来启动服务，接着查看状态，发现无法正常运行，一启动进程就failed掉了，查看daemon脚本默认的日志文件（位于tomcat目录下的logs/catalina-daemon.out）我们发现返回了143错误。\nService exit with a return value of 143 网上搜索了一下，有个解决方案是把daemon.sh脚本中的wait参数时间从10调成240，在125行左右的位置：\n# Set the default service-start wait time if necessary test \u0026#34;.$SERVICE_START_WAIT_TIME\u0026#34; = . \u0026amp;\u0026amp; SERVICE_START_WAIT_TIME=10 wait参数调大之后，等待启动成功之后（这里用的主机配置很低，启动比较耗时）就可以正常访问了\n但是在四分钟（240s）之后我们再查看tomcat9.service就会发现，进程已经结束了，再次访问默认的8080端口也无法访问，查找进程也没有找到相关的进程。\n试图分析一波\n我们来根据上面的情况结合原理来试图分析一下：\n首先我们可以看到-wait参数时长调到240之后，bash shell进程的生命周期延长了，根据之前的jsvc工作原理部分我们可以知道-wait参数会影响jsvc的启动进程的生命周期，而从systemd输出的信息来看，有包括jsvc三个进程和bash shell进程在内共计四个进程，这和之前我们直接运行daemon.sh之后最终只有jsvc的两个进程（控制进程和被控制进程不同），且Main PID参数指向的是bash shell进程。\n于是乎我们大胆猜测一下：使用daemon.sh start命令启动tomcat，systemd会把启动daemon.sh的bash的PID作为整个service的PID来监控，而这个bash进程在启动了jsvc之后是会自行退出的，这也就导致了systemd认为service已经运行失败，从而清理掉了关联的进程，进而使得jsvc相关的tomcat进程也被清理掉了。而-wait参数时长调到240之后，bash shell进程的存活时间变长，我们就能在tomcat启动完成之后且bash shell进程结束之前访问到tomcat服务器。\n考虑到这种情况，我们可以试一下使用daemon.sh run来启动tomcat，因为在终端中使用run参数的时候会一直把log信息输出到终端，我猜测这个运行方式是和start不太一样的。\n把systemd的unit文件的启动参数改为run，同时将-wait参数时长调回默认的10，再次启动服务。\n这次我们可以看到systemd的Main PID对应为jsvc的主进程，tomcat服务也能一直正常的在后台运行。应该算是成功的使用systemd来管理jsvc启动的tomcat进程了。\n那么这两者的区别在哪里呢？接着我们打开daemon.sh这个脚本来查看一下两者的不同：\n从图中我们可以看到两者最大的不同就是使用run命令的时候是exec调用jsvc来启动tomcat并且使用了-nodetach参数。\nshell中的exec命令和直接调用不同，命令exec将并不启动新的shell，而是用要被执行命令替换当前的shell进程，并且将老进程的环境清理掉，而且exec命令后的其它命令将不再执行。\n也就是说，run命令使用exec调用了jsvc，是直接替代原来启动daemon.sh的bash shell进程，并且在这个exec命令执行完之后才会执行后面的exit命令。这样就可以让systemd的Main PID从bash shell进程顺理成章地变为jsvc的启动进程。\n那么我们知道，jsvc的启动进程在启动完jsvc控制进程之后还是会退出的，这个时候systemd还是会监听失败。而-nodetach参数的作用就是不脱离父进程而成为守护进程（ don\u0026rsquo;t detach from parent process and become a daemon），这样就能顺利地使得jsvc控制进程从它的父进程jsvc启动进程那里“得到”systemd的Main PID的位置，成为该service的主要进程。\n我们直接在终端中运行jsvc并加上-nodetach参数，可以看到即使是运行成功了之后也不会退出（控制进程继承了启动进程成为守护进程一直运行），而没加的情况下则是jsvc启动进程退出后就会退出。\n这里再放上systemd使用daemon.sh启动tomcat的整个unit文件的配置及注释：\n[Unit] Description=Apache Tomcat 9 # 对整个serive的描述，相当于备注，会出现在systemd的log中 After=network.target # 在network服务启动之后再启动 [Service] User=tomcat Group=tomcat # 运行该service的用户及用户组 PIDFile=/var/run/tomcat.pid # 该service的PID文件 Environment=JAVA_HOME=/home/jdk8/ Environment=JRE_HOME=/home/jdk8/jre Environment=CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib Environment=CATALINA_HOME=/home/tomcat9 Environment=CATALINA_BASE=/home/tomcat9 Environment=CATALINA_TMPDIR=/home/tomcat9/temp # 定义了运行时需要的变量 ExecStart=/home/tomcat9/bin/daemon.sh start ExecStop=/home/tomcat9/bin/daemon.sh stop # 对应systemd控制的start和stop命令 [Install] WantedBy=multi-user.target # 运行级别为第三级（带有网络的多用户模式） 直接使用jsvc 既然搞清楚了运行原理，我们也就可以跳过脚本直接在unit文件中定义各种参数：\n[Unit] Description=Apache Tomcat 9 After=network.target [Service] User=root Group=root # 这里使用root用户启动方便jsvc监听特权端口 # 后面可以在jsvc参数中使用-user降权到tomcat用户 PIDFile=/var/run/tomcat.pid Environment=JAVA_HOME=/home/jdk8/ Environment=JRE_HOME=/home/jdk8/jre Environment=CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib Environment=CATALINA_HOME=/home/tomcat9 Environment=CATALINA_BASE=/home/tomcat9 Environment=CATALINA_TMPDIR=/home/tomcat9/temp ExecStart=/home/tomcat9/bin/jsvc \\ -user tomcat \\ -nodetach \\ -java-home ${JAVA_HOME} \\ -pidfile ${CATALINA_BASE}/tomcat.pid \\ -classpath ${CATALINA_HOME}/bin/bootstrap.jar:${CATALINA_HOME}/bin/tomcat-juli.jar \\ -outfile ${CATALINA_BASE}/logs/catalina.out \\ -errfile ${CATALINA_BASE}/logs/catalina.err \\ -Dcatalina.home=${CATALINA_HOME} \\ -Dcatalina.base=${CATALINA_BASE} \\ -Djava.io.tmpdir=${CATALINA_TMPDIR} \\ -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \\ -Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \\ org.apache.catalina.startup.Bootstrap ExecStop=/home/tomcat9/bin/jsvc \\ -stop \\ -classpath ${CLASSPATH} \\ -Dcatalina.base=${CATALINA_BASE} \\ -Dcatalina.home=${CATALINA_HOME} \\ -pidfile ${CATALINA_BASE}/tomcat.pid \\ -Djava.io.tmpdir=${CATALINA_TMPDIR} \\ -Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \\ -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \\ org.apache.catalina.startup.Bootstrap [Install] WantedBy=multi-user.target 注意：ExecStart和ExecStop两个命令中的执行文件路径需要使用绝对路径\n","permalink":"http://localhost:9011/20200327-tomcat-01-brief-introduction-installation/","summary":"\u003cp\u003e本文主要包括tomcat服务器的主要概念介绍、在systemd上的tomcat守护进程的配置、jsvc的原理介绍和systemd的并发实现原理介绍。\u003c/p\u003e","title":"Tomcat篇01-概念简介和守护进程配置"},{"content":"nginx中http模块中的sendfile指令及其原理。\nsendfile()介绍 nginx的http模块中有一个sendfile指令，默认是开启状态，官网的文档对其解释是：\nEnables or disables the use of sendfile().\nStarting from nginx 0.8.12 and FreeBSD 5.2.1, aio can be used to pre-load data for sendfile():\nlocation /video/ { sendfile on; tcp_nopush on; aio on; } In this configuration, sendfile() is called with the SF_NODISKIO flag which causes it not to block on disk I/O, but, instead, report back that the data are not in memory. nginx then initiates an asynchronous data load by reading one byte. On the first read, the FreeBSD kernel loads the first 128K bytes of a file into memory, although next reads will only load data in 16K chunks. This can be changed using the read_ahead directive.\n简单来说就是启用sendfile()系统调用来替换read()和write()调用，减少系统上下文切换从而提高性能，当 nginx 是静态文件服务器时，能极大提高nginx的性能表现，而当 nginx 是反向代理服务器时，则没什么用了。下面我们来分析一下这个sendfile的工作原理：\n原理分析 首先我们需要知道sendfile()和read()、write()之间最大的区别就是前者是属于系统调用，而后者是属于函数调用，我们来看下面这幅图\n我们不难看出，nginx是属于Applicaiton的，而read()、write()属于函数调用，也就是在Lib Func这一层，sendfile()系统调用，位于System Call这一层，而想要对硬盘进行操作，是kernel才有的权限，上面的那些层都需要往下调用。\n作为对比我们先来看一下正常情况下如果nginx调用read()和write()函数的操作过程：\n我们都知道数据是存储在硬盘上面的，当数据被调用的时候会被加载进内存再被层层递进最后被CPU使用，这里个这个过程我们进行简化。\n步骤一：首先nginx调用read函数，这时data从harddisk从被加载进Kernel Buffer（Hard Disk）中，此时是从一开始的用户态（user mode）陷入内核态（kernel mode）才能完成操作 步骤二：接着由于data需要被write()函数进行操作，所以data从Kernel Buffer（Hard Disk）传输到User Buffer中，此时从内核态（kernel mode）切换回用户态（user mode） 步骤三：再接着data被write()函数从user buffer写入到Kernel Buffer（Socket Engine），此时从用户态（user mode）陷入内核态（kernel mode） 步骤四：data从Kernel Buffer（Socket Engine）传输到Socket Engine，此时从内核态（kernel mode）切换回用户态（user mode） 这里需要说明两点，一是用户态和内核态之间的切换是需要执行上下文切换操作的，这是十分耗费系统资源和时间的操作，二是因为read()、write()属于函数调用，它们是没有权限在kernel中操作，无法将data直接从Kernel Buffer（Hard Disk）传输到Kernel Buffer（Socket Engine）。\n那么使用sendfile()呢？，由于是系统调用，所以在步骤二和步骤三的时候就可以不需要再将数据传输到User Buffer，直接在kernel中进行操作，省去了两次状态切换，也就是省去了两次的上下文切换，从而大幅度提升了性能。\n我们来看一下下面的这幅图（灵魂画手上线→_→）\n最后我们再来解释一下，为什么当 nginx 是反向代理服务器时，sendfile()就没什么用了呢。\n顾名思义，sendfile()的作用是发送文件，也就是接收数据的一段是文件句柄，发送数据的那一端是socket。而在做反向代理服务器的时候，两端都是socket，此时无法使用sendfile()，也就不存在性能提升这一说了。\n","permalink":"http://localhost:9011/20200323-nginx-06-sendfile/","summary":"\u003cp\u003enginx中http模块中的sendfile指令及其原理。\u003c/p\u003e","title":"Nginx篇06-Sendfile指令及其原理"},{"content":"nginx中http模块使用http长连接的相关配置（主要是keepalive指令）和http长连接的原理解释。\nhttp长连接 预备知识 连接管理是一个 HTTP 的关键话题：打开和保持连接在很大程度上影响着网站和 Web 应用程序的性能。在 HTTP/1.x 里有多种模型：短连接, 长连接, 和 HTTP 流水线。在解释这三种模型之前，我们需要先明确一些前提知识：\nHTTP是属于应用层（七层）的协议，同时它的传输层（四层）使用的是TCP协议，那么也就是说，HTTP的长连接和短连接，其本质就是TCP的长连接和短连接； HTTP是一个无状态的面向连接的协议（使用TCP，面向连接、可靠传输），指的是协议对于事务处理没有记忆能力，服务器不知道客户端是什么状态，无状态不代表HTTP不能保持TCP连接，更不能代表HTTP使用的是UDP协议（无连接）； TCP建立连接和断开连接是需要三握四挥的，由于这个属于计算机网络基本知识，所以原理这里不再赘述； 接下来我们开始解释。\nHTTP短连接模型 在早期，HTTP 使用一个简单的模型来处理这样的连接。这些连接的生命周期是短暂的：每发起一个请求时都会创建一个新的连接，并在收到应答时立即关闭。这就是类似上面说的三次握手，在互联网发展的早期一个网页的资源并没有现在这么多，很多可能只是一个简单的静态页面而已，所以这样的模型显然很OK。客户端获取完所需资源之后，就断开连接，不再占用服务器的资源。\n套用TCP连接的三握四挥的模型来举例：\n三次握手：\nA→B：今晚下班一起吃饭吗？\nB→A：好的，今晚下班一起吃饭。\nA→B：好的，我知道你答应我今晚下班一起吃饭的邀请了。\n然后开始去吃饭，吃完饭到了两个人需要各自回家的时候：\n四次挥手：\nA→B：我吃完饭准备走了\nB→A：等一下，我快吃完了\nB→A：好了，我吃完了可以走了\nA→B：好的，我知道你吃完了我们可以走了\n然后两人吃完饭就各回各家了\nHTTP 短连接模型是最早期的模型，也是 HTTP/1.0 的默认模型。每一个 HTTP 请求都由它自己独立的连接完成；这意味着发起每一个 HTTP 请求之前都会有一次 TCP 握手，而且是连续不断的。实际上，TCP 协议握手本身就是耗费时间的，所以 TCP 可以保持更多的热连接来适应负载。短连接破坏了 TCP 具备的能力，新的冷连接降低了其性能。\n在 HTTP/1.0 中如果没有指定 Connection协议头，或者是值被设置为 close就会启用短连接模型，要在 HTTP/1.0 中启用长连接模型，需要在协议头中指定Connection: Keep-Alive ，不过并不建议这样操作。\n而在 HTTP/1.1 中，默认使用长连接模型，只有当 Connection被设置为 close 时才会用到这个短连接模型，协议头都不用再去声明它(但是一般还是会把它加上，以防万一因为某种原因要退回到 HTTP/1.0 )。\nHTTP长连接模型 后来，网页需要请求的资源越来越多，短连接模型显然已经十分吃力了。因为短连接有两个比较大的问题：创建新连接耗费的时间尤为明显（三次握手很耗费时间），另外 TCP 连接的性能只有在该连接被使用一段时间后(热连接)才能得到改善。因此在HTTP/1.1中引入了长连接模型和流水线模型。\n在 HTTP/1.1 之前，长连接也被称为keep-alive 连接。\n一个长连接会保持一段时间，重复用于发送一系列请求，节省了新建 TCP 连接握手的时间，还可以利用 TCP 的性能增强能力。当然这个连接也不会一直保留着：连接在空闲一段时间后会被关闭(服务器可以使用 Keep-Alive 协议头来指定一个最小的连接保持时间)。\n套用上面的例子来进一步解释：\n三次握手：\nA→B：今晚下班一起吃饭吗？\nB→A：好的，今晚下班一起吃饭。\nA→B：好的，我知道你答应我今晚下班一起吃饭的邀请了。\n然后开始去吃饭，但是这时吃完饭就不是马上四次挥手断开连接，AB两人还顺便去逛街、看电影（相当于省去了三次握手使用已建立的连接来传输多个资源）\n此处省略四次挥手\n最后两人就各回各家了\n长连接也还是有缺点的；也就是前面提到的资源占用问题，就算是在空闲状态，它还是会消耗服务器资源，也更容易被DDoS攻击。本质上长连接是因为不断地三次握手建立连接消耗的资源要大于维持连接所需要的资源才使用的，如果服务器处于高负载时段或者被DDoS，可以使用非长连接，即尽快关闭那些空闲的连接，也能对性能有所提升。\nHTTP流水线模型 流水线模型的实现要复杂很多，而已效果也并不是特别好，主要还要考虑到各种兼容性，所以默认是不启用这个流水线模型的，而在HTTP/2中，流水线已经被更好的算法给代替，如multiplexing。\n默认情况下，HTTP 请求是按顺序发出的。**下一个请求只有在当前请求收到应答过后才会被发出。**由于会受到网络延迟和带宽的限制，在下一个请求被发送到服务器之前，可能需要等待很长时间。**流水线是在同一条长连接上发出连续的请求，而不用等待应答返回。这样可以避免连接延迟。**理论上讲，性能还会因为两个 HTTP 请求有可能被打包到一个 TCP 消息包中而得到提升。就算 HTTP 请求不断的继续，尺寸会增加，但设置 TCP 的 MSS(Maximum Segment Size) 选项，仍然足够包含一系列简单的请求。\n并不是所有类型的 HTTP 请求都能用到流水线：只有 idempotent方式，比如 GET、HEAD、PUT和 DELETE能够被安全的重试：因为有故障发生时，流水线的内容要能被轻易的重试，即出现了问题重试的成本要尽可能低，否则还不如使用长连接模型。\n正确的实现流水线是复杂的：传输中的资源大小，多少有效的 RTT 会被用到，还有有效带宽，流水线带来的改善有多大的影响范围。不知道这些的话，重要的消息可能被延迟到不重要的消息后面。这个重要性的概念甚至会演变为影响到页面布局！因此 HTTP 流水线在大多数情况下带来的改善并不明显。此外，流水线受制于 HOL 问题。\n摘自wiki\n队头阻塞（Head-of-line blocking或缩写为HOL blocking）在计算机网络的范畴中是一种性能受限的现象。它的原因是一列的第一个数据包（队头）受阻而导致整列数据包受阻。例如它有可能在缓存式输入的交换机中出现，有可能因为传输顺序错乱而出现，亦有可能在HTTP流水线中有多个请求的情况下出现。\n我们还是使用上面的例子来进行解释，这次的握手请求就变了，A一次向B发出了三个请求：\n三次握手：\nA→B：今晚下班一起吃饭、逛街、看电影吗？\nB→A：好的，今晚下班一起吃饭、逛街、看电影。\nA→B：好的，我知道你答应我今晚下班一起吃饭、逛街、看电影的邀请了。\n实际上这样子是有很大的风险的\n如果是按照长连接模型，A可以根据B在吃饭的时候的反应来决定要不要继续去逛街看电影，也就是如果传输完了一次数据之后还保持连接就继续传输，万一连接突然断开或者是不稳定，那可能就要重新建立连接。（万一B在吃饭的时候吃的不开心不想继续逛街看电影那就等下次再吃饭逛街看电影）\n但是如果按照流水线模型，A一次发送三个请求，虽然发送请求的时候省事儿了（三次握手的时候TCP打包传输请求更省事），但是谁也不知道吃饭逛街看电影的过程中会发生什么意外，时间越长越不稳定，而且还容易出现万一B想减肥不想吃饭，只想逛街看电影的情况呢？（HOL问题）\n最后这里补充一张图片来对比三种模型之间的差别：\nNginx中的keepalive指令 当我们配置Nginx作为代理服务器的时候，想要支持HTTP长连接，需要client到Nginx和Nginx到server都是长连接，因为此时Nginx既是client的server也是server的client。\n了解了上面的原理之后，Nginx中的keepalive指令我们就非常好理解了，相关的指令主要有三个，我们逐个进行解释：\nkeepalive Syntax:\tkeepalive connections; Default:\t— Context:\tupstream This directive appeared in version 1.1.4. 在upstream模块中配置，启用连接到upstream中的服务器的缓存，connections参数的主要作用是设定每个Nginx的**单个worker进程（each worker process）**对于upstream中的server的最大空闲连接数，当超过该数字的时候，会关闭使用得最少的连接。\n对于HTTP，应将proxy_http_version指令设置为“ 1.1”，并且应清除Connection标题字段\n对于FastCGI服务器，需要设置fastcgi_keep_conn以启用keepalive连接\nupstream http_backend { server 127.0.0.1:8080; keepalive 16; } server { location /http/ { proxy_pass http://http_backend; proxy_http_version 1.1; proxy_set_header Connection \u0026#34;\u0026#34;; } lcaotion /FastCGI/ { fastcgi_pass fastcgi_backend; fastcgi_keep_conn on; } } 需要注意的是，keepalive指令并不会限制Nginx的所有worker进程能开启的连接到upstream服务器中的连接总数（total number）。也就是如果设得太大了，会导致过多的空闲连接占满了upstream中的server资源，导致新的连接无法建立，因此这个数值的设定需要根据worker进程数量来调整。\nkeepalive_requests Syntax:\tkeepalive_requests number; Default:\tkeepalive_requests 100; Context:\tupstream This directive appeared in version 1.15.3. keepalive_requests设定可以通过一个连接（connection）发送的请求（request）数量，超过最大请求数量之后，该连接会被关闭。为了释放每个连接的内存分配，定期关闭连接是很有必要的。因此，不建议将keepalive_requests设定过大，否则可能会导致过高的内存占用。\nkeepalive_timeout Syntax:\tkeepalive_timeout timeout; Default:\tkeepalive_timeout 60s; Context:\tupstream This directive appeared in version 1.15.3. 设定连接超时时间，在此设定的时间内，client与upstream中的server的空闲keepalive连接将保持打开状态（open）。此外，虽然官方文档说的默认值是60s，但是1.17.9版本的Nginx在安装之后配置文件nginx.conf上面设定的是65s。\n","permalink":"http://localhost:9011/20200320-nginx-05-upstream-keepalive/","summary":"\u003cp\u003enginx中http模块使用http长连接的相关配置（主要是keepalive指令）和http长连接的原理解释。\u003c/p\u003e","title":"Nginx篇05-http长连接和keeplive"},{"content":"nginx的map模块配置语法。\nmap模块是由ngx_http_map_module模块提供的，只能在http模块下使用，nginx默认自带安装。**map 的主要作用简单来说就和编程语言中的赋值语句有点像，只不过这里称为映射（map）。**具体来说是创建一个自定义变量，去匹配某些指定的字符串或者是正则表达式，如果匹配成功则将该匹配值赋值给该自定义变量，然后该自定义变量可以作其他用途。\n语法格式 我们先来看一下官网给出的语法格式：\nSyntax:\tmap string $variable { ... } Default:\t— Context:\thttp 实际上现在的版本中map指令后面的第一个参数也就是源变量可以是字符串、正则表达式或者是另一个变量。因此这样表示会更容易理解一些。\nSyntax:\tmap $source_var $result_var { ... } 此外，map模块内还有default、hostnames、include、volatile这四个关键字可以使用\ndefault：使用方法为 default value，主要用于给源变量设定默认值，如果源变量没有匹配到模块中指定的任何一个，则设为指定的value，如果不指定default，则会设定为一个空字符串（NULL） hostnames：指定源变量可以为域名，并且可以使用前缀掩码或者后缀掩码，如*.example.com或者mail.* include：使用方法为include file，指定一个带有变量的文件，可以使用多次 volatile：指明该变量不可缓存 我们来看一个复杂一点的配置文件\nmap $http_host $name { default 0; hostnames; volatile; *.example.com 1; *.example.org 2; *.example.net 3; mail.* 4; mail.example.com 5; include /var/www/html/host.list; } 需要注意这里的include使用的文件格式应该如下：\n$source_var0 $result_var0; $source_var1 $result_var1; ... $source_varN $result_varN; 例如：\nnginx.example.com 7; awesome.example.com 8; 注意事项 这里有几点需要注意：\nmap指令只能在http块中使用； 如果源变量是字符串，那么在匹配的时候是不区分大小写的； 在0.9.0版本之前只能指定单个源变量； 0.9.6版本之后的源变量可以是字符串或者正则表达式； 正则表达式中，开头“~”为大小写敏感，“~*”为大小写不敏感，正则表达式必须以这两者中的一个开头； 需要匹配的的源变量中有特殊符号的需要使用反斜杠\\来进行转义； 0.9.0版本中源变量可以使用变量，1.11.0版本中源变量可以使用变量和字符串的组合； map指令对应的结果变量（$result_var）只有在之后的配置文件中使用到了该结果变量的时候，才会使用前面定义的map模块来进行映射，因此即使定义了很多个map模块，也不会对性能有额外的影响； 匹配顺序 当map块中指定的映射关系有多个可以匹配的时候，按照以下的优先顺序进行匹配：\n完整指定没有使用掩码的指定变量，e.g.“mail.example.com”； 带有前缀掩码的最长字符串值, e.g. “*.nginx.example.com”就会比 “*.example.com”优先匹配； 带有后缀掩码的最长字符串值, e.g. “mail.nginx.*”就会比 “mail.*”优先匹配； 按照配置文件中出现的先后顺序匹配的第一个正则表达式； 设定的default值； 个人记忆口诀：先整再缺，先前再后；先长再短，先实再虚；无则默认，不行查表。\n","permalink":"http://localhost:9011/20200320-nginx-04-http-map-module/","summary":"\u003cp\u003enginx的map模块配置语法。\u003c/p\u003e","title":"Nginx篇04-map模块"},{"content":"nginx的负载均衡配置，包括http、tcp和udp负载均衡，以及Round robin、Least connections、Least time（Nginx Plus专属）、Generic hash、Random、IP hash（HTTP模块专属）的原理分析。\nhttp负载均衡 我们先来看一小段配置文件\nupstream backend { server 10.0.0.1:80 weight=1; server nginx.example.com:80 weight=2; } server { location / { proxy_pass http://backend; } } 这是一个简单的使用upstream模块对http服务进行指定权重的负载均衡的配置文件，一般存放在nginx目录下的conf.d文件夹中。\nserver可以使用Unix socket、IP、DNS、FQDN等来进行服务器的指定，这里的Unix socket指的是POSIX操作系统中的组件，即用于进程间通信的那个Unix socket。也就是说如果做负载均衡的时候本机也作为server之一，使用scoket确实是可以有效提高速度的（对比DNS和IP等），因为都在同一个系统上，走进程间的通信比走网络通信要少了很多验证步骤和协议，通信的速度会更快。但是在实际业务中比较少使用这样的方式，一般都会直接使用IP方便定位主机和运维分析等。IP相比DNS和FQDN要少了一步域名解析的过程，理论上速度会快一些，但是DNS其实也可以做负载均衡，同时DNS和FQDN给了网络路由更多的控制权，实际怎么使用还要看具体的业务需求。\nupstream中server指令语法如下：\nserver address [parameters] 关键字server必选\naddress也必选，可以是主机名、域名、ip或unix socket，也可以指定端口号\nparameters是可选参数，可以是如下参数：\ndown：表示当前server已停用 backup：表示当前server是备用服务器，只有其它非backup后端服务器都挂掉了或者很忙才会分配到请求 weight：表示当前server负载权重，权重越大被请求几率越大，默认是1 max_fails和fail_timeout一般会关联使用，如果某台server在fail_timeout时间内出现了max_fails次连接失败，那么Nginx会认为其已经挂掉了，从而在fail_timeout时间内不再去请求它，fail_timeout默认是10s\nmax_fails默认是1，即默认情况是只要发生错误就认为服务器挂掉了\n如果将max_fails设置为0，则表示取消这项检查\ntcp负载均衡 我们来看一个stream模块的配置：\nstream { upstream mysql_read { server mysqlread1.example.com:3306 weight=5; server mysqlread2.example.com:3306; server 10.0.0.1:3306 backup; } server { listen 3306; proxy_pass mysql_read; } } 在这个配置中我们实现了一个MySQL的负载均衡和备份。我们先看整个stream模块包含了upstream模块和server模块，在upstream中指定了三个server，其中第二个server在没有指定权重weight的情况下，weight默认为1，而第三个server后面指定了其状态为backup，也就是备用服务器。一般来说nginx会同时监听运行服务器和备用服务器，以便在active服务器出现故障的时候能迅速切换到备用服务器。\n需要注意的是，stream模块的配置文件不建议放到nginx下的conf.d目录下（该目录一般用于放置http模块相关的配置文件），我们可以新建一个stream.conf.d目录用于存放stream模块的配置文件，同时需要在nginx目录下的nginx.conf文件中写入该目录，如：\nstream { include /etc/nginx/stream.conf.d/*.conf; } 然后我们在对应新建的stream.conf.d目录下面新建配置文件的时候，就不需要再添加stream{}了，这和之前的http模块对应的conf.d目录下的配置相似，同样的，我们其实也可以直接将整个stream模块配置全部都放到nginx目录下的nginx.conf文件中，只不过这样不方便整理，尤其是当需要配置的项目变多了的时候。\n实际上我们会发现tcp负载均衡使用的stream模块和http模块十分相似，这是因为nginx一开始是作为web服务器和七层负载均衡服务器，tcp和udp的负载均衡是属于四层负载均衡，这项功能是在1.9版本加入的，因此在一些配置和原理上都参考了http模块。\nUDP负载均衡 udp负载均衡和上面的两个负载均衡比较类似，在实现的原理上也参考了tcp的负载均衡。\n我们日常使用的服务中比较常见的使用UDP协议的有NTP、DNS等。\nstream { upstream ntp { server ntp1.example.com:123 weight=2; server ntp2.example.com:123; } server { listen 123 udp; proxy_pass ntp; } } udp负载均衡的配置和tcp基本一致，需要注意的就是要在监听的端口后面加上udp参数指定协议为udp协议即可。\n负载均衡策略 除了默认的轮询负载均衡算法，nginx还内置了其他的一些负载均衡策略，实际上对于HTPP、TCP和UDP三类负载均衡使用的策略默认有Round robin、Least connections、Least time（Nginx Plus专属）、Generic hash、Random、IP hash（仅HTTP可用）这六种。\n网上提到较多的url_hash和fair这两种策略属于第三方模块实现的策略。\n轮询Round robin 轮询算法是默认的负载均衡算法，根据设定的权重值来进行访问，权重值越高被访问的概率就越高，不设置权重值的话则会默认设置为1，最后的被访问比例从概率统计的角度上看等于设定的权重值比例。\nupstream backend { server backend1.example.com weight=5; server backend2.example.com weight=1; server backend3.example.com backup; server backend4.example.com down; } 具体使用到的是名为smooth weighted round-robin balancing的负载均衡算法，具体原理和测试有兴趣的可以看之前的文章，这里直接摘录之前的原理叙述部分。\nweight：配置文件中设置的权重值，是定值，在整个选择过程中是不会改变的，对应到这里就是3、5、7。 current_weight：后端服务器的当前权重值，初始值等于0，在每轮选择中，该值最大的服务器就会被选中 effective_weight：变化权重值，初始值等于weight，用于动态调整服务器被选择的概率，即当被选中的服务器出现了failure的时候，该服务器对应的effective_weight就会减小，具体操作我们下面再解释。 total_weight：总的权重值，即所有服务器的权重值相加，在这里为3+5+7=15。 接下来我们开始逐步解析算法执行过程：\n首先进行各类值的初始化，weight赋值为配置文件中的weight，current_weight赋值为0，effective_weight赋值为weight，total_weight为所有weight之和； 对于每个服务器的current_weight，加上该服务器对应的weight； 选取current_weight值最大的服务器来接受这次访问，然后该服务器对应的current_weight需要减去total_weight（因此current_weight是可以出现负值的） 不断重复步骤2和步骤3，当重复的次数等于total_weight时，所有服务器的current_weight刚好为0，此时结束一轮负载均衡。 最少连接数Least connections **在配置文件中使用least_conn来指定该策略。**前面的轮询算法是使每台服务器的连接数大致相同或者符合设定的权重比例来实现负载均衡，前提是每个访问请求所需要的处理时间都大致相同，如果每次访问需要的处理时间不一样，使用轮询算法的效果就比较一般。这时候就可以考虑使用最少连接数算法。\n顾名思义，nginx会将访问负载到访问数最少的服务器上，同时也会将设定的权重值weight纳入考虑因素。具体来说就是nginx会记录分配给后端服务器的连接数，当有访问过来的时候优先分配给连接数最少的服务器，而如果最少连接数的服务器出现了多台，则根据上面的轮询算法来进行选择。\nupstream backend { least_conn; server backend1.example.com; server backend2.example.com; } 最快响应时间Least time（Nginx Plus专属） 号称在这几种算法中最复杂的算法，在最少连接数算法的基础上增加了响应时间这一维度,因此在使用的时候需要加上header或者last_byte 。\nupstream backend { least_time header; server backend1.example.com; server backend2.example.com; } 对于指定了header参数，nginx会使用接收到响应报文的报头的时间来作为响应时间 对于指定了last_byte 参数，nginx会使用接收整个完整报文的时间来作为响应时间 普通哈希Generic hash 以用户自定义资源(比如URL、特定的文本、请求的变量或者多个的组合等)的方式计算hash值完成分配。当我们需要更好地控制请求的发送到哪个服务器上或者确定服务器最有可能有缓存数据时，此方法很有用。**注意此时的server语句中不能写入weight等其他的参数，hash_method是使用的hash算法。**当有服务器加入或者移除后端的服务器列表的时候，哈希请求会被重新分配，想要最小化该影响，可以添加关键字consistent。这个关键词会使用一种新的一致性哈希算法 ketama, 该算法会让管理员添加或删除某个服务实例的时候，只有一小部分的请求会被转发到与之前不同的服务实例上去，其他请求仍然会被转发到原有的服务实例上去。\nupstream resinserver { hash $request_uri consistent; server backend1.example.com; server backend2.example.com; hash_method crc32; } 随机Random 随机算法就是随机从后端服务器中挑选一个来接受访问，不过它还有一个附加参数two [parameters]，可以随机挑选两个服务器，然后根据指定的均衡算法从服务器中挑选一台接受访问。如果不指定two后面的parameters则默认使用Least time算法进行选择。\nupstream backend { random two ip_hash; server backend1.example.com; server backend2.example.com; } IP哈希IP hash（HTTP模块专属） IP哈希算法使用ipv4地址的前三段（比如说192.168.1.1就使用192.168.1这三段）或者是整个ipv6地址来进行哈希算法计算，从源码中我们可以看到实际使用的哈希算法比较简单，在nginx源码的\\src\\http\\modules\\ngx_http_upstream_ip_hash_module.c中大概181行的位置，具体如下：\nhash = (hash * 113 + iphp-\u0026gt;addr[i]) % 6271 这种算法的好处是可以保持服务器的session的一致性，因为同一个IP根据哈希算法的结果一般都是访问到同一台服务器（除非中途服务器崩了），需要注意的是该算法也可以使用轮询算法的参数。\nupstream backend { ip_hash; server backend1.example.com weight=5; server backend2.example.com weight=1; } ","permalink":"http://localhost:9011/20200319-nginx-03-load-balancing/","summary":"\u003cp\u003enginx的负载均衡配置，包括http、tcp和udp负载均衡，以及\u003ccode\u003eRound robin\u003c/code\u003e、\u003ccode\u003eLeast connections\u003c/code\u003e、\u003ccode\u003eLeast time\u003c/code\u003e（Nginx Plus专属）、\u003ccode\u003eGeneric hash\u003c/code\u003e、\u003ccode\u003eRandom\u003c/code\u003e、\u003ccode\u003eIP hash\u003c/code\u003e（HTTP模块专属）的原理分析。\u003c/p\u003e","title":"Nginx篇03-负载均衡简单配置和算法原理"},{"content":"nginx的upstream模块中的加权轮询负载均衡的算法原理。\n我们先来看一个简单的负载均衡：\nupstream backend { server 10.0.0.1:80;\t#服务器a server 10.0.0.2:80; #服务器b } 这是一个简单的负载均衡，将接受到的访问按照默认的均分原则分配给后台的两台服务器，这时候两台server的访问量应该是1：1。如果我们在后面加上权重值，例如\nupstream backend { server 10.0.0.1:80 weight=1;\t#服务器a server 10.0.0.2:80 weight=2;\t#服务器b } 这时候如果进行访问，我们得到的结果就应该是b a b，如果再进行修改权重\nupstream backend { server 10.0.0.1:80 weight=5;\t#服务器a server 10.0.0.2:80 weight=10; #服务器b } 虽然两个权重化简之后都是1：2，但是这时候访问策略就有两种选择\n第一种是先连续访问server a5次再连续访问server b10次\n第二种是a b两台服务器轮流访问，但是总的访问次数比例符合5：10也就是1：2的比例。\n显然第二种访问策略要更加优秀，可以避免一台服务器被连续访问多次而另一台服务器进入空闲状态。在nginx中的upstream模块实现这个操作使用了一个名为smooth weighted round-robin balancing的算法，直译过来就是平滑加权轮询负载均衡算法，大概意思就是实现上面说的第二种操作，下面我们用一个稍微复杂一点的配置来检验一下。\nupstream backend { server 127.0.0.1:8080 weight=3; server 127.0.0.1:8081 weight=5; server 127.0.0.1:8082 weight=7; } server { listen 80; server_name example.com www.example.com; location / { proxy_pass http://backend; } } 这里我们使用nginx监听在本机8080~8081三个端口，然后这三个端口作为本机的80端口的负载均衡后端，接着我们把权重设为比较复杂的3：5：7，然后我们进行实际测试。\n得到的结果为，c b a c b c a c b c b c a b c，这里刚好15次也就是符合权重上面的加起来的总和，从结果来看这里的访问顺序比较均衡，但是细看又比较难找出规律。\n我们去Github上面找到nginx的源码，对应的我们这里使用的测试的版本是稳定版的1.17.9，我们在目录下的nginx-master\\src\\http\\ngx_http_upstream_round_robin.c当中可以看到这部分的实现源码，主要集中在该文件的前面部分。接下来我们需要理解源码中的几个变量：\nweight：配置文件中设置的权重值，是定值，在整个选择过程中是不会改变的，对应到这里就是3、5、7。 current_weight：后端服务器的当前权重值，初始值等于0，在每轮选择中，该值最大的服务器就会被选中 effective_weight：变化权重值，初始值等于weight，用于动态调整服务器被选择的概率，即当被选中的服务器出现了failure的时候，该服务器对应的effective_weight就会减小，具体操作我们下面再解释。 total_weight：总的权重值，即所有服务器的权重值相加，在这里为3+5+7=15。 接下来我们开始逐步解析算法执行过程：\n首先进行各类值的初始化，weight赋值为配置文件中的weight，current_weight赋值为0，effective_weight赋值为weight，total_weight为所有weight之和； 对于每个服务器的current_weight，加上该服务器对应的weight； 选取current_weight值最大的服务器来接受这次访问，然后该服务器对应的current_weight需要减去total_weight（因此current_weight是可以出现负值的） 不断重复步骤2和步骤3，当重复的次数等于total_weight时，所有服务器的current_weight刚好为0，此时结束一轮负载均衡。 从上面的步骤分析我们可以看出，当被选中的服务器出现了failure的时候，该服务器对应的effective_weight就会减小，在下面的current_weight加上该服务器对应的weight操作中就不能变得足够大导致被选中，从而就可以避免过多的访问被分配到出现了failure的服务器。\n接下来我们进行简单的演算看看是否符合实际的输出结果：\n操作 current_weight 选中服务器 初始化 （0，0，0） - 步骤2 （3，5，7） - 步骤3 （3，5，-8） c 步骤2 （6，10，-1） - 步骤3 （6，-5，-1） b 步骤2 （9，0，6） - 步骤3 （-6，0，6） a 步骤2 （-3，5，13） - 步骤3 （-3，5，-2） c 下面的步骤就不继续演示了，当步骤2和步骤3重复了total_weight次，也就是相当于一轮之后，current_weight就会正好全部变回0，而前提是过程中没有出现服务器failure的情况。\n","permalink":"http://localhost:9011/20200318-nginx-02-smooth-weighted-round-robin-balancing/","summary":"\u003cp\u003enginx的upstream模块中的加权轮询负载均衡的算法原理。\u003c/p\u003e","title":"Nginx篇02-upstream模块中的加权轮询负载均衡"},{"content":"nginx的编译安装、添加模块、yum安装、简单配置、默认目录作用和静态页面配置。\n编译安装nginx 准备工作 这里我们使用nginx的mainline版本的1.17.9来进行编译安装，nginx各版本的官网下载地址：http://nginx.org/en/download.html\n首先我们下载并解压nginx源码\nwget http://nginx.org/download/nginx-1.17.9.tar.gz tar -zxvf nginx-1.17.9.tar.gz 在编译安装之前我们还需要先安装几个别的软件：\nGCC/G++编译器：GCC（GNU Compiler Collection）可用来编译C语言程序，如果你还需要使用C++来编写Nginx HTTP模块，这时还需要用到G++编译器了。\nPCRE库：PCRE（Perl Compatible Regular Expressions，Perl兼容正则表达式）是由Philip Hazel开发的函数库，目前为很多软件所使用，该库支持正则表达式。实际上在nginx的很多高级配置中都会用到正则表达式，因此我们在编译Nginx时尽量先把PCRE库编译进Nginx。\nzlib库：zlib库用于对HTTP包的内容做gzip格式的压缩，我们可以在nginx.conf里配置了gzip on，并指定对于某些类型（content-type）的HTTP响应使用gzip来进行压缩以减少网络传输量。\nOpenSSL开发库：HTTPS必备，这个就不用解释了\n上面提到的库我们都可以使用yum来进行安装：\nyum install gcc gcc-c++ pcre pcre-devel zlib zlib-devel openssl openssl-devel # pcre-devel是使用PCRE做二次开发时所需要的开发库，包括头文件等，这也是编译Nginx所必须使用的。 # 同理，zlib是直接使用的库，zlib-devel是二次开发所需要的库。 Nginx是高度自由化的Web服务器，它的功能是由许多模块来支持的。而这些模块可根据我们的使用需求来定制，如果某些模块不需要使用则完全不必理会它。同样，如果使用了某个模块，而这个模块使用了一些类似zlib或OpenSSL等的第三方库，那么就必须先安装这些软件。\n编译安装 我们进入nginx的目录，输入下面的指令可以查看各类的编译参数，或者在官网也可以看到：\n./configure --help 我们这里使用的参数是：\n./configure --sbin-path=/usr/local/nginx/nginx --conf-path=/usr/local/nginx/nginx.conf --pid-path=/usr/local/nginx/nginx.pid --with-http_ssl_module 这里我们可以看到，我们在参数里面并没有指定上面提到的几个库的目录，configure文件会默认系统已经安装的库版本和目录，当然我们也可以手动指定某个库的目录来指定版本。\n接下来进行make安装：\nmake make install 如无意外此时应该已经正常安装好了，我们到前面指定的安装目录看一下\n注意这个时候我们如果需要使用nginx需要指定这个安装目录，想要全局使用我们可以创建一个软链接：\nln -s /usr/local/nginx/nginx /usr/sbin/nginx 添加模块 同时，如果之后有需要用到的模块而在编译安装的时候忘了安装也没关系，我们可以继续编译添加新模块\n首先我们需要查看已经编译的参数:\n$ nginx -V nginx version: nginx/1.17.9 built by gcc 4.4.7 20120313 (Red Hat 4.4.7-23) (GCC) built with OpenSSL 1.0.1e-fips 11 Feb 2013 TLS SNI support enabled configure arguments: --sbin-path=/usr/local/nginx/nginx --conf-path=/usr/local/nginx/nginx.conf --pid-path=/usr/local/nginx/nginx.pid --with-http_ssl_module 需要注意上面的V是大写的V\n在configure arguments: 这一栏里面我们就可以看到之前编译的时候的参数，对比上面的记录我们可以看到是一模一样的，然后我们会到之前下载的源码目录，注意是源码的目录不是安装的目录，然后添加上之前的编译参数，再添加新的模块，\n./configure --sbin-path=/usr/local/nginx/nginx --conf-path=/usr/local/nginx/nginx.conf --pid-path=/usr/local/nginx/nginx.pid --with-http_ssl_module --with-http_v2_module --with-mail --with-mail_ssl_module 比如这里我们添加了http_v2、mail、mail_ssl三个模块\n如果想要添加第三方模块的话，只需要使用--add-module=然后加上第三方模块的路径即可。\n--add-module=/home/echo-nginx-module-0.61 最后我们的编译参数是：\n./configure \\ --sbin-path=/usr/local/nginx/nginx \\ --conf-path=/usr/local/nginx/nginx.conf \\ --pid-path=/usr/local/nginx/nginx.pid \\ --with-http_ssl_module \\ --with-http_v2_module \\ --with-mail \\ --with-mail_ssl_module \\ --add-module=/home/echo-nginx-module-0.61 接着我们使用make安装，再查看目录会发现原来的文件已经被替换成*.default了\nmake make install 最后我们再确定一下是否安装成功：\nyum安装nginx yum仓库建立和安装配置 centos自带的repo中就有nginx，可以直接安装，但是版本比较旧，想要使用yum进行安装最新的稳定版本，我们需要自行配置yum仓库。\n[nginx-stable] name=nginx stable repo baseurl=http://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck=1 enabled=1 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true [nginx-mainline] name=nginx mainline repo baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/ gpgcheck=1 enabled=0 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true 默认情况下mainline版本是不会启用的，因此我们如果需要安装mainline版本的nginx则需要手动启用这个repo。\nyum install yum-utils yum-config-manager --enable nginx-mainline yum clean all yum repolist yum install nginx 安装的时候需要注意这个列出信息中的repo应该是我们刚刚新建的repo。\n安装完成之后需要设置开机启动和防火墙放行80端口，如果使用https还需要放行443端口。\nsystemctl enable nginx systemctl start nginx firewall-cmd --permanent --zone=public --add-port=80/tcp firewall-cmd --permanent --zone=public --add-port=443/tcp firewall-cmd --reload 接下来我们可以测试一下安装和启动是否成功。\nnginx -v curl 127.0.0.1 master和worker进程 使用ps命令查看进程，我们可以看到有一个master进程和一个worker进程，默认情况下，worker的进程数量为1，实际上我们可以根据具体需要对其进行修改。\nps -ef | grep nginx 在正式提供服务的产品环境下，部署Nginx时都是使用一个master进程来管理多个worker进程，一般情况下，worker进程的数量与服务器上的CPU核心数相等。每一个worker进程都是繁忙的，它们在真正地提供互联网服务，master进程则很“清闲”，只负责监控管理worker进程。worker进程之间通过共享内存、原子操作等一些进程间通信机制来实现各种功能。\nnginx基本配置 nginx默认目录 /etc/nginx/ 这个是nginx服务器的默认配置目录\n/etc/nginx/nginx.conf 这个是nginx服务器的默认配置文件，我们可以在这里对nginx的所有全局配置进行修改，包括线程数端口号等等，同时在默认情况下它也包括了下述的/etc/nginx/conf.d/目录中的所有配置文件。\n/etc/nginx/conf.d/ 这个目录中包含的.conf配置文件主要用于单独定义某个http网页，从而使得整个配置目录文件的管理变得更加简洁而清晰。\n/var/log/nginx 这个目录是默认的log日志目录，主要有acces.log和error.log两个文件，前者负责记录每一个被访问的记录，后者负责记录访问中出现的错误。\nnginx命令 输入nginx -h即可查看所有指令，不需要特意去记忆，用多了就自然记住了。\nnginx.conf文件 我们把整个全局配置文件拿出来分析一下：\nuser nginx; worker_processes 16; 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 \u0026#39;$remote_addr - $remote_user [$time_local] \u0026#34;$request\u0026#34; \u0026#39; \u0026#39;$status $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; \u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39;; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; } user user指的是以哪个用户来创建nginx的worker进程，master进程一般都是使用root用户启动，权限较大。\nworker_processes worker_processes则是nginx的worker进程数量，一般与CPU的核心数量一致，这里我们设置为16。\nerror_log error_log是日志的存放位置和输出等级，等级的取值范围是debug、info、notice、warn、error、crit、alert、emerg，从左至右级别依次增大。当设定为一个级别时，大于或等于该级别的日志都会被输出到记录文件中，小于该级别的日志则不会输出。这里默认设定的是warn级别，则warn、error、crit、alert、emerg级别的日志都会输出。\n如果设定的日志级别是debug，则会输出所有的日志，这样数据量会很大，要确保存放日志的硬盘有足够的空间，同时，如果需要开启日志的debug功能，需要在编译安装的时候在configure时加入--with-debug配置项，如果不确定是否开启了debug功能，可以输入nginx -V查看所有的configure arguments。\npid pid是nginx的master进程的pid文件，理论上应该和查找的nginx进程中master进程的PID以及worker进程的PPID一致。\n块配置 接下来的events和http都是属于模块或者块。最基本的配置项语法格式为配置项名 配置项值1 配置项值2 … ;\n一个配置项以英文分号;结束，中间的值使用空格隔开 块配置项由一个块配置项名和一对大括号组成。 块配置项可以嵌套，内层块直接继承外层块。 当内外层块中的配置发生冲突时，究竟是以内层块还是外层块的配置为准，取决于解析这个配置项的模块。 注释部分使用井号＃ 比如上面的log_format这个配置项，变量需要在前面加上美刀符号$，如果变量之间有空格，需要使用单引号或者双引号避免语法错误，同时引号可以嵌套使用。同时需要注意的是，并不是所有的模块都支持使用变量。\nlog_format main \u0026#39;$remote_addr - $remote_user [$time_local] \u0026#34;$request\u0026#34; \u0026#39; \u0026#39;$status $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; \u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39;; 单位 当指定空间大小时，可以使用的单位包括：\nK或者k千字节（KiloByte，KB） M或者m兆字节（MegaByte，MB） 当指定时间时，可以使用的单位包括：\nms（毫秒） s（秒） m（分钟） h（小时） d（天） w（周，7天） M（月，30天） y（年，365天） 单位之间支持混合使用，如1h30m即为90m如果不指定后缀，那么默认使用s（秒）作为单位。\n配置项后的值究竟是否可以使用这些单位，取决于解析该配置项的模块。如果这个模块使用了Nginx框架提供的相应解析配置项方法，那么配置项值才可以携带单位。\nnginx配置静态页面 接下来我们尝试配置一个简单的静态页面，使用vim对/etc/nginx/conf.d/default.conf进行修改，需要注意的是默认情况下/etc/nginx/conf.d/下面的配置文件只要是.conf即可生效，前面的名称并没有特殊限制，所以最好根据文件的实际用途进行命名方便记忆和管理。\n接下来我们在/etc/hosts中将 www.example.com www.example.org www.example.net example.com example.org example.net的DNS解析手动指定为本机IP地址，方便后面使用域名进行配置页面\ndefault_server [root@localhost conf.d]# ll 总用量 16 -rw-r--r-- 1 root root 1093 3月 4 00:20 default.conf.bak -rw-r--r-- 1 root root 158 3月 18 00:08 example.com.conf -rw-r--r-- 1 root root 158 3月 18 00:09 example.net.conf -rw-r--r-- 1 root root 173 3月 18 00:11 example.org.conf [root@localhost conf.d]# cat example.* server { listen 80; server_name example.com www.example.com; location / { root /var/www/html; index example.com.html; } } server { listen 80; server_name example.net www.example.net; location / { root /var/www/html; index example.net.html; } } server { listen 80 default_server; server_name example.org www.example.org; location / { root /var/www/html; index example.org.html; } } 这里我们可以看到上面配置了三个server块，分别对应三组域名，三组域名都是指向本机的IP地址，同样都是监听的80端口，其中我们在第三个server块中指定了default_server参数，此时我们访问本机IP，返回的页面就是我们指定了default_server参数的这个页面。\n如果我们不指定default_server参数，返回的则是默认的第一个页面。\nlocation location块的默认语法如下，官网文档点这里。\nSyntax:\tlocation [ = | ~ | ~* | ^~ ] uri { ... } location @name { ... } Default: — Context: server, location 主要作用是根据请求URI设置配置，location可以由前缀字符串或正则表达式定义。\n正则表达式由前面的~*修饰符（不区分大小写）或~修饰符（区分大小写）指定。 优先顺序是，nginx首先检查使用前缀字符串定义的位置（前缀位置），其中，将选择并记住具有最长匹配前缀的位置。 然后按照在配置文件中出现的顺序检查正则表达式。 正则表达式的搜索在第一个匹配项上终止，并使用相应的配置。 如果未找到与正则表达式匹配的内容，则使用前面记住的前缀位置的配置。 我们来看一下实例：\nserver { listen 80; server_name example.com www.example.com; location / { root /var/www/html; index example.com.html; } location /images { root /var/www; } } 我们先来看一个server块，这里我们可以看到里面包含了两个location块，在/var/www/html和/var/www这两个目录下均有一个images文件夹，但是在www目录下的images文件夹没有images2.html这个文件。\n接着我们尝试访问：\n可以看到因为我们在www目录下的images文件夹没有images2.html这个文件，所以在执行curl example.com/images/images2.html的时候返回了404请求。\n所以我们可以得到结论，当访问域名后面的目录（如这里的/images/），如果在server块里面单独定义了一个相关的location块，则只会在这个/images/目录相关location块定义的目录中去查找，不存在则返回404，并不会再去根目录/的location块中的目录中查找。\n","permalink":"http://localhost:9011/20200317-nginx-01-base-conf-static-web/","summary":"\u003cp\u003enginx的编译安装、添加模块、yum安装、简单配置、默认目录作用和静态页面配置。\u003c/p\u003e","title":"Nginx篇01-基本安装配置和静态页面设置"},{"content":"主要介绍虚拟化技术的历史演进过程和KVM虚拟化的一些特点。\n简介 虚拟化技术的演变过程可以分为软件模拟、虚拟化层翻译、容器虚拟化三个大的阶段。其中，虚拟化层翻译又可以分为：软件捕获翻译（软件全虚拟化）、**改造虚拟机系统内核加虚拟化层翻译（半虚拟化）和硬件支持的虚拟化层翻译（硬件支持的全虚拟化）**三种。\n软件模拟 软件模拟是通过软件完全模拟CPU、芯片组、磁盘、网卡等计算机硬件。因为是软件模拟，所以理论上可以模拟任何硬件，甚至是不存在的硬件。但是因为这种方式全部是软件模拟硬件，所以非常低效，性能很差，因此一般只用于研究测试的场景。采用这种技术的典型产品有Bochs、QEMU等。\n虚拟化层翻译 X86平台指令集划分为4个特权模式：Ring 0、Ring 1、Ring 2、Ring 3。操作系统一般使用Ring 0级别，应用程序使用Ring 3级别，驱动程序使用Ring 1和Ring 2级别。X86平台在虚拟化方面的一个难点就是如何将虚拟机越级的指令使用进行隔离。\n软件全虚拟化 VMware公司首先想到了通过虚拟化引擎，捕获虚拟机的指令，并进行处理的解决方法。\n通过虚拟化引擎VMM来对虚拟机上的指令进行捕获并解释执行，就可以有效地隔离开虚拟机和物理机上的操作，最常见的表现就是关闭虚拟机并不会关闭物理机。这种方案也因此叫做软件全虚拟化的解决方案。\nHypervisor，又称虚拟机监视器（英语：virtual machine monitor，缩写为 VMM），是用来建立与执行虚拟机器的软件、固件或硬件。\n被Hypervisor用来执行一个或多个虚拟机器的电脑称为主体机器/宿主机/物理机（host machine），这些虚拟机器则称为客体机器/虚拟机（guest machine）。hypervisor提供虚拟的作业平台来执行客体操作系统（guest operating systems），负责管理其他客体操作系统的执行阶段；这些客体操作系统，共同分享虚拟化后的硬件资源。\n半虚拟化 软件全虚拟化的解决方案虽然可行，但是由于指令需要经过VMM，所以要在虚拟化层做大量的工作，性能上比较一般，因此Xen项目提出了使用修改虚拟机操作系统内核的解决方案。具体来说就是在虚拟机上使用修改过的内核，在修改过的内核中对特殊指令进行修改使其与物理机隔离开来，这样就可以有效地减少虚拟化层的工作，从而提高性能。但是劣势也是显而易见的，由于需要使用特殊的内核，因此在配置上也会相对麻烦一些，且由于内核原因一开始不支持windows系统的虚拟化，直到后来才加入支持。\n不过由于将一部分的指令翻译工作从虚拟化层转移到了修改过的内核当中，因此性能比上面提到的软件全虚拟化要高很多，这种虚拟化方式也称为半虚拟化（paravirtualization）或者是准虚拟化。\n硬件全虚拟化 2005年，Intel推出了硬件的方案，对CPU指令进行改造，即VT-x，如图1-5所示。VT-x增加了两种操作模式：VMX root operation和VMX non-root operation。VMM运行在VMX root operation模式，虚拟机运行在VMX non-root operation模式。这两种操作模式都支持Ring 0～Ring 3这4个特权级。\n2005年，随着Intel推出支持x86虚拟化技术的CPU，Xen亦加入全虚拟化模式。Xen的全虚拟化模式允许在虚拟机中运行Windows等非Linux系统。\n因为是基于硬件的，所以效率非常高，这种方案也称为硬件支持的全虚拟化方案。现在的一个发展趋势是不仅CPU指令有硬件解决方案，I/O通信也有硬件解决方案，称为VT-d；网络通信也有硬件解决方案，称为VT-c。\n除了Intel，AMD也在自家的处理器上加入了对硬件级虚拟化的支持，命名为AMD-v。\n当前的虚拟化引擎，都是使用硬件支持的虚拟化解决方案。并且最新的操作系统一般都支持一些半虚拟化的特性，所以宿主机和虚拟机使用比较新的版本，性能也会好一些。\n容器虚拟化 容器虚拟化的原理是基于CGroups、Namespace等技术将进程隔离，每个进程就像一台单独的虚拟机一样，有自己被隔离出来的资源，也有自己的根目录、独立的进程编号、被隔离的内存空间。基于容器的虚拟化可以实现在单一内核上运行多个实例，因此是一个更高效率的虚拟化方式。目前基于Docker+k8s的容器级虚拟化技术已经在国内许多互联网公司的生产环境中大量使用。\nKVM虚拟化 kvm简介 KVM（Kernel-based Virtual Machine）最初是由以色列的公司Qumranet开发的。KVM在2007年2月被正式合并到Linux 2.6.20核心中，成为内核源代码的一部分。2008年9月4日，RedHat公司收购了Qumranet，开始在RHEL中用KVM替换Xen，第一个包含KVM的版本是RHEL 5.4。**从RHEL 6开始，KVM成为默认的虚拟化引擎。**KVM必须在具备Intel VT或AMD-V功能的X86平台上运行。在Linux内核3.9版中，加入了对ARM架构的支持。\n具体kvm虚拟化支持的处理器可以通过官网进行查询。\nKVM包含一个为处理器提供底层虚拟化、可加载的核心模块kvm.ko（kvm-intel.ko或kvm-amd.ko），使用QEMU（QEMU-KVM）作为虚拟机上层控制工具。KVM不需要改变Linux或Windows系统就能运行。\n实际上，在Linux中，kvm就是内核中的一个模块，用户空间通过QEMU模拟硬件提供给虚拟机使用，而一台使用kvm创建的虚拟机就是一个Linux中的进程，管理这个对应的进程就是相当于管理整个对应的虚拟机。\nQEMU和KVM 以下摘录自wiki：\nQEMU有多种模式\nUser mod：又称作“使用者模式”，在这种模块下，QEMU运行针对不同指令编译的单个Linux或Darwin/macOS程序。系统调用与32/64位接口适应。在这种模式下，我们可以实现交叉编译（cross-compilation）与交叉偵错（cross- debugging）。 System mod：“系统模式”，在这种模式下，QEMU模拟一个完整的计算机系统，包括外围设备。它可以用于在一台计算机上提供多台虚拟计算机的虚拟主机。 QEMU可以实现许多客户机OS的引导，比如x86，MIPS，32-bit ARMv7，PowerPC等等。 KVM Hosting：QEMU在这时处理KVM镜像的设置与迁移，并参加硬件的仿真，但是客户端的执行则由KVM完成。 Xen Hosting：在这种托管下，客户端的执行几乎完全在Xen中完成，并且对QEMU屏蔽。QEMU只提供硬件仿真的支持。 前面我们提到过QEMU的架构是纯软件实现的，因此灵活性很强，但是性能很差，因此可以搭配KVM模块使用从而有效地提升性能表现，也就是我们常说的QEMU-KVM，此时的QEMU运行在上面提到的KVM Hosting模式下，处理KVM镜像的设置与迁移，并参加硬件的仿真，可以存储及还原虚拟机运行状态，还可以虚拟多种设备，包括网卡、多CPU、IDE设备、软驱、显卡、声卡、多种并口设备、多种串口设备、多种USB设备、PC喇叭、PS/2键盘鼠标（默认）和USB键盘鼠标、蓝牙设备，但是客户端的执行则由KVM完成。\nQEMU-KVM的分支版本发布了3个正式的版本1.1、1.2、1.3，随后和QEMU的主版本合并，也就是说现在的QEMU版本默认支持KVM，而KVM的最后一个自己的版本是KVM 83，随后和内核版本一起发布，和内核版本号保持一致，所以要使用KVM的最新版本，就要使用最新的内核。\nLibvirt和KVM 前面我们提到，基于KVM的虚拟机在Linux中可以视为一个进程，而为了方便管理，红帽公司发布了一个开源项目Libvirt，Libvirt有API，也有一套命令行工具，可以完成对虚拟机的管理，大多数的管理平台都是通过Libvirt来完成对KVM虚拟机的管理的，比如OpenStack、CloudStack、OpenNebula等。\nLibvirt主要由3部分组成：\n一套API的lib库，支持主流的编程语言，包括C、Python、Ruby等 Libvirtd服务 命令行工具virsh Libvirt的设计目标是通过相同的方式管理不同的虚拟化引擎，但是目前实际上多数场景使用Libvirt的是KVM。Libvirt可以实现对虚拟机的管理，比如虚拟机的创建、启动、关闭、暂停、恢复、迁移、销毁，以及虚拟机网卡、硬盘、CPU、内存等多种设备的热添加。Libvirt还可以通过SSH、TCP、基于TCP的TLS来实现远程的宿主机管理。\nKVM的优势 KVM虚拟化的主要优势在我看来有三点：\n开源免费\nkvm是开源的，同时也是可以免费使用的，这也意味着有很多新技术会先在kvm上尝鲜，再推广到其他的虚拟化平台，而且对于企业来说，免费这一对个人用户而言感知不强的特性就会被放大很多倍，这也是诸如openstack等云平台使用kvm作为首选虚拟化的原因。\n性能优越\n同样的硬件条件下，相比起软件模拟、半虚拟化等，kvm虚拟化可以提供更好的性能甚至于接近物理机的性能。\n支持广泛\n除了广泛的社区技术支持，还可以付费购买红帽公司的技术支持。\n","permalink":"http://localhost:9011/20200301-virtualization-intro-kvm-intro/","summary":"\u003cp\u003e主要介绍虚拟化技术的历史演进过程和KVM虚拟化的一些特点。\u003c/p\u003e","title":"虚拟化技术的演变过程和KVM虚拟化的简介"},{"content":"kvm支持嵌套虚拟化，即可以在虚拟机中创建虚拟机。本文主要介绍如何在使用Intel处理器的CentOS7中开启KVM的嵌套虚拟化功能。\nkvm主要是通过内核模块来实现的，因此我们查看系统是否开启了kvm嵌套虚拟化，只需要：\ncat /sys/module/kvm_intel/parameters/nested 根据输出的结果即可知道是否支持。\n如果不支持，我们则需要在/etc/modprobe.d/新建一个kvm-nested.conf：\noptions kvm-intel nested=1 options kvm-intel enable_shadow_vmcs=1 options kvm-intel enable_apicv=1 options kvm-intel ept=1 接着我们重新加载一下kvm模块：\nmodprobe -r kvm_intel modprobe -a kvm_intel 最后再查看一下是否成功：\ncat /sys/module/kvm_intel/parameters/nested ","permalink":"http://localhost:9011/20200213-kvm-nested/","summary":"\u003cp\u003ekvm支持嵌套虚拟化，即可以在虚拟机中创建虚拟机。本文主要介绍如何在使用Intel处理器的CentOS7中开启KVM的嵌套虚拟化功能。\u003c/p\u003e","title":"Linux上启用kvm嵌套虚拟化功能"},{"content":"Ceph的基本概念介绍和入门知识储备。\nceph简介 Ceph是一个开源项目，它提供软件定义的、统一的存储解决方案。Ceph是一个可大规模扩展、高性能并且无单点故障的分布式存储系统。从一开始它就运行在通用商用硬件上，具有高度可伸缩性，容量可扩展至EB级别，甚至更大。\nCeph的架构在设计之初就包含下列特性：\n所有的组件必须可扩展 不能存在单点故障 解决方案必须是软件定义的、开源的并且可适配 Ceph软件应该运行在通用商用硬件之上 所有组件必须尽可能自我管理 Ceph存储系统在同一个底层架构上提供了块、文件和对象存储，使得用户可以自主选择他们需要的存储方式。对象是Ceph的基础，也就是它的基本存储单元。任何格式的数据，不管是块、对象还是文件，都以对象的形式保存在Ceph集群的归置组(Placement Group，Pg) 中。使用对象存储，我们可以将平台和硬件独立开来。在Ceph中，由于对象没有物理存储路径绑定，使得对象非常灵活并且与位置无关。这也使得Ceph的规模能够近线性地从PB级别扩展到EB级别。\nCeph是圣克鲁兹加利福尼亚大学的Sage Weil在2003年开发的，也是他的博士学位项目的一部分。初始的项目原型是大约40000行C++代码的Ceph文件系统，并于2006年作为参考实现和研究平台遵循LGPL协议(Lesser GUN Public License)开源。美国劳伦斯利物莫国家实验室(Lawrence Livermore National Laboratory)资助了Sage的初始研究工作。2003～2007年是Ceph的研究开发时期。在这期间，它的核心组件逐步形成，并且社区对项目的贡献也已经开始逐渐变大。Ceph没有采用双重许可模式，也就不存在只针对企业版的特性。\nInktank是Ceph背后的公司，它的主要目的是给他们的企业客户提供专业知识、处理流程、工具和支撑，使他们能够有效地采用和管理Ceph存储系统。Sage是Inktank的CTO和创始人。\nCeph这个词是宠物章鱼的一个常见绰号。Ceph可以看作Cephalopod的缩写，它属于海洋软体类动物家族。Ceph以章鱼作为自己的吉祥物，表达了Ceph跟章鱼一样的并行行为。\nInktank这个词与章鱼有一定关系。渔民有时候也把章鱼称为墨鱼，因为它们可以喷射墨汁。这就解释了为什么章鱼(Ceph)跟墨鱼(Inktank)有一定关系。同样，Ceph和Inktank有很多共同点。你可以认为Inktank就是Ceph的智库。\n开源Linux社区2008年就预见到Ceph的潜力，并将其加入Linux内核主线。这已经成为Ceph的里程碑事件。\nCeph与云 云环境要求其存储能够以低成本纵向和横向扩展，而且能够容易与云框架中其他组件集成。\nOpenStack项目大力推动了公有云和私有云的发展。它已经证明了自己是一个端到端云解决方案。它自己的内部核心存储组件Swift提供基于对象的存储和Nova-Volume(也称为Cinder)，而Cinder则为VM提供块存储。\n与Swift(它仅提供对象存储)不同，Ceph是一个包含块存储、文件存储和对象存储的统一存储解决方案，这样可以通过单一存储集群为OpenStack提供多种存储类型。因此，你可以轻松而高效地为OpenStack云管理存储。OpenStack和Ceph社区已经一起合作了许多年，致力于为OpenStack云开发一个完全支持的Ceph存储后端。从Folsom(OpenStack第6个主要版本)开始，Ceph已经完全与OpenStack集成。Ceph开发人员确保Ceph能够适用于OpenStack的最新版，同时贡献新特性以及修正bug。OpenStack通过它的cinder和glance组件使用Ceph最苛刻的特性RADOS块设备(RBD)。Ceph RBD通过提供精简配置的快照复制(snapshotted-cloned)卷帮助OpenStack快速配置数百个VM实例，这种方式既减少空间需求，又非常快速。\nCeph与软件定义存储（SDS） Ceph是一个真正的SDS解决方案，它是开源软件，运行在商用硬件上，因此不存在厂商锁定，并且能提供低成本存储。SDS方案提供了客户急需的硬件选择的灵活性。客户可以根据自身的需要选择任意制造商的商用硬件，并自由地设计异构的硬件解决方案。在此硬件解决方案之上的Ceph的软件定义存储可以很好地工作。它可以从软件层面正确提供所有的企业级存储特性。低成本、可靠性、可扩展性是Ceph的主要特点。\n从存储厂商的角度来看，统一存储的定义就是在单一的平台上同时提供基于文件和基于块的访问。\nCeph底层中并不存在块和文件的管理，而是管理对象并且在对象之上支持基于块和文件的存储。在传统基于文件的存储系统中，文件是通过文件目录进行寻址的。类似地，Ceph中的对象通过唯一的标识符进行寻址，并存储在一个扁平的寻址空间中。剔除了元数据操作之后，对象提供了无限的规模扩展和性能提升。Ceph通过一个算法来动态计算存储和获取某个对象的位置。\n传统的存储系统并不具备更智能地管理元数据的方法。元数据是关于数据的信息，它决定了数据将往哪里存储，从哪里读取。传统的存储系统通过维护一张集中的查找表来跟踪它们的元数据。也就是说，客户端每次发出读写操作请求时，存储系统首先要查找这个巨大的元数据表，得到结果之后它才能执行客户端请求的操作。对于一个小的存储系统而言，你或许不会感觉到性能问题，但对于一个大的存储集群来说，你将会受制于这种方法的性能限制。它也会限制系统的扩展性。\nCeph中的数据副本 Ceph没有采用传统的存储架构，而是用下一代架构完全重塑了它。Ceph引入了一个叫CRUSH的新算法，而不是保存和操纵元数据。CRUSH是Controlled Replication Under Scalable Hashing的缩写。\nCRUSH算法在后台计算数据存储和读取的位置，而不是为每个客户端请求执行元数据表的查找。通过动态计算元数据，Ceph也就不需要管理一个集中式的元数据表。现代计算机计算速度极快，能够非常快地完成CRUSH查找。另外，利用分布式存储的功能可以将一个小的计算负载分布到集群中的多个节点。CRUSH清晰的元数据管理方法比传统存储系统的更好。\n**CRUSH会以多副本的方式保存数据，以保证在故障区域中有些组件故障的情况下数据依旧可用。**用户在Ceph的CRUSH map中可以自由地为他们的基础设施定义故障区域。这也就使得Ceph管理员能够在自己的环境中高效地管理他们的数据。CRUSH使得Ceph能够自我管理和自我疗愈。当故障区域中的组件故障时，CRUSH能够感知哪个组件故障了，并确定其对集群的影响。无须管理员的任何干预，CRUSH就会进行自我管理和自我疗愈，为因故障而丢失的据数执行恢复操作。CRUSH根据集群中维护的其他副本来重新生成丢失的数据。在任何时候，集群数据都会有多个副本分布在集群中。\n使用RAID技术修复多个大硬盘是一个很繁琐的过程。另外，RAID需要很多整块的磁盘来充当备用盘。这也会影响到TCO，如果你不配置备用盘，将会将会遇到麻烦。RAID机制要求在同一个RAID组中的磁盘必须完全相同。如果你改动了磁盘容量、转速和磁盘类型，则你可能要面临惩罚。这样做将会对于存储系统的容量和性能产生不利影响。\nTCO （Total Cost of Ownership ），即总拥有成本，包括产品采购到后期使用、维护的成本。这是一种公司经常采用的技术评价标准。由于常见的RAID5、6、10等阵列方式需要使用磁盘作为镜像盘或者热备盘，因此如果你买了10块10T的硬盘共计100T空间，使用阵列后会导致最后可以使用的硬盘容量小于100T。当然你也可以使用诸如RAID0之类的不损失空间的阵列方式，但是这样会导致数据的安全性大大地降低。\n基于RAID的企业级存储系统通常都需要昂贵的硬件RAID卡，这也增加了系统总成本。\nRAID系统最大的限制因素是它只能防止磁盘故障，而不能为网络、服务器硬件、OS、交换设备的故障或者区域灾害提供保护措施。\nRAID最多能为你提供防止两个磁盘故障的措施。在任何情况下你无法容忍超过两个的磁盘故障。\nCpeh是一个软件定义的存储，因此它不需要任何特殊硬件来提供数据副本功能。另外，数据副本级别可以通过命令高度定制化。这也就意味着Ceph存储管理员能够轻松地根据自身需要和底层基础设施特点来管理副本策略。\n这样的恢复操作不需要任何热备磁盘；数据只是简单地复制到Ceph集群中其他的磁盘上。\n除了数据副本方法外，Ceph还支持其他用于保证数据可靠性的方法，比如纠删码技术。纠删码方式下，毁损的数据借助纠删码计算通过算法进行恢复或再次生成。\nCeph的存储兼容性 Ceph是一个完备的企业级存储系统，它支持多种协议以及访问方式，主要可以分为块、文件和对象存储三大类。\nCeph块存储 块存储是存储区域网络中使用的一个数据存储类别。在这种类型中，数据以块的形式存储在卷里，卷会挂接到节点上。它可以为应用程序提供更大的存储容量，并且可靠性和性能都更高。这些块形成的卷会映射到操作系统中，并被文件系统层控制。\nCeph引入了一个新的RBD协议，也就是Ceph块设备(Ceph Block Device)。\nRBD块呈带状分布在多个Ceph对象之上，而这些对象本身又分布在整个Ceph存储集群中，因此能够保证数据的可靠性以及性能。\n几乎所有的Linux操作系统发行版都支持RBD。除了可靠性和性能之外，RBD也支持其他的企业级特性，例如完整和增量式快照，精简的配置，写时复制(copy-on-write)式克隆，以及其他特性。RBD还支持全内存式缓存，这可以大大提高它的性能。Ceph RBD支持的最大镜像为16EB。在OpenStack中，可以通过cinder(块)和glance(image)组件来使用Ceph块设备。这样做可以让你利用Ceph块存储的copy-on-write特性在很短的时间内创建上千个VM。\nCeph文件存储 Ceph文件系统(也就是CephFS)是一个兼容POSIX的文件系统，它利用Ceph存储集群来保存用户数据。CephFS将数据和元数据分开存储，为上层的应用程序提供较高的性能以及可靠性。\nLinux内核驱动程序支持CephFS，这也使得CephFS高度适用于各大Linux操作系统发行版。\n在Cpeh集群内部，Ceph文件系统库(libcephfs)运行在RADOS库(librados)之上，后者是Ceph存储集群协议，由文件、块和对象存储共用。要使用CephFS，你的集群节点上最少要配置一个Ceph元数据服务器(MDS)。然而，需要注意的是，单一的MDS服务器将成为Ceph文件系统的单点故障。MDS配置后，客户端可以采用多种方式使用CephFS。如果要把Ceph挂载成文件系统，客户端可以使用本地Linux内核的功能或者使用Ceph社区提供的ceph-fuse(用户空间文件系统)驱动。除此之外，客户端可以使用第三方开源程序，例如NFS的Ganesha和SMB/CIFS的Samba。\nCeph对象存储 对象存储是一种以对象形式而不是传统文件和块形式存储数据的方法。\nCeph是一个分布式对象存储系统，通过它的对象网关(object gateway)，也就是RADOS网关(radosgw)提供对象存储接口。RADOS网关利用librgw(RADOS网关库)和librados这些库，允许应用程序跟Ceph对象存储建立连接。\nRADOS网关提供RESTful接口让用户的应用程序将数据存储到Ceph集群中。RADOS网关接口满足以下特点。\n兼容Swift：这是为OpenStack Swift API提供的对象存储功能。 兼容S3：这是为Amazon S3 API提供的对象存储功能。 Admin API：这也称为管理API或者原生API，应用程序可以直接使用它来获取访问存储系统的权限以管理存储系统。 要访问Ceph的对象存储系统，也可以绕开RADOS网关层，这样更灵活并且速度更快。librados软件库允许用户的应用程序通过C、C++、Java、Python和PHP直接访问Ceph对象存储。Ceph对象存储具备多站点(multisite)的能力，也就是说它能为灾难恢复提供解决方案。通过RADOS或者联合网关可以配置多站点的对象存储。\n其他存储方案 GPFS/通用并行文件系统 GPFS(General Parallel File System，通用并行文件系统)是一个分布式文件系统，由IBM开发及拥有。这是一个专有、闭源的存储系统，这使得它缺少吸引力并且难以适应。存储硬件加上授权以及支持成本使得它非常昂贵。另外，它提供的存储访问接口非常有限；它既不能提供块存储，也不能提供RESTful接口来访问存储系统，因此这是一个限制非常严格的系统。甚至最大的数据副本数都限制只有3个，这在多个组件同时故障的情形下降低了系统的可靠性。\niRDOS iRDOS是面向规则的数据系统的代表，它是依据第三条款(3-clause)BSD协议发布的开源数据管理软件。iRDOS不是一个高度可靠的存储系统，因为它的iCAT元数据服务器是单点(single point of failure，SPOF)，并且它不提供真正的HA。另外，它提供的存储访问接口很有限；既不能提供块存储，也不能提供RESTful接口来访问存储系统，因此这是一个限制非常严格的系统。它更适合于存储少量大文件，而不是同时存储小文件和大文件。iRdos采用传统的工作方式，维护一个关于物理位置(与文件名相关联)的索引。由于多个客户端都需要从元数据服务器请求文件位置，使得元数据服务器需要承受更多的计算负载，从而导致单点故障以及性能瓶颈。\nHDFS HDFS是一个用Java写的并且为Hadoop框架而生的分布式可扩展文件系统。HDFS不是一个完全兼容POSIX的文件系统，并且不支持块存储，这使得它的适用范围不如Ceph。HDFS的可靠性不需要讨论，因为它不是一个高度可用的文件系统。HDFS中的单点故障以及性能瓶颈主要源于它单一的NameNode节点。它更适合于存储少量大文件，而不是同时存储小文件和大文件。\nLustre Lustre是一个由开源社区推动的并行分布式文件系统，依据GNU(General Public License，通用公共许可证)发布。在Lustre中，由单独一个服务器负责存储和管理元数据。因此，从客户端来的所有I/O请求都完全依赖于这个服务器的计算能力，但对企业级计算来说通常这个服务器的计算能力都比较低。与iRDOS和HDFS类似，Lustre适合于存储少量大文件，而不是同时存储小文件和大文件。与iRDOS类似，Lustre管理一个用于映射物理位置和文件名的索引文件，这就决定了它是一个传统的架构，而且容易出现性能瓶颈。Lustre不具备任何节点故障检测和纠正机制。在节点出现故障时，客户端只能自行连接其他节点。\nGluster GlusterFS最初由Gluster公司开发，该公司2011年被Red Hat收购。GlusterFS是一个横向扩展的网络附加(network-attached)文件系统。在Gluster中，管理员必须明确使用哪种安置策略来将数据副本存储到不同地域的机架上。Gluster本身不内置块访问、文件系统和远程副本，而是以扩展(add-ons)的方式支持。\n","permalink":"http://localhost:9011/20200201-ceph-01-ceph-introduction/","summary":"\u003cp\u003eCeph的基本概念介绍和入门知识储备。\u003c/p\u003e","title":"Ceph系列01-Ceph简介"},{"content":"通过修改注册表来修改windows远程的3389端口号从而提升一定的安全性。\n首先按win+R打开运行窗口，然后输入regedit打开注册表。\n接下来我们定位到注册表的这个位置\n计算机\\HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp 找到PortNumber，注意需要勾选十进制再进行修改，把默认的3389端口号改为其他的端口号即可，建议修改为一个较大的端口号，这样不容易与其他程序的端口号产生冲突。\n最后重启电脑即可生效，之后再使用远程的时候记得要在IP地址后面加上英文冒号和端口号，如：192.168.66.66:23333，如果不手动指定就是默认的3389端口号了。\n","permalink":"http://localhost:9011/20200125-windows-change-mstsc-port/","summary":"\u003cp\u003e通过修改注册表来修改windows远程的3389端口号从而提升一定的安全性。\u003c/p\u003e","title":"Windows更改MSTSC的远程端口号"},{"content":"使用ntfsfix解决Linux下无法挂载NTFS硬盘的问题，主要是由硬盘分区的$MFT文件出现了问题，可以在windows下使用chkdsk命令或者在Linux下使用ntfsfix来进行修复。\n首先是故障详情\n$ sudo mount /dev/sda1 $MFTMirr does not match $MFT (record 3). Failed to mount \u0026#39;/dev/sda1\u0026#39;: Input/output error NTFS is either inconsistent, or there is a hardware fault, or it\u0026#39;s a SoftRAID/FakeRAID hardware. In the first case run chkdsk /f on Windows then reboot into Windows twice. The usage of the /f parameter is very important! If the device is a SoftRAID/FakeRAID then first activate it and mount a different device under the /dev/mapper/ directory, (e.g. /dev/mapper/nvidia_eahaabcc1). Please see the \u0026#39;dmraid\u0026#39; documentation for more details. 接着我在askubuntu上面看到了一个同样的情况，高赞回答里面说是$MFT文件出现了问题，windows下可以使用chkdsk进行修复，之前写过相关的操作教程，这次尝试一下使用Linux下的ntfsfix来修复。\n这里需要事先安装好ntfsprogs这个工具，ubuntu下也可以直接使用apt安装，下面使用的是CentOS7作为示范。\n$ sudo yum install ntfsprogs $ sudo ntfsfix /dev/sda1 Mounting volume... $MFTMirr does not match $MFT (record 3). FAILED Attempting to correct errors... Processing $MFT and $MFTMirr... Reading $MFT... OK Reading $MFTMirr... OK Comparing $MFTMirr to $MFT... FAILED Correcting differences in $MFTMirr record 3...OK Processing of $MFT and $MFTMirr completed successfully. Setting required flags on partition... OK Going to empty the journal ($LogFile)... OK Checking the alternate boot sector... OK NTFS volume version is 3.1. NTFS partition /dev/sda1 was processed successfully. 修复完成之后我们直接进行挂载，这次没有报错，可以正常读写里面的文件了。\n$ sudo mount /dev/sda1 由于这里已经在/etc/fstab文件中制定了挂载的目录和方式，所以直接在命令行里面指定要挂载的设备号就能根据/etc/fstab内的信息进行挂载。\n","permalink":"http://localhost:9011/20200120-linux-ntfsfix-ntfs/","summary":"\u003cp\u003e使用\u003ccode\u003entfsfix\u003c/code\u003e解决Linux下无法挂载NTFS硬盘的问题，主要是由硬盘分区的\u003ccode\u003e$MFT\u003c/code\u003e文件出现了问题，可以在windows下使用\u003ccode\u003echkdsk\u003c/code\u003e命令或者在Linux下使用\u003ccode\u003entfsfix\u003c/code\u003e来进行修复。\u003c/p\u003e","title":"使用ntfsfix解决Linux下无法挂载NTFS硬盘的问题"},{"content":"在CentOS7上使用Gnome3.26版本进行安装测试，大多数的插件都可以正常运行，主题的工作情况良好，下面的推荐插件全部同时启用会发生冲突，实际需要启用那些插件大家可以根据自己的实际需求进行选择。\n注：grub界面的引导美化在CentOS7上面暂未成功。\n准备工作 安装gnome-tweak-tool 使用yum安装tweak来管理gnome的界面设置和插件\nsudo yum install gnome-tweak-tool chrome-gnome-shell 安装gnome-shell-integration 安装浏览器插件，一般来说在gnome官网界面上会提醒安装，火狐和谷歌都支持该插件，如果没有提醒，可以直接去浏览器的插件中心搜索。\nGnome shell Extension 一般来说需要的插件都可以在这里找到： gnome的插件的官网 https://extensions.gnome.org/\nUser Themes https://extensions.gnome.org/extension/19/user-themes/ 使shell界面可以使用主题\nDash to Dock https://extensions.gnome.org/extension/307/dash-to-dock/ 把dash栏变成mac那样子的dock栏\nDash to Panel https://extensions.gnome.org/extension/1160/dash-to-panel/ 把dash栏和最上面的任务栏合并变成类似windows的任务栏\nUnblank screen saver https://extensions.gnome.org/extension/1414/unblank/ 锁屏的时候使显示器不自动黑屏\nCoverflow Alt-Tab https://extensions.gnome.org/extension/97/coverflow-alt-tab/ 多任务切换的时候提供动态特效\nCPU Power Manager https://extensions.gnome.org/extension/945/cpu-power-manager/ 在任务栏动态显示CPU的工作频率，同时可以调节CPU的工作模式\nNetSpeed https://extensions.gnome.org/extension/104/netspeed/ 在任务栏实时动态显示网速\nResource Monitor https://extensions.gnome.org/extension/1634/resource-monitor/ 在任务栏显示CPU、内存、硬盘、网速等状态信息\nPixel Saver https://extensions.gnome.org/extension/723/pixel-saver/ 把程序的菜单栏和最顶端的任务栏合并用于节省屏幕空间\nHide Top Bar https://extensions.gnome.org/extension/545/hide-top-bar/ 隐藏最顶端的任务栏\n桌面主题美化 这里只推荐几个热门的主题，其他的可以在下面的网址上面慢慢找，一般github地址都有自动安装脚本。 gnome主题下载官网 https://www.gnome-look.org/\nvimx系列 主题github地址 https://github.com/vinceliuice/vimix-gtk-themes\n图标github地址 https://github.com/vinceliuice/vimix-icon-theme\ngrub引导界面下载地址 https://www.gnome-look.org/p/1009236/\n苹果mac系风格 Mojava主题github地址 https://github.com/vinceliuice/Mojave-gtk-theme\nSierra主题github地址 https://github.com/vinceliuice/Sierra-gtk-theme\n手动离线安装 在无法使用浏览器插件一键安装的时候，我们也可以进行手动下载安装包进行离线安装，需要注意还是要提前安装好gnome-tweaks。\n# 查看自己的gnome-shell版本 $ gnome-shell --version GNOME Shell 3.34.4 # 这里建议使用需要登录gnome桌面的用户操作 # 在用户的家目录下.local/share/gnome-shell/新建一个extensions的文件夹 $ cd ~/.local/share/gnome-shell/ $ mkdir extensions # 将下载解压好的插件复制到extensions目录下 $ cd extensions/ $ cp -r ~/Downloads/dash-to-paneljderose9.github.com.v29.shell-extension/ ./ # 进入插件目录中修改metadata.json文件 # 需要保证插件目录中的metadata.json文件的uuid属性值和插件存放的文件夹一致 $ mv dash-to-paneljderose9.github.com.v29.shell-extension/ dash-to-panel $ cd dash-to-panel/ $ cat metadata.json { \u0026#34;_generated\u0026#34;: \u0026#34;Generated by SweetTooth, do not edit\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;An icon taskbar for the Gnome Shell. This extension moves the dash into the gnome main panel so that the application launchers and system tray are combined into a single panel, similar to that found in KDE Plasma and Windows 7+. A separate dock is no longer needed for easy access to running and favorited applications.\\n\\nFor a more traditional experience, you may also want to use Tweak Tool to enable Windows \u0026amp;amp;amp;amp;amp;gt; Titlebar Buttons \u0026amp;amp;amp;amp;amp;gt; Minimize \u0026amp;amp;amp;amp;amp;amp; Maximize.\\n\\nFor the best support, please report any issues on Github. Dash-to-panel is developed and maintained by @jderose9 and @charlesg99.\u0026#34;, \u0026#34;extension-id\u0026#34;: \u0026#34;dash-to-panel\u0026#34;, \u0026#34;gettext-domain\u0026#34;: \u0026#34;dash-to-panel\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Dash to Panel\u0026#34;, \u0026#34;shell-version\u0026#34;: [ \u0026#34;3.18\u0026#34;, \u0026#34;3.20\u0026#34;, \u0026#34;3.22\u0026#34;, \u0026#34;3.24\u0026#34;, \u0026#34;3.26\u0026#34;, \u0026#34;3.28\u0026#34;, \u0026#34;3.30\u0026#34;, \u0026#34;3.34\u0026#34;, \u0026#34;3.32\u0026#34;, \u0026#34;3.36\u0026#34; ], \u0026#34;url\u0026#34;: \u0026#34;https://github.com/jderose9/dash-to-panel\u0026#34;, \u0026#34;uuid\u0026#34;: \u0026#34;dash-to-panel\u0026#34;, \u0026#34;version\u0026#34;: 29 } # 然后我们需要重启gnome-shell桌面，同时按下ALT+F2，然后在弹出的输入框中输入r再回车即可重启 # 此外还有gnome-tweaks也需要重启 ","permalink":"http://localhost:9011/20200110-gnome-extension-recommand/","summary":"\u003cp\u003e在CentOS7上使用Gnome3.26版本进行安装测试，大多数的插件都可以正常运行，主题的工作情况良好，下面的推荐插件全部同时启用会发生冲突，实际需要启用那些插件大家可以根据自己的实际需求进行选择。\u003c/p\u003e\n\u003cp\u003e注：grub界面的引导美化在CentOS7上面暂未成功。\u003c/p\u003e","title":"常用的高效gnome插件以及主题的下载地址"},{"content":"在CentOS上使用yum安装chrome浏览器，实测在CentOS7和CentOS8中都可以正常操作，需要保证能够直接访问谷歌。\n建立一个yum仓库\ncat \u0026gt;\u0026gt; /etc/yum.repos.d/google-chrome.repo \u0026lt;\u0026lt;EOF [google-chrome] name=google-chrome baseurl=https://dl.google.com/linux/chrome/rpm/stable/\\$basearch enabled=1 gpgcheck=1 gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub EOF 使用yum命令进行安装\nyum repolist yum install google-chrome-stable # 如果出现gpgkey认证或检验失败，可以添加 --nogpgcheck参数，效果和上面的把yum仓库的`gpgcheck`选项改为`0`一样，只不过前面是仅这次生效，后者是永久生效。 yum install google-chrome-stable --nogpgcheck ","permalink":"http://localhost:9011/20200105-centos-install-chrome/","summary":"\u003cp\u003e在CentOS上使用yum安装chrome浏览器，实测在CentOS7和CentOS8中都可以正常操作，需要保证能够直接访问谷歌。\u003c/p\u003e","title":"在CentOS中使用yum安装chrome浏览器"},{"content":"shutter和flameshot都是Linux下很强大的截图软件，之前在Ubuntu上面一直使用flameshot感觉不错，现在换到了CentOS7，改用NUX_Desktop源来安装shutter。\nnux desktop仓库官网： http://li.nux.ro/repos.html\n需要注意的是nux源目前只支持红帽系Linux的6和7系列，暂未支持8系的红帽Linux\n安装步骤十分简单，需要先启用epel源，再导入nux源，接着更新yum源，最后安装shutter即可。\nsudo yum -y install epel-release sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm sudo yum repolist sudo yum install shutter ","permalink":"http://localhost:9011/20200103-centos7-nux-install-shutter/","summary":"\u003cp\u003eshutter和flameshot都是Linux下很强大的截图软件，之前在Ubuntu上面一直使用flameshot感觉不错，现在换到了CentOS7，改用NUX_Desktop源来安装shutter。\u003c/p\u003e","title":"在CentOS中使用nux源安装shutter"},{"content":"本文主要记录了在CentOS7.7系统（物理机）下使用红帽官方的RDO工具进行OpenStack的all-in-one模式部署。关于宿主机的，在之前的文章中已经做过了详细的介绍和测试，这里不再赘述，有兴趣的同学可以点击这里查看。\nRDO简介 我们先来看一下官网的介绍：\nRDO is a community of people using and deploying OpenStack on CentOS, Fedora, and Red Hat Enterprise Linux. We have documentation to help get started, mailing lists where you can connect with other users, and community-supported packages of the most up-to-date OpenStack releases available for download.\n简单来说RDO就是红帽推出的一个针对红帽系的操作系统（RHEL、CentOS、Fedora）的简化OpenStack安装的工具，定位类似于RHEL和Fedora的关系，是属于社区维护的免费版（也有类似RHEL的付费支持版），目前支持到最新的Stein版本的OpenStack的自动安装，而OpenStack的开发进度目前是处于Train版本。\nRDO和devstack的区别\ndevstack是由openstack官方进行维护的，而RDO是由红帽发起的，通过开源社区进行维护 devstack支持ubuntu、红帽系和opensuse三大主流的Linux操作系统，而RDO只支持红帽系 红帽系Linux部署OpenStack个人推荐使用RDO 部署安装 官网的安装说明链接： https://www.rdoproject.org/install/packstack/\n硬件要求 部署仅支持64位操作系统，要求系统内存最少为16G，最少有一个网卡，最好为静态IP，且需要开启CPU虚拟化支持。\n环境语言设置 如果CentOS的默认语言选择的是非英语，需要在 /etc/environment 文件中修改：\nLANG=en_US.utf-8 LC_ALL=en_US.utf-8 RDO库要求 Enabling the Optional, Extras, and RH Common channels on RHEL\nIf using RHEL it is assumed that you have registered your system using Red Hat Subscription Management and that you have the rhel-7-server-rpms repository enabled by default. RDO also needs the Optional, Extras, and RH Common channels to be enabled:\n$ sudo subscription-manager repos --enable=rhel-7-server-optional-rpms \\ --enable=rhel-7-server-extras-rpms --enable=rhel-7-server-rh-common-rpms The Optional channel does not exist in CentOS or Scientific Linux. The required packages are included in the main repositories for those distributions. Extras is enabled by default on CentOS 7.\n对于CentOS7而言，只需要用到自带的主要的main和extras两个库，也就是默认的/etc/yum.repos.d/CentOS-Base.repo的[base]和[extras]两个库，有需要的同学可以自行换成国内的镜像源如阿里、网易、清华、中科大等镜像源来提高速度。\n网络设置 这里需要配置网卡为静态IP，禁用防火墙，然后把默认的网络管理工具从NetworkManager换成network。\nsudo systemctl disable firewalld sudo systemctl stop firewalld sudo systemctl disable NetworkManager sudo systemctl stop NetworkManager sudo systemctl enable network sudo systemctl start network 部署安装 # 安装OpenStack需要使用的yum仓库并确保仓库已经被正常启用 # 这里使用的是RDO支持的最新的stein版本，需要旧版本的可以在这里查找： http://rdoproject.org/repos/ sudo yum install -y centos-release-openstack-stein sudo yum-config-manager --enable openstack-stein # 更新yum源 sudo yum update -y # 安装packstack sudo yum install -y openstack-packstack # 使用packstack安装OpenStack sudo packstack --allinone 接下来的部署安装就十分简单了，只需要保证系统和网络正常，然后耐心等待，根据电脑的性能不同，安装的时间也不尽相同，我大概是花了15分钟左右就一次部署成功。\n[root@tiny-openstack ~]# sudo packstack --allinone Welcome to the Packstack setup utility The installation log file is available at: /var/tmp/packstack/20200102-115042-h_xYXR/openstack-setup.log Packstack changed given value to required value /root/.ssh/id_rsa.pub Installing: Clean Up [ DONE ] Discovering ip protocol version [ DONE ] Setting up ssh keys [ DONE ] Preparing servers [ DONE ] Pre installing Puppet and discovering hosts\u0026#39; details [ DONE ] Preparing pre-install entries [ DONE ] Setting up CACERT [ DONE ] Preparing AMQP entries [ DONE ] Preparing MariaDB entries [ DONE ] Fixing Keystone LDAP config parameters to be undef if empty[ DONE ] Preparing Keystone entries [ DONE ] Preparing Glance entries [ DONE ] Checking if the Cinder server has a cinder-volumes vg[ DONE ] Preparing Cinder entries [ DONE ] Preparing Nova API entries [ DONE ] Creating ssh keys for Nova migration [ DONE ] Gathering ssh host keys for Nova migration [ DONE ] Preparing Nova Compute entries [ DONE ] Preparing Nova Scheduler entries [ DONE ] Preparing Nova VNC Proxy entries [ DONE ] Preparing OpenStack Network-related Nova entries [ DONE ] Preparing Nova Common entries [ DONE ] Preparing Neutron LBaaS Agent entries [ DONE ] Preparing Neutron API entries [ DONE ] Preparing Neutron L3 entries [ DONE ] Preparing Neutron L2 Agent entries [ DONE ] Preparing Neutron DHCP Agent entries [ DONE ] Preparing Neutron Metering Agent entries [ DONE ] Checking if NetworkManager is enabled and running [ DONE ] Preparing OpenStack Client entries [ DONE ] Preparing Horizon entries [ DONE ] Preparing Swift builder entries [ DONE ] Preparing Swift proxy entries [ DONE ] Preparing Swift storage entries [ DONE ] Preparing Gnocchi entries [ DONE ] Preparing Redis entries [ DONE ] Preparing Ceilometer entries [ DONE ] Preparing Aodh entries [ DONE ] Preparing Puppet manifests [ DONE ] Copying Puppet modules and manifests [ DONE ] Applying 192.168.100.90_controller.pp Testing if puppet apply is finished: 192.168.100.90_controller.pp [ - ] 192.168.100.90_controller.pp: [ DONE ] Applying 192.168.100.90_network.pp 192.168.100.90_network.pp: [ DONE ] Applying 192.168.100.90_compute.pp 192.168.100.90_compute.pp: [ DONE ] Applying Puppet manifests [ DONE ] Finalizing [ DONE ] ​ Installation completed successfully ** Additional information: * Parameter CONFIG_NEUTRON_L2_AGENT: You have choosen OVN neutron backend. Note that this backend does not support LBaaS, VPNaaS or FWaaS services. Geneve will be used as encapsulation method for tenant networks * A new answerfile was created in: /root/packstack-answers-20200102-115042.txt * Time synchronization installation was skipped. Please note that unsynchronized time on server instances might be problem for some OpenStack components. * File /root/keystonerc_admin has been created on OpenStack client host 192.168.100.90. To use the command line tools you need to source the file. * To access the OpenStack Dashboard browse to http://192.168.100.90/dashboard . Please, find your login credentials stored in the keystonerc_admin in your home directory. * The installation log file is available at: /var/tmp/packstack/20200102-115042-h_xYXR/openstack-setup.log * The generated manifests are available at: /var/tmp/packstack/20200102-115042-h_xYXR/manifests 登录运行 留意上面的安装完成提示，我们可以获得几个重要信息：\ndashboard的登录链接一般就是http://你用来部署OpenStack的网卡IP/dashboard 登录的账号密码文件在home目录下的keystonerc_admin文件，一般使用root账户部署，所以应该是在/root/下 部署的配置文件是/root/packstack-answers-20200102-115042.txt，根据部署的时间不同，后面的数字会不一样，这个answer-file可以用于下次继续部署，只要在部署的时候使用 --answer-file 选项即可 ","permalink":"http://localhost:9011/20200102-centos7-rdo-install-openstack/","summary":"\u003cp\u003e本文主要记录了在CentOS7.7系统（物理机）下使用红帽官方的RDO工具进行OpenStack的all-in-one模式部署。关于宿主机的，在之前的文章中已经做过了详细的介绍和测试，这里不再赘述，有兴趣的同学可以\u003ca href=\"https://tinychen.com/191212-my-hedt-intro/\"\u003e点击这里\u003c/a\u003e查看。\u003c/p\u003e","title":"在CentOS7中使用RDO安装OpenStack-Stein-AIO"},{"content":"在CentOS7中使用elrepo源来进行英伟达显卡驱动的安装。\n首先是elrepo源的官网地址： http://elrepo.org/tiki/tiki-index.php\nELRepo 仓库是基于社区的用于企业级 Linux 仓库，提供对 RedHat Enterprise (RHEL) 和 其他基于 RHEL的 Linux 发行版（CentOS、Scientific、Fedora 等）的支持。\nELRepo 聚焦于和硬件相关的软件包，包括文件系统驱动、显卡驱动、网络驱动、声卡驱动和摄像头驱动等。\n如果觉得网络不稳定使用官方的elrepo源慢的话可以尝试一下指定镜像源为中科大的镜像源。\nsudo yum install https://www.elrepo.org/elrepo-release-7.0-4.el7.elrepo.noarch.rpm sudo yum install kmod-nvidia 到这里就算是安装完成了，之后也可以通过yum来对驱动进行升级，同时英伟达官网也有提供安装驱动，具体使用哪个看同学们自己的实际需求了。\n查看安装好驱动之后的显卡信息：\nnvidia-smi ","permalink":"http://localhost:9011/20200102-centos7-elrepo-install-nvidia-driver/","summary":"\u003cp\u003e在CentOS7中使用elrepo源来进行英伟达显卡驱动的安装。\u003c/p\u003e","title":"在CentOS7中使用elrepo源安装Nvidia显卡驱动"},{"content":"在Linux中对Python的pip进行更新和镜像源替换为国内的镜像源。理论上操作对于Linux系统均适用，Python2和Python3也同样适用，不过Python2将在2020年1月1日停止支持，这里需要提醒一下大家记得尽快升级迁移自己项目和系统中使用的Python版本。\n本文的操作会使用CentOS7+Python2和Ubuntu18.04.3+Python3来进行演示。\n需要注意的是，如果系统中存在多个不同版本的pip，需要确定pip命令所指向的是否是需要更新的pip版本。\n升级pip 直接使用pip升级pip sudo python -m pip install --upgrade pip # 或者适用这个命令，两个的效果是一样的 sudo pip install -U pip 重新安装pip 如果pip出现了异常状况损坏了导致不能正常使用，这时候我们可以考虑尝试一下重新安装pip，需要注意的是python-pip默认指向的是python2的pip，python3-pip才是python3的pip，还是需要注意一下版本的问题。\n# Ubuntu18.04.3 sudo apt list | egrep \u0026#39;python.*pip\u0026#39; sudo apt remove python3-pip sudo apt install python3-pip # CentOS7.7 sudo yum list | egrep \u0026#39;python.*pip\u0026#39; sudo yum remove python3-pip sudo yum install python3-pip 重新安装完成之后，再次使用上面1.1的命令对pip进行升级即可。\n使用Pypa官网安装文件重装pip 如果上面两种方法都不行，可以尝试一下使用Pypa官网的文件来进行pip的重新安装。\n我们使用curl或者wget命令来下载安装文件，并使用对应版本的python进行安装。\n# 使用curl将文件另存为get-pip.py到本地 curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py # 或者直接使用wget下载 wget https://bootstrap.pypa.io/get-pip.py # 使用python强制重新安装pip python get-pip.py --force-reinstall 使用这种方法安装的pip直接就是最新版本，不需要自己再额外手动升级。\n更换pip源 国内pip镜像源 豆瓣 https://pypi.douban.com/simple/ 清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/ 中科大 https://pypi.mirrors.ustc.edu.cn/simple/ 阿里云 https://mirrors.aliyun.com/pypi/simple/ 这四个是国内比较常用且稳定的镜像源，具体使用哪个大家可以根据自己的实际网络环境进行选择。\n永久切换镜像源 永久切换镜像源需要我们在用户的家目录下新建一个.pip的隐藏文件夹并在里面新建一个pip.conf文件用于指定镜像源。这里我们以中科大的镜像源为例。\nmkdir ~/.pip/ cat \u0026gt; ~/.pip/pip.conf \u0026lt;\u0026lt;EOF [global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple/ [install] trusted-host = https://pypi.tuna.tsinghua.edu.cn/ EOF 我们安装一个库来看一下速度如何：\n临时指定镜像源 如果需要在某个安装中指定镜像源，只需要添加-i参数并加上镜像源的地址即可。\n# 使用pip指定从清华镜像源中安装pandas库 sudo pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple/ 可以看到图中的下载速度几乎是可以达到满速的。\n","permalink":"http://localhost:9011/20191227-pip-source-upgrade/","summary":"\u003cp\u003e在Linux中对Python的pip进行更新和镜像源替换为国内的镜像源。理论上操作对于Linux系统均适用，Python2和Python3也同样适用，不过Python2将在2020年1月1日停止支持，这里需要提醒一下大家记得尽快升级迁移自己项目和系统中使用的Python版本。\u003c/p\u003e\n\u003cp\u003e本文的操作会使用CentOS7+Python2和Ubuntu18.04.3+Python3来进行演示。\u003c/p\u003e","title":"Linux中升级pip和更换pip镜像源"},{"content":"双系统情况下的Ubuntu18.04更改grub菜单引导选项和等待时间。此处使用的是UEFI引导的物理机作为示范，而非是使用传统的BIOS进行引导的虚拟机。实际的各种引导情况可能会比较复杂，需要自己根据实际情况来进行判断。\n对于修改grub参数，不建议直接对/boot/grub/grub.cfg文件进行修改，一般来说是对/etc/default/grub进行修改，然后执行update-grub命令重新生成grub引导文件即可。\nsudo vim /etc/default/grub 然后这里有四个参数是我们需要注意修改的：\n# 这个是默认的启动项，一般来说默认为0，即Ubuntu的默认启动项,1则是Ubuntu的高级启动选项，包括了除了默认内核之外的其他内核，以及对应的紧急恢复救援模式，2就是一般情况下双系统的第二个系统，这里就是windows，因为grub实际上是可以引导windows启动的，所以在更新grub文件的时候会检索到磁盘上面的引导文件并且把windows加入到引导列表中来。 GRUB_DEFAULT=2 # 这个参数需要注释掉保证下面的两个参数生效 #GRUB_TIMEOUT_STYLE=hidden # 这两个参数就是grub引导界面在登录的时候等待时间 GRUB_TIMEOUT=5 GRUB_RECORDFAIL_TIMEOUT=5 我们查看/boot/grub/grub.cfg文件下面的内容，可以从代码中看出，这里引导时间的参数主要是GRUB_RECORDFAIL_TIMEOUT。\n不同的配置文件可以参数不太一样，大家可以查看一下自己的grub文件，再尝试一下这两个参数。\n接下来只要执行sudo update-grub就可以更新grub引导参数了。\ntinychen@Tiny-Studio:~$ sudo vim /etc/default/grub tinychen@Tiny-Studio:~$ sudo update-grub Sourcing file `/etc/default/grub\u0026#39; Generating grub configuration file ... Found linux image: /boot/vmlinuz-5.3.0-26-generic Found initrd image: /boot/initrd.img-5.3.0-26-generic Found linux image: /boot/vmlinuz-5.3.0-25-generic Found initrd image: /boot/initrd.img-5.3.0-25-generic Found Windows Boot Manager on /dev/nvme2n1p2@/efi/Microsoft/Boot/bootmgfw.efi Adding boot menu entry for EFI firmware configuration done 对于系统中存在的update-grub和update-grub2两个命令，我们可以看到实际上都是指向同一个文件，现在的grub引导应该都是使用了grub2，早年的grub1应该已经被彻底换下来了。\n# 查看grub版本，可以看到确实是在使用grub2了 sudo grub-install -V ","permalink":"http://localhost:9011/20191224-ubuntu-change-grub/","summary":"\u003cp\u003e双系统情况下的Ubuntu18.04更改grub菜单引导选项和等待时间。此处使用的是UEFI引导的物理机作为示范，而非是使用传统的BIOS进行引导的虚拟机。实际的各种引导情况可能会比较复杂，需要自己根据实际情况来进行判断。\u003c/p\u003e","title":"Ubuntu18.04更改grub菜单引导选项和等待时间"},{"content":"从9月份开始计划搭建一台高性能的HEDT主机来进行毕设相关工作，前后折腾了两个多月总算是基本搞定，这里写一篇文章记录一下整个硬件选型的思路、过程以及测试，同时此文也作为毕业设计的硬件平台选型搭建部分。\n用途介绍 和大多数用户搭建HEDT平台的用途不同（影视剪辑后期制作、游戏直播），这次的HEDT平台并不单纯是用作服务器用途，而是主要用来在Linux下开虚拟机（KVM虚拟化），同时兼顾个人高性能台式机娱乐用途，因此主要的预算是花在CPU、内存、硬盘上面，对于显卡的要求很低，只要能硬解4K蓝光原盘电影即可。接下来是对主要的板U、内存、固态、显卡进行分析。\n主板\u0026amp;CPU 首先是板U部分，硬性指标非常简单，需要CPU支持VT-d、VT-x这两项（AMD处理器也有对应的选项），简单来说就是CPU必须要支持硬件虚拟化，同时单核性能并不需要太强，但是核心数要尽可能多，且PCIE通道数量要尽可能多，配套的主板扩展性要尽可能好（PCIE插槽尽可能多），除了显卡之外还需要插入PCIE转NVME的转接卡、无线网卡和万兆有线网卡等设备。\n这里首先排除了绝大多数的主流桌面CPU，因为他们的核心数都比较少，即使是9900K和3900X也才分别8C16T和12C24T，AMD这边的话3950X以上勉强够用，再往上的3960和3970当时还没有推出，当然就算是出了我也买不起，二代的线程撕裂者有设计缺陷，内存表现实在令人担忧，因此不作考虑，INTEL这边的话就是挤了好几代牙膏的HEDT的酷睿X平台，9960X或者9980XE算是够用。\n再然后看向服务器平台，由于主要考虑的是二手硬件（全新的买不到也买不起），这里就是英特尔的主场了，因为AMD早年的不给力，x86服务器领域基本就是英特尔一直独秀，某宝上能买到的二手硬件也基本是英特尔的，主要还是大量的双路E5洋垃圾和部分的3647平台的QS、ES处理器（正式版非常贵），AMD这边虽然有少量的霄龙EPYC处理器，但是总的来说选择比较少，而且这方面的评测和教程都很少，个人并不是很想在这方面冒险，万一翻车了要耽误很长一段时间，而且还不好出二手。\n再加上价格和稳定性的因素，AMD的选择就可以说被淘汰掉了，因为毕设需要用到的Linux系统主要还是CentOS7，内核版本是3.x系列，旧版本的Linux内核对AMD的支持并不算很好，综合各方面的因素考虑，AMD在这次的装机中并不算是特别优秀的选择，因此只能淘汰掉。\n而在INTEL这边，高端的intel的HEDT平台不仅本身的CPU很贵，由于不能使用服务器内存，整机的价格预算还会上升很多，到这里就可以得出结论，主要进入备选列表的板U套餐就是二手的双路E5服务器套餐、铂金3647服务器平台这两种选择，这两个平台的CPU都有很多的PCIE通道数，能使用比家用内存条便宜很多的二手服务器内存（REG ECC内存）。\n内存 对于内存的要求不算很高，容量至少需要128G，价格尽可能低一些，频率不需要太高（大多数服务器的CPU支持的最高内存频率为2933），时序不需要太低，没有大型游戏的需求，而且大多数服务器的CPU支持四通道内存（线程撕裂者支持四通道），铂金处理器还支持六通道内存（得益于CCX模块化设计，EPYC霄龙处理器还有八通道内存），如果是双路CPU的话，支持的内存通道则翻倍，多通道内存可以有效提升内存的读写性能。此外，ECC内存支持内存纠错，可以有效降低运行时候的蓝屏死机等情况，比较适合我这种24小时不关机的情况。（最重要的还是便宜，淘宝上面的32G 2666MHz的三星REG内存条单条才五百多，2400MHz的镁光/海力士只要四百多）\n硬盘 这次的虚拟机硬盘主要还是以虚拟硬盘为主，使用一般的PCIe3.0x4的NVME固态就能满足需求，由于基本都是测试用途，并没有上线使用所产生的大量文件读写存储需求，预计使用NVME固态进行虚拟化，普通的机械硬盘进行备份即可，并没有打算直通固态或者是使用HBA卡或阵列卡等组合。\n显卡 考虑到以后可能会进行黑苹果安装，这里还是尽可能地选择了A卡，而且在二手的情况来看，千元以下的价位基本就是580的天下，其他的选择都没啥性价比。而且580足以满足硬解4K蓝光，外接4KHDR显示器等需求，由于不玩大型游戏，不剪辑，不用CUDA，所以就没有考虑N卡。当然最主要的原因还是为后期的黑苹果做好准备。\n小结 总的来说，由于预算的限制，选择二手的intel服务器平台是目前对于我来说综合各方面因素比较合适的选择，在128G内存这个选择下，使用服务器内存虽然牺牲了一部分的高频和低延迟特性，但是换来了整体预算的极大程度降低和ECC纠错功能。\n不选择AMD和INTEL两家的主流HEDT平台（酷睿X和线程撕裂者）主要还是太贵了，不选择EPYC是因为二手流出配件太少，选择余地不大，而且没有太好的折腾方案。\n显卡和网卡都是选择黑苹果兼容免驱的硬件方便后期折腾黑苹果，而网卡则由于只是一台主机，暂时没有和外部机器大量高速交换数据的需求，只需要预留一个PCIEx8的插槽用来后期升级万兆网络或者是IB、RDMA网络等。\n铂金8167M+C621 在因为体积和性能的原因淘汰了老双路E5平台之后，确定了购入铂金3647平台外加华擎的永擎C621D8A主板（永擎是华擎的服务器主板产品线），处理器买的是一块据说是华为云定制的处理器，铂金8167M，26核心52线程，待机2.0睿频2.4全核2.4，支持六通道内存，有一大堆的PCIE通道，整体来说还是非常不错的，但是败笔就在这块永擎的主板。\n▼实拍看一下巴掌大的CPU\n整机平台搭建好之后在自己做的测试平台上是这样的：\n上到机箱里面是这样的\n这套平台用起来还是有着挺多不错的地方的：\n主板是有redhat认证的，也就是说会有RHEL的驱动提供，不需要担心驱动的支持问题（这在Linux服务器上尤为重要） 主板自带4个X722千兆光口网卡、1个IPMI管理网口，在对网络要求比较高的多开虚拟机中有一定的优势 主板有板载集显且带VGA接口，不需要外接独显就能连接显示器DEBUG 主板自带超多SATA接口，在不使用扩展卡的情况下就可以接入10多个SATA硬盘 CPU支持6通道内存，PCIE通道数和插槽足够多 CPU核心数非常多，十分适合多开虚拟机进行核心分配 上面的这些优点基本上都是服务器平台有的一些特性，那么下面来说一下我最终把它换掉的原因：\n非常孱弱的单核性能，由于这颗CPU的主频只有2.0，即便是睿频也只有2.4，稍微吃一点单核的应用在上面运行起来都会非常吃力，对于个人日常使用来说还是有一定的影响； 整套平台的Windows10支持比较一般，由于是服务器平台，主要还是支持的redhat和windows server系统较好，win10的驱动都能找到，但是总感觉差点意思； 主板的24pin供电和CPU的8pin供电位置调转了过来，和一般的家用ATX主板刚好相反，导致在使用普通机箱的时候一般电源的24pin线材不够长且理线很麻烦； 主板虽然有两个nvme的硬盘接口，但是居然只是PCIEx1的带宽，实在鸡肋； 3647平台的CPU安装方式非常奇葩，由于CPU太大，需要先把CPU装到散热器上，再把散热器装到主板上，且3647平台的散热器非常少，要么很贵，要么很丑。 所以最后基于各种考虑，还是把这套平台换掉了。\nW2295+X299 配置简介 最终在闲鱼上看到了一套整体价格和上面说的相当，而更适合我使用的志强W-2295 ES处理器加x299芯片组的主板，需要注意的是，正常的X299主板是并不支持REG ECC内存的，而正常的W2295处理器也是不能用这个x299芯片组的，但是由于这块U是ES不显版本，加上10代酷睿X的HEDT平台更新了之后带来的主板微码更新，使得可以在x299平台上使用这颗不显的ES版W-2295和REG ECC内存。\n下面是整套平台的汇总配置：\n配件 型号 购买平台 价格 CPU Xeon W-2295 ES不显 18核36线程 闲鱼 4750不包邮 散热器 采融ARTIST EVO 3 京东 289包邮 主板 华硕 TUF x299 mark2 闲鱼 899不包邮 内存 (SK海力士 32G DDR4 2R*4 2400MHz)x4 淘宝 1728包邮 显卡 蓝宝石RX580 8G超白金 闲鱼 750包邮 硬盘1 三星PM981 512G（5月份购买） 淘宝 465.9包邮 硬盘2 雷克沙NM610 1T 天猫 569包邮 硬盘3 雷克沙NM610 1T 京东 565包邮 硬盘4 西部数据 Elements 12TB 移动3.5寸机械硬盘拆盘 亚马逊 1436.5包邮 硬盘5 西部数据 Elements 12TB 移动3.5寸机械硬盘拆盘 亚马逊 1321.38包邮 固态散热 利民TR-M2固态散热器 京东 39.9包邮 转接卡 佳翼iHyper M2 PCIEx16拆分4NVME转接卡 天猫 149包邮 无线模块 博通BCM943602CS 闲鱼 200包邮 电源 安钛克HCG850W 金牌plus 十年保修包换 闲鱼 488不包邮 机箱 恩杰H510 闲鱼 300包邮 风扇1 (Arctic F12 PWM PST)x2 京东 79.8包邮 风扇2 (Arctic F14 PWM)x2 淘宝 79.5包邮 RGB (乔思伯NC-3内存马甲)x4+VC-3显卡支架+RGB遥控器 闲鱼 268包邮 邮费 CPU+主板+电源 顺丰 大约100 总计 14477.98 为了控制预算，大多数都是通过闲鱼购买的成色9成新以上或者是全新的配件，而在关键的存储方面则是在天猫和京东已经亚马逊购买带保修的硬盘，以确保数据的安全。\n由于购买的时间从十月份到双十二，中间跨度很大，一些价格的波动也十分大，比如内存现在又开始小幅度涨价，而显卡则是在一路降价，其他的基本都没有太大的波动。\n整机的全部价格下来在14500左右，其中硬盘占了大概有4000左右，但是也是得到了2.5T的NVME固态存储和24T的机械硬盘存储空间，相对来说还是不错的。\n接下来对一些主要的部件进行介绍：\nCPU CPU是在CHH上面看到的一位大佬评测的一款比较特别的处理器，先上CHH的链接：\nhttps://www.chiphell.com/thread-2102461-1-1.html\n然后通过INTEL官网也可以查询到这块处理器的相关信息：\nhttps://ark.intel.com/content/www/cn/zh/ark/products/198011/intel-xeon-w-2295-processor-24-75m-cache-3-00-ghz.html\n这里我们可以看到，这是一块19年第四季度才上市的工作站处理器，看起来应该是W-2195的升级版，依旧是14nm制程，18核36线程，165W的TDP，主频3.0G，睿频4.6G，还有一个特殊的英特尔® 睿频加速 Max 技术 3.0技术可以让几个核心能到4.8G。\n英特尔® 睿频加速 Max 技术 3.0 识别处理器上性能最佳的内核，同时通过提高利用电源和散热器空间时所必需的频率，提高这些内核的性能。英特尔® 睿频加速 Max 技术 3.0 的频率就是在这种模式下运行的CPU的时钟频率。\n支持ECC内存，频率最高是2933，有48条PCIE通道，刚好够用。\n主板 虽然是X299的主板基本都能点亮，但是据卖家描述，对ECC内存支持最好的就是华硕的板子，所以在闲鱼上面找到了一块899的华硕TUF X299 mark2的主板，虽然只有主板和挡板，但是成色还算不错，查询了一下保修到期时间是2024年的11月，相对而言还算是OK。\n从官网的介绍图我们可以看到主板的设计布局还是相对比较合理的，尤其是较多的USB接口和在PCIE通道插槽的分配设置上比之前的永擎C621D8A来说要对个人用户好很多。\n查询官网我们可以看到PCIE通道的分配情况如下\n由于插上了显卡之后，第一条X16插槽下面的X1插槽和X4插槽都用不了，第二条X16插槽用来扩展固态，下面的X1插槽用来扩展无线模块，最下面的看起来是X16实际上只有X8带宽的插槽用来扩展万兆网卡，正好全部插满，刚刚好够用。\n内存 内存这里在淘宝上找了一家专门卖服务器拆机件和二手服务器的老店，买的是海力士的2400MHz的2R*4的32G单条，一共四条，由于X299可以超频，后面轻松超到2933MHz使用。\n硬盘 硬盘这里比较特殊，之前5月份装机的时候买了一块三星的PM981，性能放到现在来说也还是非常强悍的，然后在双十一期间又买了两块雷克沙的NM610 1T版本，其中三星的PM981用来装宿主机的Linux系统，一块NM610用来装Windows，另一块NM610用来装虚拟机的镜像。\n由于主板只有两个NVME的固态位，且其中一个是非常奇葩的竖装方式，然后卖家也没有这个竖装支架，所以相当于只能装一块固态，因此上淘宝买了一块佳翼的PCIE转接卡，它可以把一条X16的插槽拆分成4个X4的插槽（需要主板支持PCIE拆分功能），然后就可以插4块NVME的固态了。需要注意的是转接卡本身并不支持RAID，支持RAID的转接卡太贵了。\n然后在黑五期间购入了两块西部数据的Elements的12T的移动硬盘，一块拆盘用，另一块暂时不拆放着备用，机械硬盘的主要用途是存储蓝光电影以及备份各种文件。\n拆开之后我们可以看到里面的是12TB的氦气降速盘（灯光问题拍出来的照片有点偏紫，实际硬盘是白色的），可以直接接入电脑使用，并不需要屏蔽3.3V阵脚。\n上机测试 接下来上机测试一下：\n由于是ES版的处理器，CPU表面并没有标明型号，不过由于同样都是2066阵脚，这里可以使用x299平台点亮，前提是需要把主板升级到最新的BIOS。\nCPU-Z\u0026amp;GPU-Z 然后我们使用CPU-Z进行查看，无法识别出具体的型号：\n顺便进行跑分测试：\nGPU-Z这边倒是没有什么大问题，满血版的580超白金，后期折腾黑苹果的时候可以直接免驱使用。\nHWinfo 接着我们打开HWinfo，这个软件倒是可以识别出CPU的准确信息：\n同时还可以看到三块固态都工作正常：\n这里可以正确识别出CPU的睿频信息，以及AVX指令集下的最大睿频频率。\nAIDA64 接着我们使用AIDA64对内存性能和散热进行测试。\n这里我们可以看到在四通道内存的状态下，内存的读写性能相比普通的双通道内存有了很大的提升，同时这时的内存在BIOS中简单的设置后便是工作在2933MHz C16的频率下，相对来说算是中规中矩。\n接下来我们在封闭机箱内进行烤机测试，看一下整机的负载情况和散热效果：\n这里我们使用AIDA64的稳定性测试，烤机10分钟后整机功耗稳定在420W左右，正好在电源850W的一半左右，也就是理论转换率最高的占用率。\n再来看这时候整套平台的温度情况，在室温20度左右的情况下，CPU稳定在70度附近，显卡则是稳定在75度附近，整体散热情况还算是不错。\nR20 在卖家提供的R20截图中，跑到了8600+的高分，而在我的实际跑分测试中，只有7800左右，具体的原因还不太情况，可能是主板的原因或者是微码版本也或者是其他的因素，不管怎么说，将近10%的差距还是有些令人困惑的，同样的情况也出现在了CPU-Z的跑分上，虽然多核跑分相近，但是单核跑分成绩的440和卖家的520相比也是低了10%还不止。\n无线 博通BCM943602CS这块网卡好处是在黑苹果下可以实现免驱，考虑到黑苹果对无线模块的硬件兼容性之差，这就显得格外重要，同样博通的无线网卡在RedHat和Debian系的Linux系统上都有较为友好成熟的闭源驱动支持，对Linux内核的版本要求不高。不过这块网卡在windows下的驱动并不完善，2.4G无线的速率只有216M，无法发挥标称的450M速率，不过好在5G频段能够实现1.3G的无线速率，这比几乎不怎么用到的2.4G来的更加实际一些。而驱动更为完善的94360CD则是要贵上一些，具体选择哪个就见仁见智了。\n接着我们使用Windows在局域网内通过华硕AC68U(1900P)进行速度测试，2.4G的表现情况十分差劲，不仅速度很慢，只能在10MB/s左右徘徊，而且这时候的蓝牙和2.4G鼠标都受到了严重的干扰，而在5G频段下则好很多，基本在90MB/s左右徘徊，偶尔能到100MB/s左右，相比2T2R的Intel AX200要好上很多（使用WiFi5的华硕AC68U路由器只有50~60MB/s的速度），当然943602CS的价格也是比AX200要贵上很多。\n不过这里的无线主要还是在没有有线网络的场景下的补充，以及为台式机扩展蓝牙功能用来连接各种蓝牙键鼠耳机等设备，真正需要大量的文件数据传输工作还是需要使用稳定的有线千兆网络或者是万兆网络。\n鲁大师 最后当然少不了鲁大师了，这里顺便吐槽一下升级了新版的windows1909和AMD的2020年新款鸡血驱动之后，显卡的跑分反而更低了一些，也不知道是系统的问题还是驱动的问题，希望能尽快地优化好吧。\n总结 总的来说，这次的这套主机还是相当让我满意的，除了满足了我的毕业性能需求（大概同时中度运行9~12台虚拟机）以及日常的数据存储需求，还可以兼任一定的中度游戏需求和视频剪辑需求（如果后期有的话），后期如果还有升级的话应该就是加钱买更多的NVME固态，毕竟还有两个槽位空着，以及机械硬盘增加到一定数量之后再增加万兆NAS和万兆网卡了。\n当然最后还是希望AMD和INTEL的竞争能更加激烈一些，这样消费者们才能受益更多，我也才能捡到更多更好的二手洋垃圾。\n","permalink":"http://localhost:9011/20191212-my-hedt-introduction/","summary":"\u003cp\u003e从9月份开始计划搭建一台高性能的HEDT主机来进行毕设相关工作，前后折腾了两个多月总算是基本搞定，这里写一篇文章记录一下整个硬件选型的思路、过程以及测试，同时此文也作为毕业设计的硬件平台选型搭建部分。\u003c/p\u003e","title":"为本科毕业设计搭建HEDT平台的硬件选型思路及测试"},{"content":"在Win10中通过命令行开启卓越电源性能计划。\n右键任务栏的开始菜单徽标，点击Windows Powershell(管理员)，在里面输入\npowercfg -duplicatescheme e9a42b02-d5df-448d-aa00-03f14749eb61 然后回车。\n接着在电源计划中选中卓越性能。\n电源计划可以在设置中直接搜索电源或者使用小娜搜索电源，笔记本也可以直接右键任务栏的电源图标来点击电源选项进入到对应设置界面。\n实际体验上，卓越性能和高性能对于我的台式机来说基本感觉不到差异，可能是对于笔记本等设备来说用途会更大一些吧。\n","permalink":"http://localhost:9011/20191203-win10-extreperf-mode/","summary":"\u003cp\u003e在Win10中通过命令行开启卓越电源性能计划。\u003c/p\u003e","title":"win10开启卓越电源性能计划"},{"content":"CentOS7中搭建nginx+MySQL8+PHP7的LNMP环境，并且安装wordpress来作为网站服务器。\nlinux的安装非常的简单，一般的云主机厂商都可以直接部署安装，安装方法我们这里就不再赘述了。本文使用的是CentOS7.7版本。\n这里是wordpress的官网对于安装环境的要求：\nWe recommend servers running version 7.3 or greater of PHP and MySQL version 5.6 OR MariaDB version 10.0 or greater. We also recommend either Apache or Nginx as the most robust options for running WordPress, but neither is required.\n安装nginx nginx这里为了使用较新的版本，我们使用nginx官网提供的yum源来进行安装。\n# 新建一个yum repo文件 cat \u0026gt;\u0026gt; /etc/yum.repo.d/nginx.repo \u0026lt;\u0026lt; EOF [nginx] name=nginx repo baseurl=http://nginx.org/packages/mainline/centos/7/x86_64/ gpgcheck=0 enabled=1 EOF # 使用yum安装nginx yum clean all yum repolist yum install nginx -y # 启动nginx并设置开机启动 systemctl enable nginx.service systemctl start nginx.service 安装PHP7.3 centos系统源中自带的php版本较旧，这里我们需要使用epel源和remi源来安装新版本的php。截止写这篇文章的时候php官网的稳定版本是7.3,7.4版本虽然已经发布但是好像还没有到稳定版。\n# 使用yum来安装epel源 yum install epel-release # 这里使用清华镜像源的remi源 rpm -ivh https://mirrors.tuna.tsinghua.edu.cn/remi/enterprise/remi-release-7.rpm # 启用remi源中的php73 yum install yum-utils yum-config-manager --enable remi-php73 yum install php73 php-fpm php-opcache php-cli php-gd php-curl php-mysql php-intl php-mbstring php-soap php-xml php-xmlrpc php-zip 安装完成之后我们查看一下php的版本，然后建立软链接把php指向php73。\n# 查看php73的版本信息 $ php73 -v PHP 7.3.12 (cli) (built: Nov 19 2019 10:24:51) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.12, Copyright (c) 1998-2018 Zend Technologies # 确定php73的执行文件位置 $ which php73 /usr/bin/php73 # 建立软链接将php指向php73 $ ln -s /usr/bin/php73 /usr/bin/php # 查看php指令对应的版本信息 $ php -v PHP 7.3.12 (cli) (built: Nov 19 2019 10:24:51) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.12, Copyright (c) 1998-2018 Zend Technologies 安装MYSQL8 wordpress官网要求的数据库可以是MySQL或者是MariaDB，这里我们使用MySQL8。\n详细的安装教程之前已经写过博客了，需要的同学可以查看这篇文章：\nCentOS7安装MySQL\nMySQL8重置root密码\n接下来就是在mysql中创建一个数据库和用户用来给wordpress使用。\n首先我们使用root用户登录mysql，然后执行下面的命令来创建一个名为wordpress的数据库和一个wordpress的用户：\nCREATE DATABASE wordpress; CREATE USER \u0026#39;wordpress\u0026#39; IDENTIFIED BY \u0026#39;一个复杂的密码\u0026#39;; GRANT ALL PRIVILEGES ON wordpress.* TO wordpress; 这里的grant语句的授权方式和之前的mysql版本有些不太一样，还需要注意一下。\nmysql\u0026gt; create database wordpress; Query OK, 1 row affected (0.01 sec) mysql\u0026gt; CREATE USER \u0026#39;wordpress\u0026#39; IDENTIFIED BY \u0026#39;一个复杂的密码\u0026#39;; Query OK, 0 rows affected (0.01 sec) mysql\u0026gt; GRANT ALL PRIVILEGES ON wordpress.* TO wordpress; Query OK, 0 rows affected (0.00 sec) $ mysql -u wordpress -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \\g. Your MySQL connection id is 10 Server version: 8.0.18 MySQL Community Server - GPL Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. Type \u0026#39;\\c\u0026#39; to clear the current input statement. mysql\u0026gt; show databases; +--------------------+ | Database | +--------------------+ | information_schema | | wordpress | +--------------------+ 2 rows in set (0.00 sec) mysql\u0026gt; 安装wordpress 接下来的wordpress的配置就比较简单了，我们先去官网下载最新版本的wordpress，解压之后配置一下配置文件。\n# 使用wget指令下载并使用tar -zxvf参数进行解压 wget https://wordpress.org/latest.tar.gz tar -zxvf wordpress-5.3-zh_CN.tar.gz 接下来我们需要对配置文件进行编辑，按照官网所述，我们最好将wp-config-sample.php复制成wp-config.php然后再进行编辑，不复制直接编辑wp-config-sample.php也是可以自动生成新的wp-config.php，但是出于备份一份源配置文件模板的考虑，最好还是复制一下。\ncp wp-config-sample.php wp-config.php vim wp-config.php 这里我们填入前面创建的数据库用户名和密码以及对应的数据库。\n接下来这一步就是根据主机生成独一无二的密钥。这里我们使用curl命令来访问这个链接生成密钥并保持到key.txt文件中，然后把key.txt的文件里面的内容复制替换掉里面的define部分。\ncurl -s https://api.wordpress.org/secret-key/1.1/salt/ \u0026gt; key.txt 然后我们将整个解压出来的wordpress文件夹移动到我们的web服务器的目录下面，这里我使用的目录是/etc/nginx/wordpress，这里在后面的nginx配置文件中会用到。接下来我们对nginx进行配置。\n下面的是我的nginx配置文件，文件位于/etc/nginx/nginx.conf，注意里面的目录和域名要换成自己对应的目录和域名，以及这里还额外配置了https证书加密认证和http强制跳转https，具体的操作和解释已经在这篇博客（个人博客web服务器换用nginx）里面有解释，有需要的同学可以看看。\n我们使用root用户登录mysql，然后更新wordpress用户的加密方式为mysql_native_password，然后刷新配置即可。\n// 更改用户密码的加密形式 ALTER USER \u0026#39;wordpress\u0026#39;@\u0026#39;%\u0026#39; IDENTIFIED WITH mysql_native_password BY \u0026#39;你的用户密码\u0026#39;; // 刷新MySQL的系统权限相关表 FLUSH PRIVILEGES; 再次登录就会发现已经可以连接数据库并正常设置了，简单配置过后就可以开始正常运行了。\n设置过后就可以登录后台进行管理，登录的链接就是域名后面加上/wp-admin/\n使用默认主题的首页。\n这就是wordpress的LNMP版本安装配置过程，整体还是相对简单，没有太大的难度，相对于hexo而言，wordpress的安装显得复杂很多，整体也可能被人诟病十分臃肿，但是两者各有各的好，wordpress的插件和主题丰富程度确实要比hexo强很多，但是不管是用哪个，坚持写作才是最重要的！\n","permalink":"http://localhost:9011/20191123-lnmp-install-wordpress/","summary":"\u003cp\u003eCentOS7中搭建nginx+MySQL8+PHP7的LNMP环境，并且安装wordpress来作为网站服务器。\u003c/p\u003e","title":"搭建LNMP环境并配置wordpress网站"},{"content":"CentOS7中MySQL8.0初始root密码的设置和忘记root密码的重置方法。\n初次登录 如果是刚装好MySQL的话，它会默认生成一个随机密码在log文件中，我们使用该密码登录然后重置密码即可。\n$ sudo grep \u0026#39;temporary password\u0026#39; /var/log/mysqld.log 2019-11-22T16:40:10.133730Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: \u0026gt;KG6Ybt3%lgo $ mysql -u root -p Enter password: 重置密码可以使用这条命令：\nALTER user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;你的新密码\u0026#39;; 注意这里的'localhost'也有可能是别的参数，具体可以通过下面这条命令来进行查询：\nselect user, host, authentication_string, plugin from mysql.user; 遗忘root密码 如果是忘记了root密码，就比较麻烦了。首先我们停止mysqld服务。\nsystemctl stop mysqld.service systemctl status mysqld.service 接着我们编辑/etc/my.conf让其跳过登录密码检查。\necho skip-grant-tables \u0026gt;\u0026gt; /etc/my.conf 接着我们重启mysqld服务并登录，此时不需要使用密码。\nsystemctl restart mysqld.service systemctl status mysqld.service mysql -u root 这里我们可以看到用户的账户信息都是存储在mysql这个数据库中的user表里面的。\nshow databases; use mysql; show tables; select * from user\\G; -- \\G参数表示纵向输出格式 select host, user, authentication_string, plugin from user; -- host: 允许用户登录的ip‘位置’%表示可以远程； -- user:当前数据库的用户名； -- authentication_string: 用户密码；在mysql 5.7.9以后废弃了password字段和password()函数； -- plugin： 密码加密方式； update user set authentication_string=\u0026#39;\u0026#39; where user=\u0026#39;root\u0026#39;; select host, user, authentication_string, plugin from user; 然后我们继续编辑/etc/my.conf删除掉刚刚添加的那一行skip-grant-tables，然后重启mysql。\n然后我们登录进去，这时候还是不需要输入密码的。我们通过这条命令来进行设置新密码的操作。\nALTER user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;你的新密码\u0026#39;; 这里要注意新密码要尽可能复杂。包含大小写字母数字和符号最好，否则可能会提示不符合要求。\n$ systemctl restart mysqld.service $ mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \\g. Your MySQL connection id is 8 Server version: 8.0.18 Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. Type \u0026#39;\\c\u0026#39; to clear the current input statement. mysql\u0026gt; use mysql ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement. mysql\u0026gt; show tables; ERROR 1046 (3D000): No database selected mysql\u0026gt; ALTER user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;centos7\u0026#39;; ERROR 1819 (HY000): Your password does not satisfy the current policy requirements mysql\u0026gt; ALTER user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;MySQLNB8@2333\u0026#39;; Query OK, 0 rows affected (0.01 sec) mysql\u0026gt; 到这里再退出重新登录就可以看到我们设置的新密码已经生效了。\n","permalink":"http://localhost:9011/20191122-mysql8-reset-passwd/","summary":"\u003cp\u003eCentOS7中MySQL8.0初始root密码的设置和忘记root密码的重置方法。\u003c/p\u003e","title":"MySQL8重置root密码"},{"content":"ubuntu18.04中自带了python3但是缺少了pip3，这里我们使用apt来进行安装，然后使用pip3自己对自己升级，再安装对应的socks依赖包，这样在系统使用了socks代理的时候能够使用代理下载各种包，有效提高速度。\n# 使用apt安装pip3 # 需要注意的是python3-pip和python-pip是不一样的，前者是python3而后者是python2 sudo apt-get install python3-pip # 使用apt安装的版本很旧，大概是9.0+，我们将它升级到最新 sudo pip3 install --upgrade pip # 这里顺便附上卸载pip3的命令 sudo apt-get remove python3-pip # 安装socks所需要的依赖 sudo apt install qt5-qmake qtbase5-dev libqrencode-dev libappindicator-dev libzbar-dev libbotan1.10-dev sudo pip3 install pysocks 最后我们随便安装一个包来确定一下下载安装的速度\ntinychen@Tiny-Studio:~$ sudo pip3 install numpy WARNING: The directory \u0026#39;/home/tinychen/.cache/pip/http\u0026#39; or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want sudo\u0026#39;s -H flag. WARNING: The directory \u0026#39;/home/tinychen/.cache/pip\u0026#39; or its parent directory is not owned by the current user and caching wheels has been disabled. check the permissions and owner of that directory. If executing pip with sudo, you may want sudo\u0026#39;s -H flag. Collecting numpy Downloading https://files.pythonhosted.org/packages/d2/ab/43e678759326f728de861edbef34b8e2ad1b1490505f20e0d1f0716c3bf4/numpy-1.17.4-cp36-cp36m-manylinux1_x86_64.whl (20.0MB) |████████████████████████████████| 20.0MB 4.1MB/s Installing collected packages: numpy Successfully installed numpy-1.17.4 ","permalink":"http://localhost:9011/20191121-ubuntu-install-pip3/","summary":"\u003cp\u003eubuntu18.04中自带了python3但是缺少了pip3，这里我们使用apt来进行安装，然后使用pip3自己对自己升级，再安装对应的socks依赖包，这样在系统使用了socks代理的时候能够使用代理下载各种包，有效提高速度。\u003c/p\u003e","title":"在Ubuntu18.04中安装升级pip3并使用socks代理"},{"content":"在CentOS8和Ubuntu18上使用xrandr调整显示器分辨率并设置重启后不失效。\n最近在用主板自带的板载集显外接显示器的时候，最大应该是可以输出1080P的分辨率，但是由于各种原因好像不能自动识别出来，只能自己使用xrandr手动新建分辨率。\n在CentOS8和Ubuntu18.04.3上面都实操过没有问题，步骤也都基本相同，只有最后一步的文件路径不太一样。（个人猜测CentOS7上面应该和CentOS8通用）\nCentOS8操作记录 # 以下是CentOS8中的实际操作记录 # 使用xrandr查看目前支持的分辨率，这里我们可以看到只有三个，且和显示器的最佳分辨率都对不上 $ xrandr Screen 0: minimum 320 x 200, current 1024 x 768, maximum 1920 x 2048 VGA-1 connected primary 1024x768+0+0 (normal left inverted right x axis y axis) 0mm x 0mm 1024x768 60.00* 800x600 60.32 56.25 640x480 59.94 # 使用cvt命令生成对应分辨率的数据，后面的三个参数分别是横向分辨率和纵向分辨率以及刷新率 # 这里我们使用最常见的1080p 60帧 # 注意这里生成的Modeline这一行参数我们后面要用到，需要提前保存一下 $ cvt 1920 1080 60 # 1920x1080 59.96 Hz (CVT 2.07M9) hsync: 67.16 kHz; pclk: 173.00 MHz Modeline \u0026#34;1920x1080_60.00\u0026#34; 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync # 在xrandr中添加新的分辨率选项，参数直接照搬上面的即可 $ xrandr --newmode \u0026#34;1920x1080_60.00\u0026#34; 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync # 再次查看分辨率，可以看到新增加的已经在列表中了 # 额外需要注意的是下面的VGA-1选项，后面要用到，这里是用来定位显示器的（如果有多个显示器的话） $ xrandr Screen 0: minimum 320 x 200, current 1024 x 768, maximum 1920 x 2048 VGA-1 connected primary 1024x768+0+0 (normal left inverted right x axis y axis) 0mm x 0mm 1024x768 60.00* 800x600 60.32 56.25 640x480 59.94 1920x1080_60.00 (0x3ad) 173.000MHz -HSync +VSync h: width 1920 start 2048 end 2248 total 2576 skew 0 clock 67.16KHz v: height 1080 start 1083 end 1088 total 1120 clock 59.96Hz # 将刚刚生成的分辨率选项添加到VGA-1这个显示器中 $ xrandr --addmode VGA-1 \u0026#34;1920x1080_60.00\u0026#34; # 再次查看可以发现已经是正常的了 $ xrandr Screen 0: minimum 320 x 200, current 1024 x 768, maximum 1920 x 2048 VGA-1 connected primary 1024x768+0+0 (normal left inverted right x axis y axis) 0mm x 0mm 1024x768 60.00* 800x600 60.32 56.25 640x480 59.94 1920x1080_60.00 59.96 # 手动调整分辨率为1080P，这时候应该就可以感觉到分辨率的变化了 $ xrandr -s 1920x1080_60.00 $ # 为了保证重启后分辨率依旧生效，我们新建一个配置文件 # 需要注意的是文件中的Modeline这一行就是前面我们生成的参数，如果没保存的话再重新输一遍前面的cvt命令即可 [root@tiny-studio xorg.conf.d]# vim /etc/X11/xorg.conf.d/monitor.conf [root@tiny-studio xorg.conf.d]# cat /etc/X11/xorg.conf.d/monitor.conf Section \u0026#34;Monitor\u0026#34; Identifier \u0026#34;Configured Monitor\u0026#34; Modeline \u0026#34;1920x1080_60.00\u0026#34; 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync Option \u0026#34;PreferredMode\u0026#34; \u0026#34;1920x1080_60.00\u0026#34; EndSection Section \u0026#34;Screen\u0026#34; Identifier \u0026#34;Default Screen\u0026#34; Monitor \u0026#34;Configured Monitor\u0026#34; Device \u0026#34;Configured Video Device\u0026#34; EndSection Section \u0026#34;Device\u0026#34; Identifier \u0026#34;Configured Video Device\u0026#34; EndSection Ubuntu18.04.3操作记录 Ubuntu上面操作和CentOS8基本一样，除了最后的配置文件从/etc/X11/xorg.conf.d/monitor.conf变成了/etc/X11/xorg.conf.d/monitor.conf，其他的基本一样，这里就不再赘述。\n# 以下为ubuntu18.04.3上的操作记录 root@Tiny-Studio:~# root@Tiny-Studio:~# xrandr Screen 0: minimum 320 x 200, current 1024 x 768, maximum 1920 x 2048 VGA-1 connected primary 1024x768+0+0 (normal left inverted right x axis y axis) 0mm x 0mm 1024x768 60.00* 800x600 60.32 56.25 640x480 59.94 root@Tiny-Studio:~# cvt 1920 1080 60 # 1920x1080 59.96 Hz (CVT 2.07M9) hsync: 67.16 kHz; pclk: 173.00 MHz Modeline \u0026#34;1920x1080_60.00\u0026#34; 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync root@Tiny-Studio:~# xrandr --newmode \u0026#34;1920x1080_60.00\u0026#34; 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync root@Tiny-Studio:~# xrandr Screen 0: minimum 320 x 200, current 1024 x 768, maximum 1920 x 2048 VGA-1 connected primary 1024x768+0+0 (normal left inverted right x axis y axis) 0mm x 0mm 1024x768 60.00* 800x600 60.32 56.25 640x480 59.94 1920x1080_60.00 (0x306) 173.000MHz -HSync +VSync h: width 1920 start 2048 end 2248 total 2576 skew 0 clock 67.16KHz v: height 1080 start 1083 end 1088 total 1120 clock 59.96Hz root@Tiny-Studio:~# xrandr --addmode VGA-1 \u0026#34;1920x1080_60.00\u0026#34; root@Tiny-Studio:~# xrandr Screen 0: minimum 320 x 200, current 1024 x 768, maximum 1920 x 2048 VGA-1 connected primary 1024x768+0+0 (normal left inverted right x axis y axis) 0mm x 0mm 1024x768 60.00* 800x600 60.32 56.25 640x480 59.94 1920x1080_60.00 59.96 root@Tiny-Studio:~# xrandr -s 1920x1080_60.00 root@Tiny-Studio:~# vim /etc/X11/xorg.conf root@Tiny-Studio:~# cat /etc/X11/xorg.conf Section \u0026#34;Monitor\u0026#34; Identifier \u0026#34;Configured Monitor\u0026#34; Modeline \u0026#34;1920x1080_60.00\u0026#34; 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync Option \u0026#34;PreferredMode\u0026#34; \u0026#34;1920x1080_60.00\u0026#34; EndSection Section \u0026#34;Screen\u0026#34; Identifier \u0026#34;Default Screen\u0026#34; Monitor \u0026#34;Configured Monitor\u0026#34; Device \u0026#34;Configured Video Device\u0026#34; EndSection Section \u0026#34;Device\u0026#34; Identifier \u0026#34;Configured Video Device\u0026#34; EndSection root@Tiny-Studio:~# ","permalink":"http://localhost:9011/20191105-linux-use-xrandr/","summary":"\u003cp\u003e在CentOS8和Ubuntu18上使用\u003ccode\u003exrandr\u003c/code\u003e调整显示器分辨率并设置重启后不失效。\u003c/p\u003e","title":"在Linux中使用xrandr调整分辨率"},{"content":"在CentOS8上安装xrdp和vnc并在windows中使用mstsc进行远程控制。\n这里我们使用的是epel源来进行安装，需要注意的是要在防火墙中放行端口，xrdp使用的是和windows自带的mstsc远程一样的默认3389端口，如果使用vnc进行远程，则还需要额外添加5900到5905端口。\n# 安装epel源并安装xrdp和vnc yum install epel-release -y yum install xrdp -y yum install tigervnc-server -y # 启动xrdp并设置开机启动 systemctl start xrdp.service systemctl enable xrdp.service # 在防火墙中放行vnc和xrdp firewall-cmd --permanent --add-service=vnc-server firewall-cmd --permanent --add-port=3389/tcp firewall-cmd --permanent --add-port=3389/udp firewall-cmd --reload firewall-cmd --list-all 以下是安装实录。\n$ yum install xrdp 上次元数据过期检查：0:00:04 前，执行于 Thu 31 Oct 2019 04:15:46 AM CST。 依赖关系解决。 ================================================================================================================ 软件包 架构 版本 仓库 大小 ================================================================================================================ Installing: xrdp x86_64 1:0.9.11-5.el8 epel 441 k 安装弱的依赖: xrdp-selinux x86_64 1:0.9.11-5.el8 epel 21 k 事务概要 ================================================================================================================ 安装 2 软件包 总下载：461 k 安装大小：2.2 M 确定吗？[y/N]： y 下载软件包： (1/2): xrdp-selinux-0.9.11-5.el8.x86_64.rpm 13 kB/s | 21 kB 00:01 (2/2): xrdp-0.9.11-5.el8.x86_64.rpm 205 kB/s | 441 kB 00:02 ---------------------------------------------------------------------------------------------------------------- 总计 62 kB/s | 461 kB 00:07 运行事务检查 事务检查成功。 运行事务测试 事务测试成功。 运行事务 准备中 : 1/1 Installing : xrdp-selinux-1:0.9.11-5.el8.x86_64 1/2 运行脚本 : xrdp-selinux-1:0.9.11-5.el8.x86_64 1/2 Installing : xrdp-1:0.9.11-5.el8.x86_64 2/2 运行脚本 : xrdp-1:0.9.11-5.el8.x86_64 2/2 验证 : xrdp-1:0.9.11-5.el8.x86_64 1/2 验证 : xrdp-selinux-1:0.9.11-5.el8.x86_64 2/2 已安装: xrdp-1:0.9.11-5.el8.x86_64 xrdp-selinux-1:0.9.11-5.el8.x86_64 完毕！ $ yum install tigervnc-server 上次元数据过期检查：0:01:41 前，执行于 Thu 31 Oct 2019 04:15:46 AM CST。 依赖关系解决。 ================================================================================================================ 软件包 架构 版本 仓库 大小 ================================================================================================================ Installing: tigervnc-server x86_64 1.9.0-9.el8 AppStream 252 k 事务概要 ================================================================================================================ 安装 1 软件包 总下载：252 k 安装大小：688 k 确定吗？[y/N]： y 下载软件包： tigervnc-server-1.9.0-9.el8.x86_64.rpm 186 kB/s | 252 kB 00:01 ---------------------------------------------------------------------------------------------------------------- 总计 186 kB/s | 252 kB 00:01 运行事务检查 事务检查成功。 运行事务测试 事务测试成功。 运行事务 准备中 : 1/1 Installing : tigervnc-server-1.9.0-9.el8.x86_64 1/1 运行脚本 : tigervnc-server-1.9.0-9.el8.x86_64 1/1 验证 : tigervnc-server-1.9.0-9.el8.x86_64 1/1 已安装: tigervnc-server-1.9.0-9.el8.x86_64 完毕！ $ systemctl start xrdp.service $ systemctl enable xrdp.service Created symlink /etc/systemd/system/multi-user.target.wants/xrdp.service → /usr/lib/systemd/system/xrdp.service. $ systemctl status xrdp.service ● xrdp.service - xrdp daemon Loaded: loaded (/usr/lib/systemd/system/xrdp.service; enabled; vendor preset: disabled) Active: active (running) since Thu 2019-10-31 04:18:31 CST; 22s ago Docs: man:xrdp(8) man:xrdp.ini(5) Main PID: 23889 (xrdp) Tasks: 1 (limit: 42597) Memory: 1016.0K CGroup: /system.slice/xrdp.service └─23889 /usr/sbin/xrdp --nodaemon Oct 31 04:18:31 tiny-studio systemd[1]: Started xrdp daemon. Oct 31 04:18:31 tiny-studio xrdp[23889]: (23889)(139917736187328)[INFO ] starting xrdp with pid 23889 Oct 31 04:18:31 tiny-studio xrdp[23889]: (23889)(139917736187328)[INFO ] address [0.0.0.0] port [3389] mode 1 Oct 31 04:18:31 tiny-studio xrdp[23889]: (23889)(139917736187328)[INFO ] listening to port 3389 on 0.0.0.0 Oct 31 04:18:31 tiny-studio xrdp[23889]: (23889)(139917736187328)[INFO ] xrdp_listen_pp done $ firewall-cmd --permanent --add-service=vnc-server success $ firewall-cmd --permanent --add-port=3389/tcp success $ firewall-cmd --permanent --add-port=3389/udp success $ firewall-cmd --reload success $ firewall-cmd --list-all public (active) target: default icmp-block-inversion: no interfaces: ens82f0 sources: services: cockpit dhcpv6-client ssh vnc-server ports: 3389/tcp 3389/udp protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules: 需要注意的是在登录使用的时候，直接使用windows自带的mstsc输入ip地址进行远程，然后就会进入到xrdp的界面，接着再输入账号密码登录，退出的时候要先在linux中注销用户，直接退出的话用户还是没有注销，没注销的桌面用户再用别的电脑是不能登录的。\n","permalink":"http://localhost:9011/20191029-centos8-install-xrdp/","summary":"\u003cp\u003e在CentOS8上安装\u003ccode\u003exrdp\u003c/code\u003e和\u003ccode\u003evnc\u003c/code\u003e并在windows中使用\u003ccode\u003emstsc\u003c/code\u003e进行远程控制。\u003c/p\u003e","title":"在CentOS8中安装xrdp远程桌面"},{"content":"在CentOS8上使用epel源和rpmfusion源安装VLC播放器。\n我们查看官网可以看到对应的安装页面已经有比较详细的安装指导说明了。\nRHEL/CentOS 8 Use RPM Fusion for EL8. Available for x86_64, aarch64 and ppc64le This repository uses EPEL\nThe vlc-3.0x branch will be provided for EL8 Install rpmfusion-free-release-8.noarch.rpm for EL8.\n$\u0026gt; su - #\u0026gt; yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm #\u0026gt; yum install https://download1.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm #\u0026gt; yum install vlc #\u0026gt; yum install vlc-core (for minimal headless/server install) #\u0026gt; yum install python-vlc (optionals) 大致的安装步骤和上面类似，需要注意的是，开始导入的epel源和rpmfusion源使用默认源可能会比较慢，需要替换为国内镜像源的同学可以查看这篇文章。\n需要注意的是，安装vlc的时候，vlc-core会作为依赖同时被安装，同时，python-vlc这个库需要使用pip3来进行安装。所以整理之后的安装步骤变成了下面这样。\n# a安装epel库 yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm # 安装rpmfusion库 yum install https://download1.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm # 安装vlc yum install vlc vlc-core # 安装python3，否则无法使用pip3 yum install python3 # 使用pip3安装python-vlc pip3 install python-vlc sudo pip3 install packagename 代表进行全局安装，安装后全局可用。如果是信任的安装包可用使用该命令进行安装。\npip3 install --user packagename 代表仅该用户的安装，安装后仅该用户可用。处于安全考虑，尽量使用该命令进行安装。\n以下为安装实录，需要注意的是，linux版本的vlc好像对4K HDR视频的支持不太好，画面会有泛白的情况，暂时没找到设置里面的相关选项，使用smplayer+mpvplayer就可以正常播放。\n$ yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm Last metadata expiration check: 0:49:35 ago on Fri 01 Nov 2019 10:00:32 PM CST. epel-release-latest-8.noarch.rpm 6.7 kB/s | 21 kB 00:03 Package epel-release-8-7.el8.noarch is already installed. Dependencies resolved. Nothing to do. Complete! $ yum install https://download1.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm Last metadata expiration check: 0:49:57 ago on Fri 01 Nov 2019 10:00:32 PM CST. rpmfusion-free-release-8.noarch.rpm 3.4 kB/s | 11 kB 00:03 Dependencies resolved. ==================================================================================================== Package Arch Version Repository Size ==================================================================================================== Installing: rpmfusion-free-release noarch 8-0.1 @commandline 11 k Transaction Summary ==================================================================================================== Install 1 Package Total size: 11 k Installed size: 3.7 k Is this ok [y/N]: y Downloading Packages: Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : rpmfusion-free-release-8-0.1.noarch 1/1 Verifying : rpmfusion-free-release-8-0.1.noarch 1/1 Installed: rpmfusion-free-release-8-0.1.noarch Complete! $ yum install vlc RPM Fusion for EL 8 - Free - Updates 14 kB/s | 151 kB 00:11 Dependencies resolved. ==================================================================================================== Package Arch Version Repository Size ==================================================================================================== Installing: vlc x86_64 1:3.0.9-22.el8 rpmfusion-free-updates 1.9 M Installing dependencies: fftw-libs-double x86_64 3.3.5-11.el8 AppStream 992 k freeglut x86_64 3.0.0-8.el8 AppStream 191 k libdc1394 x86_64 2.2.2-10.el8 AppStream 126 k libmad x86_64 0.15.1b-24.el8 AppStream 83 k libva x86_64 2.1.0-1.el8 AppStream 92 k libvdpau x86_64 1.1.1-7.el8 AppStream 40 k ocl-icd x86_64 2.2.12-1.el8 AppStream 51 k protobuf-lite x86_64 3.5.0-7.el8 AppStream 150 k qt5-qtsvg x86_64 5.11.1-2.el8 AppStream 182 k qt5-qtx11extras x86_64 5.11.1-2.el8 AppStream 34 k libusb x86_64 1:0.1.5-12.el8 BaseOS 42 k aalib-libs x86_64 1.4.0-0.37.rc5.el8 epel 72 k fluidsynth-libs x86_64 1.1.11-6.el8 epel 214 k game-music-emu x86_64 0.6.2-3.el8 epel 155 k jack-audio-connection-kit x86_64 1.9.12-8.el8 epel 519 k libaom x86_64 1.0.0-8.20190810git9666276.el8 epel 1.6 M libass x86_64 0.14.0-4.el8 epel 110 k libcaca x86_64 0.99-0.43.beta19.el8 epel 227 k libcddb x86_64 1.3.2-30.el8 epel 79 k libchromaprint x86_64 1.4.2-6.el8 epel 54 k libdav1d x86_64 0.5.0-1.el8 epel 344 k libdvbpsi x86_64 1.3.3-1.el8 epel 105 k libebml x86_64 1.3.7-2.el8 epel 87 k libkate x86_64 0.4.1-18.el8 epel 59 k libmatroska x86_64 1.5.0-1.el8 epel 170 k libmfx x86_64 1.25-4.el8 epel 36 k libmicrodns x86_64 0.0.10-4.el8 epel 29 k libmodplug x86_64 1:0.8.9.0-9.el8 epel 171 k libtiger x86_64 0.3.4-18.el8 epel 43 k libupnp x86_64 1.8.4-3.el8 epel 115 k libvmaf x86_64 1.3.15-1.el8 epel 347 k lirc-libs x86_64 0.10.0-19.el8 epel 139 k pugixml x86_64 1.9-1.el8 epel 97 k schroedinger x86_64 1.0.11-21.el8 epel 321 k soxr x86_64 0.1.3-4.el8 epel 97 k vid.stab x86_64 1.1.0-12.20190213gitaeabc8d.el8 epel 47 k zvbi x86_64 0.2.35-9.el8 epel 424 k faad2-libs x86_64 1:2.8.8-6.el8 rpmfusion-free-updates 177 k ffmpeg-libs x86_64 4.2.1-3.el8 rpmfusion-free-updates 7.2 M libdca x86_64 0.0.6-4.el8 rpmfusion-free-updates 113 k libmpeg2 x86_64 0.5.1-17.el8 rpmfusion-free-updates 76 k live555 x86_64 2019.06.28-1.el8 rpmfusion-free-updates 404 k opencore-amr x86_64 0.1.5-7.el8 rpmfusion-free-updates 180 k vlc-core x86_64 1:3.0.9-22.el8 rpmfusion-free-updates 10 M vo-amrwbenc x86_64 0.1.3-8.el8 rpmfusion-free-updates 77 k x264-libs x86_64 0.157-12.20190717git34c06d1.el8 rpmfusion-free-updates 651 k x265-libs x86_64 3.1.2-1.el8 rpmfusion-free-updates 1.9 M xvidcore x86_64 1.3.5-5.el8 rpmfusion-free-updates 268 k Transaction Summary ==================================================================================================== Install 49 Packages Total download size: 31 M Installed size: 121 M Is this ok [y/N]: y Downloading Packages: (1/49): libdc1394-2.2.2-10.el8.x86_64.rpm 86 kB/s | 126 kB 00:01 (2/49): libmad-0.15.1b-24.el8.x86_64.rpm 401 kB/s | 83 kB 00:00 (3/49): freeglut-3.0.0-8.el8.x86_64.rpm 96 kB/s | 191 kB 00:01 (4/49): libva-2.1.0-1.el8.x86_64.rpm 212 kB/s | 92 kB 00:00 (5/49): libvdpau-1.1.1-7.el8.x86_64.rpm 83 kB/s | 40 kB 00:00 (6/49): protobuf-lite-3.5.0-7.el8.x86_64.rpm 235 kB/s | 150 kB 00:00 (7/49): fftw-libs-double-3.3.5-11.el8.x86_64.rpm 303 kB/s | 992 kB 00:03 (8/49): qt5-qtx11extras-5.11.1-2.el8.x86_64.rpm 177 kB/s | 34 kB 00:00 (9/49): ocl-icd-2.2.12-1.el8.x86_64.rpm 35 kB/s | 51 kB 00:01 (10/49): qt5-qtsvg-5.11.1-2.el8.x86_64.rpm 351 kB/s | 182 kB 00:00 (11/49): aalib-libs-1.4.0-0.37.rc5.el8.x86_64.rpm 130 kB/s | 72 kB 00:00 (12/49): fluidsynth-libs-1.1.11-6.el8.x86_64.rpm 256 kB/s | 214 kB 00:00 (13/49): game-music-emu-0.6.2-3.el8.x86_64.rpm 259 kB/s | 155 kB 00:00 (14/49): jack-audio-connection-kit-1.9.12-8.el8.x86_64.rpm 308 kB/s | 519 kB 00:01 (15/49): libusb-0.1.5-12.el8.x86_64.rpm 16 kB/s | 42 kB 00:02 (16/49): libass-0.14.0-4.el8.x86_64.rpm 113 kB/s | 110 kB 00:00 (17/49): libaom-1.0.0-8.20190810git9666276.el8.x86_64.rpm 451 kB/s | 1.6 MB 00:03 (18/49): libcddb-1.3.2-30.el8.x86_64.rpm 56 kB/s | 79 kB 00:01 (19/49): libchromaprint-1.4.2-6.el8.x86_64.rpm 202 kB/s | 54 kB 00:00 (20/49): libdav1d-0.5.0-1.el8.x86_64.rpm 574 kB/s | 344 kB 00:00 (21/49): libdvbpsi-1.3.3-1.el8.x86_64.rpm 150 kB/s | 105 kB 00:00 (22/49): libebml-1.3.7-2.el8.x86_64.rpm 225 kB/s | 87 kB 00:00 (23/49): libcaca-0.99-0.43.beta19.el8.x86_64.rpm 66 kB/s | 227 kB 00:03 (24/49): libkate-0.4.1-18.el8.x86_64.rpm 182 kB/s | 59 kB 00:00 (25/49): libmatroska-1.5.0-1.el8.x86_64.rpm 454 kB/s | 170 kB 00:00 (26/49): libmfx-1.25-4.el8.x86_64.rpm 106 kB/s | 36 kB 00:00 (27/49): libmicrodns-0.0.10-4.el8.x86_64.rpm 76 kB/s | 29 kB 00:00 (28/49): libmodplug-0.8.9.0-9.el8.x86_64.rpm 507 kB/s | 171 kB 00:00 (29/49): libtiger-0.3.4-18.el8.x86_64.rpm 116 kB/s | 43 kB 00:00 (30/49): libupnp-1.8.4-3.el8.x86_64.rpm 234 kB/s | 115 kB 00:00 (31/49): libvmaf-1.3.15-1.el8.x86_64.rpm 420 kB/s | 347 kB 00:00 (32/49): lirc-libs-0.10.0-19.el8.x86_64.rpm 136 kB/s | 139 kB 00:01 (33/49): pugixml-1.9-1.el8.x86_64.rpm 102 kB/s | 97 kB 00:00 (34/49): schroedinger-1.0.11-21.el8.x86_64.rpm 366 kB/s | 321 kB 00:00 (35/49): soxr-0.1.3-4.el8.x86_64.rpm 134 kB/s | 97 kB 00:00 (36/49): vid.stab-1.1.0-12.20190213gitaeabc8d.el8.x86_64.rpm 76 kB/s | 47 kB 00:00 (37/49): zvbi-0.2.35-9.el8.x86_64.rpm 530 kB/s | 424 kB 00:00 (38/49): libdca-0.0.6-4.el8.x86_64.rpm 66 kB/s | 113 kB 00:01 (39/49): libmpeg2-0.5.1-17.el8.x86_64.rpm 177 kB/s | 76 kB 00:00 (40/49): live555-2019.06.28-1.el8.x86_64.rpm 330 kB/s | 404 kB 00:01 (41/49): faad2-libs-2.8.8-6.el8.x86_64.rpm 39 kB/s | 177 kB 00:04 (42/49): opencore-amr-0.1.5-7.el8.x86_64.rpm 213 kB/s | 180 kB 00:00 (43/49): ffmpeg-libs-4.2.1-3.el8.x86_64.rpm 329 kB/s | 7.2 MB 00:22 (44/49): vlc-3.0.9-22.el8.x86_64.rpm 86 kB/s | 1.9 MB 00:22 (45/49): vo-amrwbenc-0.1.3-8.el8.x86_64.rpm 15 kB/s | 77 kB 00:05 (46/49): x264-libs-0.157-12.20190717git34c06d1.el8.x86_64.rpm 296 kB/s | 651 kB 00:02 (47/49): xvidcore-1.3.5-5.el8.x86_64.rpm 29 kB/s | 268 kB 00:09 (48/49): x265-libs-3.1.2-1.el8.x86_64.rpm 167 kB/s | 1.9 MB 00:11 (49/49): vlc-core-3.0.9-22.el8.x86_64.rpm 293 kB/s | 10 MB 00:35 --------------------------------------------------------------------------------------------------------------- Total 533 kB/s | 31 MB 00:58 warning: /var/cache/dnf/rpmfusion-free-updates-4f04908c20c55925/packages/faad2-libs-2.8.8-6.el8.x86_64.rpm: Header V3 RSA/SHA1 Signature, key ID 158b3811: NOKEY RPM Fusion for EL 8 - Free - Updates 1.6 MB/s | 1.7 kB 00:00 Importing GPG key 0x158B3811: Userid : \u0026#34;RPM Fusion free repository for EL (8) \u0026lt;rpmfusion-buildsys@lists.rpmfusion.org\u0026gt;\u0026#34; Fingerprint: 8379 35CD 19E1 23AA 7F8A 8E69 979F 0C69 158B 3811 From : /etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-el-8 Is this ok [y/N]: y Key imported successfully Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : libva-2.1.0-1.el8.x86_64 1/49 Installing : libmfx-1.25-4.el8.x86_64 2/49 Installing : x265-libs-3.1.2-1.el8.x86_64 3/49 Installing : zvbi-0.2.35-9.el8.x86_64 4/49 Running scriptlet: zvbi-0.2.35-9.el8.x86_64 4/49 Installing : soxr-0.1.3-4.el8.x86_64 5/49 Installing : libkate-0.4.1-18.el8.x86_64 6/49 Installing : libebml-1.3.7-2.el8.x86_64 7/49 Installing : libdav1d-0.5.0-1.el8.x86_64 8/49 Installing : libass-0.14.0-4.el8.x86_64 9/49 Installing : libaom-1.0.0-8.20190810git9666276.el8.x86_64 10/49 Running scriptlet: jack-audio-connection-kit-1.9.12-8.el8.x86_64 11/49 Installing : jack-audio-connection-kit-1.9.12-8.el8.x86_64 11/49 Installing : ocl-icd-2.2.12-1.el8.x86_64 12/49 Running scriptlet: ocl-icd-2.2.12-1.el8.x86_64 12/49 Installing : x264-libs-0.157-12.20190717git34c06d1.el8.x86_64 13/49 Running scriptlet: x264-libs-0.157-12.20190717git34c06d1.el8.x86_64 13/49 Installing : fluidsynth-libs-1.1.11-6.el8.x86_64 14/49 Installing : libmatroska-1.5.0-1.el8.x86_64 15/49 Installing : libtiger-0.3.4-18.el8.x86_64 16/49 Installing : xvidcore-1.3.5-5.el8.x86_64 17/49 Installing : vo-amrwbenc-0.1.3-8.el8.x86_64 18/49 Installing : opencore-amr-0.1.5-7.el8.x86_64 19/49 Installing : live555-2019.06.28-1.el8.x86_64 20/49 Installing : libmpeg2-0.5.1-17.el8.x86_64 21/49 Installing : libdca-0.0.6-4.el8.x86_64 22/49 Installing : faad2-libs-1:2.8.8-6.el8.x86_64 23/49 Installing : vid.stab-1.1.0-12.20190213gitaeabc8d.el8.x86_64 24/49 Installing : schroedinger-1.0.11-21.el8.x86_64 25/49 Installing : pugixml-1.9-1.el8.x86_64 26/49 Running scriptlet: pugixml-1.9-1.el8.x86_64 26/49 Installing : libvmaf-1.3.15-1.el8.x86_64 27/49 Installing : libupnp-1.8.4-3.el8.x86_64 28/49 Installing : libmodplug-1:0.8.9.0-9.el8.x86_64 29/49 Installing : libmicrodns-0.0.10-4.el8.x86_64 30/49 Installing : libdvbpsi-1.3.3-1.el8.x86_64 31/49 Installing : libcddb-1.3.2-30.el8.x86_64 32/49 Installing : game-music-emu-0.6.2-3.el8.x86_64 33/49 Installing : aalib-libs-1.4.0-0.37.rc5.el8.x86_64 34/49 Installing : libusb-1:0.1.5-12.el8.x86_64 35/49 Installing : lirc-libs-0.10.0-19.el8.x86_64 36/49 Installing : qt5-qtx11extras-5.11.1-2.el8.x86_64 37/49 Installing : qt5-qtsvg-5.11.1-2.el8.x86_64 38/49 Installing : protobuf-lite-3.5.0-7.el8.x86_64 39/49 Installing : libvdpau-1.1.1-7.el8.x86_64 40/49 Running scriptlet: libvdpau-1.1.1-7.el8.x86_64 40/49 Installing : ffmpeg-libs-4.2.1-3.el8.x86_64 41/49 Installing : libmad-0.15.1b-24.el8.x86_64 42/49 Running scriptlet: libmad-0.15.1b-24.el8.x86_64 42/49 Installing : libdc1394-2.2.2-10.el8.x86_64 43/49 Running scriptlet: libdc1394-2.2.2-10.el8.x86_64 43/49 Installing : freeglut-3.0.0-8.el8.x86_64 44/49 Running scriptlet: freeglut-3.0.0-8.el8.x86_64 44/49 Installing : libcaca-0.99-0.43.beta19.el8.x86_64 45/49 Installing : fftw-libs-double-3.3.5-11.el8.x86_64 46/49 Installing : libchromaprint-1.4.2-6.el8.x86_64 47/49 Installing : vlc-core-1:3.0.9-22.el8.x86_64 48/49 Installing : vlc-1:3.0.9-22.el8.x86_64 49/49 Running scriptlet: vlc-1:3.0.9-22.el8.x86_64 49/49 Running scriptlet: vlc-core-1:3.0.9-22.el8.x86_64 49/49 Running scriptlet: vlc-1:3.0.9-22.el8.x86_64 49/49 Verifying : fftw-libs-double-3.3.5-11.el8.x86_64 1/49 Verifying : freeglut-3.0.0-8.el8.x86_64 2/49 Verifying : libdc1394-2.2.2-10.el8.x86_64 3/49 Verifying : libmad-0.15.1b-24.el8.x86_64 4/49 Verifying : libva-2.1.0-1.el8.x86_64 5/49 Verifying : libvdpau-1.1.1-7.el8.x86_64 6/49 Verifying : ocl-icd-2.2.12-1.el8.x86_64 7/49 Verifying : protobuf-lite-3.5.0-7.el8.x86_64 8/49 Verifying : qt5-qtsvg-5.11.1-2.el8.x86_64 9/49 Verifying : qt5-qtx11extras-5.11.1-2.el8.x86_64 10/49 Verifying : libusb-1:0.1.5-12.el8.x86_64 11/49 Verifying : aalib-libs-1.4.0-0.37.rc5.el8.x86_64 12/49 Verifying : fluidsynth-libs-1.1.11-6.el8.x86_64 13/49 Verifying : game-music-emu-0.6.2-3.el8.x86_64 14/49 Verifying : jack-audio-connection-kit-1.9.12-8.el8.x86_64 15/49 Verifying : libaom-1.0.0-8.20190810git9666276.el8.x86_64 16/49 Verifying : libass-0.14.0-4.el8.x86_64 17/49 Verifying : libcaca-0.99-0.43.beta19.el8.x86_64 18/49 Verifying : libcddb-1.3.2-30.el8.x86_64 19/49 Verifying : libchromaprint-1.4.2-6.el8.x86_64 20/49 Verifying : libdav1d-0.5.0-1.el8.x86_64 21/49 Verifying : libdvbpsi-1.3.3-1.el8.x86_64 22/49 Verifying : libebml-1.3.7-2.el8.x86_64 23/49 Verifying : libkate-0.4.1-18.el8.x86_64 24/49 Verifying : libmatroska-1.5.0-1.el8.x86_64 25/49 Verifying : libmfx-1.25-4.el8.x86_64 26/49 Verifying : libmicrodns-0.0.10-4.el8.x86_64 27/49 Verifying : libmodplug-1:0.8.9.0-9.el8.x86_64 28/49 Verifying : libtiger-0.3.4-18.el8.x86_64 29/49 Verifying : libupnp-1.8.4-3.el8.x86_64 30/49 Verifying : libvmaf-1.3.15-1.el8.x86_64 31/49 Verifying : lirc-libs-0.10.0-19.el8.x86_64 32/49 Verifying : pugixml-1.9-1.el8.x86_64 33/49 Verifying : schroedinger-1.0.11-21.el8.x86_64 34/49 Verifying : soxr-0.1.3-4.el8.x86_64 35/49 Verifying : vid.stab-1.1.0-12.20190213gitaeabc8d.el8.x86_64 36/49 Verifying : zvbi-0.2.35-9.el8.x86_64 37/49 Verifying : faad2-libs-1:2.8.8-6.el8.x86_64 38/49 Verifying : ffmpeg-libs-4.2.1-3.el8.x86_64 39/49 Verifying : libdca-0.0.6-4.el8.x86_64 40/49 Verifying : libmpeg2-0.5.1-17.el8.x86_64 41/49 Verifying : live555-2019.06.28-1.el8.x86_64 42/49 Verifying : opencore-amr-0.1.5-7.el8.x86_64 43/49 Verifying : vlc-1:3.0.9-22.el8.x86_64 44/49 Verifying : vlc-core-1:3.0.9-22.el8.x86_64 45/49 Verifying : vo-amrwbenc-0.1.3-8.el8.x86_64 46/49 Verifying : x264-libs-0.157-12.20190717git34c06d1.el8.x86_64 47/49 Verifying : x265-libs-3.1.2-1.el8.x86_64 48/49 Verifying : xvidcore-1.3.5-5.el8.x86_64 49/49 Installed: vlc-1:3.0.9-22.el8.x86_64 fftw-libs-double-3.3.5-11.el8.x86_64 freeglut-3.0.0-8.el8.x86_64 libdc1394-2.2.2-10.el8.x86_64 libmad-0.15.1b-24.el8.x86_64 libva-2.1.0-1.el8.x86_64 libvdpau-1.1.1-7.el8.x86_64 ocl-icd-2.2.12-1.el8.x86_64 protobuf-lite-3.5.0-7.el8.x86_64 qt5-qtsvg-5.11.1-2.el8.x86_64 qt5-qtx11extras-5.11.1-2.el8.x86_64 libusb-1:0.1.5-12.el8.x86_64 aalib-libs-1.4.0-0.37.rc5.el8.x86_64 fluidsynth-libs-1.1.11-6.el8.x86_64 game-music-emu-0.6.2-3.el8.x86_64 jack-audio-connection-kit-1.9.12-8.el8.x86_64 libaom-1.0.0-8.20190810git9666276.el8.x86_64 libass-0.14.0-4.el8.x86_64 libcaca-0.99-0.43.beta19.el8.x86_64 libcddb-1.3.2-30.el8.x86_64 libchromaprint-1.4.2-6.el8.x86_64 libdav1d-0.5.0-1.el8.x86_64 libdvbpsi-1.3.3-1.el8.x86_64 libebml-1.3.7-2.el8.x86_64 libkate-0.4.1-18.el8.x86_64 libmatroska-1.5.0-1.el8.x86_64 libmfx-1.25-4.el8.x86_64 libmicrodns-0.0.10-4.el8.x86_64 libmodplug-1:0.8.9.0-9.el8.x86_64 libtiger-0.3.4-18.el8.x86_64 libupnp-1.8.4-3.el8.x86_64 libvmaf-1.3.15-1.el8.x86_64 lirc-libs-0.10.0-19.el8.x86_64 pugixml-1.9-1.el8.x86_64 schroedinger-1.0.11-21.el8.x86_64 soxr-0.1.3-4.el8.x86_64 vid.stab-1.1.0-12.20190213gitaeabc8d.el8.x86_64 zvbi-0.2.35-9.el8.x86_64 faad2-libs-1:2.8.8-6.el8.x86_64 ffmpeg-libs-4.2.1-3.el8.x86_64 libdca-0.0.6-4.el8.x86_64 libmpeg2-0.5.1-17.el8.x86_64 live555-2019.06.28-1.el8.x86_64 opencore-amr-0.1.5-7.el8.x86_64 vlc-core-1:3.0.9-22.el8.x86_64 vo-amrwbenc-0.1.3-8.el8.x86_64 x264-libs-0.157-12.20190717git34c06d1.el8.x86_64 x265-libs-3.1.2-1.el8.x86_64 xvidcore-1.3.5-5.el8.x86_64 Complete! $ yum install vlc-core Last metadata expiration check: 0:01:24 ago on Fri 01 Nov 2019 10:50:54 PM CST. Package vlc-core-1:3.0.9-22.el8.x86_64 is already installed. Dependencies resolved. Nothing to do. Complete! $ pip3 install python-vlc WARNING: Running pip install with root privileges is generally not a good idea. Try `pip3 install --user` instead. Collecting python-vlc Downloading https://files.pythonhosted.org/packages/23/12/fd44ed5d105891a061b1aad7d554905e4586a3b43766250616a565d8c5c9/python_vlc-3.0.7110-py3-none-any.whl (80kB) 100% |████████████████████████████████| 81kB 9.8kB/s Installing collected packages: python-vlc Successfully installed python-vlc-3.0.7110 ","permalink":"http://localhost:9011/20191028-centos8-install-vlc/","summary":"\u003cp\u003e在CentOS8上使用\u003ccode\u003eepel\u003c/code\u003e源和\u003ccode\u003erpmfusion\u003c/code\u003e源安装VLC播放器。\u003c/p\u003e","title":"在CentOS8中安装VLC播放器"},{"content":"Windows中常用的一些服务和默认的端口。\n在windows系统中的这个目录下C:\\Windows\\System32\\drivers\\etc\\services有完整的文件。\necho 7/tcp echo 7/udp discard 9/tcp sink null discard 9/udp sink null systat 11/tcp users #Active users systat 11/udp users #Active users daytime 13/tcp daytime 13/udp qotd 17/tcp quote #Quote of the day qotd 17/udp quote #Quote of the day chargen 19/tcp ttytst source #Character generator chargen 19/udp ttytst source #Character generator ftp-data 20/tcp #FTP, data ftp 21/tcp #FTP. control ssh 22/tcp #SSH Remote Login Protocol telnet 23/tcp smtp 25/tcp mail #Simple Mail Transfer Protocol time 37/tcp timserver time 37/udp timserver rlp 39/udp resource #Resource Location Protocol nameserver 42/tcp name #Host Name Server nameserver 42/udp name #Host Name Server nicname 43/tcp whois domain 53/tcp #Domain Name Server domain 53/udp #Domain Name Server bootps 67/udp dhcps #Bootstrap Protocol Server bootpc 68/udp dhcpc #Bootstrap Protocol Client tftp 69/udp #Trivial File Transfer gopher 70/tcp finger 79/tcp http 80/tcp www www-http #World Wide Web hosts2-ns 81/tcp #HOSTS2 Name Server hosts2-ns 81/udp #HOSTS2 Name Server kerberos 88/tcp krb5 kerberos-sec #Kerberos kerberos 88/udp krb5 kerberos-sec #Kerberos hostname 101/tcp hostnames #NIC Host Name Server iso-tsap 102/tcp #ISO-TSAP Class 0 rtelnet 107/tcp #Remote Telnet Service pop2 109/tcp postoffice #Post Office Protocol - Version 2 pop3 110/tcp #Post Office Protocol - Version 3 sunrpc 111/tcp rpcbind portmap #SUN Remote Procedure Call sunrpc 111/udp rpcbind portmap #SUN Remote Procedure Call auth 113/tcp ident tap #Identification Protocol uucp-path 117/tcp sqlserv 118/tcp #SQL Services nntp 119/tcp usenet #Network News Transfer Protocol ntp 123/udp #Network Time Protocol epmap 135/tcp loc-srv #DCE endpoint resolution epmap 135/udp loc-srv #DCE endpoint resolution netbios-ns 137/tcp nbname #NETBIOS Name Service netbios-ns 137/udp nbname #NETBIOS Name Service netbios-dgm 138/udp nbdatagram #NETBIOS Datagram Service netbios-ssn 139/tcp nbsession #NETBIOS Session Service imap 143/tcp imap4 #Internet Message Access Protocol sql-net 150/tcp sqlsrv 156/tcp pcmail-srv 158/tcp #PCMail Server snmp 161/udp #SNMP snmptrap 162/udp snmp-trap #SNMP trap print-srv 170/tcp #Network PostScript bgp 179/tcp #Border Gateway Protocol irc 194/tcp #Internet Relay Chat Protocol ipx 213/udp #IPX over IP rtsps 322/tcp rtsps 322/udp mftp 349/tcp mftp 349/udp ldap 389/tcp #Lightweight Directory Access Protocol https 443/tcp MCom #HTTP over TLS/SSL https 443/udp MCom #HTTP over TLS/SSL microsoft-ds 445/tcp microsoft-ds 445/udp kpasswd 464/tcp # Kerberos (v5) kpasswd 464/udp # Kerberos (v5) isakmp 500/udp ike #Internet Key Exchange crs 507/tcp #Content Replication System crs 507/udp #Content Replication System exec 512/tcp #Remote Process Execution biff 512/udp comsat login 513/tcp #Remote Login who 513/udp whod cmd 514/tcp shell syslog 514/udp printer 515/tcp spooler talk 517/udp ntalk 518/udp efs 520/tcp #Extended File Name Server router 520/udp route routed ulp 522/tcp ulp 522/udp timed 525/udp timeserver tempo 526/tcp newdate irc-serv 529/tcp irc-serv 529/udp courier 530/tcp rpc conference 531/tcp chat netnews 532/tcp readnews netwall 533/udp #For emergency broadcasts uucp 540/tcp uucpd klogin 543/tcp #Kerberos login kshell 544/tcp krcmd #Kerberos remote shell dhcpv6-client 546/tcp #DHCPv6 Client dhcpv6-client 546/udp #DHCPv6 Client dhcpv6-server 547/tcp #DHCPv6 Server dhcpv6-server 547/udp #DHCPv6 Server afpovertcp 548/tcp #AFP over TCP afpovertcp 548/udp #AFP over TCP new-rwho 550/udp new-who rtsp 554/tcp #Real Time Stream Control Protocol rtsp 554/udp #Real Time Stream Control Protocol remotefs 556/tcp rfs rfs_server rmonitor 560/udp rmonitord monitor 561/udp nntps 563/tcp snntp #NNTP over TLS/SSL nntps 563/udp snntp #NNTP over TLS/SSL whoami 565/tcp whoami 565/udp ms-shuttle 568/tcp #Microsoft shuttle ms-shuttle 568/udp #Microsoft shuttle ms-rome 569/tcp #Microsoft rome ms-rome 569/udp #Microsoft rome http-rpc-epmap 593/tcp #HTTP RPC Ep Map http-rpc-epmap 593/udp #HTTP RPC Ep Map hmmp-ind 612/tcp #HMMP Indication hmmp-ind 612/udp #HMMP Indication hmmp-op 613/tcp #HMMP Operation hmmp-op 613/udp #HMMP Operation ldaps 636/tcp sldap #LDAP over TLS/SSL doom 666/tcp #Doom Id Software doom 666/udp #Doom Id Software msexch-routing 691/tcp #MS Exchange Routing msexch-routing 691/udp #MS Exchange Routing kerberos-adm 749/tcp #Kerberos administration kerberos-adm 749/udp #Kerberos administration kerberos-iv 750/udp #Kerberos version IV mdbs_daemon 800/tcp mdbs_daemon 800/udp ftps-data 989/tcp #FTP data, over TLS/SSL ftps 990/tcp #FTP control, over TLS/SSL telnets 992/tcp #Telnet protocol over TLS/SSL imaps 993/tcp #IMAP4 protocol over TLS/SSL ircs 994/tcp #IRC protocol over TLS/SSL pop3s 995/tcp spop3 #pop3 protocol over TLS/SSL (was spop3) pop3s 995/udp spop3 #pop3 protocol over TLS/SSL (was spop3) kpop 1109/tcp #Kerberos POP nfsd-status 1110/tcp #Cluster status info nfsd-keepalive 1110/udp #Client status info nfa 1155/tcp #Network File Access nfa 1155/udp #Network File Access activesync 1034/tcp #ActiveSync Notifications phone 1167/udp #Conference calling opsmgr 1270/tcp #Microsoft Operations Manager opsmgr 1270/udp #Microsoft Operations Manager ms-sql-s 1433/tcp #Microsoft-SQL-Server ms-sql-s 1433/udp #Microsoft-SQL-Server ms-sql-m 1434/tcp #Microsoft-SQL-Monitor ms-sql-m 1434/udp #Microsoft-SQL-Monitor ms-sna-server 1477/tcp ms-sna-server 1477/udp ms-sna-base 1478/tcp ms-sna-base 1478/udp wins 1512/tcp #Microsoft Windows Internet Name Service wins 1512/udp #Microsoft Windows Internet Name Service ingreslock 1524/tcp ingres stt 1607/tcp stt 1607/udp l2tp 1701/udp #Layer Two Tunneling Protocol pptconference 1711/tcp pptconference 1711/udp pptp 1723/tcp #Point-to-point tunnelling protocol msiccp 1731/tcp msiccp 1731/udp remote-winsock 1745/tcp remote-winsock 1745/udp ms-streaming 1755/tcp ms-streaming 1755/udp msmq 1801/tcp #Microsoft Message Queue msmq 1801/udp #Microsoft Message Queue radius 1812/udp #RADIUS authentication protocol radacct 1813/udp #RADIUS accounting protocol msnp 1863/tcp msnp 1863/udp ssdp 1900/tcp ssdp 1900/udp close-combat 1944/tcp close-combat 1944/udp nfsd 2049/udp nfs #NFS server knetd 2053/tcp #Kerberos de-multiplexor mzap 2106/tcp #Multicast-Scope Zone Announcement Protocol mzap 2106/udp #Multicast-Scope Zone Announcement Protocol qwave 2177/tcp #QWAVE qwave 2177/udp #QWAVE Experiment Port directplay 2234/tcp #DirectPlay directplay 2234/udp #DirectPlay ms-olap3 2382/tcp #Microsoft OLAP 3 ms-olap3 2382/udp #Microsoft OLAP 3 ms-olap4 2383/tcp #Microsoft OLAP 4 ms-olap4 2383/udp #Microsoft OLAP 4 ms-olap1 2393/tcp #Microsoft OLAP 1 ms-olap1 2393/udp #Microsoft OLAP 1 ms-olap2 2394/tcp #Microsoft OLAP 2 ms-olap2 2394/udp #Microsoft OLAP 2 ms-theater 2460/tcp ms-theater 2460/udp wlbs 2504/tcp #Microsoft Windows Load Balancing Server wlbs 2504/udp #Microsoft Windows Load Balancing Server ms-v-worlds 2525/tcp #Microsoft V-Worlds ms-v-worlds 2525/udp #Microsoft V-Worlds sms-rcinfo 2701/tcp #SMS RCINFO sms-rcinfo 2701/udp #SMS RCINFO sms-xfer 2702/tcp #SMS XFER sms-xfer 2702/udp #SMS XFER sms-chat 2703/tcp #SMS CHAT sms-chat 2703/udp #SMS CHAT sms-remctrl 2704/tcp #SMS REMCTRL sms-remctrl 2704/udp #SMS REMCTRL msolap-ptp2 2725/tcp #MSOLAP PTP2 msolap-ptp2 2725/udp #MSOLAP PTP2 icslap 2869/tcp icslap 2869/udp cifs 3020/tcp cifs 3020/udp xbox 3074/tcp #Microsoft Xbox game port xbox 3074/udp #Microsoft Xbox game port ms-dotnetster 3126/tcp #Microsoft .NET ster port ms-dotnetster 3126/udp #Microsoft .NET ster port ms-rule-engine 3132/tcp #Microsoft Business Rule Engine Update Service ms-rule-engine 3132/udp #Microsoft Business Rule Engine Update Service msft-gc 3268/tcp #Microsoft Global Catalog msft-gc 3268/udp #Microsoft Global Catalog msft-gc-ssl 3269/tcp #Microsoft Global Catalog with LDAP/SSL msft-gc-ssl 3269/udp #Microsoft Global Catalog with LDAP/SSL ms-cluster-net 3343/tcp #Microsoft Cluster Net ms-cluster-net 3343/udp #Microsoft Cluster Net ms-wbt-server 3389/tcp #MS WBT Server ms-wbt-server 3389/udp #MS WBT Server ms-la 3535/tcp #Microsoft Class Server ms-la 3535/udp #Microsoft Class Server pnrp-port 3540/tcp #PNRP User Port pnrp-port 3540/udp #PNRP User Port teredo 3544/tcp #Teredo Port teredo 3544/udp #Teredo Port p2pgroup 3587/tcp #Peer to Peer Grouping p2pgroup 3587/udp #Peer to Peer Grouping ws-discovery 3702/udp #WS-Discovery ws-discovery 3702/tcp #WS-Discovery dvcprov-port 3776/tcp #Device Provisioning Port dvcprov-port 3776/udp #Device Provisioning Port msfw-control 3847/tcp #Microsoft Firewall Control msdts1 3882/tcp #DTS Service Port sdp-portmapper 3935/tcp #SDP Port Mapper Protocol sdp-portmapper 3935/udp #SDP Port Mapper Protocol net-device 4350/tcp #Net Device net-device 4350/udp #Net Device ipsec-msft 4500/tcp #Microsoft IPsec NAT-T ipsec-msft 4500/udp #Microsoft IPsec NAT-T llmnr 5355/tcp #LLMNR llmnr 5355/udp #LLMNR wsd 5357/tcp #Web Services on devices wsd 5358/tcp #Web Services on devices rrac 5678/tcp #Remote Replication Agent Connection rrac 5678/udp #Remote Replication Agent Connection dccm 5679/tcp #Direct Cable Connect Manager dccm 5679/udp #Direct Cable Connect Manager ms-licensing 5720/tcp #Microsoft Licensing ms-licensing 5720/udp #Microsoft Licensing directplay8 6073/tcp #DirectPlay8 directplay8 6073/udp #DirectPlay8 ms-do 7680/tcp #Microsoft Delivery Optimization ms-do 7680/udp #Microsoft Delivery Optimization man 9535/tcp #Remote Man Server rasadv 9753/tcp rasadv 9753/udp imip-channels 11320/tcp #IMIP Channels Port imip-channels 11320/udp #IMIP Channels Port directplaysrvr 47624/tcp #Direct Play Server directplaysrvr 47624/udp #Direct Play Server ","permalink":"http://localhost:9011/20191027-common-server-port/","summary":"\u003cp\u003eWindows中常用的一些服务和默认的端口。\u003c/p\u003e","title":"常用网络服务及默认端口"},{"content":"国内常用的NTP时间服务器，可以用于Linux中的NTP时间同步或者是chrony时间同步。\nNTP时间服务器域名 # 国家授时中心 ntp.ntsc.ac.cn # 阿里云 ntp.aliyun.com # 北京邮电大学 s1a.time.edu.cn # 清华大学 s2b.time.edu.cn # 北京邮电大学 s2c.time.edu.cn # 西南地区网络中心 s2d.time.edu.cn # 西北地区网络中心 s2e.time.edu.cn # 东北地区网络中心 s2f.time.edu.cn 使用crontab+ntpdate同步 # 使用root用户每小时同步一次 crontab -e -u root * */1 * * * (/usr/sbin/ntpdate ntp.ntsc.ac.cn) 使用chrony同步 此处我们使用CentOS8作为展示。\n# 使用yum安装chrony yum install chrony -y # 设置开机启动并开启chony并查看运行状态 systemctl enable chronyd.service systemctl start chronyd.service systemctl status chronyd.service chrony的配置文件目录位于/etc/chrony.conf，我们将里面默认的服务器注释掉，换成阿里云和国家时间中心的服务器。\n然后我们重启服务查看一下状态\n# 重启服务使配置文件生效 systemctl restart chronyd.service # 查看chrony的ntp服务器状态 chronyc sourcestats -v chronyc sources -v # 查看本地时间 timedatectl hwclock 如上图所示显示System clock synchronized状态为yes并且NTP service显示为active则说明已经配置成功了。\n","permalink":"http://localhost:9011/20191026-ntp-servers/","summary":"\u003cp\u003e国内常用的NTP时间服务器，可以用于Linux中的NTP时间同步或者是chrony时间同步。\u003c/p\u003e","title":"NTP和Chrony以及常用NTP时间同步服务器"},{"content":"CentOS8和RHEL8中新加入了一项功能cockpit，可以使用web浏览器来管理系统的一些常用状态，比如用户管理、软件更新、SELinux以及ssh等操作。\n首先我们使用yum来安装cockpit\nyum install cockpit -y 安装完成之后我们设置开机启动，同时注意需要在防火墙里面设置放行端口，cockpit默认使用的是9090端口，在CentOS8中的firewall里面有cockpit这个service，所以我们不需要自己手动指定端口放行。\n需要特别注意的是这里的服务是cockpit.socket而不是cockpit.service\n# 设置开机启动 systemctl enable cockpit.socket # 开启服务 systemctl start cockpit.socket # 在防火墙中永久放行服务 firewall-cmd --permanent --add-service=cockpit # 重启防火墙 firewall-cmd --reload # 查看是否放行成功 firewall-cmd --list-all 查看运行状态\n再次使用ssh登录的时候我们就会看到终端上也会提示我们可以使用web控制台进行管理。\n我们使用浏览器打开来看看。可以使用IP或者域名来进行登录，注意需要使用https协议并且指定端口为9090。\n这里浏览器会提示我们不安全，因为使用的是https协议，而我们并没有导入权威CA颁发的证书，如果有需要的话也可以在/etc/cockpit/ws-certs.d这个目录下面导入cert证书来进行认证。同时我们可以使用sudo remotectl certificate命令来查看正在使用的是什么证书\n接着我们使用系统中的用户账号密码登录。\n登录进去之后可以查看系统的各种状态和服务，也可以在浏览器中使用终端进行更多操作。\n","permalink":"http://localhost:9011/20191025-centos8-use-cockpit/","summary":"\u003cp\u003eCentOS8和RHEL8中新加入了一项功能\u003ccode\u003ecockpit\u003c/code\u003e，可以使用web浏览器来管理系统的一些常用状态，比如用户管理、软件更新、SELinux以及ssh等操作。\u003c/p\u003e","title":"CentOS8上启动系统web管理工具cockpit"},{"content":"CentOS8中主要的内置yum源、elrepo源、epel源和rpmfusion源的国内镜像版本，直接复制替换即可使用。\nCentOS8内置yum源 这里使用的是网易163的镜像源和清华的镜像源。\n其中CentOS-Debug.repo、CentOS-Media.repo、CentOS-Sources.repo、CentOS-Vault.repo这四个repo无需替换。\nCentOS-AppStream.repo # CentOS-AppStream.repo # # The mirror system uses the connecting IP address of the client and the # update status of each mirror to pick mirrors that are updated to and # geographically close to the client. You should use this for CentOS updates # unless you are manually picking other mirrors. # # If the mirrorlist= does not work for you, as a fall back you can try the # remarked out baseurl= line instead. # # [AppStream] name=CentOS-$releasever - AppStream #mirrorlist=http://mirrorlist.centos.org/?release=$releasever\u0026amp;arch=$basearch\u0026amp;repo=AppStream\u0026amp;infra=$infra #baseurl=http://mirror.centos.org/$contentdir/$releasever/AppStream/$basearch/os/ baseurl=http://mirrors.163.com/centos/$releasever/AppStream/$basearch/os/ gpgcheck=1 enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial CentOS-Base.repo # CentOS-Base.repo # # The mirror system uses the connecting IP address of the client and the # update status of each mirror to pick mirrors that are updated to and # geographically close to the client. You should use this for CentOS updates # unless you are manually picking other mirrors. # # If the mirrorlist= does not work for you, as a fall back you can try the # remarked out baseurl= line instead. # # [BaseOS] name=CentOS-$releasever - Base #mirrorlist=http://mirrorlist.centos.org/?release=$releasever\u0026amp;arch=$basearch\u0026amp;repo=BaseOS\u0026amp;infra=$infra #baseurl=http://mirror.centos.org/$contentdir/$releasever/BaseOS/$basearch/os/ baseurl=http://mirrors.163.com/centos/$releasever/BaseOS/$basearch/os/ gpgcheck=1 enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial CentOS-centosplus.repo # CentOS-centosplus.repo # # The mirror system uses the connecting IP address of the client and the # update status of each mirror to pick mirrors that are updated to and # geographically close to the client. You should use this for CentOS updates # unless you are manually picking other mirrors. # # If the mirrorlist= does not work for you, as a fall back you can try the # remarked out baseurl= line instead. # # #additional packages that extend functionality of existing packages [centosplus] name=CentOS-$releasever - Plus #mirrorlist=http://mirrorlist.centos.org/?release=$releasever\u0026amp;arch=$basearch\u0026amp;repo=centosplus\u0026amp;infra=$infra #baseurl=http://mirror.centos.org/$contentdir/$releasever/centosplus/$basearch/os/ baseurl=http://mirrors.163.com/centos/$releasever/centosplus/$basearch/os/ gpgcheck=1 enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial CentOS-CR.repo # CentOS-CR.repo # # The Continuous Release ( CR ) repository contains rpms that are due in the next # release for a specific CentOS Version ( eg. next release in CentOS-8 ); these rpms # are far less tested, with no integration checking or update path testing having # taken place. They are still built from the upstream sources, but might not map # to an exact upstream distro release. # # These packages are made available soon after they are built, for people willing # to test their environments, provide feedback on content for the next release, and # for people looking for early-access to next release content. # # The CR repo is shipped in a disabled state by default; its important that users # understand the implications of turning this on. # [cr] name=CentOS-$releasever - cr #mirrorlist=http://mirrorlist.centos.org/?release=$releasever\u0026amp;arch=$basearch\u0026amp;repo=cr\u0026amp;infra=$infra #baseurl=http://mirror.centos.org/$contentdir/$releasever/cr/$basearch/os/ baseurl=http://mirrors.163.com/centos/$releasever/cr/$basearch/os/ gpgcheck=1 enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial CentOS-Extras.repo # CentOS-Extras.repo # # The mirror system uses the connecting IP address of the client and the # update status of each mirror to pick mirrors that are updated to and # geographically close to the client. You should use this for CentOS updates # unless you are manually picking other mirrors. # # If the mirrorlist= does not work for you, as a fall back you can try the # remarked out baseurl= line instead. # # #additional packages that may be useful [extras] name=CentOS-$releasever - Extras #mirrorlist=http://mirrorlist.centos.org/?release=$releasever\u0026amp;arch=$basearch\u0026amp;repo=extras\u0026amp;infra=$infra #baseurl=http://mirror.centos.org/$contentdir/$releasever/extras/$basearch/os/ baseurl=http://mirrors.163.com/centos/$releasever/extras/$basearch/os/ gpgcheck=1 enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial CentOS-fasttrack.repo #CentOS-fasttrack.repo [fasttrack] name=CentOS-$releasever - fasttrack #mirrorlist=http://mirrorlist.centos.org/?release=$releasever\u0026amp;arch=$basearch\u0026amp;repo=fasttrack\u0026amp;infra=$infra #baseurl=http://mirror.centos.org/$contentdir/$releasever/fasttrack/$basearch/os/ baseurl=http://mirrors.163.com/centos/$releasever/fasttrack/$basearch/os/ gpgcheck=1 enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial CentOS-HA.repo # CentOS-HA.repo # # The mirror system uses the connecting IP address of the client and the # update status of each mirror to pick mirrors that are updated to and # geographically close to the client. You should use this for CentOS updates # unless you are manually picking other mirrors. # # If the mirrorlist= does not work for you, as a fall back you can try the # remarked out baseurl= line instead. # # [HighAvailability] name=CentOS-$releasever - HA #mirrorlist=http://mirrorlist.centos.org/?release=$releasever\u0026amp;arch=$basearch\u0026amp;repo=HighAvailability\u0026amp;infra=$infra #baseurl=http://mirror.centos.org/$contentdir/$releasever/HighAvailability/$basearch/os/ baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos/$releasever/HighAvailability/$basearch/os/ gpgcheck=1 enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial CentOS-PowerTools.repo # CentOS-PowerTools.repo # # The mirror system uses the connecting IP address of the client and the # update status of each mirror to pick mirrors that are updated to and # geographically close to the client. You should use this for CentOS updates # unless you are manually picking other mirrors. # # If the mirrorlist= does not work for you, as a fall back you can try the # remarked out baseurl= line instead. # # [PowerTools] name=CentOS-$releasever - PowerTools #mirrorlist=http://mirrorlist.centos.org/?release=$releasever\u0026amp;arch=$basearch\u0026amp;repo=PowerTools\u0026amp;infra=$infra #baseurl=http://mirror.centos.org/$contentdir/$releasever/PowerTools/$basearch/os/ baseurl=http://mirrors.163.com/centos/$releasever/PowerTools/$basearch/os/ gpgcheck=1 enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial elrepo源 这里使用的是中科大的镜像源\nelrepo.repo ### Name: ELRepo.org Community Enterprise Linux Repository for el8 ### URL: http://elrepo.org/ [elrepo] name=ELRepo.org Community Enterprise Linux Repository - el8 baseurl=https://mirrors.ustc.edu.cn/elrepo/elrepo/el8/$basearch/ #baseurl=http://elrepo.org/linux/elrepo/el8/$basearch/ #\thttp://mirrors.coreix.net/elrepo/elrepo/el8/$basearch/ #\thttp://jur-linux.org/download/elrepo/elrepo/el8/$basearch/ #\thttp://repos.lax-noc.com/elrepo/elrepo/el8/$basearch/ #mirrorlist=http://mirrors.elrepo.org/mirrors-elrepo.el8 enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-elrepo.org protect=0 [elrepo-testing] name=ELRepo.org Community Enterprise Linux Testing Repository - el8 baseurl=https://mirrors.ustc.edu.cn/elrepo/testing/el8/$basearch/ #baseurl=http://elrepo.org/linux/testing/el8/$basearch/ #\thttp://mirrors.coreix.net/elrepo/testing/el8/$basearch/ #\thttp://jur-linux.org/download/elrepo/testing/el8/$basearch/ #\thttp://repos.lax-noc.com/elrepo/testing/el8/$basearch/ #mirrorlist=http://mirrors.elrepo.org/mirrors-elrepo-testing.el8 enabled=0 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-elrepo.org protect=0 [elrepo-kernel] name=ELRepo.org Community Enterprise Linux Kernel Repository - el8 baseurl=https://mirrors.ustc.edu.cn/elrepo/kernel/el8/$basearch/ #baseurl=http://elrepo.org/linux/kernel/el8/$basearch/ #\thttp://mirrors.coreix.net/elrepo/kernel/el8/$basearch/ #\thttp://jur-linux.org/download/elrepo/kernel/el8/$basearch/ #\thttp://repos.lax-noc.com/elrepo/kernel/el8/$basearch/ #mirrorlist=http://mirrors.elrepo.org/mirrors-elrepo-kernel.el8 enabled=0 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-elrepo.org protect=0 [elrepo-extras] name=ELRepo.org Community Enterprise Linux Extras Repository - el8 baseurl=https://mirrors.ustc.edu.cn/elrepo/extras/el8/$basearch/ #baseurl=http://elrepo.org/linux/extras/el8/$basearch/ #\thttp://mirrors.coreix.net/elrepo/extras/el8/$basearch/ #\thttp://jur-linux.org/download/elrepo/extras/el8/$basearch/ #\thttp://repos.lax-noc.com/elrepo/extras/el8/$basearch/ #mirrorlist=http://mirrors.elrepo.org/mirrors-elrepo-extras.el8 enabled=0 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-elrepo.org protect=0 epel源 这里使用的是阿里云的epel源\nepel-modular.repo [epel-modular] name=Extra Packages for Enterprise Linux Modular $releasever - $basearch #baseurl=https://download.fedoraproject.org/pub/epel/$releasever/Modular/$basearch #metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-modular-$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/8/Modular/$basearch enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 [epel-modular-debuginfo] name=Extra Packages for Enterprise Linux Modular $releasever - $basearch - Debug #baseurl=https://download.fedoraproject.org/pub/epel/$releasever/Modular/$basearch/debug #metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-modular-debug-$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/8/Modular/$basearch/debug enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 gpgcheck=1 [epel-modular-source] name=Extra Packages for Enterprise Linux Modular $releasever - $basearch - Source #baseurl=https://download.fedoraproject.org/pub/epel/$releasever/Modular/SRPMS #metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-modular-source-$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/8/Modular/SRPMS enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 gpgcheck=1 epel-playground.repo [epel-playground] name=Extra Packages for Enterprise Linux $releasever - Playground - $basearch #baseurl=https://download.fedoraproject.org/pub/epel/playground/$releasever/Everything/$basearch/os #metalink=https://mirrors.fedoraproject.org/metalink?repo=playground-epel$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/playground/8/Everything/$basearch/os enabled=0 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 [epel-playground-debuginfo] name=Extra Packages for Enterprise Linux $releasever - Playground - $basearch - Debug #baseurl=https://download.fedoraproject.org/pub/epel/playground/$releasever/Everything/$basearch/debug #metalink=https://mirrors.fedoraproject.org/metalink?repo=playground-debug-epel$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/playground/8/Everything/$basearch/debug enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 gpgcheck=1 [epel-playground-source] name=Extra Packages for Enterprise Linux $releasever - Playground - $basearch - Source #baseurl=https://download.fedoraproject.org/pub/epel/playground/$releasever/Everything/source/tree/ #metalink=https://mirrors.fedoraproject.org/metalink?repo=playground-source-epel$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/playground/8/Everything/source/tree/ enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 gpgcheck=1 epel-testing-modular.repo [epel-testing-modular] name=Extra Packages for Enterprise Linux Modular $releasever - Testing - $basearch #baseurl=https://download.fedoraproject.org/pub/epel/testing/$releasever/Modular/$basearch #metalink=https://mirrors.fedoraproject.org/metalink?repo=testing-modular-epel$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/testing/8/Modular/$basearch enabled=0 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 [epel-testing-modular-debuginfo] name=Extra Packages for Enterprise Linux Modular $releasever - Testing - $basearch - Debug #baseurl=https://download.fedoraproject.org/pub/epel/testing/$releasever/Modular/$basearch/debug #metalink=https://mirrors.fedoraproject.org/metalink?repo=testing-modular-debug-epel$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/testing/8/Modular/$basearch/debug gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 gpgcheck=1 [epel-testing-modular-source] name=Extra Packages for Enterprise Linux Modular $releasever - Testing - $basearch - Source #baseurl=https://download.fedoraproject.org/pub/epel/testing/$releasever/Modular/SRPMS #metalink=https://mirrors.fedoraproject.org/metalink?repo=testing-modular-source-epel$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/testing/8/Modular/SRPMS enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 gpgcheck=1 epel-testing.repo [epel-testing] name=Extra Packages for Enterprise Linux $releasever - Testing - $basearch #baseurl=https://download.fedoraproject.org/pub/epel/testing/$releasever/Everything/$basearch #metalink=https://mirrors.fedoraproject.org/metalink?repo=testing-epel$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/testing/8/Everything/$basearch enabled=0 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 [epel-testing-debuginfo] name=Extra Packages for Enterprise Linux $releasever - Testing - $basearch - Debug #baseurl=https://download.fedoraproject.org/pub/epel/testing/$releasever/Everything/$basearch/debug #metalink=https://mirrors.fedoraproject.org/metalink?repo=testing-debug-epel$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/testing/8/Everything/$basearch/debug enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 gpgcheck=1 [epel-testing-source] name=Extra Packages for Enterprise Linux $releasever - Testing - $basearch - Source #baseurl=https://download.fedoraproject.org/pub/epel/testing/$releasever/Everything/SRPMS #metalink=https://mirrors.fedoraproject.org/metalink?repo=testing-source-epel$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir baseurl=https://mirrors.aliyun.com/epel/testing/8/Everything/SRPMS enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 gpgcheck=1 epel.repo [epel] name=Extra Packages for Enterprise Linux $releasever - $basearch #baseurl=https://download.fedoraproject.org/pub/epel/$releasever/Everything/$basearch baseurl=https://mirrors.aliyun.com/epel/8/Everything/$basearch/ #metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 [epel-debuginfo] name=Extra Packages for Enterprise Linux $releasever - $basearch - Debug #baseurl=https://download.fedoraproject.org/pub/epel/$releasever/Everything/$basearch/debug baseurl=https://mirrors.aliyun.com/epel/8/Everything/$basearch/debug/ #metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-debug-$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 gpgcheck=1 [epel-source] name=Extra Packages for Enterprise Linux $releasever - $basearch - Source #baseurl=https://download.fedoraproject.org/pub/epel/$releasever/Everything/SRPMS baseurl=https://mirrors.aliyun.com/epel/8/Everything/SRPMS/ #metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-source-$releasever\u0026amp;arch=$basearch\u0026amp;infra=$infra\u0026amp;content=$contentdir enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 gpgcheck=1 rpmfusion源 这里使用的是清华的rpmfusion镜像源\nrpmfusion-free-updates-testing.repo [rpmfusion-free-updates-testing] name=RPM Fusion for EL 8 - Free - Test Updates #baseurl=http://download1.rpmfusion.org/free/el/updates/testing/8/$basearch/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=free-el-updates-testing-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/free/el/updates/testing/8/$basearch/ enabled=0 type=rpm-md gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-el-8 [rpmfusion-free-updates-testing-debuginfo] name=RPM Fusion for EL 8 - Free - Test Updates Debug #baseurl=http://download1.rpmfusion.org/free/el/updates/testing/8/$basearch/debug/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=free-el-updates-testing-debug-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/free/el/updates/testing/8/$basearch/debug/ enabled=0 type=rpm-md gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-el-8 [rpmfusion-free-updates-testing-source] name=RPM Fusion for EL 8 - Free - Test Updates Source #baseurl=http://download1.rpmfusion.org/free/el/updates/testing/8/SRPMS/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=free-el-updates-testing-source-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/free/el/updates/testing/8/SRPMS/ enabled=0 type=rpm-md gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-el-8 rpmfusion-free-updates.repo [rpmfusion-free-updates] name=RPM Fusion for EL 8 - Free - Updates #baseurl=http://download1.rpmfusion.org/free/el/updates/8/$basearch/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=free-el-updates-released-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/free/el/updates/8/$basearch/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-el-8 [rpmfusion-free-updates-debuginfo] name=RPM Fusion for EL 8 - Free - Updates Debug #baseurl=http://download1.rpmfusion.org/free/el/updates/8/$basearch/debug/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=free-el-updates-released-debug-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/free/el/updates/8/$basearch/debug/ enabled=0 type=rpm-md gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-el-8 [rpmfusion-free-updates-source] name=RPM Fusion for EL 8 - Free - Updates Source #baseurl=http://download1.rpmfusion.org/free/el/updates/8/SRPMS/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=free-el-updates-released-source-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/free/el/updates/8/SRPMS/ enabled=0 type=rpm-md gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-el-8 rpmfusion-nonfree-updates-testing.repo [rpmfusion-nonfree-updates-testing] name=RPM Fusion for EL 8 - Nonfree - Test Updates #baseurl=http://download1.rpmfusion.org/nonfree/el/updates/testing/8/$basearch/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=nonfree-el-updates-testing-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/nonfree/el/updates/testing/8/$basearch/ enabled=0 type=rpm-md gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-nonfree-el-8 [rpmfusion-nonfree-updates-testing-debuginfo] name=RPM Fusion for EL 8 - Nonfree - Test Updates Debug #baseurl=http://download1.rpmfusion.org/nonfree/el/updates/testing/8/$basearch/debug/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=nonfree-el-updates-testing-debug-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/nonfree/el/updates/testing/8/$basearch/debug/ enabled=0 type=rpm-md gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-nonfree-el-8 [rpmfusion-nonfree-updates-testing-source] name=RPM Fusion for EL 8 - Nonfree - Test Updates Source #baseurl=http://download1.rpmfusion.org/nonfree/el/updates/testing/8/SRPMS/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=nonfree-el-updates-testing-source-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/nonfree/el/updates/testing/8/SRPMS/ enabled=0 type=rpm-md gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-nonfree-el-8 rpmfusion-nonfree-updates.repo [rpmfusion-nonfree-updates] name=RPM Fusion for EL 8 - Nonfree - Updates #baseurl=http://download1.rpmfusion.org/nonfree/el/updates/8/$basearch/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=nonfree-el-updates-released-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/nonfree/el/updates/8/$basearch/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-nonfree-el-8 [rpmfusion-nonfree-updates-debuginfo] name=RPM Fusion for EL 8 - Nonfree - Updates Debug #baseurl=http://download1.rpmfusion.org/nonfree/el/updates/8/$basearch/debug/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=nonfree-el-updates-released-debug-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/nonfree/el/updates/8/$basearch/debug/ enabled=0 type=rpm-md gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-nonfree-el-8 [rpmfusion-nonfree-updates-source] name=RPM Fusion for EL 8 - Nonfree - Updates Source #baseurl=http://download1.rpmfusion.org/nonfree/el/updates/8/SRPMS/ #mirrorlist=http://mirrors.rpmfusion.org/mirrorlist?repo=nonfree-el-updates-released-source-8\u0026amp;arch=$basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/rpmfusion/nonfree/el/updates/8/SRPMS/ enabled=0 type=rpm-md gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-nonfree-el-8 ","permalink":"http://localhost:9011/20191024-centos8-replace-yum-source/","summary":"\u003cp\u003eCentOS8中主要的内置yum源、elrepo源、epel源和rpmfusion源的国内镜像版本，直接复制替换即可使用。\u003c/p\u003e","title":"CentOS8yum源替换为国内镜像源"},{"content":"在Linux上使用bash补全的时候忽略大小写。\n一般在centos和ubuntu中使用bash的时候，都会使用bash-completion来进行自动补全命令，在默认情况下，补全是区分大小写的，关闭区分大小写功能只需要在inputrc文件中修改一下即可。\n如果是最小化安装，可能没有安装这个补全工具。\n# 在centos中使用yum安装 yum install bash-completion -y # 在/etc/inputrc中添加使全局所有用户生效 echo \u0026#39;set completion-ignore-case on\u0026#39; \u0026gt;\u0026gt; /etc/inputrc # 对于个别用户，则可以在用户home目录下添加 echo \u0026#39;set completion-ignore-case on\u0026#39; \u0026gt;\u0026gt; ~/.inputrc 添加完成之后我们重新启动bash命令行或者是重新登录一下就可以生效了。\n","permalink":"http://localhost:9011/20191023-bash-completion-ignore-case/","summary":"\u003cp\u003e在Linux上使用bash补全的时候忽略大小写。\u003c/p\u003e","title":"配置bash补全忽略大小写"},{"content":"在CentOS上使用yum repo安装VSC，实测CentOS7和CentOS8均可正常安装。\nVSC的安装非常简单，因为微软官方有提供yum repo，因此我们直接添加repo然后使用yum安装即可，注意在CentOS8中的yum实际上已经被替换成了dnf，所以使用yum或者是dnf都是一样的。\n# 导入gpgkey sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc # 新建repo文件 sudo sh -c \u0026#39;echo -e \u0026#34;[code]\\nname=Visual Studio Code\\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\\nenabled=1\\ngpgcheck=1\\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc\u0026#34; \u0026gt; /etc/yum.repos.d/vscode.repo\u0026#39; # 更新repo list sudo yum check-update # 安装vsc sudo yum install code VSC官网提供了非常详细的安装教程，需要的同学可以点击这里查看。下面只贴出安装记录。\n$ sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc $ sudo sh -c \u0026#39;echo -e \u0026#34;[code]\\nname=Visual Studio Code\\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\\nenabled=1\\ngpgcheck=1\\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc\u0026#34; \u0026gt; /etc/yum.repos.d/vscode.repo\u0026#39; $ cat /etc/yum.repos.d/vscode.repo [code] name=Visual Studio Code baseurl=https://packages.microsoft.com/yumrepos/vscode enabled=1 gpgcheck=1 gpgkey=https://packages.microsoft.com/keys/microsoft.asc $ sudo dnf check-update Visual Studio Code 122 kB/s | 2.2 MB 00:18 $ dnf install code 依赖关系解决。 ================================================================================================================ 软件包 架构 版本 仓库 大小 ================================================================================================================ Installing: code x86_64 1.39.2-1571154220.el7 code 77 M 事务概要 ================================================================================================================ 安装 1 软件包 总下载：77 M 安装大小：230 M 确定吗？[y/N]： y 下载软件包： code-1.39.2-1571154220.el7.x86_64.rpm 4.5 MB/s | 77 MB 00:17 ---------------------------------------------------------------------------------------------------------------- 总计 4.5 MB/s | 77 MB 00:17 运行事务检查 事务检查成功。 运行事务测试 事务测试成功。 运行事务 准备中 : 1/1 Installing : code-1.39.2-1571154220.el7.x86_64 1/1 运行脚本 : code-1.39.2-1571154220.el7.x86_64 1/1 验证 : code-1.39.2-1571154220.el7.x86_64 1/1 已安装: code-1.39.2-1571154220.el7.x86_64 完毕！ ","permalink":"http://localhost:9011/20191022-centos-install-vsc/","summary":"\u003cp\u003e在CentOS上使用yum repo安装VSC，实测CentOS7和CentOS8均可正常安装。\u003c/p\u003e","title":"在CentOS中安装VisualStudioCode"},{"content":"在CentOS上使用rpm安装wps并导入相关的缺失字体,实测CentOS7和8均可正常安装。\n下载安装 wps官网的下载地址： https://www.wps.cn/product/wpslinux\n对于CentOS，我们需要下载rpm格式的安装包。\n# 定位到下载目录 cd /home/tinychen/Downloads/ # 对安装包权限进行修改，使所有用户获取x权限（执行权限） sudo chmod a+x wps-office-11.1.0.8865-1.x86_64.rpm # 先使用yum安装所需要的库 sudo yum install libGLU # 使用rpm命令进行安装 sudo rpm -ivh wps-office-11.1.0.8865-1.x86_64.rpm 、导入字体 字体的下载连接：\n链接：https://pan.baidu.com/s/1Dth_WFtphSuKnIpDcTWT_Q 提取码：oc#zs\n# 下载完成后使用unzip命令解压 unzip -d ./wps_symbol_fonts wps_symbol_fonts.zip # 定位到wps字体存放目录 cd /usr/share/fonts/wps-office/ # 将下载解压的字体复制到该目录中 cp /home/tinychen/Downloads/wps_symbol_fonts/* . ","permalink":"http://localhost:9011/20191020-centos-install-wps/","summary":"\u003cp\u003e在CentOS上使用rpm安装wps并导入相关的缺失字体,实测CentOS7和8均可正常安装。\u003c/p\u003e","title":"在CentOS中安装wps"},{"content":"在CentOS7和CentOS8上卸载已安装的docker并安装特定版本的docker。\n卸载已存在版本 首先我们卸载已经安装的版本\nyum remove docker-ce docker-ce-cli containerd.io yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-engine 导入yum源 导入docker官方提供的yum源，如果网络不好使用官网yum源比较慢的同学也可以使用阿里云提供的镜像yum源。\nsudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 大家可以直接复制下面的yum源直接保存到/etc/yum.repo.d/目录下。\n下面的这个是阿里云的镜像源版本：\n[docker-ce-stable] name=Docker CE Stable - $basearch baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/$basearch/stable enabled=1 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-stable-debuginfo] name=Docker CE Stable - Debuginfo $basearch baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/debug-$basearch/stable enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-stable-source] name=Docker CE Stable - Sources baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/source/stable enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-edge] name=Docker CE Edge - $basearch baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/$basearch/edge enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-edge-debuginfo] name=Docker CE Edge - Debuginfo $basearch baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/debug-$basearch/edge enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-edge-source] name=Docker CE Edge - Sources baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/source/edge enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-test] name=Docker CE Test - $basearch baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/$basearch/test enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-test-debuginfo] name=Docker CE Test - Debuginfo $basearch baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/debug-$basearch/test enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-test-source] name=Docker CE Test - Sources baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/source/test enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-nightly] name=Docker CE Nightly - $basearch baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/$basearch/nightly enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-nightly-debuginfo] name=Docker CE Nightly - Debuginfo $basearch baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/debug-$basearch/nightly enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-nightly-source] name=Docker CE Nightly - Sources baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/source/nightly enabled=0 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg 下面的这个是清华的镜像源版本：\n[docker-ce-stable] name=Docker CE Stable - $basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/$basearch/stable enabled=1 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-stable-debuginfo] name=Docker CE Stable - Debuginfo $basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/debug-$basearch/stable enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-stable-source] name=Docker CE Stable - Sources baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/source/stable enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-edge] name=Docker CE Edge - $basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/$basearch/edge enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-edge-debuginfo] name=Docker CE Edge - Debuginfo $basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/debug-$basearch/edge enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-edge-source] name=Docker CE Edge - Sources baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/source/edge enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-test] name=Docker CE Test - $basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/$basearch/test enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-test-debuginfo] name=Docker CE Test - Debuginfo $basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/debug-$basearch/test enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-test-source] name=Docker CE Test - Sources baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/source/test enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-nightly] name=Docker CE Nightly - $basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/$basearch/nightly enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-nightly-debuginfo] name=Docker CE Nightly - Debuginfo $basearch baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/debug-$basearch/nightly enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg [docker-ce-nightly-source] name=Docker CE Nightly - Sources baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/source/nightly enabled=0 gpgcheck=1 gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg 安装docker 接着我们从高到低列出yum源中各个版本的docker\nyum list docker-ce --showduplicates | sort -r 安装特定版本的命令如下\nyum install docker-ce-\u0026lt;VERSION_STRING\u0026gt; docker-ce-cli-\u0026lt;VERSION_STRING\u0026gt; containerd.io 如果需要安装最新版本的话，我们直接这样：\nyum install docker-ce docker-ce-cli containerd.io 这里我们安装18.9.0的版本\nyum install docker-ce-18.09.0-3.el7 docker-ce-cli-18.09.0-3.el7 containerd.io Install a specific version by its fully qualified package name, which is the package name (docker-ce) plus the version string (2nd column) starting at the first colon (:), up to the first hyphen, separated by a hyphen (-). For example, docker-ce-18.09.1.\n这里和官网的说明有一点不一样，这里我们需要加上后面的-3.el7，否则检测不到安装包。\n更换cgroup驱动 k8s推荐docker的cgroup驱动使用systemd，说是会更稳定，所以这里我们还需要修改一下，同时这里更换docker的镜像源为中科大的镜像源。\n## Create /etc/docker directory. mkdir /etc/docker # Setup daemon. cat \u0026gt; /etc/docker/daemon.json \u0026lt;\u0026lt;EOF { \u0026#34;exec-opts\u0026#34;: [\u0026#34;native.cgroupdriver=systemd\u0026#34;], \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34; }, \u0026#34;storage-driver\u0026#34;: \u0026#34;overlay2\u0026#34;, \u0026#34;storage-opts\u0026#34;: [ \u0026#34;overlay2.override_kernel_check=true\u0026#34; ], \u0026#34;registry-mirrors\u0026#34;: [\u0026#34;https://docker.mirrors.ustc.edu.cn\u0026#34;] } EOF mkdir -p /etc/systemd/system/docker.service.d # Restart Docker systemctl daemon-reload systemctl restart docker 配置普通用户 默认情况下，普通用户需要使用sudo才能操作docker，我们这里需要进行一些修改。\n# 切换到普通用户 su 普通用户名 # 创建docker组 sudo groupadd docker # 将当前用户加入docker用户组 sudo gpasswd -a ${USER} docker # 重启docker服务 sudo systemctl daemon-reload sudo systemctl restart docker 配置内核参数 保险起见，我们还需要配置内核参数确保docker的网络正常\ncat \u0026gt;\u0026gt; /etc/sysctl.conf \u0026lt;\u0026lt;EOF net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sysctl -p reboot 测试 # 启动docker sudo systemctl start docker # 最后我们还要设置一下开机启动 sudo systemctl enable docker # 跑个helloworld看看 sudo docker run hello-world 如果成功运行则说明docker安装成功。\n","permalink":"http://localhost:9011/20190912-centos-install-docker/","summary":"\u003cp\u003e在CentOS7和CentOS8上卸载已安装的docker并安装特定版本的docker。\u003c/p\u003e","title":"在CentOS中安装特定版本的docker"},{"content":"记录一下个人博客的web服务器从apache换成nginx的过程。\n前言 关于apache和nginx的对比有很多，小七就不在这里赘述了，之前大一搭建博客的时候还没有接触过nginx就用了apache，后来考RHCE的时候也学习配置的是apache，最近开始学习nginx，考虑到对于小七自己的静态博客和渣渣服务器性能来说，nginx应该更合适一些，因此这里就把web服务器换成了nginx。\n备份快照 在阿里云的官网控制台里面创建快照备份。如果不小心翻车了，还能用快照恢复。\n停用apache服务 $ systemctl stop httpd.service $ systemctl disable httpd.service Removed symlink /etc/systemd/system/multi-user.target.wants/httpd.service. 安装nginx 导入yum源 cat \u0026gt;\u0026gt; /etc/yum.repo.d/nginx.repo \u0026lt;\u0026lt; EOF [nginx] name=nginx repo baseurl=http://nginx.org/packages/mainline/centos/7/x86_64/ gpgcheck=0 enabled=1 EOF yum clean all yum repolist yum install nginx -y 这里小七使用的是nginx提供的yum源，因此版本可能会稍微新一点。\n想要安装最新版的同学可以前往官网下载源码进行编译安装。\n官网地址：http://nginx.org/en/download.html\n启用nginx服务并设置开机启动 $ systemctl enable nginx.service Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service. $ systemctl start nginx.service 访问服务器IP。可以看到nginx已经开始工作。\n这里有可能会出现Failed to read PID from file /run/nginx.pid的错误\n原因是nginx还没有完全启动，systemd就去读取该进程的PID导致无法读取报错，解决方法是让systemd晚一点读取。\nmkdir /etc/systemd/system/nginx.service.d cat \u0026gt;\u0026gt; /etc/systemd/system/nginx.service.d/override.conf \u0026lt;\u0026lt; EOF [Service] ExecStartPost=/bin/sleep 0.1 EOF # 重启服务再次查看 systemctl daemon-reload systemctl restart nginx.service systemctl status nginx.service 配置nginx 由于小七安装的版本里面的配置文件默认并没有nginx的web的相关配置，所以我们一次把http、https和http自动跳转https配好。\n查看版本 配置https 从申请ssl认证的供应商那里下载nginx服务器对应的https认证证书，并存放在/etc/pki/CA/certs目录下。（如果没有就新建该目录）\n如果不知道怎么申请的可以查看这篇博客：\n阿里云轻量级应用服务器CentOS系统Apache配置Https\n编辑配置文件： 这里小七直接放出整个配置文件给大家参考：\n默认的配置文件位于/etc/nginx/nginx.conf\n# 执行进程操作的用户，默认nginx 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 \u0026#39;$remote_addr - $remote_user [$time_local] \u0026#34;$request\u0026#34; \u0026#39; \u0026#39;$status $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; \u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39;; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; # Settings for a TLS enabled server. # 这里我们把80和443端口一起配置 server { listen 80 default_server; listen 443 ssl; listen [::]:443 ssl; # 这里填写自己的域名 server_name tiny777.com www.tiny777.com; # 判断端口，如果是http的80端口就跳转到https的443端口 if ($server_port = 80) { #return 301 https://$server_name$request_uri; return 301 https://$host$request_uri; } # 这里是对应的html文件的目录 root /etc/nginx/html; index index.html index.htm; # 这里是https证书的密钥和证书存放位置 ssl_certificate \u0026#34;/etc/pki/CA/certs/tiny777.com.pem\u0026#34;; ssl_certificate_key \u0026#34;/etc/pki/CA/certs/tiny777.com.key\u0026#34;; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; # 这里是支持的加密方式 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_prefer_server_ciphers on; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { root\t/etc/nginx/html; index index.html index.htm; } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } } 最后我们重启nginx服务，就能查看到博客已经可以正常工作了。\n","permalink":"http://localhost:9011/20190829-http-to-nginx/","summary":"\u003cp\u003e记录一下个人博客的web服务器从apache换成nginx的过程。\u003c/p\u003e","title":"个人博客web服务器换用nginx"},{"content":"Ansible的基本概念和入门安装配置。\nIntro Ansible是一款使用python开发的自动化运维工具，实现了批量系统配置、批量程序部署、批量运行命令等功能。Ansible是基于模块工作的，本身没有批量部署的能力。真正具有批量部署的是Ansible所运行的模块，Ansible只是提供一种框架。\nAnsible默认是通过SSH协议来远程连接集群中的各台设备并进行相关操作，因此我们只需要在一台设备上安装Ansible即可，安装、升级和卸载都只需要对一台设备进行操作，其余的设备上面并不会有软件的安装残留之类的问题。同时，由于是使用的SSH连接，Ansible也不需要在系统后台维持相关进程一直运行。\nVersion 因为Ansible的只需要在一台机器上安装，所以它的升级和卸载都十分简单，因此很多人会选择安装最新版的Ansible甚至是github上的开发版。Ansible的更新周期非常短，只有四个月，因此即使是出现了BUG，也能很快的在下一个版本中解决。\nInstall 在centos7上安装最新版本的ansible\nCurrently Ansible can be run from any machine with Python 2 (version 2.7) or Python 3 (versions 3.5 and higher) installed. Windows isn’t supported for the control node.\nThis includes Red Hat, Debian, CentOS, macOS, any of the BSDs, and so on.\n从官网上我们可以看到，截止目前（2019.8.23），安装ansible需要python2.7以上或者是python3.5以上版本。\n这里为了方便管理，我选择了epel源里面的python3.6版本进行安装，需要安装最新的python3可以参考这里。\n# 首先我们需要安装epel库 yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm # 安装python3.6 yum install python36 -y # 接着我们查看一下ansible的最新版本 yum list | grep ansible 这里我们可以看到最新版是2.8.4，官网上显示的最新版也是2.8，这就没问题了。\n然后我们直接安装\nyum install ansible -y Conf 存放目录 ansible的配置文件存放在/etc/ansible目录下：\n图中的ansible.cfg文件就是对应的配置文件，默认情况下，里面的所有配置都是注释掉的，也就是说里面是没有任何默认配置的。\n读取顺序 ansible读取配置文件的先后顺序如下：\nANSIBLE_CONFIG (environment variable if set) ansible.cfg (in the current directory) ~/.ansible.cfg (in the home directory) /etc/ansible/ansible.cfg ansible会根据上述顺序读取配置文件，并且使用第一个找到的配置文件并忽略剩余的配置文件。\nansible.cfg配置文件是ini配置文件，因此可以使用#和;作为注释，但是如果需要在已经写了配置的同一行后添加注释，则只能使用;\n# some basic default values... inventory = /etc/ansible/hosts ; This points to the file that lists your hosts ansible.cfg 这里为了方便我们配合git来进行版本控制，我们在github上面创建一个repo，然后clone到本地，接着把配置文件和之后会使用到的playbook都放入到这个文件夹内。\n具体的git使用方法比较简单，我们不在这里赘述。\nansible.cfg文件的配置比较简单：\n[defaults] # hosts文件的存放位置 inventory = ./hosts # 执行超级命令的用户 sudo_user = root # 传输方式 transport = smart # ssh的端口 remote_port = 22 # 执行sudo命令的时候使用的命令 sudo_exe = sudo # 远程执行命令的默认用户 remote_user = tinychen # log日志存放的地方 log_path = /var/log/ansible.log # 默认使用的模块 module_name = command # 默认使用的shell executable = /bin/sh # 使用rsa密钥进行验证登录而不是密码 private_key_file = /home/tinychen/.ssh/id_rsa hosts文件中我们可以添加需要控制的主机的IP或者是hostname\n[k8s] 192.168.100.22[0:6] 这里因为我的被控制主机的IP地址是连续的，因此我直接使用[]来表示，这里的k8s是这一堆被控制主机的组名，一个IP可以加入多个组，也可以一个组都不加入。\nTest 接下来我们执行几条命令来测试一下\n先执行一下ping模块看看能否连通\nansible k8s -m ping 这里可以看到是没有问题的（结果太长没有全部截图出来）\nansible k8s -m command -a \u0026#34;id\u0026#34; 可以看到这里已经是顺利执行了。\n","permalink":"http://localhost:9011/20190823-ansible-intro/","summary":"\u003cp\u003eAnsible的基本概念和入门安装配置。\u003c/p\u003e","title":"Ansible入门初试"},{"content":"记录一下线程和进程的区别。\n何为线程 线程是进程中能并发执行的实体，是进程的组成部分，也是系统调度和分派的基本单位，运行在进程的上下文中,并使用进程的资源和环境。\n引入线程的理由 为了减少程序并发执行时所付出的时空开销，使得并发粒度更细、并发性更好。\n与进程的区别 线程是进程的组成部分 线程切换较快 线程通信易于实现 线程并发程度比进程高 一个程序至少有一个进程,一个进程至少有一个线程。 线程的划分尺度小于进程，使得多线程程序的并发性高。 进程在执行过程中拥有独立的内存单元，而多个线程共享内存，从而极大地提高了程序的运行效率。 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行，必须依存在应用程序中，由应用程序提供多个线程执行控制。 从逻辑角度来看，多线程的意义在于一个应用程序中，有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用，来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动 进程是系统进行资源分配和调度的一个独立单位。 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是线程可与同属一个进程的其他的线程共享进程所拥有的全部资源。 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。 进程有独立的地址空间，一个进程崩溃后，在保护模式下不会对其它进程产生影响，而线程只是一个进程中的不同执行路径。 线程有自己的堆栈和局部变量，但线程之间没有单独的地址空间，一个线程死掉就等于整个进程死掉，所以多进程的程序要比多线程的程序健壮，但在进程切换时，耗费资源较大，效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作，只能用线程，不能用进程。 https://www.zhihu.com/question/25532384\n首先来一句概括的总论：进程和线程都是一个时间段的描述，是CPU工作时间段的描述。\n下面细说背景： CPU+RAM+各种资源（比如显卡，光驱，键盘，GPS, 等等外设）构成我们的电脑，但是电脑的运行，实际就是CPU和相关寄存器以及RAM之间的事情。\n一个最最基础的事实：CPU太快，太快，太快了，寄存器仅仅能够追的上他的脚步，RAM和别的挂在各总线上的设备完全是望其项背。那当多个任务要执行的时候怎么办呢？轮流着来?或者谁优先级高谁来？不管怎么样的策略，一句话就是在CPU看来就是轮流着来。\n一个必须知道的事实：执行一段程序代码，实现一个功能的过程介绍 ，当得到CPU的时候，相关的资源必须也已经就位，就是显卡啊，GPS啊什么的必须就位，然后CPU开始执行。这里除了CPU以外所有的就构成了这个程序的执行环境，也就是我们所定义的程序上下文。当这个程序执行完了，或者分配给他的CPU执行时间用完了，那它就要被切换出去，等待下一次CPU的临幸。在被切换出去的最后一步工作就是保存程序上下文，因为这个是下次他被CPU临幸的运行环境，必须保存。\n串联起来的事实：前面讲过在CPU看来所有的任务都是一个一个的轮流执行的，具体的轮流方法就是：进程和线程**，两个名词不过是对应的CPU时间段的描述，名词就是这样的功能。**\n**线程是什么呢？**进程的颗粒度太大，每次都要有上下的调入，保存，调出。如果我们把进程比喻为一个运行在电脑上的软件，那么一个软件的执行不可能是一条逻辑执行的，必定有多个分支和多个程序段，就好比要实现程序A，实际分成 a，b，c等多个块组合而成。那么这里具体的执行就可能变成：\n程序A得到CPU =》CPU加载上下文，开始执行程序A的a小段，然后执行A的b小段，然后再执行A的c小段，最后CPU保存A的上下文。\n这里a，b，c的执行是共享了A的上下文，CPU在执行的时候没有进行上下文切换的。这里的a，b，c就是线程，也就是说线程是共享了进程的上下文环境的更为细小的CPU时间段。到此全文结束，再一个总结：进程和线程都是一个时间段的描述，是CPU工作时间段的描述，不过是颗粒大小不同。\n","permalink":"http://localhost:9011/20190821-thread-n-process/","summary":"\u003cp\u003e记录一下线程和进程的区别。\u003c/p\u003e","title":"线程和进程的区别"},{"content":"项目github地址：https://github.com/tiny777/ServersManagementTools\n概述 这个脚本需要实现的功能很简单，就是自动ssh登录到网络设备上，查询相应的白名单，然后将结果导出成excel表格，具体的操作命令如下，对应的设备是华为的AC6605。\n操作步骤 # 执行命令system-view，进入系统视图。 system-view # 执行命令wlan，进入WLAN视图。 wlan # 查看特定白名单内的信息 display sta-whitelist-profile name profilename 由于这里使用的是netmiko库，所以除了华为的网络设备，思科的设备也能支持，只需要在代码文件中稍作修改即可。 相应的命令也可以进行修改成其他的系列命令 软件版本 eNSP的BUG非常多，这里搭建拓扑来进行测试使用的软件版本是\neNSP 1.2.00.500 V100R002C00 VirtualBox Graphical User Interface Version 5.1.26r117224(Qt5.6.2) WinPcap_4_1_3 Wireshark没有用到，版本应该无所谓 操作系统是win10 pro，Microsoft Windows Version 1903(OS Build 18362.239) 配置eNSP桥接真机 这里使用的是AC6605，对真机进行桥接，使用的桥接网卡是VMware WorkStation 15.1 的VMnet8网卡，即NAT模式的网卡，不是eNSP上面的192.168.56.1的那个网卡（这个老是有问题，各种ping不同）。\n接下来的eNSP内的桥接配置就十分简单了，添加一个UDP端口，然后添加一个网卡，再添加端口映射，就可以了。可以参考下面的这张图。\n接着我们拖入一个设备，我这里使用的是华为的AC6605。\n给它配置一个IP地址然后测试一下。\n[AC6605]vlan batch 10 Info: This operation may take a few seconds. Please wait for a moment...done. [AC6605]int Vlanif 10 [AC6605-Vlanif10]ip address 192.168.59.2 24 [AC6605-Vlanif10]q [AC6605]int GigabitEthernet 0/0/1 [AC6605-GigabitEthernet0/0/1]port link-type access [AC6605-GigabitEthernet0/0/1]port default vlan 10 然后我们ping一下真机的网卡，能通就说明桥接成功了。\n配置SSH服务 首先进行系统视图，然后创建rsa key，全部选择默认，接着启动stelnet服务。\nsystem-view rsa local-key-pair create stelnet server enable 接着配置用来远程的终端\nsystem-view user-interface vty 0 4 authentication-mode aaa protocol inbound ssh 然后创建用户并设置密码。然后启动ssh服务。\nsystem-view aaa local-user tinychen password irreversible-cipher easy1234 local-user tinychen privilege level 3 local-user tinychen service-type ssh quit 最后回到系统视图下，设置ssh用户的登录方式为密码登录。\nssh user tinychen authentication-type password 保存配置 上面的所有配置如果不保存，当前的终端退出就会失效，所以要记得保存配置，方法是退到最开始的登录视图输入save就能保存了。\ndef sshLogin 这里使用netmiko来对网络设备进行ssh登录，考虑到获取到的结果是包含了命令执行过程中的所有套接字，因此得到结果之后，先将其全部写入一个debug.log文件中保存记录，再将该次的操作结果写入cache文件用于下一步处理。\ndef sort_to_csv 对于输出的结果进行筛选处理，我们需要用到re库的正则表达式，以及split()，因为大多数的输出结果是以空格来分列的，因此使用split()来处理这些字符是最好不过的了。\n最后导出成csv文件而不是直接导出excel的xlsx文件则是因为csv相比xlsx更容易读取处理，转成其他格式也更加方便。\ndef csv_to_xlsx_pd 这里就是使用pandas库来将上一步生成的csv转为xlsx文件了，非常的简单，需要的注意的就是添加的行名和列名参数。\n","permalink":"http://localhost:9011/20190816-hw-auto-get-profile/","summary":"\u003cp\u003e项目github地址：https://github.com/tiny777/ServersManagementTools\u003c/p\u003e","title":"使用python导出华为网络设备中的配置为excel文件"},{"content":"简单介绍一下在k8s集群中删除节点和添加节点的方法。\n删除节点 如果需要在k8s集群中删除节点，首先需要在master节点上删除该节点的相关数据，再删除该节点，接着在该节点上进行reset操作，接着删除相关文件。\n在master节点上 # 其中\u0026lt; node name \u0026gt;是在k8s集群中使用\u0026lt; kubectl get nodes \u0026gt;查询到的节点名称 kubectl drain \u0026lt;node name\u0026gt; --delete-local-data --force --ignore-daemonsets kubectl delete node \u0026lt;node name\u0026gt; 在需要删除的节点上 # 重置k8s sudo kubeadm reset # 删除残留的文件 sudo rm -rf /etc/kubernetes/ # 清除iptables或者ipvs的配置 sudo iptables -F \u0026amp;\u0026amp; iptables -t nat -F \u0026amp;\u0026amp; iptables -t mangle -F \u0026amp;\u0026amp; iptables -X sudo ipvsadm --clear 如果删除的是master节点，还需要删除用户目录下的.kube文件\nrm -rf ~/.kube 最后重启设备完成操作。\n添加节点 生成token 一般来说在k8s集群初始化完成的时候，会输出一条token来让我们添加其他的节点，但是这个token的有效时间只有24小时。我们可以这样查询token。\n# 查询token kubeadm token list # 创建一个token kubeadm token create 这里我们可以看到ttl值就是这个token对应的有效时间了\n生成sha256加密字符串 除了token之外，我们还需要一个sha256的加密字符串，这个我们可以这样获得\nopenssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2\u0026gt;/dev/null | openssl dgst -sha256 -hex | sed \u0026#39;s/^.* //\u0026#39; 接下来我们就可以把node节点添加到集群中了\n命令的格式如下：\nkubeadm join \u0026lt;集群master节点IP：端口\u0026gt; --token \u0026lt;token\u0026gt; --discovery-token-ca-cert-hash sha256:\u0026lt;sha256\u0026gt; 对应到小七这里的操作命令如下：\nkubeadm join kubernetes.haproxy.com:8443 --token d98xt5.6xvz7nldv2niknnv \\ --discovery-token-ca-cert-hash sha256:ae8d99e389a6a3109c188b5c27792c490e6a18e438bb4fc81a0a44b9542f3835 接着我们在master节点中就可以查看到新添加的这个节点了。\n","permalink":"http://localhost:9011/20190801-k8s-del-n-add-node/","summary":"\u003cp\u003e简单介绍一下在k8s集群中删除节点和添加节点的方法。\u003c/p\u003e","title":"在k8s中删除和添加节点的方法"},{"content":"简单介绍一下shell的概念、分类以及脚本的几种执行方式。\n什么是shell Shell的中文翻译名是贝壳的意思，也就是相当于下图所示，是一个命令解释器，它的作用是解释执行用户输入的命令及程序等，用户每输入一条命令，Shell就解释执行一条。这种从键盘一输入命令，就可以立即得到回应的对话方式，称为交互的方式。\nShell存在于操作系统的最外层，负责与用户直接对话，把用户的输入解释给操作系统，并处理各种各样的操作系统的输出结果，然后输出到屏幕返回给用户。输入系统用户名和密码并登录到Linux后的所有操作都是由Shell解释与执行的。\n什么是shell脚本 当命令或程序语句不在命令行下执行，而是通过一个程序文件来执行时，该程序就被称为Shell脚本。\n如果在Shell脚本里内置了很多条命令、语句及循环控制，然后将这些命令一次性执行完毕，这种通过文件执行脚本的方式称为非交互的方式。Shel脚本类似于DOS或windows系统下的批处理程序（扩展名一般为“*.bat\u0026quot;）。用户可以在Shell脚本中敲入一系列的命令及命令语句组合。这些命令、变量和流程控制语句等有机地结合起来，就形成了一个功能强大的Shell脚本。\n比如下面的这个脚本，就是用来清空log日志中的messages文件\n#! /bin/bash cd /var/log cat /dev/null \u0026gt; messages echo \u0026#34;Logs clean up done\u0026#34; 上面的这个脚本就是最简单的由命令直接堆砌而成的脚本文件，需要注意的是，脚本在执行的时候可能会有权限限制，一般需要使用chmod +x给脚本加执行权限，还可能需要切换到root用户或者使用sudo。\n实际上我们可以看到上面的脚本其实一条命令就能解决\ncat /dev/null \u0026gt; /var/log/messages \u0026amp;\u0026amp; echo \u0026#34;Logs clean up done\u0026#34; 接下来我们对上面的脚本进行改进，加入执行用户和切换目录的判断以及根据执行结果返回不同的信息。\n#! /bin/bash # A shell script that use to clean the system\u0026#39;s logs. CLEAN_DIR=/var/log # $UID=0 means the user must be root ROOT_UID=0 # This script need the root to execute # We have to judge whether the user is root or not # If not, echo warning and then exit if [\u0026#34;$UID\u0026#34; -ne \u0026#34;$ROOT_UID\u0026#34;] then echo \u0026#34; Must be root to execute this script \u0026#34; exit 1 if # If change directory fail, give the tip and then exit cd $CLEAN_DIR || { echo \u0026#34;Cannot change to necessary directory\u0026#34; exit 1 } # Begin to clean the log # Echo the tips whether it success or not cat /dev/null \u0026gt; messages \u0026amp;\u0026amp; { echo \u0026#34;Logs cleaned up\u0026#34; exit 0 # eixt 0 means success and exit 1 means fail } echo \u0026#34;Logs cleaned up fail\u0026#34; exit 1 shell脚本语言的种类 Shell脚本语言是弱类型语言（无须定义变量的类型即可使用），在Unix/Linux中主要有两大类Shell：一类是Bourne shell，另一类是Cshell。\nBourne shell Bourne shell又包括Bourne shell（sh）、Korn shell（ksh）、Bourne Again Shell（bash）三种类型。\nBourne shell（sh）由AT\u0026amp;T的Steve Bourne开发，是标准的UNIX Shell，很多UNIX系统都配有sh。 Korn shell（ksh）由David Korn开发，是Bourne shell（sh）的超集合，并且添加了csh引入的新功能，是目前很多UNIX系统标准配置的Shell，这些系统上的/bin/sh往往是指向/bin/ksh的符号链接。 Bourne Again Shell（bash）由GNU项目组开发，主要目标是与POSIX标准保持一致，同时兼顾对sh的兼容，bash从csh和ksh借鉴了很多功能，是各种Linux发行版默认配置的Shell，Linux系统上的/bin/sh往往是指向/bin/bash的符号链接。 尽管如此，bash和sh还是有很多的不同之处：一方面，bash扩展了一些命令和参数；另一方面，bash并不完全和sh兼容，它们有些行为并不一致，但在大多数企业运维的情况下区别不大，特殊场景可以使用bash替代sh。\n在Linux中，一般使用的shell是bash，所以说bash其实是shell的一个子集，当然也还有其他的一些流行的shell工具。如果我们想要Linux中的一个用户无法登录使用shell交互，最简单的方法就是在/etc/passwd文件中设置该用户的默认shell为/sbin/nologin即可。\nCshell Cshel又包括csh、tcsh两种类型。\ncsh由Berkeley大学开发，随BSDUNIX发布，它的流程控制语句很像C语言，支持很多Bourne shell所不支持的功能，例如：作业控制、别名、系统算术、命令历史、命令行编辑等。 tcsh是csh的增强版，加入了命令补全等功能，在FreeBSD、Mac OSX等系统上替代了csh。 shell目前形势 以上介绍的这些Shell中，较为通用的是标准的Bourne shell（sh）和Cshell（csh）。其中Bourne shell（sh）已经被Bourne Again shell（bash）所取代。\nLinux系统中的主流Shell是bash，bash是由Bourne Shell（sh）发展而来的，同时bash还包含了csh和ksh的特色，但大多数脚本都可以不加修改地在sh上运行，如果使用了sh后发现结果和预期有差异，那么可以尝试用bash替代sh。\nShel脚本语言的优势在于处理偏操作系统底层的业务，例如：Linux系统内部的很多应用（有的是应用的一部分）都是使用Shell脚本语言开发的，因为有1000多个Linux系统命令为它做支撑，特别是Linux正则表达式及三剑客grep、awk、sed等命令。\n对于一些常见的系统脚本，使用Shell开发会更简单、更快速，例如：让软件一键自动化安装、优化，监控报警脚本，软件启动脚本，日志分析脚本等，虽然PHP/Python语言也能够做到这些，但是，考虑到掌握难度、开发效率、开发习惯等因素，它们可能就不如Shell脚本语言流行及有优势了。对于一些常规的业务应用，使用Shell更符合Linux运维简单、易用、高效的三大基本原则。\n在常用的操作系统中，Linux下默认的Shell是Bourne Again shell（bash）；Solaris和FreeBSD下默认的是Bourne shell（sh）；AIX下默认的是Korn Shell（ksh）。\n脚本的执行 Shell脚本的执行通常可以采用以下几种方式。\nbash script-name或sh script-name： 这是当脚本文件本身没有可执行权限（即文件权限属性x位为-号）时常使用的方法，或者脚本文件开头没有指定解释器时需要使用的方法。\npath/script-name或./script-name： 指在当前路径下执行脚本（脚本需要有执行权限），需要将脚本文件的权限先改为可执行（即文件权限属性加x位），具体方法为chmod +x script-name。然后通过脚本绝对路径或相对路径就可以直接执行脚本了。\nsource script-name或.script-name： 这种方法通常是使用source或.（点号）读入或加载指定的Shel脚本文件（如san.sh），然后，依次执行指定的Shell脚本文件san.sh中的所有语句。这些语句将在当前父Shell脚本father.sh进程中运行（其他几种模式都会启动新的进程执行子脚本）。\n因此，使用source或.可以将san.sh自身脚本中的变量值或函数等的返回值传递到当前父Shel脚本father.sh中使用。这是它和其他几种方法最大的区别，也是值得读者特别注意的地方。\n这里说起来可能不好理解，我们用一个简单脚本来操作一下。\n这里我们可以看到，这个脚本的主要操作是使用pwd命令打印出当前的目录\n然后我们分别用上面的方法1和这里的方法3来执行这个脚本\n这里我们可以看到，使用方法1的时候，并没有打印出当前目录userdir，或者说，当前目录userdir这个变量为空。再使用方法3来执行该脚本，我们可以看到这时候的变量userdir就不为空了。\n结论：通过source或.加载执行过的脚本，由于是在当前Shell中执行脚本，因此在脚本结束之后，脚本中的变量（包括函数）值在当前Shell中依然存在；而sh和bash执行脚本都会启动新的子Shell执行，执行完后退回到父Shell，因此，变量（包括函数）值等无法保留。\n实在不懂就看下小七画的这个灵魂流程图。\n在进行Shell脚本开发时，如果脚本中有引用或执行其他脚本的内容或配置文件的需求时，最好用.\u0026ldquo;或source先加载该脚本或配置文件，处理完之后，再将它们加载到脚本的下面，就可以调用source加载的脚本及配置文件中的变量及函数等内容了。\nsource或.命令的功能是：在当前Shell中执行source或.加载并执行的相关脚本文件中的命令及语句，而不是产生一个子Shell来执行文件中的命令。\nsource或.的实际功能相同，都是读入脚本并执行。\n注意.和后面的脚本名之间要有空格。\n如果大家学过PHP开发就知道，source或.相当于include的功能。HTTP服务软件Apache、Nginx等配置文件里都支持这样的用法。\nsh\u0026lt;script-name或cat scripts-namelsh： 同样适用于bash，不过这种用法不是很常见。一般都是用来实现拼接字符串等骚操作的，留个坑，以后有机会再来试一下。\n","permalink":"http://localhost:9011/20190724-shell-intro/","summary":"\u003cp\u003e简单介绍一下shell的概念、分类以及脚本的几种执行方式。\u003c/p\u003e","title":"shell简介"},{"content":"简单介绍一下k8s中的Service组件。\nService简介 老规矩，我们先来看一下官网对service的描述：\nKubernetes Pod 是有生命周期的，它们可以被创建，也可以被销毁，然而一旦被销毁生命就永远结束。 通过 ReplicaSets 能够动态地创建和销毁 Pod（例如，需要进行扩缩容，或者执行 滚动升级）。 每个 Pod 都会获取它自己的 IP 地址，即使这些 IP 地址不总是稳定可依赖的。 这会导致一个问题：在 Kubernetes 集群中，如果一组 Pod（称为 backend）为其它 Pod （称为 frontend）提供服务，那么那些 frontend 该如何发现，并连接到这组 Pod 中的哪些 backend 呢？\n关于 Service\nKubernetes Service 定义了这样一种抽象：逻辑上的一组 Pod，一种可以访问它们的策略 —— 通常称为微服务。 这一组 Pod 能够被 Service 访问到，通常是通过 Label Selector（查看下面了解，为什么可能需要没有 selector 的 Service）实现的。\n举个例子，考虑一个图片处理 backend，它运行了3个副本。这些副本是可互换的 —— frontend 不需要关心它们调用了哪个 backend 副本。 然而组成这一组 backend 程序的 Pod 实际上可能会发生变化，frontend 客户端不应该也没必要知道，而且也不需要跟踪这一组 backend 的状态。 Service 定义的抽象能够解耦这种关联。\n对 Kubernetes 集群中的应用，Kubernetes 提供了简单的 Endpoints API，只要 Service 中的一组 Pod 发生变更，应用程序就会被更新。 对非 Kubernetes 集群中的应用，Kubernetes 提供了基于 VIP 的网桥的方式访问 Service，再由 Service 重定向到 backend Pod。\n官网的这段话可能有些不好理解，我们用最简单的web服务器软件apache举个例子。一般来说，我们在一台电脑上安装了apache服务之后，它默认监听的是80端口（http），也就是说我们如果需要访问这个apache服务，我们只需要使用浏览器访问这台电脑的IP地址加上80端口号即可，对于其他的应用也是一样，一般都是以主机IP加上应用的监听端口来进行访问。\n那么在k8s的集群中，我们都知道应用服务被打包成容器，而容器放在pod中运行，每个pod都会被分配到一个IP地址，当然我们还是可以按照上述的方式来访问这些pod里面的服务，但是，一个应用往往会部署多个pod（保证可靠性和健壮性），而不同的pod也会因为需求的不同或者节点宕机等原因被销毁或者新建（对应又是新的IP），所以k8s定义了service这一层抽象层，用来解决这个问题，将同一应用下的一组pod进行打包，然后通过service来进行统一访问（主要通过label来实现），用户只需要访问service这一层，而不需要在意service之后的pod是怎么调度运行的。也就是说，service会负责把用户的访问请求转发到pod对应的端口上，或者是说service会将pod对应的端口和service上暴露给用户访问的端口建立映射关系。\n使用service 接下来我们来尝试着使用一下service。\n首先我们创建一个pod，对应的配置文件如下：\napiVersion: apps/v1beta1 kind: Deployment metadata: name: http-deployment spec: replicas: 3 template: metadata: labels: app: httpd spec: containers: - image: httpd name: http ports: - containerPort: 80 protocol: TCP 需要特别注意的是这里我们使用了一个labels，使用这个配置文件创建的pod都会带上一个app=httpd的labels，这个是后面创建的service用来区分pod的。\n这里我们使用curl直接对部署的三个pod进行访问，可以看到都是能够访问成功的。\n接下来我们创建一个service，配置文件如下\nkind: Service apiVersion: v1 metadata: name: http-service spec: selector: app: httpd ports: - protocol: TCP port: 7777 targetPort: 80 上述配置将创建一个名称为 “http-service” 的 Service 对象，它会将请求代理到使用 TCP 端口 80，并且具有标签(labels) \u0026quot;app=httpd\u0026quot; 的 Pod 上。 这个 Service 将被指派一个 IP 地址（通常称为 “Cluster IP”），它会被服务的代理使用（见下面）。 该 Service 的 selector 将会持续评估，处理结果将被 POST 到一个名称为 “http-service” 的 Endpoints 对象上。\n需要注意的是， Service 能够将一个接收端口映射到任意的 targetPort。 默认情况下，targetPort 将被设置为与 port 字段相同的值。\n创建成功之后我们可以使用下面的这个命令来查看service\nkubectl get service -o wide 这里我们可以看到这个service被分配了一个10.96.16.218的IP地址（这个IP地址称为ClusterIP），服务对应的端口号是7777。我们试着使用curl来对其进行访问。\n这里可以看到是可以成功访问的。\n我们再来看一下详细的情况：\n这里可以看到已经和上面的三个pods的80端口建立了映射关系。\nipvs和iptables 我们现在已经知道，service这个抽象层是将这个service的IP（10.96.16.218）的7777端口和Endpoints的pod的80端口建立映射关系来实现这个抽象的。那么这个映射关系的建立，就是由iptables来实现的，而ipvs在这里也能实现一样的功能，但是更加强大，我们可以将它简单看作是iptables的增强版。\nipvs和iptables的区别 k8s中默认使用的是iptables，但是ipvs要更加强大，\nipvs 为大型集群提供了更好的可扩展性和性能 ipvs 支持比 iptables 更复杂的复制均衡算法（最小负载、最少连接、加权等等） ipvs 支持服务器健康检查和连接重试等功能 ipvs (IP Virtual Server) 实现了传输层负载均衡，也就是我们常说的4层LAN交换（基于TCP四层(IP+端口)的负载均衡软件），作为 Linux 内核的一部分。ipvs运行在主机上，在真实服务器集群前充当负载均衡器。ipvs可以将基于TCP和UDP的服务请求转发到真实服务器上，并使真实服务器的服务在单个 IP 地址上显示为虚拟服务。\nipvs是在kubernetes v1.8版本中引进的，小七这里使用的是v1.15版本，之前配置的时候就是使用了ipvs来替换iptables。\n查看ipvs 不管是ipvs还是iptables，都是需要root权限才能查看，因此我们需要在命令前加上sudo或者是切换到root用户。\nsudo ipvsadm -ln 从这里的规则中我们可以看到。ipvs将对10.96.16.218:7777的访问转发到下面对应的三个IP地址的80端口上。\n接下来我们对这个deployment进行修改，将副本数量从3改到6，再看这个ipvs规则的变化\n副本数量增加到了6个，对应的ipvs规则也完成了更新：\n接下来我们删除掉这个service，可以看到ipvs中的规则并没有进行删除\n这里我们可以知道，k8s对ipvs的规则可以说是只增不减。\n这里留一个坑，后面再开一篇来详细研究一下ipvs\n使用DNS访问 实际上，k8s集群中内置有dns解析服务，我们可以通过\u0026lt;SERVICE_NAME\u0026gt;.\u0026lt;NAMESPACE_NAME\u0026gt;来对service进行访问。\nv1.11版本之后，coredns取\t代kube-dns成为k8s中默认的dns解析服务器。\n我们在pod中运行一个shell来实验一下：\nkubectl run busybox --rm -ti --image=busybox /bin/sh busybox对nslookup的支持似乎有些问题，不一定能成功查询到dns，而且并没有内置curl命令，我们只能用wget来进行访问。事实上wget能成功就说明dns服务是正常的。\n然后我们使用它的测试版本，这时候nslookup命令正常了，wget命令又不正常了。\nkubectl run dig --rm -it --image=docker.io/azukiapp/dig /bin/sh 这里面的10.96.0.10地址正是我们的coredns的地址：\n外网访问service NodePort简介 前面我们已经可以通过clusterIP来访问service了，但是显然这只能局限于在k8s集群内的主机进行，集群外的机器无法对其进行访问，这显然是不行的。我们先看一下官网给出的解决方案：\n对一些应用（如 Frontend）的某些部分，可能希望通过外部（Kubernetes 集群外部）IP 地址暴露 Service。\nKubernetes ServiceTypes 允许指定一个需要的类型的 Service，默认是 ClusterIP 类型。\nType 的取值以及行为如下：\nClusterIP：通过集群的内部 IP 暴露服务，选择该值，服务只能够在集群内部可以访问，这也是默认的 ServiceType。 NodePort：通过每个 Node 上的 IP 和静态端口（NodePort）暴露服务。NodePort 服务会路由到 ClusterIP 服务，这个 ClusterIP 服务会自动创建。通过请求 \u0026lt;NodeIP\u0026gt;:\u0026lt;NodePort\u0026gt;，可以从集群的外部访问一个 NodePort 服务。 LoadBalancer：使用云提供商的负载局衡器，可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。 ExternalName：通过返回 CNAME 和它的值，可以将服务映射到 externalName 字段的内容（例如， foo.bar.example.com）。 没有任何类型代理被创建，这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。 可以看到，这里我们主要将用到的就是NodePort了。\n如果设置 type 的值为 \u0026quot;NodePort\u0026quot;，Kubernetes master 将从给定的配置范围内（默认：30000-32767）分配端口，每个 Node 将从该端口（每个 Node 上的同一端口）代理到 Service。该端口将通过 Service 的 spec.ports[*].nodePort字段被指定。\n如果需要指定的端口号，可以配置 nodePort 的值，系统将分配这个端口，否则调用 API 将会失败（比如，需要关心端口冲突的可能性）。\n这可以让开发人员自由地安装他们自己的负载均衡器，并配置 Kubernetes 不能完全支持的环境参数，或者直接暴露一个或多个 Node 的 IP 地址。\n需要注意的是，Service 将能够通过 \u0026lt;NodeIP\u0026gt;:spec.ports[*].nodePort 和spec.clusterIp:spec.ports[*].port 而对外可见。\n自动分配端口 接下来我们来修改一下刚刚的service配置文件，先尝试一下让master自动分配一个端口。\n然后我们重新应用一下该配置文件，就能看到分配的端口：\n我们查看ipvs规则，可以看到本机对应的IP都已经建立了相应的映射转发规则\n正好小七用的是虚拟机，使用集群外的电脑用浏览器访问任意一个master节点均可：\n手动指定端口 我们在下面的port选项中添加nodePort: 32333，然后重新应用配置文件。\n使用浏览器尝试访问：\n","permalink":"http://localhost:9011/20190723-k8s-service/","summary":"\u003cp\u003e简单介绍一下k8s中的Service组件。\u003c/p\u003e","title":"在k8s中的Service简介"},{"content":"运行容器化应用是Kubernetes最重要的核心功能。为满足不同的业务需要，Kubernetes提供了多种Controller，主要包括Deployment、DaemonSet、Job、CronJob等。\n创建资源的两种方式 创建资源主要有通过命令行配置参数和通过配置文件这两种方式。\n通过命令行主要是使用kubectl命令来进行创建，主要可能用到的是kubectl run和kubectl create，具体的用法我们可以在命令后面加上–-help参数来查看帮助文档。\n这种方式的好处就是简单快捷，部署的速度比较快，但是遇到要求比较复杂多样的资源部署，后面就要附带一大串参数，容易出错，所以这种方式一般来说比较适用于小规模的简单资源部署或者是上线前的简单测试\n通过配置文件则主要是json格式或yaml格式的文件，好处是可以详细配置各种参数，保留的配置文件还可以用到其他的集群上进行大规模的部署操作，缺点就是部署比较麻烦，并且需要一定的门槛（要求对json或yaml有一定的了解）\n配置文件主要是通过kubectl apply -f和kubectl create -f来进行配置。\nDeployment cli部署 我们先使用命令行（cli）创建一个deployment，名字（NAME）是nginx-clideployment，使用的镜像（image）版本为1.17，创建的副本（replicas）数量为3。\nkubectl run nginx-clideployment --image=nginx:1.17 --replicas=3 我们查看一下部署是否成功：\n这里的kubectl get rs中的rs其实就是ReplicaSet（RS）\n我们还可以发现ReplicaSet的命名就是在我们指定的NAME后面加上了一串哈希数值。\n\u0026lt;the name of the Deployment\u0026gt;-\u0026lt;hash value of the pod template\u0026gt;.\n想要查看更详细的pod情况，我们可以这样：\nkubectl get pod -o wide 这里我们可以发现三个pod被k8s自动的分配到了三个节点上而实现集群中的负载均衡（LB），而这里的IP是创建pod的时候进行随机分配的，我们并不能预知。\n如果想要查看某一个pod部署之后的情况，我们可以这样：\nkubectl describe pods nginx-clideployment-5696d55d9d-gdtrt 如果我们只是输入nginx-clideployment的话就会把所有相关的nginx-clideployment都列出来。\n这里的信息很多，我们可以看到namespace是使用的默认default（一般都是default，除非是数十人以上在同时使用这个集群，否则一般不建议新建namespace来区分pod，大多数情况下使用label即可区分各类pod。）\nControlled By: 则说明了这个deployment是由ReplicaSet控制的。\nConditions:则表明了当前的pod状况\nVolumes称之为卷，这个概念我们暂时还没有接触到，等到我们的宿主机需要与容器内的服务进行数据交互的时候再进行了解。\nEvents则相当于log，记录了pod运行的所有情况，如果遇到了运行不正常的情况，我们也可以查看这里来了解详情。\nyaml配置文件部署 接下来我们尝试使用yaml文件来进行部署。我们新建一个文件，命名为nginx-yamldeployment.yml\napiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx-yamldeployment spec: replicas: 5 template: metadata: labels: app: web_server spec: containers: - name: nginx image: nginx:1.17.1 apiVersion是当前配置格式的版本。 kind是要创建的资源类型，这里是Deployment。 metadata是该资源的元数据，name是必需的元数据项。 spec部分是该Deployment的规格说明。 replicas 指明副本数量，默认为1。 template 定义Pod的模板，这是配置文件的重要部分。 metadata定义Pod的元数据，至少要定义一个label。label的key和value可以任意指定。 spec 描述Pod的规格，此部分定义Pod中每一个容器的属性，name和image是必需的。 接下来我们使用kubectl apply指令进行部署。\nkubectl apply -f nginx-yamldeployment.yml 我们可以看到这里是已经运行正常了，那么如果我们想要修改这个deployment的属性，比如说副本数量从5改为6，那么我们可以直接编辑刚刚的nginx-yamldeployment.yml文件，然后再执行kubectl apply -f nginx-yamldeployment.yml，但是如果文件找不到了，我们可以使用另外的方法：\nkubectl edit deployments.apps nginx-yamldeployment 这样子我们就能直接编辑这个deployment的yaml配置文件，编辑的操作方式和vim相同，修改完成后会自动生效。\n我们修改保存退出后，可以看到这里已经生效了。\n对于使用命令行创建的deployment，我们可以使用命令行来进行修改，也可以直接kubectl edit来编辑对应的yaml配置文件。\nDaemonSet 我们先来看一下官网对DaemonSet的解释：\nDaemonSet 确保全部（或者某些）节点上运行一个 Pod 的副本。当有节点加入集群时，也会为他们新增一个 Pod 。 当有节点从集群移除时，这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。\n使用 DaemonSet 的一些典型用法：\n运行集群存储 daemon，例如在每个节点上运行 glusterd、ceph。 在每个节点上运行日志收集 daemon，例如fluentd、logstash。 在每个节点上运行监控 daemon，例如 Prometheus Node Exporter、collectd、Datadog 代理、New Relic 代理，或 Ganglia gmond。 一个简单的用法是在所有的节点上都启动一个 DaemonSet，将被作为每种类型的 daemon 使用。 一个稍微复杂的用法是单独对每种 daemon 类型使用多个 DaemonSet，但具有不同的标志，和/或对不同硬件类型具有不同的内存、CPU要求。\n实际上，k8s本身的一些系统组件服务就是以DaemonSet的形式运行在各个节点上的。\nJob 部署job Job创建一个或多个Pod并确保指定数量的Pod成功终止。当pod成功完成后，Job会跟踪成功的完成情况。达到指定数量的成功完成时，Job完成。 删除Job将清理它创建的Pod。 一个简单的例子是创建一个Job对象，以便可靠地运行一个Pod并成功完成指定任务。如果第一个Pod失败或被删除（例如由于节点硬件故障或节点重启），Job对象将启动一个新的Pod。 我们还可以使用Job并行运行多个Pod。 直接照搬官网的解释可能会有些难以理解，我们来运行一个例子就能很好的说明情况了。\n我们先新建一个job的配置文件，命名为pijob.yml,这个任务是将pi计算到小数点后两千位，然后再打印出来。我们运行一下看看。\napiVersion: batch/v1 kind: Job metadata: name: pi spec: template: spec: containers: - name: pi image: perl command: [\u0026#34;perl\u0026#34;, \u0026#34;-Mbignum=bpi\u0026#34;, \u0026#34;-wle\u0026#34;, \u0026#34;print bpi(2000)\u0026#34;] restartPolicy: Never backoffLimit: 4 我们查看它的详细情况\nkubectl describe jobs/pi 这里可以看到，运行了37s后成功计算出结果。\n查看pod也可以看到它的状态是Completed，说明已经完成了。\n# 查看创建的pod的名称 pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath=\u0026#39;{.items[*].metadata.name}\u0026#39;) echo $pods # 查看pod的运行结果 kubectl logs $pods job运行错误 我们现在来看一下运行失败的情况，我们把command里面的路径改错，使得它无法正常运行。\n注意这里的restartPolicy: Never意味着pod运行失败了也不会重启。但是这个时候job会检测到运行失败，然后再新建一个pod来执行这个任务。\nbackoffLimit: 4意味着最多只会新建4个pod，避免一直失败一直新建从而耗尽系统资源。\n我们查看event可以看到，确实是由于路径修改错误而导致无法正常运行。\n接下来我们把restartPolicy: 改为 OnFailure\n这时候我们可以看到并没有启动多个pod，二是对发生错误的pod进行重启操作。\njob并行化 在多线程早已普及的今天，很多任务我们都可以使用并行化来进行加速运行，这里也不例外。\n我们在配置文件中的spec中加入 completions: 和parallelism: 。\n图中表示需要运行12个，每次并行运行6个。\n从图中我们可以看到确实是每次运行6个（运行时间相同说明同时开始运行）。\n等待一段时候之后，我们再次查看而已看到任务已经顺利完成了。\nCronJob 熟悉linux的同学一定不会对cron感到陌生，因为cron就是用来管理linux中的定时任务的工具，所以CronJob我们可以理解为定时版的Job，其定时任务的编写格式也和Cron相似。关于Cron可以点击这里查看小七之前的博客。\n需要注意的是，CronJob的时间以启动该CronJob任务的Master节点的时间为准。\nCronjob只负责创建与其计划相匹配的Job，而Job则负责管理它所代表的Pod。也就是说，CronJob只负责创建Job，具体的管理操作还是由Job来负责。\n这里是CronJob的官方文档。\n我们新建一个hellocronjob.yml来查看一下它的工作情况。\napiVersion: batch/v1beta1 kind: CronJob metadata: name: hello spec: schedule: \u0026#34;*/1 * * * *\u0026#34; jobTemplate: spec: template: spec: containers: - name: hello image: busybox args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure 这个定时任务的操作就是每分钟输出一次时间和Hello from the Kubernetes cluster。\n# 查看cronjob的运行状态 kubectl get cronjobs.batch kubectl get jobs.batch --watch 我们随便查看其中的一个log可以看到输出的结果：\n","permalink":"http://localhost:9011/20190722-k8s-controller/","summary":"\u003cp\u003e运行容器化应用是Kubernetes最重要的核心功能。为满足不同的业务需要，Kubernetes提供了多种Controller，主要包括\u003ccode\u003eDeployment\u003c/code\u003e、\u003ccode\u003eDaemonSet\u003c/code\u003e、\u003ccode\u003eJob\u003c/code\u003e、\u003ccode\u003eCronJob\u003c/code\u003e等。\u003c/p\u003e","title":"在k8s中的controller简介"},{"content":"在VMware虚拟机中的CentOS7集群部署高可用的k8s集群环境(v1.15)。\n这里有两篇基础知识的补充和单master节点集群的搭建过程，有需要的同学可以看一下。\nk8s重要概念及各组件简介\n在CentOS7上部署k8s集群(v1.15)\n前期工作 什么是高可用集群？ 简单来说就是master节点不止一个的集群，我们知道k8s的node节点中，非master节点都需要听从master节点来进行任务的调度分配和负载均衡等操作，这样的好处就是我们只需要操作master节点就可以调度所有的node节点，坏处就是如果master节点崩了，那么整个集群也就崩掉了。所以这里的解决方案就是配置多个master节点，如果一个崩掉了还有别的备用备用节点顶上，不至于出现崩溃的情况。\n集群机器 这里必须要说一下，k8s对每个节点的机器配置的最低要求是2C2G，即两核心的CPU，2G的内存，低于这个配置也不是不能用，只是在初始化的时候会警告而已。这里小七采用的是虚拟机的方法（穷学生没有真机集群），具体的配置如下表。\n名称 配置 IP VIP 不需要虚拟机，留一个IP即可 192.168.100.10 master20 8C4G，CentOS7.6 192.168.100.20 master21 8C4G，CentOS7.6 192.168.100.21 master22 8C4G，CentOS7.6 192.168.100.22 node30 8C8G，CentOS7.6 192.168.100.30 node31 8C8G，CentOS7.6 192.168.100.31 node32 8C8G，CentOS7.6 192.168.100.32 真机用的是一块渣渣i7-8700和64G的内存，勉强能撑一下。\n集群间免密登录 之前专门写过一篇文章，这里不再赘述。多主机间实现SSH免密登录\n需要注意的是，在hosts文件中，我们还需要为192.168.100.10添加一下解析。\necho \u0026#34;192.168.100.10 kubernetes.haproxy.com\u0026#34; \u0026gt;\u0026gt; /etc/hosts 这里的kubernetes.haproxy.com可以改成别的名字，但是注意后面要用到，建议改成有意义的名字。\n时间同步(所有机器) 这里我们使用ntpdate来进行时间同步\n# 使用yum安装ntpdate工具 yum install ntpdate -y # 使用阿里云的源同步时间 ntpdate ntp1.aliyun.com # 最后查看一下时间 hwclock 我们也可以在/etc/cron.hourly/下面直接新建一个文件，将ntpdate ntp1.aliyun.com这条命令写进去，这样cron就会每小时执行一次这个同步时间操作。关于cron可以点击这里了解。\ncat \u0026gt;\u0026gt;/etc/cron.hourly/synctime \u0026lt;\u0026lt;EOF ntpdate ntp1.aliyun.com EOF 升级内核(所有机器) 建议直接升级centos官方的内核，这样兼容性最好。\n# 查看当前内核版本 uname -r # 更新yum源 yum update -y # 升级yum源中的最新版内核 yum install -y kernel 此外我们还需要设置一下内核的namespace\ngrubby --args=\u0026#34;user_namespace.enable=1\u0026#34; --update-kernel=\u0026#34;$(grubby --default-kernel)\u0026#34; # 重新加载内核 reboot 关闭防火墙(所有机器) 因为k8s涉及的端口非常多，我们先把防火墙关了，如果安装了iptable也先关掉。\n# 关闭centos7自带的firewall防火墙 systemctl disable firewalld.service systemctl stop firewalld.service 设置selinux(所有机器) 来自官网的说明，必须把selinux改为permissive（直接生效无需重启）或者disabled（需要重启生效）\n通过命令 setenforce 0 和 sed ... 可以将 SELinux 设置为 permissive 模式(将其禁用)。 只有执行这一操作之后，容器才能访问宿主的文件系统，进而能够正常使用 Pod 网络。您必须这么做，直到 kubelet 做出升级支持 SELinux 为止。\nsetenforce 0 sed -i \u0026#39;s/^SELINUX=enforcing$/SELINUX=permissive/\u0026#39; /etc/selinux/config # 也可以直接修改/etc/selinux/config文件 关于SELinux的简单介绍，可以点击这里查看之前的博客。\n关闭swap（所有机器） k8s官方不推荐我们使用swap分区，所以这里我们也关闭掉。\nswapoff -a vim /etc/fstab # 注释掉swap分区启动挂载的那一行 # 重启机器 reboot 配置ipvs(所有机器) # 使用yum源安装ipvs yum install ipvsadm ipset -y # 使配置生效 sysctl --system # 加载模块 modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack_ipv4 # 查看是否加载 lsmod | grep ip_vs # 配置开机自加载 cat \u0026lt;\u0026lt;EOF \u0026gt;\u0026gt; /etc/rc.local modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack_ipv4 EOF chmod +x /etc/rc.d/rc.local 配置k8s相关网络参数(所有机器) 接着我们需要编辑一个k8s的配置文件\n# 需要设定/etc/sysctl.d/k8s.conf的系统参数。 cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/sysctl.d/k8s.conf net.ipv4.tcp_keepalive_time = 600 net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 10 net.ipv4.neigh.default.gc_stale_time = 120 net.ipv4.conf.all.rp_filter = 0 net.ipv4.conf.default.rp_filter = 0 net.ipv4.conf.default.arp_announce = 2 net.ipv4.conf.lo.arp_announce = 2 net.ipv4.conf.all.arp_announce = 2 net.ipv4.ip_forward = 1 net.ipv4.tcp_max_tw_buckets = 5000 net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 1024 net.ipv4.tcp_synack_retries = 2 net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.netfilter.nf_conntrack_max = 2310720 fs.inotify.max_user_watches=89100 fs.may_detach_mounts = 1 fs.file-max = 52706963 fs.nr_open = 52706963 net.bridge.bridge-nf-call-arptables = 1 vm.swappiness = 0 vm.overcommit_memory=1 vm.panic_on_oom=0 EOF 配置iptables(所有机器) 保险起见，我们还需要配置iptables\ncat \u0026gt;\u0026gt; /etc/sysctl.conf \u0026lt;\u0026lt;EOF net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF yum install -y bridge-utils.x86_64 modprobe bridge modprobe br_netfilter sysctl -p reboot 检查配置(所有机器) # 检查系统内核和模块是否适合运行 docker (仅适用于 linux 系统) curl https://raw.githubusercontent.com/docker/docker/master/contrib/check-config.sh \u0026gt; check-config.sh bash ./check-config.sh 这里只需要保证必要项通过就可以了。\n安装docker(所有机器) 安装docker centos版本的decker-ce官方安装指导：\nhttps://docs.docker.com/install/linux/docker-ce/centos/\n这里我们主要是通过yum的方式来安装，如果出现无法连接docker官网的情况，建议检查一下网络，或者使用rpm的方式进行安装。\n# 导入官网提供的repo sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装docker及其相关组件 sudo yum install -y docker-ce docker-ce-cli containerd.io # 启动docker sudo systemctl start docker # 跑个helloworld看看 sudo docker run hello-world # 最后我们还要设置一下开机启动 sudo systemctl enable docker 配置cgroup k8s推荐docker的cgroup驱动使用systemd，说是会更稳定，所以这里我们还需要修改一下。\n## Create /etc/docker directory. mkdir /etc/docker # Setup daemon. cat \u0026gt; /etc/docker/daemon.json \u0026lt;\u0026lt;EOF { \u0026#34;exec-opts\u0026#34;: [\u0026#34;native.cgroupdriver=systemd\u0026#34;], \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34; }, \u0026#34;storage-driver\u0026#34;: \u0026#34;overlay2\u0026#34;, \u0026#34;storage-opts\u0026#34;: [ \u0026#34;overlay2.override_kernel_check=true\u0026#34; ] } EOF mkdir -p /etc/systemd/system/docker.service.d # Restart Docker systemctl daemon-reload systemctl restart docker 配置普通用户 默认情况下，普通用户需要使用sudo才能操作docker，我们这里需要进行一些修改。\n# 切换到普通用户 su 普通用户名 # 创建docker组 sudo groupadd docker # 将当前用户加入docker用户组 sudo gpasswd -a ${USER} docker # 重启docker服务 sudo systemctl daemon-reload sudo systemctl restart docker 安装配置keepalived、haproxy(所有master节点) 安装 # 使用yum源直接安装 yum install socat keepalived haproxy -y # 设置开机启动 systemctl enable haproxy systemctl enable keepalived # 备份一下原来的配置文件以防万一 mv /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bak mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak 编辑haproxy配置文件 首先我们开启系统的日志功能\nvim /etc/rsyslog.conf #为其添加日志功能 # Provides UDP syslog reception $ModLoad imudp $UDPServerRun 514 ------\u0026gt;启动udp，启动端口后将作为服务器工作 # Provides TCP syslog reception $ModLoad imtcp $InputTCPServerRun 514 ------\u0026gt;启动tcp监听端口 local2.* /var/log/haproxy.log 注意配置文件里面的IP要根据自己的master节点实际情况进行修改。\ncat \u0026gt;\u0026gt; /etc/haproxy/haproxy.cfg \u0026lt;\u0026lt;EOF global log\t127.0.0.1\tlocal2 chroot /var/lib/haproxy pidfile /var/run/haproxy.pid maxconn 1024 user haproxy group haproxy daemon nbproc 1 stats socket /var/lib/haproxy/stats defaults mode tcp log global option tcplog option dontlognull option redispatch retries 3 timeout queue 1m timeout connect 10s timeout client 1m timeout server 1m timeout check 10s listen stats mode http bind\t0.0.0.0:12345 stats enable log\t127.0.0.1\tlocal2 stats\trealm Haproxy\\ Statistics stats uri /stats stats auth admin:admin stats admin if TRUE frontend k8s bind\t0.0.0.0:8443 mode tcp maxconn 200 default_backend k8s-https backend k8s-https balance roundrobin mode\ttcp server master220 192.168.100.20:6443 check inter 10000 fall 2 rise 2 weight 1 server master221 192.168.100.21:6443 check inter 10000 fall 2 rise 2 weight 1 server master222 192.168.100.22:6443 check inter 10000 fall 2 rise 2 weight 1 EOF 编辑keepalived配置文件 注意，三个节点keepalived配置文件存在区别：\nrouter_id分别为master20、master21、master22 state分别为MASTER、BACKUP、BACKUP priority分别为100、90、80 cat \u0026gt;\u0026gt; /etc/keepalived/keepalived.conf \u0026lt;\u0026lt;EOF global_defs { router_id master20 script_user root enable_script_security } vrrp_script check_haproxy { script /etc/keepalived/check_haproxy.sh interval 3 } vrrp_instance VI_1 { state MASTER interface ens32 virtual_router_id 80 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.100.10/24 } track_script { check_haproxy } } EOF 此外我们还需要编辑一个检查的脚本来确保正常\ncat \u0026gt;\u0026gt; /etc/keepalived/check_haproxy.sh \u0026lt;\u0026lt;EOF #!/bin/bash NUM=`ps -C haproxy --no-header |wc -l` if [ $NUM -eq 0 ];then systemctl stop keepalived fi EOF 安装k8s(所有机器) k8s官网提供的yum源并不太好用，我们这里使用阿里云的yum镜像源。\n# 新建一个yum源 cat \u0026gt;\u0026gt; /etc/yum.repos.d/kubernetes.repo \u0026lt;\u0026lt;EOF [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg exclude=kube* EOF yum clean all yum repolist yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes # 启动kubelet并设置开机启动 systemctl enable kubelet \u0026amp;\u0026amp; systemctl start kubelet 关于SELinux的简单介绍，可以点击这里查看之前的博客。\n获取默认配置文件 # 获得默认配置文件。 kubeadm config print init-defaults \u0026gt; kubeadm.conf # 查看需要的镜像 kubeadm config images list --config kubeadm.conf 不知道为啥这里显示的居然是1.14版本的，小七安装的明明是1.15版本，我们把kubeadm.conf里面的版本号改成1.15再查看\n编辑自定义配置文件 注意下面的ip地址和版本号需要根据实际情况来进行改动。\n# 配置文件kubuadm_master20.conf apiVersion: kubeadm.k8s.io/v1beta2 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.100.20 bindPort: 6443 --- apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration kubernetesVersion: v1.15.0 controlPlaneEndpoint: \u0026#34;kubernetes.haproxy.com:8443\u0026#34; imageRepository: registry.aliyuncs.com/google_containers apiServer: certSANs: - \u0026#34;master20\u0026#34; - \u0026#34;master21\u0026#34; - \u0026#34;master22\u0026#34; - 192.168.100.10 - 192.168.100.20 - 192.168.100.21 - 192.168.100.22 networking: podSubnet: \u0026#34;10.244.0.0/16\u0026#34; certificatesDir: /etc/kubernetes/pki clusterName: kubernetes etcd: local: extraArgs: listen-client-urls: \u0026#34;https://127.0.0.1:2379,https://192.168.100.20:2379\u0026#34; advertise-client-urls: \u0026#34;https://192.168.100.20:2379\u0026#34; listen-peer-urls: \u0026#34;https://192.168.100.20:2380\u0026#34; initial-advertise-peer-urls: \u0026#34;https://192.168.100.20:2380\u0026#34; initial-cluster: \u0026#34;master20=https://192.168.100.20:2380\u0026#34; initial-cluster-state: new serverCertSANs: - master20 - 192.168.100.20 peerCertSANs: - master20 - 192.168.100.20 --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs 初始化 # 拉取需要的镜像 kubeadm config images pull --config kubuadm_master20.conf # 初始化 kubeadm init --config kubuadm_master20.conf 这里的token需要记下来。然后我们需要切换到非root用户来进行配置。\nmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 添加kubectl的自动补全功能 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 安装flannel网络（master20） wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml 我们先下载一下配置文件，为了保险起见，我们手动指定网卡\n然后我们执行初始化安装操作。\nkubectl apply -f kube-flannel.yml 这时候我们再查看节点就会发现，coredns已经正常工作了。\nkubectl get pods -A 分发证书（master20） 我们在master20节点上编写一个脚本进行证书的分发，只需要分发到其他的master节点即可。注意里面的ip地址和用户名需要根据实际情况进行修改。\ncat \u0026gt;\u0026gt; districert \u0026lt;\u0026lt;EOF #!/bin/bash for index in 21 22; do ip=192.168.100.${index} ssh $ip \u0026#34;mkdir -p /etc/kubernetes/pki/etcd/; mkdir -p /home/tinychen/.kube/\u0026#34; scp /etc/kubernetes/pki/ca.crt $ip:/etc/kubernetes/pki/ca.crt scp /etc/kubernetes/pki/ca.key $ip:/etc/kubernetes/pki/ca.key scp /etc/kubernetes/pki/sa.key $ip:/etc/kubernetes/pki/sa.key scp /etc/kubernetes/pki/sa.pub $ip:/etc/kubernetes/pki/sa.pub scp /etc/kubernetes/pki/front-proxy-ca.crt $ip:/etc/kubernetes/pki/front-proxy-ca.crt scp /etc/kubernetes/pki/front-proxy-ca.key $ip:/etc/kubernetes/pki/front-proxy-ca.key scp /etc/kubernetes/pki/etcd/ca.crt $ip:/etc/kubernetes/pki/etcd/ca.crt scp /etc/kubernetes/pki/etcd/ca.key $ip:/etc/kubernetes/pki/etcd/ca.key scp /etc/kubernetes/admin.conf $ip:/etc/kubernetes/admin.conf scp /etc/kubernetes/admin.conf $ip:/home/tinychen/.kube/config done EOF 配置master21 编辑自定义配置文件 注意下面的ip地址和版本号需要根据实际情况来进行改动。\n# 配置文件kubeadm_master21.conf apiVersion: kubeadm.k8s.io/v1beta2 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.100.21 bindPort: 6443 --- apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration kubernetesVersion: v1.15.0 controlPlaneEndpoint: \u0026#34;kubernetes.haproxy.com:8443\u0026#34; imageRepository: registry.aliyuncs.com/google_containers apiServer: certSANs: - \u0026#34;master20\u0026#34; - \u0026#34;master21\u0026#34; - \u0026#34;master22\u0026#34; - 192.168.100.10 - 192.168.100.20 - 192.168.100.21 - 192.168.100.22 networking: podSubnet: \u0026#34;10.244.0.0/16\u0026#34; certificatesDir: /etc/kubernetes/pki clusterName: kubernetes etcd: local: extraArgs: listen-client-urls: \u0026#34;https://127.0.0.1:2379,https://192.168.100.21:2379\u0026#34; advertise-client-urls: \u0026#34;https://192.168.100.21:2379\u0026#34; listen-peer-urls: \u0026#34;https://192.168.100.21:2380\u0026#34; initial-advertise-peer-urls: \u0026#34;https://192.168.100.21:2380\u0026#34; initial-cluster: \u0026#34;master20=https://192.168.100.20:2380,master21=https://192.168.100.21:2380\u0026#34; initial-cluster-state: existing serverCertSANs: - master21 - 192.168.100.21 peerCertSANs: - master21 - 192.168.100.21 --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs 配置非root用户 前面我们在脚本那里已经创建好了文件夹，我们这里只需要修改权限和添加自动补全就好了。\nsudo chown $(id -u):$(id -g) $HOME/.kube/config # 添加kubectl的自动补全功能 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 初始化 注意这里的文件名和IP地址要根据实际情况进行修改。\n# 配置证书 kubeadm init phase certs all --config kubeadm_master21.conf # 配置etcd kubeadm init phase etcd local --config kubeadm_master21.conf # 生成kubelet配置文件 kubeadm init phase kubeconfig kubelet --config kubeadm_master21.conf # 启动kubelet kubeadm init phase kubelet-start --config kubeadm_master21.conf 然后这一步要切换到非root用户\n# 将master21的etcd加入集群 kubectl exec -n kube-system etcd-master20 -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file /etc/kubernetes/pki/etcd/peer.key --endpoints=https://192.168.100.20:2379 member add master21 https://192.168.100.21:2380 # 启动 kube-apiserver、kube-controller-manager、kube-scheduler kubeadm init phase kubeconfig all --config kubeadm_master21.conf kubeadm init phase control-plane all --config kubeadm_master21.conf # 将节点标记为master kubeadm init phase mark-control-plane --config kubeadm_master21.conf 配置master22 编辑自定义配置文件 注意下面的ip地址和版本号需要根据实际情况来进行改动。\n# 配置文件kubeadm_master22.conf apiVersion: kubeadm.k8s.io/v1beta2 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.100.22 bindPort: 6443 --- apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration kubernetesVersion: v1.15.0 controlPlaneEndpoint: \u0026#34;kubernetes.haproxy.com:8443\u0026#34; imageRepository: registry.aliyuncs.com/google_containers apiServer: certSANs: - \u0026#34;master20\u0026#34; - \u0026#34;master21\u0026#34; - \u0026#34;master22\u0026#34; - 192.168.100.10 - 192.168.100.20 - 192.168.100.21 - 192.168.100.22 networking: podSubnet: \u0026#34;10.244.0.0/16\u0026#34; certificatesDir: /etc/kubernetes/pki clusterName: kubernetes etcd: local: extraArgs: listen-client-urls: \u0026#34;https://127.0.0.1:2379,https://192.168.100.22:2379\u0026#34; advertise-client-urls: \u0026#34;https://192.168.100.22:2379\u0026#34; listen-peer-urls: \u0026#34;https://192.168.100.22:2380\u0026#34; initial-advertise-peer-urls: \u0026#34;https://192.168.100.22:2380\u0026#34; initial-cluster: \u0026#34;master20=https://192.168.100.20:2380,master21=https://192.168.100.21:2380,master22=https://192.168.100.22:2380\u0026#34; initial-cluster-state: existing serverCertSANs: - master22 - 192.168.100.22 peerCertSANs: - master22 - 192.168.100.22 --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs 配置非root用户 前面我们在脚本那里已经创建好了文件夹，我们这里只需要修改权限和添加自动补全就好了。\nsudo chown $(id -u):$(id -g) $HOME/.kube/config # 添加kubectl的自动补全功能 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 初始化 注意这里的文件名和IP地址要根据实际情况进行修改。\n# 配置证书 kubeadm init phase certs all --config kubeadm_master22.conf # 配置etcd kubeadm init phase etcd local --config kubeadm_master22.conf # 生成kubelet配置文件 kubeadm init phase kubeconfig kubelet --config kubeadm_master22.conf # 启动kubelet kubeadm init phase kubelet-start --config kubeadm_master22.conf 然后这一步要切换到非root用户\n# 将master22的etcd加入集群 kubectl exec -n kube-system etcd-master20 -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file /etc/kubernetes/pki/etcd/peer.key --endpoints=https://192.168.100.20:2379 member add master22 https://192.168.100.22:2380 # 启动 kube-apiserver、kube-controller-manager、kube-scheduler kubeadm init phase kubeconfig all --config kubeadm_master22.conf kubeadm init phase control-plane all --config kubeadm_master22.conf # 将节点标记为master kubeadm init phase mark-control-plane --config kubeadm_master22.conf 添加node节点 node节点的添加我们只需要切换到root用户执行之前master20初始化成功提示的指令即可。\nkubeadm join kubernetes.haproxy.com:8443 --token m42q2v.y8sffomwkgjlhx8h \\ --discovery-token-ca-cert-hash sha256:1829cbaf37e968b43f91e6f0b65e2f2b7985a2d3d17ec66f3af70e0d15db01de 查看最终效果 查看节点 查看pods 查看集群 kubectl exec -n kube-system etcd-master20 \\ -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt \\ --cert-file /etc/kubernetes/pki/etcd/peer.crt \\ --key-file /etc/kubernetes/pki/etcd/peer.key \\ --endpoints=https://192.168.100.20:2379 member list 查看ipvs情况 ","permalink":"http://localhost:9011/20190718-centos7-install-k8s-ha/","summary":"\u003cp\u003e在VMware虚拟机中的CentOS7集群部署高可用的k8s集群环境(v1.15)。\u003c/p\u003e\n\u003cp\u003e这里有两篇基础知识的补充和单master节点集群的搭建过程，有需要的同学可以看一下。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://tinychen.com/20190715-k8s-concepts-intro/\"\u003ek8s重要概念及各组件简介\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://tinychen.com/20190716-centos7-install-k8s/\"\u003e在CentOS7上部署k8s集群(v1.15)\u003c/a\u003e\u003c/p\u003e","title":"在CentOS7上部署高可用的k8s集群(v1.15)"},{"content":"在虚拟机中的CentOS7集群中部署单master节点的k8s环境(v1.15)。\n安装docker-ce docker简介 最早的时候docker其实不是开源的，后来docker混得不太好，无奈之下创始人决定将docker开源（然后docker就火了），然后就有了docker这个开源项目，现在主要是由docker公司维护。\n2017年年初，docker公司将原先的docker项目改名为moby，并创建了docker-ce和docker-ee。\n这三者的关系是：\nmoby是继承了原先的docker的项目，是社区维护的的开源项目，谁都可以在moby的基础打造自己的容器产品\ndocker-ce（ Community Edition）是docker公司维护的开源项目，是一个基于moby项目的免费的容器产品\ndocker-ee（ Enterprise Edition）是docker公司维护的闭源产品，是docker公司的商业产品。\n我们这里还是使用docker-ce的稳定版本，关于ce版本其实也有三个分支：\n我们来看一下官网的解释：\nStable gives you latest releases for general availability. Test gives pre-releases that are ready for testing before general availability. Nightly gives you latest builds of work in progress for the next major release. 简单的打个比方来说就是：Stable就是稳定版，Test就是开发版，Nightly就是内测版\n安装docker-ce centos版本的decker-ce官方安装指导：\nhttps://docs.docker.com/install/linux/docker-ce/centos/\n这里我们主要是通过yum的方式来安装，如果出现无法连接docker官网的情况，建议检查一下网络，或者使用rpm的方式进行安装。\n# 导入官网提供的repo sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装docker及其相关组件 sudo yum install docker-ce docker-ce-cli containerd.io 注意这里我们需要校对一下密钥，确保和官网提供的无误。\n# 启动docker sudo systemctl start docker # 跑个helloworld看看 sudo docker run hello-world # 最后我们还要设置一下开机启动 sudo systemctl enable docker 准备工作 修改host文件(所有机器) 为了保证通信方便，每台主机上的/etc/hosts文件都需要进行修改，添加节点的IP地址和hostname\n同时最好把每台主机的hostname也修改成和上面的host文件一样。\n# 可以直接编辑文件 vim /etc/hostname # 或者使用命令 hostnamectl set-hostname 关闭防火墙(所有机器) 因为k8s涉及的端口非常多，我们先把防火墙关了，如果安装了iptable也先关掉。\n# 关闭centos7自带的firewall防火墙 systemctl disable firewalld.service systemctl stop firewalld.service 我们来看一下官网对这些端口的描述。\nMaster 节点\n规则 方向 端口范围 作用 使用者 TCP Inbound 6443* Kubernetes API server All TCP Inbound 2379-2380 etcd server client API kube-apiserver, etcd TCP Inbound 10250 Kubelet API Self, Control plane TCP Inbound 10251 kube-scheduler Self TCP Inbound 10252 kube-controller-manager Self Worker 节点\n规则 方向 端口范围 作用 使用者 TCP Inbound 10250 Kubelet API Self, Control plane TCP Inbound 30000-32767 NodePort Services** All ** NodePort 服务 的默认端口范围。\n任何使用 * 标记的端口号都有可能被覆盖，所以您需要保证您的自定义端口的状态是开放的。\n虽然主节点已经包含了 etcd 的端口，您也可以使用自定义的外部 etcd 集群，或是指定自定义端口。 您使用的 pod 网络插件 (见下) 也可能需要某些特定端口开启。由于各个 pod 网络插件都有所不同，请参阅他们各自文档中对端口的要求。\n如果对安全性有要求，不放心关闭防火墙的话，最好还是修改一下防火墙的配置，放行这些端口。\n同步时间(所有机器) 这里我们使用ntpdate来进行时间同步\n# 使用yum安装ntpdate工具 yum install ntpdate -y # 使用阿里云的源同步时间 ntpdate ntp1.aliyun.com # 最后查看一下时间 hwclock 我们也可以在/etc/cron.hourly/下面直接新建一个文件，将ntpdate ntp1.aliyun.com这条命令写进去，这样cron就会每小时执行一次这个同步时间操作。关于cron可以点击这里了解。\ncat \u0026gt;\u0026gt;/etc/cron.hourly/synctime \u0026lt;\u0026lt;EOF ntpdate ntp1.aliyun.com EOF 设置selinux(所有机器) 来自官网的说明，必须把selinux改为permissive（直接生效无需重启）或者disabled（需要重启生效）\n通过命令 setenforce 0 和 sed ... 可以将 SELinux 设置为 permissive 模式(将其禁用)。 只有执行这一操作之后，容器才能访问宿主的文件系统，进而能够正常使用 Pod 网络。您必须这么做，直到 kubelet 做出升级支持 SELinux 为止。\nsetenforce 0 sed -i \u0026#39;s/^SELINUX=enforcing$/SELINUX=permissive/\u0026#39; /etc/selinux/config # 也可以直接修改/etc/selinux/config文件 关于SELinux的简单介绍，可以点击这里查看之前的博客。\n安装kubeadm, kubelet 和 kubectl 简介 我们需要在每台机器上都安装以下的软件包：\nkubeadm: 用来初始化集群的指令。 kubelet: 在集群中的每个节点上用来启动 pod 和 container 等。 kubectl: 用来与集群通信的命令行工具。 kubeadm 不能 帮我们安装或管理 kubelet 或 kubectl ，所以我们需要保证他们满足通过 kubeadm 安装的 Kubernetes 控制层对版本的要求。然而控制层与 kubelet 间的 小版本号 不一致无伤大雅，不过请记住 kubelet 的版本不可以超过 API server 的版本。例如 1.8.0 的 API server 可以适配 1.7.0 的 kubelet，反之就不行了。\n添加yum仓库(所有机器) 由于众所周知的原因，k8s官网提供的yum源并不能在国内正常使用，好在我们还可以使用阿里的镜像源。\n# 新建一个yum源 vim /etc/yum.repos.d/kubernetes.repo # 添加下列内容到repo中 [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg exclude=kube* 使用yum安装kubeadm, kubelet 和 kubectl\nyum clean all yum repolist yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes # 启动kubelet并设置开机启动 systemctl enable kubelet \u0026amp;\u0026amp; systemctl start kubelet 修改cgroup相关配置(所有机器) ## Create /etc/docker directory. mkdir /etc/docker # Setup daemon. cat \u0026gt; /etc/docker/daemon.json \u0026lt;\u0026lt;EOF { \u0026#34;exec-opts\u0026#34;: [\u0026#34;native.cgroupdriver=systemd\u0026#34;], \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34; }, \u0026#34;storage-driver\u0026#34;: \u0026#34;overlay2\u0026#34;, \u0026#34;storage-opts\u0026#34;: [ \u0026#34;overlay2.override_kernel_check=true\u0026#34; ] } EOF mkdir -p /etc/systemd/system/docker.service.d # Restart Docker systemctl daemon-reload systemctl restart docker 编辑环境变量(所有机器) vim /etc/sysctl.conf # 添加两行 net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 sysctl -p systemctl daemon-reload 关闭swap分区(所有机器) swapoff -a vim /etc/fstab # 注释掉swap分区启动挂载的那一行 # 重启机器 reboot 拉取镜像 我们可以使用kubeadm config images pull看看需要拉取哪些镜像版本，然后根据错误提示来对下面的版本进行修改。\n# https://hub.docker.com/r/mirrorgooglecontainers/kube-apiserver/tags docker pull mirrorgooglecontainers/kube-apiserver:v1.15.0 # https://hub.docker.com/r/mirrorgooglecontainers/kube-proxy/tags docker pull mirrorgooglecontainers/kube-proxy:v1.15.0 # https://hub.docker.com/r/mirrorgooglecontainers/kube-controller-manager/tags docker pull mirrorgooglecontainers/kube-controller-manager:v1.15.0 # https://hub.docker.com/r/mirrorgooglecontainers/kube-scheduler/tags docker pull mirrorgooglecontainers/kube-scheduler:v1.15.0 # https://hub.docker.com/r/mirrorgooglecontainers/etcd/tags docker pull mirrorgooglecontainers/etcd:3.3.10 # https://hub.docker.com/r/mirrorgooglecontainers/pause/tags docker pull mirrorgooglecontainers/pause:3.1 # https://hub.docker.com/r/mirrorgooglecontainers/kubernetes-dashboard-amd64/tags docker pull mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.1 # https://hub.docker.com/r/coredns/coredns/tags docker pull coredns/coredns:1.3.1 由于国内不能访问k8s官网，所以我们需要先使用docker拉取镜像，然后使用tag对其更改名称。\n# 拉取docker上的镜像到本地 # https://hub.docker.com/r/mirrorgooglecontainers/kube-apiserver/tags docker pull mirrorgooglecontainers/kube-apiserver:v1.15.0 # https://hub.docker.com/r/mirrorgooglecontainers/kube-proxy/tags docker pull mirrorgooglecontainers/kube-proxy:v1.15.0 # https://hub.docker.com/r/mirrorgooglecontainers/kube-controller-manager/tags docker pull mirrorgooglecontainers/kube-controller-manager:v1.15.0 # https://hub.docker.com/r/mirrorgooglecontainers/kube-scheduler/tags docker pull mirrorgooglecontainers/kube-scheduler:v1.15.0 # https://hub.docker.com/r/mirrorgooglecontainers/etcd/tags docker pull mirrorgooglecontainers/etcd:3.3.10 # https://hub.docker.com/r/mirrorgooglecontainers/pause/tags docker pull mirrorgooglecontainers/pause:3.1 # https://hub.docker.com/r/mirrorgooglecontainers/kubernetes-dashboard-amd64/tags docker pull mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.1 # https://hub.docker.com/r/coredns/coredns/tags docker pull coredns/coredns:1.3.1 # 使用tag对其改名，方便kubeadm直接拉取使用 docker tag mirrorgooglecontainers/kube-apiserver:v1.15.0 k8s.gcr.io/kube-apiserver:v1.15.0 docker tag mirrorgooglecontainers/kube-proxy:v1.15.0 k8s.gcr.io/kube-proxy:v1.15.0 docker tag mirrorgooglecontainers/kube-controller-manager:v1.15.0 k8s.gcr.io/kube-controller-manager:v1.15.0 docker tag mirrorgooglecontainers/kube-scheduler:v1.15.0 k8s.gcr.io/kube-scheduler:v1.15.0 docker tag mirrorgooglecontainers/etcd:3.3.10 k8s.gcr.io/etcd:3.3.10 docker tag mirrorgooglecontainers/pause:3.1 k8s.gcr.io/pause:3.1 docker tag mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.1 k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1 docker tag coredns/coredns:1.3.1 k8s.gcr.io/coredns:1.3.1 # 删除掉多余的images docker rmi mirrorgooglecontainers/kube-apiserver:v1.15.0 docker rmi mirrorgooglecontainers/kube-proxy:v1.15.0 docker rmi mirrorgooglecontainers/kube-controller-manager:v1.15.0 docker rmi mirrorgooglecontainers/kube-scheduler:v1.15.0 docker rmi mirrorgooglecontainers/etcd:3.3.10 docker rmi mirrorgooglecontainers/pause:3.1 docker rmi mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.1 docker rmi coredns/coredns:1.3.1 最后效果如下\n初始化master节点 --kubernetes-version就是我们这里使用的k8s版本\n--apiserver-advertise-address 指明用Master的哪个interface与Cluster的其他节点通信。如果Master有多个interface，建议明确指定，如果不指定，kubeadm会自动选择有默认网关的interface\n--pod-network-cidr 指定Pod网络的范围。Kubernetes支持多种网络方案，而且不同网络方案对\u0026ndash;pod-network-cidr有自己的要求，这里设置为10.244.0.0/16是因为我们将使用flannel网络方案，必须设置成这个CIDR\nkubeadm init --kubernetes-version=v1.15.0 --apiserver-advertise-address 192.168.100.50 --pod-network-cidr=10.244.0.0/16 --service-cidr=10.96.0.0/12 注意红框中的那一串密钥要保存好，后面我们添加节点的时候需要用到。\n8在master上配置kubectl 注意这里要切换到普通用户而不能使用root用户！\n注意这里要切换到普通用户而不能使用root用户！\n注意这里要切换到普通用户而不能使用root用户！\nmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 多加一个kubectl的自动补全动能方便我们使用 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 到这里我们检查一下是否安装顺利\n# 获取集群状态 kubectl get cs # 获取集群节点信息 kubectl get nodes 安装flannel网络(Master) 安装flannel\nwget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml -O kube-flannel.yml kubectl apply -f kube-flannel.yml 到这里master节点上面的操作就算是完成了\n# 查看pod运行情况 kubectl get pods -n kube-system # 查看并验证节点信息是否成功 kubectl get nodes 添加node 在node节点上使用root用户运行下列命令将node节点加入到集群中，下面的密钥就是刚刚前面生成的密钥，而这个192.168.100.50:6443就是master节点的IP地址和对应端口。\nkubeadm join --token 3pyvba.siol4xy1rvf2om8k --discovery-token-ca-cert-hash sha256:c61cdd8b8ad5d34faece61867e76d539dbc243c1f206eacbab163895777c099e 192.168.100.50:6443 最后顺利启动之后应该是如下图所示\n提示：\n如果node节点上没有顺利出现running的话，很有可能是因为镜像被墙了无法顺利拉取镜像，这时我们需要按照步骤3.6中的操作来手动拉取镜像。\n查看pod运行情况的命令\nkubectl describe pod \u0026lt;PodName\u0026gt; --namespace=\u0026lt;namespace\u0026gt; 输出内容太长了，这里只截取一部分。\n","permalink":"http://localhost:9011/20190716-centos7-install-k8s/","summary":"\u003cp\u003e在虚拟机中的CentOS7集群中部署单master节点的k8s环境(v1.15)。\u003c/p\u003e","title":"在CentOS7上部署单Master节点k8s集群(v1.15)"},{"content":"解析k8s的重要概念和介绍其工作原理。\n简单介绍一下k8s中的重要概念和各个主要的工作组件，主要参考了官方的技术文档和《每天5分钟玩转kubernetes》。\n重要概念 k8s/kubernetes Kubernetes 源于希腊语，意为 “舵手” 或 “飞行员”， 且是英文governor和 cybernetic的词根。 K8s 是通过将 8 个字母ubernete替换为 8 而导出的缩写。\nk8s是谷歌在2014年发布的一个开源项目，其主要用途就是用来管理大量的容器。Kubernetes 建立在 Google 公司超过十余年的运维经验基础之上，Google 所有的应用都运行在容器上。在谷歌内部，使用的容器管理系统叫Borg，现在已经改名叫Omega。（就是数学里面经常用到的那个欧米茄符合的读音）所以k8s其实相当于是Borg的开源版本。\n我们再来看一下k8s官网的说法：\nKubernetes 是一个跨主机集群的 开源的容器调度平台，它可以自动化应用容器的部署、扩展和操作 , 提供以容器为中心的基础架构。\nKubernetes 具有如下特点:\n便携性: 无论公有云、私有云、混合云还是多云架构都全面支持 可扩展: 它是模块化、可插拔、可挂载、可组合的，支持各种形式的扩展 自修复: 它可以自保持应用状态、可自重启、自复制、自缩放的，通过声明式语法提供了强大的自修复能力 Cluster Cluster就是集群的意思，即整个k8s管理的所有机器和容器等等的总称。它是计算、存储和网络资源的集合，k8s利用这些资源运行各种基于容器的应用。\nMaster Master是Cluster中的管理者，即该集群中所有节点的老大或老大们，因为Master可能不止一个，如果是有多个Master节点的集群我们一般称之为高可用集群。它的主要职责是调度，即决定将应用放在哪里运行。\nNode Node的职责是运行容器应用。Node由Master管理，Node负责监控并汇报容器的状态，同时根据Master的要求管理容器的生命周期。\nPod Pod是Kubernetes的最小工作单元。每个Pod包含一个或多个容器。Pod中的容器会作为一个整体被Master调度到一个Node上运行。这意味着，即使是只有一个容器，Master也是要把它作为一个Pod调度运行的。\nKubernetes 引入Pod主要基于下面两个目的： （1）可管理性。 有些容器天生就是需要紧密联系，一起工作。Pod提供了比容器更高层次的抽象，将它们封装到一个部署单元中。Kubernetes以Pod为最小单位进行调度、扩展、共享资源、管理生命周期。 （2）通信和资源共享。 Pod 中的所有容器使用同一个网络namespace，即相同的IP地址和Port空间。它们可以直接用localhost通信。同样的，这些容器可以共享存储，当Kubernetes挂载 volume到Pod，本质上是将volume挂载到Pod中的每一个容器。\nnamespace Kubernetes支持由同一物理集群支持的多个虚拟集群。这些虚拟集群称为namespace。\nnamespace旨在用于多个用户分布在多个团队或项目中的环境中。对于具有几个到几十个用户的集群，您根本不需要创建或考虑名称空间。当您需要它们提供的功能时，请开始使用命名空间。\nnamespace提供名称范围。 资源名称在namespace中必须是唯一的，但是在不同的namespace中不必唯一。 namespace不能彼此嵌套，并且每个Kubernetes资源只能位于一个namespace中。 namespace是一种在多个用户之间划分群集资源的方法（通过资源配额）。 在Kubernetes的未来版本中，默认情况下，同一namespace中的对象将具有相同的访问控制策略。 没有必要使用多个namespace来分隔略有不同的资源，例如同一软件的不同版本：可以使用标签来区分同一namespace中的资源。 Master节点 Master 组件提供的集群控制。Master 组件对集群做出全局性决策(例如：调度)，以及检测和响应集群事件(副本控制器的replicas字段不满足时,启动新的副本)。\nMaster 组件可以在集群中的任何节点上运行。然而，为了简单起见，设置脚本通常会启动同一个虚拟机上所有 Master 组件，并且不会在此虚拟机上运行用户容器。\nkube-apiserver kube-apiserver对外暴露了Kubernetes API。它是的 Kubernetes 前端控制层。它被设计为水平扩展，即通过部署更多实例来缩放。\nAPI Server 提供HTTP/HTTPS RESTful API，即Kubernetes API。API Server是Kubernetes Cluster的前端接口，各种客户端工具（CLI或UI）以及Kubernetes其他组件可以通过它管理Cluster的各种资源。\netcd etcd用于 Kubernetes 的后端存储。etcd 负责保存Kubernetes Cluster的配置信息和各种资源的状态信息，始终为 Kubernetes 集群的 etcd 数据提供备份计划。当数据发生变化时，etcd 会快速地通知Kubernetes相关组件。\nkube-controller-manager kube-controller-manager运行控制器，它们是处理集群中常规任务的后台线程。逻辑上，每个控制器是一个单独的进程，但为了降低复杂性，它们都被编译成独立的可执行文件，并在单个进程中运行。\n这些控制器包括:\n节点控制器(Node Controller): 当节点移除时，负责注意和响应。 副本控制器(Replication Controller): 负责维护系统中每个副本控制器对象正确数量的 Pod。 端点控制器(Endpoints Controller): 填充 端点(Endpoints) 对象(即连接 Services \u0026amp; Pods)。 服务帐户和令牌控制器(Service Account \u0026amp; Token Controllers): 为新的namespace创建默认帐户和 API 访问令牌. kube-scheduler kube-scheduler主要的工作就是调度新创建的Pod，当集群中出现了新的Pod还没有确定分配到哪一个Node节点的时候，kube-scheduler会根据各个节点的负载，以及应用对高可用、性能、数据亲和性的需求等各个方面进行分析并将其分配到最合适的节点上。\nPod 网络 Pod 要能够相互通信，Kubernetes Cluster必须部署Pod网络，Pod网络也能算是属于虚拟化网络/SDN的一种，比较常见的有Calico、Flannel等，也有其他更复杂高级的提供同时多种组合网络如Canal、Knitter、Multus等。\nNode节点 节点组件在每个节点上运行，维护运行的 Pod 并提供 Kubernetes 运行时环境。\nkubelet kubelet是k8s集群中的每个节点上（包括master节点）都会运行的代理。 它能够保证容器都运行在 Pod 中。kubelet 不会管理不是由 Kubernetes 创建的容器。\nkubelet 接收一组通过各类机制提供给它的 PodSpecs，确保这些 PodSpecs 中描述的容器处于运行状态且健康。 当Scheduler确定在某个Node上运行Pod后，会将Pod的具体配置信息（image、volume等）发送给该节点的kubelet，kubelet根据这些信息创建和运行容器，并向Master报告运行状态。\nkube-proxy kube-proxy 是集群中每个节点上运行的网络代理， kube-proxy通过维护主机上的网络规则并执行连接转发，实现了Kubernetes服务抽象。\nservice在逻辑上代表了后端的多个Pod，外界通过service访问Pod。service接收到的请求就是通过kube-proxy转发到Pod上的，kube-proxy服务负责将访问service的TCP/UDP数据流转发到后端的容器。如果有多个副本，kube-proxy会实现负载均衡。\n","permalink":"http://localhost:9011/20190715-k8s-concepts-intro/","summary":"\u003cp\u003e解析k8s的重要概念和介绍其工作原理。\u003c/p\u003e\n\u003cp\u003e简单介绍一下k8s中的重要概念和各个主要的工作组件，主要参考了官方的技术文档和《每天5分钟玩转kubernetes》。\u003c/p\u003e","title":"k8s重要概念及各组件简介"},{"content":"在五台CentOS7的虚拟机上尝试安装Zabbix4.2用来监控各个主机的运行状况。\nmaster前期准备工作 关闭防火墙和SELinux sed -i \u0026#34;s/SELINUX=enforcing/SELINUX=disabled/g\u0026#34; /etc/selinux/config sed -i \u0026#39;s/SELINUXTYPE=targeted/#\u0026amp;/\u0026#39; /etc/selinux/config # 可以设置配置文件永久关闭 setenforce 0 # 更改selinux需要重启系统才会生效 reboot # 关闭防火墙 # 一般来说centos7里面默认没有iptables systemctl stop iptables.service systemctl stop firewalld.service 修改字符集 修改字符集，确保输出中文的时候不会报错\nlocaledef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 export LC_ALL=zh_CN.UTF-8 echo \u0026#39;LANG=zh_CN.UTF-8\u0026#39; \u0026gt; /etc/locale.conf master安装Mariadb 安装Mariadb 需要安装一个数据库来存储zabbix监控时产生的数据，在centos7中，mysql被换成了mariadb\nyum install mariadb mariadb-devel mariadb-server systemctl enable mariadb systemctl start mariadb 导入zabbix初始数据 这里是官方的指导文档\nhttps://www.zabbix.com/documentation/4.2/manual/appendix/install/db_scripts\n-- 设置数据库root密码 mysqladmin -u root -h localhost password \u0026#39;xxxxxxxx\u0026#39; -- 进入mysql创建zabbix数据库 mysql -u root -p -- 创建zabbix数据库并设定字符集和校对集为UTF8 create database zabbix character set utf8 collate utf8_bin; -- 创建zabbix用户并授权zabbix用户对zabbix数据库有完全操作权限 grant all privileges on zabbix.* to zabbix@localhost identified by \u0026#39;xxxxxxxx\u0026#39;; master上安装zabbix-server 安装zabbix-server # 导入官网对应的centos7的rpm包 # 从链接中可以看到centos7和rhel7使用的版本是一样的 rpm -Uvh https://repo.zabbix.com/zabbix/4.2/rhel/7/x86_64/zabbix-release-4.2-1.el7.noarch.rpm yum clean all 官网的yum源有些时候可能会连不上，我们也可以使用阿里云的镜像源。\n[zabbix] name=Zabbix Official Repository - $basearch baseurl=https://mirrors.aliyun.com/zabbix/zabbix/4.2/rhel/7/$basearch/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591 [zabbix-non-supported] name=Zabbix Official Repository non-supported - $basearch baseurl=https://mirrors.aliyun.com/zabbix/non-supported/rhel/7/$basearch/ enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX gpgcheck=1 yum install zabbix-server-mysql zabbix-web-mysql zabbix-agent 修改配置文件 # 修改server的配置文件，填入刚刚的数据库账户密码和数据库名 vim /etc/zabbix/zabbix_server.conf DBHost=localhost DBName=zabbix DBUser=zabbix DBPassword=xxxxxxxx # 修改PHP时区 vim /etc/httpd/conf.d/zabbix.conf # 将下面这行修改为亚洲上海 php_value date.timezone Asia/Shanghai 修改防火墙设置 # 添加httpd的80、zabbix-server的10051和zabbix-agent的10050监听端口 firewall-cmd --permanent --add-port=80/tcp firewall-cmd --permanent --add-port=10050/tcp firewall-cmd --permanent --add-port=10051/tcp # 查看是否成功 firewall-cmd --permanent --list-all 最后我们需要重启zabbix服务、httpd服务和firewall服务使得修改的配置生效。\nsystemctl restart zabbix-server zabbix-agent httpd firewalld systemctl enable zabbix-server zabbix-agent httpd firewalld 导入数据库 # 将zabbix初始数据导入到刚刚创建的zabbix数据库中 zcat /usr/share/doc/zabbix-server-mysql*/create.sql.gz | mysql -uzabbix -p\u0026#39;xxxxxxxx\u0026#39; zabbix 设置zabbix-server的web端 在和master同一局域网内的任意电脑的浏览器上输入zabbix-server的IP地址加上/zabbix\n点击next step，我们回发现有一堆的前置要求，如无意外，应该全部都是ok\n接下来我们输入之前在数据库中创建的zabbix用户的账号密码\nname这里我们输入一个自定义的名字方便确认，当然也可以不输入留空\n最后我们确认一下即可。\n我们可以看到上面的所有配置都保存在了/etc/zabbix/web/zabbix.conf.php这个配置文件中，需要的话我们可以随时修改。\n默认的账号是Admin密码是zabbix,注意A要大写。\n接下来我们要修改密码并开启中文支持\n然后重新登录就能够生效了。\n如果在语言列表里面没有找到中文的，检查一下/usr/share/zabbix/include/locales.inc.php这个文件的chinese这一栏是否为true，如果不是，修改之后重启zabbix即可。\n这里补充说明一下，如果需要在外网能够访问zabbix的web监控端，需要给该master分配公网IP或者是端口转发或者是设置frp反向代理。\nagent上安装zabbix-agent 安装zabbix-agent 这里我们基本是重复上面3.1的步骤，但是最后安装的模块有点不同。\nrpm -Uvh https://repo.zabbix.com/zabbix/4.2/rhel/7/x86_64/zabbix-release-4.2-1.el7.noarch.rpm yum clean all yum install -y zabbix-agent zabbix-get 修改zabbix-agent配置文件 vim /etc/zabbix/zabbix_agentd.conf # 然后我们修改下列内容 # zabbix-server内网IP Server=zabbix-server内网IP # zabbix-server内网IP，Active表示agent主动推送 ServerActive=zabbix-server内网IP # zabbix-agent节点名称，需要和web端添加的名称一致 Hostname=该agent节点的命名 # 启动zabbix-agent并设置开机启动 systemctl start zabbix-agent systemctl enable zabbix-agent # 查看zabbix-agent运行状态 systemctl status zabbix-agent 修改防火墙设置 # 添加zabbix-agent的10050监听端口例外 firewall-cmd --permanent --add-port=10050/tcp # 查看是否成功 firewall-cmd --permanent --list-all # 重启firewall和zabbix systemctl restart firewalld.service zabbix-agent.service 在web端添加监控主机 登录web端，点击配置→主机→创建主机\n注意这里的主机名称要和配置文件中的hostname一致\n模板这里有提供很多预设的监控模板，我们可以直接选择\n最后我们点击主机下面的添加，就可以成功添加主机了。\n最后的整体效果如下\n","permalink":"http://localhost:9011/20190712-centos7-install-zabbix42/","summary":"\u003cp\u003e在五台CentOS7的虚拟机上尝试安装Zabbix4.2用来监控各个主机的运行状况。\u003c/p\u003e","title":"在CentOS7上安装Zabbix4.2监控系统"},{"content":"RHEL7中的ACL的简单介绍。\nACL简介 ACL是Access Control List的缩写，主要的目的是在提供传统的owner，group，others的read(r)，write(w)，execute(x) 权限之外的细部权限设定。\nACL可以针对单一使用者，单一文件或目录来进行r，w，x的权限规范，对于需要特殊权限的使用状况非常有帮助。 ACL主要可以针对以下三者：\n使用者（user）：可以针对使用者来设定权限 群组（group）：针对群组为对象来设定其权限 默认属性（mask）：还可以针对在该目录下在建立新文件/目录时，规范新数据的默认权限 也就是说，如果你有一个目录，需要给一堆人使用，每个人或每个群组所需要的权限并不相同时，在过去，传统的Linux三种身份的三种权限是无法达到的，**因为基本上，传统的Linux权限只能针对一个用户、一个群组及非此群组的其他人设定权限而己，无法针对单一用户或个人来设计权限。**而ACL的出现就是为了解决这个问题，实现更加细化的账户文件权限控制。\nsetfacl ACL的实现主要是通过setfacl和getfacl命令来操作。\ngetfacl：取得某个文件/目录的ACL设定项目 setfacl：设定某个文件/目录的ACL规范 getfacl的使用和setfacl一样，主要是用来查看acl权限，因此我们主要来看一下setfacl的用法。\nsetfacl的英文全称是set file access control list ,即“设置文件访问控制列表”。改命令可以更精确的控制权限的分配，比如让某一个用户对某一个文件具有某种权限。\nACL指文件的所有者、所属组、其他人的读/写/执行之外的特殊的权限， 对于需要特殊权限的使用状况有一定帮助。 如，某一个文件，不让单一的某个用户访问。\n语法格式：\nsetfacl [-bkndRLP] { -m|-M|-x|-X ... } file ... 我们输入setfacl -h查看一下帮助文档，可以看到里面的参数比较多\nsetfacl 2.2.51 -- set file access control lists Usage: setfacl [-bkndRLP] { -m|-M|-x|-X ... } file ... -m, --modify=acl modify the current ACL(s) of file(s) -M, --modify-file=file read ACL entries to modify from file -x, --remove=acl remove entries from the ACL(s) of file(s) -X, --remove-file=file read ACL entries to remove from file -b, --remove-all remove all extended ACL entries -k, --remove-default remove the default ACL --set=acl set the ACL of file(s), replacing the current ACL --set-file=file read ACL entries to set from file --mask do recalculate the effective rights mask -n, --no-mask don\u0026#39;t recalculate the effective rights mask -d, --default operations apply to the default ACL -R, --recursive recurse into subdirectories -L, --logical logical walk, follow symbolic links -P, --physical physical walk, do not follow symbolic links --restore=file restore ACLs (inverse of `getfacl -R\u0026#39;) --test test mode (ACLs are not modified) -v, --version print version and exit -h, --help this help text 这里需要说明一下，-m和-M以及-x和-X的区别就在于，**小写字母是后面直接跟需要设置的acl规则，而大写字母则是在后面跟着一个文件，文件里面写着acl规则，**后者适用于比较复杂的acl规则设定情况。\n接下来我们看一些比较常用的选项参数：\nsetfacl [-bkRd] [{-ml-x）ac1参数]目标文件名选项与参数： -m：设定后续的ac1参数给文件使用，不可与-x合用； -x：删除后续的ac1参数，不可与-m合用； -b：移除[所有的]ACL设定参数； -k：移除[预设的]ACL参数，关于所谓的预设]参数于后续范例中介绍； -R：递归设定acl，亦即包括次目录都会被设定起来； -d：设定预设ac1参数]的意思！只对目录有效，在该目录新建的数据会引用此默认值 实例示范 # 设置用户tinychen对acltest1有读写执行权限 setfacl -m u:tinychen:rwx acltest1 # 设置用户tinychen对acltest2无任何权限 setfacl -m u:tinychen:- acltest2 # 设置用户组tinychen对acltest3无任何权限 setfacl -m g:tinychen:- acltest3 设定了ACL权限之后，我们查看该文件权限的时候会发现多了一个+号。\n然后我们使用getfacl命令查看文件的权限:\n# 删除acltest1的所有acl权限设置 setfacl -b acltest1 getfacl acltest1 ","permalink":"http://localhost:9011/20190711-rhel7-acl/","summary":"\u003cp\u003eRHEL7中的ACL的简单介绍。\u003c/p\u003e\n\u003ch2 id=\"acl简介\"\u003eACL简介\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eACL\u003c/code\u003e是\u003ccode\u003eAccess Control List\u003c/code\u003e的缩写，主要的目的是在提供传统的\u003ccode\u003eowner\u003c/code\u003e，\u003ccode\u003egroup\u003c/code\u003e，\u003ccode\u003eothers\u003c/code\u003e的\u003ccode\u003eread(r)\u003c/code\u003e，\u003ccode\u003ewrite(w)\u003c/code\u003e，\u003ccode\u003eexecute(x)\u003c/code\u003e 权限之外的细部权限设定。\u003c/p\u003e\n\u003cp\u003eACL可以针对单一使用者，单一文件或目录来进行r，w，x的权限规范，对于需要特殊权限的使用状况非常有帮助。\nACL主要可以针对以下三者：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e使用者（user）：可以针对使用者来设定权限\u003c/li\u003e\n\u003cli\u003e群组（group）：针对群组为对象来设定其权限\u003c/li\u003e\n\u003cli\u003e默认属性（mask）：还可以针对在该目录下在建立新文件/目录时，规范新数据的默认权限\u003c/li\u003e\n\u003c/ul\u003e","title":"RHEL7的ACL简介"},{"content":"RHEL7中的CRON功能以及使用方式简介。\ncron简介 crond 是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程，与windows下的计划任务类似，当安装完成操作系统后，默认会安装此服务 工具，并且会自动启动crond进程，crond进程每分钟会定期检查是否有要执行的任务，如果有要执行的任务，则自动执行该任务。\n注意Cron在设置定时任务的时候，最多只能精确到分钟而无法精确到秒。\nLinux下的任务调度分为两类，系统任务调度和用户任务调度。\n系统任务调度：系统周期性所要执行的工作，比如写缓存数据到硬盘、日志清理等。/etc/crontab文件是系统任务调度的配置文件。\n用户任务调度：用户定期要执行的工作，比如用户数据备份、定时邮件提醒等。用户可以使用 crontab 工具来定制自己的计划任务。所有用户定义的crontab 文件都被保存在 /var/spool/cron目录中。其文件名与用户名一致。\ncron安装 # cron的安装进程是cronie yum install -y cronie # 设置cron开机启动 systemctl enable crond.service # 关闭cron开机启动 systemctl disable crond.service # 启动cron systemctl start crond.service # 查看cron运行状态 systemctl status crond.service # 重启cron systemctl restart crond.service # 关闭cron systemctl stop crond.service cron相关文件 我们先看一下在/etc目录下和cron相关的文件主要有哪些，接下来我们逐个对其进行分析。\n/etc/crontab 我们cat一下/etc/crontab这个文件看看里面的内容\n# 使用哪种she11接口 SHELL=/bin/bash # 执行文件搜寻路径 PATH=/sbin:/bin:/usr/sbin:/usr/bin # 若有额外STDOUT，以email将数据送给谁 MAILTO=root # For details see man 4 crontabs # Example of job definition: # .---------------- minute (0 - 59) # | .------------- hour (0 - 23) # | | .---------- day of month (1 - 31) # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat # | | | | | # * * * * * user-name command to be executed **MAILTO=root：**这个项目是说，当/etc/crontab这个文件中的例行性工作的指令发生错误时，或者是该工作的执行结果有STDOUT/STDERR时，默认是由系统直接寄发一封mail给root。由于root 并无法在客户端中以POP3之类的协议接收邮件，因此你也可以改成自己的邮箱。 **PATH：**这里就是输入执行文件的搜寻路径，一般使用默认的路径即可 **分-时-日-月-周-执行用户-执行指令：**七个字段的设定，这个/etc/crontab里面可以设定的基本语法就在这个文件里面，当我们忘了格式的时候可以cat这个文件看一下，需要注意的是不填写的字段需要使用星号*来占位，同一个字段要写多个数字的时候用英文逗号隔开；执行用户方面，系统默认是以root的身份来进行的。 这里需要注意的是，在设定时间的时候，不能同时使用周和日月，即我们只能设定该任务在周几执行或者是几月几日执行，不能同时满足这两个条件，否则系统可能会无法正确识别。\n/etc/cron.allow和/etc/cron.deny /etc/cron.allow和/etc/cron.deny这两个文件主要是对使用cron的用户进行管理。\n这里可以分为四种情况。\n如果这两个文件都不存在，那么就只有root用户能够使用crontab命令 如果只存在cron.allow，那么就只有cron.allow上面的用户能使用crontab命令，如果root用户也不在里面，则root用户也不能使用crontab 如果只存在cron.deny，那么就只有cron.deny上面的用户不能使用crontab命令 如果两个文件都存在，则列在cron.allow文件中而且没有列在cron.deny中的用户可以使用crontab，**如果两个文件中都有同一个用户，以cron.allow文件里面是否有该用户为准，**如果cron.allow中有该用户，则可以使用crontab命令 crontab命令我们将在本文后面讲解\n/etc/cron.d /etc/cron.d/ 这个目录用来存放任何要执行的crontab文件或脚本\n/etc/cron.*ly/ cron.monthly/， cron.weekly/ ， cron.daily/ ， cron.hourly/这四个目录下面存放的文件会在对应的时间内被执行，如 cron.hourly/里面的文件会在每个小时被执行一次，需要注意的是，这里放置的是直接执行的脚本文件，而不是上述的crontab格式的定时任务描述文件。\n/var/spool/cron/ /var/spool/cron/目录下存放着每个用户自己的crontab文件。\ncrontab命令 **语法格式：**crontab [参数]\n常用参数：\n参数 含义 -e 编辑该用户的计时器设置 -l 列出该用户的计时器设置 -r 删除该用户的计时器设置 -u 指定要设定计时器的用户名称 –help 显示帮助信息 参考实例\n# 使用tinychen用户，每年的1月1日输出HappyNewYear！ crontab -e -u tinychen # 然后和vim一样的编辑模式，输入下列参数 0 0 1 1 * /bin/echo HappyNewYear! \u0026gt; /dev/pts/0 需要注意的是，和/etc/crontab文件不同，使用crontab命令进入到编辑模式的时候不需要再加执行用户这一参数，默认是使用当前的登录账户，如果需要指定，使用-u参数即可。\n此外，crontab中还有几种简化的写法需要我们了解一下\n*\t代表所有的取值范围内的数字 /\t代表每的意思,如/5表示每5个单位 -\t代表从某个数字到某个数字 ,\t分开几个离散的数字 需要注意的是，crontab -r是删除该用户的所有定时任务。\n小结 个人化的行为使用crontab-e：\n如果你是依据个人需求来建立的例行工作排程，建议直接使用crontab-e。这样也能保障你的指令行为不会被大家看到（/etc/crontab是大家都能读取的）；\n系统维护管理使用vim/etc/crontab：\n如果这个例行工作排程是系统的重要工作，为了让自己管理方便，同时容易追踪，建议直接写入/etc/crontab较佳！\n自己开发软件使用vim/etc/cron.d/newfile：\n如果是想要自己开发软件，那当然最好就是使用全新的配置文件，并且放置于/etc/cron.d/目录内即可。\n固定每小时、每日、每周、每天执行的特别工作：\n如果与系统维护有关，还是建议放置到/etc/crontab中来集中管理较好。如果想要偷懒，或者是一定要再某个周期内进行的任务，也可以放置到上面谈到的几个目录中，直接写入指令即可！\n","permalink":"http://localhost:9011/20190710-rhel7-cron/","summary":"\u003cp\u003eRHEL7中的CRON功能以及使用方式简介。\u003c/p\u003e\n\u003ch2 id=\"cron简介\"\u003ecron简介\u003c/h2\u003e\n\u003cp\u003ecrond 是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程，与windows下的计划任务类似，当安装完成操作系统后，默认会安装此服务 工具，并且会自动启动crond进程，crond进程\u003cstrong\u003e每分钟\u003c/strong\u003e会定期检查是否有要执行的任务，如果有要执行的任务，则自动执行该任务。\u003c/p\u003e","title":"RHEL7中的CRON简介"},{"content":"K8S部署的前期准备工作，多台CentOS主机之间使用SSH免密登录。\n实现原理 这里我们需要实现的一共有两个效果，一个是这五台CentOS主机之间任意两台之间都能使用ssh免密登录，另一个就是能够直接使用 ssh hostname 的方式登录而不需要使用 ssh account@ip_addr的方式登录。\nrsa密钥对 一般来说我们的SSH使用的是RSA类型的密钥对，使用ssh-keygen默认生成的也是RSA类型的密钥对。一般来说会产生两个文件，分别是id_rsa和id_rsa.pub,其中前者是私钥，后者是公钥。私钥不能泄露，而公钥是放置在目标主机的authorized_keys文件中，从而实现免密登录的效果。\n要实现五台CentOS主机之间任意两台之间都能使用ssh免密登录，只需要将这五台主机的id_rsa.pub都存放到一个authorized_keys文件中，然后再将这个文件复制到每一台主机上即可。\nhosts文件 CentOS中的/etc/hosts文件中存放着主机名和对应的ip地址等信息，想要实现直接使用 ssh hostname 的方式登录的效果，只要在hosts文件中添加ip地址和对应hostname（主机名）即可。\n操作步骤 安装启用ssh # master和每台node节点上安装ssh sudo yum install -y openssl openssh-server # master和每台node节点上启动ssh并设置开机自启 systemctl start sshd.service systemctl enable sshd.service 生成密钥对 # master和每台node节点上都生成ssh密钥,接下来一路回车 ssh-keygen 需要注意的是，这里提示输入的passphrase相当于ssh密钥对的密码，不输入的话直接回车跳过即可，输入了之后，每次使用ssh免密登录的时候会再要求输入passphrase以保证安全。\n# 将每台node节点的id_rsa.pub复制到master上面，注意要重新命名避免覆盖 scp /home/tinychen/.ssh/id_rsa.pub tinychen@192.168.100.50:/home/tinychen/.ssh/node5* # 在master上使用cat命令将master和所有node节点的id_rsa.pub追加到authorized_keys中 cat id_rsa.pub \u0026gt;\u0026gt; authorized_keys cat node51 \u0026gt;\u0026gt; authorized_keys cat node52 \u0026gt;\u0026gt; authorized_keys cat node53 \u0026gt;\u0026gt; authorized_keys cat node54 \u0026gt;\u0026gt; authorized_keys 配置sshd_config # 编辑master的ssh配置文件，去掉红圈中三项前面的#使其生效 sudo vim /etc/ssh/sshd_config 编辑hosts # 编辑master节点的/etc/hosts文件，追加ip地址和对应的主机名，使得能够直接使用ssh+主机名的方式登录 sudo vim /etc/hosts 192.168.100.50 master50 192.168.100.51 node51 192.168.100.52 node52 192.168.100.53 node53 192.168.100.54 node54 复制master的配置文件 # 将master节点的ssh配置文件复制到每一个节点上 # 这里必须使用root账户，否则权限不足 sudo scp /etc/ssh/sshd_config root@192.168.100.51:/etc/ssh/sshd_config sudo scp /etc/ssh/sshd_config root@192.168.100.52:/etc/ssh/sshd_config sudo scp /etc/ssh/sshd_config root@192.168.100.53:/etc/ssh/sshd_config sudo scp /etc/ssh/sshd_config root@192.168.100.54:/etc/ssh/sshd_config # 将master节点的authorized_keys文件复制到每一个节点上 scp /home/tinychen/.ssh/authorized_keys tinychen@192.168.100.51:/home/tinychen/.ssh/authorized_keys scp /home/tinychen/.ssh/authorized_keys tinychen@192.168.100.52:/home/tinychen/.ssh/authorized_keys scp /home/tinychen/.ssh/authorized_keys tinychen@192.168.100.53:/home/tinychen/.ssh/authorized_keys scp /home/tinychen/.ssh/authorized_keys tinychen@192.168.100.54:/home/tinychen/.ssh/authorized_keys 修改ssh文件权限 # 对master和每一个node节点都进行ssh相关文件权限的修改 sudo chmod 700 /home/tinychen/.ssh/ sudo chmod 600 /home/tinychen/.ssh/* 验证效果 需要注意的是，第一次ssh登录的时候，因为known_hosts文件里面没有保存这台主机的对应信息，我们需要输入yes才能登录，之后便不再需要。\n此外，如果使用ssh+主机名的方式登录，两台主机必须是使用的同一个用户，这里我使用的是tinychen用户，由于我已经在ssh的配置文件中修改了配置允许ssh使用root账户登录，因此这里我们使用管理员账户tinychen，如果有需要了再切换到root账户，保证安全性\n补充 # 如果想要实现root账户之间的ssh免密登录，将.ssh文件夹整个复制过去即可 rm -rf /root/.ssh/ cp -r /home/tinychen/.ssh/ /root/ chmod 700 /root/.ssh/ chmod 600 /root/.ssh/* ","permalink":"http://localhost:9011/20190709-multi-ssh/","summary":"\u003cp\u003eK8S部署的前期准备工作，多台CentOS主机之间使用SSH免密登录。\u003c/p\u003e","title":"多主机间实现SSH免密登录"},{"content":"Linux中的七种启动级别介绍及CentOS7修改默认启动级别的方法。\nLinux的7种启动级别 代号 含义 0 关机 1 单用户 2 多用户（无NFS） 3 多用户（有NFS） 4 未使用，保留给用户 5 GUI图形化模式 6 正常关闭系统并重新启动 一般来说我们使用的比较多的是3或者5，现在默认的一般都是5，启用了GUI图形化界面，方便用户操作，但是有的时候我们觉得GUI太占内存且启动太慢，又或者觉得没有必要启用GUI，就可以使用init命令进行切换。\n# 关机 init 0 # 切换到命令行界面 init 3 # 切换到GUI界面 init 5 # 重启 init 6 修改系统默认启动级别 在CentOS中，一般是/etc/inittab这个文件对系统启动级别进行管理，但是到了CentOS7中，由于引入了新的管理工具systemctl，这个文件已经被弃用，我们打开这个文件可以看到：\n# inittab is no longer used when using systemd. # # ADDING CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM. # # Ctrl-Alt-Delete is handled by /usr/lib/systemd/system/ctrl-alt-del.target # # systemd uses \u0026#39;targets\u0026#39; instead of runlevels. By default, there are two main targets: # # multi-user.target: analogous to runlevel 3 # graphical.target: analogous to runlevel 5 # # To view current default target, run: # systemctl get-default # # To set a default target, run: # systemctl set-default TARGET.target # 也就是说我们需要使用systemctl set-default来设置默认的启动级别。\n# 查看默认启动级别 systemctl get-default # 设置默认启动级别为init 3 多用户（NFS）的命令行界面 systemctl set-default multi-user.target # 设置默认启动级别为init 5 GUI图形化界面 systemctl set-default graphical.target 重启之后我们就能看到系统已经以我们修改后的启动级别来运行了。\n","permalink":"http://localhost:9011/20190708-rhel7-change-init-level/","summary":"\u003cp\u003eLinux中的七种启动级别介绍及CentOS7修改默认启动级别的方法。\u003c/p\u003e","title":"CentOS7修改默认启动级别"},{"content":"RHEL7中的rpm和yum的简单介绍。\nRPM和dpkg简介 目前在Linux界软件安装方式最常见的有两种，分别是：\ndpkg 这个机制最早是由Debian Linux社群所开发出来的，透过dpkg的机制，Debian提供的软件就能够简单的安装起来，同时还能提供安装后的软件信息，实在非常不错。只要是衍生于Debian的其他Linux distributions大多使用dpkg这个机制来管理软件的，包括B2D，Ubuntu等等。 RPM 这个机制最早是由Red Hat 这家公司开发出来的，后来实在很好用，因此很多distributions就使用这个机制来作为软件安装的管理方式。包括Fedora，CentOS，SuSE等等知名的开发商都是用的RPM。 如前所述，不论dpkg/rpm这些机制或多或少都会有软件依赖的问题，那该如何解决呢？其实前面不是谈到过每个软件文件都有提供对应依赖的的检查吗？那么如果我们将每个软件对应依赖的数据做成列表，等到实际软件安装时，若发生需要安装依赖的情况，例如安装A需要先安装B与C，而安装B则需要安装D与E时，那么当你要安装A，通过查询依赖的数据列表，管理机制自动去取得B，C，D，E来同时安装，就可以很方便的解决依赖的了。\n在dpkg管理机制上就开发出APT的在线升级机制，RPM则依开发商的不同，有Red Hat系统的yum，SuSE系统的Yast Online Update（YOU）等。\ndistribution代表 软件管理机制 使用指令 在线升级机制（指令） Red Hat/Fedora RPM rpm yum(yum) Debian/Ubuntu dpkg dpkg apt(apt-get) RPM和SRPM RPM RPM全名是RedHat Package Manager，简称RPM。顾名思义，当初这个软件管理的机制是由Red Hat 这家公司发展出来的。RPM是以一种数据库记录的方式来将你所需要的软件安装到你的Linux系统的一套管理机制。\nRPM最大的特点就是将你要安装的软件先编译过，并且打包成为RPM机制的包装文件，透过包装好的软件里头默认的数据库记录，记录这个软件要安装的时候必须具备的依赖软件，当安装在你的Linux主机时，RPM会先依照软件里头的数据查询Linux主机的所需依赖软件是否满足，若满足则予以安装，若不满足则不予安装。那么安装的时候就将该软件的信息整个写入RPM的数据库中，以便未来的查询、验证与卸载！这样一来的优点是：\n由于已经编译完成并且打包完毕，所以软件传输与安装上很方便（不需要再重新编译）； 由于软件的信息都已经记录在Linux主机的数据库上，很方便查询、升级与卸载。 由于RPM文件是已经编译完成了的，所以，该软件文件几乎只能安装在原本默认的硬件与操作系统版本中。也就是说，你的主机系统环境必须要与当初建立这个软件文件的主机环境相同才行！\n举例来说，rp-pppoe这个ADSL拨接软件，他必须要在ppp这个软件存在的环境下才能进行安装！如果你的主机并没有ppp这个软件，那么很抱歉，除非你先安装ppp否则rp-pppoe就是不让你安装的（当然你可以强制安装，但是通常都会有点问题发生就是了！）。\nSRPM SRPM就是Source RPM的意思，也就是这个RPM文件里面含有源码。也就是说这个SRPM所提供的软件内容并没有经过编译，它提供的是原始码。\n通常SRPM的扩展名是以***src.rpm这种格式来命名的。**需要注意的是，虽然RPM提供的是原始码，但是他仍然含有该软件所需要的依赖软件说明、以及所有RPM文件所提供的数据。同时，他与RPM不同的是，他也提供了参数配置文件（就是configure与makefile）。**所以，如果我们下载的是SRPM，那么要安装该软件时，你就必须要：\n先将该软件以RPM管理的方式编译，此时SRPM会被编译成为RPM文件； 然后将编译完成的RPM文件安装到Linux系统当中 通常一个软件在释出的时候，都会同时释出该软件的RPM与SRPM。我们现在知道RPM文件必须要在相同的Linux环境下才能够安装，而SRPM既然是原始码的格式，我们可以修改SRPM内的参数配置文件，然后重新编译产生能适合我们Linux环境的RPM文件。\nx86和noarch 一般对应的rpm包会有不同的几种平台对应，下表列出了比较常见的一些平台，需要注意的是，现在基本已经全部步入了64位时代(主要是服务器、桌面端和移动端)，==主打服务器端的CentOS7已经不再发布32位版本，因此在CentOS7上面见到的主要是x86_64和noarch。==\n平台 说明 i386 几乎适用于所有的x86平台，不论是旧的pentum或者是新的Intel Core2与K8系列的CPU等等，都可以正常的工作。那个i指的是Intel兼容的CPU的意思。386就是CPU的等级。 i586 就是针对586等级的计算机进行优化编译。那是哪些CPU呢？包括pentum第一代MMXCPU，AMD的K5，K6系列CPU（socket7插脚）等等的CPU都算是这个等级； i686 在pentunll以后的Intel系列CPU，及K7以后等级的CPU都属于这个686等级！由于目前市面上几乎仅剩P-ll以后等级的硬件平台，因此很多distributions都直接释出这种等级的RPM文件。 x86_64 针对64位的CPU进行优化编译设定，包括Intel的Core 2以上等级CPU，以及AMD的Athlon64以后等级的CPU，都属于这一类型的硬件平台。 noarch noarch是no architecture的缩写，说明这个包可以在各个不同的cpu上使用。一般来说，这种类型的RPM文件，里面应该没有binary program存在，较常出现的就是属于shell script方面的软件。 Yum yum简介 当客户端有升级、安装的需求时，yum会向软件库要求清单的更新，等到清单更新到本机的 /var/cache/yum里面后，等一下更新时就会用这个本机清单与本机的RPM数据库进行比较，这样就知道该下载什么软件。接下来yum会跑到软件库服务器（yum server）下载所需要的软件（因为有记录软件所在的网址），然后再透过RPM的机制开始安装软件。这就是yum安装软件的整个流程。\nyum的repo文件一般在/etc/yum.repo.d/目录下，如图所示就是小七的CentOS7中的yum repo list\nyum update和upgrade # 升级所有包同时也升级软件和系统内核 yum update # 只升级所有包，不升级软件和系统内核 yum upgrade yum repo文件构成 一般来说，一个yum repo文件由下面的几部分构成\n# 必须是独一无二的ID来标记这个yum repo，如果重复了，后面的会覆盖掉前面的 [serverid] # 添加一段文字来描述这个yum repo name=Description # 设置资源库的地址，可以是http或者ftp等多种形式 baseurl={ftp://|http://|file://} # enabled=1开启本地更新模式 enabled={1|0} # gpgcheck=1表示检查；可以不检查gpgcheck=0 gpgcheck={1|0} # 检查的key；如果上面不检查这一行可以不写 # gpgkey的地址也可以是http或者ftp等多种形式 gpgkey= ","permalink":"http://localhost:9011/20190708-rhel7-yum-repo/","summary":"\u003cp\u003eRHEL7中的rpm和yum的简单介绍。\u003c/p\u003e","title":"RHEL7的yum简介"},{"content":"RHEL7中的SELinux简单介绍。\nSELinux简介 SELinux，Security Enhanced Linux 的缩写，也就是安全强化的 Linux，是由美国国家安全局（NSA）联合其他安全机构（比如 SCC 公司）共同开发的，旨在增强传统 Linux 操作系统的安全性，解决传统 Linux 系统中自主访问控制（DAC）系统中的各种权限问题（如 root 权限过高等）。\nSELinux 项目在 2000 年以 GPL 协议的形式开源，当 Red Hat 在其 Linux 发行版本中包括了 SELinux 之后，SELinux 才逐步变得流行起来。现在，SELinux 已经被许多组织广泛使用，几乎所有的 Linux 内核 2.6 以上版本，都集成了 SELinux 功能。\n对于 SELinux，初学者可以这么理解，它是部署在 Linux 上用于增强系统安全的功能模块。\n我们知道，传统的 Linux 系统中，默认权限是对文件或目录的所有者、所属组和其他人的读、写和执行权限进行控制，这种控制方式称为自主访问控制（DAC）方式；而在 SELinux 中，采用的是强制访问控制（MAC）系统，也就是控制一个进程对具体文件系统上面的文件或目录是否拥有访问权限，而判断进程是否可以访问文件或目录的依据，取决于 SELinux 中设定的很多策略规则。\n说到这里，读者有必要详细地了解一下这两个访问控制系统的特点：\n不过，系统中有这么多的进程，也有这么多的文件，如果手工来进行分配和指定，那么工作量过大。所以 SELinux 提供了很多的默认策略规则，这些策略规则已经设定得比较完善，我们稍后再来学习如何查看和管理这些策略规则。\n**自主访问控制系统（Discretionary Access Control，DAC）**是 Linux 的默认访问控制方式，也就是依据用户的身份和该身份对文件及目录的 rwx 权限来判断是否可以访问。不过，在 DAC 访问控制的实际使用中我们也发现了一些问题： root 权限过高，rwx 权限对 root 用户并不生效，一旦 root 用户被窃取或者 root 用户本身的误操作，都是对 Linux 系统的致命威胁。 Linux 默认权限过于简单，只有所有者、所属组和其他人的身份，权限也只有读、写和执行权限，并不利于权限细分与设定。 不合理权限的分配会导致严重后果，比如给系统敏感文件或目录设定 777 权限，或给敏感文件设定特殊权限——SetUID 权限等。 **强制访问控制（Mandatory Access Control，MAC）**是通过 SELinux 的默认策略规则来控制特定的进程对系统的文件资源的访问。也就是说，即使你是 root 用户，但是当你访问文件资源时，如果使用了不正确的进程，那么也是不能访问这个文件资源的。 这样一来，SELinux 控制的就不单单只是用户及权限，还有进程。每个进程能够访问哪个文件资源，以及每个文件资源可以被哪些进程访问，都靠 SELinux 的规则策略来确定。\nSELinux作用 注意，在 SELinux 中，Linux 的默认权限还是有作用的，也就是说，一个用户要能访问一个文件，既要求这个用户的权限符合 rwx 权限，也要求这个用户的进程符合 SELinux 的规定。\n不过，系统中有这么多的进程，也有这么多的文件，如果手工来进行分配和指定，那么工作量过大。所以 SELinux 提供了很多的默认策略规则，这些策略规则已经设定得比较完善，我们稍后再来学习如何查看和管理这些策略规则。\n为了使读者清楚地了解 SELinux 所扮演的角色，这里举一个例子，假设 apache 上发现了一个漏洞，使得某个远程用户可以访问系统的敏感文件（如 /etc/shadow）。如果我们的 Linux 中启用了 SELinux，那么，因为 apache 服务的进程并不具备访问 /etc/shadow 的权限，所以这个远程用户通过 apache 访问 /etc/shadow文件就会被 SELinux 所阻挡，起到保护 Linux 系统的作用 。\n需要注意的是，SELinux 的 MAC 并不会完全取代 DAC，恰恰相反，对于 Linux 系统安全来说，它是一个额外的安全层，换句话说，当使用 SELinux 时，DAC 仍然被使用，且会首先被使用，如果允许访问，再使用 SELinux 策略；反之，如果 DAC 规则拒绝访问，则根本无需使用 SELinux 策略。 例如，若用户尝试对没有执行权限（rw-）的文件进行执行操作，那么传统的 DAC 规则就会拒绝用户访问，因此，也就无需再使用 SELinux 策略。\n相比传统的 Linux DAC 安全控制方式，SELinux 具有诸多好处，比如说：\n它使用的是 MAC 控制方式，这被认为是最强的访问控制方式； 它赋予了主体（用户或进程）最小的访问特权，这也就意味着，每个主体仅被赋予了完成相关任务所必须的一组有限的权限。通过赋予最小访问特权，可以防止主体对其他用户或进程产生不利的影响； SELinux 管理过程中，每个进程都有自己的运行区域（称为域），各进程仅运行在自己的域内，无法访问其他进程和文件，除非被授予了特殊权限。 SELinux 可以调整到 Permissive 模式，此模式允许查看在系统上执行 SELinux 后所产生的印象。在 Permissive 模式中，SELinux 仍然会记录它所认为的安全漏洞，但并不会阻止它们。 其实，想要了解 SELinux 的优点，最直接的办法就是查看当 Linux 系统上没有运行 SELinux 时会发生什么事情。\n根据相关的所有者和所属组的rwx权限，可以访问任何文件或目录； 完成存在安全隐患的活动，比如允许上传文件或更改系统显示； 可以监听任何端口的传入请求。 但在一个受 SELinux 约束的系统上，httpd 守护进程受到了更加严格的控制。仍然使用上面的示例，httped仅能监听 SELinux 允许其监听的端口。SELinux 还可以防止 httpd 访问任何没有正确设置安全上下文的文件，并拒绝没有再 SELinux 中显式启用的不安全活动。\n因此，从本质上讲，SELinux 最大程序上限制了 Linux 系统中的恶意代码活动。\nSELinux工作模式 基本概念解释 在解释 SELinux 的工作模式之前，先解释几个概念。\n主体（Subject）：就是想要访问文件或目录资源的进程。想要得到资源，基本流程是这样的：由用户调用命令，由命令产生进程，由进程去访问文件或目录资源。在自主访问控制系统中（Linux 默认权限中），靠权限控制的主体是用户；而在强制访问控制系统中（SELinux 中），靠策略规则控制的主体则是进程。\n目标（Object）：这个概念比较明确，就是需要访问的文件或目录资源。\n策略（Policy）：Linux 系统中进程与文件的数量庞大，那么限制进程是否可以访问文件的 SELinux 规则数量就更加烦琐，如果每个规则都需要管理员手工设定，那么 SELinux 的可用性就会极低。还好我们不用手工定义规则，SELinux 默认定义了两个策略，规则都已经在这两个策略中写好了，默认只要调用策略就可以正常使用了。这两个默认策略如下\ntargeted：这是 SELinux 的默认策略，这个策略主要是限制网络服务的，对本机系统的限制极少。我们使用这个策略已经足够了。 mls：多级安全保护策略，这个策略限制得更为严格。 安全上下文（Security Context）：每个进程、文件和目录都有自己的安全上下文，进程具体是否能够访问文件或目录，就要看这个安全上下文是否匹配。如果进程的安全上下文和文件或目录的安全上下文能够匹配，则该进程可以访问这个文件或目录。当然，判断进程的安全上下文和文件或目录的安全上下文是否匹配，则需要依靠策略中的规则。\n举个例子，我们需要找对象，男人可以看作主体，女人就是目标了。而男人是否可以追到女人（主体是否可以访问目标），主要看两个人的性格是否合适（主体和目标的安全上下文是否匹配）。不过，两个人的性格是否合适，是需要靠生活习惯、为人处世、家庭环境等具体的条件来进行判断的（安全上下文是否匹配是需要通过策略中的规则来确定的）。\n解释一下这张示意图：当主体想要访问目标时，如果系统中启动了 SELinux，则主体的访问请求首先需要和 SELinux 中定义好的策略进行匹配。如果进程符合策略中定义好的规则，则允许访问，这时进程的安全上下文就可以和目标的安全上下文进行匹配；如果比较失败，则拒绝访问，并通过 AVC（Access Vector Cache，访问向量缓存，主要用于记录所有和 SELinux 相关的访问统计信息）生成拒绝访问信息。如果安全上下文匹配，则可以正常访问目标文件。当然，最终是否可以真正地访问到目标文件，还要匹配产生进程（主体）的用户是否对目标文件拥有合理的读、写、执行权限。\n我们在进行 SELinux 管理的时候，一般只会修改文件或目录的安全上下文，使其和访问进程的安全上下文匹配或不匹配，用来控制进程是否可以访问文件或目录资源；而很少会去修改策略中的具体规则，因为规则实在太多了，修改起来过于复杂。不过，我们是可以人为定义规则是否生效，用以控制规则的启用与关闭的。\n三种工作模式 SELinux 提供了 3 种工作模式：Disabled、Permissive 和 Enforcing，而每种模式都为 Linux 系统安全提供了不同的好处。\nDisable工作模式（关闭模式） 在 Disable 模式中，SELinux 被关闭，默认的 DAC 访问控制方式被使用。对于那些不需要增强安全性的环境来说，该模式是非常有用的。\n例如，若从你的角度看正在运行的应用程序工作正常，但是却产生了大量的 SELinux AVC 拒绝消息，最终可能会填满日志文件，从而导致系统无法使用。在这种情况下，最直接的解决方法就是禁用 SELinux，当然，你也可以在应用程序所访问的文件上设置正确的安全上下文。\n需要注意的是，在禁用 SELinux 之前，需要考虑一下是否可能会在系统上再次使用 SELinux，如果决定以后将其设置为 Enforcing 或 Permissive，那么当下次重启系统时，系统将会通过一个自动 SELinux 文件重新进程标记。\n关闭 SELinux 的方式也很简单，只需编辑配置文件 /etc/selinux/config，并将文本中 SELINUX= 更改为 SELINUX=disabled 即可，重启系统后，SELinux 就被禁用了。\nPermissive工作模式（宽容模式） 在 Permissive 模式中，SELinux 被启用，但安全策略规则并没有被强制执行。当安全策略规则应该拒绝访问时，访问仍然被允许。然而，此时会向日志文件发送一条消息，表示该访问应该被拒绝。\nSELinux Permissive 模式主要用于以下几种情况：\n审核当前的 SELinux 策略规则； 测试新应用程序，看看将 SELinux 策略规则应用到这些程序时会有什么效果； 解决某一特定服务或应用程序在 SELinux 下不再正常工作的故障。 某些情况下，可使用 audit2allow 命令来读取 SELinux 审核日志并生成新的 SELinux 规则，从而有选择性地允许被拒绝的行为，而这也是一种在不禁用 SELinux 的情况下，让应用程序在 Linux 系统上工作的快速方法。\nEnforcing工作模式（强制模式） 从此模式的名称就可以看出，在 Enforcing 模式中， SELinux 被启动，并强制执行所有的安全策略规则。\nSELinux配置文件 SELinux 配置只能由root 用户进行设置和修改。配置和策略文件位于 /etc/selinux 目录中，主配置文件位 /etc/selinux/config 文件，该文件中的内容如下：\n$ vim /etc/selinux/config # This file controls the state of SELinux on the system. # SELINUX= can take one of these three values： # enforcing - SELinux security policy is enforced. # permissive - SELinux prints warnings instead of enforcing. # disabled - No SELinux policy is loaded. SELINUX=enforcing # 指定SELinux的运行模式。有enforcing（强制模式）、permissive（宽容模式）、disabled（不生效）三种模式 # SELINUXTYPE= can take one of these two values： # targeted - Targeted processes are protected， # mls - Multi Level Security protection. SELINUXTYPE=targeted # 指定SELinux的默认策略。有 targeted（针对性保护策略，是默认策略）和 mls（多级安全保护策略）两种策略 主配置文件中，除去以‘#’符号开头的注释行，有效配置参数仅有 2 行。其中，SELinux=enforcing 为 SELinux 默认的工作模式，有效值还可以是 permissive 和 disabled；SELINUXTYPE=targeted 用于指定 SELinux 的默认策略。\n**这里需要注意，如果从强制模式（enforcing）、宽容模式（permissive）切换到关闭模式（disabled），或者从关闭模式切换到其他两种模式，则必须重启 Linux 系统才能生效。但是强制模式和宽容模式这两种模式互相切换不用重启 Linux 系统就可以生效。**这是因为 SELinux 是整合到 Linux 内核中的，所以必须重启才能正确关闭和启动。而且，如果从关闭模式切换到启动模式，那么重启 Linux 系统的速度会比较慢，那是因为需要重新写入安全上下文信息。\n","permalink":"http://localhost:9011/20190707-rhel7-selinux/","summary":"\u003cp\u003eRHEL7中的SELinux简单介绍。\u003c/p\u003e","title":"RHEL7的SELinux简介"},{"content":"RHEL系列Linux破解ROOT密码的操作。\n在开机选择内核的时候，快速按下e，注意这个时候要选中需要修改root密码的内核，而不要选错了内核。\n接着我们找到倒数第二行的LANG=en_US.UTF-8这个位置，在后面加入rd.break console=tty0这一行指令，然后按ctrl+x执行\n接下来我们会进入到一个操作终端，进行下列操作\n# 重新挂载/sysroot分区为可读写模式 mount -o rw,remount /sysroot # 更改root到这个可读写的/sysroot分区 chroot /sysroot # 使用passwd指令修改root账户的密码 paawd # 或者使用echo的方式修改密码 echo 你的新密码 | passwd --stdin root # 如果系统打开了SELinux，还需要新建一个文件，否则修改密码不生效 touch /.autorelabel 接着输入两次exit退出重启，我们可以看到如果开启了SELiunx的话还会有一个读条的操作\n读条完成重启之后就可以使用修改完密码的root账户登录了。\n","permalink":"http://localhost:9011/20190707-rhel7-break-passwd/","summary":"\u003cp\u003eRHEL系列Linux破解ROOT密码的操作。\u003c/p\u003e","title":"RHEL7破解ROOT密码"},{"content":"使用github上面的开源项目Frp和一台有公网IP的主机来实现反向代理（内网穿透），从而达到使用微软自带的远程桌面mstsc来远程没有公网IP的windows电脑和使用ssh远程Linux系统（Centos和Ubuntu）的效果。\n本文不涉及反向代理的具体理论知识，使用的公网IP主机为阿里云主机（十块钱一个月的学生版），由于使用的阿里云主机带宽有限，因此使用远程桌面的效果可能比较一般，但是对于SSH远程来说肯定是足够了的。\n需要注意的是，如果你的电脑本身所处的网络是有动态公网IP的，建议直接在路由器中设置DDNS，效果要比这个好很多，这个仅适用于那些没有公网IP的电脑。\n服务端配置 首先我们对服务端的阿里云主机进行配置，当然有其他的公网IP主机都是可以的。这里使用的操作系统是CentOS7.3。\n下载Frp github上各个配置版本下载地址\n# 使用wget指令下载linux版本 wget https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_linux_amd64.tar.gz # 使用tar命令解压 tar -zxvf frp_0.27.0_linux_amd64.tar.gz # 使用cp命令复制到/etc目录中并重命名为frps cp -r frp_0.27.0_linux_amd64 /etc/frps # 使用rm命令删除掉多余的frpc客户端文件，只留下服务端frps相关的文件 rm -f frpc frpc_full.ini frpc.ini 这些就是剩下的我们需要的文件，这里我们可以看到有两个ini配置文件，其中frps_full,ini是完整的带有注释说明和所有功能的配置文件，我们把需要的简单配置摘到frps.ini这个文件，方便我们管理配置。\n编辑配置文件 接下来我们编辑配置文件启用frp的监听端口7000和监控面板dashboard的监听端口7500。\n# 将下列内容加入到frps.ini中 [common] bind_addr = 0.0.0.0 bind_port = 7000 auto_token = 你的连接认证密码 dashboard_port = 7500 dashboard_user = admin dashboard_pwd = 你的管理登录密码 需要注意的是，这上面说的7000和7500端口都需要在阿里云的防火墙里面配置放行，否则会无法访问。\n设置开启自动和后台运行 我们新建一个文件，将Frps注册成Linux中的服务进程。\n# 新建一个文件设置frps服务能够实现后台启动和开机启动 vim /etc/systemd/system/frpsd.service # 将下列内容加入到frpsd.service中 [Unit] Description=frps daemon After=syslog.target network.target Wants=network.target [Service] Type=simple ExecStart=/etc/frps/frps -c /etc/frps/frps.ini Restart= always RestartSec=1min [Install] WantedBy=multi-user.target # 启动frps服务 systemctl start frpsd.service # 设置frps服务开机启动 systemctl enable frpsd.service # 查看frps状态 systemctl status frpsd.service # 重启frps服务 systemctl restart frpsd.service Windows客户端配置 下载Frp 我们按照之前的下载好对应的Windows版本之后，删除掉多余的frps服务端文件，只留下对应的frpc客户端文件。\n编辑配置文件 [common] server_addr = 你的公网主机的公网IP server_port = 7000 [MSRDP] type = tcp local_ip = 127.0.0.1 local_port = 3389 remote_port = 7777 这里的server_port就是我们前面的服务端配置的端口7000，local_port则是微软自带的远程桌面mstsc的默认端口3389，remote_port就是到时候使用mstsc远程的时候需要在这个公网IP后面加上的端口号，注意这个端口号也是需要在阿里云主机的防火墙中放行。\n然后我们就可以启用frpc客户端来建立连接\n# 在命令行中输入 frpc.exe -c frpc.ini 注册Frpc为Windows服务 为了使frpc能够开启后台运行，我们需要将它注册为系统服务。\n这里我们需要使用到GitHub上面的另一个项目winsw\n这里我使用的是WinSW.NET4.exe这个版本，这个需要电脑安装了.net4才可以使用，一般win10应该都是安装了的，此外我们还要下载一个xml文件作为启动配置文件。\n然后我们在对应的xml配置文件中加入下列内容\n\u0026lt;configuration\u0026gt; \u0026lt;!-- ID of the service. It should be unique accross the Windows system--\u0026gt; \u0026lt;id\u0026gt;frpc\u0026lt;/id\u0026gt; \u0026lt;!-- Display name of the service --\u0026gt; \u0026lt;name\u0026gt;Frpc Service (powered by WinSW)\u0026lt;/name\u0026gt; \u0026lt;!-- Service description --\u0026gt; \u0026lt;description\u0026gt;This frpc service can realize reverse Proxy\u0026lt;/description\u0026gt; \u0026lt;!-- Path to the executable, which should be started --\u0026gt; \u0026lt;executable\u0026gt;frpc\u0026lt;/executable\u0026gt; \u0026lt;arguments\u0026gt;-c frpc.ini\u0026lt;/arguments\u0026gt; \u0026lt;logmode\u0026gt;reset\u0026lt;/logmode\u0026gt; \u0026lt;/configuration\u0026gt; 接着我们把这两个文件都命名为WinSW，然后放在刚刚frpc的目录下。\n然后我们以管理员身份启动powershell或者cmd，输入下列命令\nWinSW.exe install win+r后通过services.msc进入到服务列表页面找到frpc服务。\n我们打开设置，将它设置为开机启动，服务失败后一分钟自动重启服务。\n到这里windows客户端的配置就已经完成了。我们在远程的时候只需要输入刚刚使用的公网IP地址加上端口号即可进行远程连接，如果有多台电脑需要进行远程，只需要给每个电脑设置一个不同的端口号即可。\nLinux客户端配置 Linux客户端的配置和上面的服务端大同小异，只需要将frps换为frpc并且修改对应的配置文件即可。注意下面的操作小七在CentOS7.6和Ubuntu18.04上面都能成功实现。\n下载Frp # 使用wget指令下载linux版本 wget https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_linux_amd64.tar.gz # 使用tar命令解压 tar -zxvf frp_0.27.0_linux_amd64.tar.gz # 使用cp命令复制到/etc目录中并重命名为frpc cp -r frp_0.27.0_linux_amd64 /etc/frpc # 使用rm命令删除掉多余的frps服务端文件，只留下客户端 rm -f frps frps_full.ini frps.ini # 使用vim编辑客户端配置文件 vim /etc/frpc/frpc.ini 编辑配置文件 # 配置本地的frpc.ini文件 [common] server_addr = 你的公网主机的公网IP server_port = 7000 [ssh] type = tcp local_ip = 127.0.0.1 local_port = 22 remote_port = 8000 设置开机启动和后台运行 # 新建一个文件设置frps服务能够实现后台启动和开机启动 vim /etc/systemd/system/frpcd.service # 将下列内容加入到frpcd.service中 [Unit] Description=frpc daemon After=syslog.target network.target Wants=network.target [Service] Type=simple ExecStart=/etc/frpc/frpc -c /etc/frpc/frpc.ini Restart= always RestartSec=1min [Install] WantedBy=multi-user.target # 启动frpc服务 systemctl start frpcd.service # 设置frpc服务开机启动 systemctl enable frpcd.service # 查看frpc服务状态 systemctl status frpcd.service 错误记录 在Ubuntu上面出现了一点小意外，发现服务并没有成功启用\n[control.go:142] [ssh] start error: proxy name [ssh] is already in use\n提示是说ssh这个名字已经被别的服务占用了，这里我们只需要修改之前的配置文件里面的ssh为别的名字，然后重启服务即可。\n最后注意这时候我们如果需要使用ssh远程需要加上-p命令来指定端口。\n","permalink":"http://localhost:9011/20190706-frp/","summary":"\u003cp\u003e使用github上面的开源项目Frp和一台有公网IP的主机来实现反向代理（内网穿透），从而达到使用微软自带的远程桌面mstsc来远程没有公网IP的windows电脑和使用ssh远程Linux系统（Centos和Ubuntu）的效果。\u003c/p\u003e\n\u003cp\u003e本文不涉及反向代理的具体理论知识，使用的公网IP主机为阿里云主机（十块钱一个月的学生版），由于使用的阿里云主机带宽有限，因此使用远程桌面的效果可能比较一般，但是对于SSH远程来说肯定是足够了的。\u003c/p\u003e\n\u003cp\u003e需要注意的是，如果你的电脑本身所处的网络是有动态公网IP的，建议直接在路由器中设置DDNS，效果要比这个好很多，这个仅适用于那些没有公网IP的电脑。\u003c/p\u003e","title":"Frp内网穿透实现MS远程桌面和SSH"},{"content":"《数据库原理》部分重点内容，小七自己整理的面向期末考考题的复习提纲。\n题型 一共10题，每题10分。\n问答题：3题（1题是第一章数据库概念相关、1题三级模式+二级映像\u0026ndash;数据独立性、1题是数据库恢复或并发控制协议内容+协议解决的问题） 编程题：5题（4题查询相关的，1题是安全性控制或完整性控制） 分析题：2题（1题是模式分解、范式相关的，1题是设计小型数据库） 问答题（3*10） DBMS 数据库管理系统（Database Management System），简称DBMS，是位于用户与操作系统之间的一层数据管理软件。\n数据库系统的特点\n数据结构化 数据的高共享性 数据独立性 DBMS对数据统一管理和控制 数据管理技术的发展过程 人工管理阶段(40年代中\u0026ndash;50年代中) 文件系统阶段(50年代末\u0026ndash;60年代中) 数据库系统阶段(60年代末\u0026ndash;现在) 产生的背景 人工管理(40年代中\u0026ndash;50年代中) 文件系统(50年代末\u0026ndash;60年代中) 数据库系统(60年代末\u0026ndash;现在) 应用需求 科学计算 科学计算、管理 大规模管理 硬件水平 无直接存取存储设备 磁盘、磁鼓 大容量磁盘 软件水平 没有操作系统 有文件系统 有数据库管理系统 处理方式 批处理 联机实时处理、批处理 联机实时处理,分布处理,批处理 数据模型 通俗地讲数据模型就是现实世界的模拟，在数据库中用数据模型这个工具来抽象、表示和处理现实世界中的数据和信息。\n数据模型分成两个不同的层次\n概念模型（用户），也称信息模型，它是按用户的观点来对数据和信息建模。 数据模型（计算机），主要包括网状模型、层次模型、关系模型、面向对象模型等，它是按计算机系统的观点对数据建模。 数据模型的组成要素 数据结构\n什么是数据结构：对象类型的集合 两类对象：（1）与数据类型、内容、性质有关的对象（2）与数据之间联系有关的对象 数据结构是对系统静态特性的描述 数据操作\n数据操作：对数据库中各种对象（型）的实例（值）允许执行的操作及有关的操作规则 数据操作的类型：（1）检索（2）更新（包括插入、删除、修改） 数据操作是对系统动态特性的描述。 数据的约束条件\n一组完整性规则的集合。 完整性规则是给定的数据模型中数据及其联系所具有的制约和储存规则，用以限定符合数据模型的数据库状态以及状态的变化，以保证数据的正确、有效、相容。 数据库系统的三级模式结构 三级模式 三级模式结构：外模式、模式、内模式。 外模式（也称子模式或用户模式），数据库用户（包括应用程序员和最终用户）使用的局部数据的逻辑结构。 模式（也称逻辑模式），数据库中全体数据的逻辑结构 内模式（也称存储模式），是数据物理结构和存储方式的描述。 一个数据库只有一个模式和一个内模式，但是可以有多个外模式。 模式与外模式的关系：一对多 外模式通常是模式的子集 一个数据库可以有多个外模式。反映了不同的用户的应用需求、看待数据的方式、对数据保密的要求 对模式中同一数据，在外模式中的结构、类型、长度、保密级别等都可以不同 外模式与应用的关系：一对多 同一外模式也可以为某一用户的多个应用系统所使用 但一个应用程序只能使用一个外模式 三级模式是对数据的三个抽象级别，二级映象在DBMS内部实现这三个抽象层次的联系和转换。\n外模式/模式映象（逻辑独立性） 定义外模式与模式之间的对应关系 每一个外模式都对应一个外模式／模式映象 外模式／模式映象的用途：保证数据的逻辑独立性 当模式改变时，数据库管理员修改有关的外模式／模式映象，使外模式保持不变 应用程序是依据数据的外模式编写的，从而应用程序不必修改，保证了数据与程序的逻辑独立性，简称数据的逻辑独立性。 模式/内模式映象（物理独立性） 模式／内模式映象定义了数据全局逻辑结构与存储结构之间的对应关系。例如，说明逻辑记录和字段在内部是如何表示的 数据库中模式／内模式映象是唯一的 模式／内模式映象的用途：保证数据的物理独立性 当数据库的存储结构改变了（例如选用了另一种存储结构），数据库管理员修改模式／内模式映象，使模式保持不变 应用程序不受影响。保证了数据与程序的物理独立性，简称数据的物理独立性。 事务 事务的基本概念 事务(Transaction)是用户定义的一个数据库操作序列，这些操作要么全做，要么全不做，是一个不可分割的工作单位，事务是恢复和并发控制的基本单位。\n事务的特性（ACID） 原子性（Atomicity） 事务是数据库的逻辑工作单位，事务中包括的诸操作要么都做，要么都不做。\n一致性（Consistency） 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态\n**一致性状态：**数据库中只包含成功事务提交的结果 **不一致状态：**数据库中包含失败事务的结果 隔离性（Isolation） 对并发执行而言\n一个事务的执行不能被其他事务干扰。 一个事务内部的操作及使用的数据对其他并发事务是隔离的。 并发执行的各个事务之间不能互相干扰 持续性（Durability ） 持续性也称永久性（Permanence）\n一个事务一旦提交，它对数据库中数据的改变就应该是永久性的。 接下来的其他操作或故障不应该对其执行结果有任何影响。 破坏事务ACID特性的因素 多个事务并行运行时，不同事务的操作交叉执行 事务在运行过程中被强行停止 故障的种类 事务故障（应该不会考） 什么是事务故障 某个事务在运行过程中由于种种原因未运行至正常终止点就夭折了\n事务故障的恢复 先UBDO再ROLLBACK\n（1）撤消事务**（UNDO）（2）强行回滚(ROLLBACK)**该事务\n清除该事务对数据库的所有修改，使得这个事务像根本没有启动过一样。\n事务故障的常见原因 输入数据有误、运算溢出、违反了某些完整性限制、某些应用程序出错、并行事务发生死锁\n系统故障 什么是系统故障 整个系统的正常运行突然被破坏 所有正在运行的事务都非正常终止 内存中数据库缓冲区的信息全部丢失 外部存储设备上的数据未受影响 系统故障的恢复 先UNDO再REDO\n正向扫描日志文件（即从头扫描日志文件） 对Undo队列事务进行UNDO处理 对Redo队列事务进行REDO处理 系统故障的常见原因 操作系统或DBMS代码错误、操作员操作失误、特定类型的硬件错误(如CPU故障)、突然停电\n介质故障 什么是介质故障 硬件故障使存储在外存中的数据部分丢失或全部丢失。\n介质故障比前两类故障的可能性小得多，但破坏性大得多\n介质故障的恢复 先找备份点再重新执行备份点之后的成功事务\n装入数据库发生介质故障前某个时刻的数据副本\n**（REDO）**重做自此时始的所有成功事务，将这些事务已提交的结果重新记入数据库\n恢复操作的基本原理：冗余 利用存储在系统其它地方的冗余数据来重建数据库中已被破坏或不正确的那部分数据\n介质故障的常见原因 硬件故障：磁盘损坏、磁头碰撞、操作系统的某种潜在错误、瞬时强磁场干扰\n数据转储 转储是指DBA将整个数据库复制到磁带或另一个磁盘上保存起来的过程。这些备用的数据文本称为后备副本或后援副本。\n静态转储与动态转储 静态转储 在系统中无运行事务时进行转储，转储开始时数据库处于一致性状态，转储期间不允许对数据库的任何存取、修改活动。\n**优点：**实现简单； **缺点：**降低了数据库的可用性（转储必须等用户事务结束、新的事务必须等转储结束） 动态转储 转储操作与用户事务并发进行，转储期间允许对数据库进行存取或修改。\n**优点：**不用等待正在运行的用户事务结束、不会影响新事务的运行 **缺点：**不能保证副本中的数据正确有效 利用动态转储得到的副本进行故障恢复，需要把动态转储期间各事务对数据库的修改活动登记下来，建立日志文件，后备副本加上日志文件才能把数据库恢复到某一时刻的正确状态。\n海量转储与增量转储 海量转储: 每次转储全部数据库\n增量转储: 只转储上次转储后更新过的数据\n海量转储与增量转储比较：\n从恢复角度看，使用海量转储得到的后备副本进行恢复往往更方便\n但如果数据库很大，事务处理又十分频繁，则增量转储方式更实用更有效\n日志文件 日志文件(log) 是用来记录事务对数据库的更新操作的文件 日志文件的用途： 进行事务故障恢复 进行系统故障恢复 协助后备副本进行介质故障恢复 并发控制 三种数据不一致的情况 丢失修改(Lost Update)\n丢失修改是指事务1与事务2从数据库中读入同一数据并修改，事务2的提交结果破坏了事务1提交的结果，导致事务1的修改被丢失。\n不可重复读(Non-Repeatable Read)\n不可重复读是指事务1读取数据后，事务2执行更新操作，使事务1无法再现前一次读取结果。\n读“脏”数据(Dirty Read)\n事务1修改某一数据，并将其写回磁盘 事务2读取同一数据后 事务1由于某种原因被撤消，这时事务1已修改过的数据恢复原值 事务2读到的数据就与数据库中的数据不一致，是不正确的数据，又称为“脏”数据。\n产生三种数据不一致的主要原因就是操作时破坏了事务的隔离性。\n封锁(Locking) 封锁的基本类型：\n排它锁(Exclusive Locks，简称X锁)，通常X锁用于事务修改数据(写入数据)时，所以也称为写锁。\n共享锁(Share Locks，简称S锁)通常S锁用于事务读取数据时，所以也称为读锁。\n封锁协议\n**一级封锁协议：**事务T在修改数据R之前须先进行X锁，直到事务结束才释放。\n**二级封锁协议：**在一级上增加事务T在读取数据R之前先进行S锁，读完之后立即释放这个S锁。\n**三级封锁协议：**在一级上增加事务T在读取数据R之前先进行S锁，直到事务结束释放。\n二段锁 二段协议是指所有事务必须分二个阶段对数据对象进行加锁和解锁：\n在对任何数据进行读、写操作之前，首先申请并获得对该数据的封锁。 在释放一个封锁后，事务不再申请和获得任何其他的封锁。 不丢失修改 不读脏数据 可重复读 二段锁 √ × √ 编程题（5*10） 视图的用法 -- [例4.14]建立计算机系学生的视图 -- 把对该视图的SELECT权限授予王平 -- 把该视图上的所有操作权限授予张明。 -- 先建立视图CS_Student CREATE VIEW CS_Student\tAS SELECT* FROM Student WHERE Sdept=\u0026#39;CS\u0026#39;; -- 王平老师只能检索计算机系学生的信息 GRANT SELECT\tON CS_Student TO 王平; -- 系主任具有检索和增删改 --计算机系学生信息的所有权限 GRANT ALLPRIVILEGES\tON CS_Student TO 张明; 9个SQL动词的用法 CREATE，DROP，ALTER CREATE -- SQL CREATE DATABASE 语法 CREATE DATABASE dbname; -- SQL CREATE TABLE 语法 CREATE TABLE table_name ( column_name1 data_type(size), column_name2 data_type(size), column_name3 data_type(size), .... ); -- Create table S CREATE TABLE S ( SNO VARCHAR(20), SNAME VARCHAR(50), STATUS INT, CITY VARCHAR(50) ) CHARSET = UTF8; DROP -- 用于 MySQL 的 DROP INDEX 语法： ALTER TABLE table_name DROP INDEX index_name -- DROP TABLE 语句用于删除表。 DROP TABLE table_name -- DROP DATABASE 语句用于删除数据库。 DROP DATABASE database_name ALTER -- 在表中添加列: ALTER TABLE 表名 ADD 列名 数据类型 --删除表中的列: ALTER TABLE 表名 DROP COLUMN 列名 -- 创建主键 ALTER TABLE E ADD PRIMARY KEY (ENO); ALTER TABLE P ADD PRIMARY KEY (PNO); -- 创建外键 ALTER TABLE E ADD FOREIGN KEY (PNO) REFERENCES P(PNO); -- 创建约束C1， -- 定义职工年龄不得超过60岁 ALTER TABLE E ADD CONSTRAINT C1 CHECK (AGE \u0026lt; 60); SELECT，INSERT，DELETE，UPDATE SELECT -- 5.1 找出所有供应商的姓名和所在城市 SELECT SNAME,CITY FROM S; -- 5.2 找出所有零件的名称、颜色、重量 SELECT PNAME,COLOR,WEIGHT FROM P; -- 5.3 找出使用供应商S1所供应零件的工程号码 SELECT JNO FROM SPJ WHERE SNO = \u0026#39;S1\u0026#39; ORDER BY JNO; -- 5.4 找出工程项目J2使用的各种零件的名称及其数量 SELECT PNAME,QTY FROM P,SPJ WHERE P.PNO = SPJ.PNO AND SPJ.JNO = \u0026#39;J2\u0026#39;; -- 5.5 找出上海厂商供应的所有零件号码 SELECT DISTINCT PNO FROM S,SPJ WHERE S.SNO = SPJ.SNO AND CITY = \u0026#39;上海\u0026#39; ORDER BY PNO; -- 5.6 找出使用上海产的零件的工程名称 SELECT DISTINCT JNAME FROM S,J,SPJ WHERE S.SNO = SPJ.SNO AND J.JNO = SPJ.JNO AND S.CITY = \u0026#39;上海\u0026#39;; -- 5.7 找出没有使用天津产的零件的工程号码 SELECT DISTINCT JNO FROM J WHERE JNO NOT IN ( SELECT DISTINCT SPJ.JNO FROM S,J,SPJ WHERE S.SNO = SPJ.SNO AND J.JNO = SPJ.JNO AND S.CITY = \u0026#39;天津\u0026#39; ORDER BY SPJ.JNO ); INSERT -- Insert data into table S INSERT INTO S (SNO,SNAME,STATUS,CITY) VALUES (\u0026#39;S1\u0026#39;,\u0026#39;精益\u0026#39;,20,\u0026#39;天津\u0026#39;); DELETE -- DELETE完整语法 DELETE FROM table_name WHERE some_column=some_value; -- 5.10 从供应商关系中删除S2的记录， -- 并从供应情况关系中删除相应的记录 -- 先删除外键再删除主键 DELETE FROM SPJ WHERE SNO = \u0026#39;S2\u0026#39;; DELETE FROM S WHERE SNO = \u0026#39;S2\u0026#39;; UPDATE -- UPDATA完整语法 UPDATE table_name SET column1=value1,column2=value2,... WHERE some_column=some_value; -- 5.8 把全部红色零件的颜色改为蓝色 UPDATE P SET COLOR = \u0026#39;蓝\u0026#39; WHERE COLOR = \u0026#39;红\u0026#39;; -- 5.9 由S5供给J4的零件P6改为由S3供应 UPDATE SPJ SET SNO = \u0026#39;S3\u0026#39; WHERE SNO = \u0026#39;S5\u0026#39; AND JNO = \u0026#39;J4\u0026#39; AND PNO = \u0026#39;P6\u0026#39;; GRANT，REVOKE（安全性控制） -- ALL PRIVILIGES 表示所有权限 -- PUBLIC或者ALL 表示所有用户 -- GRANT完整语法 GRANT \u0026lt;权限\u0026gt;[,\u0026lt;权限\u0026gt;]… ON \u0026lt;对象类型 对象名\u0026gt;[,\u0026lt;对象类型 对象名\u0026gt;…] TO \u0026lt;用户\u0026gt;[,\u0026lt;用户\u0026gt;]… [WITH GRANT OPTION]; -- 指定了WITH GRANT OPTION子句： -- 获得某种权限的用户还可以把这种权限再授予别的用户 -- 例题：把查询Student表和修改学生学号的权限授给用户U4。 GRANT SELECT, UPDATE(Sno) ON TABLE Student TO U4; -- REVOKE完整语法 REVOKE \u0026lt;权限\u0026gt;[,\u0026lt;权限\u0026gt;]… ON \u0026lt;对象类型 对象名\u0026gt;[,\u0026lt;对象类型 对象名\u0026gt;…] FROM \u0026lt;用户\u0026gt;[,\u0026lt;用户\u0026gt;]… [CASCADE|RESTRICT]; -- CASCADE表示级联收回权限 -- RESTRICT是默认选项，不级联收回 主键和外键的用法（完整性控制） -- 插入主键语法 ALTER TABLE 表名 ADD PRIMARY KEY 主键列; -- 设置主键 ALTER TABLE S ADD PRIMARY KEY (SNO); -- 插入外键语法 ALTER TABLE 表名 ADD FOREIGN KEY 外键列 REFERENCES 表名（主键列）; -- 设置外键 ALTER TABLE SPJ ADD FOREIGN KEY (SNO) REFERENCES S(SNO); 约束CONSTRAINT --[例10] 创建Student表，要求： --（1）学号在90000～99999之间 --（2）姓名不能为空 --（3）年龄小于30 --（4）性别只能是 \u0026#39;男\u0026#39; 或 \u0026#39;女\u0026#39; CREATE TABLE Student ( Sno NUMERIC(6) CONSTRAINT C1 CHECK(Sno BETWEEN 90000 AND 99999), Sname CHAR(20) CONSTRAINT C2 NOT NULL, Sage SMALLINT CONSTRAINT C3 CHECK(Sage \u0026lt; 30), Ssex CHAR(2) CONSTRAINT C4 CHECK(Ssex IN(\u0026#39;男\u0026#39;,\u0026#39;女\u0026#39;)), Sdept CHAR(20), CONSTRAINT StudentPK PRIMARY KEY(Sno) ) -- 创建约束C1，定义职工年龄不得超过60岁 ALTER TABLE E ADD CONSTRAINT C1 CHECK (AGE \u0026lt; 60); -- 当表已被创建时， -- 如需在 \u0026#34;P_Id\u0026#34; 列创建 UNIQUE 约束， -- 请使用下面的 SQL： ALTER TABLE Persons ADD UNIQUE (P_Id) -- 如需命名 UNIQUE 约束（此处为C1）， -- 并定义多个列的 UNIQUE 约束， -- 请使用下面的 SQL 语法： ALTER TABLE Persons ADD CONSTRAINT C1 UNIQUE (P_Id,LastName) 分析题（2*10） 范式 看课本6.2章对应的例题\n概念（了解即可） 范式：Normal Format，是一种离散数学中的知识，是为了解决一种数据的存储与优化的问题：保存数据的存储之后，凡是能够通过关系寻找出来的数据，坚决不再重复存储：终极目标是为了减少数据的冗余。\n范式：是一种分层结构的规范，分为六层：每一次层都比上一层更加严格：若要满足下一层范式，前提是满足上一层范式。\n六层范式：1NF，2NF，3NF，BCNF，4NF，5NF，1NF要求最低；5NF要求最严格。\nMysql属于关系型数据库：有空间浪费：也是致力于节省存储空间：与范式所有解决的问题不谋而合：在设计数据库的时候，会利用到范式来指导设计。\n但是数据库不单是要解决空间问题，要保证效率问题：范式只为解决空间问题，所以数据库的设计又不可能完全按照范式的要求实现：一般情况下，只有前三种范式需要满足。范式在数据库的设计当中是有指导意义：但是不是强制规范。\n一个低一级范式的关系模式通过模式分解（schema decomposition）可以转换为若干个高一级范式的关系模式的集合，这种过程就叫规范化（normalization）。\n1NF 第一范式：在设计表存储数据的时候，如果表中设计的字段存储的数据，在取出来使用之前还需要额外的处理（拆分），那么说表的设计不满足第一范式：第一范式要求字段的数据具有原子性：不可再分。（关键还要看实际业务需求）\n2NF 第二范式：在数据表设计的过程中如果有复合主键（多字段主键），且表中有字段并不是由整个主键来确定，而是依赖主键中的某个字段（主键的部分）：存在字段依赖主键的部分的问题，称之为部分依赖：第二范式就是要解决表设计不允许出现部分依赖。\n3NF 第三范式：理论上讲，应该一张表中的所有字段都应该直接依赖主键（逻辑主键：代表的是业务主键），如果表设计中存在一个字段，并不直接依赖主键，而是通过某个非主键字段依赖，最终实现依赖主键：把这种不是直接依赖主键，而是依赖非主键字段的依赖关系称之为传递依赖。\n数据库设计 需求说明 先陈述清楚所有的需求。需求分析就是分析用户的需要与要求。\n需求分析是设计数据库的起点 需求分析的结果是否准确地反映了用户的实际要求，将直接影响到后面各个阶段的设计，并影响到设计结果是否合理和实用 E-R图和关系模式 E-R图 画一些简单的ER图来描述数据表。\n关系模式 对每个实体定义的属性如下所示。加粗的为实体的码。\n顾客：{顾客号，顾客名，地址，电话，信贷状况，账目余额}\n订单：{订单号，顾客号，订货项数，订货日期，交货日期，工种号，生产地点}\n订单细则：{订单号，细则号，零件号，订货数，金额}\n应收账款：{顾客号，订单号，发票号，应收金额，支付日期，支付金额，当前余额，货款限额}\n产品：{产品号，产品名，单价，重量}\n折扣规则：{产品号，订货量，折扣}\n建表叙述 ","permalink":"http://localhost:9011/20190618-database-review/","summary":"\u003cp\u003e《数据库原理》部分重点内容，小七自己整理的面向期末考考题的复习提纲。\u003c/p\u003e","title":"数据库原理部分重点内容"},{"content":"Ubuntu18.04升级5.1.9的Linux内核，如果提示权限不足记得切换root用户。\nCentOS7版本请移步这里。\n下载内核 # 查看当前内核 uname -r 下载所需内核，官网地址：\nhttps://kernel.ubuntu.com/~kernel-ppa/mainline\n目前来看最新的应该是5.1.9\n取决于您的操作系统类型，请依次下载和安装软件包：\nlinux-headers-5.1.9-xxxxxx_all.deb\nlinux-headers-5.1.9-xxx-generic(/lowlatency)_xxx_amd64(/i386).deb\nlinux-modules-5.1.9-xxx-generic(/lowlatency)_xxx_amd64(/i386).deb\nlinux-image-xxx-5.1.9-xxx-generic(/lowlatency)_xxx_amd64(/i386).deb\n选择通用系统的generic，低延迟系统（例如用于录制音频）的lowlatency，64位系统的amd6432位系统的i386，其他OS类型的armhf，arm64等。\nwget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.1.9/linux-headers-5.1.9-050109_5.1.9-050109.201906111132_all.deb wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.1.9/linux-headers-5.1.9-050109-generic_5.1.9-050109.201906111132_amd64.deb wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.1.9/linux-image-unsigned-5.1.9-050109-generic_5.1.9-050109.201906111132_amd64.deb wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.1.9/linux-modules-5.1.9-050109-generic_5.1.9-050109.201906111132_amd64.deb 安装内核 sudo dpkg -i *.deb 重启后查看当前内核版本\nuname -r 卸载内核 # 查看所有内核 sudo dpkg --get-selections | grep linux # 卸载内核 sudo apt autoremove \u0026lt;package\u0026gt; # 更新grub sudo update-grub sudo update-grub2 重启后再次查看，对类型为deinstall的进行下列操作彻底删除\nsudo dpkg --get-selections | grep linux sudo dpkg -P \u0026lt;package\u0026gt; 更改启动内核 cat /boot/grub/grub.cfg | grep \u0026#39;menuentry\u0026#39;| grep Ubuntu 从图中可以看到这里有四个启动内核共计两个版本。\ncat /boot/grub/grub.cfg | grep \u0026#39;set default\u0026#39; set default=\u0026quot;0\u0026quot;就是上面列表中的第一个，修改为需要启动的内核序号即可\n","permalink":"http://localhost:9011/20190614-ubuntu-update-kernel/","summary":"\u003cp\u003eUbuntu18.04升级5.1.9的Linux内核，如果提示权限不足记得切换root用户。\u003c/p\u003e\n\u003cp\u003eCentOS7版本请移步\u003ca href=\"https://tinychen.com/20190612-centos-update-kernel/\"\u003e这里\u003c/a\u003e。\u003c/p\u003e","title":"Ubuntu18.04升级Linux内核"},{"content":"CentOS7.6升级5.1.9的Linux内核，如果提示权限不足记得切换root用户。\nUbunut18.04版本请移步这里\nyum升级内核 # 查看当前内核版本 uname -r # 更新yum源 yum update -y # 升级yum源中的最新版内核 yum install -y kernel 升级ELRepo库提供的内核 # 导入 ELRepo 仓库的KEY rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org # 使用yum安装 elrepo的仓库包 yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm ELRepo 仓库是基于社区的用于企业级 Linux 仓库，提供对 RedHat Enterprise (RHEL) 和 其他基于 RHEL的 Linux 发行版（CentOS、Scientific、Fedora 等）的支持。\nELRepo 聚焦于和硬件相关的软件包，包括文件系统驱动、显卡驱动、网络驱动、声卡驱动和摄像头驱动等。\n# 查看可用的系统内核包 yum --disablerepo=\u0026#34;*\u0026#34; --enablerepo=\u0026#34;elrepo-kernel\u0026#34; list available # 安装最新版内核 yum --enablerepo=elrepo-kernel install kernel-ml # 查看当前一共安装了多少个内核 cat /etc/grub2.cfg | grep menuentry | awk \u0026#39;{print $2$4$5$6}\u0026#39; # 或者这样，找到网上的一个大佬的正则表达式，比我这种菜鸡强多了 awk -F\\\u0026#39; \u0026#39;$1==\u0026#34;menuentry \u0026#34; {print i++ \u0026#34; : \u0026#34; $2}\u0026#39; /etc/grub2.cfg 注意这个升级的内核和通过yum升级的不同，yum升级内核之后会把新内核作为启动的默认内核，而这样升级并不会，因此我们需要手动更改默认的启动内核。\n# 将GRUB_DEFAULT从saved改为0 vim /etc/default/grub # 使用grub2重新生成grub配置文件 grub2-mkconfig -o /boot/grub2/grub.cfg # 如果是使用uefi引导安装启动的话目录应该是下面这个 grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg 重启之后就会看到默认启动的是新内核\n删除旧内核 # 查看系统中的所有内核相关包 rpm -qa | grep kernel # 使用yum卸载 yum remove \u0026lt;package\u0026gt; # 删除最旧的那个内核 yum remove kernel-3.10.0-957.el7.x86_64 kernel-devel-3.10.0-957.el7.x86_64 留一个最新的yum源的内核以防万一。\n","permalink":"http://localhost:9011/20190612-centos-update-kernel/","summary":"\u003cp\u003eCentOS7.6升级5.1.9的Linux内核，如果提示权限不足记得切换root用户。\u003c/p\u003e\n\u003cp\u003eUbunut18.04版本请移步\u003ca href=\"https://tinychen.com/20190614-ubuntu-update-kernel/\"\u003e这里\u003c/a\u003e\u003c/p\u003e","title":"CentOS升级Linux内核"},{"content":"使用Wireshark抓包分析DHCP过程，并分析在同一广播域中存在多个DHCP服务器的产生的路由干扰过程。\nDHCP简介 动态主机设置协议（英语：Dynamic Host Configuration Protocol，DHCP）是一个局域网的网络协议，使用UDP协议工作，主要有两个用途：用于内部网或网络服务供应商自动分配IP地址；给用户用于内部网管理员作为对所有计算机作中央管理的手段。\nDHCP有三种机制分配IP地址：\n自动分配方式（Automatic Allocation），DHCP服务器为主机指定一个永久性的IP地址，一旦DHCP客户端第一次成功从DHCP服务器端租用到IP地址后，就可以永久性的使用该地址。 动态分配方式（Dynamic Allocation），DHCP服务器给主机指定一个具有时间限制的IP地址，时间到期或主机明确表示放弃该地址时，该地址可以被其他主机使用。 手工分配方式（Manual Allocation），客户端的IP地址是由网络管理员指定的，DHCP服务器只是将指定的IP地址告诉客户端主机。 三种地址分配方式中，只有动态分配可以重复使用客户端不再需要的地址。\nDHCP状态机 DHCP状态机 如下图所示是比较常见的DHCP状态机，下面我们结合Wireshark抓到的数据包来进行分析。\n如何抓取数据包 在windows下，使网卡重新发出DHCP请求获取IP地址比较简单的方法就是使用ipconfig命令。\n# 释放IP地址 ipconfig -realease # 重新获取IP地址 ipconfig -renew 需要注意的是要确定网卡配置中是使用了DHCP而不是手动指定IP地址。\n一次DHCP过程的四个数据包 正常来说，一次DHCP过程只需要上面状态机的前四步，后面的续约是在DHCP的租期到达1/2和7/8的时候进行的。\n打开任意一个数据包，我们可以看到wireshark已经把里面的数据分层显示。\n从上到下依次为物理层、数据链路层、网络层、传输层和应用层。\n接下来我们对这四个数据包进行分析。\n发现阶段——DHCP Discover（广播） DHCP Discover 理论分析 DHCP Discover（广播）是第一个阶段即DHCP客户端寻找DHCP服务端的阶段。\n由于DHCP服务端的IP地址等信息对于DHCP客户端来说是未知，此时就需要使用广播的方式进行发送消息，基于UDP的源端口号68，目的端口号67来发送DHCP Discover发现信息来寻找DHCP服务器。\n由于是广播包，在同一个广播域中的每一台安装了TCP/IP协议的主机都会接收到这种广播信息，但只有DHCP服务器才会做出响应。\n在二层中，广播地址是12个f即ff:ff:ff:ff:ff:ff。\n在三层中，广播地址是4个255（IPv4）即255.255.255.255。\n换算成二进制，即全为1。\nDHCP Discover 物理层 物理层给出的有用信息并不多，但是我们能够看到网卡相关信息和这个是DHCP的数据包且使用了UDP协议。\nDHCP Discover 数据链路层 数据链路层中我们可以看到本机的MAC地址和目标MAC地址（广播）。\nDHCP Discover 网络层 网络层的参数较多，我们对其进行一一解析：\n版本（Version）：4，表明这里使用IPv4\n头部长度（Header Length）：20 bytes，即20个字节\n区分服务字段（Differentiated Services Field）：0x00，十六进制，换算成二进制正好是下面的8个0，表明当前的IP数据报中没有使用服务类型字段，采用默认的“尽力传输”优先级别\n总长度（Total Lenth）：328，表明该数据报的总长度是328个字节\n标识（Identification）：0xe752，十六进制，换算成十进制正好是括号中的59218，表明该数据报的标识为0xe752\n分段标识（Flags）： 保留位（Reserved bit）为0，Don\u0026rsquo;t fragment为0表示该数据报可以被分段，More fragments为0表明后面没有分段\n分段偏移（Fragment offset）：0，表明没有被分段\n生存时间（Time to live）：128，表明该数据报最多可以经过128个路由\n上层协议（Protocol）： UDP，值为17\n头部校验（Header checksum）： validation disabled，表示没有进行校验\n校验状态（Header checksum status）：Unverified，同上，没有进行校验\n源IP地址（Source）：0.0.0.0，表明当前尚未获得DHCP服务端分配的IP地址，只能使用0.0.0.0\n目标IP地址（Destination）：255.255.255.255，广播IP地址，对同一广播域内的所有TCP/IP协议客户端进行广播\nDHCP Discover 传输层 传输层这里可以看到源端口为68，目的端口为67，且校验状态为未校验。\nDHCP Discover 应用层 应用层的报文也有很多重要数据，我们截取重要的进行分析。\nBootp flags：0表示单播，1表示广播，这里为0，即DHCP服务端回复的DHCP Offer报文应为单播形式 部分书上讲述的DHCP四个包都是使用广播的方式进行，实际上DHCP Offer和DHCP Ack这两个数据包是广播还是单播是由DHCP的客户端发送的数据包来决定的。\nCiaddr即客户端IP地址（Client IP address）、\nYiaddr即被分配的DHCP客户端IP地址（ Your(client) IP address）、\nSiaddr即下一个为DHCP客户端分配IP地址的DHCP服务器地址（Next server IP address ）、\nGiaddr即DHCP中继IP地址（Relay agent IP address）、\n这四个IP地址均为0.0.0.0，因为此时还处于DHCP的发现阶段，DHCP客户端对这些信息还是一无所知。\n我们还可以看到请求信息里面需要请求获取包括子网掩码（Subnet Mask）、路由网关（Router）、 DNS服务器（Domain Name Server） 、以及域名（Domain Name）等信息\n提供阶段——DHCP Offer（单播/广播） DHCP Offer 理论分析 提供阶段，即DHCP服务器向DHCP客户端提供预分配IP地址的阶段。网络中的所有DHCP服务器接收到客户端的DHCP Discover报文后都会根据自己的DHCP地址池中IP地址分配的优先次序选出一个IP地址，然后与其他参数一起通过传输层的UDP67号端口在DHCP Offer报文中以单播/广播方式发送给客户端的UDP68号端口。\nDHCP服务器在分配这个地址之间会ping一下这个分配的ip地址，如果没有Response就才会预分配这个地址\n客户端通过封装在帧中的目的MAC地址（也就是DHCP Discover报文中的CHADDR字段值）的比对来确定是否接收该帧。但这样以来，理论上DHCP客户端可能会收到多个DHCP Offer报文（当网络中存在多个DHCP服务器时），但DHCP客户端只接收第一个到来的DHCP Offer报文。\nDHCP Offer 数据链路层 这时可以看到根据之前的DHCP报文，这里使用了单播的形式。而源MAC地址就是DHCP服务器的MAC地址且目的MAC地址是之前发送DHCP Discover报文的客户端MAC地址。\nDHCP Offer 网络层 网络层报文中我们可以看到这时候目的IP地址和源IP地址都已经有了数据，10.22.25.118就是即将要分配的IP地址，而10.22.25.254就是DHCP服务器的地址又或者是DHCP中继代理服务器的IP地址，具体要看应用层的报文。\n通过DHCP中继代理服务，与DHCP服务器不在同一子网的DHCP客户端可以通过DHCP中继代理（通常是路由器或三层交换机设备开启DHCP中继功能）与位于其他网段的DHCP服务器通信，最终是DHCP客户端获取到从DHCP服务器上分配而来的IP地址。\n此时的DHCP中继代理就位于DHCP客户端和DHCP服务器之间，负责广播DHCP报文的转发。\nDHCP Offer 应用层 这个时候我们再看应用层的报文：\nCiaddr即客户端IP地址（Client IP address）：此时还没有分配，所以还是0.0.0.0 Yiaddr即被分配的DHCP客户端IP地址（ Your(client) IP address）：10.22.25.118，这个IP即将分配给该客户端 Siaddr即下一个为DHCP客户端分配IP地址的DHCP服务器地址（Next server IP address ）：192.168.112.240，这个是分配IP地址的DHCP服务器 Giaddr即DHCP中继IP地址（Relay agent IP address）：10.22.25.254，这个是DHCP的中继代理服务器的IP地址，在这里应该是一个开启了DHCP中继的三层交换机 子网掩码（Subnet Mask）、路由网关（Router）、 DNS服务器（Domain Name Server） 、以及域名（Domain Name）等信息均已经包含在里面 租约时间是需要注意的一个问题，这里我们可以看到DHCP租约时间（IP Address Lease Time）实际上是8天，单位是s，wieshark换算成了天方便我们查看 而Renewal Time Value就是初次续约的时间，这个时间是租约时间的1/2，这里就是4天，这个时候一般客户端会以单播的方式向DHCP服务器发送报文请求续约 而Rebinding Time Value就是第二次续约时间，这个时间是租约时间的7/8，这里也就是7天，只有在初次续约失败之后，才会在这个时间以广播的方式向网络中的DHCP服务器再次申请IP地址 选择阶段——DHCP Request（广播） DHCP Request 理论分析 选择阶段，即DHCP客户端选择IP地址的阶段。\n如果有多台DHCP服务器向该客户端发来DHCPOFFER报文，客户端只接收第一个收到的DHCP Offer报文，然后以广播方式发送DHCP Request报文。在该报文的Requested Address选项中包含DHCP服务器在DHCP Offer报文中预分配的IP地址、对应的DHCP服务器IP地址等。\n这样也就相当于同时告诉其他DHCP服务器，它们可以释放已提供的地址并将这些地址返回到可用的地址池中。\n在DHCP REQUEST报文封装的IP协议头部中，客户端的Source Address仍然是0.0.0.0，数据包的Destination仍然是255.255.255.255。但在DHCP Request报文中Ciaddr、Yiaddr、Siaddr、Giaddr 字段的地址均为0.0.0.0\nDHCP Request 数据链路层 这时候又变回了广播。\nDHCP Request 网络层 由于是广播，所以目标IP地址是255.255.255.255，而源IP地址还是0.0.0.0是因为还没有完成整个DHCP过程，本机尚未获取到IP地址。\nDHCP Request 应用层 Bootp flags：0表示单播，1表示广播，这里为0，即DHCP服务端回复的报文应为单播形式 Ciaddr、Yiaddr、Siaddr、Giaddr 字段的地址均为0.0.0.0 确认阶段——DHCP Ack（单播/广播） DHCP Ack 理论分析 确认阶段，即DHCP服务器确认分配级DHCP客户端IP地址的阶段。\n某个DHCP服务器在收到DHCP客户端发来的DHCP REOUEST报文后，只有DHCP客户端选择的服务器会进行如下操作：如果确认将地址分配给该客户端，则以单播/广播方式返回DHCP ACK报文；否则返回DHCP NAK报文，表明地址不能分配给该客户端。\nDHCP Ack 数据链路层 可以看到这里是单播，源MAC地址是DHCP服务器（此处为中继代理）的MAC地址，目标MAC地址是DHCP客户端的MAC地址。\nDHCP Ack 网络层 10.22.25.118就是即将要分配的IP地址，而10.22.25.254就是DHCP服务器的地址（此处为DHCP中继代理服务器）\nDHCP Ack 应用层 在DHCPACK报文中Yiaddr字段包含要分配给客户端的IP地址，而Chaddr和DHCP:Client Identifier字段是发出请求的客户端中网卡的MAC地址。\n同时，在选项部分也会把在DHCPOFFER报文中所分配的IP地址的子网掩码、默认网关、DNS服务器、租约期、续约时间等信息加上。\nDHCP的IP地址租约更新 DHCP服务IP地址租约更新原理 如果采用动态IP地址分配策略，则DHCP服务器分配给客户端的IP地址都是有一定租约期限的，当租约期满后DHCP服务器又会收回原来分配的这个IP地址。\n如果DHCP客户端希望继续使用该地址，则需要向DHCP服务器提出更新IP地址租约的申请，也就是前面所说到的“续约”。\nIP地址租约更新或者IP地址续约也就是更新服务器端对IP地址的租约信息，使其恢复为初始状态。\n申请续约的方法 在DHCP客户端的IP地址租约期限达到1/2时，由DHCP客户端向为它分配IP地址的DHCP服务器以单播方式发送DHCP REOUEST请求报文，以期进行IP租约的更新。 如果DHCP服务器同意续约，则DHCP服务器向客户端以单播方式返回DHCP ACK报文，通知DHCP客户端已经获得新IP租约，可以继续使用此IP地址；相反，如果DHCP服务器不同意续约，则DHCP服务器以单播方式返回DHCP NAK报文，通知DHCP客户端不能获得新的租约，此IP地址不可以再分配给该客户端。 如果上面的续约申请失败，则DHCP客户端还会在租约期限达到7/8时再次以广播方式发送DHCP REQUEST请求报文进行续约。DHCP服务器的处理方式同上，不再赘述。 如果第二次续约请求还是失败，则原来租约的IP地址将被释放。 DHCP中继代理服务 在前面我们已经说过，在DHCP客户端初次从DHCP服务器获取IP地址的过程中，所有从DHCP客户端发出的请求报文均是以广播方式（目的地址为255.255.255.255）进行发送的，所以DHCP服务只适用于DHCP客户端和DHCP服务器处于同一个子网（也就是DHCP服务器至少有一个端口与DHCP客户端所在的子网是直接连接的）的情况，因为广播包是不能穿越子网的。\n基于DHCP服务的以上限制，这样一来，如果DHCP客户端与DHCP服务器之间隔了路由器设备，不在同一子网，就不能直接通过这台DHCP服务器获取IP地址，即使DHCP服务器上已配置了对应的地址池。这也就意味着，如果想要让多个子网中的主机进行动态IP地址分配，就需要在网络中的所有子网中都设置一个DHCP服务器。这显然是很不经济的，也是没有必要的。\nDHCP中继功能可以很好地解决DHCP服务的以上难题。**通过DHCP中继代理服务，与DHCP服务器不在同一子网的DHCP客户端可以通过DHCP中继代理（通常也是由路由器或三层交换机设备担当，但需要开启DHCP中继功能）与位于其他网段的DHCP服务器通信，最终使DHCP客户端获取到从DHCP服务器上分配而来的IP地址。**此时的DHCP中继代理就位于DHCP客户端和DHCP服务器之间，负责广播DHCP报文的转发。\n从前面的报文分析我们可以轻松的看出10.22.25.118和192.168.112.240是明显不属于同一个子网的，他们之间通信肯定需要使用路由器进行路由，而10.22.25.254就是担任着DHCP中继代理的角色。\n至于和DHCP服务器之间经过了多少个路由，我们可以tracert一下。\n从图中我们可以看到，从10.22.25.254这个DHCP中继代理到DHCP服务器之间需要经过5个路由节点。\n而实际上，在很多的大型园区网络中，都会使用一个DHCP服务器集群+多个DHCP中继代理这样的方式进行DHCP分配。\n路由干扰 路由干扰原理 路由干扰是园区网络中经常出现的问题，主要体现在用户无法获取到正确的IP地址/网关地址/DNS服务器地址等信息而无法上网。\n主要原因则是因为其他用户私接路由器且配置不当，导致私接的路由器成为所在的局域网中的第二个DHCP服务器，这时其他用户使用DHCP获取IP地址的时候就会有不只一个DHCP服务器提供IP地址。不幸的是，私接的路由器虽然能正确提供DHCP服务和IP地址等信息，却往往无法提供上网功能，这就会造成所谓的路由干扰。\n从上面的解释我们可以知道，路由干扰只可能发生在使用DHCP协议的客户端，如果是使用静态IP地址，是可以避免路由干扰的。\n因为这时候当客户端接入网络之后，会直接发送ARP请求来查询设置的网关IP地址的MAC地址信息，而根本不会去发送DHCP Discover报文。\n但是，大型网络中使用静态网络地址往往容易产生管理混乱，IP地址冲突等问题。\n路由干扰模拟 下面我们在一个局域网内接入两个DHCP服务器，进行路由干扰的模拟。\n从本文前面我们可以知道，由于DHCP Discover是以广播的方式进行发送，因此局域网内的所有DHCP服务器都会进行回应，而客户端只会选择最快回应的那一个DHCP服务器，其他的DHCP服务器则会被拒绝，如果此时我们的局域网中出现了一个能提供DHCP服务而不能提供上网服务的设备，就会对其产生路由干扰。\n模拟1 第一次模拟中我们可以看到，在客户端发送了DHCP Discover报文之后，10.22.25.254和192.168.100.1两个DHCP服务器都进行了回应，不同的是这时10.22.25.254的回应显然要快得多，因此客户端选择了10.22.25.254这个DHCP服务器而192.168.100.1这个DHCP服务器则是发送了DHCP NAK报文来表示DHCP失败。\n模拟2 这次我们调整192.168.100.1这个路由器对于DHCP报文的查询频率（加快），再次进行模拟。\n这次我们可以看到是192.168.100.1这个DHCP服务器先建立了连接。\n实例分析 从上图的IPv4地址和对应的子网掩码，我们可以计算出对应的默认网关应该是10.27.26.254，而这里则是显示10.27.26.59，显然是错误的，很可能就是受到了路由干扰。\n这种IP地址在接了路由器的情况下是很常见的，但是如果是接入的分配10开头的IP地址的网络中出现这种IP地址信息，也是很有可能受到了路由干扰。\n定位干扰源 定位干扰源最简单的方法就是查询arp缓存表。\n# 在cmd中输入下列命令查询arp缓存 arp -a 再根据IP信息中的默认网关（或者是DHCP服务器等其他异常信息）对应的IP地址找到干扰源的MAC地址，在交换机后台查询该MAC地址对应的交换机端口（往往需要网络管理员协助），即可定位干扰源。\n路由干扰解决方案 用户合理配置路由器 将墙上端口接出来的网线插到用户自己的路由器的wan口上，这时候相当于在该网络中下接了一层网络，即又建立了一个小型的局域网； 关闭路由器的DHCP功能，将网线插到路由器的lan口上，此时路由器相当于一个傻瓜交换机，只能起到增加网络接口和提供无线接入（如果路由器有无线功能的话）的作用； 这种方法的弊端是很多用户并不知道如何合理的配置路由器，因此可行性不大。\nDHCP Snooping DHCP Snooping技术是DHCP安全特性，通过建立和维护DHCP Snooping绑定表过滤不可信任的DHCP信息，这些信息是指来自不信任区域的DHCP信息。DHCP Snooping绑定表包含不信任区域的用户MAC地址、IP地址、租用期、VLAN-ID 接口等信息。\n当交换机开启了 DHCP-Snooping后，会对DHCP报文进行侦听，并可以从接收到的DHCP Request或DHCP Ack报文中提取并记录IP地址和MAC地址信息。另外，DHCP-Snooping允许将某个物理端口设置为信任端口或不信任端口。信任端口可以正常接收并转发DHCP Offer报文，而不信任端口会将接收到的DHCP Offer报文丢弃。这样，可以完成交换机对假冒DHCP Server的屏蔽作用，确保客户端从合法的DHCP Server获取IP地址。\nDHCP Snooping可以非常有效地防止园区网中的路由干扰现象发生，但是需要网络管理员对每台路由网关进行配置，考虑到大型网络中可能会使用多种品牌和型号的交换机，这样子的工作量会比较大。\n","permalink":"http://localhost:9011/20190609-wireshark-dhcp/","summary":"\u003cp\u003e使用Wireshark抓包分析DHCP过程，并分析在同一广播域中存在多个DHCP服务器的产生的路由干扰过程。\u003c/p\u003e","title":"Wireshark抓包分析DHCP以及路由干扰过程"},{"content":"使用Wireshark抓包分析校园网登录过程，并使用curl实现模拟登录。\n登录过程抓包 TCP三次握手建立连接 下图是经典的TCP三次握手示意图。\n下面是抓到的和校园网登录界面1.1.1.2建立TCP链接的三个数据报文。\n第一次握手 客户端发送syn包(seq=x)到服务器，并进入SYN_SEND状态，等待服务器确认；\n从下图可以看到此时\nseq=x=0\n客户端发送一个TCP，标志位为SYN，序列号为0，代表客户端请求建立连接，如下图：\n第二次握手 服务器收到syn包，必须确认客户的SYN(ack=x+1)，同时自己也发送一个SYN包(seq=y)，即SYN+ACK包，此时服务器进入SYN_RECV状态；\n从下图可以看到此时\nack=x+1=0+1=1\nseq=y=0\n第三次握手 客户端收到服务器的SYN+ACK包，向服务器发送确认包ACK(ack=y+1)，此包发送完毕，客户端和服务器进入ESTABLISHED状态，完成三次握手。\n从下图可以看到此时\nack=y+1=0+1=1\n登录过程分析 我们查看登录的报文，可以看到是一个HTTP POST请求，目标URL为http://a.stu.edu.cn/ac_portal/login.php\n在后面的报文分析中我们可以发现不仅是登录，查询和注销的目标URL也是http://a.stu.edu.cn/ac_portal/login.php，不同之处是opr选项的不同。\n再来查看报文内容，我们发现登录的选项较多\nopr为登录选项，此处为pwdLogin，即账号密码登录 userName为用户名 pwd为用户密码 ipv4or6实则为你的Temporary IPv6 Address，如果无则为空 rememberPwd为是否记住密码 查询过程分析 查询登录状态的报文也非常简单，使用HTTP POST提交一个opr=online_check的请求。\n然后服务端会返回一条html的text消息，里面的主要内容是两个选项online和username。\n其中online有0和1两个状态，当online为1的时候，表示已经登录成功，且会返回用户名username；当online为0的时候，表示没有登录，不会返回用户名username。\n注销过程分析 IPv4注销过程 IPv4的注销过程非常简单，只需要提交一个带有opr=logot的HTTP POST请求即可。\nIPv6注销过程 在本机获得IPv6地址之后，注销的HTTP POST请求中还需要多加一项ipv4or6=你的Temporary IPv6 Address，才能成功注销。\nCurl模拟登录 Curl简介 Curl是一个利用URL语法在命令行下工作的文件传输工具，1997年首次发行。它支持文件上传和下载，所以是综合传输工具，但按传统，习惯称Curl为下载工具。Curl还包含了用于程序开发的libcurl。\nCurl支持的通信协议有FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3、SMTP和RTSP。\nCurl还支持SSL认证、HTTP POST、HTTP PUT、FTP上传, HTTP form based upload、proxies、HTTP/2、cookies、用户名+密码认证(Basic, Plain, Digest, CRAM-MD5, NTLM, Negotiate and Kerberos)、file transfer resume、proxy tunneling。\n我们可以输入curl --help来查看相关的指令帮助。\n下面我们要用到的模拟登录主要是使用curl模拟表单登录。\n登录过程分析 从上面的报文分析我们得知，登录的时候http post请求提交了多个选项，但实际上需要登录成功只需要提交登录选项（opr）、账号（userName）和密码（pwd）即可。\ncurl -d \u0026#34;opr=pwdLogin\u0026amp;userName=你的账号\u0026amp;pwd=你的密码\u0026#34; http://a.stu.edu.cn/ac_portal/login.php 查询过程分析 curl -d \u0026#34;opr=online_check\u0026#34; http://a.stu.edu.cn/ac_portal/login.php 注销过程分析 在无IPv6地址的情况下，我们注销登录只需要提交一个注销请求（opr=logout）即可。\n# 因为返回的数据中有中文，可能会产生乱码，所以要使用iconv进行编码转换 curl -d \u0026#34;opr=logout\u0026#34; http://a.stu.edu.cn/ac_portal/login.php | iconv -f utf-8 -t gb2312 在有IPv6地址的情况下，我们从上面的报文分析可知，除了注销请求之外，我们还需要提交一个IPv6地址（``Temporary IPv6 Address`），才能够顺利注销。\n# 因为返回的数据中有中文，可能会产生乱码，所以要使用iconv进行编码转换 curl -d \u0026#34;opr=logout\u0026amp;ipv4or6=你的Temporary IPv6 Address\u0026#34; http://a.stu.edu.cn/ac_portal/login.php | iconv -f utf-8 -t gb2312 实现定时登录和爆流量换账号 首先要说明一下现在的网络状况，在宿舍里面，断网时间是从晚上12点到早上6:30，且每个账号每天有限制使用流量，而在早上6：30之后，如果宿舍接入路由器需要手动登录，这里我们可以通过定时任务来实现自动登录和一个账号流量用完之后切换到另一个账号。\n定时登录 curl -d \u0026#34;opr=pwdLogin\u0026amp;userName=你的账号\u0026amp;pwd=你的密码\u0026#34; http://a.stu.edu.cn/ac_portal/login.php 在linux中（树莓派或NAS）设置定时任务，6:32分执行该任务。\n爆流量换账号 由于通过校园网登录界面查询是否爆流量会有延迟（大概5分钟），所以我们需要使用ping命令来实时检测。\nping baidu.com || curl -d \u0026#34;opr=logout\u0026#34; http://a.stu.edu.cn/ac_portal/login.php \u0026amp;\u0026amp; curl -d \u0026#34;opr=pwdLogin\u0026amp;userName=你的账号\u0026amp;pwd=你的密码\u0026#34; http://a.stu.edu.cn/ac_portal/login.php 使用linux中的crontab工具，设置该任务执行时间为每分钟执行一次，执行时间为7:00到23:59。\n","permalink":"http://localhost:9011/20190608-wireshark-stu/","summary":"\u003cp\u003e使用Wireshark抓包分析校园网登录过程，并使用curl实现模拟登录。\u003c/p\u003e","title":"Wireshark抓包分析校园网登录过程"},{"content":"在CentOS7.6和Ubuntu18.04上面更换软件源为阿里源。\nUbuntu18.04 # 首先备份源镜像源 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak # 接着将源镜像源的内容全部注释掉，更改为下列内容 deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse # 更新镜像源使其生效 sudo apt-get update sudo apt-get upgrade CentOS7 # 首先备份源镜像源 cd /etc/yum.repos.d mv CentOS-Base.repo CentOS-Base.repo.bak # 下载新镜像源并更改名称 wget http://mirrors.aliyun.com/repo/Centos-7.repo -O CentOS-Base.repo # 更新镜像源使其生效 yum clean all yum makecache yum update ","permalink":"http://localhost:9011/20190605-linux-use-ali-source/","summary":"\u003cp\u003e在CentOS7.6和Ubuntu18.04上面更换软件源为阿里源。\u003c/p\u003e","title":"Ubuntu18.04和CentOS7更换阿里源"},{"content":"在CentOS7.6和Ubuntu19.04上面安装MPI开发环境。\n需要注意的是除非使用root账号，不然需要加上sudo保证有足够的权限执行安装操作。\n下载MPI MPI的官网下载地址\n这里小七直接下载的3.3稳定版。\n# 使用wget命令直接下载 wget http://www.mpich.org/static/downloads/3.3/mpich-3.3.tar.gz 解压MPI sudo tar -zxvf mpich-3.3.tar.gz 编译安装 cd mpich-3.3/ # 这里如果系统没有安装fortran的编译器的话需要禁用fortran sudo ./configure -prefix=/usr/local/mpich --disable-fortran sudo make sudo make install 配置环境变量 # 返回当前用户目录 cd # 编辑环境变量配置文件 vim .bashrc # 在里面添加环境变量 export PATH=/usr/local/mpich/bin:$PATH # 更新配置文件使其生效 source .bashrc 测试 # 测试 cd mpich-3.3/ # 此处的12为对应的线程数 mpirun -np 12 ./examples/cpi ","permalink":"http://localhost:9011/20190604-linux-install-mpi/","summary":"\u003cp\u003e在CentOS7.6和Ubuntu19.04上面安装MPI开发环境。\u003c/p\u003e\n\u003cp\u003e需要注意的是除非使用root账号，不然需要加上\u003ccode\u003esudo\u003c/code\u003e保证有足够的权限执行安装操作。\u003c/p\u003e","title":"Linux安装MPI"},{"content":"因为作业要求，最近拜读了IEEE上面的一篇关于ISP升级到SDN的方法的研究文章，然后记录了文章的一些要点和对一些自己不懂的知识进行补充。\nAbstract Nowadays, there is a fast-paced shift from legacy telecommunication systems to novel software-deﬁned network (SDN) architectures that can support on-the-ﬂy network reconﬁguration, therefore, empowering advanced trafﬁc engineering mechanisms.\nDespite this momentum, migration to SDN cannot be realized at once especially in high-end networks of Internet service providers (ISPs). It is expected that ISPs will gradually upgrade their networks to SDN over a period that spans several years.\nIn this paper, we study the SDN upgrading problem in an ISP network:**which nodes to upgrade and when we consider a general model that captures different migration costs and network topologies, and two plausible ISP objectives: **\nthe maximization of the trafﬁc that traverses at least one SDN node,\nthe maximization of the number of dynamically selectable routing paths enabled by SDN nodes.\nWe leverage the theory of submodular and supermodular functions to devise algorithms with provable approximation ratios for each objective. Using realworld network topologies and trafﬁc matrices, we evaluate the performance of our algorithms and show up to 54% gains over state-of-the-art methods.\nMoreover, we describe the interplay between the two objectives; maximizing one may cause a factor of 2 loss to the other. We also study the dual upgrading problem, i.e., minimizing the upgrading cost for the ISP while ensuring speciﬁc performance goals. Our analysis shows that our proposed algorithm can achieve up to 2.5 times lower cost to ensure performance goals over state-of-the-art methods. Index Terms—Software deﬁned networks, gradual deployment, ISP networks.\nMotivation 传统网络不足 硬件升级难题 纵观网络设备的诞生，传统网络行业按需发展，即根据暴露的问题然后去研发解决这个问题。同时，网络硬件研发周期长，迭代和升级也远远跟不上软件。 在传统网络行业中，话语权是掌控在网络设备商手上的，如思科、华为、新华三等。底层对于用户来说，是完全封闭的，如同黑盒子般，无法去掌控。\n网管系统的不足 传统的主流网络方案中，一般是配置网管服务器，网络设备（路由器、交换机、防火墙）和网管系统部署SNMP协议，通过网管系统对全网进行可视化拓扑发现、配置管理、链路质量检测。 然而，SNMP作为简单网络管理协议，更多侧重于网络设备的监控。而不是部署和配置。一般仅仅对IDC机房的故障进行告警，无法通过网管服务器去自动配置。\n流量分配不均衡 同时，针对互联网公司的链路流量分配不均衡，也没有一个很好的解决方案，可分配均衡的一大难点，又在于流量的可视化。\n常规流控产品只能实现部分带宽分配可视化，常规网管系统只能实现链路故障检测，无法带宽可视化 全网流量可视化是带宽智能调配的基础 网络设备本身问题 网络设备通过“网路协议”进行对话，如OSPF、BGP、MPLS、MSTP等，建立连接会通过三个步骤：邻居建立、信息共享、路径选择。 而由于大部分的网络设备采用“分布式架构”，每次交互都会根据“路径算法（如SPF算法）”选择最优的路径。**但是选择路径时，只能选择最短，不能根据流量等因素加以区分。**同时，由于每个交换机都会有自己的控制器，也会消耗一部分的转发性能。\nSDN定义 SDN：即软件定义网络，是一种网络设计理念\n网络设备可以集中式管理，可编程，控制和转发分离。即可定义为SDN\nSDN框架由应用层、控制层、转发层（基础设施层）组成，其中应用层提供应用和服务（网管、安全、流控等），控制层统一管理和控制（协议计算、策略下发、链路信息等）、转发层提供硬件设备（交换机、路由器、防火墙）进行数据转发\n基于REST API的北向接口负责面向应用，提供网络抽象，使得网络具备软件编程的能力。南向接口主要负责面向基础设施层，主要提供Openflow流。\n注意：控制层接口也属于北向接口\n传统网络对比SDN网络 传统网络 SDN 控制转发耦合 控制转发分离 分布式控制 集中式控制 不可编程 可编程 不开放 开放接口 硬件化 虚拟化 第三条主要是SDN可以通过代码写脚本实现转发策略，如C/JAVA/Python。\n第四条开放接口也很好理解，基于开放协议的方案是当前SDN实现的主流方案。 （主要是OpenFlow）\n第五条网络虚拟化，即虚拟化平台是介于数据网络拓扑和租户控制器之间的中间层，为了实现虚拟化，虚拟化平台需要对物理网络资源进行抽象虚拟化，其中包括拓扑虚拟化，节点资源虚拟化和链路资源虚拟化。\nMPLS简介 多协议标签交换（英语：Multi-Protocol Label Switching，缩写为MPLS）是一种在开放的通信网上利用标签引导数据高速、高效传输的新技术。多协议的含义是指MPLS不但可以支持多种网络层层面上的协议，还可以兼容第二层的多种数据链路层技术。\n**MPLS是利用标记（label）进行数据转发的。**当分组进入网络时，要为其分配固定长度的短的标记，并将标记与分组封装在一起，在整个转发过程中，交换节点仅根据标记进行转发。\n**MPLS 独立于第二和第三层协议，诸如ATM 和IP。**它提供了一种方式，将IP地址映射为简单的具有固定长度的标签，用于不同的包转发和包交换技术。它是现有路由和交换协议的接口，如IP、ATM、帧中继、资源预留协议（RSVP）、开放最短路径优先（OSPF）等等。\nOSPF简介 **OSPF(Open Shortest Path First开放式最短路径优先）是一个内部网关协议(Interior Gateway Protocol，简称IGP），用于在单一自治系统（autonomous system,AS）内决策路由。**是对链路状态路由协议的一种实现，隶属内部网关协议（IGP），故运作于自治系统内部。**著名的迪克斯加算法(Dijkstra)被用来计算最短路径树。**OSPF分为OSPFv2和OSPFv3两个版本,其中OSPFv2用在IPv4网络，OSPFv3用在IPv6网络。\n混合SDN的好处 对于跨越至少一个SDN节点的流量，可以应用各种复杂的策略，例如访问控制，防火墙动作以及其他支持中间盒的网内服务。 使用SDN节点可以通过覆盖底层的OSPF或MPLS来动态地控制流的路由路径，从而创造更灵活的网络。 图1.部分升级到SDN的网络。两个SDN节点可以充当防火墙或动态控制路由路径。\n让我们用一个简单的例子来说明这种方法的潜力。考虑图1所示的混合SDN网络，它将流从源节点1路由到目的节点3.这里，七个节点中只有两个用SDNcapabilities（节点1和4）升级。使用OSPF，流始终沿最短路径路由。然而，节点1可以动态地决定丢弃（而不是转发）报文，例如作为防火墙。它还可以通过节点4路由分组来覆盖OSPF最短路径。然后，报文将跟随备用路径1，该备用路径1是具有3的OSPF最短路径连接节点4.当最短路径的链路失败或变为临时拥塞时，这种流重新路由是重要的。由于节点4也升级到SDN，因此它可以类似地将报文推向替代路径2.换句话说，随着启用SDN的节点的数量增加，替代路径的集合也增加。因此，在执行动态TE时存在更多的自由度（或灵活性）。\n小结 总之，每个ISP在升级SDN的时候都必须解决以下两个问题：\n（升级的节点数量和时间）每个时段要升级多少个节点？它应该尽早升级所有节点还是等待价格下降后再升级？ （升级哪些特定节点）在决定要升级的节点数量之后，要选择哪些特定节点先进行升级？ 因此，我们在这项工作中的目标是研究大型（昂贵）运营ISP网络中SDN升级调度的策略，并主要关注时间维度的影响以及流量可编程性和TE灵活性优势之间的相互作用。\nTherefore, our goal in this work is to investigate policies for SDN upgrade scheduling in large (and expensive) operational ISP networks, and focus mainly on the impact of time-dimension and the interplay between traffic programmability and TE flexibility benefits.\nMethodology and Contributions 简介 ISP升级SDN的时候需要关注的两个重要因素：\n至少遍历一个SDN节点的流量最大化，因为这允许ISP控制流量在自己的网络中的流动方式。 First, we target the maximization of the programmable traffic, i.e., the traffic that traverses at least one SDN node (Obj1).\n旨在最大化TE的灵活性。通过增加通过SDN升级的备选路径的数量来实现该目标。 The second objective (Obj2) aims to maximize the TE flexibility.\n次模函数和超模函数 定义 在数学中，一个函数f:R^k^→R是超模的，当f(x⬆y)+f(x⬇y)≥f(x)+f(y)对所有x,y∈R^k^成立\n如果-f是超模函数，那么f称为次模函数，如果不等式变为相等，则函数是模块化的。\nSubmodularity 次模函数（submodular function）又称“子模函数”或“亚模函数”，次模函数具有次模性（submodularity），它是经济学上边际效益递减（property of diminishing returns）现象的形式化描述。\n给定一个集合函数f:2V→R f:2^V^→R，其将有限集V VV的一个子集S⊆V 映射为一个实数。如果对于任意S，满足：\nf(S∪T)+f(S∩T)≤f(S)+f(T)\t(1)\n则称f(⋅) 是次模函数。从边际效益递减的角度考虑，次模函数还有一种等价定义：\n对任意的R⊆S⊆V，并且s∈V∖S，\nf(S∪{s})−f(S)≤f(R∪{s})−f(R)\t(2)\n**公式（2）指出，当集合越来越大，s的“价值”将越来越小，正是边际效益递减的特性。**这个现象在自然界普遍存在，例如：香农熵函数就是随机变量集合上的次模函数。当S⊆T时有f(S)≤f(T) f(S)，则称该次模函数是单调的（monotone）。\n更进一步，次模性是convexity（凸性）的离散模拟。由于convexity使得连续函数更容易最优化，因而次模性在组合优化中重要作用。当目标函数是次模函数时，许多组合优化问题能够在多项式时间内得到最优解或近似解。次模函数最大化被证明是一个NP-hard问题，幸运的是，存在高效并且解的质量有保证的近似算法。\n一个流行的结果是：最大化一个单调非负的带基数约束（cardinality constraint，即对子集S大小的约束）的次模函数，贪心算法至少能够达到(1−1/e)f(Sopt)的结果，其中f(Sopt)表示问题的最优解，1−1/e大约是0.63。\nf(Sapp)≥(1−1/e)f(Sopt)\n目标1分析 对于Obj1，这个问题是NP-Hard事件，近似于任何优于1-1 / e的因子。\nNP问题是指可以在多项式的时间里验证一个解的问题。\nNP问题：可以在多项式时间内被验证的问题。或者说，可以在非确定性多项式时间内被解决的问题。\n即可以在非确定型图灵机上在多项式时间内找出解的问题。NP问题可以在多项式时间内被验证，但是不确定是否可以在多项式时间内找出解。\nNPC问题的定义非常简单。同时满足下面两个条件的问题就是NPC问题。首先，它得是一个NP问题；然后，所有的NP问题都可以归约到它。\n**NP-Hard问题是这样一种问题，它满足NPC问题定义的第二条但不一定要满足第一条（就是说，NP-Hard问题要比NPC问题的范围广），注意是不一定，并不是完全否定。**NP-Hard问题同样难以找到多项式的算法，但它不列入我们的研究范围，因为它不一定是NP问题。即使NPC问题发现了多项式级的算法，NP-Hard问题有可能仍然无法得到多项式级的算法。事实上，由于NP-Hard放宽了限定条件，它将有可能比所有的NPC问题的时间复杂度更高从而更难以解决。\n对于所有节点升级发生在同一时间段的特殊情况，我们展示了经典贪婪算法的修改版本，它枚举了所有可能的节点三元组作为候选解决方案，实现了最佳可能的近似。我们还展示了一种扩展此算法的简单方法，适用于节点升级可以在不同时间段进行的一般情况。我们还提出了第二类更复杂的算法，它们通过将Obj1表示为次模函数[12]的最大化来改进近似度，即满足递减回归性质的函数。\nIn both cases, finding the upgrading policy requires the solution of challenging combinatorial optimization problems. Namely, we show that for Obj1 this problem is NP-Hard even to approximate to any factor better than 1−1/e. For the special case in which all the node upgrades take place at the same time period, we show that a modified version of a classic greedy algorithm, which enumerates all possible triplets of nodes as candidate solutions, achieves the best possible approximation ratio. We also show a simple way to extend this algorithm for the general case where the node upgrades can take place at different time periods. We also present a second class of more sophisticated algorithms with improved approximation ratios by expressing Obj1 as the maximization of a submodular set function [12], i.e., a function that satisfies the diminishing returns property.\n目标2分析 然后，我们研究Obj2（最大化TE灵活性）。这是一个更复杂的问题，可以表达为具有有界超模程度的函数的最大化[13]。使用此结果，我们提出了另一种基于贪婪的算法，该算法大致解决了这个问题。为了完整起见，我们还考虑升级问题（Obj3）的“双重”版本，其中上述目标被视为约束并受其约束，我们将迁移成本降至最低。对于简单但实用的情况，使用二分搜索技术提出了近似算法。\nThen, we study Obj2 (maximizing TE flexibility). This is a more complex problem which can be expressed as the maximization of a function with bounded supermodular degree [13]. Using this result, we present another greedy-based algorithm that approximately solves this problem. For the sake of completeness, we also consider the “dual” version of the upgrading problem (Obj3), where the above objectives are treated as constraints and subject to them we minimize migration costs. For a simple, yet practical, case an approximation algorithm is proposed using a binary search technique.\nMODEL AND PROBLEM FORMULATION SDN升级问题（SDN Upgrading Problem） 我们介绍了使用通用的成本模型和不同的目标，逐步（部分）将ISP网络升级到SDN的问题。升级可以在不同的时间段进行，在每个时期引入不同的成本，技术成熟度，网络设备的不同生命周期和其他实际限制。\n最大化可编程流量（Obj1）（Maximizing Programmable Traffic (Obj1)） 可编程流量最大化目标，我们表明SDN升级问题是NP-Hard接近任何优于1-1 / e的因素。然后，针对一个时间段的特殊情况，我们提出了匹配该因子的简单算法，并说明了如何在一般情况下对其进行扩展。我们还使用子模函数理论提出了更多更复杂的近似算法。\n最大化TE灵活性（Obj2）（Maximizing TE Flexibility (Obj2)） 为了通过支持SDN的路由路径的最大化来最大化TE灵活性，我们表明优化问题更复杂。我们通过将其表示为具有有界超模函数的最大化来提出近似算法。\n最小化迁移成本（Obj3）（Minimizing migration costs (Obj3)） 对于最小化迁移成本的“双重”问题，我们表明它与上述问题在很大程度上有所不同。我们还使用二元搜索技术进行近似算法。\n数据集驱动评估（Dataset-driven Evaluation） 我们使用真实网络拓扑和流量矩阵评估提出的算法。我们发现，与实际场景中的两种最先进的方法相比，我们的方法可以将可编程流量的数量增加54％。我们还发现，通过优化Obj1，对Obj2进行了多方面的好处（反之亦然），我们探索了它们之间的相互作用。这两个目标。最后，我们展示了我们提出的Obj3算法可以节约高达2.5倍的成本，以确保性能目标超越其他最先进的方法。\n示例 在传统的IP协议下，如OSPF，流量总是遵循最短路径的原则到达目的地址，使用更高级的协议，如MPLS，流量则可以遵循其他的自定义原则不走最短的路径。\nDataset-driven Evaluation 一般而言，ISP通过将升级扩展到许多而不是一年来获得更多好处。然而，当SDN成本随时间相对稳定（每年下降高达20％）时，这种策略可能是有害的。\n我们还指出，通过优化可编程流量最大化的目标，也可以实现最大化灵活性最大化的目标（反之亦然）。然而，由于每个算法支持一个目标而不是另一个目标，因此会有性能损失（高达2倍）。\nIn general, the ISP acquires more benefits by spreading the upgrades over many instead of one year. Nevertheless, this strategy can be detrimental when the SDN costs are relatively stable over time (up to 20% drop per year).\nHowever, there will be a performance loss (up to a factor of 2), since each algorithm favors one objective over the other.\n涉及算法 DEG 此方案在拓扑图中升级具有最高度数（传入和传出的相邻链接数）的节点，直到所有预算用完。所有升级都在第一时间进行。\nVOL 此方案升级具有最高流量的节点，直到所有预算用完。所有升级都在第一时间进行。\nModified-greedy 此方案使用算法1进行分时间段升级。\nLocal search 此方案使用算法2进行分时间段升级。其中变量e为2\nSuper greedy 此方案使用算法3进行TE灵活性最大化。\nMUcPF 此方案升级覆盖最大流量的节点，直到满足最小可编程流量目标。所有升级都在第一时间进行。\nThis scheme upgrades the node that covers the maximum number of flows until the minimum programmable traffic target is met. All the upgrades take place at the first time period.\nHighest ratio 该方案升级具有最高流量比率的节点，使其超过成本，直到满足最小可编程流量目标。所有升级都在第一时间进行\nThis scheme upgrades the node with the highest ratio of traffic volume that traverses it over upgrading cost, until the minimum programmable traffic target is met. All the upgrades take place at the first time period.\nBinary search 变量e= 0.1，可以最大限度地降低升级成本。所有升级都在第一时间进行。\n上述前四种算法将根据特定预算B对Obj1进行比较。将根据相同预算对Obj2评估淡化算法。最后，最后三个算法将针对Obj3进行比较，具体取决于特定的性能目标Pt。\n网络拓扑 评估的主要部分是使用从北美的教育骨干网络获得的“艾利森”数据集[14]进行的。该网络由12个节点和30个有向链路组成，如图3所示。数据集记录流量矩阵，即每对节点之间传输的数据，每5分钟传输一个六个月的整个周期。\n所有流量聚合之后最大可达5.46Gbps，并设定每年增长22%。同时使用OSPF记录链接之间的最短路径。\n结果分析 一次升级 我们注意到，由于Abilene网络相当小（N = 12个节点）和T = 1个时间段，我们可以通过使用穷举搜索方法在合理的时间内计算出最优解。这是通过枚举所有2^12^ = 4,096个可能的解决方案，然后选择产生最大可编程流量的解决方案。通过执行此过程，我们观察到Modified greedy和Local search算法非常接近最优（图（a）中的场景小于1％）。但是，我们无法应用详尽的搜索方法来找到更大或更大网络的最佳解决方案。\n分段升级 然后我们探讨了图（b）中时间段数的影响。在这里，我们保持B = $ 200K，但T设定为从1到5。\n为了捕获技术成熟度，我们将SDN升级成本每年降低40％，\n当T = 1，结果与图（a）相匹配。\n当T\u0026gt; 1，通过推迟一些升级后的成本将会降低，可以获得额外的好处。\nLocal search算法可以通过分时间段逐渐升级，以实现四种算法中的最佳性能。\n最好的情况下（T=5）比Local search算法比最差的VOL要高47%且比第二高的Modified greedy高5.5％\n一次升级和分段升级 在图（c）中，我们仔细研究了使用Local search算法时多年来的升级分布。我们评估了各种情景，这些情景与升级成本的年降低率不同。我们发现，对于相对较低的成本降低率（高达20％），所有升级都应在第一年内完成。但是，在此之后，将来推迟一些升级更有利。随着成本降低的速度增加，分时间段进行升级。\nObj1和Obj2相互关系 然后探讨的是traffic programmability (Obj1) 和TE flexibility benefits (Obj2)的相互关系。\nLocal search实际上是一种非常有效的算法，可以最大化可编程流量。但是，大量的可编程流量不能保证自己有大量的备用路由路径。\n图（a）旨在通过比较Local search algorithm（优化可编程流量）和Super-greedy（优化TE灵活性）的性能来解决这个问题。\n在这里，为了模拟TE flexibility benefits (Obj2)，我们关注具有最高速率的10个Flow，其中TE是最重要的。然后，我们将每个流的第二和第三最短路径视为替代路径，该路径不与最短路径（Pfsets）重叠。\n我们发现通过优化其中一个目标，也可以为另一个目标实现收益。然而，由于每种算法都偏向于另一种目标，因此会有性能损失（高达2倍）。\nObj3 我们还提出了Obj3的评估结果，以研究所提出的算法（Binary search）如何与最先进的方法（MUcPF和Highest ratio）进行比较。\n我们将这一时间段与Pt的不同值进行比较。结果如图（b）所示。\n使用更大型网络 虽然在我们的评估中我们使用了真实的网络拓扑和流量矩阵，但研究大型网络中的结果也很有意思。\n为实现这一目标，我们使用了北美Deltacom骨干网络的拓扑结构，该网络由113个节点和161个链路组成，并且在[15]中可在线公开。\n由于没有关于流量的可用信息，我们人为地生成此信息。\n特别是，我们通过在随机原始 - 目的地对统一选择来创建F = 1,000flow。\n我们根据跳数长度计算最短路径，并且我们将流速设置为与它成比例（遵循重力模型[36]）。\n在图中，我们重复7.1中所示的实验，但对于这个更大的网络。我们发现所提出的算法比其对应的算法执行得更好。\n饱和点为B = $ 3M，大约是小型网络的三倍。\n我们将这种差异归因于Deltacom网络中较大的节点数（10x）和拓扑特征，因为Deltacom具有更高的链路密度，可以实现SDN节点覆盖更多的流量。\n","permalink":"http://localhost:9011/20190601-isp-upgrade-sdn/","summary":"\u003cp\u003e因为作业要求，最近拜读了IEEE上面的一篇\u003ca href=\"https://ieeexplore.ieee.org/document/8610160\"\u003e关于ISP升级到SDN的方法\u003c/a\u003e的研究文章，然后记录了文章的一些要点和对一些自己不懂的知识进行补充。\u003c/p\u003e","title":"Optimizing Gradual SDN Upgrades in ISP Networks"},{"content":"主要是在CentOS7上安装MariaDB服务。\n为什么换用MariaDB？ 之前写过一篇教程是把内置的MariaDB卸载然后换用MySQL，最近因为MySQL不支持Check子句，还有一次插入多条数据也不行，反正就是想用一下MariaDB，就又换回来了。\n卸载MySQL 注意，在卸载MySQL之前请先使用dump命令备份数据库。\n# 查看系统中安装的MySQL服务 rpm -qa | grep mysql # 使用rpm卸载掉对应的组件 rpm -e \u0026lt;packege\u0026gt; # 如果出现提示依赖的问题，加上--nodeps rpm -e --nodeps \u0026lt;packege\u0026gt; 新建yum源 MariaDB的yum安装官网链接：\nhttp://yum.mariadb.org/\n进去可以看到目前最新的应该是10.4.5版本，于是我们就在/etc/yum.repos.d目录下面新建一个yum源文件。\nvim /etc/yum.repos.d/MariaDB.repo 在repo文件中写入下列内容，注意baseurl可以根据版本的更新而改变。\n[MariaDB] name = MariaDB baseurl = http://yum.mariadb.org/10.4.5/centos7-amd64/ gpgkey = http://yum.mariadb.org/RPM-GPG-KEY-MariaDB gpgcheck = 1 # 输入下列命令清除并更新yum源 yum clean all -v 安装MariaDB # 查看yum源中的Maria，注意字母的大小写要和上面写的yum源文件一样 yum list | grep Maria 这里应该是10.4.5，就说明是之前我们写的yum源。\n# 输入下面这条指令进行安装MariaDB，注意不要加-y选项，先确定一下版本是否正确 yum install MariaDB 确定版本无误后，按y确定安装。\n# 设置开机启动 systemctl enable mariadb # 开启mariadb服务 systemctl start mariadb # 查看安装后的mysql版本 mysql --version 这里可以看到MariaDB，使用的方法还是和之前一样，账号也还是之前的Mysql的账号。\n安装过程的一些意外 小七在安装的时候，启动的过程中出现了报错。\n虽然使用systemctl可以成功开启服务，但是无法顺利登录进去\n使用-l参数查看详细状态\n看到这里有两个ERROR，再按照提示输入下列命令升级mysql即可\nmysql_upgrade -u root -p 报错的原因应该是因为mysql换为mariadb之后配置信息的链接没有及时更新导致无法正常启动，因此需要upgrade。\n","permalink":"http://localhost:9011/20190521-centos-install-mariadb/","summary":"\u003cp\u003e主要是在CentOS7上安装MariaDB服务。\u003c/p\u003e","title":"CentOS7安装MariaDB"},{"content":"CentOS7配置Python3.7开发环境\n下载Python3 wget https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tgz # 使用wget命令下载官网的tgz安装包 tar -zxvf Python-3.7.3.tgz # 使用tar命令解压安装包 安装相关工具 yum install -y gcc # 安装gcc编译器 yum -y groupinstall \u0026#34;Development tools\u0026#34; yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel libffi-devel # 安装相对应的插件 编译安装Python mkdir /usr/local/python3 # 创建目录给python3 cd Python-3.7.3/ # 进入刚刚解压的python安装包目录里面 ./configure --prefix=/usr/local/python3 # 生成makefile文件，这里的安装目录要设置为刚刚自己新建的目录 make # 使用make命令编译一波 make test # 检查一下有没有错误 make install # 进行安装 看到这个就算安装成功啦。\n创建软连接 ln -s /usr/local/python3/bin/python3.7 /usr/bin/python3 ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3 修改默认版本python和yum 由于这时候默认的python还是之前内置的2.x版本，所以我们要修改一下\nrm /usr/bin/python # 删除之前的python2的软连接 ln -s /usr/local/python3/bin/python3.7 /usr/bin/python # 新建一个软连接到python 由于yum默认使用的是python2，这个时候修改了肯定就无法运行，我们需要把yum的配置文件也修改一下\nll /usr/bin/ | grep python # 首先我们查看一下python的安装情况 从图中我们可以看到这台电脑安装了python2.7和python3.7，默认的python是指向3.7的。\nvim /usr/bin/yum # 把第一行的python改成对应的版本，图示为python2.7 vim /usr/libexec/urlgrabber-ext-down # 这里操作也和上面的一样 最后分别输入yum，python，python2，python3，pip等命令检查一下\n注意这里的V是大写哦。\n","permalink":"http://localhost:9011/20190413-centos-install-python/","summary":"\u003cp\u003eCentOS7配置Python3.7开发环境\u003c/p\u003e","title":"CentOS配置Python3开发环境"},{"content":"使用SSH远程连接Linux主机的时候，会因为一段时间没有执行操作，就和主机断开连接，我们可以通过修改主机端的SSHD配置文件来解决这个问题。\nvim /etc/ssh/sshd_config # 编辑sshd的配置文件 在大概一百多行的位置找到这三个参数，取消掉注释\nTCPKeepAlive是保存TCP连接存活 ClientAliveInterval的意思是服务器每隔一段时间发送一个加密的探活包到客户端，这里的默认单位是秒 ClientAliveCountMax则是当客户端多少次没有响应之后，与服务器断开连接。注意一般情况下都不会不响应 那么我们把配置改成上面这样，就可以理解为60秒*30次（30分钟）无响应就断开连接。\n最后我们重启一下服务让修改的配置生效即可。\nsystemctl restart sshd.service # 重启sshd服务让配置生效 ","permalink":"http://localhost:9011/20190411-fix-ssh-disconnect/","summary":"\u003cp\u003e使用SSH远程连接Linux主机的时候，会因为一段时间没有执行操作，就和主机断开连接，我们可以通过修改主机端的SSHD配置文件来解决这个问题。\u003c/p\u003e","title":"SSH远程连接Linux主机超时自动断开"},{"content":"iperf3是一个开源的多平台测速工具，涵盖了Windows，Android，Linux，iOS，MacOS等主流操作系统。本文以Windows为例记录一下在使用iperf3的过程中踩的一些坑。\n下载安装iperf3 iperf3的官网提供了所有平台的下载方法，我们点击这里跳转到官网。\nwindows端 Windows分为32位和64位的，对应自己的系统版本下载，下载解压完成之后，将解压得到的文件复制到%systemroot%目录下，就能够直接使用了。\n这里需要注意两点：\nWindows的%systemroot%目录是C:\\Windows\\System32 不管是使用CMD还是Powershell，都必须要以管理员身份运行，否则无法正常使用 Linux端 常见的主流Linux发行版（红帽系、debian系、suse系等）都可以直接通过对应的源或者包进行安装，如果是其他的Linux发行版，也可以选择手动安装\nsudo wget -O /usr/lib/libiperf.so.0 https://iperf.fr/download/ubuntu/libiperf.so.0_3.1.3 sudo wget -O /usr/bin/iperf3 https://iperf.fr/download/ubuntu/iperf3_3.1.3 sudo chmod +x /usr/bin/iperf3 如果出现下面这一类的报错，则需要将依赖文件拷贝到/lib目录和/lib64目录\nroot@tiny-unraid:~# iperf3 --help iperf3: error while loading shared libraries: libiperf.so.0: cannot open shared object file: No such file or directory root@tiny-unraid:~# ldd /usr/bin/iperf3 linux-vdso.so.1 (0x00007ffd35bde000) libiperf.so.0 =\u0026gt; not found libc.so.6 =\u0026gt; /lib64/libc.so.6 (0x0000154f00c69000) /lib64/ld-linux-x86-64.so.2 (0x0000154f00e5e000) root@tiny-unraid:~# cp /usr/lib/libiperf.so.0 /lib/libiperf.so.0 root@tiny-unraid:~# cp /usr/lib/libiperf.so.0 /lib64/libiperf.so.0 root@tiny-unraid:~# ldd /usr/bin/iperf3 linux-vdso.so.1 (0x00007ffd36c72000) libiperf.so.0 =\u0026gt; /lib64/libiperf.so.0 (0x000014dd232e4000) libc.so.6 =\u0026gt; /lib64/libc.so.6 (0x000014dd230ff000) libm.so.6 =\u0026gt; /lib64/libm.so.6 (0x000014dd22fb2000) /lib64/ld-linux-x86-64.so.2 (0x000014dd23516000) 运行服务端(server) iperf3是一款c-s软件，即client-server软件，分为客户端和服务端，服务端需要一直运行，客户端才能够正常使用。\niperf3 -help # 查看所有的操作指令 运行与退出 最常见的运行服务端的命令是：\niperf3 -s # 这里的s就是server的意思 这种情况下默认监听的是5201端口\n按下ctrl+c就可以关闭服务端\n如果是只运行一次测速就退出，可以这样：\niperf3 -s -1 # 运行一次后自动退出服务端 指定监听端口 如果需要指定特定的监听端口，我们可以使用-p参数，这里的p就是port的意思。\niperf3 -s -p 7777 # 手动指定监听端口为7777 需要注意的是手动指定监听端口的时候不要和其他应用的端口冲突了，否则会无法运行服务或产生服务冲突。\n守护进程模式 如果需要服务端在后台运行而不被关闭，可以尝试使用守护进程模式。\nDaemon()程序是一直运行的服务端程序，又称为守护进程。通常在系统后台运行，没有控制终端，不与前台交互，Daemon程序一般作为系统服务使用。Daemon是长时间运行的进程，通常在系统启动后就运行，在系统关闭时才结束。一般说Daemon程序在后台运行，是因为它没有控制终端，无法和前台的用户交互。Daemon程序一般都作为服务程序使用，等待客户端程序与它通信。我们也把运行的Daemon程序称作守护进程。\niperf3 -s -D # 注意D一定要大写 这个时候iperf3以服务端模式在后台运行，该进程不会轻易被结束运行。\n使用netstat命令可以看到此时5201端口正在被使用。\n如果需要关闭的话，我们可以使用taskkill指令。\ntasklist | findstr iperf3 # Windows下的CMD无法使用grep命令可以使用findstr命令来代替 taskkill /PID 9492 /F # 这里的9492要改成查询到的对应的PID # 由于是守护进程，所以需要强制终止 其他的一些指令大家可以自己继续尝试，小七就不再赘述。\n运行客户端(Client) 运行客户端的前提是有服务端在运行且我们知道服务端的IP并能正常访问服务端（有时候可能会被防火墙或其他杀软拦截）。\n简单测试 因为外部设备所限，接下来小七同时在本机上运行服务端和客户端进行测速示例。\n最简单的使用方法就是：\niperf3 -c ServerhostIP # hostIP为服务端的IP地址 由于这里小七是在本机上运行的服务端，因此使用本机的IP地址或者localhost或者127.0.0.1都可以。\n主要的几个参数列这里解释一下，Intervel指的是时间间隔，Transfer则是对应时间段里面传输的数据总量，Bandwidth则是该时间段内的平均带宽。\n自定义测试时间和输出格式 iperf3 -c localhost -i 2 -t 30 --logfile output.log 对于这一条指令，我们对后面的参数进行逐个解析。\n-i 2的作用是每2秒输出一次测试结果，数字2可以按照需要改成不同的数字 -t 30的作用是连续测试30s的时间 --logfile output.log的作用是将测试的结果输出到当前目录的output.log文件里面，需要注意的是此时不会在控制台上输出测试信息 ▲打开log文件可以看到对应的测试数据都保存在里面了\n如果需要指定路径输出json文件，我们只需要使用-J并在文件名前面指定路径即可：\niperf3 -c localhost -i 2 -t 30 -J --logfile C:\\Users\\Mr7th\\Desktop\\output.log # 以Json格式将测试结果文件输出到桌面 下面为截取的部分json文件\n{ \u0026#34;start\u0026#34;:\t{ \u0026#34;connected\u0026#34;:\t[{ \u0026#34;socket\u0026#34;:\t5, \u0026#34;local_host\u0026#34;:\t\u0026#34;::1\u0026#34;, \u0026#34;local_port\u0026#34;:\t7013, \u0026#34;remote_host\u0026#34;:\t\u0026#34;::1\u0026#34;, \u0026#34;remote_port\u0026#34;:\t5201 }], \u0026#34;version\u0026#34;:\t\u0026#34;iperf 3.1.3\u0026#34;, \u0026#34;system_info\u0026#34;:\t\u0026#34;CYGWIN_NT-10.0 Tiny-Surface 2.5.1(0.297/5/3) 2016-04-21 22:14 x86_64\u0026#34;, \u0026#34;timestamp\u0026#34;:\t{ \u0026#34;time\u0026#34;:\t\u0026#34;Tue, 09 Apr 2019 11:40:34 GMT\u0026#34;, \u0026#34;timesecs\u0026#34;:\t1554810034 }, \u0026#34;connecting_to\u0026#34;:\t{ \u0026#34;host\u0026#34;:\t\u0026#34;localhost\u0026#34;, \u0026#34;port\u0026#34;:\t5201 }, \u0026#34;cookie\u0026#34;:\t\u0026#34;Tiny-Surface.1554810034.839053.017f3\u0026#34;, \u0026#34;tcp_mss_default\u0026#34;:\t0, \u0026#34;test_start\u0026#34;:\t{ \u0026#34;protocol\u0026#34;:\t\u0026#34;TCP\u0026#34;, \u0026#34;num_streams\u0026#34;:\t1, \u0026#34;blksize\u0026#34;:\t131072, \u0026#34;omit\u0026#34;:\t0, \u0026#34;duration\u0026#34;:\t30, \u0026#34;bytes\u0026#34;:\t0, \u0026#34;blocks\u0026#34;:\t0, \u0026#34;reverse\u0026#34;:\t0 } }, \u0026#34;intervals\u0026#34;:\t[{ \u0026#34;streams\u0026#34;:\t[{ \u0026#34;socket\u0026#34;:\t5, \u0026#34;start\u0026#34;:\t0, \u0026#34;end\u0026#34;:\t2.000219, \u0026#34;seconds\u0026#34;:\t2.000219, \u0026#34;bytes\u0026#34;:\t1222770688, \u0026#34;bits_per_second\u0026#34;:\t4.890547e+09, \u0026#34;omitted\u0026#34;:\tfalse }], \u0026#34;sum\u0026#34;:\t{ \u0026#34;start\u0026#34;:\t0, \u0026#34;end\u0026#34;:\t2.000219, \u0026#34;seconds\u0026#34;:\t2.000219, \u0026#34;bytes\u0026#34;:\t1222770688, \u0026#34;bits_per_second\u0026#34;:\t4.890547e+09, \u0026#34;omitted\u0026#34;:\tfalse } }, { \u0026#34;streams\u0026#34;:\t[{ \u0026#34;socket\u0026#34;:\t5, \u0026#34;start\u0026#34;:\t2.000219, \u0026#34;end\u0026#34;:\t4.000079, \u0026#34;seconds\u0026#34;:\t1.999860, \u0026#34;bytes\u0026#34;:\t1384644608, \u0026#34;bits_per_second\u0026#34;:\t5.538966e+09, \u0026#34;omitted\u0026#34;:\tfalse }], \u0026#34;sum\u0026#34;:\t{ \u0026#34;start\u0026#34;:\t2.000219, \u0026#34;end\u0026#34;:\t4.000079, \u0026#34;seconds\u0026#34;:\t1.999860, \u0026#34;bytes\u0026#34;:\t1384644608, \u0026#34;bits_per_second\u0026#34;:\t5.538966e+09, \u0026#34;omitted\u0026#34;:\tfalse } }, 使用反转模式(Reverse) 默认情况下我们进行测试，都是客户端发送数据，服务器端接收数据，这种情况下相当于只对上行带宽进行了测试，如果我们需要检测下行带宽，只需要使用-R命令，即可变为客户端接收，服务器端发送。\n这里我们采用手机作为服务器端进行测试。\n▼默认情况下，电脑作为客户端，主要是发送数据（上行带宽）。\n▼使用反转模式(Reverse)，此时电脑仍然是客户端，只不过变成了接收数据（下行带宽）。\n其他的一些指令，大家感兴趣的可以点击这里查看官方的说明文档。\n","permalink":"http://localhost:9011/20190409-iperf3/","summary":"\u003cp\u003eiperf3是一个开源的多平台测速工具，涵盖了Windows，Android，Linux，iOS，MacOS等主流操作系统。本文以Windows为例记录一下在使用iperf3的过程中踩的一些坑。\u003c/p\u003e","title":"iperf3安装配置使用踩坑记录"},{"content":"记录一下给Hexo博客配置https域名过程中踩的各种坑。\n申请证书 SSL的证书基本可以分为申请的和自己创建的，阿里云上面有免费的可以申请，本着多一事不如少一事的原则（其实是太菜了），果断选择阿里云的SSL证书申请。\n▼点击SSL证书（应用安全）\n▼选择免费的那个。\n▼填写一些申请的信息。\n▼然后需要进行信息验证，一般都是DNS验证，如果是在阿里云购买的域名，会自动添加DNS解析验证，其他的就需要到域名管理里面手动添加了。\n▼提交成功之后耐心等待一下，很快就会申请通过。\n安装Apache和openssl 安装Apache 由于是在apache服务器上面搭建的博客，所以我的服务器已经安装好了apache，输入下列命令可以查看apache的版本信息。\nhttpd -v # 输入该命令查看apache的版本信息 如果没有安装apache的话，可以选择手动下载安装包安装或者是使用yum进行安装，不同的安装方式生成的文件目录会有一些不太一样，这对后面的SSL证书配置有着很大的影响。\nyum install httpd # 使用yum安装apache，这样安装的httpd的默认目录是在/etc/httpd文件夹里面 接着再安装一些必要的工具和扩展\nyum -y install gcc* make* apr apr-util pcre apr-devel apr-util-devel pcre-devel yum -y install httpd-manual mod_ssl mod_perl mod_auth_mysql 安装openssl 接着是使用yum安装openssl模块。如果有特殊需求的，可以到官网下载openssl自行编译，在这里只需要使用yum进行安装即可。\nyum install openssl openssl-devel 查看openssl版本\nopenssl version -a 确认安装好apache和openssl之后，我们进入下一步。\n上传证书到服务器 阿里云那边的证书申请好之后，我们下载证书到自己的电脑里面，解压可以得到类似这样子的三个文件。\n图中的是默认的名字，改不改都可以，不要搞混了就行，接下来我们把它们上传到服务器中的apache的安装目录下面，给他们专门新建一个cert文件夹。\n首先我们在服务器中给他新建一个目录\nmkdir /etc/httpd/cert 然后把本机的证书上传到服务器，我们可以直接使用scp命令上传文件，也可以使用Xshell中的Xftp来传输，还有其他的各种方式也都可以，反正传上去了就行。\n我这里使用scp命令为例：\n# scp命令的最基本用法是： scp fileLocation username@hostIP: DestinationLocation # 以在桌面传送一个文件到/etc/httpd/cert目录为例（此时我是在桌面直接打开gitbash） scp 1692315_tiny777.com.key root@47.107.188.168:/etc/httpd/cert 开启443端口 https默认是使用443端口进行监听，虽然阿里云默认是开启了443端口的，但是为了保险起见，我们还是确认一下。\n点开阿里云的控制台，点击轻量级应用服务器。如下图所示：\n配置ssl 启用ssl模块支持 这里就是比较坑的地方了，阿里云的帮助文档说的可能是别的方式安装的apache服务器，和我的情况不太一样。但是思路却还是正确的。\n这两步，第一步的主要作用是开启apache的ssl模块支持，第二步则是Include代码ssl的配置文件使其生效。\n使用yum安装的apache有一些不太一样，相对应的配置文件都在/etc/httpd/conf.modules.d这个目录下面，我们接下来主要会用到的就是这个00-ssl.conf和00-base.conf文件。\n打开00-ssl.conf就能看到对应阿里云教程里面的第一步操作，启用ssl模块支持。\n接下来我们在/etc/httpd/conf/httpd.conf这个文件里面，可以找到这一行（在56行左右的位置）\nInclude conf.modules.d/*.conf 这一行代码的意思就是Include了整个conf.modules.d文件夹里面的所有conf后缀的文件，这个00-ssl.conf文件当然也不例外。\n配置ssl 完成上面的两步操作之后，我们就要配置ssl的配置文件了。我们再看阿里云的文档。\n# 添加 SSL 协议支持协议，去掉不安全的协议 SSLProtocol all -SSLv2 -SSLv3 # 修改加密套件如下 SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:+MEDIUM SSLHonorCipherOrder on # 证书公钥配置 SSLCertificateFile cert/a_public.crt # 证书私钥配置 SSLCertificateKeyFile cert/a.key # 证书链配置，如果该属性开头有 \u0026#39;#\u0026#39;字符，请删除掉 SSLCertificateChainFile cert/a_chain.crt 还是一样的问题，路径不太正确，我们这里找到的文件路径是/etc/httpd/conf.d/ssl.conf\n在更改配置文件之前，为了保险起见，咱们先备份一下，以备不时之需。\ncp ssl.conf ssl.conf.bak 这里再给出我自己的ssl.conf文件和大家交流一下：\nListen 443 \u0026lt;VirtualHost *:443\u0026gt; DocumentRoot \u0026#34;/var/www/html\u0026#34; ServerName tiny777.com ServerAlias www.tiny777.com SSLEngine on SSLProtocol all -SSLv2 -SSLv3 SSLCertificateFile /etc/httpd/cert/1692315_tiny777.com_public.crt SSLCertificateKeyFile /etc/httpd/cert/1692315_tiny777.com.key SSLCertificateChainFile /etc/httpd/cert/1692315_tiny777.com_chain.crt DirectoryIndex index.html index.htm index.php \u0026lt;Directory \u0026#34;/var/www/html\u0026#34;\u0026gt; Options Indexes FollowSymLinks AllowOverride None Require all granted \u0026lt;/Directory\u0026gt; \u0026lt;/VirtualHost\u0026gt; 理论上这个时候重启apache服务已经可以输入https加上我们的域名来开启https访问了，但是这个时候的https和http相互独立，默认是访问http的域名，我们需要开启http域名自动跳转到https域名。\n自动跳转到https 保险起见，我们坚持一下自动跳转需要用到的rewrite模块是否开启了，还是在之前说的/etc/httpd/conf.d/00-base.conf文件里面查找一下rewrite模块，确定已经启用。\nLoadModule rewrite_module modules/mod_rewrite.so 接下来我们定位到这个目录\ncd /etc/httpd/conf 更改httpd.conf文件，在最后面加入这几行代码：\nRewriteEngine on RewriteCond %{SERVER_PORT} !^443$ RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301] 最后我们重启apache服务，就能开启域名的https访问了。\nsystemctl restart httpd.service 最终效果如下：\n","permalink":"http://localhost:9011/20190407-aliyun-centos-apache-https/","summary":"\u003cp\u003e记录一下给Hexo博客配置https域名过程中踩的各种坑。\u003c/p\u003e","title":"阿里云轻量级应用服务器CentOS系统Apache配置Https"},{"content":"通过修改注册表来删除资源管理器里面的3D Objects文件夹\n这个文件夹用不到而且很占位置，把下面的代码复制到文本文件里面并且更改后缀为reg文件即可通过修改注册表来对其删除。\nWindows Registry Editor Version 5.00 [-HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MyComputer\\NameSpace\\{0DB7E03F-FC29-4DC6-9020-FF41B59E513A}] 想要恢复的话，把上面代码的注册表路径最前面的减号“-”去掉就可以了。\nWindows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MyComputer\\NameSpace\\{0DB7E03F-FC29-4DC6-9020-FF41B59E513A}] ▲如图所示我将其命名为Add3DObject.reg和Delete3DObject.reg，然后直接双击运行。\n运行的时候会有警告，点击确定就可以了。\n最后的效果：\n","permalink":"http://localhost:9011/20190405-delete-3d-objects/","summary":"\u003cp\u003e通过修改注册表来删除资源管理器里面的3D Objects文件夹\u003c/p\u003e","title":"删除Windows中的3D对象文件夹"},{"content":"使用六类网线和六类水晶头制作直通线和交叉线，多图预警。\n工具准备 网线和水晶头 首先我们需要准备原材料：网线（双绞线）和水晶头，这里使用的是250Mhz的六类非屏蔽双绞线（UTP）和六类的水晶头。\n仔细观察线缆外面的塑料皮，我们能够看到一些基本信息，其中\n250指的是电缆里面的电流是250Mhz Category 6 则是我们常说的6类网线 U/UTP则是 Unshielded Twisted Pair的缩写，意为非屏蔽双绞线 制作工具 制作工具最少应该包括剥线刀、尖嘴钳、压线钳、测线仪四样工具。但是有些压线钳也有刀片能完成剥线刀和尖嘴钳的功能，测线仪如果没有的话，可以直接接到电脑上看能不能正常使用。\n剥皮理线 剥皮 首先我们使用剥线刀拨开双绞线的外皮，注意六类的双绞线比较粗，剥皮的时候注意不要太用力把里面的线芯伤到了。\n剥开外皮之后，我们可以看到六类双绞线里面有四股线共八根，分别是橙、绿、蓝、棕四种纯色和对应的白色相间颜色，每两根相互绞在一起，用来相互抵消电流的磁效应。这就是双绞线名称的由来。\n中间还有十字绝缘骨架和棉芯，其中棉芯是双绞线内部加强抗拉能力，用来防拉断的，而十字绝缘骨架则是六类线中新加入的，六类之前的双绞线都没有（包括超五类），其作用和棉芯基本相同。\nT568A和T568B类线序 我们按照从左到右的顺序给八根线依次标号为1~8号线，T568A类线序和T568B类线序对应的不同之处就是1236这四根线的线序不一样，我们看下表做个对比。\n1 2 3 4 5 6 7 8 T568A 绿白 绿 橙白 蓝 蓝白 橙 棕白 棕 T568B 橙白 橙 绿白 蓝 蓝白 绿 棕白 棕 在这里我们就能够看出来，A类线序和B类线序的区别就在于橙色和绿色这两股线的对应位置进行了对调。\n直通线和交叉线 一般来说，我们默认两端的线序都是T568B类的为直通线，而一端为A类，另一端为B类的为交叉线。\n那么直通线和交叉线的区别在哪里呢？\n在用途上，**直通线用于连接不同种的设备，**例如连接电脑和交换机，交换机和路由器等，**而交叉线用于连接同种设备，**例如电脑和电脑之间。\n那么为什么会有这两种线呢？\n首先我们需要知道，如果速率不超过百兆，即100Mbit，只需要双绞线中的1236四根线正常工作即可，其中两根负责接收/输入，两根负责发送/输出。\n线号 用途 1 输出数据(+) 2 输出数据(-) 3 输入数据(+) 6 输入数据(-) 那么在同种设备连接通信的时候，我们只需要把一端的输出输入线序对调，即可完成同种设备之间的连接通信。\n但是对于现在的网络设备来说，已经没有必要再使用交叉线了，因为现在的网卡基本都支持自动翻转功能，能将自动将输入/输出端进行对调，而不需要我们对双绞线的线序进行更改，因此现在基本都是统一使用直通线（两端都是B类线序，制作方便，不容易搞混）。\n制作水晶头 屏蔽和非屏蔽水晶头 **水晶头按照是否屏蔽可以分为屏蔽水晶头和非屏蔽水晶头，**一般来说我们日常接触到的应该都是非屏蔽水晶头。\n屏蔽水晶头最明显的特征就是是全金属或者部分金属，因为需要和屏蔽线缆的屏蔽层接触完成屏蔽工作。所以屏蔽水晶头更贵一些，能够用在屏蔽线（STP）和非屏蔽线（UTP）上面，而非屏蔽水晶头只能用在非屏蔽线上，用在屏蔽线上的话会使屏蔽线失去屏蔽效果。\n什么是8P8C **8P8C（8 position 8 contact）**的意思是8个位置（Position，指8个凹槽）、8个触点（Contact，指8个金属接点），正好和双绞线里面的8根线相对应，一根线一个槽一个触点。\n但是在比较久之前，一些廉价的网线会缩减非1236号线的线芯（使用廉价材料），有的甚至直接不放4578号线，一根网线里面只有1236这四根线，这些都是和现在的主流千兆网速所不兼容的遗留产物，大家选购网线的时候还是要买可靠的大牌子或者是从可靠的途径购买，切忌贪小便宜误了大事儿。\n超五类和六类水晶头 同样是非屏蔽水晶头的情况下，超五类的水晶头和六类水晶头看起来是一样的，唯一不同的地方就是，内部的金属触点的排列方式，由于六类线的纤芯比超五类的要更粗，所以六类水晶头的内部金属触点是上下交错排列，而超五类的是水平一字排开。\n制作水晶头 我们把上一步剥开的网线按照顺序排列好，然后用尖嘴钳把线头剪齐。\n▽如下图所示为T568B类线序\n▽如下图所示则为T568A类线序\n我们以T568B类线序为例，左手拿双绞线，右手拿水晶头，注意金属触点是面向自己，否则线序会做反。\n将双绞线插入水晶头，并且顶到最里面，注意插进去的时候不要把线序弄乱了，可以查看一下顶部确定每根线都顶到了最里面并且线序是正确的。\n将水晶头放入压线钳，压线钳也和网线一样分为五类六类七类等，高等级的可以压制低等级的水晶头（七类可以压制六类，六类不一定能压制七类）。\n水晶头压制前一定要确认好线序，因为一旦压下去了，发现线序错了或者有部分线没有接触好，只能把水晶头剪了重新制作。\n这里我使用的是七类的压线钳，压制六类线完全没有问题。\n将水晶头插入到最里面。\n使用压线钳之前，先把压线钳压到最底部，然后会自动回弹，这时再插入水晶头，再压制，只有压到底部压好了，压线钳才会自动回弹，这时候就能确定水晶头已经是做好了。\n测线 最后需要使用测线仪进行测线检测是否正常。一般的测线仪能进行单端测线和两端测线。测线仪上面标着的1-8号灯分别对应1-8号线，G对应的是屏蔽线的屏蔽层。\n如果是制作的直通线，两端的灯同时按照1-8的顺序亮起就是正常的，而如果做的是交叉线，则测线仪的主端（发射端）还是会按照1-8的顺序亮灯，小端（接收端）则亮灯的顺序有所不同，具体参考下表。\n主端（发射端） 小端（接收端） 1 3 2 6 3 1 4 4 5 5 6 2 7 7 8 8 测线通过之后，网线就算是制作好了，可以愉快地上网啦。\n","permalink":"http://localhost:9011/20190404-make-cat6-cable/","summary":"\u003cp\u003e使用六类网线和六类水晶头制作直通线和交叉线，多图预警。\u003c/p\u003e","title":"直通线和交叉线的制作"},{"content":"记录一下小米MIX2从MIUI稳定版→解锁BL→刷入MIUI开发版→获取ROOT权限→安装Google相机的全过程。\n解锁BL 小米MIX2有BL锁，因此需要先去官网申请解锁BL\nhttp://www.miui.com/unlock/index.html\n在一系列骚操作获得了解锁资格之后，下载解锁工具，按照指示来进行解锁。\n需要注意的是，这会清除掉手机上的所有数据！\n需要注意的是，这会清除掉手机上的所有数据！\n需要注意的是，这会清除掉手机上的所有数据！\n解锁BL之后没有那么安全了，因此需要将这台手机和小米账号进行绑定，之后每次重置手机都需要输入对应绑定的小米账号的密码，因此这里设置的小米账号的密码一定要记住了。\n下载的解锁BL工具包中有驱动和解锁工具，建议先进入BL模式，然后安装驱动，再进行解锁BL操作。\n解锁BL成功之后，手机会重置，需要进行重新设置，相当于刚买回手机的时候。这时候随便设置一下就可以了，因为待会儿还要刷入开发版，还要重新设置一次。\n刷入MIUI开发版 下载刷机包和工具 首先前往官网下载开发版的刷机包和线刷工具，使用线刷刷入。\nhttp://www.miui.com/shuaji-393.html\n进入Fastboot模式 关机状态下，同时按住 音量下+电源键 进入Fastboot模式将手机USB连接电脑。\n刷入刷机包 打开刷机工具，这里提示需要安装一些驱动，点击Install安装即可。\n对下载完的tar压缩包进行二次解压，最后得到的文件夹目录应该类似下面的图片。\n打开刷机工具选择解压生成的文件夹，右下角要手动选择clean all而不是clean all and lock，lock会再次对BL上锁，此时之前的解锁操作就白忙活了。\n确认上面的几处位置都没有问题之后，我们点击flash进行刷机，此时需要耐心等待一小会儿。\n刷机成功之后手机会自动重启进入设置界面，这次我们可以认真按照自己的需要进行设置。\n获取ROOT权限 MIUI的开发版可以很方便的获取ROOT权限，虽然说是阉割版的ROOT，但是也够谷歌相机使用了。\n我们打开手机内置的安全中心，选择应用管理，点击权限，选择开启ROOT权限，这时候会下载ROOT包并且重启安装。\n安装谷歌全家桶 首先保证手机可以正常访问Google（需要科学上网），然后进入应用商店（推荐安智），搜索谷歌安装器下载安装谷歌全家桶。\n安装好全家桶之后，登录谷歌账户，确认能正常访问GooglePlay商店，即可进入下一步。\n安装终端模拟器 安装终端模拟器（下载链接在后面），在窗口输入\nsu 这时系统会提示终端模拟器获取ROOT权限被拒绝，我们需要打开安全中心的权限管理→ROOT权限管理，手动给它授予ROOT权限。\n此时我们再打开终端模拟器，再次输入\nsu #切换到超级用户模式 setprop persist.camera.HAL3.enabled 1 #开启HAL3的支持 setprop persist.camera.eis.enable 1 #开启EIS的支持 getprop persist.camera.HAL3.enabled #返回1则成功，否则重启手机再次尝试 安装谷歌相机 谷歌相机和终端模拟器的下载地址在这里（MIX2版）\n链接：https://pan.baidu.com/s/1T-4_eXkiiFSwX03axsppZA 提取码：eo6r\n下载后直接安装即可，打开设置微调一下HDR，就能体验谷歌相机的变态级夜景模式和HDR+了。\n效果对比 上面的是小米自带的相机，下面的是谷歌相机，都打开了夜景模式，谷歌的还多了一个HDR+，小米的貌似不能同时使用夜景增强和HDR。\n","permalink":"http://localhost:9011/20190401-mix2-install-google-camera/","summary":"\u003cp\u003e记录一下小米MIX2从MIUI稳定版→解锁BL→刷入MIUI开发版→获取ROOT权限→安装Google相机的全过程。\u003c/p\u003e","title":"小米MIX2刷谷歌相机"},{"content":"简单介绍一百条关于计算机网络的基本知识。由于太长了，分成了上下两篇，每篇50条。\n当在不同的平台（如UNIX和Windows）之间传输文件时，可以应用什么协议？ 使用FTP（文件传输协议（英文：File Transfer Protocol））协议在不同的服务器之间进行文件传输是可能的，因为FTP是平台无关的。\n默认网关是什么？ 默认网关（Default Gateway）提供了本地网络连接到外部网络的方法。用于连接外部网络的默认网关通常是外部路由器端口的地址。\n保护网络的一种方法是使用密码。什么可以被认为是好的密码？ 良好的密码不仅由字母组成，还包括字母和数字的组合，结合大小写字母的密码比使用所有大写字母或全部小写字母的密码有利。密码必须不能被黑客很容易猜到，比如日期、姓名、收藏夹等等。\nUTP电缆的正确终止率是多少？ 非屏蔽双绞线的正常终止是100欧姆。\n什么是 netstat? Netstat 是一个命令行实用程序。它提供有关连接当前 TCP/IP 设置的有用信息。\nC 类网络中的网络 ID 数量是多少? C类网段计算： 根据规定，C类地址的网络标识必须以“110”开头。那么其网段数应该为110XXXXX．XXXXXXXX．XXXXXXXX．YYYYYYYY即后面有21位数字，因为是二进制数，所以网段数应该为：221，即2的21次幂个网段，等于2097152，所以B类网络可以有2097152个网段。 C类主机数计算： 因为后面8位是主机标识，所以主机数应该是28，即2的8次幂=256，扣除两个保留地址后，主机最大数应该是254个。\n全为0和全为1的为保留地址。\n使用长于规定长度的电缆时会发生什么? 电缆太长会导致信号丢失。这意味着数据传输和接收将受到影响，因为信号长度下降。\n什么常见的软件问题可能导致网络缺陷? 软件相关问题可以是以下任何一种或其组合：\n客户端服务器问题 应用程序冲突 配置错误 协议不匹配 安全问题 用户政策和权利问题 什么是 ICMP? **ICMP 是（Internet Control Message Protocol）Internet控制报文协议。**它为 TCP/IP 协议栈内的协议提供消息传递和通信。这也是管理由 PING 等网络工具使用的错误信息的协议。\n什么是 Ping? Ping 是一个实用程序，允许您检查网络上的网络设备之间的连接。您可以使用其 IP 地址或设备名称 (如计算机名称)ping 设备。\n什么是点对点（P2P）？ 对等网络（Peer-to-peer networking）或对等计算（Peer-to-peer computing），其可以定义为：网络的参与者共享他们所拥有的一部分硬件资源（处理能力、存储能力、网络连接能力、打印机等），这些共享资源通过网络提供服务和内容，能被其它对等节点（Peer）直接访问而无需经过中间实体。在此网络中的参与者既是资源、服务和内容的提供者（Server），又是资源、服务和内容的获取者（Client） 。\n该网络上的所有PC都是单独的工作站/服务器。\n什么是DNS？ 域名系统（英文：Domain Name System，缩写：DNS），该网络上的所有PC都是单独的工作站。\n光纤与其他介质有什么优势？ 光纤的一个主要优点是不太容易收到电气干扰（使用二氧化硅作为介质传导光信号），它还支持更高的带宽，意味着可以发送和接收更多的数据，长距离信号降级也非常小。\n集线器和交换机有什么区别？ 集线器中所有的端口为同一个冲突域，而交换机中每个端口都是一个单独的冲突域。\n集线器充当多端口中继器，然而，随着越来越多的设备连接到它，它将无法有效地管理通过它的流量。交换机提供了一个更好的替代方案，可以提高性能，特别是在所有端口上预期由高流量时。\nWindows RRAS服务支持的不同网络协议是什么？ **路由和远程访问服务器（Routing and Remote Access Service）**支持三种主要的网络协议：NetBEUI，TCP/IP和IPX。\nNetBEUI，即NetBios Enhanced User Interface，或NetBios增强用户接口。它是NetBIOS协议的增强版本，曾被许多操作系统采用，例如Windows for Workgroup、Win 9x系列、Windows NT等。NETBEUI是为IBM开发的非路由协议，用于携带NETBIOS通信。\nIPX（Internetwork Packet Exchange protocol，互联网分组交换协议），IPX协议与IP协议是两种不同的网络层协议，它们的路由协议也不一样，IPX的路由协议不像IP的路由协议那样丰富，所以设置起来比较简单。但IPX协议在以太网上运行时必须指定封装形式。\nABC三类网络中的最大网络和主机是什么？ 网络类别 可能的网络 最大主机数 A 126 16,777,214(2的24次方减2) B 16,384 65,534（2的16次方减2） C 2,097,152 254（2的8次方减2） 结合35题和56题的表格和解答数据不难算出上表中的数据。\n直通电缆的标准颜色顺序是什么？ 一般采用T568B类标准，具体线序为：橙白、橙、绿白、蓝、蓝白、绿、棕白、棕\n什么协议属于TCP/IP协议栈的应用层？ 以下是TCP/IP应用层协议：FTP,TFTP,Telnet和SMTP。\n可否不使用集线器或路由器而直接连接两台电脑进行文件共享？ 可以，使用交叉型电缆直接连接两台电脑的网卡即可。此时一条交叉型电缆的两头的数据发送和接收的引脚对调。\n什么是ipconfig？ ipconfig是一个常用于识别网络上计算机的地址信息的实用程序，它可以显示MAC地址、IP地址、子网掩码等各种信息。\n$ ipconfig -help Error: unrecognized or incomplete command line. USAGE: ipconfig [/allcompartments] [/? | /all | /renew [adapter] | /release [adapter] | /renew6 [adapter] | /release6 [adapter] | /flushdns | /displaydns | /registerdns | /showclassid adapter | /setclassid adapter [classid] | /showclassid6 adapter | /setclassid6 adapter [classid] ] where adapter Connection name (wildcard characters * and ? allowed, see examples) Options: /? Display this help message /all Display full configuration information. /release Release the IPv4 address for the specified adapter. /release6 Release the IPv6 address for the specified adapter. /renew Renew the IPv4 address for the specified adapter. /renew6 Renew the IPv6 address for the specified adapter. /flushdns Purges the DNS Resolver cache. /registerdns Refreshes all DHCP leases and re-registers DNS names /displaydns Display the contents of the DNS Resolver Cache. /showclassid Displays all the dhcp class IDs allowed for adapter. /setclassid Modifies the dhcp class id. /showclassid6 Displays all the IPv6 DHCP class IDs allowed for adapter. /setclassid6 Modifies the IPv6 DHCP class id. The default is to display only the IP address, subnet mask and default gateway for each adapter bound to TCP/IP. For Release and Renew, if no adapter name is specified, then the IP address leases for all adapters bound to TCP/IP will be released or renewed. For Setclassid and Setclassid6, if no ClassId is specified, then the ClassId is removed. Examples: \u0026gt; ipconfig ... Show information \u0026gt; ipconfig /all ... Show detailed information \u0026gt; ipconfig /renew ... renew all adapters \u0026gt; ipconfig /renew EL* ... renew any connection that has its name starting with EL \u0026gt; ipconfig /release *Con* ... release all matching connections, eg. \u0026#34;Wired Ethernet Connection 1\u0026#34; or \u0026#34;Wired Ethernet Connection 2\u0026#34; \u0026gt; ipconfig /allcompartments ... Show information about all compartments \u0026gt; ipconfig /allcompartments /all ... Show detailed information about all compartments 直通和交叉型电缆有什么区别？ 物理上，直通型电缆两头的线序相同（国内一般均为T568B类接法），而交叉型电缆两头的线序不同，接收端和发射端对调（国内一般是一头为T568A另一头为T568B）。\n用途上，直通型电缆连接不同类型设备，如电脑和交换机之间的连接；交叉型电缆连接同类型设备，如电脑和电脑直连。\n现在的新型网卡基本都支持自动翻转功能，能自动识别翻转接收端和发射端的线序，因此普遍都使用T568B类线序的直通型电缆来连接各种设备。\n什么是客户端/服务端？ 客户端/服务端是一种类型的网络，其中一个或多个计算机充当服务器。服务器提供集中的资源库，如打印机和文件等。客户端是指访问服务器的工作站。\n描述网络。 网络是指用于数据通信的计算机和外围设备之间的互连，可以使用有线电缆或通过无线链路进行网络连接。\n将NIC卡从一台PC移动到另一台PC时，MAC地址是否也被转移？ 是的，因为MAC地址时硬连线到NIC电路而不是PC，这也意味着当NIC卡被另一个替换时，PC可以具有不同的MAC地址。\n解释集群支持。 集群支持时指网络操作系统在容错组中连接多台服务器的能力。这样做的主要目的时在一台服务器发生故障的情况下，集群中的下一个服务器将继续进行所有处理。\n在包含两个服务器和二十个工作站的网络中，安装防病毒程序的最佳位置是哪里？ 必须在所有服务器和工作站上安装防病毒程序，以确保安全。这是因为个人用户可以访问任何工作站，并在插入可移动硬盘驱动器或闪存驱动器时引入计算机病毒。\n描述以太网。 以太网是当今使用的流行网络技术之一。它是在20实际70年代初开发的，并且基于IEEE中规定的规范。以太网在局域网中使用。\n实现环形拓扑有什么缺点？ 如果网络上的一个工作站发生故障，可能会导致整个网络丢失。另一个缺点是，当需要在网络的特定部分进行调整和重新配置时，整个网络也必须被暂时关闭。\nCSMA/CD和CSMA/CA有什么区别？ CSMA/CD：带有冲突检测的载波监听多路访问，可以检测冲突，但无法“避免”\nCSMA/CA：带有冲突避免的载波监听多路访问，发送包的同时不能检测到信道上有无冲突，只能尽量“避免”\nCSMA/CD（Carrier Sense Multiple Access with Collision Detection）即带冲突检测的载波监听多路访问技术(载波监听多点接入/碰撞检测)。\n**CSMA/CD控制方式的优点是：**原理比较简单，技术上易实现，网络中各工作站处于平等地位 ，不需集中控制，不提供优先级控制。但在网络负载增大时，发送时间增长，发送效率急剧下降。\nCSMA/CD应用在 OSI 的第二层数据链路层。\nCSMA/CA（Carrier Sense Multiple Access with Collision Avoidance）即载波侦听多路访问/冲突避免。\n工作原理是：首先检测信道是否有使用，如果检测出信道空闲，则等待一段随机时间后，才送出数据。接收端如果正确收到此帧，则经过一段时间间隔后，向发送端发送确认帧ACK。发送端收到ACK帧，确定数据正确传输，在经历一段时间间隔后，会出现一段空闲时间。\n什么是SMTP？ 简单邮件传输协议 (Simple Mail Transfer Protocol, SMTP) ，该协议处理所有内部邮件，并在TCP/IP协议栈上提供必要的邮件传递服务。\n什么是组播路由？ 组播路由是一种有针对性的广播形式，将消息发送到所选择的用户组，而不是将其发送到子网上的所有用户。\n加密在网络上的重要性是什么？ 加密是将信息转换成用户不可读的代码的过程，然后使用秘密密钥或密码将其翻译或解密回其正常可读格式。加密有助于确保中途截获的信息仍然不可读，因为用户必须具有正确的密码或密钥。\n如何安排和显示IP地址？ IP地址显示为一系列由周期或点分隔的四位十进制数字。这种安排的另一个术语是点分十进制格式。一个例子是192.168.1.1\n解释认证的重要性。 认证是在用户登录网络之前验证用户凭证的过程。它通常使用用户名和密码进行。这提供了限制来自网络上的有害入侵者的访问的安全手段。\n隧道模式是什么意思？ 这是一种数据交换模式，其中两个通信计算机本身不适用IPSec。相反，将LAN连接到中转网络的网关创建了一个使用IPSec协议来保护通过它的所有通信的虚拟隧道。\nIPSec（英语：Internet Protocol Security，缩写为IPsec），是一个协议包，透过对IP协议的分组进行加密和认证来保护IP协议的网络传输协议族（一些相互关联的协议的集合）。\n建立WAN链路涉及的不同技术有哪些？ 模拟连接——使用常规电话线； 数字连接——使用数字电话线； 交换连接——使用发送方和接收方之间的多组链接来移动数据。 网状拓扑的一个优点是什么？ 在一个链接失败的情况下，总会有另一个链接可用。网状拓扑实际上是容错率最高的网络拓扑之一。\n在排除计算机网络问题时，可能会发生什么常见的硬件相关问题？ 大部分网络由硬件组成，这些领域的问题可能包括硬盘故障，NIC损坏甚至硬件启动。不正确的硬件配置也是其中一个疑难问题。\n可以做什么来修复信号衰减问题？ 处理这种问题的常见方法时使用中继器和集线器，因为它将有助于重新生成信号，从而防止信号丢失。检查电缆是否正确终止也是必须的。\n动态主机配置协议（DHCP）如何协助网络管理？ 网络管理员不必访问每台客户端计算机来配置静态IP地址，而是可以应用DHCP来创建称为可以动态分配给客户端的范围的IP地址池。\n解释用户配置文件？ 配置文件是为每个用户设置的配置设置。例如，可以创建将用户至于组中的配置文件。\n什么是Sneakernet？ Sneakernet被认为是最早的联网形式，其中使用可移动介质（如磁盘、磁带）物理传输数据。\nSneakernet是一个非正式术语，用于通过将磁带，软盘，光盘，USB闪存驱动器或外部硬盘驱动器等媒体从一台计算机移动到另一台计算机来传输电子信息。而不是通过计算机网络传输信息。\nIEEE在计算机网络中的作用是什么？ 电气和电子工程师协会（IEEE，全称是Institute of Electrical and Electronics Engineers）是一个国际性的电子技术与信息科学工程师的协会。\nIEEE制定了许多关于计算机网络的协议和标准，如802.3等。\nTCP/IP Internet层下有哪些协议？ 该层管理的协议有4种。分别是ICMP,IGMP,IP和ARP。\n谈到网络，什么是权限？ 权限是指在网络上执行特定操作的授权许可。网络上的每个用户可以分配个人权限，具体取决于该用户必须允许的内容。\n建立VLAN的一个基本要求是什么？ 需要一个支持VLAN的网络设备。因为在交换机级别只有一个广播域，这意味着每当新用户连接时，该信息都会传播到整个网络。交换机上的VLAN有助于在交换机级别创建单独的广播域，它用于安全目的。\n什么是IPv6？ **IPv6是英文“Internet Protocol Version 6”（互联网协议第6版）的缩写，**是互联网工程任务组（IETF）设计的用于替代IPv4的下一代IP协议，能够克服IPv4地址数量不足的缺陷。\n什么是RSA算法？ RSA是Rivest-Shamir-Adleman算法的缩写。它是目前最常用的公钥加密算法。\n什么是网状拓扑？ 网状拓扑是一种设置，其中每个设备都直接连接到网络上的每个其他设备。因此，它要求每个设备具有至少两个网络连接。\n100Base-FX网络的最大段长度是多少？ 使用100Base-FX的网段的最大允许长度为412米，整个网络的最大长度为5公里。\n","permalink":"http://localhost:9011/20190331-base-know-of-cn-2/","summary":"\u003cp\u003e简单介绍一百条关于计算机网络的基本知识。由于太长了，分成了上下两篇，每篇50条。\u003c/p\u003e","title":"100条计算机网络基本知识（下）"},{"content":"简单介绍一百条关于计算机网络的基本知识。由于太长了，分成了上下两篇，每篇50条。\n什么是链接？ 链接是指两个设备之间的连接，它包括用于一个设备能够与另一个设备通信的电缆类型和协议。\nOSI参考模型的层次是什么？ 7个层次。\n物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。\n什么是骨干网？ 骨干网络是集中的基础设施，旨在将不同的路由和数据分发到各种网络。它还处理带宽管理和各种通道。\n什么是LAN？ **LAN是Local Area Network的缩写，即局域网。**它是指计算机与位于小物理位置（一般是方圆几千米以内）的其他网络设备之间的连接。\n什么是节点？ 节点是指发生连接的点。它可以是作为网络一部分的计算机或设备。为了形成网络连接，需要两个或更多个节点。\n什么是路由器？ 路由器可以连接两个或更多的网段。这些是在其路由表中存储信息的智能网络设备，例如路径、跳数等。有了这个信息，他们就可以确定数据传输的最佳路径。\n路由器在OSI网络层（三层）运行。\n什么是点对点连接？ 它是指网络上两台计算机之间的直接连接。除了将电缆连接到两台计算机的NIC卡之外，点对点连接不需要任何其他设备。\n什么是匿名FTP？ 匿名FTP（File Transfer Protocol）是授予用户访问公共服务器中的文件的一种方式。允许访问这些服务器中的数据的用户不需要识别自己，而是以匿名访客身份登录。\n什么是子网掩码? 子网掩码与IP地址组合，以识别两个部分：扩展网络地址和主机地址。像IP地址一样，子网掩码由32位组成。\n详情可以了解一下CIDR。\nUTP电缆允许的最大长度是多少？ UTP（Unshielded Twisted Pair，非屏蔽双绞线）电缆的单段具有90到100米的允许长度，这种限制可以通过使用中继器和开关来克服。\n什么是数据封装？ 数据封装是在通过网络传输信息之前将信息分解成更小的可管理块的过程。在这个过程中，源和目标地址与奇偶校验一起附加到标题中。\n描述网络拓扑。 网络拓扑是指计算机网络的布局。它显示了设备和电缆的物理布局，以及它们如何连接到彼此。\n什么是VPN？ VPN意味着虚拟专用网络（Virtual Private Network），这种技术允许通过网络（如Internet）创建安全通道。例如，VPN允许您建立到远程服务器的安全拨号连接。\n简要描述NAT。 NAT是网络地址转换（Network Address Translation）。这是一种协议，为公共网络上的多台计算机提供一种方式来共享到Internet的单一连接。\nOSI网络层的工作是什么？ 网络层负责数据路由，分组交换和网络拥塞控制。路由器在此层下运行。\n网络拓扑如何影响您在建立网络时的决策？ 网络拓扑决定了互联设备必须使用什么媒介。它还作为适用于设置的材料，连接器和终端的基础。\n什么是RIP? RIP(Routing Information Protocol,路由信息协议)，由路由器用于将数据从一个网络发送到另一个网络，它通过将其路由表广播到网络中的所有其他路由来有效地管理路由数据。它以跳数为单位确定网络距离。\n什么是不同的方式来保护计算机网络? 有几种方法可以做到这一点。建议多种方法组合使用。\n在所有计算机上安装可靠和更新的防病毒程序。 确保防火墙的设置和配置正确。 使用用户认证。 什么是NIC？ NIC（Network Interface Controller）网络接口控制器（网卡）。这是连接到PC以连接到网络的设备。每个NIC都有自己的MAC地址，用于标识网络上的PC。\n什么是WAN？ WAN（Wide Area Network）广域网。它是地理上分散的计算机和设备的互连，它连接位于不同地区和国家/地区的网络。\nOSI物理层的重要性是什么？ 物理层进行**从数据位到电信号的相互转换，**为数据端设备提供传送数据的通路。\nTCP/IP下有多少层？ 四层。\n网络层、互联网层、传输层、应用层。\n什么是代理服务器，它们如何保护计算机网络？ 代理服务器主要防止外部用户识别内部网络的IP地址，不知道正确的IP地址，甚至无法识别网络的物理位置，代理服务器可以使外部用户几乎看不到网络。\nOSI会话层的功能是什么？ 该层为网络上的两个设备提供协议和方法，通过举行会话来相互通信。这包括设置会话，管理会话期间的信息交换以及终止会话时的解除过程。\n实施容错系统的重要性是什么？有限吗？ 容错系统确保持续的数据可用性。这是通过消除单点故障来实现的。\n但是，在某些情况下，这种类型的系统将无法保护数据，例如意外删除（删库）。\nBase - T是什么意思？ 10是指数据传输速率，在这种情况下是10Mbps，“Base”是指基带传输。T表示双绞线，这是用于该网络的电缆。\n什么是私有IP地址？ 专用IP地址被分配用于内部网。这些地址用于内部网络，不能在外部公共网络上路由。这些确保内部网络之间不存在任何冲突，同时私有IP地址的范围同样可重复使用于多个内部网络，因为它们不会“看到”彼此。\n什么是NOS? NOS（Network Operating System）即网络操作系统是专门的软件，其主要任务是向计算机提供网络连接，以便能够与其他计算机和连接的设备进行通信。\n什么是DoS？ DoS（Denial of Service）即拒绝服务攻击是试图阻止用户访问互联网或任何其他网络服务。这种攻击可能有不同的形式，由一群永久者组成。这样做的一个常见方法是使系统服务器过载，使其无法再处理合法流量，并将被强制重置。\n什么是OSI，它在电脑网络中扮演什么角色？ **OSI（开放系统互联(Open System Interconnection)）**作为数据通信的参考模型。它由7层组成，每层定义了网络设备如何连接和通信的特定方面。每一层要完成相应的功能，**下一层（低层）为上一层（高层）提供服务，**从而把复杂的通信过程分成了多个独立的、比较容易解决的子问题。\n屏蔽电缆的屏蔽目的是什么？ 其主要目的是防止串扰。串扰是电磁干扰或噪音，可能影响通过电缆传输的数据。\n地址共享的优点是什么？ 通过使用地址转换（NAT）而不是路由，地址共享提供了固有的安全性优势。这是因为互联网上的主机只能看到提供地址转换的计算机上的外部接口的公共IP地址，而不是内部网络上的私有IP地址。\n什么是MAC地址？ **MAC（Media Access Control）即媒体介入控制，属于OSI中的数据链路层，由12位16进制的数字组成，共计6个Byte。**可以唯一地标识网络上的设备，它也被称为物理地址或以太网地址。\n在OSI参考模型方面，TCP/IP应用层的等同层或多层是什么？ TCP/IP应用层实际上在OSI模型上具有三个对等体：会话层，表示层和应用层。\n如何识别给定IP地址的IP类？ IP地址由32个Bit组成，每8个Bit分成一段，一共四段。通过查看任何给定IP地址的第一段的8个Bit，就可以识别它是哪一类。\n第一段八个Bit（二进制） 第一段8个Bit（十进制） IP类别 以0位开头 0~127 Class A 以10开头 128~191 Class B 以110开头 192~223 Class C OSPF的主要目的是什么？ OSPF(Open Shortest Path First开放式最短路径优先）,是使用路由表确定数据交换的最佳路径的链路状态路由协议。\n什么是防火墙？ 防火墙用于保护内部网络免受外部攻击。这些外部威胁可能是黑客想要窃取数据或计算机病毒，可以立即消除数据。它还可以防止来自外部网络的其他用户访问专用网络。\n描述星型拓扑。 星型拓扑由连接到节点的中央集线器（HUB）组成，这是最简单的设置和维护之一。\n什么是网关？ **网关提供两个或多个网段之间的连接。**它通常是运行网关软件并提供翻译服务的计算机。该翻译是允许不同系统在网络上通信的关键。\n星型拓扑的缺点是什么？ 星型拓扑的一个主要缺点是：一旦中央集线器或交换机损坏，整个网络就会瘫痪。\n什么是SLIP？ **SLIP（Serial Line Internet Protocol，串行线路网际协议）**实际上是在UNIX早期开发的旧协议。这是用于远程访问的协议之一。\n给出一些私有网络地址的例子。 IP地址 子网掩码 10.0.0.0 255.0.0.0 172.16.0.0 255.240.0.0 192.168.0.0 255.255.0.0 什么是Tracert？ Tracert是一个Windows实用程序，可用于跟踪从路由器到目标网络的数据采集的路由。它还显示了在整个传输路由期间采用的跳数。\n网络管理员的功能是什么? 网络管理员有许多责任，可以总结为3个关键功能：\n安装网络 配置网络 网络的维护和故障排除 描述对等网络的一个缺点。 当正在访问由网络上的某个工作站共享的资源的时候，该工作站的性能会降低。\n对等网络（Peer-to-peer networking）或对等计算（Peer-to-peer computing），其可以定义为：网络的参与者共享他们所拥有的一部分硬件资源（处理能力、存储能力、网络连接能力、打印机等），这些共享资源通过网络提供服务和内容，能被其它对等节点（Peer）直接访问而无需经过中间实体。在此网络中的参与者既是资源、服务和内容的提供者（Server），又是资源、服务和内容的获取者（Client） 。\n什么是混合网络？ 混合网络是利用客户端-服务器和对等体系结构的网络设置。\n什么是DHCP？ DHCP，动态主机设置协议（Dynamic Host Configuration Protocol）是一个局域网的网络协议，使用UDP协议工作。\n其主要任务是自动为网络上的设备分配IP地址，它首先检查任何设备尚未占用的下一个可用地址，然后将其分配给网络设备。\nARP的主要工作是什么？ ARP（Address Resolution Protocol）地址解析协议的主要任务是将已知的IP地址映射到MAC层地址。\n什么是TCP/IP？ TCP/IP(Transmission Control Protocol/Internet Protocol)传输控制协议/互联网协议，这是一组协议层/协议栈，旨在在不同类型的计算机网络（异构网络）上进行数据交换。\n如何使用路由器管理网络？ 路由器内置了控制台，可以配置不同的设置，如安全和数据记录。网络管理员可以为计算机分配限制，例如允许访问的资源，或者可以浏览互联网的特定时间段限制，甚至可以对整个网络中看不到的网站加以限制。\n","permalink":"http://localhost:9011/20190330-base-know-of-cn-1/","summary":"\u003cp\u003e简单介绍一百条关于计算机网络的基本知识。由于太长了，分成了上下两篇，每篇50条。\u003c/p\u003e","title":"100条计算机网络基本知识（上）"},{"content":"在CentOS7上面实现JDK的环境配置。\n下载软件包 注意使用wget命令的时候不能直接对文件链接进行下载\n因为Oracle官网需要我们在下载之前先同意协议，所以我们需要使用cookie，否则只能下载到一个html文件\nwget --no-check-certificate --no-cookies --header \u0026#34;Cookie: oraclelicense=accept-securebackup-cookie\u0026#34; https://download.oracle.com/otn-pub/java/jdk/8u201-b09/42970487e3af4f5aa5bca3f542482c60/jdk-8u201-linux-x64.tar.gz 使用file命令检测下载到的文件是否正常\nfile jdk-8u201-linux-x64.tar.gz $ file jdk-8u201-linux-x64.tar.gz jdk-8u201-linux-x64.tar.gz: gzip compressed data, from Unix, last modified: Sun Dec 16 03:48:30 2018 检测并卸载相关JDK 输入该指令检测是否存在已安装的jdk或者openJDK\nrpm -qa | grep java 使用该下面两个指令对已安装的jdk进行卸载和强制卸载（记得补充软件名）\nrpm e rpm e --nodeps 解压文件 解压文件\ntar -zxvf jdk-8u201-linux-x64.tar.gz 将解压后的文件夹移动到home目录下\ncp -r jdk1.8.0_201/ /home/ 配置环境变量 编辑环境变量\nvim /etc/profile JAVA_HOME=/home/jdk_1.8.0_201 CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin export JAVA_HOME CLASSPATH PATH 重新载入配置文件\nsource /etc/profile 检验安装是否成功 查看java版本是否配置成功\njava -version $ java -version java version \u0026#34;1.8.0_201\u0026#34; Java(TM) SE Runtime Environment (build 1.8.0_201-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode) ","permalink":"http://localhost:9011/20190317-centos-install-jdk/","summary":"\u003cp\u003e在CentOS7上面实现JDK的环境配置。\u003c/p\u003e","title":"CentOS配置JDK环境"},{"content":"MySQL学习笔记，第3篇。主要是在实现MySQL的一些基本操作（增删改查）。\nMySQL数据库 MySQL数据库是一种C/S结构的软件：客户端/服务端。 特点：若想访问服务器必须通过客服端（服务器一直运行，客户端在需要使用的时候运行）\n交互方式 客户端连接认证：连接服务器，认证身份：mysql.exe -hPup\nmysql -hlocalhost -P3306 -uroot -p 其中h是找主机的IP，这里是localhost即为本机，P是找端口，mysql对应的是3306端口，u是用户，这里是root用户，p则是使用密码\n客户端发送SQL指令 服务器接受SQL指令；处理SQL指令；返回操作结果 客户端接受结果：显示结果 断开连接（释放资源：保证服务器的并发性） MySQL服务器对象 因为我们没有办法完全了解服务器内部的内容，只能粗略地去分析数据库服务器内部的结构。\n将MySQL服务器内部对象分为四层：\n数据库管理系统（DBMS）-\u0026gt; 数据库（DB）-\u0026gt; 数据表（table）-\u0026gt; 字段（field）\nSQL基本操作 基本操作：增删改查\n将SQL的基本操作根据操作对象分为三类：库操作，表操作（字段），数据操作\n数据库操作 新增数据库 指令 create database 数据库名 [库选项] 2数据库命名规则 数据库名字不能使用关键字或者保留字 如果使用关键字或者保留字，需要使用反引号把数据库名字括起来 保证服务器可以识别中文的情况下可以使用中文命名数据库，但是非常不建议这样做 库选项 库选项：用来约束数据库，分为两个选项\n字符集设定：charset/character set 具体字符集（数据存储的编码格式）\n校对集设定：collate 具体校对集（数据比较的规则）\n注释 双中划线+空格或者使用井号\n-- 这是一个注释 # 这也是一个注释 报错规则 静默模式，即只会告知报错的大概位置而不会告知错误的原因。\n创建了数据库之后发生了什么 在数据库系统中，增加了对应的数据库信息； 会在保存数据库的文件目录下新增一个对应数据库名字的文件夹； 每个数据库文件夹中都会有一个对应的db.opt文件，文件中记录库选项 ▼图中为对应数据库的字符集设定和校对集设定。（校对集依赖字符集）\n查看数据库 查看所有数据库 mysql\u0026gt; show databases; 查看指定部分的数据库（模糊查询） show databases like \u0026#39;pattern\u0026#39;； 其中pattern是匹配模式的意思，分为两种匹配模式\n%：匹配多个字符 _:匹配单个字符\n查看以mydata_开头的数据库时，需要对_进行转义处理，即在前面加一个反斜杠，否则会查询错误，因为系统会认为mydata_中的_是匹配单个字符，而%是匹配多个字符，这样子的话：\nshow databases like \u0026#39;mydata_%\u0026#39; show databases like \u0026#39;mydata%\u0026#39; 这两条语句的实际使用效果就是一样的了。\n正确的操作应该是\nshow databases like \u0026#39;mydata\\_%\u0026#39; 查看创建数据库的时候使用的语句 show create database 数据库名; 因为SQL是一种编译型语言，所以数据库在执行SQL语句之前会对其进行优化，导致最终我们查询到的创建数据库时使用的语句和我们输入的语句并不完全一致。\n更新数据库 数据库名字不可以修改（修改名字不安全，牵一发而动全身）\n**数据库的修改 仅限于库选项，**即字符集和校对集（注意校对集依赖字符集）\nalter database 数据库名 [库选项]\ncharset/character set = 字符集\t//注意此处可以使用=也可以不用，但是一般不用\ncollate = 校对集\n一般不轻易修改库选项\n删除数据库 数据库属于结构，所以使用ddl语言，即drop语句；\ndrop database 数据库名; 注意：数据库删除后极难恢复，一定要注意备份，不要轻易删库\n数据库删除后发生了什么 在数据库内部该数据库被删除了； 数据库对应的文件夹也被递归删除了； ","permalink":"http://localhost:9011/20190310-mysql-note-03/","summary":"\u003cp\u003eMySQL学习笔记，第3篇。主要是在实现MySQL的一些基本操作（增删改查）。\u003c/p\u003e","title":"MySQL学习笔记3-SQL基本操作"},{"content":"主要是在CentOS7上安装MySQL服务。\nMySQL和MariaDB 需要注意的是，在CentOS7中，MySQL已经从默认的程序列表中移除了，并更换为了MariaDB。因此我们可以选择使用MariaDB来替代MySQL又或者是用手动的方式来下载yum资源包再进行安装。\nMariaDB 数据库管理系统是 MySQL 的一个分支，主要由开源社区在维护，采用 GPL 授权许可。开发这个分支的原因之一是：甲骨文公司收购了 MySQL 后，有将 MySQL 闭源的潜在风险，因此社区采用分支的方式来避开这个风险。MariaDB的目的是完全兼容MySQL，包括API和命令行，使之能轻松成为MySQL的代替品。\n检测并删除MySQL rpm -qa | grep mysql 这条指令是用rpm来查询安装的所有软件，其中q就是query，而a就是all。使用管道命令将查询到的结果交给grep命令，来抓取里面是否有mysql相关的软件。\n如果查询到没有mysql，就可以直接进入下一步，如果有，就先卸载掉。\nrpm -e mysql //-e 就是erase卸载软件 rpm -e --nodeps mysql //强力删除模式，如果使用上面命令删除时，提示有依赖的其它文件，则用该命令可以对其进行强力删除 安装MySQL 正如上面所说，CentOS7已经将MySQL移除出默认程序列表，所以我们要先去官网下载yum资源包，下载地址：https://dev.mysql.com/downloads/repo/yum/\nwget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm //使用wget命令下载资源包到本地目录中 rpm -ivh mysql-community-release-el7-5.noarch.rpm 使用rpm命令进行安装 RPM安装参数解释 -i, \u0026ndash;install install package(s) -v, \u0026ndash;verbose provide more detailed output -h, \u0026ndash;hashprint hash marks as package installs (good with -v)\nyum update //更新yum yum install mysql-server //安装mysql-server 设置权限 chown mysql:mysql -R /var/lib/mysql //使用chown命令更改权限，R表示递归处理该目录 启动MySQL 在centos7中，service命令已被systemctl命令替代\nsystemctl start mysqld.service //启动mysql服务 systemctl status mysqld.service //查询mysql服务状态 systemctl restart mysqld.service //重启mysql服务 查询mysql版本信息\nmysqladmin --version 输出结果如下\n$ mysqladmin --version mysqladmin Ver 8.42 Distrib 5.6.43, for Linux on x86_64 设置密码 mysql安装后，默认的root用户密码为空，因此我们需要设置密码\nmysql -u root //此时还没有密码，直接使用root用户即可登录 set password for root@localhost = password(\u0026#39;a_new_password\u0026#39;); //此时即可为root用户设置新的密码 mysql\u0026gt; set password for root@localhost = password(\u0026#39;a_new_password\u0026#39;); Query OK, 0 rows affected (0.00 sec) 登录使用mysql 使用mysql -u root -p进行登录，然后输入密码，即可进入mysql命令行，此时如果想要退出mysql命令行，输入quit或者exit或者按下ctrl+C即可。\n$ mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \\g. Your MySQL connection id is 2 Server version: 5.6.43 MySQL Community Server (GPL) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. Type \u0026#39;\\c\u0026#39; to clear the current input statement. mysql\u0026gt; show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | +--------------------+ 3 rows in set (0.01 sec) mysql\u0026gt; Ctrl-C -- exit! Aborted ","permalink":"http://localhost:9011/20190310-centos-install-mysql/","summary":"\u003cp\u003e主要是在CentOS7上安装MySQL服务。\u003c/p\u003e","title":"CentOS7安装MySQL"},{"content":"MySQL学习笔记。主要是各类基本定义和概念的了解。\n什么是数据库？ 数据（Data）的定义 描述事物的符号记录称为数据。\n数据库（DataBase）的定义 数据库是长期储存在计算机内的，有组织的，可共享的大量数据的集合。\n数据库中的数据按一定的数据模型组织、描述和储存，具有较小的冗余度、较高的数据独立性和易扩展性，并可为各种用户共享。概况地讲，数据库数据具有永久存储、有组织和可共享三个特点。\n数据库管理系统（DataBase Management System）的定义## 数据库管理系统是位于用户与操作系统之间的一层数据管理软件。\n数据库管理系统是一个系统软件，它的任务是科学地组织和存储数据，高效地获取和维护数据。\nDBMS的主要功能有数据定义功能，数据组织、存储和管理，数据操纵功能，数据库的事务管理和运行管理，数据库的建立和维护功能，其他功能。\n数据库系统（DataBase System）的定义 数据库系统是由数据库、数据库管理系统（及其应用开发工具）、应用程序和数据库管理员（DataBase Administrator）组成的存储、管理、处理和维护数据的系统。\n存储数据库的介质 介质主要分为两种：硬盘和内存\n数据库的分类和区别 分类 数据库基于存储介质的不同，可以分为两类：关系型数据库（SQL）和非关系型数据库（NoSQL）。\nNoSQL：Not Only SQL， 不是关系型的数据库都叫做非关系型数据库\n此外还可以基于对数据的处理方式的不同进行分类，还有诸如**面向对象型数据库（object-oriented database(OODB)）**等。\n2区别 SQL 安全（保存到磁盘，断电不会丢失） 基于关系的数据库便于人类理解和使用 但是比较浪费空间（使用二维表，对应的表格中不论有没有数据都要占用空间） NoSQL 效率高（因为存储在内存中，内存的速度要远高于硬盘） 不安全（内存断点丢失数据） 常见的数据库 关系型数据库 大型：Oracle,DB2 中型：SQL-SERVER,MySQL 小型：access等\n非关系型数据库 memcached(小)，mongodb（中），redis（大，可实现断电不丢失数据）\n什么是关系型数据库 定义 关系型数据库是一种建立在**关系模型（数学模型）**上的数据库。\n关系模型 关系模型是一种建立在关系上的模型，关系模型包含下列三个部分：\n数据结构：解决数据存储的问题，使用二维表来存储数据（包括行和列） 操作指令集合：所有的SQL语句 完整性约束：表内数据约束（字段与字段），表与表之间的数据约束（外键） 数据完整性约束指的是为了防止不符合规范的数据进入数据库，在用户对数据进行插入、修改、删除等操作时，DBMS自动按照一定的约束条件对数据进行监测，使不符合规范的数据不能进入数据库，以确保数据库中存储的数据正确、有效、相容。\n关系型数据库的设计 关系型数据库：从需要存储的数据需求中分析，如果 是一类数据（实体事物）应该设计成一张二维表。\n关系型数据库：维护实体内部，实体与实体之间的联系。\n表是由表头和数据部分组成。其中表头为字段名，用来规定数据的名称；数据组成部分为实际存储的数据单元。\n如下所示为一张简单的二维表:\n表头 字段名1 字段名2 数据单元a 数据a1 数据a2 数据单元b 数据b1 数据b2 数据单元c 数据c1 数据c2 简单案例分析 以教师、学生、班级三者为核心的教学系统案例进行一个简单的分析：\n找出该教学系统中的所有实体：教师、学生、班级\n列出每个实体对应所拥有的数据信息：（此处仅列举少量数据）\n教师：姓名、年龄、职称 学生：姓名、学号、专业 班级：班级名称、班级编号、上课地点 下面开始建表\n实体内部联系 下面是一张学生的信息表\n姓名 学号 专业 小明 001 计算机 小红 002 艺术设计 小黑 003 null 这里我们可以看到，表中小黑的专业这一栏是空白的（null），但是这一栏的位置还是要给小黑留着，不能就这样将其删除，这就是为什么关系型数据库占用位置的原因。\n第二行的所有字段（即整个第二行）都是在描述小明的个人信息，即描述小明这个学生，这个是内部联系。 第二列的所有字段都是学号，即整个第二列只能存放对应的学号信息，这个是内部约束。 实体与实体之间的关系 每个学生肯定属于某个班级，而一个班级应该有多个学生，这种是一对多的对应关系。\n这里以班级和学生为例\n首先我们补充一个班级表，还有补充限定规则，表中的班级名称和班级编号是唯一不可重复的，而上课地点则不是。\n班级表\n班级编号 班级名称 上课地点 CS01 计算机1班 D301 CS02 计算机2班 E204 AD01 艺术设计1班 G306 AD02 艺术设计2班 G407 这个时候我们如果需要把学生和班级之间建立联系，有两种选择：\n在班级表中插入学生信息 在学生表中插入班级信息 因为我们知道班级和学生是一对多的关系，一个班级可以有很多个学生，而一个学生只能属于一个班级，因此在学生表中插入班级信息则只需要多添加一列字段即可。 为了保证添加班级信息的时候只能够找到唯一一个对应的班级而不发生重复冲突的情况，对应的班级数据信息必须是在班级表中唯一不重复的，也就是班级编号或者是班级名称。\n于是我们得到了下表\n带班级信息的学生表\n姓名 学号 专业 班级 小明 001 计算机 CS01 小红 002 艺术设计 AD02 小黑 003 null null 行/记录和列/字段的区别\n行/记录：row/record，本质相同，都是指表中的一行/一条记录。\n列/字段：column/field，本质相同，都是指表中的一列/一个字段。\n只是行和列是从二维表的结构角度出发，而记录和字段是从数据库的数据角度出发。\n什么是SQL SQL：Structured Query Language，结构化查询语言，（并不只是查询操作，也有其他操作，只是因为数据库中的99%的操作都是查询操作） SQL分为三个部分：\n**DDL：Data Definition Language，数据定义语言，**用来维护存储数据的结构（数据库、表），代表指令：create，drop，alter等\n**DML：Data Manipulation Language，数据操纵语言，**用来对数据进行操作（数据表中的内容），代表指令：insert，delete，update等（分别对应增删改三个操作）；\n其中在DML内部又专门划分出一个分类：DQL：Data Query Language：数据查询语言，如select\n**DCL：Data Control Language，数据控制语言，**主要是负责权限管理（用户），代表指令：grant，revoke等。（对应分配和回收权限） 注意，SQL是关系型数据库的操作指令，SQL是一种约束但不强制（类似W3C标准）。这意味着不同的数据库如Oracle和MySQL之间的指令不一定能通用，可能会有细微的差别。\nMySQL数据库 MySQL数据库是一种C/S结构的软件：客户端/服务端。 特点：若想访问服务器必须通过客服端（服务器一直运行，客户端在需要使用的时候运行）\n交互方式 客户端连接认证：连接服务器，认证身份：mysql.exe -hPup\nmysql -hlocalhost -P3306 -uroot -p 其中h是找主机的IP，这里是localhost即为本机，P是找端口，mysql对应的是3306端口，u是用户，这里是root用户，p则是使用密码\n客户端发送SQL指令 服务器接受SQL指令；处理SQL指令；返回操作结果 客户端接受结果：显示结果 断开连接（释放资源：保证服务器的并发性） MySQL服务器对象 因为我们没有办法完全了解服务器内部的内容，只能粗略地去分析数据库服务器内部的结构。\n将MySQL服务器内部对象分为四层：\n数据库管理系统（DBMS）-\u0026gt; 数据库（DB）-\u0026gt; 数据表（table）-\u0026gt; 字段（field）\nSQL基本操作 基本操作：增删改查\n将SQL的基本操作根据操作对象分为三类：库操作，表操作（字段），数据操作\n数据库操作 新增数据库 指令 create database 数据库名 [库选项] 2数据库命名规则 数据库名字不能使用关键字或者保留字 如果使用关键字或者保留字，需要使用反引号把数据库名字括起来 保证服务器可以识别中文的情况下可以使用中文命名数据库，但是非常不建议这样做 库选项 库选项：用来约束数据库，分为两个选项\n字符集设定：charset/character set 具体字符集（数据存储的编码格式） 校对集设定：collate 具体校对集（数据比较的规则） 注释 双中划线+空格或者使用井号\n-- 这是一个注释 # 这也是一个注释 报错规则 静默模式，即只会告知报错的大概位置而不会告知错误的原因。\n创建了数据库之后发生了什么 在数据库系统中，增加了对应的数据库信息； 会在保存数据库的文件目录下新增一个对应数据库名字的文件夹； 每个数据库文件夹中都会有一个对应的db.opt文件，文件中记录库选项 ▼图中为对应数据库的字符集设定和校对集设定。（校对集依赖字符集）\n查看数据库 查看所有数据库 mysql\u0026gt; show databases; 查看指定部分的数据库（模糊查询） show databases like \u0026#39;pattern\u0026#39;； 其中pattern是匹配模式的意思，分为两种匹配模式\n%：匹配多个字符 _:匹配单个字符\n查看以mydata_开头的数据库时，需要对_进行转义处理，即在前面加一个反斜杠，否则会查询错误，因为系统会认为mydata_中的_是匹配单个字符，而%是匹配多个字符，这样子的话：\nshow databases like \u0026#39;mydata_%\u0026#39; show databases like \u0026#39;mydata%\u0026#39; 这两条语句的实际使用效果就是一样的了。\n正确的操作应该是\nshow databases like \u0026#39;mydata\\_%\u0026#39; 查看创建数据库的时候使用的语句 show create database 数据库名; 因为SQL是一种编译型语言，所以数据库在执行SQL语句之前会对其进行优化，导致最终我们查询到的创建数据库时使用的语句和我们输入的语句并不完全一致。\n更新数据库 数据库名字不可以修改（修改名字不安全，牵一发而动全身）\n**数据库的修改 仅限于库选项，**即字符集和校对集（注意校对集依赖字符集）\nalter database 数据库名 [库选项]\ncharset/character set = 字符集\t//注意此处可以使用=也可以不用，但是一般不用\ncollate = 校对集\n一般不轻易修改库选项\n删除数据库 数据库属于结构，所以使用ddl语言，即drop语句；\ndrop database 数据库名; 注意：数据库删除后极难恢复，一定要注意备份，不要轻易删库\n数据库删除后发生了什么 在数据库内部该数据库被删除了； 数据库对应的文件夹也被递归删除了； ","permalink":"http://localhost:9011/20190309-mysql-base-know/","summary":"\u003cp\u003eMySQL学习笔记。主要是各类基本定义和概念的了解。\u003c/p\u003e","title":"MySQL数据库基本概念"},{"content":"记录一些特殊的IP地址，排查网络故障的时候有利于快速定位问题。\n严格说来，这个奇葩的地址0.0.0.0已经不是一个真正意义上的IP地址了。它表示的是这样一个集合：也就是说；所有不清楚的主机和目的网络。这里的“不清楚”是指在本机的路由表里没有特定条目指明如何到达。对本机来说，它就是一个“收容所”，所有不认识的“三无”人员，一律送进去。如果你在网络设置中设置了缺省网关，那么计算机系统会自动产生一个目的地址为0.0.0.0的缺省路由。 限制广播地址 对本机来说，这个地址指本网段内(同一广播域)的所有主机。 然而它的意思很明确，使用人类语言来说意思就是“这里的所有计算机都注意了”这个地址不能被路由器所转发。\n本机地址 主要用于测试。用汉语表示，就是“我自己”。在Windows系统中，这个地址有一个别名“Localhost”。 寻址这样一个地址，是不能把它发到网络接口的。除非出错，否则在传输介质上永远不应该出现目的地址为“127.0.0.1”的数据包。 组播地址 注意它和广播的区别。从224.0.0.0到239.255.255.255都是这样的地址。224.0.0.1特指所有主机，224.0.0.2特指所有路由器。这样的地址多用于一些特定的程序以及多媒体程序。如果你的主机开启了IRDP(Internet路由发现协议），使用组播功能功能，那么你的主机路由表中应该有这样一条路由。 x.x 如果你的主机使用了DHCP功能自动获得一个IP地址，那么当你的DHCP服务器发生故障，或响应时间太长而超出了一个系统规定的时间，计算机操作系统会为你分配这样一个地址。如果你发现你的主机IP地址是一个诸如此类的地址，很不幸的是，现在你的网络不能正常运行了。 x.x.x；172.16.0.0\u0026mdash;172.31.255.254；192.168.x.x；私有地址 这些地址被大量用于企业内部网络中。一些宽带路由器，也往往使用192.168.1.1作为缺省地址。私有网络由于不与外部互连，因而可能使用随意的IP地址。保留这样的地址供其使用是为了避免以后接入公网时引起地址混乱。使用私有地址的私有网络在接入Internet时，要使用地址翻译(NAT)，将私有地址翻译成公用合法地址。在Internet上，这类地址是不能出现的。对一台网络上的主机来说，它可以正常接收的合法目的网络地址有三种：本机的IP地址、广播地址以及组播地址。\n","permalink":"http://localhost:9011/20190227-special-ip/","summary":"\u003cp\u003e记录一些特殊的IP地址，排查网络故障的时候有利于快速定位问题。\u003c/p\u003e","title":"计算机中的一些特殊IP地址"},{"content":"《编译原理》复习提纲，啥也别说了，大家考试好运(ง •_•)ง\n编译过程 编译程序的基本任务是将源语言程序翻译成等价的目标语言程序。\n一般的，将编译过程划分成词法分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成6个阶段。\n词法分析 词法分析的任务是从左到右一个字符一个字符地读入源程序，对构成源程序的字符流进行扫描和分解，从而识别出一个个单词*（一些场合下也称单词符号或符号）*。\n这里所谓的单词是指逻辑上紧密相连的一组字符，这些字符具有集体含义。\n语法分析 语法分析的任务是再词法分析的基础上将单词序列分解成各类语法短语。\n这种语法短语也称为语法单位，可表示成语法树，如图所示。 语法分析所依据的是语言的语法规则，即描述程序结构的规则。通过语法分析确定整个输入串是否构成一个语法上正确的程序。\n词法分析和语法分析本质上都是对源程序的结构进行分析。但词法分析的任务仅对源程序进行线性扫描即可完成，比如识别标识符，因为标识符的结构是字母打头的字母和数字串，这只要顺序扫描输入流，遇到既不是字母又不是数字的字符时，将前面所发现的所有字母和数字组合在一起构成标识符单词即可。但这种线性扫描不能用于识别递归定义的语法成分，比如不能用此办法去匹配表达式中的括号。\n语义分析 语义分析是审查源程序有无语义错误，为代码生成阶段收集类型信息。 例如，语义分析的一个工作是进行类型审查，审查每个算符是否具有语言规范允许的运算对象，当不符合语言规范时，编译程序应报告错误。\n中间代码生成 在进行了上述的语法分析和语义分析阶段的工作之后，有的编译程序将源程序变成一种内部表示形式，这种表现形式叫做中间语言或中间代码。\n所谓“中间代码”是一种结构简单、含义明确的记号系统，这种记号系统可以设计为多种多样的形式，重要的设计原则为两点：一是容易生成；二是容易将它翻译成目标代码。\n很多编译程序采用了一种近似“三地址指令”的“四元式”中间代码，这种四元式的形式为\n（运算符，运算对象1，运算对象2，结果）\nid1:= id2 + id3 * 10 (1)\t(*\tid3\t10\tt1) (2)\t(+\tid2\tt1\tt2) (3)\t(:=\tt2\t-\tid1) 代码优化 对前一阶段产生的中间代码进行变换或进行改造，目的是使生成的目标代码更为高效，即省时间和省空间。\nid1:= id2 + id3 * 10 (1)\t(*\tid3\t10\tt1) (2)\t(+\tid2\tt1\tid1) 目标代码生成 把中间代码变换成特定机器上的一个绝对指令代码或可重定位的指令代码或汇编指令代码。\n(1)\t(*\tid3\t10\tt1) (2)\t(+\tid2\tt1\tid1) mov id3,R2 mul 10, R2 mov id2,R1 add R2,R1 mov R1,id1 上述编译过程的阶段划分是一个典型处理模式，**事实上并非所有的编译程序都分成这几个阶段，**有些编译程序并不需要生成中间代码，有些编译程序不进行优化，即优化阶段可省去，有些最简单的编译程序在语法分析的同时产生目标指令代码，如PL/0语言编译程序。不过多数实用的编译程序都包含上述几个阶段的工作过程。\n编译程序其他要点 符号表 记录源程序中使用的名字\n收集每个名字的各种属性信息\n类型、作用域、分配存储信息\n出错处理 检查错误、报告出错信息、排错、恢复编译工作。\n编译阶段的划分 前端和后端\n编译的前端：与源语言有关但与目标机无关的那些部分组成\n编译的后端：与目标机有关的那些部分组成\n一个前端+多个后端：多平台编译器 例 Java 多个前端+一个后端：多语言编译器 例 .NET 遍（趟）：从头到尾扫描源程序一次称为一遍 解释过程（解释程序） 这里介绍另一种语言处理程序，它不需要在运行前先把源程序翻译成目标代码，也可以实现在某台机器上运行程序并生成结果。\n**解释程序接受某个语言的程序并立即运行这个源程序。**它的工作模式是一个个的获取、分析并执行源程序语句，一旦第一个语句分析结束，源程序便开始运行并生成结果，它特别适合程序员以交互工作方式工作的情况，即希望在获取下一个语句之前了解每个语句的执行结果，允许执行时修改程序。\n著名的解释程序有Basic语言解释程序 ,Lisp语言解释程序,UNIX命令语言解释程序(shell),数据库查询语言SQL 解释程序以及bytecode解释程序。\n编译程序和解释程序的对比 概念 编译：整个程序全部翻译结束之后，程序才能开始运行；编译和运行是两个独立分开的阶段。\n解释：不需要将分析和执行阶段分开，一边分析一边执行；更加适用于交互环境中。\n编译和解释的比较 编译结果的执行效率比解释快很多\n解释执行的空间开销大大超过编译结果的执行\nJava C# 和 传统语言 C Pascal Java语言的处理环境既有编译程序，也有解释程序\njava源程序经过JAVA编译程序编译后生成class可执行字节码文件（BYTECODE）\n可执行字节码文件（BYTECODE）在java虚拟机（JVM）上经过解释程序一行行解释执行\n可以说JAVA编译程序兼具编译型语言和解释型语言的特点\n传统语言都是一次编译，没有生成中间代码\n文法的概念 文法即是以有穷的集合刻画无穷的集合的一个工具。\n文法类型与形式（0—3） 0型文法→短语文法\n1型文法→上下文有关\n2型文法→上下文无关\n3型文法→正规文法\n编程语言使用哪些文法 语法上使用的是上下文无关文法（2型）\n标识符、数字等使用的都是正规文法（3型）\n句型、句子 我不知道(ノ｀Д)ノ\n有害规则和多余规则 文法中不含有有害规则和多余规则 有害规则 形如U→U的产生式。会引起文法的二义性\n多余规则 指文法中任何句子的推导都不会用到的规则\n文法中某些非终结符（除了开始符）不在任何规则的右部出现，该非终结符称为不可到达。\n文法中某些非终结符，由它不能推出终结符号串，该非终结符称为不可终止。\n上下文无关文法及其语法树 **最左（最右）推导：**在推导的任何一步α=\u0026gt;β，其中α、β是句型，都是对α中的最左（右）非终结符进行替换,则称这种推导为最左（右）推导。\n最右推导被称为规范推导，由规范推导所得的句型称为规范句型\n**短语：**子树的所有叶子节点集合为一个短语，拥有多个子树就有多个短语（一个句型的语法树中任一子树叶结点所组成的符号串都是该句型的短语）\n**直接短语：**子树中不再包含其他的子树，即A只能推导出b，而b不能再推出其他的式子，则b为此句型的直接短语\n**句柄：**最左直接短语\n文法的二义性与语言的二义性关系 如果一个文法存在某个句子对应两颗不同的语法树，则说这个文法是二义的。\n二义性文法可能产生同一个语言。\n如果产生上下文无关语言的每一个文法都是二义的，则说此语言是先天二义的。\n优先关系概念 算符文法概念 子程序参数类型 机器指令 动态堆式分配 ","permalink":"http://localhost:9011/20190117-compiling/","summary":"\u003cp\u003e《编译原理》复习提纲，啥也别说了，大家考试好运(ง •_•)ง\u003c/p\u003e","title":"编译原理复习提纲"},{"content":"《操作系统》部分重点内容，包括讨论题和部分小七自己整理的概念. 重要知识点。\n请你给操作系统下个定义？ 一般可把操作系统定义为：管理系统资源. 控制程序执行. 改善人机界面. 提供各种服务，并合理组织计算机工作流程和为用户方便有效地使用计算机提供良好运行环境的一种系统软件。\n请叙述操作系统的专业地位和在计算机系统中的地位？ 操作系统属于专业基础课\n系统软件（操作系统）层是最靠近硬件的一层软件，它一方面直接和硬件交互，在裸机上运行；另一方面和上层的支撑软件和应用软件交互，把它们与计算机硬件隔离开来。\n操作系统是最靠近硬件的一层软件，它把硬件裸机改造成为完善的虚拟机，使得机器功能得到扩展，运行环境得到改善，系统效率得到提高，安全性能得到保障。\n系统软件（操作系统）层是最靠近硬件的一层软件，它一方面直接和硬件交互，在裸机上运行，把硬件的复杂性封装起来，负责管理和控制机器硬件并对其做首次扩充和改造，主要做好资源的调度与分配. 信息的存取与保护. 并发活动的协调与控制等工作；另一方面和上层的支撑软件和应用软件交互，把它们与计算机硬件隔离开来，为程序员提供方便的编程接口. 有力的功能支撑. 良好的运行环境，使得计算机系统成为完整. 可用和高效的计算平台。\n操作系统系统的作用是什么？ 操作系统在计算机系统中起4个方面的作用。\n服务用户观点——操作系统作为用户接口和公共服务程序 进程交互观点——操作系统作为进程执行的控制者和协调者 系统实现观点——操作系统作为扩展机或虚拟机 资源管理观点——操作系统作为资源的管理者和控制者 操作系统所管理的资源有那些？支持的界面使用方式有哪些？ 在计算机系统中，能分配给用户使用的各种软硬件设施总称为资源。\n**资源包括两大类：硬件资源和软件资源。**其中，硬件资源有处理器. 存储器. 外部设备等；软件资源则分为程序和数据。\n支持的界面使用方式主要有两种：命令行模式（CLI）和图形化界面（GUI）\n什么原因推动我们操作系统的发展？ 两个原因：人类日常生产生活的实际需求和硬件设备（尤其是集成电路）的发展。\n何为中断？中断处理过程如何？中断分类？中断服务？中断源？中断优先级？中断屏蔽与开关中断？中断要保存和恢复的内容有哪些？ 中断的定义 中断指在程序执行过程中遇到急需处理的事件时，暂时中止现行程序在CPU上的运行，转而执行相应的事件处理程序，待处理完成后再返回断点或调度其他程序执行的过程。\n中断处理过程如何？ 一般来说，中断/异常的响应需要顺序做4件事：\n发现中断源 保护现场 转向中断/异常事件处理程序执行 恢复现场 中断分类？ 由硬件发出或产生的中断称为硬中断，按硬中断事件的来源和实现手段可将中断划分为外中断和内中断。\n**外中断又称中断或异步中断，**是指来自处理器之外的中断信号； **内中断又称异常或同步中断，**是指来自处理器内部的中断信号； 中断的发生与CPU当前状态无关，即可发生在用户态，又可发生在内核态； 异常与CPU是同步的，大部分异常发生在用户态，内核态唯一发生的异常是“缺页异常”\n中断服务？ **摘自百度百科：**中断服务程序，处理器处理“急件”，可理解为是一种服务，是通过执行事先编好的某个特定的程序来完成的，这种处理“急件”的程序被称为——中断服务程序。\n中断源？ 中断源指中断信号的来源。\n中断优先级 中断装置所预设的响应顺序称为中断优先级。\n中断屏蔽与开关中断？ 中断屏蔽是指禁止CPU响应中断或禁止中断产生。\n前者指硬件产生中断请求后，CPU暂时不予响应的状态，等待直到中断开放后被屏蔽的中断才能被响应并获得处理。 后者指可引起中断的事件发生时，硬件不允许提出中断请求也不通知处理器，故由于中断被禁止而不可能导致中断。 中断要保存和恢复的内容有哪些？ 通用寄存器. 状态寄存器. 程序计数器(PC). 程序状态字(PSW)\n系统调用？ 系统调用是一种中介角色，把用户和硬件隔离开来，应用程序只有通过系统调用才能请求系统服务并使用系统资源。\n系统调用的作用：一是内核可以基于权限和规则对资源访问进行裁决，保证系统的安全性；二是系统调用对资源进行抽象，提供一致性接口，避免用户在使用资源时发生错误，且使编程效率大大提高。\n可以这样认为：内核的主体是系统调用的集合，可以将内核的服务例程看成特殊的公共子程序。\n系统调用是应用程序获得操作系统服务的唯一途径。\n市场上os的分类？ 市场上OS的分类依据分类标准的不同可以有许多种分法：\n按照使用设备来进行分类的话，大概可以分为三类： 移动端：主要是Android和IOS占主流，也有少数的如WindowsMobile等小众移动操作系统；\n桌面端：主要是Windows. MacOS和主流的Linux发行版如Redhat. Ubuntu. CentOS. SUSE等，还有少数的Unix操作系统；\n服务器端：主要运行的是Linux和Unix操作系统\n其他还有更细致的分类诸如嵌入式系统等。\n按照功能. 特点和使用方式，可以把操作系统分为三种基本类型 批处理操作系统 批处理操作系统服务于一系列称为批（batch）的作业。\n采用批处理方式工作的操作系统称为批处理操作系统。\n批处理操作系统是最先采用多道程序设计技术的系统，它根据预先设定的调度策略选择若干作业并发地执行，系统资源利用率高，作业吞吐量大。\n批处理操作系统的缺点是作业周转时间延长，不具备交互式计算能力，不利于程序的开发和调试。\n批处理操作系统的特征是批量集中处理. 多道程序运行. 作业脱机工作。\n分时操作系统 实质上，分时操作系统是多道程序的一个变种，CPU被若干交互式用户多路复用，不同之处在于每个用户都拥有一台联机终端。\n分时操作系统的四大特点为：同时性. 独立性. 及时性. 交互性。\n**同时性：**即若干终端用户联机使用计算机，分时是指多个用户分享同一台计算机的CPU时间；\n**独立性：**即终端用户彼此独立，互不干扰，每个终端用户感觉好像独占整台计算机；\n**及时性：**即终端用户没有大计算量的立即型请求能够在足够短时间内得到响应；\n**交互性：**即人机交互，联机工作时用户直接控制程序运行，便于程序调试和排错。\n实时操作系统 实时操作系统时指外部事件或数据产生时，能够对其予以足够快的速度进行处理，所得结果能够在规定的时间内控制生产过程或对控制对象做出快速响应，并控制所有实时任务协调运行的操作系统。\n提供及时响应和高可靠性是其主要特点。\n有三种典型实时系统：过程控制系统. 信息查询系统和事务处理系统。\n如果某个操作系统兼具批处理. 分时和实时处理的全部或两种功能，则此操作系统称为通用操作系统。\n单道程序设计和多道的区别？ 在早期单道批处理系统中，内存中仅有单个作业在运行，CPU和其他硬件设备串行工作，致使系统中仍有许多资源空闲，设备利用率极低。\n多道程序设计是指允许多个作业（程序）同时进入计算机系统的内存并启动交替计算的方法。\n系统调用与函数调用之间的区别？ 两者从调用形式到具体实现都存在很大区别：\n调用形式和实现方式不同： 函数调用所转向的地址式固定不变的，但系统调用中不包含内核服务例程入口地址，仅提供功能号，按功能号调用；\n函数调用是在用户态执行，只能访问用户栈；\n系统调用需要通过陷阱机制，从用户态转换到内核态，服务例程在内核态执行并访问核心栈。\n被调用代码的位置不同： 函数调用是静态调用，调用程序和被调用代码处于同一程序内，这是用户级程序\n系统调用是动态调用，系统调用的服务例程位于操作系统中，这是系统级程序\n提供方式不同： 函数调用通常由编程语言提供，不同的语言提供的函数功能的类型和数量可以不同\n系统调用由操作系统提供，操作系统一般设计好，系统调用的功能. 类型和数量便固定不变\n操作系统结构分类？ Linux系统使用的是单体式结构。\n单体式结构 操作系统单体式结构采用模块组合法，是基于结构化程序设计的一种软件结构设计方法。\n**优点是：**结构紧密. 组合方便，对不同环境和用户的不同需求可以组合不同模块来满足，灵活性大；针对某个功能可用最有效的算法和任意调用其他模块中的过程来实现，因此系统效率高。\n** 缺点是：**模块独立性差，模块之间牵连甚多，形成复杂的调用关系，甚至有循环调用，造成系统结构不清晰，正确性难以保证，可靠性降低，系统的增. 删. 改困难。\n层次式结构 这种结构把操作系统划分为内核和若干模块（进程），这些模块（进程）按功能的调用次序排列成若干层次，各层之间只能存在单向依赖或单向调用关系，即低层为高层服务，高层可以调用低层功能，反之则不能。\n虚拟机结构 它基于如下思想：物理计算机资源通过多重化和共享技术可改造成多个虚拟机。 这种技术的基本做法是：通过用一类物理设备来模拟另一类物理设备，或通过分时地使用一类物理设备，把一个物理实体改变成若干个逻辑上的对应物。\n微内核结构 操作系统仅将所有应用必需的核心功能放入内核，称为微内核，其他功能都在内核之外，由在用户态运行的服务进程实现，通过微内核所提供的消息传递机制完成进程间消息通信。\n微内核结构的优点，一是对进程的请求提供一致性接口，不必区分内核级服务和用户级服务，所有服务均借助消息传递机制提供；二是具有较好的可扩充性和易修改性，三是可移植性好；四是对分布式系统提供有力支撑；\n微内核结构的缺点是运行效率较低，这是因为进程间必须通过内核的通信机制才能进行通信。\n自由软件？ 自由软件（free software，又称freeware）是指遵循通用公共许可证（General Public License，GPL）规则，保证使用上的自由，获得源程序的自由，可以自行修改的自由，可以复制和推广的自由，也可以有收费自由的一种软件。\n注意 ：自由软件不是免费软件，自由软件也有收费的。\n作业生命周期状态？ 输入状态 后备状态 执行状态 完成状态 何为低级调度？ **低级调度又称进程调度/线程调度. 短程调度，**根据某种原则决定就绪队列中的哪个进程/线程获得处理器，并将处理器出让给它使用。\n进程调度方式有哪些？解释这些方式？ 低级调度有两类基本调度方式：剥夺式和非剥夺式。\n剥夺式 剥夺式又称为抢先式。当进程/线程正在处理器上运行时，**系统可根据规定的原则剥夺分配给此进程/线程的处理器，**并将其移入就绪队列，选择其他进程/线程运行。\n非剥夺式 非剥夺式又称非抢先式。**一旦某个进程/线程开始运行后便不再让出处理器，**除非此进程/线程运行结束或主动放弃处理器，或因发生某个事件而不能继续执行。\n进程调度队列模型有何作用？ 进程队列调度模型负责处理器的分配，作用为以下几点：\n将系统中各进程的执行情况和状态特征记录在各进程的PCB表中并将各进程的PCB表排成相应的队列。\n通过PCB变化来掌握系统中存在的所有进程的执行情况和状态特征，并在适当的时机从就绪队列中选择出一个进程占据CPU。\n当进程调度为剥夺式时且新就绪进程优先级高于当前进程时能取回CPU并分配给新进程。\n何为周转时间？平均周转时间？响应时间？ 周转时间 批处理用户从向系统提交作业开始到作业完成为止的时间间隔称为作业周转时间。\n平均周转时间 所有进程的周转时间之和除以进程数得到的就是平均周转时间。\n响应时间 从交互式进程提交一个请求（命令）直到获得响应之间的时间间隔称为响应时间。\n进程调度算法的作用是什么？ 无论是在批处理系统还是分时系统中，用户进程数一般都多于处理机数. 这将导致它们互相争夺处理机。另外，系统进程也同样需要使用处理机。这就要求进程调度程序按一定的策略，动态地把处理机分配给处于就绪队列中的某一个进程，以使之执行。\n掌握三种进程调度算法的名字与思想？重点掌握多级反馈队列调度算法。 FCFS/先来先服务算法 非剥夺式调度算法 FCFS按照作业进入系统后备作业队列的先后次序来挑选作业，先进入系统的作业将优先被挑选进入内存，创建用户进程，分配所需资源，然后移入就绪队列。 SJF/最短时间优先算法 非剥夺式调度算法 SJF以进入系统作业所要求的CPU运行时间的长短为标准，总是选取预计计算时间最短的作业投入运行。 SRTF/最短剩余时间优先算法 剥夺式调度算法 假设当前某进程/线程正在运行，如果有新进程/线程移入就绪队列，若它所需要的CPU运行时间比当前运行进程/线程所需要的剩余CPU时间还短，抢占式最短作业优先算法强行剥夺当前执行者的控制权，调度新进程/线程执行。 HRRF/最高响应比优先算法 非剥夺式调度算法 响应比 = 作业周转时间 / 作业处理时间 = 1 + 作业等待时间 / 作业处理时间 作业处理时间由用户给出，是一个常量；作业等待时间开始于时间点0，随着作业在后备队列中等待时间的增加而变长，每当调度作业运行时，计算后备作业队列中每个作业的响应比作为其优先级，选择响应比最高者投入运行。 优先级调度算法 系统可预先规定为非剥夺式或剥夺式 优先级调度算法根据确定的优先级来选取进程/线程，总是选择就绪队列中优先级最高者投入运行。 RR/轮转调度算法/时间片调度 剥夺式调度算法 调度程序每次把CPU分配给就绪队列首进程/线程使用规定的时间间隔，称为时间片，通常为10ms~200ms，就绪队列中的每个进程/线程轮流地运行一个时间片，当时间片耗尽时就强迫当前运行进程/线程让出处理器，转而排列到就绪队列尾部，等候下一轮调度。 MLFQ/多级反馈队列调度算法 剥夺式调度算法 由系统建立多个就绪队列，每个队列对应于一个优先级，第一个队列的优先级最高，第二个队列的优先级次之，其后的优先级逐个降低。 较高优先级队列的进程/线程分配给较短时间片，较低优先级队列的进程/线程分配给较长时间片，最后一个队列进程/线程按FCFS算法进行调度。 多级反馈调度算法有何性能特点？ MLFQ调度算法具有较好的性能，能满足各类应用需要。\n对于分时交互性短作业，系统通常可在第一队列（最高优先级队列）规定的时间片内完成工作。 对于短的批处理作业，通常只需在第一队列和第二队列中各执行一个时间片就能完成工作，周转时间仍然很短。 对于长的批处理作业，它将依次在第一. 第二. 第三等各个队列中获得时间片运行。 假如一个长作业进入MLFQ，最终进入最低优先级队列后，有很多短作业进入队列，使其一直处于等待状态，则会产生饥饿。一种预防措施是对于低优先级队列中等待时间足够长得进程提升其优先级，从而让它获得运行机会。\n处理器调度分为哪三级？ 高级调度：用于决定哪些满足资源需求的后备作业被选中进入内存去多道运行，FCFA. SJF. SRTF. HRRF. 优先级调度算法等是常用的作业调度算法； 中级调度：起均衡系统负载的作用，根据内存资源情况决定内存中所能容纳的进程数目，并完成外存和内存中的进程对换工作； 低级调度：用于决定选择哪个进程/线程占有处理器运行，FCFS. RR. 优先数. MLFQ等是常用的进程/线程调度算法。 衡量调度算法优劣的因素包括响应时间. 周转时间. 资源利用率. 作业吞吐率和公平性等。\n何为并发与并行？区别？ 并发 在操作系统中，在某一时间段有多个进程或线程同时存在。\n并发的实质是一个处理器在几个进程之间的多路复用，并发是对有限的物理资源强制行使多用户共享，消除计算机部件之间的互等现象，以提高系统资源利用率。\n并行 在操作系统中，在某一时间段有多个进程或线程同时执行\n区别 并行一定要多个进程或线程同时执行，而并发只要求能允许多个进程或线程同时存在，可以在单个CPU中交替执行，因此，并行是并发的一个子集。\n何为并发过程的不确定性？为何会导致不确定性问题？请你举例说明？ 并发过程的不确定性指每次执行结果都可能有所不同。导致不确定性的原因是程序外部的顺序特性消失，程序与计算不再一一对应。\n例子：在煮咖啡时，若每次都按说明书上的顺序操作，那每次出来的咖啡都一样。但若采用并发，在某次准备往热水里放咖啡豆时，老板出来中断了你，然后喝了一口热水，之后将咖啡壶还给你，在进行相同操作后咖啡比以前浓。\n何为进程？请用你自己的方式给进程下个定义？为何要引入进程？ 进程是操作系统中最重要和最基本的概念之一，引入进程是由系统资源的有限和系统内的并发性所决定的。 **进程具有生命周期，**由创建而产生，由调度而执行， 由撤销而消亡，操作系统的基本功能是进程的创建. 管理和撤销。 为何程序一当并发执行，结果就可能不再如传统顺序执行时的可再现性？ 因为程序外部的顺序特性消失，程序与计算不再一一对应。每次执行都可能被中断或者环境变量被别的进程改变。\n进程的特征有那些？如何理解这些特征？ 进程的状态有哪些？如何理解这些状态？ 进程的状态有运行态. 就绪态. 等待态\n运行态 进程占用处理器正在运行的状态\n就绪态 进程具备运行条件，等待系统分配处理器以便运行的状态\n等待态 又称阻塞态或睡眠态，指进程不具备运行条件，正在等待某个事件完成的状态\n请解释P95，118图2-4？ 进程有哪几部分构成？这些构成存于何处？PCB主要构成？PCB排队靠什么方式实现？ 进程由进程控制块(PCB). 进程程序块. 进程核心栈和进程数据块组成\n存储位置 进程控制块存于系统内存中的一个连续区域 进程程序块存于？？ 进程核心栈存于？？ 进程数据块存于？？ PCB主要构成 标志信息 现场信息 控制信息 PCB排队 链接方式 索引方式 何为内核态与用户态？ 内核态 当处理器处于内核态时，这是操作系统管理程序运行时所在状态，可认为处理器正在运行可信系统软件，此时全部机器指令都被允许在处理器上执行，程序可访问所有内存单元和系统资源并具有改变处理器状态的能力。\n用户态 当处理器处于用户态时，它正在运行非可信应用程序此时无法执行特权指令，且访问权限仅限于当前处理器上执行程序所在的地址空间。\n原语指啥？为何要引入原语？原语不可中断靠啥实现？ 原语 原语原语通常由若干条指令组成，用来实现某个特定的操作。通过一段不可分割的或不可中断的程序实现其功能。\n为何引入原语 我们希望有若干指令的连续操作不会被打断\n原语不可中断靠啥实现 通过关中断来实现\n何为线程？现代OS为何要引入线程？与进程的区别？ 何为线程 线程是进程中能并发执行的实体，是进程的组成部分，也是系统调度和分派的基本单位，运行在进程的上下文中,并使用进程的资源和环境。\n引入线程的理由 为了减少程序并发执行时所付出的时空开销，使得并发粒度更细. 并发性更好。\n与进程的区别 线程是进程的组成部分 线程切换较快 线程通信易于实现 线程并发程度比进程高 单道与多道系统优缺点比较？ 多道程序能交替使用CPU，提高了CPU及其他系统资源的利用率，同时也提高了系统的效率。缺点是延长了作业的周转时间，用户不能进行直接干预，缺少交互性，不利于程序的开发与调试。\n为何说中断是现代OS的一项必备技术？ 因为中断使在程序执行过程中，遇到急需处理的事件时，可以暂时中止CPU上现行程序的运行，转去执行相应的事件处理程序，待处理完成后再返回原程序被中断处或调度其他程序执行。\n请你给死锁下个定义？为何有死锁存在？ 死锁定义 操作系统中的死锁指：如果在一个进程集合中的每个进程都在等待只能由该集合中的其他一个进程才能引发的事件，则称一组进程或系统此时发生死锁。\n死锁存在原因 死锁产生不仅与系统拥有的资源数量有关，而且与资源分配策略，进程对资源的使用要求以及并发进程的推进顺序有关。\n产生死锁的原因？ 死锁产生不仅与系统拥有的资源数量有关，而且与资源分配策略，进程对资源的使用要求以及并发进程的推进顺序有关。\n死锁产生的必要条件？举例说明？ 互斥条件 进程互斥使用资源\n例如，当只有一袋咖啡粉时，A拿了B就不能拿。\n部分分配条件 申请新资源时不释放已占有资源\n例如，当A拿到咖啡粉时，在拿到热水之前A不会将咖啡粉放出来，其他人不能使用咖啡粉。\n不剥夺条件 一个进程不能抢夺其他进程占有的资源\n例如，在上面的例子中，B想要咖啡粉但它不能抢A的。\n环路条件 存在一组进程循环等待资源\n例如，在上述例子中，B恰好有热水，但他在拿到咖啡粉前不会放出热水给A使用，两者形成一个环路。\n解释并分析其不同：预防死锁. 避免死锁. 检测死锁. 解除死锁？ 预防死锁 在资源分配中破坏部分死锁必要条件。\n避免死锁 如果一个进程当前请求资源会导致死锁，系统将拒绝启动此进程；如果一个资源分配会导致系统下一步死锁，便拒绝本次分配。\n死锁检测 系统定时地运行一个“死锁检测”程序，判断系统内是否已出现死锁。\n解除死锁 检测到死锁发生以后，采用各种方法解除死锁状态以恢复到可运行状态。\n差异 预防死锁是通过资源分配策略让死锁不可能发生，避免死锁是每次分配都进行安全性检测来避免进入死锁状态。而死锁检测和解除死锁是不防止或避免死锁，当检测到系统已经进入死锁状态后再处理。\n预先分配策略是资源动态还是静态分配法？有何优缺点？ 预先分配策略是资源静态分配法。\n优点 策略实现简单\n缺点 资源利用率低\n动态与静态哪个对资源的利用率高？为什么？它们为何可以预防死锁？ 动态资源利用率高，因为在每个进程占有的资源中，有些资源在运行后期使用，有些资源在例外情况下才被使用，可能会造成进程占有一些几乎用不到的资源，而使其他想使用这些资源的进程等待。\n静态破坏了部分分配条件，而动态破坏环路条件。\n何为安全状态？何为不安全状态？如何避免资源分配时进入不安全状态？ 安全状态就是存在一个安全序列使所有进程都能执行完并收回所有资源 。不安全状态是不存在这样的安全序列，即有可能进入死锁状态。在资源分配时，应该先对资源进行预分配，若该分配会使系统进入不安全状态，则拒绝此分配。\n银行家算法的作用是什么？属于什么策略？ 每一次分配之前都进行预分配和安全性评估来决定是否进行此分配\n属于死锁避免策略。\n银行家算法与安全检测算法的关系是什么？ 安全检测算法是银行家算法的核心。\n银行家算法的开销和对资源的利用率如何？ 银行家算法的开销较高，因为要不断进行预分配和安全检测，资源利用率较高。\n从死锁必要条件的角度解释资源预先分配法. 资源暂时释放法. 资源有序使用法. 银行家算法等是如何预防或避免死锁发生的？ 预先分配法破坏了部分分配条件 资源暂时释放法破坏了不剥夺条件 资源有序使用法破坏了环路条件 银行家算法破环了环路条件 简单叙述银行家算法的思想？ 系统中的所有进程进入进程集合, 在安全状态下系统收到进程的资源请求后,先把资源试探性分配给它。 系统用剩下的可用资源和进程集合中其他进程还要的资源数作比较，在进程集合中找到剩余资源能满足最大需求量的进程,从而,保证这个进程运行完毕并归还全部资源。 把这个进程从集合中去掉, 系统的剩余资源更多了,反复执行上述步骤。 最后,检查进程集合,若为空表明本次申请可行,系统处于安全状态,可实施本次分配;否则,有进程执行不完，系统处于不安全状态,本次资源分配暂不实施,让申请进程等待。 何为资源分配图？ 约定Pi→Rj为请求边，表示进程Pi申请资源类Rj中的一个资源得不到满足而处于等待Rj类资源的状态，该有向边从进程开始指到方框的边缘，表示进程Pi申请Rj类中的一个资源。 Rj→Pi为分配边，表示Rj类中的一个资源已被进程Pi占用，由于已把一个具体的资源分给了进程Pi，故该有向边从方框内的某个黑圆点出发指向进程。 何为死锁定理？ 如果能在进程-资源分配图中消去此进程的所有请求边和分配边，成为孤立结点。经一系列简化，使所有进程成为孤立结点，则该图是可完全简化的；否则则称该图是不可完全简化的。 系统为死锁状态的充分条件是：当且仅当该状态的进程-资源分配图是不可完全简化的。该充分条件称为死锁定理。 为何从资源分配图能否化简就能判断死锁的情形？图不能化简是否就说明机器存在死锁？ 因为对资源分配图进行简化就相当于让一个进程获得足够的资源，在有限时间内执行完并释放资源。若不能完全化简，就意味着没有方法让进程全部执行完并收回资源，系统此时是死锁状态。\n死锁检测算法的理论基础是什么？ 理论基础就是模拟资源分配图的化简过程，若不能完全化简，则进入死锁状态。\n死锁如何解除？谈谈解除死锁的代价？ 结束所有进程的执行，重新启动操作系统。方法简单，但以前工作全部作废，损失很大。 撤销陷于死锁的所有进程，解除死锁继续运行。 逐个撤销陷于死锁的进程，回收其资源重新分派，直至死锁解除。 剥夺陷于死锁的进程占用的资源，但并不撤销它，直至死锁解除。可照撤销陷于死锁进程的条件来选择剥夺资源的进程 根据系统保存的检查点，让所有进程回退，直到足以解除死锁，这种措施要求系统建立保存检查点. 回退及重启机制。 当检测到死锁时，如果存在某些未卷入死锁的进程，而随着这些进程执行到结束，有可能释放足够的资源来解除死锁。 立体（多级）存储体系指啥？为何要进行重定位？为何要进行地址映射? 立体（多级）存储体系指由寄存器. 高速缓存. 内存储器. 磁盘缓存. 固定磁盘. 可移动存储介质组成的一个存储器层次结构。\n从单道到现代OS的虚拟内存管理这一发展线条？你来纵论一下？ OS最初的单任务系统 OS从单任务处理变为多任务并行处理 MMU的开始使用 虚拟内存概念的出现 你能否说清一种内存管理思想的原理及其地址映射过程（如：虚拟分页）？ 这些概念指啥：快表命中，页内零头，淘汰算法，抖动，碎片，紧凑，页故障率。 快表命中 当把页号交给快表后，它通过并行匹配同时对所有快表项进行比较，如果找到，则为快表命中。\n页内零头 在页里面用户没有利用的部分。\n淘汰算法 一个将快表或页表里将某一页淘汰掉并装入新的页的算法。\n抖动 当存储管理方式采用页式存储时，缓存中的页需要可能被替换，下次要使用的页在这回却替换出去了，这样的现象就称为抖动。就会使得刚被替换出去的页又要重新加载。\n碎片 指操作系统在内存分配过程中，遗留下来的不能被利用到的内存区域。内存碎片导致部分内存被浪费，大量的内存碎片也会影响到系统的性能。\n紧凑 将内存空闲区域集中在内存的一端拼接成一个较大的空闲分区，这种方法成为紧凑。\n页故障率 发生缺页中断的比率。\n内存地址映射过程为何要引入硬件寄存器？它与页表. 段表. 快表. 进程表在数量设置上有何关系？内容呢，何时装入？ 引入硬件寄存器为了减少内存访问次数，提高访问效率。\n页表只有一个，只有占用CPU的进程才占有它。\n每个用户作业都有自己的段表。\n快表只有一个，存最近访问的页表项。\n进程表只有一个，存进程信息。\n页表中存放的是页框的逻辑地址，在有新页框创建时装入。\n段表中存放的是段的逻辑地址，在有新段创建时装入。\n快表存的是常用页框的逻辑地址，在淘汰页框并更新快表时装入。\n进程表存的是正在运行或就绪的进程信息，在创建进程时装入。\n设备驱动指什么？是否是OS的一部分？为何使用设备时需要安装驱动？不能事先集成入吗？ 设备驱动程序包括与设备密切相关的所有代码，其任务是把用户提交的逻辑I/O请求转化为物理I/O操作的启动和执行；同时，监督设备是否正确执行，管理数据缓冲区，进行必要的纠错处理。\n笼统地说，设备驱动程序的功能是从独立于设备的软件中接收并执行I/O请求。\n设备驱动是OS的一部分\n驱动负责沟通对应的硬件和操作系统，是两者之间的桥梁，不安装驱动的话操作系统则无法识别对应的硬件设备\n可以事先集成设备驱动\nCPU如何实现对设备的控制？有哪些方式？各种方式的特点与原理？设备（如硬盘）要与内存传数，难道不和CPU冲突吗？ 按照I/O控制器功能的强弱以及它和CPU之间联系方式的不同，可以把设备控制方式分为轮询. 中断. DMA. 和通道4类。 它们之间的差别在于CPU和设备并行工作的方式和程度不同。 轮询方式 轮询方式又称程序直接控制方式，使用查询指令测试设备控制器的忙闲状态位，确定内存和设备是否能交换数据。\n轮询方式使用三条指令：\n**查询指令：**查询设备是否就绪；\n**读写指令：**当设备就绪时执行数据交换；\n**转移指令：**当设备未就绪时执行转移指令转向查询指令继续查询；\n中断方式 -中断方式要求CPU与设备控制器及设备之间存在中断请求线，设备控制器的状态寄存器有相应的中断允许位。\nDMA方式 在DMA方式中，内存和设备之间有一条数据通路成块地传送数据，无须CPU干扰，实际数据传输操作由DMA直接完成。 DMA方式需以下设施：\n(1)**内存地址寄存器：**存放内存中需要交换数据的地址，DMA传送之前由程序送入首地址；DMA传送过程中，每次交换数据都把地址寄存器的内容加1。\n(2)**字计数器：**记录传送数据的总字数，每次传送一个字就把字计数器减1。\n(3)**数据缓冲寄存器或数据缓冲区：**暂存每次传送的数据。\n(4)**设备地址寄存器：**存放I/O信息的地址，如磁盘的柱面号. 磁头号. 扇区号。\n(5)**中断机制和控制逻辑：**用于向CPU提出I/O中断请求及保存CPU发来的I/O。\n通道方式 采用通道后的I/O操作过程：CPU在执行主程序时遇到I/O请求，它启动指定通道上选址的外围设备，一旦启动成功，通道开始控制外围设备进行操作。CPU就可执行其他任务并与通道并行工作，直到I/O操作完成。通道发出操作结束中断时，CPU才停止当前工作，转向处理I/O操作结束事件。 OS的设备管理I/O子系统工作机制如何？请以缺页中断为例加以说明？ I/O操作执行步骤 进程对已打开文件的文件描述符执行读库函数；\n独立设备I/O软件检查参数正确性。高速缓存中有要读的信息块，从缓冲区直接读到用户区，完成I/O请求；\n若数据不在缓冲区，执行物理I/O，实现将设备逻辑名转换成物理名，检查对设备操作的权限，将I/O请求排队，阻塞进程且等待I/O完成；\n内核启动设备驱动程序，分配存放读出块的缓冲区，准备接收数据，且向设备控制寄存器发启动命令，或建立DMA传输，启动I/O；\n设备控制器操作设备，执行数据传输；\nDMA控制器控制一块传输完成，硬件产生I/O结束中断；\nCPU响应中断，转向磁盘中断处理程序。\n当应用进程被再次调度执行时，从I/O系统调用的断点恢复执行。\n缺页异常处理过程 缺页异常是由于发现当前访问页面不在内存时由硬件所产生的一种特殊中断信号，是在指令执行期间产生并由系统处理的。 **缺页本身是一种中断，与一般的中断一样，需要经过4个处理步骤： **\n保护CPU现场\n分析中断原因\n转入缺页中断处理程序进行处理\n恢复CPU现场，继续执行\n**但是缺页中断时由于所要访问的页面不存在内存时，有硬件所产生的一种特殊的中断，因此，与一般的中断存在区别： **\n在指令执行期间产生和处理缺页中断信号\n一条指令在执行期间，可能产生多次缺页中断\n缺页中断返回时，执行产生中断的那一条指令，而一般的中断返回时，执行下一条指令\n缺页异常的整个过程\n首先硬件会陷入内核，在堆栈中保存程序计数器。大多数机器将当前指令的各种状态信息保存在CPU中特殊的寄存器中。\n**启动一个汇编代码例程保存通用寄存器及其它易失性信息，**以免被操作系统破坏。这个例程将操作系统作为一个函数来调用。（在页面换入换出的过程中可能会发生上下文换行，导致破坏当前程序计数器及通用寄存器中本进程的信息）\n当操作系统发现是一个页面中断时，查找出来发生页面中断的虚拟页面（进程地址空间中的页面）。**这个虚拟页面的信息通常会保存在一个硬件寄存器中，**如果没有的话，操作系统必须检索程序计数器，取出这条指令，用软件分析该指令，通过分析找出发生页面中断的虚拟页面。\n**检查虚拟地址的有效性及安全保护位。**如果发生保护错误，则杀死该进程。\n**操作系统查找一个空闲的页框(物理内存中的页面)，**如果没有空闲页框则需要通过页面置换算法找到一个需要换出的页框。\n如果找的页框中的内容被修改了，则需要将修改的内容保存到磁盘上，此时会引起一个写磁盘调用，发生上下文切换（在等待磁盘写的过程中让其它进程运行）。（注：此时需要将页框置为忙状态，以防页框被其它进程抢占掉）\n页框干净后，操作系统根据虚拟地址对应磁盘上的位置，将保持在磁盘上的页面内容复制到“干净”的页框中，此时会引起一个读磁盘调用，发生上下文切换。\n**当磁盘中的页面内容全部装入页框后，向操作系统发送一个中断。操作系统更新内存中的页表项，**将虚拟页面映射的页框号更新为写入的页框，并将页框标记为正常状态。\n**恢复缺页中断发生前的状态，**将程序指令器重新指向引起缺页中断的指令。\n**调度引起页面中断的进程，**操作系统返回汇编代码例程。\n**汇编代码例程恢复现场，**将之前保存在通用寄存器中的信息恢复。\n缺页中断的过程涉及了用户态和内核态之间的切换，虚拟地址和物理之间的转换（这个转换过程需要使用MMU和TLB），同时涉及了内核态到用户态的转换。\n磁盘驱动这一举例中为何要提到“调度策略”？ **对于系统性能产生重要影响的是磁盘I/O，**为了提高磁盘I/O性能，广为使用的有两种方法：磁盘驱动调度和磁盘缓冲区。\n不同的调度策略算法对于磁盘驱动调度的性能影响很大，从而对磁盘I/O性能再到系统整体性能表现影响也很大。\n为何要用到缓冲？DMA中的缓冲区作用为何？ 为何要用到缓冲？ 数据离开设备之后通常不能直接送达目的地，所以必须通过缓冲区来消除填满速率和清空速率的影响。\n也就是说缓冲是为了解决高速设备和低速设备之间的速度不匹配问题。\nDMA中的缓冲区作用为何？ 内存中用于与外设交互数据的一块区域被称作DMA缓冲区。\n外设的设备读写速度与内存相比非常慢，因此需要使用缓冲区来减少等待时间，提高DMA的运行效率。\n何为虚拟设备？ 在一台共享设备上模拟若干台独享设备的操作，把独占型设备变成逻辑上的共享型设备，这种技术叫做虚拟设备技术。\n实现这种技术的软件和硬件被称为SPOOLING系统，使用SPOOLING技术所提供的设备就称为虚拟设备。\n实现虚拟设备的主要条件是：硬件上需要在磁盘上开辟输入井和输出井用做缓冲的存储区域。软件上需要预输入程序. 缓输出程序和井管理程序。\nOS中为何要引入逻辑设备？即在驱动中完成“逻辑设备—物理设备”对应？ 概念 为了实现设备独立性而引入了逻辑设备和物理设备两概念。这使得应用程序独立于具体使用的物理设备。 这样的好处是提高了OS的可适应性和可扩展性,在应用程序中,使用逻辑设备名称来请求使用某类设备;而系统在实际执行时,还必须使用物理设备名称。\n设备独立性带来的好处 应用程序与具体物理设备无关，系统增减或变更设备时对源程序不必加以任何修改；\n易于应对I/O设备故障，从而提高系统可靠性，增加设备分配的灵活性，能更有效地利用设备资源，实现多道程序设计；\nIDE是什么？串口与并口指什么？举例？PCI呢？ IDE 平常所说的IDE接口，也称之为ATA接口。ATA的英文拼写为“Advanced Technology Attachment”，含义是“高级技术附加装置”。\n早期的IDE接口有两种传输模式，一个是PIO（Programming I/O）模式，另一个是DMA（Direct Memory Access）。\n串口与并口 串行接口简称串口，也称串行通信接口或串行通讯接口**（通常指COM接口），是采用串行通信方式的扩展接口。串行接口 (Serial Interface) 是指数据一位一位地顺序传送，其特点是通信线路简单，只要一对传输线就可以实现双向通信**（可以直接利用电话线作为传输线），从而大大降低了成本，特别适用于远距离通信，但传送速度较慢。\n**并行接口，指采用并行传输方式来传输数据的接口标准。**一个并行接口的接口特性可以从两个方面加以描述：1. 以并行方式传输的数据通道的宽度，也称接口传输的位数；2. 用于协调并行数据传输的额外接口控制线或称交互信号的特性。 数据的宽度可以从1～128位或者更宽\nSCSI和IDE都是并行接口。\n串口形容一下就是：一条车道，而并口就是有多个车道同一时刻能传送多位（一个字节）数据。但是并不是并口快。由于多位通道之间的互相干扰，传输时速度就受到了限制。而且当传输出错时，要同时重新传多个位的数据。而串口没有干扰，传输出错后重发一位就可以了，所以要比并口快。\nPCI PCI是Peripheral Component Interconnect(外设部件互连标准)的缩写，它是目前个人电脑中使用最为广泛的接口，几乎所有的主板产品上都带有这种插槽。（可惜现在都变成了PCIe接口） PCI （Peripheral Component Interconnect）总线是一种高性能局部总线，是为了满足外设间以及外设与主机间高速数据传输而提出来的。在数字图形. 图像和语音处理，以及高速实时数据采集与处理等对数据传输率要求较高的应用中，采用PCI总线来进行数据传输，可以解决原有的标准总线数据传输率低带来的瓶颈问题。 磁盘调度算法 先来先服务/FCFS First Come First Serve algorithm\n顾名思义，先来的先服务，不考虑各I/O请求之间的相对次序和移动臂当前所处位置；\n进程等待I/O请求的时间会很长，寻道性能较差；\n最短查找时间优先算法/SSTF Shortest Seek Time First algorithm\nSSTF考虑I/O请求之间的区别，**总是先执行查找时间最短的请求，**与FCS算法相比有较好的寻道性能。\n扫描算法/SCAN SCAN algorithm\n在SCAN中，移动臂每次沿一个方向移动，扫过所有柱面，遇到最近的I/O请求便进行处理，直至到达最后一个柱面后，再向相反的方向移动回来。\nSCAN与电梯调度算法不同的是：即使当前移动方向暂时没有I/O请求，移动臂也需要扫描到头。\n扫描算法偏爱那些最接近里面或靠近外面的请求，对最近扫描所跨越区域的I/O请求的响应速度会较慢。\n分步扫描算法/N-steps N-steps scan algorithm\n将I/O请求分为长度为N的子队列，按FIFO算法依次处理每个子队列，而每个子队列采用扫描算法，处理完一个后再服务下一个子队列。\n在一段时间内进程重复请求访问同一柱面会垄断整个设备，造成磁盘臂停留在柱面上不动，**称为“磁臂粘性”，**使所有其他柱面的访问请求可能长时间得不到服务。\n当N很大的时候 ，接近于扫描算法性能；当N=1的时候，接近于FIFO算法性能。\n电梯调度算法/LOOK elevator algorithm又称LOOK算法\nSCAN算法的改进，无访问请求时，移动臂停止不动，有访问请求时，移动臂按电梯规律移动。\nLOOK不需要扫描到磁盘的尽头！\n循环扫描算法/Circular scan Circular scan algorithm\n**移动臂总是从0号柱面至最大号柱面顺序扫描，然后直接返回0号柱面重复进行，归途中不再提供服务，**构成一个循环，缩短处理新来请求的最大延迟。\n文件如何分类？为何有这么多种分类？ 文件是由文件名字标识的一组信息的集合。可按各种方法进行分类：\n按用途分类：\n按保护级别分类：\n按信息流向分类：\n按存放时限分类：\n按设备类型分类：\n按文件的结构分类：\n有多种分类的原因是文件本身的信息和属性多样，根据不同的用途. 权限. 存储方式等均可进行不同需求的划分。\n参考资料：https://blog.csdn.net/liu_fei_er/article/details/80619625\n系统实现按名存取文件主要依靠什么数据结构？ **文件控制块 (FCB)**是OS为每个文件建立的唯一数据结构，其中包含了全部文件属性，其目的是既便于用户的操作和使用，又便于操作系统对文件的管理和控制。\n**一个文件由两部分组成 ：FCB 和文件体（文件信息）。**有了 FCB 就可以实现文件的“按名存取”\n文件的组织有哪几种形式？文件和存储介质有什么关系？ 文件组织是指文件中信息的配置和构造方式，应该从文件的逻辑结构和组织以及文件的物理结构和组织两方面加以考虑。\n文件和存储介质的关系 文件的增删改查操作都与存储介质密切相关。\n文件系统中的磁盘管理除管理文件空间外，还将文件的逻辑地址转换成磁盘的物理地址，即由逻辑块号找到柱面号. 磁头号和扇区号，设备与内存之间的数据传输操作由文件系统调用设备管理实现。\n文件的逻辑组织是什么概念？有哪些方式？ 文件的逻辑结构和组织指从用户观点出发，研究用户概念中抽象的信息组织方式，这是用户所能观察到的，可加以处理的数据集合。\n文件的逻辑结构分为两种形式：流式文件和记录式文件。\n流式文件 **流式文件是一种无结构的文件，**文件内的数据不再组成记录，只是依次的一串信息集合，**称为字节流文件，**可以看成是只有一个记录的记录式文件。\n为了简化系统，大多数现代操作系统如Linux系统只提供流式文件。\n记录式文件 **记录式文件是一种有结构的文件，**它包含若干逻辑记录，逻辑记录是文件中按信息在逻辑上的独立含义所划分的信息单位。\n记录式文件中有两种常用的记录组织和使用方法：\n**记录式顺序文件：**文件的记录顺序生成并被顺序访问。\n**记录式索引顺序文件：**这种文件使用索引表，表项包含记录键和索引指针，记录键由应用程序确定，而索引指针便指向相应记录。\n文件的物理组织是什么概念？有哪些方式？ 文件的物理结构和组织是指逻辑文件在物理存储空间中的存放方法和组织关系，这时的文件看做物理文件，即相关物理块的集合。\n文件的存储结构涉及：块的划分. 记录的排列. 索引的组织. 信息的搜索，其优劣直接影响文件系统的性能。\n有两类方法可以用来构造文件物理结构：计算法和指针法。\n常用的文件物理结构和组织方法有：顺序文件. 连接文件. 索引文件. 直接文件。\n文件目录与目录文件是指什么？ 文件目录 为了加快文件查找速度，通常把FCB汇集和组织在一起形成文件目录，文件目录包含许多目录项，目录项有两种，分别用于描述子目录和描述文件。\n目录文件 目录项的格式按统一标准定义，全部由目录项所构成的文件称为目录文件。\n目录文件保存在外存上，查找文件时调入内存工作区。\n与普通文件不同，目录文件永远不会空，它至少包含两个目录项：当前目录“.”和父目录项“..”。\n文件的访问方式有哪些？请做说明？ 常用的文件访问方式有：顺序存取. 直接存取和索引存取\n顺序存取 无论是无结构字节流文件，还是有结构记录式文件，存取操作都在上次操作的基础上进行。系统设置读写两个位置指针，指向要读出或写入的字节位置或记录位置。\n顺序存取主要用于磁带文件，但也适用于磁盘上的顺序文件。\n直接存取 又称随机存取，可以非顺序地从文件中的任何位置存取文件内容。\n**直接存取方法适合于要求快速地以任意次序直接读写某条记录的应用，**如订机票；它也通常用于磁盘文件。\n索引存取 这是基于索引文件的存取方法，由于文件中的记录不按位置而是按其记录名或记录键来编址，所以用户提供记录名或记录键后，先按名搜索，再查找所需要的记录。 文件控制块（FCB）的作用？ 文件控制块（File Control Block，FCB）是操作系统为每个文件建立的唯一数据结构，其中包含了全部文件属性，其目的是为方便操作系统对文件的管理. 控制和存取。\n有了FCB就可以方便地实现文件的按名存取。\n每当创建一个文件时，系统就要为其建立一个FCB，用来记录文件的属性信息；每当存取文件时，先找到其FCB，再找到文件信息盘块号. 首块物理位置或索引表就能存取文件信息。\n目录结构的概念与分级情形？ 最简单的文件目录时一级目录结构，所有FCB排列在一张线性表中，其缺点是文件重名和文件共享问题难以解决。\n实际上，所有文件系统都支持多级层次结构，根目录是唯一的，每一级目录可以是下一级目录的说明，也可以是文件的说明，从而形成树状目录结构。\n树状多级目录结构有许多优点：\n可以较好地反映现实世界中具有层次关系的数据集合，确切地反映系统内部文件的分支结构；\n不同文件可以重名，只要它们不位于同一末端子目录中即可；\n易于规定不同层次或子目录中文件的不同存取权限，便于文件的保护. 保密和共享等；\n有利于系统的维护和查找。\n删除文件主要删除哪些内容？为何删除的文件可以被恢复？你认为删除文件应该删除哪些内容？请从安全性与性能两方面来解释各自的影响？ 一个文件由两部分组成：FCB和文件体（文件信息）；\n删除文件主要删除其FCB信息，文件信息还保存在磁盘中，因此只要找到磁盘中对应位置的数据就可以恢复被删除的文件。\n从性能方面来说，只删除FCB是优秀的选择，这样速度更快，系统开销更小；\n从安全性方面来说，删除文件时应将文件信息也一并删除；\n就你所了解的文件物理组织中，哪些是你认为不错的组织方式？ 顺序文件 将文件中逻辑上连续的信息存放到存储介质的相邻物理块上形成顺序结构，叫做顺序文件，又称连续文件。\n顺序文件的优点是顺序存取记录时速度快，在批处理文件. 系统文件中用得很多。\n顺序文件的缺点是建立文件之前需要预先确定文件长度，以便分配存储空间；修改. 插入和添加文件记录有一定的难度；对于变长记录的处理很困难；对磁盘作连续分配会造成空闲块的浪费。\n连接文件 连接结构的特点是使用连接字，又称指针，来表示文件中各条记录之间的关系。\n连接结构克服了顺序结构不适应于增. 删. 改的缺点，对某些操作会带来好处，但在其他方面又会失去一些性能。\n使用连接文件很容易把数据记录组织起来，但是查找某条记录需遍历链接结构，效率很低。\n索引文件 索引结构式实现非连续存储的另一种方法，适用于数据记录保存在磁盘上的文件。\n索引结构是连接结构的一种扩展，除了具备连接文件的优点外，记录可以散列存储，具有直接读写任意记录的能力，便于信息的增. 删. 改。\n缺点是索引表的空间开销和查找时间的开销大，大型文件的索引表的信息量甚至可能远远超过文件记录本身的信息量。\n直接文件 在直接存取存储设备上，利用哈希法将记录的关键字与其地址之间建立某种对应关系，以便实现快速存取的文件叫做直接文件. 散列文件或哈希文件。\n这种存储结构用在不能采用顺序组织方法. 次序较乱. 又需在极短时间内进行存取的场合，对于实时处理文件. 目录文件. 存储管理的页表查找. 编译程序变量名表等的管理十分有效。\n请你把文件操作与上一章的设备管理结合起来，叙述文件读写的整个过程？ 心累，不说。\n定期紧缩磁盘空间会导致什么好处？ **因为文件被分散保存到整个磁盘的不同地方，而不是连续地保存在磁盘连续的簇中形成的。**硬盘在使用一段时间后，由于反复写入和删除文件，**磁盘中的空闲扇区会分散到整个磁盘中不连续的物理位置上，**从而使文件不能存在连续的扇区里。这样，再读写文件时就需要到不同的地方去读取，增加了磁头的来回移动，降低了磁盘的访问速度。\n定期紧缩磁盘空间可以使原本分散的文件碎片被重新整理到一起，减少磁盘在读取的时候的磁头移动，增高磁盘的读写效率。\n","permalink":"http://localhost:9011/20190109-operating-system/","summary":"\u003cp\u003e《操作系统》部分重点内容，包括讨论题和部分小七自己整理的概念. 重要知识点。\u003c/p\u003e","title":"操作系统部分重点内容"},{"content":"本文介绍使用JAVA8+Apache Tomcat 9.0.19的安装配置。\n文中部分内容更新于2019年5月10日。\nTomcat是什么？ Tomcat 服务器是一个免费的开放源代码的Web 应用服务器，属于轻量级应用服务器，在中小型系统和并发访问用户不是很多的场合下被普遍使用，是开发和调试JSP 程序的首选。对于一个初学者来说，可以这样认为，当在一台机器上配置好Apache 服务器，可利用它响应HTML（标准通用标记语言下的一个应用）页面的访问请求。实际上Tomcat是Apache 服务器的扩展，但运行时它是独立运行的，所以当你运行tomcat 时，它实际上作为一个与Apache 独立的进程单独运行的。\n摘自百度百科\n配置JAVA8 JAVA8下载地址，下载前记得要先勾选accept，否则无法下载，i586/x86即为32位，x64为64位，大家根据自己的机器配置下载对应的版本。 https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html\n注意，现在在甲骨文官网下载JDK需要先进行注册登录，否则无法正常下载。\nJAVA8安装比较简单，这里不再贴过多的图片，需要安装更新的版本的同学可以看看这篇文章：JDK安装与环境变量配置\n安装下载好的JDK文件，此处为jdk-8u211-windows-x64.exe，安装完成后，配置环境变量，右击【我的电脑】\u0026mdash;【属性】\u0026mdash;\u0026ndash;【高级】\u0026mdash;【环境变量】;\n选择【新建系统变量】\u0026ndash;弹出“新建系统变量”对话框，在“变量名”文本框输入JAVA_HOME,在“变量值”文本框输入JDK的安装路径，单击“确定”按钮;\n在“系统变量”选项区域中查看PATH变量，如果不存在，则新建变量 PATH，否则选中该变量，单击“编辑”按钮，在“变量值”文本框的起始位置添加%JAVA_HOME%\\bin;%JAVA_HOME%\\jre\\bin;，或者是直接%JAVA_HOME%\\bin，单击确定按钮；\n在“系统变量”选项区域中查看CLASSPATH 变量，如果不存在，则新建变量CLASSPATH，否则选中该变量，单击“编辑”按钮，在“变量值”文本框的起始位置添加.;%JAVA_HOME%\\lib\\dt.jar;%JAVA_HOME%\\lib\\tools.jar;\n测试环境变量的配置成功与否。在DOS命令行窗口或powershell输入JAVAC，输出帮助信息即为配置正确。\n安装Apache的Tomcat 用浏览器打开tomcat官网： https://tomcat.apache.org/ 点击左侧的导航栏Download下方选择最新的Tomcat 9，出现以下页面，点击页面下方的“64-bit Windows zip (pgp, sha1,sha512)“进行下载。 完成下载后，直接解压，路径自选。（这里以D:\\apache-tomcat-9.0.19为例）\n右键 \u0026ldquo;此电脑\u0026rdquo;，点击\u0026quot;属性\u0026quot;，点击\u0026quot;高级系统设置\u0026quot;—\u0026gt;\u0026ldquo;高级\u0026rdquo;—\u0026gt;\u0026ldquo;环境变量\u0026rdquo;\n在\u0026quot;系统变量\u0026quot;里新建变量名：CATALINA_BASE，变量值：D:\\apache-tomcat-9.0.19（此处为你的解压包路径）\n在\u0026quot;系统变量\u0026quot;里新建变量名：CATALINA_HOME，变量值：D:\\apache-tomcat-9.0.19 在\u0026quot;系统变量\u0026quot;里打开Path变量，添加变量值：%CATALINA_HOME%\\lib和%CATALINA_HOME%\\bin（这一步可有可无，有些电脑无需配置此步骤便可完成，因此可以先跳过） 使用DOS或者powershell进入tomcat下的bin目录（本人电脑的路径为D:\\apache-tomcat-9.0.19\\bin），执行“service.bat install” 。（附：service卸载命令：service.bat remove） 启动tomcat，在tomcat解压路径下的bin文件夹内双击打开\u0026quot;tomcat9w.exe\u0026quot;，在打开的软件界面点击“Start”即可 这里的startup type为启动类型，即开机自动启动（Automatic），手动启动（Manual）和禁用（Disabled）\n注：在cmd中或者powershell直接运行bin目录下的shutdowm.bat文件或startup.bat文件也可以直接关闭或打开tomcat服务\n完成方法操作后，在浏览器地址栏输入 http://localhost:8080/ 出现下图所示信息即为成功配置 Eclipse 配置Tomcat 下载Eclipse 注意在安装Eclipse的时候要选择Eclipse IDE for Enterprise Java Developers\nEclipse官网下载地址：\nhttps://www.eclipse.org/downloads/download.php?file=/oomph/epp/2019-03/R/eclipse-inst-win64.exe\nEclipse中关联Tomcat 打开Eclipse，点击菜单栏的Window，然后选择Preferences，选择Server，点击Runtime Environments ，点击右侧的Add, 选择Apache Tomcat v9.0\n选择电脑上安装好tomcat的目录，然后选择JRE。\n确认之后，点击Apply and Close。\n","permalink":"http://localhost:9011/20181112-win-install-tomcat/","summary":"\u003cp\u003e本文介绍使用JAVA8+Apache Tomcat 9.0.19的安装配置。\u003c/p\u003e\n\u003cp\u003e文中部分内容更新于2019年5月10日。\u003c/p\u003e","title":"Windows上Apache Tomcat的安装配置"},{"content":"最近在安装Ubuntu18.04和win10双系统的时候发现两个系统的时间总是相差八个小时，即win10的时间要比Ubuntu早八个小时，这里介绍一个在Ubuntu下的解决方案。\n为什么时间会不一致 在我们电脑的BIOS中，有着一个记录的时间，windows系统会将BIOS中的时间视为本地时间，即你windows中设置为中国地区，那么这个BIOS的时间对于windows来说就是中国时间。\n在Ubuntu中，会将BIOS中的时间视为UTC时间，即Universal Time Coordinated(协调世界时)，而中国这边的时间是UTC+8，即正好相差八个小时。\n协调世界时是以原子时秒长为基础，在时刻上尽量接近于世界时的一种时间计量系统。中国大陆采用ISO 8601-1988的《数据元和交换格式信息交换日期和时间表示法》（GB/T 7408-1994）称之为国际协调时间，代替原来的GB/T 7408-1994；中国台湾采用CNS 7648的《资料元及交换格式–资讯交换–日期及时间的表示法》，称之为世界统一时间。\n中国大陆、中国香港、中国澳门、中国台湾、蒙古国、新加坡、马来西亚、菲律宾、西澳大利亚州的时间与UTC的时差均为+8，也就是UTC+8。\n如何修改 先介绍在Ubuntu下的解决方案。\n首先我们把硬件时间（BIOS时间）将默认的UTC改为CST，然后重启，使得两个系统的时间保持一致。\nsudo timedatectl set-local-rtc 1 #上述代码中的1改为0即可将硬件时间修改为默认的UTC时间 sudo reboot #重启系统使改动生效 接着我们更新一下系统时间\nsudo apt-get install ntpdate #这里的操作是安装ntpdate工具 sudo ntpdate time.windows.com #使用ntpdate工具从time.windows.com上面同步时间 最后我们将时间更新到硬件上，以保证重启后改动不会被还原\nsudo hwclock --localtime --systohc #这里的代码意思是使用hwclock命令将本地时间localtime从sysclock同步到hwclock 到这里双系统的时间就都正常了。\n或者也可以使用windows下的解决方案，使用管理员权限打开powershell，然后输入下面的命令，接着重启就可以了。\nreg add HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation /v RealTimeIsUniversal /t REG_DWORD /d 1 原理就是：在注册表项HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation中添加一个名为RealTimeIsUniversal的值，类型为REG_DWORD，数据为1。此项的作用就是让Windows将硬件时间当作UTC，与Ubuntu的默认设置一致。\n","permalink":"http://localhost:9011/20181109-ubuntu-change-time/","summary":"\u003cp\u003e最近在安装Ubuntu18.04和win10双系统的时候发现两个系统的时间总是相差八个小时，即win10的时间要比Ubuntu早八个小时，这里介绍一个在Ubuntu下的解决方案。\u003c/p\u003e","title":"Ubuntu18.04与Win10时间不一致"},{"content":"小七比较喜欢在宿舍和室友一起看电影或听音乐，且对画质和音质要求较高，一般都是观看1080P的蓝光REMUX电影（约30G一部）和听无损音质的音乐（30M一首），且观看设备较多（电视盒子、电脑、手机），再加上平时需要下载电影且自己有写博客的习惯，博客和一些其他的重要资料也需要备份，因此决定使用树莓派3B+、一块移动硬盘和一个路由器搭建一个宿舍多媒体中心来满足这些需求。\n功能简介 Samba Samba是在Linux和UNIX系统上实现SMB协议的一个免费软件，由服务器及客户端程序构成。SMB（Server Messages Block，信息服务块）是一种在局域网上共享文件和打印机的一种通信协议，它为局域网内的不同计算机之间提供文件及打印机等资源的共享服务。\n换言之，使用Samba可以在局域网内实现文件的共享操作，有些放在移动硬盘里面的文件需要用的时候就不用再插移动硬盘，在网上邻居处拷贝即可。\nminiDLNA DLNA的全称是DIGITAL LIVING NETWORK ALLIANCE(数字生活网络联盟)，DLNA并不是创造技术，而是形成一种解决的方案，一种大家可以遵守的规范。\n所以，其选择的各种技术和协议都是当前所应用很广泛的技术和协议。miniDLNA可以实现音乐视频图片的局域网跨设备共享，且目前大多数智能手机、平板和电视均支持DLNA协议，在树莓派上安装miniDLNA服务后即可让处在同一局域网下的设备能轻松访问到树莓派上的影音资源。\n下载机 **Transmission全称TransmissionBittorrent，由C开发而成（Mac OS上用的是Objective-C），硬件资源消耗极少，界面极度精简。**支持包括Linux、BSD、Solaris、Mac OS X等多种操作系统，以及Networked Media Tank、WD MyBook、ReadyNAS、D-Link DNS-323 \u0026amp; CH3SNAS、Synology等多种设备。支持GTK+、命令行、Web等多种界面。\nAria2支持Http、FTP、磁力链接和BT下载，可以和Transimission互补。\n自动备份 数据备份是一个好习惯，但是总是会有遗忘或者是疏漏的情况出现，因此我们可以利用树莓派来实现自动备份。首先可以创建powershell命令实现备份功能，再另存为bat脚本文件，最后利用windows自带的定时任务功能和linux的定时执行命令操作来实现文件自动备份到树莓派上的操作。\n状态监控 作为长时间运行的多媒体中心，树莓派的运行状态不能忽视，因此我们可以使用LCD1602显示屏连接树莓派，显示一些必要的信息来监控它的运行状态。（CPU，GPU, RAM, IP, TIME）\n安装操作 Samba 安装ntfs-3g 树莓派接上移动硬盘后，会自动挂载到/media目录下，但是由于我的硬盘是ntfs格式，在树莓系统下只能读不能写，因此我需要安装ntfs-3g服务实现对移动硬盘的写操作，然后再设置开机自动挂载移动硬盘。\nsudo apt-get install ntfs-3g #安装ntfs -3g服务 sudo mkdir /home/pi/share #创建用于挂载移动硬盘的目录 sudo umount /dev/sda1 #有些系统会自动挂载，因此先使用umont命令取消挂载移动硬盘 #后面的/dev/sda1是硬盘在此系统中对应的编号，可以使用 df -h命令查看，或者直接使用硬盘名代替 sudo mount -t ntfs-3g /dev/sda1 /home/pi/share #使用ntfs -3g挂载硬盘到指定目录 sudo chmod 777 /home/pi/share #使用chmod命令赋予目录读写权限 sudo chmod 777 /home/pi #目录的上级目录也需要赋予读写权限 sudo vim /etc/fstab #编辑/etc/fstab文件实现开机自动挂载硬盘 #在文件的最后一行加入下列代码 /dev/sda1 /home/pi/share ntfsdefaults0 0 #第一列是挂载的硬盘设备名或者uuid #第二列是挂载的目录 #第三列是硬盘的文件系统类型 #第四列是文件系统的参数，defaults表示同时具有rw, suid, dev, exec, auto, nouser, async等默认参数的设置 #第五列是能否被dump备份命令作用：dump是一个用来作为备份的命令。通常这个参数的值为0或者1，0表示不备份 #第六列是是否检验扇区：开机的过程中，系统默认会以fsck检验我们系统是否为完整（clean）。0表示不要检验 2安装Samba sudo apt-get update #首先更新一下系统源 sudo apt-get install samba samba-common-bin #安装samba-common版本 #samba-common：这个套件则主要提供了 SAMBA 的主要设定档(smb.conf) 、 smb.conf 语法检验的测试程序 ( testparm )等等； sudo vim /etc/samba/smb.conf #编辑配置文件，在文件中加入下列内容 security = share #开放security权限等级为share #share---不需要提供用户名和密码。 #user----需要提供用户名和密码，而且身份验证由 samba server 负责。 #server--需要提供用户名和密码，可指定其他机器(winNT/2000/XP)或另一台 samba server作身份验证。 #domain--需要提供用户名和密码，指定winNT/2000/XP域服务器作身份验证。 [share] #share为开启共享后的文件夹名 comment = samba share #comment为备注，帮助理解这个共享文件夹 path = /home/pi/share #path为共享的文件目录路径 valid user = pi root #vaild users-----设定只有此名单内的用户才能访问共享资源(拒绝优先)(用户名/@组名)，这里设置为pi和root两个 public = yes #public----------是yes/否no公开共享，若为否则进行身份验证(只有当security = share 时此项才起作用) browseable = yes #browseable------是yes/否no在浏览资源中显示共享目录，若为否则必须指定共享路径才能存取 writable = yes #writable--------是yes/否no不以只读方式共享当与read only发生冲突时，无视read only sudo smbpasswd -a pi #新建一个名为pi的用户，然后会提示输入两次密码 #smbpasswd命令的一些用法如下 #-a：向smbpasswd文件中添加用户； #-c：指定samba的配置文件； #-x：从smbpasswd文件中删除用户； #-d：在smbpasswd文件中禁用指定的用户； #-e：在smbpasswd文件中激活指定的用户； #-n：将指定的用户的密码置空。 #设置完用户名和密码后输入下列命令激活用户 sudo smbpasswd -e pi #然后重启samba服务即可生效 sudo /etc/init.d/samba restart #最后还是要设置开机启动 sudo vim /etc/rc.local #在下面添加如下代码 sudo /etc/init.d/samba restart 3添加网络映射 添加网络映射主要是方便访问，可以将共享的samba文件夹添加到我的电脑中。首先右键我的电脑，点击添加网络映射/Add a network location 输入共享的文件夹路径，然后命名，最后即可完成。 miniDLNA 安装miniDLNA sudo apt-get update #先更新一下安装源 sudo apt-get install minidlna #安装minidlna #安装完后编辑配置文件 sudo vim /etc/minidlna.conf media_dir=A,/home/pi/share/Music #A表示这个目录是存放音乐的，当minidlna读到配置文件时，它会自动加载这个目录下的音乐文件 media_dir=P,/home/pi/share/Picture #P表示图片文件 media_dir=V,/home/pi/share/Video #V表示视频文件 db_dir=/home/pi/share/dlnadb #配置minidlna的数库数据的存放目录 log_dir=/home/pi/share/dlnalog #配置日志目录 #然后建立对应的文件夹并给予对应的权限 sudo mkdir /home/pi/share/Music sudo mkdir /home/pi/share/Picture sudo mkdir /home/pi/share/Video sudo mkdir /home/pi/share/dlnadb sudo mkdir /home/pi/share/dlnalog sudo chmod 777 /home/pi/share/Music sudo chmod 777 /home/pi/share/Picture sudo chmod 777 /home/pi/share/Video sudo chmod 777 /home/pi/share/dlnadb sudo chmod 777 /home/pi/share/dlnalog #使用下列命令重启dlna服务 sudo /etc/init.d/minidlna restart #再使用下列命令查看dlna状态 sudo /etc/init.d/minidlna status #最后修改开机启动文件 sudo vim /etc/rc.local #在后面加入以下代码 sudo /etc/init.d/minidlna restart 添加DLNA设备 点击我的电脑左上方的流媒体\n系统会自动搜索到局域网中的支持DLNA的设备，点击添加，等待添加完成。\n打开支持DLNA或者是流媒体播放的软件，就能看到树莓派中的流媒体文件。\n下载机 安装transmission sudo apt-get update #先更新一下安装源 sudo apt-get install transmission-daemon #然后安装transmission #创建目录用于存放下载文件 sudo mkdir /home/pi/share/Downloads/Incomplete #存放未下载完成的文件 sudo mkdir /home/pi/share/Downloads/complete #存放下载完成的文件 #更改文件所属组别 sudo chgrp debian-transmission /home/pi/share/Downloads/Incomplete sudo chgrp debian-transmission /home/pi/share/Downloads/complete #赋予文件夹权限 sudo chmod 777 /home/pi/share/Downloads/Incomplete sudo chmod 777 /home/pi/share/Downloads/complete #修改配置文件 sudo vim /etc/transmission-daemon/settings.json #找到以下项进行修改 #已完成的下载目录 \u0026#34;download-dir\u0026#34;: \u0026#34;/home/pi/share/Downloads/complete\u0026#34;, #未完成的下载目录 \u0026#34;incomplete-dir\u0026#34;: \u0026#34;/home/pi/share/Downloads/Incomplete\u0026#34;, #允许Web访问的白名单地址,这里要根据自己的路由器实际IP地址进行修改 \u0026#34;rpc-whitelist\u0026#34;: \u0026#34;192.168.8.*\u0026#34;, #登录的用户名 “rpc-username”: “yourname”, #登录的密码 “rpc-password”: “yoursecretcode”, #最后依次执行下面的两条命令完成重启服务 sudo service transmission-daemon reload sudo service transmission-daemon restart 最后我们访问树莓派的IP再加上9091端口就能登录到下载界面。 安装Aria2 安装Aria2 sudo apt-get update #更新一下安装源 sudo apt-get install aria2 #安装aria2 #在/etc目录下创建aria2目录用来存放配置文件： sudo mkdir /etc/aria2 #创建空白的aria2.session文件： sudo touch /etc/aria2/aria2.session #创建配置文件 sudo vim /etc/aria2/aria2.conf #在该文件中输入以下内容： dir=/home/pi/share/Downloads #设置文件下载的存放目录 disable-ipv6=true #打开rpc的目的是为了给web管理端用 enable-rpc=true rpc-allow-origin-all=true rpc-listen-all=true #rpc-listen-port=6800 continue=true input-file=/etc/aria2/aria2.session save-session=/etc/aria2/aria2.session max-concurrent-downloads=3 #设置最大同时下载的任务数 #启动aria2 sudo aria2c --conf-path=/etc/aria2/aria2.conf #如果没有提示任何错误信息，那就按ctrl+c停止上面的语句，转为后台运行： sudo aria2c --conf-path=/etc/aria2/aria2.conf －D #同时其此句写到开机启动中，编辑/etc/rc.local sudo vim /etc/rc.local #在文件的最后面添加这一行 sudo aria2c --conf-path=/etc/aria2/aria2.conf －D 安装appache #安装appach sudo apt-get install apache2 #修改/var/www/html的权限 chmod 777 /var/www/html 安装yaaw 从https://github.com/binux/yaaw下载yaaw，点击右下角的Download Zip, 下载后将解压后的文件夹内容拷贝到/var/www/html文件夹下。这时在浏览器内输入树莓派的IP，如果出现以下页面，则表示已经正常工作了。\n点击左上方的add就可以进行下载。\n自动备份 创建自动执行文件 使用记事本新建一个文件，里面输入下列代码，然后保存并更改文件名后缀为bat执行文件。\n@echo off echo Backuping D:\\MyBlog\\source\\_posts---------\u0026gt;192.168.8.106\\share\\Backup xcopy \u0026#34;D:\\MyBlog\\source\\_posts\u0026#34; \u0026#34;\\\\RASPBERRYPI\\share\\Backup\u0026#34; /e/I/d/h/r/y exit 其中xcopy指令后两个路径分别为需要备份的文件夹路径和用于存放备份的文件夹路径，其余参数说明如下：\n/e：拷贝所有子目录，包括空子目录；\n/I： 如果目标文件或目录不存在且拷贝的文件数多于一，则假设目标为目录；\n/d：只拷贝文件日期与在目标文件后的文件（即修改过的源文件）\n/h：同时拷贝隐藏文件和系统文件\n/r：拷贝并覆盖只读文件\n/y： 复制文件审核设置（不显示已有文件覆盖确认）\n以上参数可以根据需要添加，推荐都加上最好。\n设置定时任务 然后我们打开\u0026quot;控制面板\u0026quot;—“计划任务”添加计划任务，计划任务里的执行目标为该批处理文件，设定在什么时候执行则由个人决定。\n执行效果示例如下：\n状态监控 按照针脚跳线连接好LCD1602和树莓派并调节好LCD1602的对比度，使用python编写代码，控制1602输出相关信息，具体代码如下：\n将下面这个文件保存为lcd1602.py\n#!/usr/bin/python # # based on code from lrvick and LiquidCrystal # lrvic - https://github.com/lrvick/raspi-hd44780/blob/master/hd44780.py # LiquidCrystal - https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/LiquidCrystal.cpp # from time import sleep class lcd1602: # commands LCD_CLEARDISPLAY= 0x01 LCD_RETURNHOME = 0x02 LCD_ENTRYMODESET= 0x04 LCD_DISPLAYCONTROL = 0x08 LCD_CURSORSHIFT = 0x10 LCD_FUNCTIONSET = 0x20 LCD_SETCGRAMADDR= 0x40 LCD_SETDDRAMADDR= 0x80 # flags for display entry mode LCD_ENTRYRIGHT = 0x00 LCD_ENTRYLEFT = 0x02 LCD_ENTRYSHIFTINCREMENT = 0x01 LCD_ENTRYSHIFTDECREMENT = 0x00 # flags for display on/off control LCD_DISPLAYON = 0x04 LCD_DISPLAYOFF = 0x00 LCD_CURSORON= 0x02 LCD_CURSOROFF = 0x00 LCD_BLINKON = 0x01 LCD_BLINKOFF= 0x00 # flags for display/cursor shift LCD_DISPLAYMOVE = 0x08 LCD_CURSORMOVE = 0x00 # flags for display/cursor shift LCD_DISPLAYMOVE = 0x08 LCD_CURSORMOVE = 0x00 LCD_MOVERIGHT = 0x04 LCD_MOVELEFT= 0x00 # flags for function set LCD_8BITMODE= 0x10 LCD_4BITMODE= 0x00 LCD_2LINE = 0x08 LCD_1LINE = 0x00 LCD_5x10DOTS= 0x04 LCD_5x8DOTS = 0x00 ​ ​\ndef __init__(self, pin_rs=14, pin_e=15, pins_db=[17, 18, 27, 22], GPIO = None): # Emulate the old behavior of using RPi.GPIO if we haven\u0026#39;t been given # an explicit GPIO interface to use if not GPIO: import RPi.GPIO as GPIO self.GPIO = GPIO self.pin_rs = pin_rs self.pin_e = pin_e self.pins_db = pins_db self.GPIO.setmode(GPIO.BCM) self.GPIO.setwarnings(False) self.GPIO.setup(self.pin_e, GPIO.OUT) self.GPIO.setup(self.pin_rs, GPIO.OUT) for pin in self.pins_db: self.GPIO.setup(pin, GPIO.OUT) self.write4bits(0x33) # initialization self.write4bits(0x32) # initialization self.write4bits(0x28) # 2 line 5x7 matrix self.write4bits(0x0C) # turn cursor off 0x0E to enable cursor self.write4bits(0x06) # shift cursor right self.displaycontrol = self.LCD_DISPLAYON | self.LCD_CURSOROFF | self.LCD_BLINKOFF self.displayfunction = self.LCD_4BITMODE | self.LCD_1LINE | self.LCD_5x8DOTS self.displayfunction |= self.LCD_2LINE \u0026#34;\u0026#34;\u0026#34; Initialize to default text direction (for romance languages) \u0026#34;\u0026#34;\u0026#34; self.displaymode = self.LCD_ENTRYLEFT | self.LCD_ENTRYSHIFTDECREMENT self.write4bits(self.LCD_ENTRYMODESET | self.displaymode) # set the entry mode self.clear() ​\ndef begin(self, cols, lines): if (lines \u0026gt; 1): self.numlines = lines self.displayfunction |= self.LCD_2LINE self.currline = 0 ​\ndef home(self): self.write4bits(self.LCD_RETURNHOME) # set cursor position to zero self.delayMicroseconds(3000) # this command takes a long time! ​\ndef clear(self): self.write4bits(self.LCD_CLEARDISPLAY) # command to clear display self.delayMicroseconds(3000)# 3000 microsecond sleep, clearing the display takes a long time ​\ndef setCursor(self, col, row): self.row_offsets = [ 0x00, 0x40, 0x14, 0x54 ] if ( row \u0026gt; self.numlines ): row = self.numlines - 1 # we count rows starting w/0 self.write4bits(self.LCD_SETDDRAMADDR | (col + self.row_offsets[row])) ​\ndef noDisplay(self): \u0026#34;\u0026#34;\u0026#34; Turn the display off (quickly) \u0026#34;\u0026#34;\u0026#34; self.displaycontrol \u0026amp;= ~self.LCD_DISPLAYON self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol) ​\ndef display(self): \u0026#34;\u0026#34;\u0026#34; Turn the display on (quickly) \u0026#34;\u0026#34;\u0026#34; self.displaycontrol |= self.LCD_DISPLAYON self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol) ​\ndef noCursor(self): \u0026#34;\u0026#34;\u0026#34; Turns the underline cursor on/off \u0026#34;\u0026#34;\u0026#34; self.displaycontrol \u0026amp;= ~self.LCD_CURSORON self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol) ​\ndef cursor(self): \u0026#34;\u0026#34;\u0026#34; Cursor On \u0026#34;\u0026#34;\u0026#34; self.displaycontrol |= self.LCD_CURSORON self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol) ​\ndef noBlink(self): \u0026#34;\u0026#34;\u0026#34; Turn on and off the blinking cursor \u0026#34;\u0026#34;\u0026#34; self.displaycontrol \u0026amp;= ~self.LCD_BLINKON self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol) ​\ndef noBlink(self): \u0026#34;\u0026#34;\u0026#34; Turn on and off the blinking cursor \u0026#34;\u0026#34;\u0026#34; self.displaycontrol \u0026amp;= ~self.LCD_BLINKON self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol) ​\ndef DisplayLeft(self): \u0026#34;\u0026#34;\u0026#34; These commands scroll the display without changing the RAM \u0026#34;\u0026#34;\u0026#34; self.write4bits(self.LCD_CURSORSHIFT | self.LCD_DISPLAYMOVE | self.LCD_MOVELEFT) ​\ndef scrollDisplayRight(self): \u0026#34;\u0026#34;\u0026#34; These commands scroll the display without changing the RAM \u0026#34;\u0026#34;\u0026#34; self.write4bits(self.LCD_CURSORSHIFT | self.LCD_DISPLAYMOVE | self.LCD_MOVERIGHT); ​\ndef leftToRight(self): \u0026#34;\u0026#34;\u0026#34; This is for text that flows Left to Right \u0026#34;\u0026#34;\u0026#34; self.displaymode |= self.LCD_ENTRYLEFT self.write4bits(self.LCD_ENTRYMODESET | self.displaymode); ​\ndef rightToLeft(self): \u0026#34;\u0026#34;\u0026#34; This is for text that flows Right to Left \u0026#34;\u0026#34;\u0026#34; self.displaymode \u0026amp;= ~self.LCD_ENTRYLEFT self.write4bits(self.LCD_ENTRYMODESET | self.displaymode) ​\ndef autoscroll(self): \u0026#34;\u0026#34;\u0026#34; This will \u0026#39;right justify\u0026#39; text from the cursor \u0026#34;\u0026#34;\u0026#34; self.displaymode |= self.LCD_ENTRYSHIFTINCREMENT self.write4bits(self.LCD_ENTRYMODESET | self.displaymode) ​\ndef noAutoscroll(self): \u0026#34;\u0026#34;\u0026#34; This will \u0026#39;left justify\u0026#39; text from the cursor \u0026#34;\u0026#34;\u0026#34; self.displaymode \u0026amp;= ~self.LCD_ENTRYSHIFTINCREMENT self.write4bits(self.LCD_ENTRYMODESET | self.displaymode) ​\ndef write4bits(self, bits, char_mode=False): \u0026#34;\u0026#34;\u0026#34; Send command to LCD \u0026#34;\u0026#34;\u0026#34; self.delayMicroseconds(1000) # 1000 microsecond sleep bits=bin(bits)[2:].zfill(8) self.GPIO.output(self.pin_rs, char_mode) for pin in self.pins_db: self.GPIO.output(pin, False) for i in range(4): if bits[i] == \u0026#34;1\u0026#34;: self.GPIO.output(self.pins_db[::-1][i], True) self.pulseEnable() for pin in self.pins_db: self.GPIO.output(pin, False) for i in range(4,8): if bits[i] == \u0026#34;1\u0026#34;: self.GPIO.output(self.pins_db[::-1][i-4], True) self.pulseEnable() ​\ndef delayMicroseconds(self, microseconds): seconds = microseconds / float(1000000) # divide microseconds by 1 million for seconds sleep(seconds) ​\ndef pulseEnable(self): self.GPIO.output(self.pin_e, False) self.delayMicroseconds(1) # 1 microsecond pause - enable pulse must be \u0026gt; 450ns self.GPIO.output(self.pin_e, True) self.delayMicroseconds(1) # 1 microsecond pause - enable pulse must be \u0026gt; 450ns self.GPIO.output(self.pin_e, False) self.delayMicroseconds(1) # commands need \u0026gt; 37us to settle ​\ndef message(self, text): \u0026#34;\u0026#34;\u0026#34; Send string to LCD、Newline wraps to second line\u0026#34;\u0026#34;\u0026#34; for char in text: if char == \u0026#39;\\n\u0026#39;: self.write4bits(0xC0) # next line else: self.write4bits(ord(char),True) ​\nif __name__ == \u0026#39;__main__\u0026#39;: lcd = lcd1602() lcd.clear() lcd.message(\u0026#34;hello world!\u0026#34;) 再将这个文件保存为1602.py\n#!/usr/bin/python from lcd1602 import * from datetime import * import commands def get_cpu_temp(): tmp = open(\u0026#39;/sys/class/thermal/thermal_zone0/temp\u0026#39;) cpu = tmp.read() tmp.close() return \u0026#39;{:.2f}\u0026#39;.format( float(cpu)/1000 ) + \u0026#39; C\u0026#39; def get_gpu_temp(): tmp = commands.getoutput(\u0026#39;vcgencmd measure_temp|awk -F= \\\u0026#39;{print $2}\\\u0026#39;\u0026#39;).replace(\u0026#39;\\\u0026#39;C\u0026#39;,\u0026#39;\u0026#39;) gpu = float(tmp) return \u0026#39;{:.2f}\u0026#39;.format( gpu ) + \u0026#39; C\u0026#39; def get_time_now(): return datetime.now().strftime(\u0026#39;%H:%M:%S\\n %Y-%m-%d\u0026#39;) def get_ip_info(): ip= commands.getoutput(\u0026#39;ifconfig eth0 | grep inet | awk \\\u0026#39;{ print $2 }\\\u0026#39; | awk \\\u0026#39;NR==1\\\u0026#39;\u0026#39;) return \u0026#39;Ethernet IP:\\n\u0026#39; + ip def get_mem_info(): total= commands.getoutput(\u0026#39;free -m|grep Mem:|awk \\\u0026#39;{print $2}\\\u0026#39;\u0026#39;) free= commands.getoutput(\u0026#39;free -m|grep Mem:|awk \\\u0026#39;{print $4}\\\u0026#39;\u0026#39;) return \u0026#39;MEM:\\n\u0026#39; + free +\u0026#39;/\u0026#39;+ total +\u0026#39;M\u0026#39; lcd = lcd1602() lcd.clear() if __name__ == \u0026#39;__main__\u0026#39;: while(1): lcd.clear() lcd.message( get_ip_info() ) sleep(5) lcd.clear() lcd.message( get_time_now() ) sleep(5) lcd.clear() lcd.message( get_mem_info() ) sleep(5) lcd.clear() lcd.message( \u0026#39;CPU: \u0026#39; + get_cpu_temp()+\u0026#39;\\n\u0026#39; ) lcd.message( \u0026#39;GPU: \u0026#39; + get_gpu_temp() ) sleep(5) 最后将两个文件保存到同一个目录下面，然后编辑文件设置开机启动即可让LCD1602循环显示信息。\n我将这两个文件保存到/home/pi/1602目录下\n#编辑配置文件 sudo vim /etc/rc.local #将此命令添加到文件最后 sudo python /home/pi/1602/1602.py 爬虫服务器 Python源码示例 #encoding=\u0026#39;UTF-8\u0026#39; from urllib import request import json import time from datetime import datetime from datetime import timedelta import pandas as pd from lxml import etree from tqdm import tqdm import random import re import csv # 获取数据，根据url获取 def get_data(url): headers = { \u0026#39;User-Agent\u0026#39;: \u0026#39;Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36\u0026#39; } req = request.Request(url, headers=headers) response = request.urlopen(req) if response.getcode() == 200: return response.read() return None # 处理数据 def parse_data(html): data = json.loads(html)[\u0026#39;cmts\u0026#39;] # 将str转换为json comments = [] for item in data: comment = { \u0026#39;id\u0026#39;: item[\u0026#39;id\u0026#39;], #\u0026#39;nickName\u0026#39;: item[\u0026#39;nickName\u0026#39;], \u0026#39;cityName\u0026#39;: item[\u0026#39;cityName\u0026#39;] if \u0026#39;cityName\u0026#39; in item else \u0026#39;\u0026#39;, # 处理cityName不存在的情况 # 处理评论内容换行的情况,并且将逗号替换为空格 \u0026#39;content\u0026#39;: item[\u0026#39;content\u0026#39;].replace(\u0026#39;”\u0026#39;,\u0026#39; \u0026#39;).replace(\u0026#39;“\u0026#39;,\u0026#39; \u0026#39;).replace(\u0026#39;,\u0026#39;,\u0026#39; \u0026#39;).replace(\u0026#39;，\u0026#39;,\u0026#39; \u0026#39;).replace(\u0026#39;\\n\u0026#39;, \u0026#39; \u0026#39;), \u0026#39;score\u0026#39;: item[\u0026#39;score\u0026#39;], \u0026#39;startTime\u0026#39;: item[\u0026#39;startTime\u0026#39;] } comments.append(comment) return comments # 存储数据，存储到文本文件 def savetoCSV(): #设置指定时间向前爬取评论数据或者从特定的时间段爬取评论 #start_time = \u0026#39;2015-05-12 02:40:34\u0026#39; start_time = datetime.now().strftime(\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;) # 获取当前时间，从当前时间向前获取 #设置爬取评论数据的截至时间 end_time = \u0026#39;2015-05-01 00:00:00\u0026#39; while start_time \u0026gt; end_time: url = \u0026#39;http://m.maoyan.com/mmdb/comments/movie/248170.json?_v_=yes\u0026amp;offset=0\u0026amp;startTime=\u0026#39; + start_time.replace(\u0026#39; \u0026#39;, \u0026#39;%20\u0026#39;) html = None \u0026#39;\u0026#39;\u0026#39; 问题：当请求过于频繁时，服务器会拒绝连接，实际上是服务器的反爬虫策略 解决：1.在每个请求间增加延时0.1秒，尽量减少请求被拒绝 2.如果被拒绝，则0.5秒后重试 \u0026#39;\u0026#39;\u0026#39; try: html = get_data(url) except Exception as e: time.sleep(0.5) html = get_data(url) else: time.sleep(0.1) comments = parse_data(html) print(comments) start_time = comments[14][\u0026#39;startTime\u0026#39;] # 获得末尾评论的时间 start_time = datetime.strptime(start_time, \u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;) + timedelta(seconds=-1) # 转换为datetime类型，减1秒，避免获取到重复数据 start_time = datetime.strftime(start_time, \u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;) # 转换为str ​\nfor item in comments: with open(\u0026#39;test3.csv\u0026#39;, \u0026#39;a\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: f.write(str(item[\u0026#39;id\u0026#39;]) + \u0026#39;,\u0026#39; + item[\u0026#39;startTime\u0026#39;].strip(\u0026#39;[\\\u0026#39;\u0026#39;).split(\u0026#39; \u0026#39;)[0] + \u0026#39;,\u0026#39; + str(item[\u0026#39;score\u0026#39;]) + \u0026#39;,\u0026#39; + item[\u0026#39;cityName\u0026#39;] + \u0026#39;,\u0026#39; + str(item[\u0026#39;content\u0026#39;]) + \u0026#39;\\n\u0026#39;) #f.write(str(item[\u0026#39;id\u0026#39;])+\u0026#39;,\u0026#39;+item[\u0026#39;nickName\u0026#39;] + \u0026#39;,\u0026#39; + item[\u0026#39;cityName\u0026#39;] + \u0026#39;,\u0026#39; + item[\u0026#39;content\u0026#39;] + \u0026#39;,\u0026#39; + str(item[\u0026#39;score\u0026#39;])+ \u0026#39;,\u0026#39; + item[\u0026#39;startTime\u0026#39;] + \u0026#39;\\n\u0026#39;) ​ ​\nif __name__ == \u0026#39;__main__\u0026#39;: html = get_data(\u0026#39;http://m.maoyan.com/mmdb/comments/movie/248170.json?_v_=yes\u0026amp;offset=0\u0026amp;startTime=2018-12-31%2022%3A25%3A03\u0026#39;) comments = parse_data(html) print(comments) savetoCSV() 效果展示 写在最后 重启之后就可以尽情地享用树莓派打造的多媒体中心了，虽然树莓派3B+只有USB2.0接口，但是只要搭配上百兆lan口的路由器，局域网内流畅观看40G左右大小的一部电影还是毫无问题的。\n如果有更高的需求，还是建议上更好的路由器和NAS吧。\n","permalink":"http://localhost:9011/20180910-raspi-multi-media-centre/","summary":"\u003cp\u003e小七比较喜欢在宿舍和室友一起看电影或听音乐，且对画质和音质要求较高，一般都是观看1080P的蓝光REMUX电影（约30G一部）和听无损音质的音乐（30M一首），且观看设备较多（电视盒子、电脑、手机），再加上平时需要下载电影且自己有写博客的习惯，博客和一些其他的重要资料也需要备份，因此决定使用树莓派3B+、一块移动硬盘和一个路由器搭建一个宿舍多媒体中心来满足这些需求。\u003c/p\u003e","title":"树莓派3B+打造多媒体中心"},{"content":"(多图预警！！！)Android Studio作为谷歌官方的安卓开发工具，配置上并不算太难，但是有些地方需要额外注意一下。\n注意：本文遵循一步一截图的原则，尽量详细（也可能有点冗长），希望各位同学注意耐心、时间和流量。\n配置JDK 安装Android Studio（以下简称AS），需要先配置JAVA，还没有配置的同学可以点击下面的链接看小七之前的教程，这里就不再赘述。\nJDK安装与环境变量配置\n下载AS 由于众所周知的原因，国内无法直接上谷歌的官网，但是对于AS的下载，还是比较简单的。\n我们登录这个网址：\thttp://www.android-studio.org/\t选择对应的版本进行下载，一般来说对于新手建议下载exe文件进行安装，如果对AS已经有一定的了解则可以下载zip文件直接解压进行使用。\n安装AS 新建安装目录 为了方便管理，我在D盘根目录下面新建了一个Android的文件夹，里面再新建三个分别名为AndroidStudio、AndroidProject和AndroidSDK的文件夹用于存放AS,Android的项目和AS的SDK。\n建议不要在C盘根目录下新建文件夹，容易出现权限问题，也一定不要使用中文名给文件夹命名，包括你现在登录到windows系统的用户名也不要是中文名，否则会出错。\n运行exe安装文件 ▲双击运行安装文件，点击Next\n▲**此处勾选Android Virtual Device(AVD)，即安卓虚拟机，**可以在不连接安卓手机的情况下进行项目的调试\n▲此处即为AS的安装目录，将其更改为我们之前的新建的安装目录，最好不要使用默认目录！\n▲这里我们可以看到，即便是更改了安装目录，也会在系统的Users文件夹对应的账户下面新建一个.android文件夹,因此这个账户名注意不能是中文，否则后面会报错。\n▲到这里AS就已经算是基本安装完成了，但是AS需要的其他的一些插件都还没有安装，因此我们勾选Start，点击Finish。\n安装SDK和AVD ▲初次运行AS会提示你是否需要导入之前的配置文件，如果有同学之前使用过AS并且导出了配置文件，这里只需要直接导入就可以了，没有的同学直接点击Do not然后OK。\n▲这时候就会弹出提示说找不到Android SDK，我们不需要设置代理，直接点击右边的Cancel\n▲点击右边的Cancel之后会开始下载一些配置文件\n▲下载完成之后就会开始配置SDK\n▲同样还是为了方便管理，我们在安装选项这里选择Custom（自定义)\n▲首先进行选择的是界面风格，有明暗两种色调，像小七这种夜猫子果断选暗色调。\n▲这里会自动选择最新的SDK和API，不过我们要更改下面的安装目录为我们之前准备好的新目录，然后把上面的Intel HAXM和Android Virtual Device也勾选上，这样就把AVD也一并安装了。\n注意：运行安卓虚拟机需要安装Intel HAXM和在电脑的BIOS中开启Intel Virtualization Technology（Intel VT/英特尔虚拟化技术），二者缺一不可。\n▲接下来是设置AVD的内存大小，一般我们选择默认的2G推荐值，如果电脑本身内存是4G可以调小一点，同样，如果电脑内存是16G之类的大内存请随意。\n▲接下来会列出各种安装包组件的大小，我们直接点击Finish进行安装\n▲安装的过程可能会有点慢，请同学们保持耐心和网络畅通，等待全部安装完成之后再点击Finish。\n到这里AS和对应的SDK还有AVD就已经安装完成了啦，下面我们来跑个HelloWorld试试。\nHelloWorld 新建项目 ▲这里来到了AS的启动界面，和JerBrain家出品的其他IDE风格一脉相承，我们点击Start a new Android Studio Project来新建我们的第一个HelloWorld程序。\n▲接下来就是设置项目的名称（一般也是APP的名称)，接着将项目目录改成我们之前新建的Android Project文件夹，中间的Company Domain，如果是个人开发者就填自己的邮箱或者名字啥的，公司开发者就填公司。\n▲接下来就是选择你的项目类型，是正常的安卓手机APP还是其他的安卓TV、Wear等等，这里我们以安卓手机APP为例，然后就要选择API版本，**这里的API版本指的是这个项目向下兼容的最低版本，一般来说，API越低，兼容的设备越多，但是新API里面的新功能和特性就越容易出问题，**具体如何选择看同学们的实际需求。\n▲接下来就是选择Activity，AS官方提供了一些常用的Activity，这里我们选择一个空白的Activity（Empty Activity)。\n▲接下来是填写Activity的名字和Layout的名字，我们直接使用默认值。（Activity和Layout数量多了之后最好根据功能用途进行命名，否则查找的时候很麻烦)\n▲第一次新建项目还有一些SDK需要再配置一下。\n▲接着就是下载Gradle，Gradle的版本可以在对应的项目配置文件中修改，修改的时候注意会引发一系列的连锁反应，因此要小心谨慎。\nGradle是AS中很重要的一个部分，以后有机会我们再开新帖研究探讨一下。\n▲AS到了3.x版本之后就会又这个温馨提示窗口，有兴趣的同学可以看一下，这里我们直接跳过。\n▲接下来我们点击右上方的Sync按钮看一下项目有没有什么问题，如图所示这里是比较正常的。\n运行AVD ▲接下来我们点击Sync旁边的AVD按钮，打开AVD窗口\n▲如图所示，我们之前在安装的时候勾选了AVD选项，所以这里已经有了一个配置好的AVD，我们点击**右边的绿色播放按钮（RUN)**就可以直接运行。为了方便同学们理解，此处我们再新建一个AVD设备，点击左下方的Creat Virtual Devices。\n▲这里我们可以看到谷歌自家的一些机型以及对应的分辨率和屏幕尺寸，我们可以直接使用这些设备，也可以点击左下方导入或者新建一个，这里我们选择谷歌家的Pixel2 XL。\n▲接下来就是选择API版本也就是系统版本，我们可以建立同一机型的不同系统版本的AVD方便我们调试。\n▲接下来可以选择AVD是横向还是纵向，我们使用默认设置，直接完成即可。\n▲如图所示AVD虚拟机正在开启。\n▲到这里我们可以看到虚拟机已经开启完毕，里面的系统正在开机，旁边是我们使用手机常用到的一些操作。\n▲接下来我们把界面切回AS。点击右上方的绿色播放按钮，其实是RUN按钮。\n▲然后我们选择在刚刚已经开启的Pixel2 XL虚拟机上运行。\n▲接着我们就可以看到Hello World啦。\n▲我们打开虚拟机里面的应用抽屉，可以看到我们刚刚run的应用已经安装在这上面了。\n文字内容小改 接下来我们试试把HelloWorld改成HelloAndroid\n▲在如图所示的目录中找到HelloWorld对应的控件，直接修改文字内容，保存然后再Sync再Run一次。\n▲Hello Android！\n","permalink":"http://localhost:9011/20180824-androidstudio-install/","summary":"\u003cp\u003e(多图预警！！！)Android Studio作为谷歌官方的安卓开发工具，配置上并不算太难，但是有些地方需要额外注意一下。\u003c/p\u003e","title":"Android Studio配置"},{"content":"Ubuntu18.04发布了很长一段时间了，基本上系统本身也趋于稳定，那么我们现在来尝试一下在已经存在win10系统的情况下安装Ubuntu18.04。本文使用的是单硬盘+GPT分区表配置方案，也是目前的主流方案。\n下载Ubuntu18.04及相关工具 下载Ubuntu Ubuntu已经有了中文官网，而且在国内也能正常访问，因此下载相对简单。需要注意的是，Ubuntu18.04只有64位的版本。\n这是下载链接：\thttp://releases.ubuntu.com/18.04/\n▼我们下载图中的第一个后缀为desktop-amd64的iso镜像文件。\n下载UltraISO 使用搜索引擎搜索下载UltraISO并安装，关于UltraISO的使用可以参考一下小七之前发过的文章（双启动U盘）。\n准备U盘和硬盘分区 准备一个8G左右的U盘即可，USB3.0最好，没有的话2.0的U盘也可以。\n硬盘上需要划分出一块空白空间，最少应该要有20G左右，小七这里划分了50G左右的空间。如果只是想尝试一下Ubuntu的话，预留20到30G是比较合适的。\n具体的划分空间操作可以使用windows系统自带的磁盘管理进行压缩操作，也可以使用DiskGenius进行分区，操作十分简单，这里不再赘述。\n刻录安装U盘 ▼如图所示，打开UltraISO后，点击左上角的文件-\u0026gt;打开，选择刚刚下载好的ISO镜像文件。\n▼再点击上方菜单栏的启动-\u0026gt;写入硬盘，硬盘驱动器要选择对应的U盘，不能选错，然后点击便捷启动-\u0026gt;写入新的驱动器引导扇区-\u0026gt;Syslinux。\n▼接下来点击写入，就会对U盘进行格式化并将ISO文件刻录到U盘里面。\n进入安装界面 刻录完成之后，我们**重启电脑进入BIOS或者是快捷启动菜单选择启动项，**一般来说都是ESC/F1/F2/F8/F9/F11/F12等按键，具体会因笔记本的型号不同或是台式机的主板不同而不同，进入之后我们选择对应的UEFI启动项，名称应该是对应的U盘型号。\n小七注：有些电脑可能需要关闭BIOS设置中的Security Boot选项才能顺利使用U盘启动。\n▼到这里我们选择Try Ubuntu without install，这样可以先看一下Ubuntu的界面UI和各种操作，当然也可以直接选择下面的Install Ubuntu。\n▼接下来就进入到了Ubuntu的界面，这里除了还没有对应的Ubuntu硬盘分区之外，其他的基本没有太大差别，我们点击桌面的Install Ubuntu 18.04.1LTS进行安装。 开始安装 接下来到分区之前都是一些简单的设置，我们根据实际需要选择即可。\n语言设置 ▼语言选择中文\n键盘设置 ▼键盘布局选择汉语\n网络设置 ▼WiFi暂时先不要连接\n系统安装分区 ▼安装类型选择其他选项\n▼如图所示，硬盘上的分区多数是NTFS分区，这是windows系统使用的分区，而上方的EFI分区则是使用UEFI+GPT分区表模式下产生的用于记录系统引导文件的分区\n一般来说，Ubuntu的安装分区可以分为三种情况：\n第一种 简单粗暴，只分一个\t/ （主分区），将所有的空间全部分到这个分区里面。\n这种方案比较适合刚入门Ubuntu的萌新，想先体验上手一下Ubuntu，不需要担心分区的设置合理情况。\n第二种 分两个，Swap交换分区和/主分区，swap分区一般根据内存大小来分，剩下的全部给/主分区\nSwap交换分区其实就是虚拟内存分区，如果电脑内存比较小（小于8G）而且平时需要用到较多内存的话可以划分大一点（4G左右），如果平时内存足够用可以划分小一点，几百M到一两G都是没问题的。\n有兴趣的同学可以点击这里跳转到小七之前的博客了解一下。（虚拟内存）\n第三种 分四个，/boot引导分区、/home用户分区、swap交换分区和 / 主分区 **/boot（引导分区）：**相当于windows的efi分区，大小设置为200M即可，也可以更大一点；\n**/（主分区）：**用于存放Ubuntu系统，相当于windows的C盘，一般来说10到15G就够了。\n**Swap(交换分区）：**虚拟内存分区，大小参看第二种分法。\n**/home（用户分区）：**存储用户的各种数据，剩下的硬盘空间有多大就分多大给这个分区\n这里小七选择第二种分区方案，一来是因为小七这里只划了50G来安装Ubuntu，硬盘本身的空间就不大，分太多区不太好；二来就是小七自己也没有什么分区的习惯（windows下也只是分了两个盘）。 ▼如图所示，给交换分区（Swap）分2G（这台电脑的内存是16G，不需要分太大的交换空间）\n▼如图所示，剩下的空间全部分给\t/\t主分区\n▼确认无误后点击确认。\n设置区域 ▼设置区域，选择中国，默认城市是上海。\n设置用户名 ▼设置用户名和密码，也可以不设置，但是最好还是设置一下，一些超级用户操作需要输入密码解锁权限，没有密码的话会比较麻烦。\n▼接下来就静静地等待安装完成。\n▼安装完成后会提示重启进入系统。\n重启系统 ▼重启后我们再进入启动选项可以看到Ubuntu已经在默认的第一个启动项中，如果平时使用windows比较多的可以把第一个改回windows。\n▼如图所示即可进入Ubuntu系统，下面的第三个就是windows系统。\n","permalink":"http://localhost:9011/20180817-ubuntu-install/","summary":"\u003cp\u003eUbuntu18.04发布了很长一段时间了，基本上系统本身也趋于稳定，那么我们现在来尝试一下在已经存在win10系统的情况下安装Ubuntu18.04。本文使用的是单硬盘+GPT分区表配置方案，也是目前的主流方案。\u003c/p\u003e","title":"Win10下安装Ubuntu双系统"},{"content":"有些电脑的硬盘出厂模式就是RaidOn,RAID模式对于混合硬盘来说有一定的好处，但是对于单固态来说还是AHCI更为方便一些。今天在装Ubuntu的时候发现安装程序不认RaidOn模式的硬盘，只能改成AHCI模式。\n如果直接在BIOS中更改硬盘模式为AHCI，则会出现无法进入系统的情况，因此我们需要使用安全启动模式。\n打开安全启动模式 同时按下win+R按键，输入msconfig：\n打开配置菜单如下图所示，点击boot，勾选safe boot，选择minimal。\n点击OK，然后重新启动系统。\n更改BIOS中硬盘模式 进入BIOS，更改硬盘模式为AHCI，点击确定。\n关闭安全启动模式 此时重启完成进入电脑是安全模式，重复第一步，取消勾选。\n点击OK，再次重启即可。\n","permalink":"http://localhost:9011/20180814-raidon-2-ahci/","summary":"\u003cp\u003e有些电脑的硬盘出厂模式就是RaidOn,RAID模式对于混合硬盘来说有一定的好处，但是对于单固态来说还是AHCI更为方便一些。今天在装Ubuntu的时候发现安装程序不认RaidOn模式的硬盘，只能改成AHCI模式。\u003c/p\u003e","title":"硬盘RaidOn模式无损转换为AHCI模式"},{"content":"本文以JAVA10为例，JAVA8的安装配置基本大同小异。\n下载JAVA10 首先我们要去官网下载JAVA10的安装包。点击这里跳转到官网。\n截至到发文为止，最新的版本是10.0.2。下载之前要记得先点击上面的Accept License Agreement，否则将无法下载。\n另外，Windows上JAVA10只提供64位的版本。不过现在大多数电脑都是64位，这个问题应该不大。\n下载完成之后，直接双击安装。\n安装JAVA10 因为小七这里只有10.0.1的版本，且系统的默认显示语言为英语，所以就以这个版本为例。（这里需要额外提一下，各种开发工具和IDE的存放文件夹和路径最好都以英文命名，不容易出现问题）\n▲我们点击next\n▲这里我们取消勾选JRE，因为没有必要而且会使后面的环境变量配置变得很复杂。 ▲为了方便管理和记忆，我将它安装到了D盘，这个安装路径需要记住，因为后面配置环境变量的时候需要用到。\n▲到这里JAVA10就已经安装完成了，下面我们进入环境变量的配置阶段。\n配置环境变量 JAVA10 ▲我们找到我的电脑，右键选择属性，然后点击左上方列表最下面的高级系统设置，点击下方的环境变量设置，看到最下面的系统变量。\n▲首先我们添加一个名为JAVA_HOME的变量，变量的值就是刚刚我们安装JAVA10的目录。\n需要注意的是，如果你前面安装了JRE，在对应的安装目录下面会有JDK和JRE两个文件夹，此时我们需要选择JDK文件夹。\n▲接着我们修改PATH变量（path不区分大小写）。在path变量的最前面添加如下变量值：\n;%JAVA_HOME%\\bin; 注意这个变量值要和上面完全一样。\n依次点击确定并关闭所有窗口。到这里我们就已经安装配置好JAVA10了。\nJAVA8 JAVA8的环境变量配置大同小异。\nC:\\Java\\jdk1.8.0_202 %JAVA_HOME%\\bin;%JAVA_HOME%\\jre\\bin; .;%JAVA_HOME%\\lib\\dt.jar;%JAVA_HOME%\\lib\\tools.jar; 检验安装 ▲我们打开CMD，输入java或者javac，出现下面的命令行就表示已经配置成功。\n为了进一步确定是全局变量配置成功，我们在其他位置（如D盘）新建一个java文件，写个简单的hello测试一下。\npublic class Hello{ public static void main(String[] args){ System.out.println(\u0026#34;HelloJAVA10!\u0026#34;); } } ▲然后我们先使用javac命令编译java文件生成class文件，再使用java命令执行class文件就可以看到实际效果了。\n","permalink":"http://localhost:9011/20180803-jdk-install/","summary":"\u003cp\u003e本文以JAVA10为例，JAVA8的安装配置基本大同小异。\u003c/p\u003e","title":"JDK安装与环境变量配置"},{"content":"app.json文件用来对微信小程序进行全局配置，决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 和debug模式五方面的配置。\n下面我们通过一个Demo来对其进行深入的了解。\n1、Demo 先看一下微信官方给出的一些说明。\n属性 类型 必填 描述 pages String Array 是 设置页面路径 window Object 否 设置默认页面的窗口表现 tabBar Object 否 设置底部 tab 的表现 networkTimeout Object 否 设置网络超时时间 debug Boolean 否 设置是否开启 debug 模式 再来看一段包含了上述五个部分的代码。\n{ \u0026quot;pages\u0026quot;: [ \u0026quot;pages/index/index\u0026quot;, \u0026quot;pages/logs/logs\u0026quot;, \u0026quot;pages/mine/mine\u0026quot;, \u0026quot;pages/resume/resume\u0026quot;, \u0026quot;pages/share/share\u0026quot; ], \u0026quot;window\u0026quot;: { \u0026quot;backgroundTextStyle\u0026quot;: \u0026quot;light\u0026quot;, \u0026quot;navigationBarBackgroundColor\u0026quot;: \u0026quot;#fff\u0026quot;, \u0026quot;navigationBarTitleText\u0026quot;: \u0026quot;SchoolHunting\u0026quot;, \u0026quot;navigationBarTextStyle\u0026quot;: \u0026quot;black\u0026quot; }, \u0026quot;tabBar\u0026quot;: { \u0026quot;list\u0026quot;: [ { \u0026quot;pagePath\u0026quot;: \u0026quot;pages/index/index\u0026quot;, \u0026quot;text\u0026quot;: \u0026quot;首页\u0026quot;, \u0026quot;iconPath\u0026quot;: \u0026quot;images/tabBar/index.png\u0026quot;, \u0026quot;selectedIconPath\u0026quot;: \u0026quot;images/tabBar/index_sel.png\u0026quot; }, { \u0026quot;pagePath\u0026quot;:\u0026quot;pages/share/share\u0026quot;, \u0026quot;text\u0026quot;: \u0026quot;分享\u0026quot;, \u0026quot;iconPath\u0026quot;: \u0026quot;images/tabBar/share.png\u0026quot;, \u0026quot;selectedIconPath\u0026quot;: \u0026quot;images/tabBar/share_sel.png\u0026quot; }, { \u0026quot;pagePath\u0026quot;:\u0026quot;pages/resume/resume\u0026quot;, \u0026quot;text\u0026quot;: \u0026quot;简历\u0026quot;, \u0026quot;iconPath\u0026quot;: \u0026quot;images/tabBar/resume.png\u0026quot;, \u0026quot;selectedIconPath\u0026quot;: \u0026quot;images/tabBar/resume_sel.png\u0026quot; }, { \u0026quot;pagePath\u0026quot;: \u0026quot;pages/mine/mine\u0026quot;, \u0026quot;text\u0026quot;: \u0026quot;我的\u0026quot;, \u0026quot;iconPath\u0026quot;: \u0026quot;images/tabBar/mine.png\u0026quot;, \u0026quot;selectedIconPath\u0026quot;: \u0026quot;images/tabBar/mine.png\u0026quot; } ] }, \u0026quot;networkTimeout\u0026quot;: { \u0026quot;request\u0026quot;: 10000, \u0026quot;downloadFile\u0026quot;: 10000 }, \u0026quot;debug\u0026quot;: true } 2、pages：页面文件的路径 接受一个数组，每一项都是字符串，来指定小程序由哪些页面组成。\n每一项代表对应页面的【路径+文件名】信息，数组的第一项代表小程序的初始页面。\n小程序中新增/减少页面，都需要对 pages 数组进行修改，在IDE的文件目录新建pages的时候IDE会提醒是否自动更新app.json中的pages字段。\n文件名不需要写文件后缀，因为框架会自动去寻找路径下 .json, .js, .wxml, .wxss 四个文件进行整合。\n3、window：窗口表现 主要会用到的是navigationBarTitleText这个变量，变量设置的是小程序界面上方显示的名称，一般设置成小程序的名称，其余变量是对其的样式设置。可以点击这里查看官方的详细说明。\n4、tabBar：设置多bar tabBar字段主要是设置小程序的Bar，最少为两个，最多为五个。\nposition变量可以设置Bar的位置是在下面（bottom）还是在上方（top），需要额外注意的是，当设置为上方（top）时，不会显示图标Icon。\nlist作为数组，其中的变量pagePath和text设置bar对应的页面路径和名称，iconPath和selectediconPath是未选中状态和选中状态的两个图标的路径，不设置则无，只设置其中一个则另一个也相同，建议要设置两个且最好用相同图标不同颜色（黑灰或黑彩）来进行区分。\n这里给出两张对比图。\n5、networkTimeout：设置网络超时时间 **主要是四个网络请求的超时设置（request、connectSocket、uploadFile和downloadFile），**单位是毫秒（ms），一秒等于一千毫秒，默认的时间是60000ms即一分钟。\n6、debug：debug模式 这个比较简单，debug这个变量本身是个布尔型，默认值也是true（开启状态）。\ndebug模式开启之后，在开发者工具的控制台面板，调试信息以 info 的形式给出，其信息有Page的注册，页面路由，数据更新，事件触发 。 可以帮助开发者快速定位一些常见的问题。\n这里同样给出两张对比图。\n","permalink":"http://localhost:9011/20180718-tiny-program-02/","summary":"\u003cp\u003eapp.json文件用来对微信小程序进行全局配置，决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 和debug模式五方面的配置。\u003c/p\u003e","title":"小程序之app.json"},{"content":"微信小程序也推出了有些时间了，最近因为种种原因终于要正儿八经地开始微信小程序开发了，先记录一下基本的开发者账号注册和开发环境搭建。\n1、开发者账号注册 微信小程序的开发和其他的软件开发不太一样，需要先实名注册账号，而且官方的IDE得先登录才能使用。\n注册的网址在这里： https://mp.weixin.qq.com/cgi-bin/wx\n需要注意的是，一个邮箱只能对应一个小程序开发，如果你想开发多个小程序，那就多拿几个邮箱去注册，邮箱还要绑定已经用银行卡实名认证的微信号绑定，其他的信息如实填写即可，填错了也没关系，反正填了之后还能修改。\n2、开发环境搭建 微信官方也提供了开发IDE，安装包并不大，只有几十MB，下载之后傻瓜式安装即可。\n这是下载链接：\thttps://developers.weixin.qq.com/miniprogram/dev/devtools/download.html\n安装之后点击打开，需要使用微信扫描二维码登陆\n然后我们选择小程序\n接下来就要填写小程序项目的目录，AppID以及小程序的名称\n其中，AppID需要登录网页的微信公众平台，\thttps://mp.weixin.qq.com\t在设置→开发设置里面查看。\n3、熟悉基本开发环境 3.1 基本环境 接下来我们看一下整个IDE的基本开发环境\n左上方可以选择模拟器，由于小程序和web程序差不多，（IDE本身也叫web开发者工具）所以这个模拟器基本也就是模拟一下分辨率的样子，当然不同的可能还有安卓和IOS平台之间的一些权限。\n左边的这一大个就是模拟器了。\n再过来的就是文件目录，以及主要的工作区。\n上面的部分是工具栏，下面的控制台，和Visual Studio、Android Studio 这类IDE基本大同小异。\n可能不同的地方就是微信的web开发者工具BUG比较多？\n3.2 四类文件 这里我们再说一下小程序里面的四类主要文件：\n网页编程采用的是 HTML + CSS + JS 这样的组合，其中 HTML 是用来描述当前这个页面的结构，CSS 用来描述页面的样子，JS 通常是用来处理这个页面和用户的交互。在小程序中，也是如此。\n.js **js是典型的脚本文件，**在小程序这里，**还可以在js中调用小程序的API，**实现更丰富的功能。\n（官方是这么说的，但是实际上我总觉得微信小程序的有很多功能都无法实现，被限制得太死了）\n.json json就是配置文件，这个没什么好说，但是根据文件名，我们可以看到这里的json文件可以分为三类，我们以 在根目录下的app.json ， project.config.json，和在 pages/logs 目录下的logs.json为例依次来说明一下他们的用途。\n**app.json 是小程序的配置文件。**它是对当前小程序的全局配置，包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等。\n**project.config.json是整个IDE的配置文件。**它保存着IDE的各类配置，比如代码的字体颜色大小等各类信息，类似于JetBrains家的IDE导出的setting文件，方便迁移IDE环境的时候恢复原来的开发环境配置的。\n**page.json是所属页面的配置文件。**和app.json不同，它只对所在目录的页面生效。如果将app.json看作是全局配置，那么page.json就可以看作是局部配置。\n.wxss 类比css文件，就是用来配置页面的各种样式。\n.wxml 类比html文件，在小程序中 WXML 充当的就是类似 HTML 的角色。\nHelloWorld 回到模拟器这里，我们可以看到…… emmm，对的，hello world就在这里。 （天哪好没成就感，这个helloworld没有一个从无到有的出现过程，太没意思了。）\n我们点击获取头像昵称，然后就可以登录了，用的微信账号就是我们刚刚扫码登录的那个微信账号，也是我们用来绑定这个小程序开发账号的微信账号。\n最后到这里就算是登录成功了，整个第一步也就完成了。\n最后再额外提一下，关于第三方开发者能获取的微信用户的信息其实很少，除了头像昵称和ID基本就没有了。\n但是有些例外：\n**腾讯的嫡系（亲儿子）：**QQ音乐、腾讯视频等可以通过小程序实现一些简单的输出到朋友圈的操作。\n**腾讯的旁系（干儿子）：**大众点评、美团等等可以退一步，能够获取微信用户的好友以及展示他们的操作。\n**和腾讯扯不上啥关系的第三方开发者：**获取昵称头像ID就差不多了。\n所以我们可以看到，微信的小程序并不是对所有开发者平等的，至少在现在来说，对开发者能获取的权限来说是不平等的，它和Facebook的bots虽然看起来很像，但是在开发者层面来说还是有很大差别的。\n","permalink":"http://localhost:9011/20180717-tiny-program-01/","summary":"\u003cp\u003e微信小程序也推出了有些时间了，最近因为种种原因终于要正儿八经地开始微信小程序开发了，先记录一下基本的开发者账号注册和开发环境搭建。\u003c/p\u003e","title":"小程序之HelloWorld"},{"content":"汕头大学于津老师的数据结构复习题，对应教材是清华的严蔚敏老师的《数据结构》。\n以Niklus Wirth的观点，程序等于什么? 程序=数据结构+算法\n算法的重要特性；好算法的标准。 算法的重要特性：①有穷性②确定性③可行性④输入⑤输出\n好算法的标准：①正确性②可读性③健壮性④高效率和低存储\n数据结构主要研究对象： 逻辑结构（线性、非线性）、\n存贮结构（顺序、链式、索引、散列hash）；\n运算/操作（对数据的最主要的操作：增删改查）；\n数据的逻辑结构有几大类？ 线性、非线性\n数据的存贮结构有几类？ 四类：顺序、链式、索引、散列hash\n线性结构的特点。 线性结构中的数据元素存在一个对一个的关系，在线性结构中只有一个开始结点和一个终端结点，其他的每一个结点有且仅有一个前驱结点和后继结点。\n线性结构与非线性结构的区别。 线性结构中的元素必须是一对一的关系，而非线性结构中的元素可以是一对多或者是多对多。\n列出所学过的线性结构与非线性结构。 **线性结构：**线性表，栈，队列，串，一维数组；\n**非线性结构：**二维数组，多维数组，广义表，树，森林，图；\n头指针、头结点、首元结点的区别。 **头指针：**指向链表中第一个结点（头结点/首元结点）的指针；\n**头结点：**链表的首元结点之前附设的一个结点；\n**首元结点：**链表中存储线性表中第一个数据元素a1的结点；\n带头结点和不带头结点的线性链表(单链表)的区别。 **在结构上，**带头结点的单链表，不管链表是否为空，均含有一个头结点，不带头结点的单链表不含头结点。\n**在操作上，**带头结点的单链表的初始化为申请一个头结点。无论插入或删除的位置是首元结点还是其他结点，算法步骤都相同。不带头结点的单链表，则要考虑插入或删除的位置。\n单链表、双链表、循环链表的区别、各自的优缺点及怎样决定选取何种存贮结构。 **单链表：**每个结点中只包含一个指针域\n**优点：**插入和删除时候不需要移动大量的元素\n**缺点：**指针只能单方向移动\n**双链表：**有两个指针域，其一指向直接后继，其二指向直接前驱\n**优点：**查找直接前驱的时候，则从头指针出发，能够克服单链表这种单向性的缺点\n**缺点：**插入删除操作时需要修改多个指针域\n**循环链表：**最后一个结点的指针域指向头结点\n**优点：**使两个表连接起来就很简单，这个操作仅需两个指针即可，插入、删除时，不会断链等。\n**缺点：**不容易确定退出循环的条件\n栈和队列是什么样的线性表? 栈和队列都是操作受限的线性表。\n栈是一种**后进先出（LIFO）**的线性表，限定仅在表尾进行插入或删除操作的线性表。\n队列是一种**先进先出（FIFO）**的线性表，只允许在表的一端进行插入，而在另一端删除元素。\n指出顺序线性表、顺序栈、顺序队列的区别。 相同：都是线性表，都是一维数组，\n不同：操作不同；（线性表是任意位置操作，栈是栈顶操作，队列是尾进头出）\n顺序线性表：用一组地址连续的存储单元依次存储线性表的数据元素。\n顺序栈：即栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素，同时附设指针top指示栈顶元素在顺序栈中的位置。\n顺序队列：除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素之外，尚需附设两个指针front和rear分别指示队列头元素及队列尾元素的位置。\n例举栈和队列的实例及用栈和队列所能解决的问题。 **栈：后进先出的数据（LIFO），**如：数制转换、括号匹配的校验、行编辑程序、迷宫求解、表达式求值、铁路中转站，餐厅的食物盘, 子弹壳。\n**队列：先进先出的数据 (FIFO)，**如：操作系统作业排队，排队买东西\n指出通常解决队列和栈溢出时所能用到的方法。 **队列：**双向队列，链队列，循环队列\n**栈：**双栈共享，多栈共享，链栈\n循环队列的循环是怎样实现的? 队头、队尾指针加1，用取模(余数)运算实现循环\n给出对称矩阵、三角矩阵、对角矩阵节省内存的存贮结构并写出相应的输入、输出算法。 对称矩阵的存贮结构：\n利用 = 来存储(以按行存储为例)\nK=I(I-1)/2 +J-1 I\u0026gt;=J;\nK=J(J-1)/2 +I-1 I\u0026lt;J;\nk 是对称矩阵位于（i，j）位置的元素在一维数组中的存放位置，从0开始\na11 a21 a22 a31 \u0026hellip;\u0026hellip; ann\n三角矩阵的存贮结构：\n以下三角为例，当i\u0026lt;j时,aij=0\nK=0的位置存储0\n0 a11 a21 a22 \u0026hellip;\u0026hellip; a\n对角矩阵的存贮结构：\n记住loc( aij )=loc( a11 )+( 2i+j-3 ) L i-1\u0026lt;=j\u0026lt;=i+1\n输入输出算法：\n给出稀疏矩阵的节省内存的存贮结构并写出相应的输入、输出算法。 为了节省存储单元，可只存储非零元素。由于非零元素的分布一般是没有规律的，因此在存储非零元素的同时，还必须存储非零元素所在的行号、列号，才能迅速确定一个非零元素是矩阵中的哪一个元素。其中每一个非零元素所在的行号、列号和值组成一个三元组，并由此三元组惟一确定。\n//三元组表 #define MAXSIZE 100 //非零元个数的最大值 typedef struct{ int i,j; // 行下标,列下标 ElemType e; // 非零元素值 }Triple; typedef struct {Triple data[MAXSIZE+1]; //非零元三元组表,data[0]未用 int mu,nu, tu; // 矩阵的行数、列数和非零元个数 }TSMatrix; //十字链表 typedef struct OLNode //结点的定义 {int i,j; // 非零元的行和列下标 ElemType e; // 非零元素值 struct OLNode *right,*down; // 该非零元所在行表和列表的后继链域 }OLNode, *OLink; typedef struct //链表的定义 { OLink *rhead,*chead; // 行和列链表头指针向量基址,由CreatSMatrix_OL()分配 int mu,nu,tu; // 稀疏矩阵的行、列数和非零元个数 }CrossList; 输入输出算法\n用十字链表存贮稀疏矩阵时, 矩阵的每个元素同时在几条链上, 分别被称为什么链？ 两条链：行链和列链\n给出树的不同的几种表示（图示）形式。 （一）层次表示法\n（二）广义表表示法\n（三）嵌套表示法\n（四）凹入法表示法\n在二叉树的第 i层上至多有多少个结点。深度为 K的二叉树至多有多少个结点。 第i层上至多有2^（i-1）^个结点\n深度为 K的二叉树至多有2^k^-1个结点\n在一颗二叉树中, 其叶子结点数n0和度为二的结点数n2之间的关系。 n0=n2+1\n证明过程如下：\n假设二叉树的0度,1度,2度结点为n0,n1,n2,总节点数为T\n则有按照结点求和的：T = n0 + n1 + n2 （1）\n按照边求和得：T = n1 + 2 * n2 + 1 （2）\n所以 （2） - （1）可得：n2 + 1 - n0 = 0\n所以n0 = n2 + 1\n有 n个结点的完全二叉树的深度。 (log2n)+1\n在二叉树的顺序存贮结构中如何求结点的双亲、孩子? 双亲：i/2； 左孩子：2*i； 右孩子：2*i+1；\n25. 有 n个结点的二叉树用二叉链表存贮时有多少个空链域，用三叉链表存贮时有多少个空链域。\n二叉：n+1个空链域\n三叉：n+2个空链域\n26. 为什么可在不增加指针域的情况下，对二叉树进行线索化，线索化的目的是什么?\n①利用n+1个空链域\n②目的：遍历方便\n27. 对于已线索化二叉树如何识别指针域是指向孩子还是其后继结点?\n**增加标志域：**LeftThread和RightThread（0指向孩子指针，1指向前驱/后继指针）\n28. 树的几种存贮结构(双亲表示法、孩子表示法、孩子兄弟表示法)的优缺点，各自适应的运算。\n**双亲表示法：**便于查找双亲，缺点：找孩子难\n**孩子表示法：**便于涉及到孩子的操作，缺点：找双亲难\n**孩子兄弟法：**操作容易，缺点：破坏了树的层次\n29. 哪种存贮结构可将森林转为二叉树。对此种结构的各个域给予注释。说明在这个结构中怎样找到森林的n棵树。\n**孩子兄弟表示法，**左指针是第一个孩子，右指针是第一个兄弟，最右的为第n棵树\n30. 树的先根遍历、后根遍历对应其二叉树的哪种遍历，森林的先根遍历、中根遍历对应其二叉树的哪种遍历?\n（先根对应先序，剩下的凑合一下）\n树的先根遍历 → 二叉树的先序遍历；\n树的后根遍历 → 二叉树的中序遍历；\n森林的先根遍历 → 二叉树的先序遍历；\n森林的中根遍历 → 二叉树的中序遍历。\n31. （送命题）写算法求树中结点的度；树的度；树中的叶子结点数；树中的非终端结点数；树中某结点的兄弟、祖先、子孙、层次、堂兄弟；树的高度；森林中树的数目。（默认为树是二叉树）\n求树中结点的度：（孩子表示法）求树的度只需要指针移动，度数递增就好\n**树的度：**先把每个结点的度数求出来，再把所有结点的度数总和求出来就好啦\n求树的叶子结点的个数：（下面这个只是求二叉树的）\nint Leaf_Count(Bitree T) {*//求二叉树中叶子结点的数目* if(!T) return 0; //空树没有叶子 else if(!T-\u0026gt;lchild\u0026amp;\u0026amp;!T-\u0026gt;rchild) return 1; //叶子结点 else return Leaf_Count(T-lchild)+Leaf_Count(T-rchild); //左子树的叶子数加上右子树的叶子数 } **求森林中树的数目：**就是右孩子循环过去，算出右孩子的数目，再加上本身根节点（即右孩子树+1）\n32. Huffman树能够解决的问题是什么？图示huffman编码过程。\n1）数据通信中的数据压缩编码问题\n2）构造Huffman树步骤：\n图示：\n33. 如何设计Huffman编译码器最有效？\n（看不懂就看上面的图）\n①根据给定的n个权值{w1,w2,\u0026hellip;\u0026hellip;wn}，构造n棵只有根 结点的二叉树，令其权值为wj；\n②在森林中选取两棵根结点权值最小的树作左右子树，构造一棵新的二叉树，置新二叉树根结点权值为其左右子树根结点权值之和；\n③在森林中删除这两棵树，同时将新得到的二叉树加入森林中重复上述两步，直到只含一棵树为止，这棵树即哈夫曼树。\n34. 何为完全图、稀疏图、稠密图。\n**完全图：**即一个图中的任意一个顶点都与其余所有顶点相邻（有连线）。对有向图来说，边数为n(n-1),对无向图来说,边数为n(n-1)/2\n稀疏图和稠密图没有明确量化定义\n**稀疏图：**有很少条边的图(e\u0026lt;\u0026lt;n ) **稠密图：**有很多条边的图\n35. 写算法求无向图中结点的度；有向图中结点的入度和出度。（邻接矩阵存图）\n** 无向图：邻接矩阵的一行或一列的数值和代表对应定点的度**。\n** 有向图：邻接矩阵的对应顶点的行代表出度，列代表入度**。\n36. 图的邻接矩阵、邻接表存贮结构各自优缺点，适应运算。\n**数组表示法（邻接矩阵表示法）：**二维数组存储图\n优点：容易求各个顶点的度\n缺点：当图为稀疏图时浪费空间\n邻接表表示法：\n优点：容易找到第一个邻接点和下一个邻接点\n缺点：不方便找一个结点的入度\n37. 最小代价生成树的实际应用背景。\n要在n个城市间建立通信联络网，顶点表示城市，权表示城市间建立通信线路所需花费代价，希望找到一棵生成树，它的每条边上的权值之和（即建立该通信网所需花费的总代价）最小\u0026mdash;\u0026mdash;\u0026mdash;最小代价生成树\n38. 什么图适合用Prim算法求最小代价生成树，什么图适合用 Kruskal算法求最小代价生成树。\nPrim算法（稠密图，n\u0026gt;47）Kruskal算法（稀疏图）\n39. 图示用 Prim算法及 Kruskal算法求最小代价生成树过程。\n（看作业题7.5）\nPrim算法\u0026mdash;\u0026mdash;加点法，时间复杂度O(n2)\n从某顶点开始，找其相邻边中权值最小的边所连另一个顶点，再找与这两个顶点相邻边中权值最小的边所连第三个顶点，重复，扩展到所有顶点。\nKruskal算法\u0026mdash;\u0026mdash;加边法,时间复杂度与边相关\n先将所有顶点都看作无边的非连通图，选择各顶点间最小边做连通分量，直到所有顶点都在同一个连通分量上。\n40. 举例简述\u0026quot;拓扑排序\u0026quot;所解决的实际问题。\n流程图，施工流程图，课程决定的优先权\n41. 请图示\u0026quot;拓扑排序\u0026quot;的过程。\n拓扑排序的方法：\n①在有向图中选一个没有前驱的顶点且输出之\n②从图中删除该顶点和所有以它为尾的弧\n③重复上述两步，直至全部顶点均已输出；或者当图中不存在无前驱的顶点为止\n42. 举例简述\u0026quot;关键路径\u0026quot;所解决的实际问题。\n一个工程的并行的进行过程\n（1） 完成整个工程至少需要多少时间；\n（2） 哪些活动是影响工程的关键。\n43. 最短路径的两个算法是什么？\n迪杰斯特拉(Dijkstra)算法和弗洛伊德(Floyd)算法\n（看作业7.6和PPT第7章第100-117页）\n迪杰斯特拉(Dijkstra)算法\n**按路径长度递增次序产生最短路径，**求从某个源点到其余各顶点的最短路径；\nvoid ShortestPath_DIJ( MGraph G, int v0, PathMatrix \u0026amp;pre, ShortPathTable \u0026amp;D) {*// 用Dijkstra 算法求有向网G的v0顶点到其余顶点v的最短路径pre[v]及其带权长度D[v]* // final[v]为TRUE当且仅当v∈S, 即已经求得v0到v的最短路径 for (v=0;v\u0026lt;G.vexnum;++v) { pre[v] = -1; final[v]=FALSE; D[v]=G.arcs[v0][v]; if (D[v]\u0026lt;INFINITY) pre[v]=V0; } //for D[v0]=0; final[v0]=TRUE; //初始化，v0顶点属于S集 //开始主循环，每次求得v0到某个v顶点的最短路径，并加v到S集 for (i=1;i\u0026lt;G.vexnum; ++i){ // 其余G.vexnum-1个顶点 min=INFINITY; // 当前所知离v0顶点的最近距离 for (w=0;w\u0026lt;G.vexnum; ++w) if(!final[w]) if(D[w]\u0026lt;min) {v=w; min=D[w];} //w顶点离v0顶点更近 final[v]=TRUE; //离v0顶点最近的v加入S集 for (w=0; w\u0026lt;G.vexnum; ++w) // 更新当前最短路径及距离 if(!final[w] \u0026amp;\u0026amp;(min+G.arcs[v][w]\u0026lt;D[w])) { //修改D[w]和pre[w], w∈V-S D[w]=min+G.arcs[v][w]; pre[w]=v; } //if } //for } //ShortestPath_DIJ 弗洛伊德(Floyd)算法 逐个顶点试探法，求从某个源点到其余各顶点的最短路径； void shortestPath_FLOYD(MGrgph G, PathMatrix \u0026amp;path ,DistancMatrix \u0026amp;length) { //用Floyd算法求得有向网G中各对顶点v和w之间的最短路径 Path[v][w]及其带权 //长度length[v][w]。 path[i][j]是相应路径上顶点 j 的前一顶点的顶点号, for ( int i = 0; i \u0026lt; n; i++ ) //矩阵length与path初始化 for ( int j = 0; j \u0026lt; n; j++ ) { length[i][j] = G.arcs [i][j]; if ( i \u0026lt;\u0026gt; j \u0026amp;\u0026amp; length [i][j] \u0026lt; INFINITY ) path[i][j] = i; // i 到 j 有路径 else path[i][j] = -1; // i 到 j 无路径 } for ( int k = 0; k \u0026lt; n; k++ ) //产生length(k)及path(k) for ( i = 0; i \u0026lt; n; i++ ) for ( j = 0; j \u0026lt; n; j++ ) if ( length[i][k] + length[k][j] \u0026lt; length[i][j] ) { length [i][j] = length [i][k] + length [k][j]; path[i][j] = k; } //缩短路径长度, 绕过 k 到 j } 44. 简述静态查找和动态查找？\n**静态查找：**基于线性表的查找 **动态查找：**基于树的查找\n查找表（Search Table）：是一种以集合为逻辑结构、以查找为核心运算的数据结构。\n**静态查找表：只对查找表进行查询某个特定的数据元素或某个特定数据元素的各种属性的操作。**如：查询成绩表中是否有某学生或该学生某门课程的成绩。\n**动态查找表：对查找表进行查找，找不到就插入某个数据元素的操作。**如：查找某个学生信息，找不到就插入。\n45. 顺序查找、折半查找、分块查找算法适合的关键字结构和存贮结构。\n**顺序查找：**对存储结构和关键字排列方式没有特殊要求\n**折半查找：**关键字整体有序，只适合顺序存储的有序表\n**分块查找：**关键字局部有序，即分块有序，对存储结构为顺序和线性链表的均适用\n46. 怎样从二叉排序树得到有序表。\n中序遍历\n47. 已知长度为n 的表按表中元素顺序构造平衡二叉排序树，图示构造过程。\nhttps://blog.csdn.net/lemon_tree12138/article/details/50393548\n先看上面的博客搞清楚LL、RR、LR、RL四种骚操作，再看解释\n树的平衡性简单来说就是左边\u0026lt;中间\u0026lt;右边,插入新的点可能会破会这个平衡，就要通过上面的四个骚操作来恢复平衡。\n48. 解释构造平衡二叉排序树的过程中做各种旋转后仍能满足二叉排序树的特性。\n因为记录的是导致整棵平衡二叉树失去平衡的那棵子树的根节点，因此只要调节这棵子树便能让整棵平衡二叉树树平衡\n49. 各种查找算法的平均时间复杂度。\n50. 简述Hash查找的构建过程；为一组关键字构造哈希函数并建立哈希表。\n1、分析数据；\n2、构建合适的哈希函数及解决冲突的方法；\n3、用哈希函数对数据计算存储位置，存储数据；\n4、对哈希表进行查找；\n51. 指出希尔排序，归并排序，快速排序，堆排序，基数排序中稳定的排序，对不稳定的举出反例。\n52. 堆排序算法选用什么样的存贮结构，按此算法得到的有序表是递增还是递减的。图示建堆过程。\n一维数组存储；小顶堆（递增）；大顶堆（递减）\n53. 藉助于\u0026quot;比较\u0026quot;进行排序的算法在最坏情况下能达到的最好的时间复杂度是什么?\nn*log2n\n54. 指出直接插入排序，冒泡排序，快速排序, 堆排序，基数排序算法各适合的关键字结构。\n直接插入： 基本有序\n冒泡排序： 基本有序\n快速排序： 关键字混乱，均匀随机分布\n堆排序： 数据非常大，需要常常获得最大和最小值\n基数排序： 多关键字排序\n55. 指出各种排序算法的平均时间复杂度、最坏情况的时间复杂度。\n","permalink":"http://localhost:9011/20180630-data-structure/","summary":"\u003cp\u003e汕头大学于津老师的数据结构复习题，对应教材是清华的严蔚敏老师的《数据结构》。\u003c/p\u003e","title":"数据结构复习提纲"},{"content":"一般来说，修电脑的各位老铁们，在制作PE启动盘的时候，就会需要格式化U盘，然后将PE所需要的文件刻录进U盘里面。\n对于像小七这样喜欢使用U盘刻录安装原生纯净的操作系统的人，就需要准备两个U盘，一个用来制作PE工具盘，一个用来安装操作系统，现在小七给大家介绍一个方法，让一个U盘实现双启动或者三启动。\n此处先上一张成品图\n图中三个盘其实都是一个U盘的，第一个里面的是PE工具箱，第三个是原装win10刻录文件，第二个则可以当作普通U盘使用。\n这样的好处是一个U盘双启动，平时放的文件和工具箱/win10镜像的文件不容易混淆。\n有些同学说在刻录win10镜像的盘符里面用文件夹存放文件也可以，这个方法虽然简单，但是作为启动盘一般都是FAT32格式，是无法存放单个文件大小超过4G的文件的。\n（PE工具箱也可以安装操作系统，习惯用PE安装系统的朋友可以忽略这篇推文）\n准备工作 原装系统镜像（此处使用最新版的64位win10，可在msdn itellyou中下载）\nPE工具箱的ISO文件（此处使用微PE2.0版本，后台回复PE有惊喜）\nUltra ISO软件（试用版即可）\n一个8G以上的U盘（此处使用一个32G的USB3.0的U盘）\n一台能用的正常的电脑\n刻录原版win10镜像 首先，安装并打开UltraISO，点击上方的菜单栏中的 文件 —\u0026gt; 打开，然后选择win10镜像文件\n然后再点击上方的菜单栏中的** 启动 —\u0026gt; 写入硬盘映像**\n在弹出的界面中，检查下图红框的两项：\n硬盘驱动器要确定是自己用来制作的U盘\n隐藏启动分区里面要选择高端隐藏\n接下来会格式化U盘，请务必要备份U盘中的重要文件\n刻录完成之后，打开我的电脑，会发现U盘已经被分成两个分区\n如果不需要PE工具箱，到这里就可以停了，因为到这里已经制作了一个正常的win10安装盘，需要重装系统的话，用这个U盘就已经OK了。\n利用磁盘管理进行二次分区 右键我的电脑，点击管理，在左边找到磁盘管理，然后在下方找到自己的U盘，删除掉上图中的20多G的磁盘\n然后新建两个磁盘，一个大小为800M，文件系统类型记得选择FAT32，否则PE工具箱会无法正常启动\n剩下的全部分到另一个盘，文件系统类型随意选择**（建议exFAT）**。\n最后应该和下图类似。\n解压ISO文件制作PE工具箱 解压PE工具箱的ISO文件，PE工具箱的文件一般都是两三百MB，将解压后的文件全部复制到容量比较小的磁盘中\n测试 将U盘插入电脑，按下开机键的时候按ESC或者F1/F2/F9/F12等按键即可进入启动项选择菜单（不同电脑情况不同），如果像下图这样出现了两个一样的启动项，那么就制作成功。\n最后，如何区分哪个才是PE，哪个才是WIN10呢？\n你可能会说，直接点进去试一下不就行了。\n对的，没错这确实可以，但是这样的话就太没有技术含量了。\n我们还是打开我的电脑中的磁盘管理，会发现左边的是PE，右边的是WIN10，所以上图的第一个就是PE，第二个才是win10。\n","permalink":"http://localhost:9011/20180318-dou-boot-udisk/","summary":"\u003cp\u003e一般来说，修电脑的各位老铁们，在制作PE启动盘的时候，就会需要格式化U盘，然后将PE所需要的文件刻录进U盘里面。\u003c/p\u003e\n\u003cp\u003e对于像小七这样喜欢使用U盘刻录安装原生纯净的操作系统的人，就需要准备两个U盘，一个用来制作PE工具盘，一个用来安装操作系统，现在小七给大家介绍一个方法，让一个U盘实现双启动或者三启动。\u003c/p\u003e","title":"双启动U盘"},{"content":"有很多同学会有给笔记本电脑外接键盘的需求， 但是如果直接将外接键盘放到内置键盘上面容易误触，小七在这里跟大家分享一个通过CMD命令禁用笔记本内置键盘的方法。\n禁用 首先，以管理员身份运行CMD。\n然后输入这条命令：\nsc config i8042prt start= disabled 重启之后就可以禁用笔记本内置键盘的服务。\n恢复 需要恢复的话也很简单：\nsc config i8042prt start= auto 重启之后即可启用笔记本内置键盘的服务。\n这个方法的好处在于简单快捷稳定。不需要什么拆机拔笔记本键盘排线，卸载键盘驱动/安装错误键盘驱动等等一系列骚操作。\n","permalink":"http://localhost:9011/20180225-disable-keyboard/","summary":"\u003cp\u003e有很多同学会有给笔记本电脑外接键盘的需求， 但是如果直接将外接键盘放到内置键盘上面容易误触，小七在这里跟大家分享一个通过CMD命令禁用笔记本内置键盘的方法。\u003c/p\u003e","title":"禁用笔记本内置键盘"},{"content":"本周，英特尔宣布：将从今年（2018）开始添加对802.11ax的支持，包括路由芯片和消费级零售产品。其实早在去年，通信行业巨头博通就发布了三款支持802.11ax的芯片BCM43684/43694/4375，高通也宣布了IPQ8074/QCA6290。至此，三家芯片巨头都表明了对802.11ax协议的支持，802.11ax也终于是坐稳了第六代Wi-Fi协议的位置了。\n本文将介绍历代Wi-Fi协议，按照顺序一次为802.11a/b/g/n/ac/ax，本文旨在让不了解Wi-Fi协议的读者对其能有一个粗略的认识，并不涉及过深的专业知识，同时文章本身不短，阅读耗时较长，请各位读者耐心阅读。\n小七注：802.11系列协议应用非常广泛，协议本身也非常复杂庞大，本文只设计上述的6种协议和802.11ad协议共计7种。\n什么是Wi-Fi？ 既然是讲Wi-Fi协议，那么首先讲的必然就应该是Wi-Fi了。Wi-Fi其实说白了就是一种让包括电脑手机平板在内的电子设备能够连接到一个无线网络的技术。但很多人都不知道的是，Wi-Fi其实本身是一个品牌或者说是商标，由Wi-Fi联盟所持有。Wi-Fi联盟的前身是1999年成立的无线以太网兼容性联盟WECA（Wireless Ethernet Compatibility Alliance）。而Wi-Fi联盟一直在使用的这个802.11系列的无线通信协议标准，是由IEEE下属的802.11工作组所制定的。\n小七注：电气和电子工程师协会( IEEE，全称是Institute of Electrical and Electronics Engineers)是一个国际性的电子技术与信息科学工程师的协会，是目前全球最大的非营利性专业技术学会，其会员人数超过40万人，遍布160多个国家。IEEE致力于电气、电子、计算机工程和与科学有关的领域的开发和研究，在太空、计算机、电信、生物医学、电力及消费性电子产品等领域已制定了900多个行业标准，现已发展成为具有较大影响力的国际学术组织。\n看到这里是不是觉得很懵？简单来说就是由IEEE内的802.11工作组制定802.11系列标准，而Wi-Fi联盟对使用802.11系列标准的设备进行认定，符合Wi-Fi联盟的认定标准的设备就可以打上Wi-Fi的这个logo。\n下面进入正文。\n11——过于平庸的一代 二战之后，世界科技进入迅猛发展时期，人们对于无线通讯的需要开始爆发性地增长，IEEE在20世纪90年代初成立了专门的802.11工作组，专门研究和定制WLAN(无线局域网)的标准协议，并在1997年6月推出了第一代WLAN协议——IEEE 802.11-1997。\n小七注：此处的802.11指的是IEEE制订的第一代协议，并非是整个系列协议，在802.11之后的协议都加入了字母后缀来进行区分。\n作为IEEE最初制定的一个无线局域网标准，802.11协议定义了物理层工作在ISM的2.4G频段，数据传输速率设计为2Mbps。很遗憾的是，由于它在传输速度和传输距离上的表现都不尽如人意，因此并未被规模使用。\n3、802.11a——生不逢时的一代 1999年．IEEE吸取了上一次的教训，这一次就直接下了猛药。在制定802.11a标准的时候，直接将频段定在了5GHz（频率越高最高传输速度越快），物理层的最高速率也随之水涨船高到了54Mbps。相比前一代来说，不可谓不给力。但是，802.11a协议也并没有被市场认可，相对来说表现的更出色的反而是几乎和它同时制订的802.11b协议。\n尽管2003世界无线电通信会议让802.11a在全球的应用变得更容易，不同的国家还是有不同的规定支持。美国和日本甚至都已经出现了相关规定对802.11a进行了认可，但是在其他地区，如欧盟却因为标准的问题被禁止使用。再加上802.11a产品中5GHz的组件研制成功太慢，等其开始大规模推广的时候，市场早已被大批的802.11b产品占领，802.11a没有被广泛的采用。再加上802.11a的一些弱点，和一些地方的规定限制，使得它的使用范围更窄了。\n4、802.11b——奠定基础的一代 802.11b协议可以说是802.11a是同胞兄弟了。但它本身却是基于2.4GHz频率，同时最大的传输速度相比802.11a来说也只有11Mbps。11Mbps的传输速率在现在看来肯定算不了什么，但在2000年的时候，虽然不是翘楚，但也已经能够满足大部分人的需求了。\n更何况基于2.4GHz的802.11b在传输距离和穿墙能力上本来就比基于5GHz的802.11a协议要有优势（高频率波传输距离和穿墙能力较低频率波差），加上当时802.11a的核心芯片研发进度缓慢，802.11b就此抓住了机会，占领了市场，为日后称霸天下打下了坚实的基础。\n5、802.11g——融合前人的一代 时间来到了2003年7月，IEEE制订了第三代Wi-Fi标准：802.11g。（为什么不是C呢？因为802.11协议还应用在其他的很多领域，有些字母被用了，就只能排到g了）\n802.11g继承了802.11b的2.4GHz频段和802.11a的最高54Mbps传输速率。同时，它还使用了CCK技术后向兼容802.11b产品。此时开始，IEEE在制订每一代新协议的时候都会将后向兼容考虑进去，毕竟换了新路由器旧手机就因为不支持新协议而连不上Wi-Fi这种情况谁都受不了。\n说到这里，就还要再提一下在802.11a和802.11g上都有使用到的一种技术：OFDM。\nOFDM(Orthogonal Frequency Division Multiplexing) 即正交频分复用技术，是由MCM（Multi-Carrier Modulation，多载波调制）发展而来的一种实现复杂度低、应用最广的一种多载波传输方案。**OFDM主要思想是：将信道分成若干正交子信道，将高速数据信号转换成并行的低速子数据流，调制到在每个子信道上进行传输。**正交信号可以通过在接收端采用相关技术来分开，这样可以减少子信道之间的相互干扰(ISI) 。每个子信道上的信号带宽小于信道的相关带宽，因此每个子信道上可以看成平坦性衰落，从而可以消除码间串扰，而且由于每个子信道的带宽仅仅是原信道带宽的一小部分，信道均衡变得相对容易。\n上面这段很难理解？没关系，看小七打个比喻你们就懂了。\n假设我们现在有很多车要从A地到B地，没有使用OFDM技术之前，路是一条路，所有的车四处乱开，横冲直撞，结果谁都快不了。现在使用了OFDM技术，将一条大路划分为很多个车道，大家都按照车道驾驶，这样既可以提高速度，又能减少车与车之间的干扰。同时这条道的车多了，就匀一点到那条车少的道上去，管理上也方便很多。\nOFDM技术也因此被应用在之后的每一代Wi-Fi协议中。\n6、802.11n——初露锋芒的一代 如果说802.11b是奠定了整个帝国的基础的一代，那么802.11n一定是给帝国开疆扩土的一代。\n时间继续推进，这时的互联网已经开始出现了在线图片、视频、流媒体等服务，而随着YouTube、无线家庭媒体网关、企业VoIP Over WLAN等应用对WLAN技术提出了越来越高的带宽要求，传统技术802.11a/g已经无法支撑。用户需求呼唤着全新一代WLAN接入技术。\n2009年，IEEE宣布了新的802.11n标准。传输速率最高可达600Mbps。\n但是，802.11n协议还是基于2.4GHz频段，速度怎么突然就快了这么多呢？正所谓事出反常必有妖，而这背后的“妖”，就是MIMO、波束成形和40Mhz绑定。\n6.1 MIMO MIMO（Multiple-Input Multiple-Output）的中文名称为多输入多输出技术是指在发射端和接收端分别使用多个发射天线和接收天线，使信号通过发射端与接收端的多个天线传送和接收，从而改善通信质量。\nMIMO技术最早是由马可尼于1908年提出的，它利用发射端的多个天线各自独立发送信号，同时在接收端用多个天线接收并恢复原信息，就可以实现以更小的代价达到更高的用户速率。MIMO可大大提高网络传输速率、覆盖范围和性能。当基于MIMO而同时传递多条独立空间流时，系统的吞吐量可成倍地提高。\n简单来说，MIMO技术就是在信号的发射源和接收源都安装了多个天线，通过堆天线的方式来实现更高的传输速率，因此现在的买路由器看天线数量这一个说话虽然不可靠，但也不是没有历史渊源的。\n▲通过MIMO传递多条空间流（图片来自网络）\nMIMO系统支持空间流的数量取决于发送天线和接收天线的最小值。如发送天线数量为3,而接收天线数量为2，则支持的空间流为2。MIMO/SDM系统一般用“发射天线数量×接收天线数量”表示。如上图为2*2 MIMO/SDM系统。显然，增加天线可以提高MIMO支持的空间流数。但是综合成本、实效等多方面因素，当时业界的WLAN AP都普遍采用3×3的模式。而现在的旗舰级路由器都轻松的堆到8×8或者更高。\n▲图为 MIMO利用多径传输数据（图片来自网络）\n6.2 波束成形 而至于波束成形技术，它本身并不是什么新名词，波束成形是天线技术与数字信号处理技术的结合，目的用于定向信号传输或接收。在20世纪60年代，波束成形技术就已经在军事应用上得到了相当高的重视。\n只不过，由于早年半导体技术还处在微米级，所以它没有在民用通信中发挥到理想的状态。\n而发展到无线通讯阶段，特别是应用在消费级产品中，信号传输距离和信道质量以及无线通信的抗干扰问题便成为瓶颈。提高传输速率是WLAN技术发展历程的关键。802.11n主要是结合物理层和MAC层的优化，来充分提高WLAN技术的吞吐。此时，波束成形又有了用武之地。\n波束成形技术的具体原理很复杂，小七在这里用图片给大家简单展示一下，波束成形就是将原本发散的波聚合，再往指定的方向发送，从而提高传输距离。\n▲波束成形技术增加传输距离的示意图（图片来自网络）\n但是波束成形技术固然能改善系统性能，增加接收距离，但同时也会增加设备成本和功耗。在多天线都处于连接的状态下，即使在严重的衰落情况下，它提供的信号增益也可获提高，但要求信号处理能力也要很强。所以，多天线带来的问题是要求数据处理速度高，控制成本，并降低功耗。\n6.3 40Mhz绑定 事实上，802.11n协议还使用了40Mhz绑定技术。这个技术最容易理解，对于无线技术来说，提高所用频谱的宽度，可以最为直接地提高吞吐。就好比是马路变宽了，车辆的通行能力自然提高。传统802.11a/g使用的频宽是20MHz，而802.11n支持将相邻两个频宽绑定为40MHz来使用，所以可以最直接地提高吞吐。\n▲图左为802.11a/g，图右为802.11n\n6.4 MCS 802.11n引进了如此多的新技术，导致它的速率也会因为配置方法不同而不同。在802.11a/b/g时代，配置AP工作的速率非常简单，只要指定特定radio类型(802.11a/b/g) 所使用的速率集，速率范围从1Mbps到54Mbps,一共有12种可能的物理速率。到了802.11n时代，由于物理速率依赖于调制方法、编码率、空间流数量、是否40MHz绑定等多个因素。这些影响吞吐的因素组合在一起，将产生非常多的物理速率供选择使用。\n对此，IEEE直接推出了MCS (Modulation Coding Scheme)，MCS可以理解为将上述影响速率因素的完整组合，每种组合用整数来唯一标示。给每种情况标码，然后直接看对应的MCS码就可以知道准确的速率。\n6.5 802.11n小结 总的来说，MIMO和40Mhz绑定技术使得传输速率大大提升，而波束成形则增大了传输距离。\n7、802.11ac——锋芒毕露的一代 随着时代的继续发展，人们身边拥有着越来越多的无线设备，**而2.4GHz这个频段，因为本身的优越性，被各种协议使用（常见的蓝牙4.0系列协议，无线键鼠等），已经变得拥挤不堪，**IEEE此时就将新的第五代Wi-Fi协议制订在了5GHz的频段上。现在说的很多双频Wi-Fi，其实就是2.4GHz和5GHz的混合双频Wi-Fi，而这种路由器常见的四天线设计，一般都是两根天线基于2.4GHz，两根基于5GHz。\n802.11ac在提供良好的后向兼容性的同时，把每个通道的工作频宽将由802.11n的40MHz，提升到80MHz甚至是160MHz，再加上大约10%的实际频率调制效率提升，最终理论传输速度将由802.11n最高的600Mbps跃升至1Gbps。当然，实际传输率可能在300Mbps～400Mbps之间，接近目前802.11n实际传输率的3倍(目前802.11n无线路由器的实际传输率为75Mbps～150Mbps之间)，完全足以在一条信道上同时传输多路压缩视频流。\n7.1 MU-MIMO 实际上，802.11ac协议还分为wave1和wave2两个阶段,两者的主要区别就在于后者提升多用户数据并发处理能力和网络效率。而这背后的功臣，就非MU-MIMO莫属了。\n前面已经跟大家介绍过了，IEEE在802.11n协议时代就引入了MIMO技术，而MU-MIMO技术可以理解为它的升级版或者是多用户版本。\n为什么这么说呢？看下面的图片大家就明白了。\n▲高通官方展示MU-MIMO技术所用的图片\nMU-MIMO是Multi-User Multiple-Input Multiple-Output（多用户-多输入多输出）的英文缩写。顾名思义，MU-MIMO能让路由器同时和多个设备进行沟通，这极大的改善了网络资源利用率。\n通俗来说，以前在802.11n上面的MIMO只能说是SU-MIMO（Single-User），传统的SU-MIMO路由器信号呈现一个圆环，以路由器圆心，呈360度向外发射信号，并依据远近亲疏，依次单独与上网设备进行通讯。当接入的设备过多时，就会出现设备等待通讯的情况，网络卡顿的情况就由此产生；更为严重的是，这种依次单独的通讯，是基于设备对AP（路由器或热点等）总频宽的平均值。也就是说，如果拥有100MHz的频宽，按照“一次只能服务一个”的原理，在有3个设备同时接入网络的情况下，每个设备只能得到约33.3MHz频宽，另外的66.6MHz则处于闲置状态。即在同一个Wi-Fi区域内，连接设备越多宽频被平均得越小，浪费的资源越多，网速也就越慢。\n▲图为SU-MIMO（左）和MU-MIMO（右）的对比\nMU-MIMO路由器则不同，MU-MIMO路由的信号在时域、频域、空域三个维度上分成三部分，就像是同时发出三个不同的信号，能够同时与三部设备协同工作；尤其值得一提的是，由于三个信号互不干扰，因此每台设备得到的频宽资源并没有打折扣，资源得到最大化的利用，从路由器角度衡量，数据传输速率提高了3倍，改善了网络资源利用率，从而确保Wi-Fi无间断连接。\n**MU-MIMO技术就赋予了路由器并行处理的能力，**让它能够同时为多台设备传输数据，极大地改善了网络拥堵的情况。在今天这种无线联网设备数量爆发式增长的时代，它是比单纯提高速率更有实际意义的。\n世界上首台支持MU-MIMO的路由器是Linksys EA8500于2015年发布，采用的是全高通的MU-MIMO解决方案（Qualcomm MU | EFX）。现在MU-MIMO已经是旗舰级路由器的标配了，而那些写着ac双频路由器却不支持MU-MIMO技术的，都只能算是残缺的ac双频路由器或者是ac wave1阶段的路由器。\n这里放一张到ac为止各代协议的主要参数对比图（图片来自网络）：\n8、802.11ad——先天不足的一代 在确定第六代的Wi-Fi协议标准的时候，有一段时间，大家都认为会是802.11ad协议，说这个名字大家可以不太熟悉，它另一个名字叫WiGig。\n相比我们熟知的802.11n(工作在2.4GHz和5GHz频段)和802.11ac(工作在5GHz频段)，**802.11ad则是工作在60GHz频段，且无线传输速率可高达7Gbps!**当然， 802.11ac标准也可以通过堆BUFF（8x8 MIMO、256 QAM调制和信道绑定4个40 MHz信道）达到7Gbps的理论无线传输速率；但是，11ad达到7Gbps的速度，仅需通过一个空间流、64QAM调制和单个信道即可实现。此外，802.11ad还在容量、功耗和延迟方面有着11ac无法比拟的优势，特别是在延迟方面，其延迟通常仅有10微秒，堪比有线!\n但可惜，802.11ad协议有着它的先天不足——60GHz，这么高的频率注定它的传输距离和穿墙能力弱到不堪一击，而在一些知名评测媒体的评测中，人们惊人地发现只需要一个纸皮箱就能隔绝802.11ad协议路由器的信号。真是可谓成也萧何，败也萧何。\n9、802.11ax——肩负使命的一代 802.11ax协议基于2.4GHz和5GHz两个频段，对，就是两个频段，并非是ac双频路由器那样不同的频段对应不同的协议，ax协议本身就支持两个频段。这显然迎合了当下物联网、智能家居等发展潮流。对于一些对带宽需要不高的智能家居设备，可以使用2.4GHz频段去连接，保证足够的传输距离，而对于需要高速传输的设备，就使用5GHz频段。这看起来和现在的ac双频路由器是一样的，但实际上，ax作为第六代Wi-Fi协议的扛把子，可不只这两把刷子。\n**802.11ax又被称为“高效率无线标准”（High-Efficiency Wireless，HEW），**将大幅度提升用户密集环境中的每位用户的平均传输率，即在高密环境下为更多用户提供一致且稳定的数据流（平均传输率），将有效减少网络拥塞、大幅提升无线速度与覆盖范围。其实，设计802.11ax的首要目的是解决网络容量问题，因为随着公共Wi-Fi的普及，网络容量问题已成为机场、体育赛事和校园等密集环境中的一个大问题。\n此处要介绍两个新技术，上行MU-MIMO和OFDMA。\n9.1 上行MU-MIMO MU-MIMO技术在前面已经提到过，802.11ac协议中的MU-MIMO技术只是单纯的下行MU-MIMO，只有在路由器给设备传输数据的时候才可以用，而如今随着智能设备的发展，人们对于上行速率的要求也在提高，传个超清视频图片什么的已经是家常便饭了。上行MU-MIMO技术就是为此而生，改善了设备在向路由器传输数据时的拥堵情况，提高了网络资源利用率。\n9.2 OFDMA 802.11ax与以前的无线局域网(WLAN)系统相比最大的变化在于其是采用了“正交频分多址接入”(orthogonal frequency division multiple access, OFDMA)。\n在OFDM系统中，用户占用了整个信道。随着用户数量的增多，用户之间的数据请求会发生冲突，从而造成瓶颈，从而导致当这些用户在请求数据（特别是在流式视频等高带宽应用中）时，服务质量较差。\n而在OFDMA中，用户仅在规定时间内占用子载波的一个子集。OFDMA要求所有用户同时传输，因此每个用户都需要将其数据包缓冲为相同的规定比特数，这样无论数据量有多少所有用户都能在时间上保持一致。此外， OFDMA AP可根据用户对带宽的需求来动态地改变用户所占用频谱的数量。例如，相比较对实时性能要求不高的电子邮件，流媒体视频用户需要更多子载波（频谱）。\n不理解的话我们看下图：\n▲用户在ODFM中占据整个信道，而在“正交频分多址接入”(OFDMA)中不是\n我们还是用回马路开车的例子，假设现在有一条马路有三条车道，现在有甲乙丙三个车队要走这条路（每队都要走一个小时，走前需要半个小时准备），如果按照一次走一个车队的方法，甲乙丙依次走完需要四个半小时，而使用OFDMA技术，给他们一队一条道，则只需要三个半小时，省下了一个小时的准备时间。OFDM和OFDMA在用户数量少的时候差距可能不大，但是一旦用户数量多了起来，差距可就不是一星半点了。\nOFDMA一路走来，其实就是“从无到有，再从有到善用”的演变历程。\n9.3 802.11ax小结 实际上，802.11ax给我们带来的提升远不止此，其他的如更低的延迟、更精确的功耗控制等等也是不可忽略的一环。现在市面上使用802.11ax协议的产品并不是很多，只是零星的几款，**但是好在它的太子身份已经确定，登基只是早晚的问题。**而按照目前的进度来看，小七预计2019年802.11ax的产品就能实现不错的普及率。\n","permalink":"http://localhost:9011/20180109-wifi-dev/","summary":"\u003cp\u003e本周，英特尔宣布：将从今年（2018）开始添加对802.11ax的支持，包括路由芯片和消费级零售产品。其实早在去年，通信行业巨头博通就发布了三款支持802.11ax的芯片BCM43684/43694/4375，高通也宣布了IPQ8074/QCA6290。至此，三家芯片巨头都表明了对802.11ax协议的支持，802.11ax也终于是坐稳了第六代Wi-Fi协议的位置了。\u003c/p\u003e","title":"第六代Wi-Fi协议的前世今生"},{"content":"汕大计算机系2017年的计组课最后期末的复习提纲，我个人整理各类资料和PPT总结，有部分可能会有错漏或不足，这里分享给有需要的同学。\n01讲 词汇 ACM：Association for Computing Machinery，美国计算机协会\nENIAC：Electronic Numerical Integrator And Computer，电子数字积分计算机\nALU：算术逻辑部件，Arithmetic and Logical Unit\nCP：时钟，Clock Pulse\nISA：Instruction Set Architecture，指令集体系结构（指令集架构）\nLSIC：大规模集成电路(Large Scale Integrated circuits\nMOOC：massive open online courses,大型开放式网络课程\n两个PC：Person Computer, Program Counter\nPLC：可编程逻辑控制器,Programmable Logic Controller\n问题: 1.计算机系统是?\n计算机系统＝硬件/固件＋软件\n2.固件是什么？\n①\t固化的程序（firmware），例如计算机主板上的BIOS。 ②\t固件一般存储于设备中的电可擦除只读存储器EEPROM(Electrically Erasable Programmable ROM)或FLASH芯片中，一般可由用户通过特定的刷新程序进行升级的程序。 ③\t固件常见于：手机、数码相机、mp3、mp4、路由器、交换机、U盘、主板的BIOS（BIOS就是一种固件）、显卡的BIOS。\n3.什么是“软件摩尔定律”？\n软件摩尔定律：每18个月，软件体积大一倍，效率差一半。\n4.冯·诺依曼体系和哈佛总线体系的区别？\n程序空间和数据空间是否是一体的。 冯·诺依曼结构数据空间和地址空间不分开，哈佛结构数据空间和地址空间是分开的。\n5.冯.诺依曼计算机体系的主要特点 ?\n①\t计算机由运算器、控制器、存储器、输入设备和输出设备五部分组成； ②\t采用存储程序的方式； ③\t数据以2进制表示。\n6.计算机系统结构概念的实质?\n确定计算机系统中软硬件的界面，界面之上是软件实现的功能，界面之下是硬件和固件实现的功能。\n02讲（数制和编码、整数的表示） 问题: 1.定点整数的原、反、补码表示?\n小数点位置约定再固定位置的数称为定点数；小数点位置约定为可浮动的数称为浮点数。对于定点整数，其小数点总是固定在数的最右边，因此可用定点整数来表示整数。\n03讲（浮点数-数据的宽度和存储） 词汇 IEEE：Institute of Electrical and Electronics Engineers，电气和电子工程师协会\nMSB(msb)，Most Significant Bit，最高有效位。或Most Significant Byte，最高有效字节。\nLSB(msb)，least significant bit，最低有效位。或least significant byte，最低有效字节。\n问题: 1.IEEE754标准32位浮点数的表示?\n2.大端方式与小端方式?\n大端是高低结合；大端方式将数据的最高有效字节存放在低地址单元中，将最低有效字节存放在高地址单元中，即数据的地址就是MSB所在的地址。 小端是高高结合；小端方式将数据的最高有效字节存放在高地址单元中，将最低有效字节存放在低地址单元中，即数据的地址就是LSB所在的地址。 最高有效位指数据最左边的一位数，最低有效位指最右边的那一位数 高地址是数字大的，低地址是数字小的。 04讲（数据的基本运算） 词汇 ASCII，ASCII（American Standard Code for Information Interchange，美国信息交换标准代码\nZF，零标志，Zero Flag\nOF，溢出标志，Overflow Flag\nCF，进/借位标志，Carry Flag\nSF，符号标志，Sign Flag\nFA，全加器，Full Adder\nHA，半加器，Half Adder\nRCA，行波进位加法器，ripple carry adder\nCAS，可控加减单元，Controllable Adder Subtracter\nCLA，先行进位发生器，carry look-ahead\n问题: 1.理解并简单解释本讲介绍的典型电路?\n2.补码加减法电路示意图?\n3.对2求补器电路?\n05讲（程序转换概述，初步认识ISA） 词汇 MAR（AR）：Memory Address Register，地址寄存器\nMDR(DR)： Memory Data Register，数据寄存器\nIR：Instruction Register，指令寄存器\nGPRs：General Purpose Registers，通用寄存器\nACC：Accumulator，累加器\nOP：操作码，Operation Code\nCISC：Complex Instruction Set Computer，复杂指令系统计算机\nRISC：Reduced Instruction Set Computer，精简指令系统计算机\nRTL：Register Transfer Level，寄存器传输级\n问题: 1.RR、RM、MM是什么?\nRR结构：寄存器-寄存器结构（Register-Register） RM结构：寄存器-存储器结构（Register-Memory） MM结构：存储器-存储器结构（Memory-Memory） 06讲（IA-32指令系统概述） 词汇 IA：Intel Architecture，Intel架构\n07讲（MIPS体系结构1） 词汇 MIPS：Microprocessor without Interlocked Pipeline Stages，无内部互锁流水级的微处理器\nMIPS：Million Instructions Per Second)，每秒处理百万指令数\n问题: 1.简单MIPS程序?\n08讲（MIPS体系结构2） 问题: 1.简单MIPS程序?\n2.伪指令和伪操作的区别?\n伪操作(directive)不会被编译器编译为机器指令， 伪指令(pseudo-instruction)会编译为机器指令。 09讲（ARM汇编） 词汇 ARM：Advanced RISC Machine，高级RISC处理机\nIP：知识产权，intellectual property\nGNU：通用公共许可证，GNU General Public License\n10讲（程序执行概述，CPU结构和功能） 词汇 PSW：Program Status Word，程序状态字\n问题: 1.现代CPU的组成？\n现代CPU一般由运算器、控制器、数据通路（datapath）和高速缓冲存储器（Cache）组成。\n——数据通路是指各部件之间通过数据线的相互连接。\n——选择什么样的数据通路，对于CPU的性能有很大的影响。\n2.CPU控制器的基本组成？\n程序计数器（PC） 指令寄存器（IR） 指令译码器（ID） 时序发生器 操作控制器 3.运算器的基本组成？\n算术逻辑单元（ALU） 累加寄存器（AC）或通用寄存器 数据寄存器（DR） 程序状态字寄存器（PSW） 4.\tCPU中的主要寄存器？\n指令寄存器（IR） 程序计数器（PC） 地址寄存器（AR） 数据寄存器（DR） 累加寄存器（ACC）或GPRs 程序状态字寄存器（PSW） 5.CPU的具体功能?\n指令控制：控制程序的顺序执行 操作控制：产生完成每条指令所需的控制命令 时间控制：对各种操作加以时间上的控制 数据加工：对数据进行算术运算和逻辑运算 中断处理：处理运行过程中出现的异常情况和特殊请求 11讲（datapath） 问题: 指令周期、机器周期、时钟周期?\n1.指令周期\n指令周期是取出一条指令并执行这条指令的时间。一般由若干个机器周期组成，是从取指令、译码到执行完所需的全部时间。\n**一条指令的执行过程包括3个基本步骤： **\n取指令：从存储器取出一条指令，该指令的地址由程序计数器PC给出。\n译码：对该指令的操作码进行译码分析，确定是哪一种指令，并转到这种指令对应的执行阶段。\n执行：按指令操作码的要求执行该指令。执行过程可能需要多步操作，控制器将为之形成完成该指令功能所需要的操作控制信号。执行完毕后，回到取指令阶段，去取下一条指令。如此反复，直到整个程序执行完。\n2.机器周期\n通常把一条指令周期划分为若干个机器周期，每个机器周期完成一个基本操作。 可以用主存的工作周期(存取周期)为基础来规定机器周期，比如，可以用CPU读取一个指令字的最短时间来规定机器周期 不同的指令，可能包含不同数目的机器周期。 一个机器周期中，包含若干个节拍脉冲或T脉冲。 机器周期的定义和规定，不同的计算机中规定不同 3.时钟周期\n在一个机器周期内，要完成若干个微操作。这些微操作有的可以同时执行，有的需要按先后次序串行执行。因而需要把一个机器周期分为若干个相等的时间段，每一个时间段称为一个节拍脉冲或T周期。 时钟周期通常定义为机器主频的倒数。 12讲（单周期及多周期CPU构造） 13讲（流水线技术1） 问题: 1.给出五段流水线示意图（段里面给出名称）?\n2.流水线冲突有哪几种情况（结构,数据,控制）?\n结构冲突：因硬件资源满足不了指令重叠执行的要求而发生的冲突。\n数据冲突：当指令在流水线中重叠执行时，因需要用到前面指令的执行结果而发生的冲突。\n控制冲突：流水线遇到分支指令和其他会改变PC值的指令所引起的冲突。\n3.流水线时空图（吞吐率、加速比和效率）?\n吞吐率（throughput）：在单位时间内流水线所完成的任务数量或输出结果的数量。\n加速比：完成同样一批任务，不使用流水线所用的时间与使用流水线所用的时间之比。\n流水线的效率：流水线中的设备实际使用时间与整个运行时间的比值，即流水线设备的利用率。\n14讲（流水线技术2） 词汇 ILP：Instruction-Level-Parallelism，指令级并行\n问题: 1.超标量处理机（时空图）?\n2.超流水线处理机（时空图）?\n3.超标量超流水线处理机（时空图）?\n15讲（总线技术） 词汇 ISA：工业标准总线，Industry Standard Architecture\nPCI：外部设备部件互连，Peripheral Component Interconnect\n问题: 1.总线分类?\n总线分类1 ①\t内部总线：CPU内部连接各寄存器及运算器部件之间的总线\n②\t系统总线：CPU和计算机系统中其他高速功能部件相互连接的总线\n③\tI/O总线：CPU和中低速I/O设备相互连接的总线\n④\t通信总线\n总线分类2\n①\t数据总线\n②\t地址总线\n③\t控制总线\n④\t电源总线\n总线分类3\n①\t并行总线\n②\t串行总线\n2.总线的一次信息传送过程，大致分为哪几个阶段? 若采用同步定时协议，请画出读数据的时序图来说明。\n分五个阶段：请求总线，总线仲裁，寻址（目的地址），信息传送，状态返回（错误报告）\n3.总线带宽计算?\n总线带宽 总线带宽定义为总线本身所能达到的最高传输速率，它是衡量总线性能的重要指标\n总线宽度 又称位宽，指的是总线同时传送数据的位数\n总线频率\n4.链式查询电路单元的逻辑图?\nBG(Bus Granted) BB(Bus Busy) BR(Bus Request) 5.并行判优电路图\n16讲（微程序与硬布线） 问题: 1.微程序控制器组成部分?\n微程序控制器主要由控制存储器、微指令寄存器和地址转移逻辑三大部分组成。\n17讲（半导体存储器） 词汇 ROM（Read-Only Memory，只读存储器）\nRAM（Random-Access Memory，随机存取存储器）\nEPROM（Erasable Programmable Read－Only Memory，可擦可编程只读存储器）\nEEPROM（Electrically Erasable Programmable read only memory，电可擦可编程只读存储器）\nSRAM（Static Random Access Memory，静态随机存取存储器）\nDRAM（Dynamic Random Access Memory，动态随机存取存储器）\n问题: 1.存储器的片位扩展?\n复杂而且重要，看PPT吧，实在不行问老师。\n18讲（磁存储与光存储） 词汇 CAV，constant angular velocity，恒定角速度\nCLV，constant linear velocity，恒定线速度\nCRC：循环冗余校验码\nECC：Error Checking and Correcting\n问题: 1.磁记录的几种方式（列出即可）?\n①\t不归零制(NRZ0)\n②\t见“1”就翻不归零制(NRZ1)\n③\t调相制(PM)\n④\t调频制(FM)\n⑤\t改进调频制(MFM)\n19讲（高速缓冲存储器1） 问题: 1.三种映射方式分析?\n①\t直接（direct）：每个主存块映射到cache的固定行中\n②\t全相联（full associate）：每个主存块映射到cache的任意行中\n③\t组相联（set associate）：每个主存块映射到cache的固定组的任意行中\n20讲（高速缓冲存储器2） 问题: 1.cache命中率、平均访问时间、效率的分析?\n2.写回、写直达的区别?\n21讲（虚拟存储器） 词汇 VM，虚拟存储器，Virtual Memory\nVA，虚拟地址，Virtual Address\nLA，逻辑地址，Logical Address\nPA，物理地址，Physical Address\nTLB，快表，translation lookaside buffer\n问题: 1.TLB的作用？\n（又称为快表技术。由于“页表”存储在主存储器中，查询页表所付出的代价很大，由此产生了TLB。TLB是一个小的，虚拟寻址的缓存，其中每一行都保存着一个由单个页表项组成的块。如果没有TLB，则每次取数据都需要两次访问内存，即查页表获得物理地址和取数据。TLB和CPU里cache之间不存在本质的区别，都是半导体硬件，只不过前者缓存页表数据，而后两个缓存实际数据。）\n22讲（RAID及其它） 词汇 RAID：廉价磁盘冗余阵列，Redundant Array of Inexpensive Disks；或Redundant Arrays of Independent Disks，独立磁盘冗余阵列\nSCSI：Small Computer System Interface，小型计算机系统接口）\n23讲（中断） 问题: 1.CPU管理外围设备有几种方式?\n①\t程序查询方式：好处是硬件结构简单，不足是太浪费CPU时间，目前除单片机外，很少使用查询方式。\n②\t程序中断方式：是管理I/O操作的一个比较有效的方法。\n③\t直接内存访问(DMA)方式：是一种完全由硬件执行I/O交换，DMA控制器接管总线的控制，数据交换不经过CPU，适用于内存和高速外围设备之间大批数据交换的场合。\n④\t通道方式：通道是一个具有特殊功能的处理器。\n⑤\t外围处理机（PPU，Peripheral processing unit）方式：PPU基本上独立于主机工作，是通道方式的进一步发展。\n2.程序内部异常的原因?\n3.程序外部中断的原因?\n4.简述中断服务程序的流程?\n5.中断屏蔽码的应用?\n在CPU的中断管理部件中必须有一个中断屏蔽触发器，它可以在程序的控制下置“1”(设置屏蔽)，或置“0”(取掉屏蔽)。只有在中断屏蔽标志为“0”时，CPU才可以受理中断。\n24讲（DMA） 词汇 DMA：Direct Memory Access，直接存储器访问\n问题: 1.DMA功能示意图?\n2.DMA传送的三种方式?\n25讲（通用IO接口标准，芯片组，外部设备） 词汇 ATA：AT Attachment，AT计算机附加设备\nPATA（并行ATA，Parallel ATA）\nSATA（串行ATA，Serial ATA）\nVGA：Video Graphics Array，视频图形阵列\nDVI：Digital Visual Interface，数字视频接口\nHDMI：High Definition Multimedia Interface，高清晰度多媒体接口\nLCD：Liquid Crystal Display，液晶显示器\nLED：Light Emitting Diode，发光二极管\n26讲（并行计算，计算机系统性能评价） 词汇 SISD：单指令流单数据流\nSIMD：单指令流多数据流\nMIMD：多指令流多数据流\nHPC：高性能计算，High Performance Computing\nSC：超级计算，Super Computing\nCOW：工作站机群，Cluster of Workstation\nPVP：并行向量机，Parallel Vector Processor\nUMA：Uniform Memory Access，共享存储器架构\nSMP：Symmetrical Multi-Processing，对称多处理器结构\nNUMA：Non Uniform Memory Access Architecture，非一致内存架构\nMPP：Massive Parallel Processing，海量并行处理结构\nUMA：均匀存储器存取，Uniform Memory Access\nNUMA：非均匀存储器存取，Non-uniform Memory Access\nCOMA：只用高速缓存的存储器结构（Cache-Only Memory Architecture），是NUMA的特例\n问题: 1.从系统架构来看，目前的商用服务器大体可以分为三类\n①\t对称多处理器结构(SMP：Symmetric Multi-Processor)\n②\t非一致存储访问结构(NUMA：Non-Uniform Memory Access)\n③\t海量并行处理结构(MPP：Massive Parallel Processing)\n2.共享存储型多处理机有两种模型\n均匀存储器存取（Uniform Memory Access，简称UMA）模型 非均匀存储器存取（Nonuniform Memory Access，简称NUMA）模型 27讲（GPU，树莓电脑硬件探密） 词汇 GPU：图形处理器，Graphics Processing Unit\n","permalink":"http://localhost:9011/20171227-computer-arch/","summary":"\u003cp\u003e汕大计算机系2017年的计组课最后期末的复习提纲，我个人整理各类资料和PPT总结，有部分可能会有错漏或不足，这里分享给有需要的同学。\u003c/p\u003e","title":"汕大计组复习提纲2017版"},{"content":"BIOS和UEFI这两个概念对于刚接触计算机硬件的朋友来说可能有些难以区分，最近正好有空，就稍微整理了一下有关知识和大家分享交流一下，如文中有任何错误不足，还望不吝赐教。\nBIOS ▲图为经典BIOS操作界面\n简介 **BIOS，读作/\u0026lsquo;baious/，全称Basic Input Outpout System（基本输入输出系统），本质上是一组固化到计算机内主板上一个ROM芯片上的程序，它保存着计算机最重要的基本输入输出的程序、开机后自检程序和系统自启动程序，它可从CMOS中读写系统设置的具体信息。**BIOS和手机的Bootloader有些相似，实际上，不止是电脑和手机，基本所有电子产品都有与BIOS/Bootloader功能相似的部分，它们往往体积很小（最大的也才几MB），功能看似简单但是非常重要。\n存储上，在主板中有着专门的芯片（BIOS芯片/CMOS RAM）用于存储BIOS，因此它并不存储在内存或者硬盘中，稳定性和可靠性有较好的保证。\nBIOS的存储介质 早期的BIOS是存储在ROM（Read-Only-Memory，只读存储器）芯片中，在主板出厂前，需要通过特殊的手段将BIOS烧录进BIOS芯片中。而且由于ROM的只读特性，一方面保证了BIOS不会受到轻易更改而保证硬件的正常稳定运行，另一方面也限制了BIOS的升级（ROM不能被写入数据）。\n不能被升级的BIOS显然不能迎合科技发展的需要，于是EPROM芯片开始替代ROM成为BIOS的存储芯片。\n**EPROM全程是Erasable Programmable ROM，名为可擦除可编程ROM，这种芯片可重复擦除和写入，解决了ROM芯片只能写入一次的弊端。**EPROM内资料的写入要用专用的编程器，并且往芯片中写内容时必须要加一定的编程电压，写入资料后，还要以不透光的贴纸或胶布把窗口封住，以免受到周围的紫外线照射而使资料受损。\n尽管EPROM可以重复擦除和写入，但是操作方式和条件都过于苛刻，后来就使用了EEPROM芯片。**EEPROM（Electrically Erasable Programmable ROM，电可擦除可编程ROM）。**通过跳线开关和系统配带的驱动程序盘，可以对EEPROM进行重写，方便地实现BIOS升级。\n到了现在，我们可以用软件轻易地升级BIOS，是因为现在的BIOS大多是使用了NOR Flash。NOR Flash 的特点是芯片内执行（XIP ，eXecute In Place），这样应用程序可以直接在Flash闪存内运行，不必再把代码读到系统RAM中。\n**NOR 的传输效率很高，在1~4MB的小容量时具有很高的成本效益，但是很低的写入和擦除速度大大影响到它的性能。**尽管如此，对于BIOS来说，小容量足以满足需求，而BIOS的升级并非常事，偶尔一次升级，由写入擦除速度慢导致的升级时间延长也没有太大的影响。\nBIOS里面有什么 一般来说，人们普遍认为BIOS是沟通软件与硬件的桥梁。讲完存储介质，我们再来了解一下这个存储芯片（BIOS芯片）里面都储存着什么东西。\n**●自诊断程序：**通过读取CMOSRAM中的内容识别硬件配置，并对其进行自检和初始化；\n这就是电脑开机最开始的自检步骤，一般出现错误主板会报警，发出响声，一般可以根据响声的长短和数量来判断出现问题的部分，当然也有一些主板在上面加了一块小型的LED显示屏，通过上面的数字来直接反馈错误信息。\n**● CMOS设置程序：**这个部分就是大家通常见到的BIOS（蓝色背景界面）， 引导过程中，用特殊热键启动（一般是Del、esc、F9、F12等），进行设置后，存入CMOS RAM中；\n大多数的电脑停留在这个部分的时间并不长，因此想要进入BIOS的CMOS设置程序，需要在一开机就不停地按对应的特殊热键，错过了这个时间段就只能重启再来。\n● 系统自举装载程序：在自检成功后将磁盘相对0道0扇区上的引导程序（即MBR分区表）装入内存，让其运行以装入操作系统。\n● 主要I/O设备的驱动程序和中断服务：由于BIOS直接和系统硬件资源打交道，因此总是针对某一类型的硬件系统，而各种硬件系统又各有不同，所以存在各种不同种类的BIOS，随着硬件技术的发展，同一种BIOS也先后出现了不同的版本。\nUEFI 简介 由于BIOS是使用汇编语言进行编写，因此在操作界面上相对比较简陋（没有做出太多的图形化界面），对于技术人员来说维护升级也比较麻烦，不可能使用鼠标操作，更加别说截图之类的功能了。\n更致命的是，BIOS已经存在了二十多年，可是其发展却是极度缓慢，已经阻碍到了与其相关的硬件的发展，首当其冲的就是CPU。至此，个人PC行业消费市场的龙头老大英特尔坐不住了，由它主导，开始推广一种名为EFI的技术，旨在用其取代传统的BIOS。\n小七注：在x86系列处理器进入32位的时代，由于兼容性的原因，新的处理器(i80386)保留了16位的运行方式(实模式)，此后多次处理器的升级换代都保留了这种运行方式。甚至在含64位扩展技术的至强系列处理器中，处理器加电启动时仍然会切换到16位的实模式下运行。英特尔将这种情况归咎于BIOS技术的发展缓慢。自从PC兼容机厂商通过净室的方式复制出第一套BIOS源程序，BIOS就以16位汇编代码，寄存器参数调用方式，静态链接，以及1MB以下内存固定编址的形式存在了十几年。虽然由于各大BIOS厂商的努力，有许多新元素添加到产品中，如PnP BIOS，ACPI，传统USB设备支持等等，但BIOS的根本性质没有得到任何改变。这迫使英特尔在开发更新的处理器时，都必须考虑加进使效能大大降低的兼容模式。有人曾打了一个比喻：这就像保时捷新一代的全自动档跑车被人生套上去一个蹩脚的挂档器。 ——摘自百度百科\n**EFI（Extensible Firmware Interface）的中文名一般叫做可扩展固件接口，是一种由英特尔主导的用于替代BIOS的方案。**有人称EFI是“未来的类PC的电脑系统中替代BIOS的升级方案”，但实际上，这种说法有一些不太严谨，因为EFI已经被弃用，如今使用的是基于EFI标准1.10版本发展而来的UEFI标准。\n如今UEFI早已经普及开来，近些年发售的主板（包括笔记本）基本都支持UEFI和BIOS两种解决方案，前者一般被称为UEFI，后者则称为Legacy。\n那么为什么EFI的前面会多了一个U，变成UEFI了呢？实际上，EFI可以被理解为是在BIOS和UEFI过渡时期的产物。\n当传统的BIOS（Legacy BIOS）阻碍了计算机硬件发展的时候，英特尔主导推行EFI标准，但是其他企业自然不会那么傻，让英特尔一人主导了整个EFI标准，正所谓“一流企业卖标准”，由Intel, AMD, American Megatrends, Apple, Dell, Hewlett Packard Enterprise, HP，IBM, Insyde Software, Lenovo, Microsoft 和Phoenix Technologies这12家计算机软硬件和零售的巨头在2005年联合成立了一个名为Unified Extensible Firmware Interface Forum（简称Uefi Forum）的非营利性组织，专门负责制定和管理新一代的EFI标准，随后就基于最后一版的EFI标准（1.10）发布了全新的UEFI标准，EFI也就“变”成了UEFI（全称“统一的可扩展固件接口”，Unified Extensible Firmware Interface）。\n▲图为UEFI协会的Logo\nUEFI的优势 那么相对于传统的BIOS，UEFI的优势在哪里呢？\n首先，与使用汇编语言编写的传统BIOS不同，UEFI使用C语言编写，因此整体风格上也就延续了C语言风格的参数堆栈传递和动态链接，模块化特征显著。使用C语言开发和维护对于技术人员来说也方便了很多，更容易实现更强的健壮性和容错性。\nUEFI内置图形驱动功能，可以提供一个高分辨率的图形化界面，用户进入后完全可以像在Windows系统下那样使用鼠标进行设置和调整，操作上更为简单快捷。**同时由于UEFI在逻辑上可分为硬件控制与软件管理两部分，前者属于标准化的通用设置，而后者则是可编程的开放接口，因此主板厂商可以借助后者的开放接口在自家产品上实现各种丰富的功能，包括截图、数据备份 、硬件故障诊断、脱离操作系统进行UEFI在线升级等，**功能上也要比传统BIOS更多、更强。\n小七注：UEFI可以识别FAT/FAT32文件系统格式的U盘，因此可以通过特殊热键将截图存储在U盘中或者是将UEFI的设置导出到U盘中进行备份，还可以将升级文件放入U盘中进行UEFI系统的升级，部分高端的主板甚至可以直接在UEFI环境下联网升级。\nUEFI的组成 ▲UEFI在逻辑上可分为软件和硬件两个部分\nUEFI在概念上可以分为平台初始化框架和UEFI Image两大部分，细分下来则由初始化模块（Pre EFI，简称PEI）、驱动执行环境（DXE）、驱动程序（UEFI Drivers）、兼容性支持模块（CSM）、UEFI应用（UEFI Applications）和GUID磁盘分区（GPT分区）/ OS Loaders组成。\n初始化模块和驱动执行环境是UEFI的运行基础，通常被整合在主板的闪存芯片中，这点与传统BIOS是比较类似的。而这两者也被归属在UEFI的平台初始化框架中。\n驱动程序、兼容性支持模块、UEFI应用和GUID磁盘分区则归属于UEFI Image。\n▲图为UEFI各部分组件之间的交互逻辑\n**●初始化模块：**开机的时候初始化模块首先得到执行，负责CPU、主板芯片及存储设备的初始化工作。\n**●驱动执行环境：**初始化完成后则载入驱动执行环境，即Driver Execution Environment，简称DXE，DXE是硬件的UEFI驱动程序加载和运行的必要基础环境。\n**●驱动程序：**DXE通过枚举的方式加载各种总线及设备的驱动，而这些驱动程序则可以放置在系统的任意位置，只要确保其可以按顺序被正确枚举即可。硬件的UEFI驱动一般是放置在硬盘的UEFI专用分区中，只需要系统正确加载这个硬盘，对应的驱动就可以正常读取并应用\n**●兼容性支持模块：**兼容性支持模块（CSM）是有点特殊的过渡时期的产物，是为了让不具备UEFI引导功能的操作系统也能在UEFI环境下顺利完成引导开机，这个兼容性支持模块将为UEFI提供类似于传统BIOS的系统服务，以此保证UEFI在技术上能有良好的过渡。\n小七注：Windows操作系统在vista之后均已支持UEFI启动，而需要实现UEFI启动，还需要主板支持，且硬盘分区表要是GPT分区表，此三者缺一不可，缺一都要使用CSM才可以正常开机进入操作系统。\n**●UEFI应用：**硬件初始化完，操作系统启动之前的核心应用，比如：启动管理（多硬盘多系统的启动顺序）、BIOS设置、UEFI Shell、诊断程式、调度和供应程式、调试应用、包括CPU的超频，主板散热风扇的转速等等都可以在这里控制。\n▲图为UEFI Shell\n**●GUID磁盘分区/ OS Loaders：**GPT分区表作为UEFI标准中不可或缺的重要一环，突破了老旧的MBR分区表最大仅支持2TB硬盘，四个主分区、分区表容易丢失等不足，补足了UEFI启动三要素（主板、硬盘分区表、操作系统）中硬盘分区表这关键的一环。\nUEFI的未来 UEFI有着如此多的好处，不仅收到了广大电脑用户的喜爱，同时也受到了……额……一些居心叵测的人的喜爱。由于UEFI使用C语言编写，在难度上较汇编语言降低了很多，因此容易遭到黑客的破解或者是恶意软件的植入，加上UEFI强大的应用能力（相对BIOS而言），在UEFI搞一个恶意软件什么的不是美滋滋？\n尽管如此，UEFI依然是目前的主流趋势。\n","permalink":"http://localhost:9011/20171007-bios-n-uefi/","summary":"\u003cp\u003eBIOS和UEFI这两个概念对于刚接触计算机硬件的朋友来说可能有些难以区分，最近正好有空，就稍微整理了一下有关知识和大家分享交流一下，如文中有任何错误不足，还望不吝赐教。\u003c/p\u003e","title":"BIOS与UEFI"},{"content":"随着科技的发展，智能手机的性能愈发强大，普及度也是越来越高，而它在我们的日常生活中的地位更是水涨船高。但是在电池技术没有什么突破性发展的今天，续航成了所有手机不得不提的一项参数，而充电作为其中不可或缺的一环，也是被各大手机厂商不遗余力地进行宣传。今天小七就带大家来简单了解一下手机充电技术的发展史。\n充电知识 在继续后面的故事之前，我们需要先补充一点基础的充电方面的知识。\n1.1 知识点一：充电的电压以及发热 目前大多数手机使用的电芯都是单锂或者多并锂组成，电芯工作电压在3.0V4.4V之间，均压平台3.6V-3.8V。当进行充电行为时，电能进入手机后通过手机内的降压电路处理后再输出3.34.5V左右的电压给电池充电。而这个电压转换压降过程，就是由手机内的充电管理IC模块负责。 这个电压转换压降过程会产生发热，电压差距越大产生的热量会越多。\n▲图为充电器工作原理示意图\n1.2 知识点二：充电的电流以及功率 手机充电时的电流并不是一直不变的。优秀的充电方案应该是手机需要多大的功率，充电器就给多大的功率，而不是手机只能被动地接受充电器一成不变的功率。 当手机处于低电量的时候，手机会要求充电器火力全开，这就是所谓的充电峰值。在这个时候充电速度非常快，但是损耗和发热也很大。 充电时，随着手机电量的上升，充电的功率往往是逐渐下降的。当手机的电量充到60%~80%的时候（具体看手机厂商的设定），充电电流会减小，以达到减少电池损耗和手机发热量等目的。 在后面这个阶段，电流往往只有几百毫安甚至更小，充电的功率也很小，也就是我们常说的涓流充电。 需要注意的是，涓流充电是大功率充电时代的产物，对于5V/500mA的充电器是没有涓流充电这么一说的。（电流本来就很小）\n下面开始正文，本文分为“史前”时期、初露锋芒、快充现世、高低之争和一统天下五个部分。\n“史前”时期 我们先来说一下当年功能机还统治着地球的时期，比较早期的充电器和现在的充电器主要有三个比较明显的区别：\n①\t以前的充电器并不像现在这样数据线和适配器（充电头）分开，而是一体的，也就是充电头连着数据线； ②\t以前的充电器功率其实很小，一般都是5V/500mA,也就是2.5W（瓦）的功率； ③\t以前的充电器单纯只能充电，而现在负责的充电器把数据线拔下来接到电脑上还可以进行数据传输（同时也能充电） ▲图左为老式一体充电器（黑），右为新式分体式充电器（白）\n注：此处并非指分体的充电器要优于一体的充电器，实际上两者各有优劣\n为什么当时的充电器功率这么小呢？主要原因有两个：\n①\t手机相对功能少，耗电少，因此电池也小（大多数是几百毫安时/mAh），不需要太大功率的充电器； ②\t大功率的充电器制造难度大，成本高，在当时的环境下显然不划算； 但是随着手机的发展，尤其是安卓手机的崛起，手机功能也开始增多，耗电量也开始增大，电池容量也开始突飞猛进，到了动则上千毫安时的时代。这个时候如果还是继续使用着5V/500mA的充电器，那么就会出现充电非常慢，而且会出现边充电边玩手机，电量还是越来越少的这种”入不敷出”的情况。\n在智能手机萌芽的时候，USB接口在手机上也随之普及开来。而手机充电技术的变革，就从这里开始了。\n初露锋芒 比较早开始出现的充电标准是USB BC 1.2（BC是Battery Charge的简称）。\nUSB规格第一次是于1995年，由Intel（英特尔）、NEC（日本电气株式会社）、Compaq（康柏）、DEC（美国数字设备公司）、IBM（国际商业机器公司）、Microsoft（微软）、Northern Telecom（北方电信公司）等七家公司组成的USB IF（USB Implement Forum）共同提出。\nBC 1.2的出现，让同时进行充电和数据传输成为了主流。\nUSB BC1.2标准由USB IF协会于2010年颁布，指的是可直接为关闭的便携式设备电池充电，成为建立通过USB端口为电池充电的正确方式的关键标准。\n说白了，BC1.2就是可以给包括手机在内的便携式设备使用USB接口充电（包括关机充电）的一套官方标准。\nBC 1.2出来之前，除了日渐强大的只能手机，诸如数码相机、DV等便携式设备也开始逐渐受到人们的青睐。这些便携式设备与电脑之间的数据交换也变得频繁了许多，因此，USB接口在这些设备上开始普遍起来。\n尽管USB接口出现的目的是为了传输数据和连接诸如键盘鼠标的设备而并不是充电，但是在这时候，如果能使用USB接口给这些设备充电，那么就会方便很多，USB BC 1.2也就应运而生了。\n目前市面上主流的USB接口可以分为USB 2.0和USB 3.0两种，其中两者的电压都是5V，而电流方面USB 2.0为500mA，USB 3.0为900mA。\n注：\n①\tUSB 3.1 gen1其实就是USB 3.0（最大传输速率仍然是5Gb/s），而USB 3.1 gen2才是升级版，最大传输速率可达10Gb/s，最大输出电压/电流可达20V/5A）； ②\t传输数据的USB 2.0的线缆中只有四根线，充电的MicroUSB 2.0线缆中有五根线，而USB 3.0中升级为了九根线； ③\t常见的USB分线器/集线器/HUB等可以将一个USB接口分出多个USB接口的设备，在没有独立外接电源的情况下，很有可能因为给每个USB接口提供的电流不足而导致连接在该USB接口上的设备无法正常使用甚至是损坏，因此大家在使用这类产品的时候一定要小心。 USB BC 1.2最大的功劳就是使得USB充电的最大电流能够达到1500mA也就是1.5A，尽管它没有提升电压（因为要适配其他便携设备），但是将电流提升到1.5A之后，USB接口充电的最大功率就能够达到7.5瓦（W），这个时候的USB BC 1.2已经足够应付当时的手机充电了。\nUSB BC 1.2的出现不仅使得当时的USB充电规范混乱的场面得到了规范，而且它对于集线器/分线器/HUB也有着很好的支持，特别需要注意的是，为了保证每个接口能有足够的电流，支持BC 1.2的集线器/分线器/HUB往往都需要外接电源。\n▲图为USB BC 1.2工作方案\n前排提示：千万不要小看USB IF协会哦\n快充现世 尽管USB BC 1.2标准已经能满足当时的充电需求，但是科技的发展是无止境的。随着智能手机的发展，对充电的速度再次提出了更高的要求。而就在这个时候，发布USB BC 1.2标准的USB IF协会居然没有给出一套可行的解决方案，而这就为后来的快充标准大混战埋下了伏笔。\n到了2013，高通大佬就出场了。当时高通一拍桌子，振臂一呼：同志们，跟着我有肉吃！它率先突破了USB IF协会关于USB BC 1.2标准中的1.5A的最大电流限制，将其提升到了2A也就是10W的功率（5V/2A），充电速度大幅提升。\n这就是高通的QuickCharge快充1.0版本，也就是QC 1.0快充。\n了解高通的同学应该都知道，高通在手机芯片和通信专利方面可谓是一方巨擘，而凭借着这个霸主级别的地位优势，高通可以迅速推广自己的QC快充标准。然后，就可以坐着收QC标准的授权费。\n而到了2014年，情况就有些不一样了，这个时候虽然Type-C的数据线已经面世，但却还没有普及，手机上依旧还使用着MicroUSB 2.0接口的数据线（也就是大家常说的安卓线）。前面我们已经提到了，由于MicroUSB 2.0的数据线内部只有四根线，对电流的承载能力非常有限，2A基本就是极限了，而Type-C接口优于设计的优越性，接口的触点非常多，因此内部可以增加的线比MicroUSB 2.0要多得多,最大可支持5A的电流。所以Type-C接口与生俱来就对大电流有着极大的友好。\n▲图左为Type-C型接口，图右为MicroUSB 2.0接口\n注：\n①\tType-C接口只是一种接口类型，由于体积小，正反可插，以及可以兼容诸多协议，因此有着“数据接口的终极形态”之称，但也因为其兼容了诸多协议，可以集成视频、音频、数据、供电等各种接口和协议（如雷电三），导致目前市面上的Type-C接口功能不一，对新手来说比较不友好。 ②\t常见的MicroUSB除了2.0，还有MicroUSB 3.0，手机上有三星的note3和s5采用了这种接口。但是由于MicroUSB 3.0体积太大，因此后来就没有手机采用这种接口了，反倒是移动硬盘现在用的比较多。 ▲图为MicroUSB 3.0数据接口\n注：以下提及的MicroUSB如无标注则均为MicroUSB 2.0\n高低之争 我们继续回到快充发展史的2014年。\n而这个时候，如果还要继续通过增大电流的方法来提升充电功率，MicroUSB那孱弱的身躯可承受不了这巨大的电流。于是乎，以高通QC为首的高压快充方案和以OPPO VOOC为首的低压大电流快充方案就此分道扬镳，而快充协议的混战也从此展开。\n先来说说高通这边的高压快充方案。\n我们都知道P（功率）=U（电压）*I（电流），而既然当时增大电流不行，那就增大电压呗，同样是18W功率的快充，如果要用5V电压的话，电流已经超过了3A，正常的MicroUSB是绝对受不了的。而使用12V的电压，电流就只需要1.5A，一下子电流就降下来了。这种方案的一大好处就是成本比较低，而坏处就是将充电器的电压升到这么高，在二次降压过程中手机的充电管理IC产生的热量也是极大的，所以高压快充的一大特点就是手机发热严重。\n注意，1.5A是QC标准比较推荐的电流，因为2A是Micro USB的极限，业界的普遍共识是，不要把器件用到极限值，而是要预留余量。\n▲图为高压快充方案示意图\n接下来我们再来说说OPPO的VOOC低压大电流快充。\n▲图为低压大电流快充原理示意图 和高通不同，OPPO这边采用的是另外一种解决方案。不是说正常的MicroUSB数据线承载不了这么大的电流嘛，那就把充电器从头到尾彻底改造一番。OPPO采用在当时来说相当另类的解决方案，在普通的MicroUSB数据线中增加了两个个触电，使得内部从五根线变成了七根线，充电头也因为整合了IC电路而变得奇大无比。\n▲图片来自充电头网\n不仅如此，因为从头改造电路，所以数据线只能用官方的特制数据线，一般的数据线无法达到快充的效果，而且这样做的成本也很高，大电流充电对于电池的损耗也更为明显，很多使用初期的VOOC快充的手机在使用大约一年之后，电池续航严重下降。\n但是，付出了如此巨大的代价，OPPO也不是没有收获的。\n初代的VOOC快充就凭借着5V/5A的25W超大功率，**在充电速度上一骑绝尘，**使得其余手机都难以望其项背。而由于它将发热源外置到充电器中，手机在充电时发热量明显小于高压快充方案。既然VOOC快充如此优秀，OPPO自然也不能藏着掖着，于是乎……\n充电五分钟，通话两小时的广告词响彻大江南北。\n▲图为OPPO R11官方宣传文案\n低压大电流方案虽然成本高，且对充电设备有比较高的要求（尤其是线材），但是手机上的发热量小，充电速度更快。\n一统天下 随后联发科也推出自己的Pump Express（PE）和后来的Pump Express Plus（PEP）快充，而魅族的mCharge快充就是基于此，华为早期推出了Fast Charge Protocol（fsp）快充，国际巨头三星也有自己的AFC（Adaptive Fast Charging）快充，而小米和努比亚等一众使用高通SoC作为自己旗舰手机SoC的厂商也是使用的高通的QC快充。以上提到的这些都是使用的高压快充方案。\n当然低压大电流方案这边也不是没有援军，比如说一加。一加CEO刘作虎作为OPPO前高管，尽管一加使用的是高通的SoC，但是在一加推出自己的快充方案时，还是选择了低压大电流方案，也就是一加Dash闪充。\n看到这里是不是觉得高压快充方案已经赢得了胜利，低压方案只能在一旁苟延残喘？\n当然不是！事情在2016年发生了转变。\n2016年，Type-C接口已经普及得七七八八了，安卓旗舰手机基本都是使用这种接口，这就为低压方案的翻身提供了有利条件。（尽管Type-C接口支持大电流，但是其线材依旧比较粗）\n这一年，华为改变了快充方案，推出了自家的另一类快充（scp），全称Super Charge Protocol，搭载的机型有荣耀Magic、Mate9和P10/plus，使用的是4.5V/5A的低压大电流方案。\n而联发科这边的PEP快充，也转投了低压方案，魅族最新发布的旗舰Pro7 Plus搭载的mCharge4.0也是使用了低压方案，早前的mCharge3.0属于高压快充方案（24W），充电器输出电压最高可达12V；而mCharge4.0（25W）属于低压大电流方案，充电器输出电压5V，电流可达5A。\n努比亚也推出了自家的快充方案，名为NeoCharge，使用的是5V/5.2A的26W低压大电流快充方案，搭载在努比亚2017年发布的M2上。\n至于小米……额……好像没有低压快充方案，目前已有的澎湃S1上搭载有9V/2A的18W的澎湃快充，是典型的高压快充方案。不知道澎湃S2会不会给我们带来惊喜呢？\n再说说高通，高通似乎也发现了低压方案的优势，在最新的QC4快充上，也使用了低压大电流方案，不过于此同时也还支持着高压快充方案。\n尽管目前低压大电流方案已经基本统治了快充，但是各家的快充协议互不兼容，可以说给消费者带来了很大的苦恼。\n这个时候，又到USB IF协会出场了。\n**USB IF协会之前当然也没有闲着，发布了基于USB 3.1中Type-C接口的USB Power Delivery（简称USB PD）的充电标准，最高可以提供100W（20V/5A）的充电功率，旨在统一便携移动设备的充电标准。**USB IF的梦想是美好的，只是现实往往比较残酷。各家厂商自己做手机，再做快充标准，当然都是首选自家的快充协议，USB PD标准也就被晾到了一边。\n但是梦想还是要有的，万一实现了呢？\n要知道，USB IF协会的背景可不弱（都是行业巨头联合成立的），而谷歌官方也是表示安卓一定要支持USB PD协议，不要乱搞些有的没的快充协议。加上USB IF协会积极与各国以及各个高端实验室沟通，其最新发布的USB PD3.0已经成功收编高通的QC4快充协议，至此，高通QC、联发科PEP、华为fcp、scp，OPPO的VOOC等快充协议基本被USB PD3.0收纳。而厂商日后研发自己的快充技术，只要基于USB PD的协议即可。USB PD协议有望一统江湖。\n▲图为USB PD3.0协议\n但是，尽管前途一片光明，道路还是一片崎岖，快充大一统，还是有不短的路要走。\n","permalink":"http://localhost:9011/20170814-phone-charging-dev/","summary":"\u003cp\u003e随着科技的发展，智能手机的性能愈发强大，普及度也是越来越高，而它在我们的日常生活中的地位更是水涨船高。但是在电池技术没有什么突破性发展的今天，续航成了所有手机不得不提的一项参数，而充电作为其中不可或缺的一环，也是被各大手机厂商不遗余力地进行宣传。今天小七就带大家来简单了解一下手机充电技术的发展史。\u003c/p\u003e","title":"手机充电技术发展史"},{"content":"说起新浪，大家第一反应可能就是微博。没错，微博如今在国内确实有着举足轻重的影响，但是谁又能想到，在17年前，这家公司还因为互联网危机而解雇了身为CEO的创始人，并免去其董事身份。而这个遭遇了乔布斯老爷子那般被扫地出门的厄运之后，再也没能回到自己创办的公司。\n这个人，就是王志东。\n莞中学霸，北大才子 让我们把时间的指针拨回五十年前。\n1967年，那时，文革才刚刚开始，国家的经济还是一片荒芜。在广东虎门的一个小农村里，随着婴儿的啼哭声响起，这个世界又迎来了一个新生命。\n王志东的少年时期，可以说是非常困难了。不过好在父母两人都是教师，家里住的是单位的房，虽然不可能每顿都有肉吃，但是赶上运气好的时候，杀只鸭子，全家人可以吃两星期。至于那些高档的生活用品，就更加不可想象了。\n捡块砖头，拾个木块，这就是王志东小时候的玩具。家里只能给他一个放瓶瓶罐罐的小柜子，毕竟除了他，还有三个孩子。\n幼年的艰苦并没有影响到他的学业，小学时就因为连跳两级升入初中，而获得学校的特许，能自由进入学校的图书馆。父亲也因此奖励了他一个他梦寐以求的电烙铁，这在王志东的无线电生涯中，可谓是浓墨重彩的一笔。\n在就读东莞中学（高中）时，他不仅参加了学校的无线电兴趣班，后来还因设计了一种教学演示用的高阻电压表而获得了一个教学仪器比赛的第一名。\n1984年，王志东考入了北大无线电系。这在当时，可以说是非常厉害了。\n而王志东第一次接触计算机，就有点意思了。\n当时刚入校，学校组织新生参观机房，老师一边演示一边问有谁学过。在当时的中国计算机可还是稀罕物件儿，出身贫困的王志东身处在东莞这样的小城市（当时），自然是没有见过的。但身边好几个北京上海的同学都说学过，王志东不知咋滴就跟着说学过。而他又正好站在前面，老师就让他上去操作一下。之后就是脸红脖子粗，什么都不知道了。\n这件事不但没有打压王志东的斗志，反而激起了他不服输的精神。自学一个月后，王志东以不错的成绩提前考试并通过这门课程。在让老师惊讶之余，也得到了老师的重视。\n1987年，王志东还没毕业，但是却已经利用课余时间到中关村搞些小买卖。\n踏上中关村这片孕育了中国互联网雏形的土地，本以为在学校学得差不多的王志东感觉找到了第二个大学。他进入了一家小公司，一个人从头拼到尾，拉客户、谈判、讲价、签合同、装货、调试、配软件、培训、收费都要自己来。而正是这些在他的同学眼里看来“不搞学问、赚点小钱、特俗”的经历，给他在客户、同行和技术等方面夯实了基础。\n毕业之后，王志东在方正和新天地公司待了一段时候，最后进入了四通集团，成为了四通利方公司的总经理。\n而四通利方，正是新浪的前身之一。\n网景上市，泡沫初显 1995年8月9日，硅谷一家创始资金只有四百万美元的小公司——网景，在华尔街上市的几个小时后，瞬间成为了二十亿美元的巨人。网景的上市，瞬间引爆了美国大众和华尔街。通用动力公司花了四十三年才使市值达到二十七亿美元，而网景，只用了一分钟。\n美国计算机历史博物馆策展人马克·韦伯如是说道。\n“网景公司的上市证明，一个基于万维网的公司，可以引起商界的重视，这是互联网繁荣的开始。”\n网景一夜崛起的神话，再加上当时雅虎的成功，杨致远成为新美国梦的象征，互联网技术第一次向世人展现出惊人速度与庞大规模，整个硅谷都为互联网而疯狂。\n在互联网的飓风中，整个硅谷都忙得人仰马翻，脑子也没办法再理性地思考了，很多人都失去了方向。果敢与理性著称的风险投资家们，在这股前所未有的气氛中，变得盲目而疯狂。\n“我想上市，像苹果的乔布斯一样。“王志东说。\n“你现在没办法上市，要经过几个步骤：调整业务方向、管理架构、融资等等。”摩根斯坦利银行的一位董事总经理回应。\n“那你们能帮我吗？”王志东问。\n“你的公司请不起我们！”对方答道。\n这是1995年7月发生的一幕，当时王志东正赴美寻求风险投资。而摩根斯坦利银行就是帮助网景成功上市的那家投行。\n1995年底，王志东应IBM邀请，第三次访美。\n前两次访美，每次他都要带一大箱子的科技杂志，他甚至还想在硅谷设立一个办事处，这样便能有一个能够了解世界的通道。但是互联网改变了他的想法。\n第三次访美时，王志东用一个存贮在一个附在科技杂志封底的软盘中的上网账号，在美国酒店的房间里利用电话线第一次连上了网络。王志东当时就意识到：这样的技术一旦被推广开来，人们猎取信息的速度、数量和便捷性，都会呈现爆炸性的增长。就像相对论里提到的‘虫洞’一样，时间与空间的距离可以被瞬间穿透，而这一浩大的工程一旦展开，自然会带来无限商机！\n英特尔创始人之一的安迪·格罗夫曾说：\n“能够识别风向的转变，并及时采取正确的行动以避免沉船，对于一个企业的未来是至关重要的。\u0026quot;\n在接触了互联网之后，从在方正做程序员开始就在做中文平台的王志东对于软件技术的想法发生了裂变。他认为，如果把软件和互联网结合起来，将在一个新的平台上产生一系列新的应用，这就是下一个风口。\n事实证明了王志东的预判是正确的。今天，那怕只是你手机上的一个计算器APP，都可能需要联网获取汇率信息来帮助你完成货币转换。\n而当时的王志东认为：互联网会给他们带来一个全新的游戏规则——他们有可能去打破旧的体系。中国IT业怎么做都是在微软的阴影下，就是把微软拿走还有其他公司，作为一个后来者，最好的办法就是等新的游戏规则出来的时候，自己和竞争对手都站在同一起跑线上，各自的优势也就发挥出来了。\n1996年4月29日，四通利方的 www.srsnet.com 中文网站正式开通。\n四通利方想成为华人首选资讯网站的构想渐渐浮现出来。\n四通华渊，新浪面世 “当时特别担心，这要是吵翻了，人家一生气不开车送我，恐怕就要一个人客死在美利坚新大陆了。”\n在回忆当时自己赴美和华渊网谈判合并事项时，王志东调侃道。\n当时，四通利方想成为华人首选资讯网站，华渊网（Sinanet）是一个不得不面对的强劲对手。但是，有意思的是，四通和华渊尽管都看对方不过眼，想要消灭掉对方，但却谁都没有这个能力把对方吃下。\n98年9月，王志东和华渊网CEO姜丰年在北京会面，30分钟后，姜丰年迫不及待地提出了合并的想法，而王志东也表示不排除任何形式的合作。\n实际上，当时占有中国大陆市场的四通想要进军美国和台湾市场，而占有美国市场的华渊网想要进军中国大陆市场，狭路相逢，两者必有一战。更何况旁边还有着虎视眈眈，随时可能杀入局的雅虎。\n尽管在合并谈判过程中波折重重，但是王志东还是凭借着中国大陆市场巨大的潜力，使得合并方案按照他的设想进行。\n广告大师克劳德·霍普金曾说：恰当的名称本身就是广告。\n而新浪，正是如此。\n合并后的网站英文名确定为Sina，而当时的员工则根据音译确定中文名为“赛诺王“。当时印刷品即将交付印刷，而王志东却对姜丰年说：“等一下，明天我给你回音。”第二天，他告诉对方一个名称：新浪。\n新浪模式，赴美上市 1999年，新浪、搜狐、网易等中国第一批互联网企业已经在红红火火地运营着，没有人提出它们的生存有法律上的问题。但在它们酝酿出境上市时，需要得到包括信息产业部、证监会等部门的审批，而这些犹如潜藏的定时炸弹一般的合规问题突然爆发，首当其冲的就是新浪。\n在当时的中国，有着这么一条规定：放开经营的电信业务，一律不允许境外各类团体、企业、个人以及在中国境内已兴办的外商独资、中外合资和合作企业经营或者参与经营，也不得以任何形式吸引外资参股经营。\n尽管在2001年12月11日，为了适应中国加入世贸的要求，信息产业部决定废止上述的规定。不过这些都是后话了。\n在新浪筹划赴美上市的那段时间里，王志东白天要去信息产业部的办公室给主管领导们解释互联网和新浪的情况，晚上还要回到公司和律师一起研究新浪的股权结构、资本、现金、人流、架构等等。\n最后王志东想出来一个模式：一个公司没法上市，那就分为三个，其中做互联网内容服务的留在国内，不上市；上市的是美国公司，不在中国做互联网业务。\n这就是后来被包括搜狐、网易在内的很多互联网公司使用的新浪模式。\n当时的具体情况是这样的，被初步认可的方案是：一是要把提供互联网内容服务的运营权交到国内公司手里；二是提供互联网内容服务的必须是个全中资公司，上市公司在国内公司里不占任何股份；三是合约本身必须遵守国内的法律法规。\n而根据新浪最后文本的招股说明书中，上市的是一家在开曼群岛注册的控股公司，拥有四个全资子公司：香港注册的利方投资有限责任公司、运行香港网站的香港新浪有限责任公司、美国加州注册的新浪在线（Online，包括北美和台湾两个网站）、以及在英属维尔京群岛注册的新浪有限公司。\n其中上市主体的全资子公司之一利方投资有限责任公司成立于1993年3月，由它控股97.3%和四通集团下属的北京四通电子技术有限责任公司合资建立了北京四通利方信息技术有限责任公司。北京四通利方信息技术有限责任公司为一个技术服务公司，和国内的ICP公司仅发生商业协议关系。同时，另在国内注册成立一家北京新浪互动广告有限责任公司。四通利方向国内ICP提供技术服务，而国内ICP以双方商定的价格购买服务。\n（有兴趣的童鞋可以搜索一下新浪模式，这在商业上是非常著名的一个上市模式，在这里碍于篇幅，小七只能简述一下）\n实际上，新浪当时的情况要更复杂一些，新浪赴美上市时，已经将关键业务剥离。虽然还是和拆分出来的公司有着千丝万缕的关系，但对于崇尚简单的投资者来说还是过于复杂。这必然会导致其上市筹措资金的能力被削弱。但王志东表示：尽管对投资者来说有遗憾，但这是当时他们可以拿出的最好的解决方案了。\n2000年4月13日11点，新浪开始挂牌交易，每股定价为17美元。当日以20.875美元收盘。当天交易额共达684.79万股，大约筹得资金6600万美元。\n新浪总算是赶上了赴美上市的末班车，而迎接他们的，是更大的危难。\n泡沫破灭，扫地出门 2000年，全世界都在兴奋地庆祝着新千年的到来，而一场意想不到的危机却悄无声息地降临了。从3月10日开始，纳斯达克指数在长达两年时间里，狂跌百分之七十八。七千五百亿美元的资产和六十万个工作岗位蒸发，只有不到一半的网络公司活过了2004年。\n进入2001年，新浪股票价格一路下滑，跌破2美元。董事局的董事们纷纷抱怨新浪的财经状况，称王志东应该为这一状况负责。但其实当时整个互联网泥沙俱下，大家的日子都不好过，许多公司的股票价格与企业CEO的经营能力并没有太大的关系。但总有肤浅无知的董事会幻想着能够通过换帅之举来扭转乾坤。\n据芝加哥当时一家统计机构统计，进2001年2月一个月，各大科技公司离职的CEO高达119人，比去年同期增长了37%。曾经高高在上的CEO也变得朝不保夕。\n而王志东，也没有例外。\n2001年6月1日，新浪董事们决定免去王志东的CEO及董事身份。\n王志东事后回忆：得到自己被解职的消息，有三个强烈的感觉。一是震惊，二是感觉被出卖了，三是想回家，想回到中国。\n“互联网用如此短的时间内就创造了一场人类历史上影响全球的波澜，史无前例。辉煌的泡沫盒破碎，是互联网这个新生命能量的另一种表达。”\n——摘自央视纪录片《互联网时代》\n后来的新浪还是挺过了危机，而王志东再也没有回到新浪，而是创办了点击科技。\n王志东认为点击科技是他的另一个孩子，他说：以前攀登过几个高峰，现在终于你有机会去攀登一次新的高峰，这样人反而有一种很兴奋的感觉。到了春天更重要的是你要更努力地去耕作，你不把握好现在这个机会，错过这个机会的话，到了收获的季节，你有可能就一无所有。\n不论如何，作为如今互联网的受益者，我们都应该向曾经的互联网元老们献上最崇高的敬意。\n","permalink":"http://localhost:9011/20170810-wzd-n-sina/","summary":"\u003cp\u003e说起新浪，大家第一反应可能就是微博。没错，微博如今在国内确实有着举足轻重的影响，但是谁又能想到，在17年前，这家公司还因为互联网危机而解雇了身为CEO的创始人，并免去其董事身份。而这个遭遇了乔布斯老爷子那般被扫地出门的厄运之后，再也没能回到自己创办的公司。\u003c/p\u003e","title":"王志东和新浪"},{"content":"目前来说，比较主流的两种硬盘的分区表是MBR和GPT（GUID）。今天小七带大家了解一下比较旧一点的MBR分区表（相对于GPT）。\n简介 **MBR这个概念首次出现，是在1983年的IBM PC DOS 2.0操作系统当中，因此也被称为DOS分区结构。**实际上，MBR分区结构是一种应用非常广泛的硬盘分区结构，不仅仅是DOS，包括windows系列操作系统、Linux以及基于X86架构的UNIX操作系统等平台均有使用。\nMBR的英文全称是Main Boot Record（一说Master Boot Record），翻译过来的中文名称是主引导记录。\n它主要有以下几个特点：\n1、在我们常见的电脑中，一般是BIOS+MBR、(U)efi+GPT的组合；\n2、MBR分区表中单个分区最大仅支持2TB，而整个分区表最大也是仅支持2TB的硬盘；\n3、MBR分区表最大只支持四个主分区（可少不可多），如果觉得四个不够用，可以使用三个主分区+一个扩展分区的组合（扩展分区仅能存在一个，但扩展分区下可以分出无数个逻辑分区）；\n▲图为MBR分区常见的两种组成\n结构 MBR分区表占用一个扇区，位于硬盘的0磁头、0柱面、1扇区。\n这里需要引用一下度娘百科对于磁头柱面扇区这三个概念的解释。\n磁头(Heads)：每张磁片的正反两面各有一个磁头，一个磁头对应一张磁片的一个面。因此，用第几磁 头就可以表示数据在哪个磁面。\n柱面(Cylinder)：所有磁片中半径相同的同心磁道构成“柱面\u0026quot;，意思是这一系列的磁道垂直叠在一起，就形成一个柱面的形状。简单地理解，柱面数=磁道数。\n扇区(Sector)：将磁道划分为若干个小的区段，就是扇区。虽然很小，但实际是一个扇子的形状，故称为扇区。\n▲图为磁头、柱面、扇区示意图\n①不管是CHS寻址还是LBA寻址，都需要知道磁头柱面和扇区来对数据进行定位；\n②通常来说，一个扇区的大小为512字节（Byte）；\n③该扇区为隐藏扇区，实际上，0磁头0柱面的扇区均为隐藏扇区，一般的硬盘访问命令无法访问，因此MBR分区表放在此处比较安全，不容易遭到破坏，同理，许多病毒也会隐藏在这里，不容易被查杀。\n由于MBR扇区中大部分都被MBR所占领，因此这个扇区就被称为MBR扇区。而MBR扇区主要是由MBR、DPT和结束标识三个部分组成。\n▲图为标准MBR结构\n工作原理 MBR的产生并不依赖于任何一个操作系统，在电脑开机通电后，BIOS进行自检，自检完成后就开始执行MBR中的启动代码（Bootloader），通过读取启动代码来确定需要引导的系统。因此，双系统或者是多系统安装完成之后，需要修改引导启动项，即为修改此处的启动代码（Bootloader）。\n执行完启动代码之后，系统下一步就会读取MBR扇区中的最后两个字节，如果是55AA，则继续读取磁盘分区表DPT来确定是否有活动分区，从而启动系统；如果不是55AA，系统会认为该MBR为非法MBR，系统将停止操作并报错。\n▲图为MBR分区表的硬盘开机过程\n这里再额外提一下。\nMBR中的选用磁盘标识，这个在MBR分区中比较重要。如果安装的是windows操作系统的话，系统在启动时需要依赖该磁盘签名来识别硬盘并确定该磁盘是否初始化。\n而DPT的作用其实非常简单，就是管理磁盘的分区信息。如果DPT被破坏，那么系统将无法识别该磁盘上的所有分区，并且有可能会提示需要格式化磁盘。\n一般小白遇到这种情况会比较无奈，只能格式化，对磁盘中宝贵的数据说拜拜，但是，其实只要修复DPT，就可以读取分区，找回数据，实际上，硬盘的数据一直都在那里。那怕是对硬盘进行一般的格式化操作，也只是将DPT中对应的分区的数据清空，并没有影响到原来的在其他扇区的数据，此时只要不进行数据的读写操作（防止覆盖原有数据），还是有可能找回丢失的数据的。\nDPT DPT其实有挺多可以讲的地方，这里拿一个分区（16Byte）为例，讲解一下MBR分区最大只能识别2TB硬盘的原因。\n分区项表内容及含义 注：1 Byte=8 bit\n▲图为MBR中的DPT的分区项表的内容及含义\n再对其作进一步的解释：\n第1字节的引导标识：如果该分区内安装了操作系统，则该分区必须为活动分区，方可成功引导该分区内的操作系统（即为80H）；\n第5字节的分区类型符：记录该分区的类型，每一种类型都有一个对应的值，如00H——表示该分区未用（即没有指定，DOS和win不识别该类型分区）、06H——FAT16分区；0BH——FAT32分区、 07H——NTFS分区等；\n第2、3、4、6、7、8字节：记录了这个分区的起始和结束磁头、扇区、柱面，即可确定该分区在硬盘中的位置以及该分区的容量；\n最后的8个字节就是MBR分区为何最大只能识别2TB容量的原因了。\n前提知识背景：\n①\t计算机中采用2进制，bit是最小的大小计量单位；\n②\t1 Byte=8 bit；\n③\t一个扇区的大小是512Byte；\n④\tByte、KB、MB、GB、TB、PB这六者按照从小到大的顺序排列，且相邻两者间差1024倍（即2^10）；\n⑤\t在上面的缩写中，大写的B表示Byte，小写的b表示bit，两者相差8倍；\n由于只有4 Byte来记录本分区的总扇区数，4 Byte=32 bit，在2进制即有2^32种状态，假设全部为1，则最多有2^32个扇区。即\n所以MBR分区中单个分区最多只能识别2TB容量，而由于记录已使用的扇区的字节数也是4，所以整个分区最多也只能识别2TB容量。\n扩展分区 前面我们已经提到了当四个主分区无法满足需求的时候，可以使用三个主分区+一个扩展分区的方法，在扩展分区下，理论上可以分出无数个分区（实际还要看操作系统的限制等其他因素）。\n这里还要介绍一个概念——**EBR（Extended Boot Record），即扩展分区引导记录。其原理和工作模式与MBR非常相似，但它是链式的，**也就是说一个EBR不够用，可以再增加一个，新增加的EBR链接在旧的EBR之后，一个链接着一个，就像链条一样。理论上就可以实现无数个分区。\nMBR分区表的内容介绍到这里就结束了，如果对它还有兴趣的话，或者觉得这里的介绍太过简单的话，可以去查询一些相关书籍和论坛。另外，如果文中有任何错误，还望不吝赐教。\n","permalink":"http://localhost:9011/20170808-mbr/","summary":"\u003cp\u003e目前来说，比较主流的两种硬盘的分区表是MBR和GPT（GUID）。今天小七带大家了解一下比较旧一点的MBR分区表（相对于GPT）。\u003c/p\u003e","title":"MBR分区表"},{"content":"不知道大家现在还有没有使用屏保的习惯，反正小七是有的。毕竟在挂机下载，或者是写作业需要开着电脑的时候，开着屏保还是相当不错的。\n今天小七就给大家安利一款在Windows平台上免费的屏保软件吧。（苹果系貌似现在也免费了）\n话不多说，咱们进入正文。\nFilqlo简介 Fliqlo原本是一款在iOS应用商店里面的付费时钟屏保软件，但是由于其复古简单的设计，迅速吸引了一大波粉丝，现在也有了Windows版本和Mac版本。\n他们的官网是fliqlo.com，需要科学上网才能正常访问。有兴趣的童鞋可以去他们的官网看看。\n下面搬运一小部分他们官网的介绍。\nFliqlo for iOS is a clock app that allows you to make your mobile/tablet device screen look like a flip clock、Thanks to its visibility, you can read the time even from a distance.\nFeatures Enlarge/reduce to any size\nSwitch between 12/24 hour clock (* without a leading zero)\nSwitch between portrait and landscape mode\nPrice $0.00 Free for charge\n小七注：windows平台的版本并不需要收费，因此大家可以放心下载，如果想支持开发者的话，可以通过它们网站下方的paypal来进行捐赠。\n设置教程 Mac \u0026amp; IOS 对于iOS和Mac平台的用户，直接打开AppStore输入关键字Fliqlo下载安装即可使用。Mac用户也可以去官网下载。\nWindows 至于Windows的用户就稍稍麻烦一些，需要科学上网到他们的官网进行下载。\n为了方便大家使用，小七已经将windows版本从官网搬运过来，点击这里即可下载。\n链接：https://pan.baidu.com/s/1eROCql0 密码：7w2w\n下载完成之后，我们直接双击打开。就会弹出windows中的屏幕保护程序的设置界面。\n我们点击设置，可以看到有几个简单的参数可以进行修改。\n它们分别是调节12/24小时制，放大和缩小（最大125%）和恢复默认设置。\n▲默认设置\n▲放大到125%（最大）\n▲切换为24小时制\n小七注：由于windows平台是免费的，所以BUG的存在也是不可避免的，初次设置的时候可能会出现崩溃的情况，但是只要重启一下电脑就可以解决。（只是初次设置时可能需要重启，不是每次都要重启）\n虽然Fliqlo在功能上没有之前很火的Wallpaper Engine那么强大，但是它几乎不占用系统资源且在安装上要简单不少。\n玩腻了Wallpaper Engine的同学，确定不来一个Fliqlo试试吗？\n","permalink":"http://localhost:9011/20170703-filqlo/","summary":"\u003cp\u003e不知道大家现在还有没有使用屏保的习惯，反正小七是有的。毕竟在挂机下载，或者是写作业需要开着电脑的时候，开着屏保还是相当不错的。\u003c/p\u003e\n\u003cp\u003e今天小七就给大家安利一款在Windows平台上免费的屏保软件吧。（苹果系貌似现在也免费了）\u003c/p\u003e","title":"Filqlo时钟屏保"},{"content":"今天聊一个很怀旧的话题。\n相信很多人都知道我们的电脑是从C盘开始的，C盘也就是我们平时说的系统盘。\n很多人可能会好奇，为什么要从C盘开始，而不是从A盘开始？A盘和B盘去哪了？\n其实要解释的话，一句话就够了。 AB这两个盘符是预留给软驱的。 换句话说， AB盘是计算机发展初期给软驱留的位置。\n下面是讲故事时间。\nSystem 370 大概是在20世纪70年代初，准确来说是1970年6月30日的样子，IBM推出了System360的继任者——System 370。\n▲System/370 Model 145（图片来源于IBM官网）\n关于System370的功能进步小七就不在这里赘述，有兴趣的童鞋可以打开IBM官网输入System370进行搜索既可查看相关信息。\n但是IBM的System370计算机面临这样一个问题，就是这种计算机的操作指令存储在半导体内存中，一旦计算机关机，指令便会被抹去。\n这句话是什么意思呢？\n**说白了就是这种电脑并没有我们现在意义上的硬盘（ROM），它只有半导体内存（RAM），**因此只能将数据都存储到半导体内存中。而我们都知道，RAM是断电不保存数据的，也就是说，万一一个不小心重启一下电脑或者是断电了，你辛辛苦苦写了几天几夜的代码就没了……\n想象一下你在办公室奋战几天几夜，赶在DDL之前把代码写了出来，然后累到在办公室睡着了，结果那晚就断电了，第二天PM找你要代码，好家伙，你要怎么办？\n8英寸软盘 IBM当然也意识到了这个问题，所以早在1967年，IBM的SanJose实验室的存储小组受命开发一种廉价的设备，为大型机处理器和控制单元保存和传送微代码。这种设备成本必须在5美元以下，以便易于更换，而且必须携带方便，于是软盘的研制之路开始了。\n研发组：又要便宜又要便携，你咋不上天呢？\n这个其实可以算是现代的软盘驱动器的鼻祖，但是在携带形式上又有点像U盘等移动存储介质。事实上，在当时计算机的体积没有达到足够小以至于能满足人们便携的时候，这种方式确实是最靠谱也是最保险的。 想象一下：一个程序猿把自己花高价买来的DOS系统装到软驱里面，然后再把自己花了毕生精力写的代码放到软驱里面，然后这很可能就是这个程序猿最大的财富了。\n乍一想，怎么有点武功秘诀的感觉？\n1971年，还在IBM推出了第一款8英寸的软盘，这是一种直径8英寸的表面涂有金属氧化物的塑料质磁盘，这个就是真正意义上的现代软盘驱动器的鼻祖了。\n▲图为8英寸软盘（图片来源见水印）\n磁盘之父——Alan Shugart 事实上，软盘的成功推出并没有我们字面上理解的这么简单，其实早在20世纪50年代时，IBM公司董事长小托马斯·沃森迅速把事业扩展到美国西海岸，下令在加利福尼亚圣何塞市附近新建实验室和工厂。约翰逊带领着30多名青年工程师，在不到三年时间，就为IBM创造了引人注目的技术成果——磁盘存储器。在约翰逊领导IBM圣何塞实验室研制硬盘的过程中，一位名叫Alan Shugart的青年工程师发挥了关键作用。\n实际上，Alan Shugart也是一个硅谷的传奇人物，年幼家贫的他曾经三度创业，并且两度被自己公司的董事会扫地出门，其中就包括了著名的希捷公司（全球最大的PC硬盘制造商之一）。尽管如此，这位令人敬佩的前辈还是保持着对生活的热爱，实在是值得我们学习。\n后来因为Alan Shugart对于磁盘的杰出贡献，人们亲切地称呼他为磁盘之父。\n▲Alan Shugart\n25英寸软盘 上面说到的Alan Shugart，不久后就离开了IBM，并且创办了一家名为Shugart Associates的公司。没错，就是用他自己的名字命名的公司。\n当时Alan Shugart和其他几位联合创始人的目标就是打造一家出色的八英寸软盘驱动器制造公司。因为，当时软盘驱动器制造工业刚刚起步，而且这项技术是IBM公司的薄弱环节。于是，他们就决定以此为契机大干一场。\n但是，8英寸软盘虽然便携，始终还是大了些。后来他们就推出了5.25英寸的软盘。\n▲如图从左往右以此为3.5/5.25/8英寸软盘（图片来源见水印）\n5英寸软盘 5.25英寸的软盘虽然从体积到容量上都有了一定的进步，但它还是有很多缺点，比如软盘采用的外包装比较脆弱，容易损坏，体积也比较大。因此很多厂家并没有满足于这种软盘，他们都在不断地进行探索，以寻求更为先进的软盘。\n新一代软盘的开发终于被日本的索尼公司拔得头筹。\n1980年，索尼公司率先推出体积更小、容量更大的3.5英寸软驱和软盘，不过刚推出的时候在当时并没有被一些主要PC厂家所接受，市面上流行的依旧是5.25英寸的软盘。\n1982年，微软基于索尼的3.5英寸软盘标准制定了统一的行业标准。\n但是标准制定了是一回事儿，人家用不用就是另外一回事儿了。关于3.5英寸软盘的普及，这里我们还要提一下苹果公司。\n1984年，苹果推出的麦金塔（Macintosh）电脑，根本就不存在5.25英寸软盘接口这种东西，直接就上了3.5英寸的软盘接口。这个能不能算是帮忙普及小七还真的说不准，但是至少可以说明苹果在换接口这件事上还是不遗余力的。\n2016款MacBookPro：喵喵喵？\n为什么要占用AB两个盘符而不是A一个？\n这个说起来就有点扎心了。当年的软盘容量普遍不大，一般都是几十KB。后来大的有几MB。\n那么我们说回一开始软盘容量还是几十KB的时候。假设只有一个软盘接口，然后你需要从一张软盘复制数据到另一张软盘上，你只能先将数据复制到半导体内存（RAM）中，然后拔出第一张软盘再插入另一张软盘完成复制。\n那么现在问题来了：\n假设我要一次复制60KB的数据，而RAM只有32KB那怎么办？\n额……好吧，那就给你两个软盘接口吧。\n还有一说是随着操作系统的发展，一些指令不能像以前那样将其从软盘中存放到RAM中操作，因此需要额外读取操作数据的时候只能再多加一个软盘接口。\n不管怎么说，两个软盘接口在某些时候总是比一个要方便很多的。\n硬盘驱动器 人的野心是永远没有办法满足的，不然就不会有科技的进步这一回事儿了。硬盘当然是越大越好，很快人们就发明了存储容量更大的硬盘驱动器（即为我们现在用的机械硬盘），硬盘驱动器也很快凭借其容量大，速度快的优势获得了广大人民群众的喜爱和支持。\n但是，早期的硬盘驱动器只能作为软盘的“从属”。\n这是什么意思呢？就是说硬盘驱动器刚出来的时候，操作系统还是装在软盘上面的，开机还是得要像之前那样，先插入软盘，然后开机，电脑从软盘中读取数据进行操作，硬盘还是不能当作启动盘的。所以硬盘驱动器的盘符就是从C开始的。\n然而随着技术的发展，到了MS-DOS 5.0时代，微软官方钦定了C盘作为系统的启动盘/主硬盘。\n随后，软盘渐渐地被淘汰，但是AB这两个盘符还是为他们空出来了。\n2006年，Alan Shugart因心脏病手术失败而永远离开了我们。\n","permalink":"http://localhost:9011/20170512-disk-a-b-introduction/","summary":"\u003cp\u003e今天聊一个很怀旧的话题。\u003c/p\u003e\n\u003cp\u003e相信很多人都知道我们的电脑是从C盘开始的，C盘也就是我们平时说的系统盘。\u003c/p\u003e\n\u003cp\u003e很多人可能会好奇，为什么要从C盘开始，而不是从A盘开始？A盘和B盘去哪了？\u003c/p\u003e","title":"电脑的AB盘去哪了？"},{"content":"给大家分享一个用win10自带命令完成驱动备份的方法。\n什么是驱动？ 驱动的英文名称叫做driver，一说到driver，大家就懂了，司机嘛。\n没错，驱动就是司机，只不是驱动是驾驭硬件的司机，而驱动是听从于系统和软件的调配的。\n也就是说，驱动是沟通硬件和软件的桥梁。\n那么在装系统之后，一定要干的一件事就是装驱动。一般来说，装驱动有三种方法：\n一是打开系统更新，让系统自动搜索安装； 二是自己手动下载，去官网等比较可靠的网站下载然后手动安装； 三是用第三方软件，下个驱动精灵驱动人生什么的一键安装。 这三种方法都各有优劣，**但是，有一点它们是一样的，那就是都需要联网下载。**一些小的驱动还好，几MB或者几十MB，大的轻松达到四五百MB，下载起来费时费力，因此最好的方法当然是事先备份。\n而对于大多数人来说，备份就是下载一个驱动精灵/驱动人生什么的软件，然后再一键备份，恢复的时候再安装这个软件来恢复。\n小七个人还是比较喜欢用系统自带的命令来完成备份这个操作。\n方法 新建备份目录 我们首先在非C盘的磁盘驱动器里面新建一个文件夹，命名的话随意，自己能记住就行，建议直接新建在根目录，这样后面会比较方便操作（这里小七在X盘根目录下新建了一个名为drivers的文件夹）；\n找到CMD/Powershell 由于接下来的操作需要系统管理员的权限，而一般在运行菜单中运行CMD是没有系统管理员权限的，因此我们需要在这个目录下找到CMD，并且将他发送到桌面快捷方式以便于以后操作，目录：C:\\Windows\\System32\n小七注：温馨提示：微软在Win10创意者版本之后已经使用了功能更加强大的Powershell来替代CMD成为系统的默认命令执行工具，但是CMD依旧可用（就是相当于你的默认音乐播放器从酷狗音乐变成了网易云音乐），因此并不影响本次操作。\n因此所有能够用CMD完成的操作，用POWERSHELL也能够完成，具体使用哪个看童鞋们自己的爱好。 不嫌麻烦的同学可以直接忽略上一步，找到CMD后，右键选择以系统管理员身份运行；\n开始备份 然后我们在CMD窗口中输入下列命令：\ndism /online /export-driver /destination:X:\\drivers 注意：X：drivers部分为驱动的备份目录，根据情况不同此处也有所不同。\n然后稍等片刻，系统会自动备份所有的驱动；\n驱动的具体数量因电脑而异，驱动的数量多少与电脑的质量并没有必然联系。\n操作完成之后我们再打开刚刚选择的备份文件夹看看，会发现多了很多文件夹。\n每个文件夹都对应着一个驱动。看到这里可能有些童鞋就懵了，这命名，我怎么知道哪个文件夹是哪个驱动呢？\n答案是：不需要知道。\n恢复驱动 当我们需要安装驱动的时候，直接打开设备管理器\n点击左上角的设备管理器\n①找到需要安装的驱动（一般驱动程序有问题的硬件设备都会有一个黄色的感叹号）\n②右键点击我们需要安装驱动程序的硬件，选择更新驱动程序软件\n③选择下面的浏览计算机以查找驱动程序软件\n④然后选择刚刚备份的目录，并默认勾选下面的包括子文件夹，然后系统会自动帮你搜索安装驱动程序\n⑤最后安装完成\n这个操作难度不大，只要记住一条命令即可。适用于那些不喜欢使用第三方软件安装备份驱动的人群。\n","permalink":"http://localhost:9011/20170509-backup-drivers/","summary":"\u003cp\u003e给大家分享一个用win10自带命令完成驱动备份的方法。\u003c/p\u003e","title":"驱动备份(免第三方)"},{"content":"因此小七整理了一些常见的安卓刷机相关的基础概念，希望能给大家一些帮助。\n什么是安卓刷机？ 刷机，就是通过一定的技术手段向安卓手机中刷入ROM的过程。那么随着刷机的不断发展，刷入的东西类型也在逐渐增多，包括REC、ROM、内核甚至是基带固件等等。\n刷机方式都有哪些？ 目前来说，主流的刷机方式基本可以分为两种：线刷和卡刷。 线刷和卡刷最大的区别就是：线刷刷机时需要通过USB数据线操作,而卡刷是通过内存卡进行操作。\n关于卡刷 因为在早期，安卓手机的内存普遍偏小，因此一般会通过支持内存卡的方式来让用户获得更大的存储空间。而刷机包相对于早期的手机来说，算是比较大的了，一般来说是将刷机包复制进入内存卡中，再通过REC将刷机包刷入，最终完成整个刷机操作。 这里提一下，我们平时的正常情况下的手机系统升级，其实就是卡刷的一种。系统将系统更新文件下载到手机存储空间中，然后重启通过REC刷入更新的增量包或者是完整的刷机包。\n温馨提示：\n1、增量包指的是在某个特定的系统版本上更新加入某些功能的更新包，它是不带有完整的整个手机操作系统的，因此在刷机的时候是不能当作刷机包使用的。 2、有些手机尽管不支持内存卡，但是会将手机自带的存储空间划分一部分出来当作内存卡，这部分的空间在地位上跟内存卡是一样的，只是不能像内存卡那样取出手机进行读写操作。这种情况前两年还比较常见，现在这么操作的手机好像比较少了。\n关于线刷 线刷最明显的特点就是需要通过数据线来操作，因此在线刷的过程中，需要非常注意数据线和手机的连接情况，千万不能断开数据线或者是关闭计算机或者是关闭手机等。\n同样地，由于线刷需要使用数据线的特点，它所能够进行的操作也就比卡刷更多，同时也更危险，比如说我们之前的刷入REC，就是通过线刷操作的。而很多情况下，手机不小心刷成砖了，救砖操作也是需要通过线刷进行的。\n而现在很多手机厂商提供的官方刷机助手，都是需要连接数据线来进行操作，因此也是属于线刷。\n由于现在手机已经有取消支持SD卡拓展的这个趋势，因此线刷的重要性也就凸现出来了。但是未来线刷和卡刷应该是会并存的，原因很简单：卡刷可以进行简单的刷机和系统更新等操作，线刷则可以进行更深度更敏感的操作。\n什么是ROM image？ 对于安卓刷机来说，这个ROM image跟我们一般说的ROM是两个东西。 这里我们简单地讲一下ROM和RAM这对双子星。\nRAM RAM的全称是Random Access Memory，中文名是随机存取存储器，我们日常称之为运行内存。也就是说，它是不能够断电保存数据的，主要是在我们的电子产品工作的时候，将需要调用的文件预先从ROM中读取出来，放入到RAM中（因为一般来说RAM的读写速度要比ROM快），以便于我们的CPU/GPU或者是SoC等调用。\nROM ROM的全称是Read-Only Memory，中文名是只读内存，就是我们平时说的手机存储空间（用来放音乐图片视频等等的那个）。也就是说，它是可以断电保存数据，主要是用来长期存储数据的。\nROM image **ROM image的全称是Read-Only Memory image，简称是ROM，**中文名称叫只读内存镜像，因此会有些容易跟上面的ROM混淆。这里注意，ROM image的存在形式是一个文件，相当于我们电脑平时装系统的镜像文件。\n那么在安卓刷机中，我们常常用ROM来指代自己要刷入的操作系统。\n什么是OS和UI？ **OS就是Operating System，中文名叫做操作系统。**比如说我们的Windows操作系统，安卓（Android，基于Linux）操作系统，苹果操作系统（iOS，全称叫iPhone Operating System）都可以叫做OS。\n而对于移动端来说，除了比较常见的Android和 iOS，还有比较小众的WindowsMobile，Sailfish（旗鱼），Ubuntu（乌班图），BlackBerry OS（黑莓）等等。\n**UI就是User Interface，中文名叫做用户界面。**比如说我们常见的MIUI（小米），EMUI（华为，全称Emotion UI），Flyme（魅族）等等都是UI。\n而一般来说，国内的安卓厂商为了做出自己的特色，都会有属于自己的UI，由于他们这些UI都是基于安卓，因此称不上是OS，只能算作UI。如果你见到名字当中有OS的（没错说的就是锤子的Smartisan OS），尽管名字里面有OS，但是还是属于UI。\n由于手机厂商在UI的发力程度越来越高，修改的东西也越来越多，所以就有一种很尴尬的情况出现了，说它们是UI吧，好像又不止，说是OS吧，好像又不够格，然后就搞出了一些名词，比如说深度定制UI等等。\n刷机的时候，为了方便，我们一般来说是统一使用ROM或者是刷机包来指代这些东西。\n什么是砖？ 这里的砖跟我们平时说的搬砖的砖不是一个概念。**这里的砖指的是在刷机过程中因为操作不当而无法正常使用的手机，**用不了的手机跟砖头没什么区别，因此人们很形象地称之为砖。一般来说，砖也会根据手机“受内伤”的程度进行划分，一些受伤较轻的砖还是可以救回来的，而那些受伤较重的……\n什么是BL？ BL简介 **BL的全称叫做Bootloader，中文名字叫做（小七也不知道叫什么）。**一般来说，大家都认为Bootloader是嵌入式系统在通电后执行的第一段代码。\n在这里用人话来说就是：手机开机要干的第一件事。\nBL在功能上有些类似于我们电脑的BIOS，但是实际上两者并不完全一样，手机中是不存在BIOS这一个说法的。\nBL能干什么 那么要手机在通电之后（也就是我们按下开机键之后），需要干的第一件事是什么呢？打个比方来说，将军跟士兵们说要出征了，那么士兵们要干的第一件事当然是检查一下自己的装备、身体状况和精神状态有没有问题。\n而对于手机来说，这里要干的事情就是初始化各个组件（屏幕、闪存、SoC、各个传感器等等），检查即将运行的固件、系统等等是否正常。\n什么叫锁BL 既然BL能够检查即将运行的固件和系统，那么手机厂商就能在BL上动手，直接锁死BL，使得它只能识别官方的固件和系统，从而确定手机的稳定性和安全性。但是，这也就意味着不能刷机了，因为很多官方的REC只支持官方的ROM，因此刷机很多时候第一步要做的事情就是解锁BL，**而对于很多厂商来说，解锁BL意味着放弃保修，**因此刷机的童鞋一定要注意。\n怎么解锁BL？ 解锁BL的方法我们可以分为两种，官方解锁和暴力解锁。\n官方解锁的意思就是通过官方渠道解锁，一般是去官网或者是官方论坛，提供手机的IMEI码等进行申请，然后就可以获得解锁BL的解锁码。\n暴力解锁的意思就是非官方渠道解锁，对于一些厂商来说，它们是不会提供BL的解锁方式的，因此需要一些民间的大神破解BL，破解的途径一般是利用各种漏洞，因此这种暴力破解的方法难度要更大一些。\n由于安卓机型众多，小七难免会有疏漏，小七建议童鞋们可以去自己手机对应的论坛或者是贴吧了解详细情况。\n什么是REC？ REC的全称叫做Recovery，直译中文名叫做恢复，百度百科有个很奇怪的翻译叫做：Android手机备份功能。\n实际上，REC的功能远不止备份这么简单。进入REC模式后，我们可以通过卡刷来升级操作系统，也可以擦除（wipe）手机数据，还可以恢复出厂设置，备份当前数据等等。\n由于安卓系统的复杂原理，REC所处于的分区和我们手机系统所处的分区不同，因此，如果你不小心把系统搞崩了，可以进入REC对其进行一定的修复。也就是说，因为REC和手机的ROM处于不同分区，而不同分区之间是不会互相影响的，用REC可以进行一定程度的刷机。\n有些人会将REC比作我们修电脑时常用到的PE，实际上两者在地位上有些相似，但是在功能上还是有一定的差距的。\n官方的REC一般限制较多（只能刷入官方的固件、补丁、更新等等），而第三方的REC则在功能上要更加丰富一些，除了能够任意刷入第三方的ROM之外，有些甚至能够支持调整分区大小、在手机上实现双系统（早期的小米手机有这个功能，现在的不太清楚）。\n以前比较旧的版本的REC在刷机的时候只能通过音量键和电源键操作，并且在卡刷的时候只支持将刷机包以update的文件名，zip的文件格式放入SD卡的根目录下进行操作，现在的REC则要好很多，除了能够支持触控操作，还能刷入任意命名的刷机包。\n什么是fastboot？ Fastboot的功能与REC相似，但是要更为高级，更为接近系统的底层。\n这句话可能比较难理解，这里简单地解释一下。更为高级的意思就是Fastboot能够执行的操作要比REC更多，比如我们之前的使用ADB刷入REC就是进入了fastboot模式执行的操作。\n而更为接近系统的底层则意味着优劣共存。比如说，当你折腾手机的时候，不小心把REC搞崩了，没关系，我们还能够进入fastboot模式进行刷机，重新刷入REC，然后继续愉快地刷机。因此，几乎所有的救砖操作都是在fastboot模式下进行，但是，如果你连fastboot都进不去，那就意味着你的手机很有可能真的变砖了。\n注意，我们如果使用第三方刷机软件（刷机精灵、刷机大师这些）进行一键刷机操作的时候，是属于线刷，它们会让你的手机进入fastboot模式，然后刷入这些刷机软件自己的REC，再进行刷入ROM的操作。\n什么是恢复出厂设置/双清/三清？ 恢复出厂设置 顾名思义，恢复出厂设置就是将你的手机恢复到出厂状态，也就是手机刚从工厂里面加工组装完成，包装到包装盒里面的状态。需要注意的是如果你的手机升级了系统（安卓版本更新或者是UI的大版本更新），那么恢复出厂设置之后一般是不会回退你已经更新的版本的。 同时，恢复出厂设置默认不清除储存卡上的文件，且你先前做的关于设置的更改都不会保存。\n双清 双清和三清往往需要进入REC或者是fastboot或者是使用ADB进行操作。在REC中，一般都是wipe data和wipe cache。\nwipe data/factory reset:\t清除用户数据并恢复出厂设置(刷机前必须执行的选项) wipe cache partition:\t清除系统缓存(刷机前执行，系统出问题也可尝试此选项,一般能够解决)\n三清 三清比双清多了一个清除虚拟机缓存，基本上三清已经是最彻底的清除手机数据的操作了。\nwipe data/factory reset:\t清除用户数据并恢复出厂设置(刷机前必须执行的选项) wipe cache partition:\t清除系统缓存(刷机前执行，系统出问题也可尝试此选项,一般能够解决) wipe dalvik cache:\t清空虚拟机缓存(可以解决一些程序fc的问题)\n很多人在刷机前都会有双清或者是三清的习惯，这样一来可以使手机刷机更加纯净，二来也可以避免之前残留的缓存文件对新刷入的ROM产生各种莫名其妙的影响，导致出现一些奇奇怪怪的问题（比如耗电量暴增，经常卡顿等等）。 现在一般三清用的不多，双清要更加多一些，原因就是三清会比较复杂，风险也比较大。\n什么是OTA升级？ **OTA，全称为On-The-Air，中文名不详。**OTA更新的原理是通过网络下载更新包，存储在手机的存储空间中，然后再重启进行升级。OTA升级在原理上是属于卡刷的。\n那么我们在日常生活中哪里会接触到OTA升级呢？实际上，我们所有的官方推送的手机更新，都是属于OTA更新。而能进行OTA升级的前提条件一般都是使用的官方REC+官方ROM+没有ROOT。是否解锁BL并不影响。\n什么是底包？ 之前我们说过，厂商可以通过锁BL的形式，使得手机只能识别官方的固件、ROM等等。那么这是不是意味着我们就不能刷机了呢？当然不是，要相信高手在民间，既然它要，我们就给它。\n底包就是在这种情况下诞生的产物，它的主要目的就是使得手机能够先识别这个官方的ROM，然后我们再在这个ROM上面刷入我们自己制作的第三方ROM。\n此处最典型的例子就是华为的EMUI，因为华为手机的特点，很多基于EMUI的第三方刷机包都需要基于某个特定的EMUI版本才能刷入。因此有一部分大神在制作第三方的ROM的时候，会基于官方的ROM进行修改和精简，并且加入一定的新功能。而这些制作出来的刷机包，就是需要先刷入底包的。由于华为对于刷机的支持并不友好，而且华为的主要用户群体并非是这一类的刷机发烧友，因此很多华为的机型的刷机包都是属于上述类型。\n这里再额外补充一下。如果你的华为手机原来就是官方的ROM，在刷入这一类的刷机包的时候，只要你的EMUI版本符合这个第三方的刷机包的要求，是不需要刷入底包的。但是，如果你的手机已经刷入了第三方的ROM，再需要刷回这一类的ROM的时候，就需要事先刷入底包了。\n什么是ADB？ **ADB的全称是Android Debug Bridge，中文名就是“安卓调试桥”，**也就是起到了一个连接安卓手机和电脑的桥梁的作用，可以实现许多安卓手机的调试功能（此处不一一列举），因此很形象地被称呼为Android Debug Bridge。\nADB其实并非是第三方工具，它是谷歌官方出品的工具，因此最纯净的下载方式就是去谷歌官网下载（需要科学上网）。\n如果你嫌去官网麻烦，可以直接打开搜索引擎下载，小心一些不要下到流氓软件则问题不大。\n什么是内核？ **内核，顾名思义就是我们手机的核心，准确的来说是我们手机的操作系统的核心。**事实上不仅是手机的操作系统，每个操作系统都有对应的核心。\n那么核心主要负责什么呢？**基本上，所有沟通硬件和底层驱动的任务都是由核心来负责的。**而不同的核心会有不同的特性。比如说：如果你的手机耗电量比较严重，那么你就可以刷入一个第三方优化过的内核，来达到省电的效果。\n什么是ROOT？ ROOT在英语里面有根源的意思，**而在安卓手机中，ROOT则意味着获取手机系统的最高权限，**也就是相当于手机系统的超级管理员，能够执行一切操作，包括但不限于修改系统底层文件，后台安装应用程序，自动发送扣费短信等等，因此对于不知道如何使用ROOT权限的小白来说风险还是相当大的。\n但是ROOT作为一把双刃剑，有坏也有好。修改系统文件虽然有可能导致系统损坏，但是也可以解锁一些手机在出厂时因为各种原因被手机厂商锁死的功能，又或者是卸载一些没用的系统软件，让手机从臃肿变得清爽。\n因此关键还是看你怎么使用它。\n什么是Xposed？ Xposed是一个框架，它本身是不具备具体的功能的。但是有很多模块可以通过在Xposed框架上运行，实现一些很有意思的功能如各种个性化的手机定制。\n因此，打个比方来说就是：Xposed本身相当于是一个容器，是用来装东西的，放米就成了米缸，放水就成了水缸，放木炭硫磺硝酸钾就成了炸弹。\n","permalink":"http://localhost:9011/20170507-android/","summary":"\u003cp\u003e因此小七整理了一些常见的安卓刷机相关的基础概念，希望能给大家一些帮助。\u003c/p\u003e","title":"安卓刷机基础"},{"content":"虚拟内存，分页文件，交换空间等等，它们本质上都是一个东西。\n硬盘内存基本概念 1.1 什么是虚拟内存 虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存（一个连续完整的地址空间），而实际上，它通常是被分隔成多个物理内存碎片，还有部分暂时存储在外部磁盘存储器上，在需要时进行数据交换。目前，大多数操作系统都使用了虚拟内存，如Windows家族的“虚拟内存”；Linux的“交换空间”等。\n这么专业的解释对于大家来说估计是有点难理解的，那么小七简单地来说一下：\n小七注：虚拟内存就是在硬盘中“割了一块地”当作内存用。是很典型的内存不够，硬盘来凑的行为。\n再来简单地解释一下内存和硬盘：\n出于成本和技术的考虑，电脑的存储空间其实是分为两种的，也就是我们常说的内存（RAM）和硬盘（ROM）。**两者有一个很重要的区别就是，内存是断电不保存数据，而硬盘是断电可以保存数据。**因此，我们的需要长期保存的数据，比如各种照片视频游戏，都是存储在硬盘当中。\n那么我们的电脑和手机在处理任务的时候，数据的是怎么流通的呢？前面我们说过了，数据是存储在硬盘里面，但是处理数据的是处理器，因此这里就有一个过渡的介质，也就是内存。\n原理大概是这样：硬盘→内存→CPU\n再来看一下更深一点的解释。\n1.2 内存（RAM） **RAM的全称是Random Access Memory，中文名是随机存取存储器，我们日常称之为运行内存。**也就是说，它是不能够断电保存数据的，主要是在我们的电子产品工作的时候，将需要调用的文件预先从ROM中读取出来，放入到RAM中（因为一般来说RAM的读写速度要比ROM快），以便于我们的CPU/GPU或者是SoC等调用。\n1.3 硬盘（ROM） **ROM的全称是Read-Only Memory，中文名是只读内存，就是我们平时说的手机存储空间（用来放音乐图片视频等等的那个），电脑硬盘等等。**也就是说，它是可以断电保存数据，主要是用来长期存储数据的。\n虚拟内存为何而生 在电脑刚刚开始普及的时候，内存还没有我们现在那么大（稍微旧一点的电脑会是2G，现在的新电脑基本4G/8G起步，一些高端点的游戏本则是16G/32G/64G，土豪一般都是128G起步），内存如果不够，在运行一些对内存需求很“旺盛”的程序的时候，电脑就会吃不消，于是机智的程序猿就想到了用硬盘来当“替补“，也就是说：内存不够，硬盘来凑。\n这样一来，虽然解决了内存大小不够用的问题，但是却没有解决另外一个问题，就是速度。众所周知，内存的读写速度要远高于硬盘（即便是现在已经普及的SSD也是如此），那么在设置电脑的虚拟内存的时候，如果设置过小，可能会不够用，如果设置过大，则会拖低电脑的运行速度。\n所以合理设置虚拟内存，也是一门“艺术”。\n合理设置虚拟内存 3.1 找到虚拟内存设置页面 ①右键此电脑，点击属性；\n②点击左边的高级系统设置；\n③点击高级，找到性能，点击设置；\n④继续找到高级，找到虚拟内存，点击更改；\n小七注：虚拟内存会以一个或者多个隐藏文件pagefile.sys的形式存在于硬盘中，因此也叫做分页文件。\n3.2 设置虚拟内存的三种情况 第一种：系统自动设置，如下图中的自动管理所有驱动器的分页文件大小；\n第二种：手动设置最大值和最小值，如下图中的自定义大小；\n第三种：关闭虚拟内存，如下图的无分页文件；\n3.3 手动设置虚拟内存大小 ①如果我们需要手动设置虚拟内存的大小，则取消勾选自动管理所有驱动器的分页文件大小，然后点击自定义大小；\n填写好数值之后，我们要点击下方的设置，这一点很重要，不点击设置是不会保存你的更改的。\n小七注： 在驱动器这一栏里面，我们可以选择设置虚拟内存所占用的硬盘空间位于哪一个分区。由于小七的电脑只有C盘一个盘，因此无法给大家做示范。有需要的同学可以将虚拟内存设置在C盘之外的其他盘。\n一般来说，本身内存够用的情况下虚拟内存不宜设置过大。\n②然后我们点击确定，接着重启计算机即可。\n3.4 设置前后对比 最后小七附上两张禁用虚拟内存和设置虚拟内存的硬盘空间差距的对比的图片。\n▲禁用虚拟内存时的可用硬盘空间为44.8G\n▲启用后变为40.7G（差距可忽略）\n最后小七不得不提一下，尽管现在内存的价格飞涨，但是想要解决内存不够用的问题，靠虚拟内存是不实际的，最好的办法还是直接买一根内存加上。\n","permalink":"http://localhost:9011/20170504-pagefiles/","summary":"\u003cp\u003e虚拟内存，分页文件，交换空间等等，它们本质上都是一个东西。\u003c/p\u003e","title":"虚拟内存"},{"content":"前面小七给童鞋们介绍了关闭硬盘开机自检的方法，那么遇到一些特殊情况，需要手动检查硬盘的健康状况，有没有一些简单的方法呢？\n那么在这里小七给童鞋们介绍一下Windows自带的硬盘检测和修复工具CHKDSK。\nCHKDSK是什么？ CHKDSK，就是CHECK DISK的缩写，中文名就是磁盘检查。\n这个工具是Windows系统中自带的一个简单实用的硬盘检错和修复工具，用于验证文件系统的逻辑完整性。\n如果 CHKDSK 在文件系统数据中发现存在逻辑不一致性，CHKDSK 将执行可修复该文件系统数据的操作（前提是这些数据未处于只读模式）。\n**小七注：**只读模式即只能对文件进行读取操作而无法对其进行修改。\n如何使用CHKDSK检测磁盘？ 注意事项\n①不要中断CHKDSK的运行\n原因很简单：医生在给病人做手术的时候不能被中断。CHKDSK在运行过程中，视磁盘的情况不同，所需时间长短也可能会有很大的差异，因此请保持耐心。\n②如果需要修复磁盘错误，确保被修复的磁盘没有文件被打开\n如果有文件被打开了，在修复之前CHKDSK会报错。因此如果不能确定是什么文件被打开，最好进入DOS环境操作。\n步骤如下 以系统管理员身份运行CMD 由于对硬盘的检测操作需要系统管理员的权限，而一般在运行窗口中运行CMD是没有系统管理员权限的。 因此我们需要在这个目录下找到CMD，并且将他发送到桌面快捷方式以便于操作，目录：C:\\Windows\\System32。\n运行的时候，不要直接双击运行，右键菜单点击以管理员身份运行。\n输入命令 ①直接输入\nchkdsk X：/f 小七注： X为所需要检测修复的硬盘盘符，chkdsk和盘符X之间有一个空格，其余均无空格，所有符号请在英文半角下输入，也就是使用英文键盘，不要使用中文输入法。\n②直接按下回车，即会自动开始检测 如果检测出磁盘错误，会自动修复(一般修复分为三个阶段），如果没有检测出问题，则如下图：（图中的D盘是小七的一个U盘）\n如何检测系统盘？ 特别的，当你检测的是系统盘C盘时，由于此时正处于操作系统界面下，无法进行检测操作，所以询问你是否在下次启动系统时进行检测。\n输入Y后回车，然后重启即可。\n什么情况下需要用到CHKDSK？ 由于情况较多，下面只列举三种比较常见的情况。\n系统直接提醒 系统直接提醒你的时候，这种情况会比较严重，如果你的系统某个或者某些文件出现了问题，系统弹出提示框让你运行chkdsk工具进行检测，那么就可以照着上面的教程进行操作\n移动设备提示有错误 移动设备（U盘，移动硬盘等）插入电脑时提示有出现错误，系统询问你是否需要扫描并修复时，也可以使用这个，当然你也可以直接让系统来执行；\n非法操作之后 你的硬盘关闭了开机自检而且你对系统进行了非法操作（强制关机等）的时候，系统盘既有可能会出现错误或者是冗余文件。 这个时候运行chkdsk工具，则会帮你处理掉这些冗余文件，所以有些童鞋在有时候在运行完chkdsk工具后会发现自己的硬盘的可用空间增多了，就是这个原因\nCHKDSK全部指令 最后放出CHKDSK的全部命令，有兴趣的童鞋可以研究研究。\n","permalink":"http://localhost:9011/20170503-chkdsk/","summary":"\u003cp\u003e前面小七给童鞋们介绍了关闭硬盘开机自检的方法，那么遇到一些特殊情况，需要手动检查硬盘的健康状况，有没有一些简单的方法呢？\u003c/p\u003e\n\u003cp\u003e那么在这里小七给童鞋们介绍一下Windows自带的硬盘检测和修复工具CHKDSK。\u003c/p\u003e","title":"CHKDSK使用方法"},{"content":"相信有很多电脑都可能会出现开机硬盘自检的情况，那么其实对于大部分同学来说，开机自检有没有必要呢？\n引言 其实，开机硬盘自检的存在是有一定的道理的，因为很多电脑的硬盘（尤其是机械硬盘）可能由于使用者的一些不当操作，而产生各种各样奇奇怪怪的问题。\n这个时候，硬盘的开机自检就能够发挥作用，在一定程度上缓解这些问题，或者说是，预防这些问题。\n因此小七的建议是：硬盘自检，能不关就不关。\n小七注：奇奇怪怪的问题包括但不限于：系统文件丢失、引导文件丢失、硬盘产生坏道等等。硬盘坏道是物理级别的损伤，只能屏蔽坏道或者是更换硬盘，无法修复。\n但是总会因为各种需要而要关闭硬盘自检，小七在这里给大家分享一个通过修改注册表来关闭硬盘开机自检的方法。\n▲如图所示为戴尔笔记本的开机硬盘自检\n打开注册表编辑器 ①在任意情况下，我们直接同时按下windows键（开始菜单键）和字母键R，此时会出现一个运行窗口\n②然后我们在里面输入regedit（如上图所示），回车即可打开注册表编辑器（如下图所示）\n小七注：\n注册表编辑器中包含有全部系统设置及软件的有关信息，在不确定的情况下，请不要随意进行更改，注册表一旦损坏且如无事先备份，后果不堪设想\n**Win+R是系统级别的快捷键，**可以直接打开运行窗口，因此无论你处于任何应用窗口都可以直接使用此快捷键。\n定位到 BootExecute ①根据这个地址，找到Session Manager中的BootExecute\nHKEY_LOCAL_MACHINE → SYSTEM → ControlSet001 → Control → Session Manager\n②如下图所示，右键BootExecute，点击修改\n清空数值 ①清空里面的数值autocheck autochk *\n②点击确定\n*到这里，硬盘自检就已经被关闭了，如果想要重新开启，只需要将数值重新修改为autocheck autochk 即可。\n","permalink":"http://localhost:9011/20170502-disable-disk-onboot-check/","summary":"\u003cp\u003e相信有很多电脑都可能会出现开机硬盘自检的情况，那么其实对于大部分同学来说，开机自检有没有必要呢？\u003c/p\u003e","title":"关闭硬盘开机自检"},{"content":"今天给大家分享一个现在可能不太会用到的小技巧，将FAT32的磁盘无损转换为NTFS格式。\n为什么说不太会用到呢，因为fat32格式现在已经很少有人在用，基本要被淘汰了。当然，不排除会有这种情况出现，因此小七还是决定分享一下解决方法。\n下面进入正文。\n示范 下图是小七自己的一个64G的U盘，由于小七自己的粗心，格式化的时候选用了FAT32格式，这种格式是比较旧的一种文件系统格式，最明显的缺点就是： 即使你的磁盘有大于4G的空余空间，也无法向里面复制大于4G的文件。\n▲如图，小七的U盘有将近20G的空余空间。\n下面复制一个4.02G的文件。\n这是为什么呢？原理其实有点复杂，但是罪魁祸首就是这个FAT32的文件系统。\n小七的建议是，如果你的磁盘是U盘，那么先把里面的数据备份出来，然后格式化，选择文件系统为exFAT格式，就可以解决问题。\n温馨提示：NTFS格式的文件系统由于其原理的特殊性，会对U盘产生一定的损伤，因此不建议对U盘使用NTFS格式。\n方法 U盘 ** 方法如下： ** ①\t右键点击U盘，选择格式化；\n②\t在文件系统中选择exFAT，然后选中快速格式化，接着点击确定即可。\n硬盘 如果你的磁盘是固态硬盘或者是机械硬盘，那么小七建议可以使用下面这种方法，无损转换为NTFS，方便快捷。 ** 方法如下： ** ①\t同时按下win+R键，输入CMD（不区分大小写）；\n②\t我们在CMD窗口中输入下列命令：\nconvert X:/FS:NTFS （其中X为所需转换的磁盘盘符，符号需要在英文半角下输入，不区分大小写） 然后系统会自动检测磁盘是否存在问题并且开始转换文件系统格式\n③\t当然，如果你像小七这样出了错误，不要急，可能是磁盘有些小问题，我们再次使用chkdsk工具对磁盘进行检测和修复（chkdsk工具的使用方法可以点这里）\n④\t最后我们再次输入下面的命令\nconvert X:/FS:NTFS 就成功地转换文件系统格式为NTFS了。\n▲注意！小七此处将U盘转换为NTFS是会损伤U盘的，在这里仅仅是作为示范。\n当然，最后我们再来试一下能不能复制大于4G的单个文件。\n▲成了！\n最后小七说一下，很多时候电子设备出了问题，单一的教程或方法可能并不适用，这就很可能会用到多种方法或者是稍微复杂一点的知识，这个时候小七希望童鞋们不要气馁，多尝试。毕竟吃一堑长一智嘛。\n","permalink":"http://localhost:9011/20170418-fat32-to-ntfs/","summary":"\u003cp\u003e今天给大家分享一个现在可能不太会用到的小技巧，将FAT32的磁盘无损转换为NTFS格式。\u003c/p\u003e\n\u003cp\u003e为什么说不太会用到呢，因为fat32格式现在已经很少有人在用，基本要被淘汰了。当然，不排除会有这种情况出现，因此小七还是决定分享一下解决方法。\u003c/p\u003e","title":"FAT32无损转换为NTFS"},{"content":"对于Win10来说，直接点开任务栏的时间就可以看到具体的秒，但是有没有什么办法让秒这一个选项常驻在任务栏的时间里面，不用点开就能查看呢？\n答案是肯定的，我们只需要在注册表中进行简单地操作就可以实现这个功能啦。\nBAT文件 开始之前，我们先简单地说明一下这个bat文件。\nbat文件是DOS下的批处理文件，是一种文本文件（可以使用记事本编辑）。它能够将你写好的命令并存储在里面的命令直接调用cmd.exe这个程序帮你操作，省去了你重复操作的麻烦。\n方法如下：\n我们打开windows里面的记事本（也可以使用notepad++这款软件，如果已经安装了的话），在里面输入下列代码，然后右键另存为bat文件，运行的时候要以管理员身份运行，否则可能不起作用。\n显示秒 @echo off reg add \u0026#34;HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\u0026#34; /v ShowSecondsInSystemClock /t REG_DWORD /d 1 /f TASKKILL /F /IM explorer.exe echo. echo 重启explorer.exe START %windir%\\explorer.exe echo. pause 删除秒 @echo off reg delete \u0026#34;HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\u0026#34; /v ShowSecondsInSystemClock /t REG_DWORD /d 1 /f TASKKILL /F /IM explorer.exe echo. echo 重启explorer.exe START %windir%\\explorer.exe echo. pause 展示 最后上一张效果图\n","permalink":"http://localhost:9011/20170414-win10-show-seconds/","summary":"\u003cp\u003e对于Win10来说，直接点开任务栏的时间就可以看到具体的秒，但是有没有什么办法让秒这一个选项常驻在任务栏的时间里面，不用点开就能查看呢？\u003c/p\u003e\n\u003cp\u003e答案是肯定的，我们只需要在注册表中进行简单地操作就可以实现这个功能啦。\u003c/p\u003e","title":"win10时间栏显示秒"}]