diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..220f76d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+#=========================================
+# Author : Colben
+#=========================================
+
+/public/
+/resources/
+/themes/
+
diff --git a/README.md b/README.md
index fd8e7a0..419f716 100644
--- a/README.md
+++ b/README.md
@@ -48,15 +48,11 @@
- 克隆本仓库的前端目录
```bash
docker exec -ti gitea /bin/bash # 进入 gitea 容器
- mkdir /html/www.colben.cn
- chown gitea.www-data /html/www.colben.cn
+ chown gitea.www-data /html
su - gitea
- cd /html/www.colben.cn
- git init
- git remote add -f origin https://git.colben.cn/colben/www.colben.cn.git
- #git config core.sparsecheckout true
- #echo 'public' > .git/info/sparse-checkout
- git pull origin master
+ cd /html
+ git clone https://git.colben.cn/colben/www.colben.cn.git
hugo
+ exit # 退出容器
```
diff --git a/archetypes/default.md b/archetypes/default.md
new file mode 100644
index 0000000..f7ccd84
--- /dev/null
+++ b/archetypes/default.md
@@ -0,0 +1,9 @@
+---
+title: "{{ replace .TranslationBaseName "-" " " | title }}"
+date: {{ .Date }}
+lastmod: {{ .Date }}
+draft: true
+tags: []
+categories: []
+---
+
diff --git a/archetypes/default.md.origin b/archetypes/default.md.origin
new file mode 100644
index 0000000..f4ff840
--- /dev/null
+++ b/archetypes/default.md.origin
@@ -0,0 +1,43 @@
+---
+title: "{{ replace .TranslationBaseName "-" " " | title }}"
+date: {{ .Date }}
+lastmod: {{ .Date }}
+draft: true
+keywords: []
+description: ""
+tags: []
+categories: []
+author: ""
+
+# You can also close(false) or open(true) something for this content.
+# P.S. comment can only be closed
+comment: false
+toc: false
+autoCollapseToc: false
+postMetaInFooter: false
+hiddenFromHomePage: false
+# You can also define another contentCopyright. e.g. contentCopyright: "This is another copyright."
+contentCopyright: false
+reward: false
+mathjax: false
+mathjaxEnableSingleDollar: false
+mathjaxEnableAutoNumber: false
+
+# You unlisted posts you might want not want the header or footer to show
+hideHeaderAndFooter: false
+
+# You can enable or disable out-of-date content warning for individual post.
+# Comment this out to use the global config.
+#enableOutdatedInfoWarning: false
+
+flowchartDiagrams:
+ enable: false
+ options: ""
+
+sequenceDiagrams:
+ enable: false
+ options: ""
+
+---
+
+
diff --git a/config.toml b/config.toml
new file mode 100644
index 0000000..f312f41
--- /dev/null
+++ b/config.toml
@@ -0,0 +1,224 @@
+baseURL = "https://colben.cn/"
+languageCode = "en"
+defaultContentLanguage = "zh-cn" # en / zh-cn / ... (This field determines which i18n file to use)
+title = "Colben Notes"
+preserveTaxonomyNames = true
+enableRobotsTXT = true
+enableEmoji = true
+theme = ["hugo-search-fuse-js", "even"]
+enableGitInfo = false # use git commit log to generate lastmod record # 可根据 Git 中的提交生成最近更新记录。
+
+# Syntax highlighting by Chroma. NOTE: Don't enable `highlightInClient` and `chroma` at the same time!
+pygmentsOptions = "linenos=table"
+pygmentsCodefences = true
+pygmentsUseClasses = true
+pygmentsCodefencesGuessSyntax = true
+
+hasCJKLanguage = true # has chinese/japanese/korean ? # 自动检测是否包含 中文\日文\韩文
+paginate = 10 # 首页每页显示的文章数
+disqusShortname = "" # disqus_shortname
+googleAnalytics = "" # UA-XXXXXXXX-X
+copyright = "" # default: author.name ↓ # 默认为下面配置的author.name ↓
+
+[markup]
+ defaultMarkdownHandler = "blackfriday"
+
+[author] # essential # 必需
+ name = "Colben"
+
+[sitemap] # essential # 必需
+ changefreq = "weekly"
+ priority = 0.5
+ filename = "sitemap.xml"
+
+[[menu.main]] # config your menu # 配置目录
+ name = "主页"
+ weight = 10
+ identifier = "home"
+ url = "/"
+[[menu.main]]
+ name = "归档"
+ weight = 20
+ identifier = "archives"
+ url = "/post/"
+[[menu.main]]
+ name = "标签"
+ weight = 30
+ identifier = "tags"
+ url = "/tags/"
+[[menu.main]]
+ name = "分类"
+ weight = 40
+ identifier = "categories"
+ url = "/categories/"
+
+[params]
+ version = "4.x" # Used to give a friendly message when you have an incompatible update
+ debug = false # If true, load `eruda.min.js`. See https://github.com/liriliri/eruda
+
+ since = "2019" # Site creation time # 站点建立时间
+ # use public git repo url to link lastmod git commit, enableGitInfo should be true.
+ # 指定 git 仓库地址,可以生成指向最近更新的 git commit 的链接,需要将 enableGitInfo 设置成 true.
+ gitRepo = "https://git.colben.cn/colben/www.colben.cn.git"
+
+ # site info (optional) # 站点信息(可选,不需要的可以直接注释掉)
+ logoTitle = "" # default: the title value # 默认值: 上面设置的title值
+ keywords = ["colben","notes"]
+ description = "personal blogs"
+
+ # paginate of archives, tags and categories # 归档、标签、分类每页显示的文章数目,建议修改为一个较大的值
+ archivePaginate = 16
+
+ # show 'xx Posts In Total' in archive page ? # 是否在归档页显示文章的总数
+ showArchiveCount = true
+
+ # The date format to use; for a list of valid formats, see https://gohugo.io/functions/format/
+ dateFormatToUse = "2006-01-02"
+
+ # show word count and read time ? # 是否显示字数统计与阅读时间
+ moreMeta = false
+
+ # Syntax highlighting by highlight.js
+ highlightInClient = false
+
+ # 一些全局开关,你也可以在每一篇内容的 front matter 中针对单篇内容关闭或开启某些功能,在 archetypes/default.md 查看更多信息。
+ # Some global options, you can also close or open something in front matter for a single post, see more information from `archetypes/default.md`.
+ toc = true # 是否开启目录
+ autoCollapseToc = false # Auto expand and collapse toc # 目录自动展开/折叠
+ fancybox = true # see https://github.com/fancyapps/fancybox # 是否启用fancybox(图片可点击)
+
+ # mathjax
+ mathjax = false # see https://www.mathjax.org/ # 是否使用mathjax(数学公式)
+ mathjaxEnableSingleDollar = false # 是否使用 $...$ 即可進行inline latex渲染
+ mathjaxEnableAutoNumber = false # 是否使用公式自动编号
+ mathjaxUseLocalFiles = false # You should install mathjax in `your-site/static/lib/mathjax`
+
+ postMetaInFooter = true # contain author, lastMod, markdown link, license # 包含作者,上次修改时间,markdown链接,许可信息
+ linkToMarkDown = false # Only effective when hugo will output .md files. # 链接到markdown原始文件(仅当允许hugo生成markdown文件时有效)
+ contentCopyright = '' # e.g. 'CC BY-NC-ND 4.0'
+
+ changyanAppid = "" # Changyan app id # 畅言
+ changyanAppkey = "" # Changyan app key
+
+ livereUID = "" # LiveRe UID # 来必力
+
+ baiduPush = false # baidu push # 百度
+ baiduAnalytics = "" # Baidu Analytics
+ baiduVerification = "" # Baidu Verification
+ googleVerification = "" # Google Verification # 谷歌
+
+ # Link custom CSS and JS assets
+ # (relative to /static/css and /static/js respectively)
+ customCSS = []
+ customJS = []
+
+ uglyURLs = false # please keep same with uglyurls setting
+
+ [params.publicCDN] # load these files from public cdn # 启用公共CDN,需自行定义
+ enable = true
+ jquery = ''
+ slideout = ''
+ fancyboxJS = ''
+ fancyboxCSS = ''
+ timeagoJS = ''
+ timeagoLocalesJS = ''
+ flowchartDiagramsJS = ' '
+ sequenceDiagramsCSS = ''
+ sequenceDiagramsJS = ' '
+
+ # Display a message at the beginning of an article to warn the readers that it's content may be outdated.
+ # 在文章开头显示提示信息,提醒读者文章内容可能过时。
+ [params.outdatedInfoWarning]
+ enable = false
+ hint = 30 # Display hint if the last modified time is more than these days ago. # 如果文章最后更新于这天数之前,显示提醒
+ warn = 180 # Display warning if the last modified time is more than these days ago. # 如果文章最后更新于这天数之前,显示警告
+
+ #[params.gitment] # Gitment is a comment system based on GitHub issues. see https://github.com/imsun/gitment
+ # owner = "colben" # Your GitHub ID
+ # repo = "comments" # The repo to store comments
+ # clientId = "1480b0d51916c635576f" # Your client ID
+ # clientSecret = "86344d4eecfa8e1cc1ee1e099f8489f731b43c77" # Your client secret
+
+ [params.utterances] # https://utteranc.es/
+ owner = "" # Your GitHub ID
+ repo = "" # The repo to store comments
+
+ [params.gitalk] # Gitalk is a comment system based on GitHub issues. see https://github.com/gitalk/gitalk
+ owner = "" # Your GitHub ID
+ repo = "" # The repo to store comments
+ clientId = "" # Your client ID
+ clientSecret = "" # Your client secret
+
+ # Valine.
+ # You can get your appid and appkey from https://leancloud.cn
+ # more info please open https://valine.js.org
+ [params.valine]
+ enable = false
+ appId = '你的appId'
+ appKey = '你的appKey'
+ notify = false # mail notifier , https://github.com/xCss/Valine/wiki
+ verify = false # Verification code
+ avatar = 'mm'
+ placeholder = '说点什么吧...'
+ visitor = false
+
+ [params.flowchartDiagrams]# see https://blog.olowolo.com/example-site/post/js-flowchart-diagrams/
+ enable = false
+ options = ""
+
+ [params.sequenceDiagrams] # see https://blog.olowolo.com/example-site/post/js-sequence-diagrams/
+ enable = false
+ options = "" # default: "{theme: 'simple'}"
+
+ [params.busuanzi] # count web traffic by busuanzi # 是否使用不蒜子统计站点访问量
+ enable = false
+ siteUV = true
+ sitePV = true
+ pagePV = true
+
+ [params.reward] # 文章打赏
+ enable = false
+ wechat = "/path/to/your/wechat-qr-code.png" # 微信二维码
+ alipay = "/path/to/your/alipay-qr-code.png" # 支付宝二维码
+
+ #[params.social] # 社交链接
+ # a-email = "mailto:colbenlee@163.com"
+ # #b-stack-overflow = "http://localhost:1313"
+ # c-twitter = "https://twitter.com/ColbenLee"
+ # #d-facebook = "http://localhost:1313"
+ # #e-linkedin = "http://localhost:1313"
+ # #f-google = "http://localhost:1313"
+ # g-github = "https://github.com/colben"
+ # #h-weibo = "http://localhost:1313"
+ # #i-zhihu = "http://localhost:1313"
+ # #j-douban = "http://localhost:1313"
+ # #k-pocket = "http://localhost:1313"
+ # #l-tumblr = "http://localhost:1313"
+ # #m-instagram = "http://localhost:1313"
+ # n-gitlab = "https://git.colben.cn"
+ # #o-bilibili = "http://localhost:1313"
+
+# See https://gohugo.io/about/hugo-and-gdpr/
+[privacy]
+ [privacy.googleAnalytics]
+ anonymizeIP = true # 12.214.31.144 -> 12.214.31.0
+ [privacy.youtube]
+ privacyEnhanced = true
+
+# 将下面这段配置取消注释可以使 hugo 生成 .md 文件
+# Uncomment these options to make hugo output .md files.
+#[mediaTypes]
+# [mediaTypes."text/plain"]
+# suffixes = ["md"]
+#
+#[outputFormats.MarkDown]
+# mediaType = "text/plain"
+# isPlainText = true
+# isHTML = false
+#
+#[outputs]
+# home = ["HTML", "RSS"]
+# page = ["HTML", "MarkDown"]
+# section = ["HTML", "RSS"]
+# taxonomy = ["HTML", "RSS"]
+# taxonomyTerm = ["HTML"]
diff --git a/content/about.md b/content/about.md
new file mode 100644
index 0000000..fd2f15c
--- /dev/null
+++ b/content/about.md
@@ -0,0 +1,27 @@
+---
+title: "关于"
+date: 2019-10-29T17:38:52+08:00
+lastmod: 2019-10-29T17:44:00+08:00
+menu: "main"
+weight: 50
+---
+
+

+俊赛潘安,才比管乐
+
+
+### **IT 运维工作注意事项**
+- 无论世界怎样变化,用户总会有问题
+- 当你拿不准时,请重启机器。
+- 迟早有一天会遇到一个忘了插电源的人说电脑启动不起来。
+- 会逐渐害怕来电话,因为没有人会通过技术支持电话向我道早安.
+- 在通话的一开始没有用户会告诉你所有的事情真相
+- "我没有做任何事"和"突然间就变成这样了"是用户的咒语,作为一个技术支持人员,你要做的事是识破用户的谎言,解决问题不过是一件附带的事
+- 有些用户永远都不会去学习,这意味着运维永远都不会失业
+- 要一直保持平静的语气。
+- 无论你在做什么,都不要恐慌。
+- 永远都应该像这样回答用户的问题: "相信我,我知道我在做什么"
+- 修理一台电脑要比指出损坏原因要简单的多
+- 用户总想知道问题被解决的原因,如果你并不太清楚请尽管撒谎,他们永远都不会知道的。例如说: "一个偏离的电子进入了处理器然后问题就产生了......"
+- 同行之间探讨问题,如有可能尽量向年龄最小的人询问,因为只有他们会说实话。
+
diff --git a/content/others.md b/content/others.md
new file mode 100644
index 0000000..41055e4
--- /dev/null
+++ b/content/others.md
@@ -0,0 +1,44 @@
+---
+title: "其他"
+date: 2019-10-30T13:43:55+08:00
+lastmod: 2019-10-30T13:43:55+08:00
+menu: "main"
+weight: 60
+---
+
+# Linux
+- [**Linux监控命令图文详述**](http://www.linuxidc.com/Linux/2015-01/111577.htm)
+- [**Linux crontab 命令详细用法及示例**](http://www.linuxidc.com/Linux/2015-03/114339.htm)
+- [**Linux下top命令详解**](http://www.linuxidc.com/Linux/2015-04/116101.htm)
+- [**Ubuntu 通过 Live CD 更新Grub恢复引导Boot Menu**](http://www.linuxidc.com/Linux/2015-04/116451.htm)
+- [**Kickstart配置文件超详细解析**](http://www.linuxidc.com/Linux/2017-08/146168.htm)
+- [**PXE+Kickstart无人值守安装CentOS 7**](http://www.linuxidc.com/Linux/2017-08/146169.htm)
+- [**PXE+Kickstart无人值守安装CentOS 6**](http://www.linuxidc.com/Linux/2017-08/146170.htm)
+- [**Cobbler无人值守批量安装Linux系统**](http://www.linuxidc.com/Linux/2017-08/146171.htm)
+- [**PXE+DHCP+TFTP+Cobbler 无人值守安装CentOS 7**](http://www.linuxidc.com/Linux/2017-09/146705.htm)
+- [**Linux下搭建无人执守安装服务器**](http://www.linuxidc.com/Linux/2017-04/143182.htm)
+
+# Network
+- [**关于TCP连接建立与终止那点事**](http://www.linuxidc.com/Linux/2015-09/122777.htm)
+
+# Database
+- [**Oracle Linux 5.8安装Oracle 11g RAC**](http://www.linuxidc.com/Linux/2013-05/84251.htm)
+- [**RAC环境数据库重启实例**](http://www.linuxidc.com/Linux/2013-08/88855.htm)
+- [**使用Oracle 的 imp ,exp 命令实现数据的导入导出**](http://blog.csdn.net/studyvcmfc/article/details/5674290)
+- [**ORACLE EXPDP命令使用详细**](http://blog.csdn.net/zftang/article/details/6387325)
+- [**控制文件和控制文件的备份**](http://blog.csdn.net/seertan/article/details/8449050)
+- [**MySQL如何通过EXPLAIN分析SQL的执行计划**](https://www.linuxidc.com/Linux/2018-08/153354.htm)
+
+# Container
+- [**Docker 终极指南**](http://www.linuxidc.com/Linux/2015-01/111631.htm)
+
+# Python
+- [**Python 的 OptionParser 模块**](http://www.it165.net/pro/html/201211/4140.html)
+
+# Firewalld
+- [**Iptables防火墙规则使用详解**](https://www.linuxidc.com/Linux/2018-08/153378.htm)
+
+# Dev
+- [**Make 命令教程详解**](http://www.linuxidc.com/Linux/2015-06/118278.htm)
+- [**深入理解Java内存与垃圾回收调优**](https://www.linuxidc.com/Linux/2018-08/153457.htm)
+
diff --git a/content/post/ansible.md b/content/post/ansible.md
new file mode 100644
index 0000000..602d487
--- /dev/null
+++ b/content/post/ansible.md
@@ -0,0 +1,56 @@
+---
+title: "Ansible"
+date: 2019-10-30T11:38:39+08:00
+lastmod: 2019-10-30T11:38:39+08:00
+tags: ["ansible"]
+categories: ["dev/ops"]
+---
+
+# CentOS7 安装 ansible
+```bash
+yum install ansible
+```
+
+# 修改配置文件 /etc/ansible/ansible.conf
+```
+# 取消 ssh key 验证
+host_key_checking = False
+# 改用 debug 输出模式
+stdout_callback = debug
+```
+
+# 修改配置文件 hosts
+```
+# 增加 tomcat 服务器组
+[tomcat]
+tomcat101 ansible_ssh_host=192.168.1.101
+tomcat102 ansible_ssh_host=192.168.1.102
+tomcat103 ansible_ssh_host=192.168.1.103
+# 增加 mysql 服务器组
+[mysql]
+mysql201 ansible_ssh_host=192.168.1.201 ansible_ssh_pass=111111
+mysql202 ansible_ssh_host=192.168.1.202
+mysql203 ansible_ssh_host=192.168.1.203
+# 全局设置全部服务器的默认设置
+[all:vars]
+ansible_ssh_port=22
+ansible_ssh_user=root
+ansible_ssh_pass=123456
+```
+
+# authorized_key 模块
+```bash
+# 分发密钥
+ansible all -m authorized_key -a "user=root key='{{ lookup('file', '/root/.ssh/id_rsa.pub') }}'"
+```
+
+# 简单使用
+- 批量设置主机名为资产名
+ ```bash
+ ansible all -m shell -a 'hostnamectl set-hostname {{inventory_hostname}}'
+ ansible all -m shell -a 'echo "{{ansible_ssh_host}} {{inventory_hostname}}" >> /etc/hosts'
+ ```
+
+# ansible 常用 roles
+- [https://gitee.com/colben/ansible](https://gitee.com/colben/ansible)
+
diff --git a/content/post/archlinux-install.md b/content/post/archlinux-install.md
new file mode 100644
index 0000000..e98cc6e
--- /dev/null
+++ b/content/post/archlinux-install.md
@@ -0,0 +1,211 @@
+---
+title: "安装 Archlinux"
+date: 2021-07-04T11:23:00+08:00
+lastmod: 2021-07-04T11:23:00+08:00
+keywords: []
+tags: ["archlinux"]
+categories: ["os"]
+---
+
+# U 盘启动,进入 archlinux live
+- 下载 archlinux 镜像
+ ```bash
+ curl -LO https://mirrors.tuna.tsinghua.edu.cn/archlinux/iso/2021.07.01/archlinux-2021.07.01-x86_64.iso
+ ```
+
+- 把镜像写进一个无用的 U 盘(/dev/sdX)
+ ```bash
+ dd if=archlinux-2021.07.01-x86_64.iso bs=1M of=/dev/sdX
+ ```
+
+- 使用该 U 盘启动自己的 PC,进入 archlinux live
+
+# 联网,硬盘分区,安装系统
+- 插上网线,自动分配地址,验证是否可以连接外网
+ ```bash
+ curl -I https://www.baidu.com
+ # 返回 200
+ ```
+
+- 规划硬盘,选择一个无用硬盘(/dev/sdX),规划 efi 分区和根分区
+ ```bash
+ # fdisk 规划分区
+ fdisk /dev/sdX
+ g # 创建新的 gpt 分区表
+ n # 创建一个新分区
+ # 直接回车
+ # 直接回车
+ +256M # 指定大小
+ t # 更改分区类型
+ # 直接回车
+ 1 # 选择 EFI System
+ n # 创建一个新分区
+ # 直接回车
+ # 直接回车
+ +XXXG # 指定根分区大小
+ w # 保存并退出 fdisk
+
+ 官网还有 swap 分区,现在内存都比较大,不需要 swap 了
+ ```
+
+- 格式化分区
+ ```bash
+ mkfs.fat -F32 /dev/sdX1
+ mkfs.ext4 /dev/sdX2
+ ```
+
+- 挂载分区
+ ```bash
+ mount /dev/sdX2 /mnt
+ mkdir /mnt/EFI
+ mount /dev/sdX1 /mnt/EFI
+ ```
+
+- 修改软件源(vim /etc/pacman.d/mirrorlist),只保留中国源
+ ```
+ ## China
+ Server = http://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch
+ ## China
+ Server = http://mirrors.bit.edu.cn/archlinux/$repo/os/$arch
+ ## China
+ Server = http://mirrors.aliyun.com/archlinux/$repo/os/$arch
+ ## China
+ Server = https://mirrors.cloud.tencent.com/archlinux/$repo/os/$arch
+ ## China
+ Server = http://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch
+ ## China
+ Server = http://mirrors.zju.edu.cn/archlinux/$repo/os/$arch
+ ## China
+ Server = http://mirrors.xjtu.edu.cn/archlinux/$repo/os/$arch
+ ## China
+ Server = http://mirrors.163.com/archlinux/$repo/os/$arch
+ ## China
+ Server = http://mirrors.sohu.com/archlinux/$repo/os/$arch
+ ## China
+ Server = http://mirrors.neusoft.edu.cn/archlinux/$repo/os/$arch
+ ## China
+ Server = http://mirrors.shu.edu.cn/archlinux/$repo/os/$arch
+ ## China
+ Server = http://mirror.lzu.edu.cn/archlinux/$repo/os/$arch
+ ```
+
+- 安装系统包
+ ```bash
+ pacstrap /mnt base linux linux-firmware
+ # 这里会从中国源里下载安装包并安装,具体时间就看网速了
+ ```
+
+- 生成 fstab
+ ```bash
+ genfstab -U /mnt >> /mnt/etc/fstab
+ ```
+
+- chroot 进入新系统
+ ```bash
+ arch-chroot /mnt /bin/bash
+ ```
+
+# 配置新安装的系统
+- 设置时区
+ ```bash
+ ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+ ```
+
+- 编辑 /etc/locale.gen,选择 en_US 和 zh_CN
+ ```bash
+ sed -i \
+ -e '/^#en_US.UTF-8/s/^#//' \
+ -e '/^#zh_CN/s/^#//' \
+ /etc/locale.gen
+
+ locale-gen
+
+ # 生成 /etc/locale.conf
+ echo 'LANG=zh_CN.UTF-8' > /etc/locale.conf
+ ```
+
+- 设置主机名(xxxx)
+ ```bash
+ echo xxxx > /etc/hostname
+ ```
+
+- 安装内核
+ ```bash
+ mkinitcpio -P
+ ```
+
+- 设置 root 密码
+ ```bash
+ passwd
+ ```
+
+- 创建一个普通用户(xxxx),用于自己日常使用
+ ```bash
+ useradd -m xxxx
+ passwd xxxx
+ echo "xxxx ALL=(ALL) ALL" > /etc/sudoers.d/xxxx
+ ```
+
+- 安装其他必须包
+ ```bash
+ pacman -S grub networkmanager
+ pacman -S intel-ucode # intel 处理器
+ pacman -S amd-code # amd 处理器
+ ```
+
+- 创建 efi 启动项(xxxx)
+ ```bash
+ grub-install --target=x86_64-efi --efi-directory=/EFI --bootloader-id=xxxx
+ ```
+
+- 生成 grub.cfg
+ ```bash
+ grub-mkconfig -o /boot/grub/grub.cfg
+ ```
+
+- 退出新系统,返回 archlinux live
+ ```bash
+ exit
+ ```
+
+# 卸载硬盘,重启
+- 卸载硬盘
+ ```bash
+ umount -R /mnt
+ ```
+
+- 重启
+ ```bash
+ reboot
+ ```
+
+# 日常使用
+- 登陆普通用户
+- 启动 NetworkManager,并设置开机自动启动
+ ```bash
+ sudo systemctl start NetworkManager
+ sudo systemctl enable NetworkManager
+ ```
+
+- 连接网络
+ ```bash
+ nmtui
+ ```
+
+- 安装常用命令行软件
+ ```bash
+ sudo pacman -S ctags cscope tmux expect git p7zip unrar zip unzip \
+ ntfs-3g espeak hostapd dnsmasq openssh tcpdump vim cdrtools \
+ tree ethtool openbsd-netcat arch-install-scripts
+ ```
+
+- 安装 gnome 桌面
+ ```bash
+ sudo pacman -S wqy-zenhei ttf-liberation
+ sudo pacman -S gnome
+ sudo systemctl enable gdm.service
+ sudo pacman -S gvim file-roller freerdp mpv ibus-sunpinyin
+ ```
+
+- [Gnome 主题网站](https://gnome-look.org)
+
diff --git a/content/post/awk.md b/content/post/awk.md
new file mode 100644
index 0000000..2ae113e
--- /dev/null
+++ b/content/post/awk.md
@@ -0,0 +1,141 @@
+---
+title: "awk 命令"
+date: 2019-10-30T00:47:01+08:00
+lastmod: 2019-10-30T00:47:01+08:00
+keywords: []
+tags: ["awk"]
+categories: ["shell"]
+---
+
+# 格式
+- awk -F '分隔符正则' -v 变量名=值 'BEGIN{动作} 条件{动作} END{动作}' 文件1 文件2 ...
+- awk -F '分隔符正则' -f awk脚本文件名 文件1 文件2 ...
+
+# 常用内置变量
+变量 | 说明
+---- | ----
+$0 | 当前记录
+$1~$n | 当前记录的第n个字段,字段间由FS分隔
+FS | 输入字段分隔符 默认是空格
+NF | 当前记录中的字段个数,就是有多少列
+NR | awk 开始执行程序后所读取的数据行数,就是行号,从1开始
+RS | 输入的记录他隔符默 认为换行符
+OFS | 输出字段分隔符 默认也是空格
+ORS | 输出的记录分隔符,默认为换行符
+FNR | 当前记录数,awk每打开一个新文件,FNR便从0重新累计
+ARGC | 命令行参数个数
+ARGV | 命令行参数字典,ARGV[0] 是 'awk',ARGV[1] 是 FILENAME
+FILENAME | 当前输入文件的名字
+IGNORECASE | 如果为真,则进行忽略大小写的匹配
+ENVIRON | UNIX环境变量,如 ENVIRON["PATH"]
+FIELDWIDTHS | 输入字段宽度的空白分隔字符串
+OFMT | 数字的输出格式(默认值是%.6g)
+RSTART | 被 match 匹配函数匹配的字符串位置
+RLENGTH | 被 match 匹配函数匹配的字符串长度
+
+# 函数
+### 常用内置函数
+函数 | 说明
+---- | ----
+int(x) | 返回 x 的整数部分
+rand() | 返回 0 到 1 之间的浮点数
+gsub(Ere, "dest", str) | 把 str 中匹配扩展正则 Ere 的全部子串换成字符串 "dest"
+sub(Ere, "dest", str) | 把 str 中匹配扩展正则 Ere 的第一个子串换成字符串 "dest"
+substr(str, start, len) | 返回 str 中从 start 开始(长度为 len )的子串
+index(str1, str2) | 返回 str1 中出现 str2 的位置,如果未找到,返回 0
+length(str) | 返回 str 的字符个数,如果未指定 str,返回 $0 包含字符个数
+blength(str) | 返回 str 的字节个数,如果未指定 str,返回 $0 包含字节个数
+match(str, Ere) | 返回 str 中匹配扩展正则 Ere 的位置,如果未找到,返回 0
+split(str, arr, Ere) | 把 str 按照扩展正则 Ere 切分成字典,存储到 arr 中,返回字典长度
+tolower(str) | 把 str 换成小写
+toupper(str) | 把 str 换成大写
+sprintf("%s-%d", "abcd", 1234) | 返回 abcd-1234
+strtonum(str) | 返回十进制数字
+delete arr[n] | 删除字典/字典的对应元素
+getline | 读入当前行的下一行,重写变量 0
+next | 停止处理当前记录,开始处理下一行
+nextfile | 停止处理当前文件,开始处理下一个文件
+system(shell-command) | 返回命令退出状态
+exit n | 终止 awk,返回 n
+
+### 自定义函数
+- 格式
+ ```awk
+ function fun_name(arg1, arg2, ...){
+ #函数体
+ return
+ }
+ ```
+
+# 判断语句
+```awk
+if(条件){
+ # 语句
+}else if(条件){
+ # 语句
+}else{
+ # 语句
+}
+```
+
+# 循环语句
+- for
+ ```awk
+ for(初始化;条件;变化){
+ # 语句
+ }
+ for(变量 in 字典){
+ # 语句
+ }
+ ```
+- while
+ ```awk
+ while(条件){
+ # 语句
+ }
+ ```
+- do while
+ ```awk
+ do{
+ # 语句
+ }while(条件)
+ ```
+- break 退出当前循环体
+- continue 退出本次循环,继续下一次循环
+
+# 脚本
+```awk
+#!/usr/bin/awk -f
+
+# 自定义的变量和函数
+# ...
+
+# 从这里开始执行
+BEGIN{
+ # 语句
+}
+条件{
+ # 语句
+}
+END{
+ # 语句
+}
+```
+
+# 其他说明
+- 变量在使用时直接赋值即可,无需提前声明或定义
+- 个人认为,awk 没有数组只有字典,数组是键为整数的字典
+- 运算符(+, -, \*, /, ++, -- 等)和关系符操作(>, >=, <, <=, ~, !~ 等)与 C 基本一致,也支持三目运算符(条件?值1:值2)
+- shell 中 awk 执行显式命令时,加载 shell 中的变量
+ ```bash
+ #!/bin/bash
+ shell_value='abcd'
+ echo | awk '{print "'$shell_value'"}'
+ # 输出 abcd
+ ```
+
+# 参考
+- man awk
+- [其他内置函数](https://www.gnu.org/software/gawk/manual/html_node/Built_002din.html#Built_002din)
+- [gnu awk 手册](https://www.gnu.org/software/gawk/manual/gawk.html)
+
diff --git a/content/post/bond.md b/content/post/bond.md
new file mode 100644
index 0000000..eeac0ad
--- /dev/null
+++ b/content/post/bond.md
@@ -0,0 +1,140 @@
+---
+title: "CentOS7 双网卡绑定"
+date: 2019-10-30T11:07:30+08:00
+lastmod: 2019-10-30T11:07:30+08:00
+keywords: []
+tags: ["centos", "bond"]
+categories: ["network"]
+---
+
+# bond 概要
+### 什么是 bond
+- 网卡bond是通过把多张网卡绑定为一个逻辑网卡,实现本地网卡的冗余,带宽扩容和负载均衡。在应用部署中是一种常用的技术。
+### bond的模式种类
+- Mode=0(balance-rr) 表示负载分担round-robin,和交换机的聚合强制不协商的方式配合
+- Mode=1(active-backup) 表示主备模式,只有一块网卡是active,另外一块是备的standby
+ - **如果交换机配的是捆绑,将不能正常工作,因为交换机往两块网卡发包,有一半包是丢弃的**
+- Mode=2(balance-xor) 表示XOR Hash负载分担,和交换机的聚合强制不协商方式配合(需要xmit_hash_policy)
+- Mode=3(broadcast) 表示所有包从所有interface发出,这个不均衡,只有冗余机制...和交换机的聚合强制不协商方式配合
+- Mode=4(802.3ad) 表示支持802.3ad协议,和交换机的聚合LACP方式配合(需要xmit_hash_policy)
+- Mode=5(balance-tlb) 是根据每个slave的负载情况选择slave进行发送,接收时使用当前轮到的slave
+- Mode=6(balance-alb) 在5的tlb基础上增加了rlb
+
+# CentOS7 配置 bond
+### 环境
+- 操作系统 CentOS7.6,禁用 NetworkManager 服务
+- 物理网卡 eth0, eth1 绑定到 bond0
+- 物理网卡 eth2, eth3 绑定到 bond1
+
+### 网卡 eth0 配置
+- 修改 /etc/sysconfig/network-scripts/ifcfg-eth0
+ ```
+ TYPE=Ethernet
+ BOOTPROTO=none
+ DEVICE=eth0
+ ONBOOT=yes
+ USERCTL=no
+ SLAVE=yes
+ MASTER=bond0
+ ```
+
+### 网卡 eth1 配置
+- 修改 /etc/sysconfig/network-scripts/ifcfg-eth1
+ ```
+ TYPE=Ethernet
+ BOOTPROTO=none
+ DEVICE=eth1
+ ONBOOT=yes
+ USERCTL=no
+ SLAVE=yes
+ MASTER=bond0
+ ```
+
+### 网卡 eth2 配置
+- 修改 /etc/sysconfig/network-scripts/ifcfg-eth2
+ ```
+ TYPE=Ethernet
+ BOOTPROTO=none
+ DEVICE=eth2
+ ONBOOT=yes
+ USERCTL=no
+ SLAVE=yes
+ MASTER=bond1
+ ```
+
+### 网卡 eth3 配置
+- 修改 /etc/sysconfig/network-scripts/ifcfg-eth3
+ ```
+ TYPE=Ethernet
+ BOOTPROTO=none
+ DEVICE=eth3
+ ONBOOT=yes
+ USERCTL=no
+ SLAVE=yes
+ MASTER=bond1
+ ```
+
+### 增加网卡 bond0 配置
+- 创建 /etc/sysconfig/network-scripts/ifcfg-bond0
+ ```
+ TYPE=Ethernet
+ BOOTPROTO=static
+ NAME=bond0
+ DEVICE=bond0
+ ONBOOT=yes
+ IPADDR=192.168.1.101
+ NETMASK=255.255.255.0
+ GATEWAY=192.168.1.1
+ DNS1=114.114.114.114
+ ```
+
+### 增加网卡 bond1 配置
+- 创建 /etc/sysconfig/network-scripts/ifcfg-bond1
+ ```
+ TYPE=Ethernet
+ BOOTPROTO=static
+ NAME=bond0
+ DEVICE=bond0
+ ONBOOT=yes
+ IPADDR=192.168.1.102
+ NETMASK=255.255.255.0
+ #GATEWAY=192.168.1.1
+ #DNS1=114.114.114.114
+ ```
+
+### 配置绑定模式
+- 创建 /etc/modprobe.d/bonding.conf,加入以下内容
+ ```
+ alias bond0 bonding
+ alias bond1 bonding
+ options bonding miimon=100 mode=1
+ ```
+
+### 载入 bonding 模块,重启 network 服务
+```bash
+modprob bonding
+systemctl restart network
+```
+
+### 查看网卡绑定状态
+```bash
+cat /proc/net/bonding/bond0
+cat /proc/net/bonding/bond1
+```
+
+# CentOS8 配置 bond
+- 建立 bond 连接配置文件
+ ```bash
+ nmcli c add con-name bond0 type bond ifname bond0 mode active-backup
+ ```
+- 增加两个网卡都 bond0
+ ```bash
+ nmcli c add type bond-slave ifname eth1 master bond0
+ nmcli c add type bond-slave ifname eth2 master bond0
+ ```
+- 启动这两个 slave 连接
+ ```bash
+ nmcli c up bond-slave-eth1
+ nmcli c up bond-slave-eth2
+ ```
+
diff --git a/content/post/cassandra-install.md b/content/post/cassandra-install.md
new file mode 100644
index 0000000..7c6c9e9
--- /dev/null
+++ b/content/post/cassandra-install.md
@@ -0,0 +1,154 @@
+---
+title: "CentOS7 安装 Cassandra 集群"
+date: 2019-10-30T00:59:02+08:00
+lastmod: 2019-10-30T00:59:02+08:00
+keywords: []
+tags: ["cassandra"]
+categories: ["database"]
+---
+
+# 环境
+
+主机名 | Public IP | Cluster IP| 操作系统 | Cassandra 版本
+---- | ---- | ---- | ---- | ----
+cassandra101 | 10.0.4.101 | 10.10.10.101 | CentOS7.6 | 3.0.18
+cassandra102 | 10.0.4.102 | 10.10.10.102 | CentOS7.6 | 3.0.18
+cassandra103 | 10.0.4.103 | 10.10.10.103 | CentOS7.6 | 3.0.18
+
+- 下载 [apache-cassandra-3.0.18-bin.tar.gz](https://mirrors.tuna.tsinghua.edu.cn/apache/cassandra/3.0.18/apache-cassandra-3.0.18-bin.tar.gz)
+
+# 各节点初始配置
+
+- 关闭 selinux、防火墙
+- 部署 java 运行环境
+- 创建 cassandra 用户
+ ```bash
+ useradd -m cassandra
+ ```
+- 创建数据目录
+ ```bash
+ cd /var/lib
+ mkdir -p cassandra/data1 #多个存储磁盘可以创建多个数据存储目录
+ mkdir -p cassandra/hints #建议与数据磁盘分开
+ mkdir -p cassandra/commitlog #建议与数据磁盘分开
+ mkdir -p cassandra/saved_caches #建议与数据磁盘分开
+ chown -R cassandra.cassandra cassandra/
+ ```
+- 创建日志目录
+ ```bash
+ cd /var/log
+ mkdir -p cassandra
+ chown -R cassandra.cassandra cassandra/
+ ```
+- 创建 pid 目录
+ ```bash
+ cd /run
+ mkdir -p cassandra
+ chown -R cassandra.cassandra cassandra/
+ ```
+- 增加 sysctl.conf 配置,执行 sysctl -p 生效
+ ```
+ vm.max_map_count=1048576
+ ```
+- 安装 jemalloc (推荐)
+ ```bash
+ yum install jemalloc
+ ```
+- 创建文件 /usr/lib/systemd/system/cassandra.service,内容如下
+ ```
+ [Unit]
+ Description=Cassandra
+ Requires=network.service
+ After=network.service
+ [Service]
+ Type=forking
+ WorkingDirectory=/opt/cassandra
+ Environment=JAVA_HOME=/opt/jre
+ Environment=LOCAL_JMX=no
+ PIDFile=/run/cassandra/cassandra.pid
+ ExecStart=/opt/cassandra/bin/cassandra -p /run/cassandra/cassandra.pid
+ User=cassandra
+ Group=cassandra
+ LimitNOFILE=65536
+ LimitNPROC=65536
+ LimitMEMLOCK=infinity
+ SuccessExitStatus=143
+ [Install]
+ WantedBy=multi-user.target
+ ```
+
+# 部署 Cassandra
+
+- 登陆 cassandra101,下载 cassandra,解压至 /opt/ 下
+- 修改 /opt/cassandra/conf/cassandra.yaml
+ ```yaml
+ cluster_name: CassandraCluster
+ hists_directory: /var/lib/cassandra/hints
+ data_file_directories:
+ - /var/lib/cassandra/data1
+ commitlog_directory: /var/lib/cassandra/commitlog
+ saved_caches_directory: /var/lib/cassandra/saved_caches
+ seed_provider:
+ - class_name: org.apache.cassandra.locator.SimpleSeedProvider
+ parameters:
+ - seeds: "10.10.10.101,10.10.10.102,10.10.10.103"
+ listen_address: 10.10.10.101
+ rpc_address: 10.0.4.101
+ ```
+- 修改 /opt/cassandra/conf/logback.xml
+ ```bash
+ sed -i 's,\${cassandra.logdir},/var/log,' /opt/cassandra/conf/logback.xml
+ ```
+- 修改 /opt/cassandra/conf/cassandra-env.sh
+ ```bash
+ # 这里我暂时关闭了 jmx 远程验证,否则需要手动创建 jmxremote.password 文件
+ sed -i 's/jmxremote.authenticate=true/jmxremote.authenticate=false/' /opt/cassandra/conf/cassandra-env.sh
+ ```
+- 修改 cassandra 目录的权限
+ ```bash
+ chown -R cassandra.cassandra cassandra/
+ ```
+- 打包 cassandra 目录,部署到 cassandra102 和 cassandra103 的 /opt 下,并修改 cassandra.yaml
+ ```
+ # cassandra102
+ listen_address: 10.10.10.102
+ rpc_address: 10.0.4.102
+ # cassandra103
+ listen_address: 10.10.10.103
+ rpc_address: 10.0.4.103
+ ```
+
+# 启动集群
+
+- 启动 cassandra 服务
+ ```bash
+
+ systemctl daemon-reload
+ systemctl start cassandra
+ ```
+
+# 简单使用
+
+- cqlsh 连接数据库
+ ```bash
+ /opt/cassandra/bin/cqlsh 10.0.4.101 9042
+ cqlsh> desc keyspaces;
+ ```
+
+# 注意事项
+- 创建包含复合主键的表
+ ```cql
+ create table t1 (
+ c1 text,
+ c2 text,
+ c3 text,
+ c4 text,
+ c5 text,
+ primary key((c1,c2),c3,c4)
+ );
+- 复合主键的第一列 "(c1,c2)" 构成 PartitionKey,其余列 c3,c4 都是 ClusteringKey
+- Cassandra 对 PartitionKey 计算 Hash 值,决定该记录的存放 node,ClusteringKey 在 Partition 内部排序
+- 默认只支持**主键列**和**索引列**查询,否则需要手动指定 **allow filtering**
+- 根据多个 ClustringKey 查询时,需指定全部的 PartitionKey,ClusteringKey 不能跳过
+- 主键列不可修改
+
diff --git a/content/post/centos6-migrate.md b/content/post/centos6-migrate.md
new file mode 100644
index 0000000..c1dc7e5
--- /dev/null
+++ b/content/post/centos6-migrate.md
@@ -0,0 +1,63 @@
+---
+title: "Centos6 系统盘迁移"
+date: 2019-10-30T13:34:47+08:00
+lastmod: 2019-10-30T13:34:47+08:00
+tags: ["centos", "系统盘迁移"]
+categories: ["os"]
+---
+
+# 环境
+- Linux 物理机,已安装 VirtualBox 虚拟机软件
+- CentOS6.9 live 启动 U 盘
+- CentOS6.9 iso 镜像文件
+- 待安装笔记本 IBM x32
+
+# VirtualBox 创建 Redhat6 虚拟机
+- VMware 没用过,建议硬件配置尽量和目标设备一致
+- Thinkpad X32 的处理器只有一核,无 PAE,内存 1024MB
+- 虚拟硬盘 8G 就够了,使用 CentOS6.9 iso 装好虚拟机后,禁用 selinux,可能需要重启生效。
+
+# 打包操作系统根目录
+```
+cd /
+tar cvpzf backup.tgz --exclude=/backup.tgz --one-file-system /
+```
+# 导出 /backup.tgz 文件
+- 返回物理机操作系统,通过 ssh、http 或其他方式把虚拟机的 /backup.tgz 拷贝至物理机中 /root 下
+
+# 格式化磁盘
+- 取出待安装笔记本的硬盘,通过 USB 或其他方式挂载到该物理机上,fdisk 分区,格式化
+ ```
+ #fdisk 分成俩个分区,前面一个大的根分区,后面一个2G的 swap 分区,其他情况自己决定
+ #假设刚挂载的这个目标磁盘设备是 sdg
+ mkfs.ext3 /dev/sdg1
+ mkswap /dev/sdg2
+ ```
+
+# 部署操作系统
+- 挂载待部署磁盘的根分区,解压操作系统文件,修改启动相关参数
+ ```
+ mount /dev/sdg1 /mnt
+ tar xvpzf /root/backup.tgz -C /mnt/
+ #查看目标磁盘根分区的 uuid,替换 /mnt/boot/grub/grub.conf 和 /mnt/etc/fstab 中对应项
+ ls -lh /dev/disk/by-uuid/|grep sdg1
+ #查看目标磁盘 swap 分区的uuid,替换 /mnt/etc/fstab 中对应项
+ ls -lh /dev/disk/by-uuid/|grep sdg2
+ #检查 /mnt/etc/mtab 中列出的信息是否正确
+ ```
+
+# 安装 grub2
+- 取消挂载,把部署好的磁盘安装回待安装笔记本中,插上 CentOS6.9 的启动 U 盘,从 U 盘启动待安装笔记本,进入 live 模式(安装盘可以在安装界面开始时按下 Alt + F1 切换到 live 模式下),安装 grub2
+ ```
+ #在 live 模式下切换到 root
+ sudo -i
+ #挂载(假设 live 识别到的硬盘根分区是 /dev/sda1)
+ mount /dev/sda1 /mnt
+ #安装 grub2
+ grub-install --root-directory=/mnt/ /dev/sda
+ #如果显示 “no error”,即可退出,取消挂载
+ exit
+ umount -f /mnt
+ ```
+- 此时重启就可以正常进入系统了
+
diff --git a/content/post/centos6-nopae.md b/content/post/centos6-nopae.md
new file mode 100644
index 0000000..65e0a75
--- /dev/null
+++ b/content/post/centos6-nopae.md
@@ -0,0 +1,88 @@
+---
+title: "Centos6 安装 nopae 内核"
+date: 2019-10-30T13:06:52+08:00
+lastmod: 2019-10-30T13:06:52+08:00
+tags: ["centos", "nopae"]
+categories: ["os"]
+---
+
+# 环境
+- IBM Thinkpad X31
+- VirtualBox
+- [CentOS-6.9-i386-minimal.iso](http://mirrors.ustc.edu.cn/centos/6.9/isos/i386/CentOS-6.9-i386-minimal.iso)
+
+# 需求
+- 手头有台笔记本 IBM thinkpad X31,处理器不支持 pae,不支持 64 位操作系统,要安装一个32位 CentOS6,且内核无 pae 要求。
+
+# 准备环境
+- 在支持 pae 的计算机上安装 [VirtualBox](https://www.virtualbox.org/wiki/Downloads) 及其扩展包,以支持 VirtualBox 虚拟机中挂载宿主机 USB 存储。
+- 取出 Thinkpad 笔记本的硬盘,通过移动硬盘盒等方法连接刚刚安装 VirtualBox 的计算机,确保该存储正常识别可用。
+- 下载[CentOS-6.9-i386-minimal.iso](http://mirrors.ustc.edu.cn/centos/6.9/isos/i386/CentOS-6.9-i386-minimal.iso)
+
+# VirtualBox 下安装 CentOS6.9 虚拟机
+- 创建 CentOS6.9 x32 虚拟机,这里无需创建虚拟磁盘,后面会把 Thinkpad 笔记本的硬盘挂载到虚拟机中,直接把操作系统安装到该硬盘中。
+
+- 设置虚拟机,启动 USB 3.0 控制器,增加筛选器,选中刚刚 USB 连接的 Thinkpad 硬盘;网络模式自选,确保虚拟机可上网。
+
+- 使用刚刚下载好的 CentOS-6.9-i386-minimal.iso 启动 CentOS6.9 虚拟机,如下图
+
+- 此时查看菜单栏"设备",会发现 Thinkpad 硬盘已挂载,如下图
+
+- 选择 "Install or upgrade an existing system" 安装系统,选择安装设备,会看到唯一一个磁盘,如下图
+
+- 该设备就是通过 USB 挂载的 Thinkpad 磁盘,划分好分区,完成系统安装。
+
+# CentOS6.9 虚拟机 Rescue 启动挂载
+- 由于 VirtuaBox 虚拟机尚不支持从 USB 启动,所以在上一步完成系统安装后,重启虚拟机,依旧使用 CentOS-6.9-i386-minimal.iso 启动。
+- 此时从 "Rescue Installed system" 启动,如下图
+
+- 根据提示选择好语言、键盘,激活网卡,如下图
+
+- "OK" 确认,选择网卡,如下图
+
+- "OK",根据自己的网络环境配置上网方式,如下图
+
+- "OK",进入 Rescue 界面,如下图
+
+- "Continue",选择好磁盘,"OK" 确认,提示根分区已挂载至 /mnt/sysimage 下,如下图
+
+- "OK", 进入 Rescue Shell,此时执行
+ ```bash
+ chroot /mnt/sysimage/ /bin/bash
+ ```
+- 至此,通过 VirtualBox 虚拟机成功启动 USB 存储(Thinkpad 硬盘)中的根分区并进入其 Bash 环境。
+
+# 安装 NONPAE 内核
+- 在刚刚启动的 Bash Shell 中,检查网络
+ ```bash
+ ping www.baidu.com
+ ```
+- 关闭 selinux (可选)
+ ```bash
+ sed -i 's/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
+ ```
+- 安装 NONPAE 内核
+ ```bash
+ rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
+ rpm -Uvh http://www.elrepo.org/elrepo-release-6-8.el6.elrepo.noarch.rpm
+ yum --enablerepo=elrepo-kernel install kernel-lt-NONPAE
+ ```
+- 检查系统已安装的内核
+ ```bash
+ rpm -qa|grep kernel
+ ```
+- 发现两个内核,一个是官方的 kernel-2.6,另一个是刚刚安装的 kernel-3.10,kernel-3.10 就是可以在无 pae 处理器上启动的 NONPAE 内核。
+- 退出当前 Chroot Shell 和 Rescure Shell
+ ```bash
+ exit
+ exit
+ ```
+
+# 启动 Thinkpad X31
+- 关闭虚拟机,退出 USB 磁盘,把磁盘装回 Thinkpad X31 笔记本中,开机,选择 3.10 内核即可正常启动。
+- 卸载官方 2.6 内核(推荐)
+ ```bash
+ yum erase kernel-2.6.32*
+ yum erase kernel-firemware-2.6.32*
+ ```
+
diff --git a/content/post/centos6-oracle11g.md b/content/post/centos6-oracle11g.md
new file mode 100644
index 0000000..f7f5c29
--- /dev/null
+++ b/content/post/centos6-oracle11g.md
@@ -0,0 +1,122 @@
+---
+title: "CentOS6 安装 Oracle11g"
+date: 2019-10-30T17:56:57+08:00
+lastmod: 2019-10-30T17:56:57+08:00
+tags: ["oracle", "centos"]
+categories: ["database"]
+---
+
+# 安装依赖
+```bash
+yum install binutils compat-libstdc++-33 compat-libstdc++-33.i686 \
+elfutils-libelf elfutils-libelf-devel gcc gcc-c++ glibc glibc.i686 \
+glibc-common glibc-devel glibc-devel.i686 glibc-headers ksh libaio \
+libaio.i686 libaio-devel libaio-devel.i686 libgcc libgcc.i686 libstdc++ \
+libstdc++.i686 libstdc++-devel make sysstat unixODBC unixODBC-devel
+```
+
+# 创建用户和用户组
+```bash
+groupadd dba oinstall
+useradd -g oinstall -m oracle
+```
+
+# 配置oracle用户环境变量
+- 打开 oracle 用户的的默认shell配置文件 ~/.bashrc,在最后添加以下代码
+ ```bash
+ export ORACLE_BASE=/opt/oracle/app #oracle数据库安装目录
+ export ORACLE_HOME=$ORACLE_BASE/product/11.2.0/db_home1 #oracle数据库路径
+ export ORACLE_SID=orcl #oracle启动数据库实例名
+ export PATH=$ORACLE_HOME/bin:/usr/sbin:$PATH #添加系统环境变量
+ export LD_LIBRARY_PATH=$ORACLE_HOME/lib:/lib:/usr/lib #添加系统环境变量
+ #export NLS_LANG="SIMPLIFIED CHINESE_CHINA.AL32UTF8" #设置Oracle客户端中文utf8
+ export NLS_LANG=AMERICAN_AMERICA.ZHS16GBK #设置Oracle客户端中文gbk
+ ```
+- 使设置立刻生效
+ ```bash
+ source ~/.bashrc
+ ```
+
+# 创建oracle 11g软件安装路径
+```bash
+mkdir /opt/oracle/app/product/11.2.0/db_home1 -p
+chown oracle.oinstall /opt/oracle -R
+```
+
+# 配置内核参数
+- 编辑 /etc/sysctl.conf,在文件尾追加下面的参数设置
+ ```
+ fs.file-max = 6815744
+ fs.aio-max-nr =1048576
+ net.ipv4.ip_local_port_range = 9000 65500
+ net.core.rmem_default = 262144
+ net.core.rmem_max = 4194304
+ net.core.wmem_default = 262144
+ net.core.wmem_max = 1048576
+ kernel.sem = 250 32000 100 128
+ ```
+- 使设置生效
+ ```bash
+ sysctl -p
+ ```
+
+# 限制 oracle 用户资源
+- 编辑 /etc/security/limits.conf,在末尾添加以下代码
+ ```
+ oracle soft nproc 2047
+ oracle hard nproc 16384
+ oracle soft nofile 1024
+ oracle hard nofile 65536
+ ```
+
+# 安装字体
+- 一般需要安装中易宋体字体,百度随便下载一个zysong.ttf,切换到zysong.ttf所在路径,运行:
+ ```bash
+ mkdir /usr/share/fonts/zh_CN/TureType/ -p
+ mv zydong.ttf /usr/share/fonts/zh_CN/TrueType/
+ fc-cache -fv
+ ```
+
+# 安装oracle 11g
+- 解压下载好的oracle 11g文件
+ ```bash
+ unzip linux.x64_11gR2_database_1of2.zip -d /home/oracle/
+ unzip linux.x64_11gR2_database_2of2.zip -d /home/oracle/
+ chown oracle.oinstall /home/oracle/database/ -R
+ ```
+- **切换到 oracle 用户下**,运行安装程序
+ ```bash
+ su - oracle
+ cd database
+ ./runInstaller
+ ```
+- 若提示swap空间不足,自行百度解决!
+
+# 配置监听器数据库
+- Oracle软件安装完后,执行 netca 命令配置监听器
+```
+netca
+```
+- 在图形界面中按提示配置监听器
+- 执行 dbca 命令安装数据库
+```bash
+dbca
+```
+- 在图形界面中按提示安装数据库就可以了。
+
+# 测试运行
+- 数据库安装完后监听器与数据库实例就已启动,
+- 停止和启动监听器
+```bash
+lsnrctl stop
+lsnrctl start
+```
+- 停止和启动实例
+```
+sqlplus /nolog
+SQL> connect / as sysdba;
+SQL> shutdown
+SQL> startup
+#执行其它SQL语句测试数据库
+```
+
diff --git a/content/post/centos7-migrate.md b/content/post/centos7-migrate.md
new file mode 100644
index 0000000..525f41b
--- /dev/null
+++ b/content/post/centos7-migrate.md
@@ -0,0 +1,126 @@
+---
+title: "CentOS7 系统盘迁移"
+date: 2019-10-30T01:03:45+08:00
+lastmod: 2019-10-30T01:03:45+08:00
+keywords: []
+tags: ["centos", "系统", "迁移"]
+categories: ["os"]
+---
+
+# 环境
+- 两台服务器(A,B)
+- A 已安装好 CentOS7,且已**关闭 selinux**
+- B 裸机,待安装操作系统
+
+# 打包根分区
+- 从 B 上拆下系统硬盘,接在 A 上,启动 A
+- 清空日志(推荐)
+ ```bash
+ cd /var/log/
+ find . -type f | xargs rm -f
+ ```
+- 关闭 selinux
+ ```bash
+ sed -i '/^SELINUX=/cSELINUX=disabled' /mnt/etc/selinux/config
+ ```
+- 如果 A 是 MBR 启动,则直接打包根分区
+ ```bash
+ tar zcpf /centos7.tgz --exclude=/centos7.tgz --one-file-system /
+ ```
+- 如果 A 是 EFI 启动,则需打包根分区和 EFI 分区
+ ```bash
+ # 假设 efi 分区挂载在 /boot/efi 下
+ tar zcpf /centos7.tgz --exclude=/centos7.tgz --one-file-system / /boot/efi
+ ```
+
+# 硬盘分区
+- 假设 /dev/sdb 是 B 的系统硬盘
+- MBR 启动时,分区表是 dos,只分一个根分区即可
+- EFI 启动时,分区表是 gpt,需要分一个 512MB 的 **efi 分区**和一个根分区
+ ```bash
+ fdisk /dev/sdb
+ # n 创建新分区
+ # t 指定分区类型 1 (即 efi system)
+ ```
+
+# 格式化
+- MBR 启动
+ ```bash
+ mkfs.xfs /dev/sdb1
+ ```
+- EFI 启动
+ ```bash
+ mkfs.vfat -F32 /dev/sdb1
+ mkfs.xfs /dev/sdb2
+ ```
+
+# 挂载硬盘
+- MBR 启动
+ ```bash
+ mount /dev/sdb1 /mnt/
+ ```
+- EFI 启动
+ ```bash
+ mount /dev/sdb2 /mnt/
+ mkdir -p /mnt/boot/efi
+ mount /dev/sdb1 /mnt/boot/efi/
+ ```
+
+# 部署操作系统
+- 解压之前打包的 /centos7.tgz
+ ```bash
+ tar zxpf /centos7.tgz -C /mnt/
+ ```
+- 替换 fstab 中的 uuid 信息
+ ```bash
+ # 获取 B 的系统硬盘分区的 uuid 信息
+ lsblk -f /dev/sdb
+ # 把结果中的 uuid 替换到 /mnt/etc/fstab 中的相应位置
+ ```
+- 如果打包时未关闭 selinux,此时可以修改配置文件
+ ```bash
+ sed -i '/^SELINUX=/cSELINUX=disabled' /mnt/etc/selinux/config
+ ```
+- 删除网卡硬件标识(推荐)
+ ```bash
+ sed -i -e '/HWADDR/d' -e '/UUID/d' /mnt/etc/sysconfig/network-scripts/ifcfg-{eth,enp}*
+ ```
+- 删除 ssh 主机密钥(推荐)
+ ```bash
+ rm -rf /etc/ssh/ssh_host_*
+ ```
+
+# 部署 grub
+- MBR 启动
+ ```bash
+ mount --bind /dev/ /mnt/dev/
+ mount -t proc procfs /mnt/proc/
+ mount -t sysfs sysfs /mnt/sys/
+ chroot /mnt
+ grub2-install /dev/sdb
+ grub2-mkconfig -o /boot/grub2/grub.cfg
+ exit
+ ```
+- EFI 启动
+ ```bash
+ mount --bind /dev/ /mnt/dev/
+ mount -t proc procfs /mnt/proc/
+ mount -t sysfs sysfs /mnt/sys/
+ mount -t efivarfs efivarfs /target/sys/firmware/efi/efivars/
+ chroot /mnt
+ efibootmgr -c -p 1 -d /dev/sdb -L "centos"
+ grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
+ exit
+ ```
+
+# 启动操作系统
+- 卸载 B 的系统硬盘
+ ```bash
+ umount -R /mnt
+ ```
+- 关闭 A,拆下刚部署好操作系统的硬盘,接回 B 中
+- 启动 B,刚部署的 CentOS7 正常启动
+
+# 参考
+- [https://wiki.centos.org/zh/HowTos/ManualInstall?highlight=%28grub2-install%29](https://wiki.centos.org/zh/HowTos/ManualInstall?highlight=%28grub2-install%29)
+
diff --git a/content/post/centos7.md b/content/post/centos7.md
new file mode 100644
index 0000000..f6e9774
--- /dev/null
+++ b/content/post/centos7.md
@@ -0,0 +1,189 @@
+---
+title: "CentOS7 笔记"
+date: 2019-10-30T10:58:18+08:00
+lastmod: 2019-10-30T10:58:18+08:00
+keywords: []
+tags: ["centos"]
+categories: ["os"]
+---
+
+# 常用初始配置
+- 系统更新
+ ```bash
+ yum update
+ ```
+- 禁用 firewalld
+ ```bash
+ systemctl stop firewalld
+ systemctl disable firewalld
+ ```
+- 禁用 NetworkManager
+ ```bash
+ systemctl stop NetworkManager
+ systemctl disable NetworkManager
+ ```
+- 禁用 postfix
+ ```bash
+ systemctl stop postfix
+ systemctl disable postfix
+ ```
+- 如果不用 NFS,可以禁用 rpcbind
+ ```bash
+ systemctl stop rpcbind
+ systemctl disable rpcbind
+ ```
+- 禁用 selinux,可能需要重启操作系统
+ ```bash
+ sed -i '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
+ setenforce 0
+ # 可能需要重启
+ ```
+- 配置网卡静态地址
+ ```bash
+ cd /etc/sysconfig/network-scripts
+ sed -i -e '/^BOOTPROTO/d' -e '/^ONBOOT/d' \
+ -e '/^IPADDR/d' -e '/^NETMASK/d' -e '/^PREFIX/d' \
+ -e '/^GATEWAY/d' -e '/^DNS/d' ${ifcfg}
+ cat >> ${ifcfg} <<-END
+ ONBOOT=yes
+ BOOTPROTO=static
+ IPADDR=${ip}
+ PREFIX=${mask}
+ GATEWAY=${gw}
+ DNS1=${dns}
+ END
+ systemctl restart network
+ ```
+- 修改 sysctl.conf
+ ```bash
+ cat >> /etc/sysctl.conf <<-END
+ # 防止一个套接字在有过多试图连接到达时引起过载
+ net.ipv4.tcp_syncookies = 1
+ # 连接队列的长度,默认值为128
+ net.core.somaxconn = 1024
+ # timewait的超时时间,设置短一些
+ net.ipv4.tcp_fin_timeout = 10
+ # os直接使用timewait的连接
+ net.ipv4.tcp_tw_reuse = 1
+ # 回收timewait连接
+ net.ipv4.tcp_tw_recycle = 1
+ END
+ sysctl -p
+ ```
+- 修改主机名
+ ```bash
+ hostnamectl set-hostname ${hostname}
+ sed -i "/[ \t]\+${hostname}[ \t]*$/d" /etc/hosts
+ echo "${ip} ${hostname}" >> /etc/hosts
+ ```
+- 禁用 sshd 域名解析
+ ```bash
+ sed -i '/UseDNS/d' /etc/ssh/sshd_config
+ echo 'UseDNS no' >> /etc/ssh/sshd_config
+ ```
+- 删除可能存在的 TMOUT 环境变量
+ ```bash
+ sed -i '/^export[ \t]\+TMOUT=/d' /etc/profile
+ ```
+- 配置 history 命令数量和执行时间
+ ```bash
+ echo 'export HISTSIZE=10000' > /etc/profile.d/history.sh
+ echo 'export HISTTIMEFORMAT="[%F %T] "' >> /etc/profile.d/history.sh
+ ```
+- 修改时间同步服务器地址
+ ```bash
+ sed -i '/^server /d' /etc/chrony.conf
+ echo "server ${ip|domain} iburst" >> /etc/chrony.conf
+ ```
+- 修改 rsyslog 服务的时间格式
+ ```bash
+ cat > /etc/rsyslog.d/custom.conf <> /etc/hosts
+ echo "10.0.4.42 ceph42" >> /etc/hosts
+ echo "10.0.4.43 ceph43" >> /etc/hosts
+ ```
+
+# 配置 yum 源
+- 在全部节点上执行如下操作
+- 移动系统默认的 repo 文件到备份目录
+ ```bash
+ cd /etc/yum.repos.d
+ mkdir bak
+ mv Rocky-*.repo bak/
+ ```
+
+- 创建新的系统 yum 源文件 /etc/yum.repos.d/rocky-nju.repo,使用南京大学镜像站,内容如下
+ ```
+ [appstream]
+ name=Rocky Linux $releasever - AppStream
+ baseurl=https://mirrors.nju.edu.cn/$contentdir/$releasever/AppStream/$basearch/os/
+ gpgcheck=1
+ enabled=1
+ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial
+
+ [baseos]
+ name=Rocky Linux $releasever - BaseOS
+ baseurl=https://mirrors.nju.edu.cn/$contentdir/$releasever/BaseOS/$basearch/os/
+ gpgcheck=1
+ enabled=1
+ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial
+ ```
+
+- 创建 epel yum 源文件 /etc/yum.repos.d/epel-tsinghua.repo,使用清华大学镜像站,内容如下
+ ```
+ [epel]
+ name=Extra Packages for Enterprise Linux $releasever - $basearch
+ baseurl=https://mirrors.tuna.tsinghua.edu.cn/epel/$releasever/Everything/$basearch
+ failovermethod=priority
+ enabled=1
+ gpgcheck=1
+ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$releasever
+ ```
+
+- 下载 RPM-GPG-KEY-EPEL-8
+ ```bash
+ cd /etc/pki/rpm-gpg/
+ curl -LO https://mirrors.nju.edu.cn/epel/RPM-GPG-KEY-EPEL-8
+ ```
+
+- 创建 ceph yum 源文件 /etc/yum.repos.d/ceph-tsinghua.repo,使用清华大学镜像站,内容如下
+ ```
+ [ceph]
+ name=Ceph packages for $basearch
+ baseurl=https://mirrors.tuna.tsinghua.edu.cn/ceph/rpm-15.2.14/el8/$basearch
+ enabled=1
+ priority=2
+ gpgcheck=1
+ gpgkey=https://mirrors.tuna.tsinghua.edu.cn/ceph/keys/release.asc
+
+ [ceph-noarch]
+ name=Ceph noarch packages
+ baseurl=https://mirrors.tuna.tsinghua.edu.cn/ceph/rpm-15.2.14/el8/noarch
+ enabled=1
+ priority=2
+ gpgcheck=1
+ gpgkey=https://mirrors.tuna.tsinghua.edu.cn/ceph/keys/release.asc
+ ```
+
+- 导入 release.asc
+ ```bash
+ rpm --import 'https://mirrors.tuna.tsinghua.edu.cn/ceph/keys/release.asc'
+ ```
+
+- 更新 yum 缓存
+ ```bash
+ dnf clean all
+ dnf makecache
+ ```
+
+# 配置时间同步
+- 在全部节点上执行如下操作
+- 安装 chrony
+ ```bash
+ dnf install chrony
+ ```
+
+- 如果内网没有时间服务器,可以在 ceph41 上启动一个时间同步服务,修改 /etc/chrony.conf
+ ```
+ ...
+ pool ntp.aliyun.com iburst
+ ...
+ allow 10.0.4.0/24
+ ...
+ local stratum 10
+ ...
+ ```
+
+- 设置 ceph42 和 ceph43 从 ceph41 上同步时间,修改 /etc/chrony.conf
+ ```
+ ...
+ pool ceph41 iburst
+ ...
+ ```
+
+- 在全部服务器上启动 chronyd 服务,并设置开机自动启动
+ ```bash
+ systemctl start chronyd
+ systemctl enable chronyd
+ ```
+
+# 安装 ceph
+- 在全部节点上执行如下操作
+ ```bash
+ dnf install leveldb gdisk gperftools-libs python3-ceph-argparse nvme-cli
+ dnf install ceph
+ ```
+
+- 创建 ceph 配置文件 /etc/ceph/ceph.conf,内容如下
+ ```
+ [global]
+ fsid = aaaa0000-bbbb-1111-cccc-2222dddd3333
+ mon_initial_members = ceph41, ceph42, ceph43
+ mon_host = 10.0.4.41, 10.0.4.42, 10.0.4.43
+ public_network = 10.0.4.0/24
+ cluster_network = 192.168.4.0/24
+ auth_cluster_required = cephx
+ auth_service_required = cephx
+ auth_client_required = cephx
+ osd_pool_default_size = 2 # 推荐使用官方默认的 3
+ osd_pool_default_min_size = 2
+ ```
+
+# 部署 mon
+- 在 ceph41 上执行如下操作
+- 这里创建了一堆傻逼密钥文件,没看懂啥意思,照搬官网
+ ```bash
+ ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'
+ ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'
+ ceph-authtool --create-keyring /var/lib/ceph/bootstrap-osd/ceph.keyring --gen-key -n client.bootstrap-osd --cap mon 'profile bootstrap-osd' --cap mgr 'allow r'
+ ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring
+ ceph-authtool /tmp/ceph.mon.keyring --import-keyring /var/lib/ceph/bootstrap-osd/ceph.keyring
+ chown ceph:ceph /tmp/ceph.mon.keyring
+ monmaptool --create --add ceph41 10.0.4.41 --add ceph42 10.0.4.42 --add ceph43 10.0.4.43 --fsid aaaa0000-bbbb-1111-cccc-2222dddd3333 /tmp/monmap
+ ```
+
+- 初始化 mon 数据目录
+ ```bash
+ sudo -u ceph mkdir /var/lib/ceph/mon/ceph-ceph41
+ sudo -u ceph ceph-mon --mkfs -i ceph41 --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring
+ ```
+
+- 启动 mon 服务,并设置开机自动启动
+ ```bash
+ systemctl start ceph-mon@ceph41
+ systemctl enable ceph-mon@ceph41
+ ```
+
+- 复制密钥文件到 ceph42 和 ceph43 上
+ ```bash
+ scp /tmp/{ceph.mon.keyring,monmap} ceph42:/tmp/
+ scp /etc/ceph/ceph.client.admin.keyring ceph42:/etc/ceph/
+ scp /var/lib/ceph/bootstrap-osd/ceph.keyring ceph42:/var/lib/ceph/bootstrap-osd/
+
+ scp /tmp/{ceph.mon.keyring,monmap} ceph43:/tmp/
+ scp /etc/ceph/ceph.client.admin.keyring ceph43:/etc/ceph/
+ scp /var/lib/ceph/bootstrap-osd/ceph.keyring ceph43:/var/lib/ceph/bootstrap-osd/
+ ```
+
+- 在 **ceph42** 上执行如下操作
+- 初始化 mon 数据目录
+ ```bash
+ chown ceph:ceph /tmp/ceph.mon.keyring
+ sudo -u ceph mkdir /var/lib/ceph/mon/ceph-ceph42
+ sudo -u ceph ceph-mon --mkfs -i ceph42 --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring
+ ```
+
+- 启动 mon 服务,并设置开机自动启动
+ ```bash
+ systemctl start ceph-mon@ceph42
+ systemctl enable ceph-mon@ceph42
+ ```
+
+- 在 **ceph43** 上执行如下操作
+- 初始化 mon 数据目录
+ ```bash
+ chown ceph:ceph /tmp/ceph.mon.keyring
+ sudo -u ceph mkdir /var/lib/ceph/mon/ceph-ceph43
+ sudo -u ceph ceph-mon --mkfs -i ceph43 --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring
+ ```
+
+- 启动 mon 服务,并设置开机自动启动
+ ```bash
+ systemctl start ceph-mon@ceph43
+ systemctl enable ceph-mon@ceph43
+ ```
+
+- 在任一节点上执行如下操作
+- mon 额外配置
+ ```bash
+ # 开启 msgr2,监听 tcp 3300 端口
+ ceph mon enable-msgr2
+
+ # 禁用 auth_allow_insecure_global_id_reclaim
+ ceph config set mon auth_allow_insecure_global_id_reclaim false
+ ```
+
+## 查看集群状态
+- 在任一节点上执行如下操作
+ ```bash
+ ceph -s
+ ```
+
+- 集群状态如下
+ ```
+ cluster:
+ id: aaaa0000-bbbb-1111-cccc-2222dddd3333
+ health: HEALTH_OK
+
+ services:
+ mon: 3 daemons, quorum ceph41,ceph42,ceph43 (age ...)
+ mgr: no daemons active
+ osd: 0 osds: 0 up, 0 in
+
+ data:
+ pools: 0 pools, 0 pgs
+ objects: 0 objects, 0 B
+ usage: 0 B used, 0 B / 0 B avail
+ pgs:
+ ```
+
+# 部署 mgr
+- 在 ceph41 上执行如下操作
+- 又是创建密钥文件,没看懂啥意思,照搬官网
+ ```bash
+ sudo -u ceph mkdir /var/lib/ceph/mgr/ceph-ceph41
+ ceph auth get-or-create mgr.ceph41 mon 'allow profile mgr' osd 'allow *' mds 'allow *' -o /var/lib/ceph/mgr/ceph-ceph41/keyring
+ chown ceph.ceph /var/lib/ceph/mgr/ceph-ceph41/keyring
+ ```
+
+- 启动 mgr 服务,并配置开机自动启动
+ ```bash
+ systemctl start ceph-mgr@ceph41
+ systemctl enable ceph-mgr@ceph41
+ ```
+
+- 在 ceph42 上执行如下操作
+- 创建密钥文件
+ ```bash
+ sudo -u ceph mkdir /var/lib/ceph/mgr/ceph-ceph42
+ ceph auth get-or-create mgr.ceph42 mon 'allow profile mgr' osd 'allow *' mds 'allow *' -o /var/lib/ceph/mgr/ceph-ceph42/keyring
+ chown ceph.ceph /var/lib/ceph/mgr/ceph-ceph42/keyring
+ ```
+
+- 启动 mgr 服务,并配置开机自动启动
+ ```bash
+ systemctl start ceph-mgr@ceph42
+ systemctl enable ceph-mgr@ceph42
+ ```
+
+- 在 ceph43 上执行如下操作
+- 创建密钥文件
+ ```bash
+ sudo -u ceph mkdir /var/lib/ceph/mgr/ceph-ceph43
+ ceph auth get-or-create mgr.ceph43 mon 'allow profile mgr' osd 'allow *' mds 'allow *' -o /var/lib/ceph/mgr/ceph-ceph43/keyring
+ chown ceph.ceph /var/lib/ceph/mgr/ceph-ceph43/keyring
+ ```
+
+- 启动 mgr 服务,并配置开机自动启动
+ ```bash
+ systemctl start ceph-mgr@ceph43
+ systemctl enable ceph-mgr@ceph43
+ ```
+
+## 查看集群状态
+- 在任一节点上执行如下操作
+ ```bash
+ ceph -s
+ ```
+
+- 集群状态如下
+ ```
+ cluster:
+ id: aaaa0000-bbbb-1111-cccc-2222dddd3333
+ health: HEALTH_WARN
+ OSD count 0 < osd_pool_default_size 2
+
+ services:
+ mon: 3 daemons, quorum ceph41,ceph42,ceph43 (age ...)
+ mgr: ceph41(active, since ...), standbys: ceph42, ceph43
+ osd: 0 osds: 0 up, 0 in
+
+ data:
+ pools: 0 pools, 0 pgs
+ objects: 0 objects, 0 B
+ usage: 0 B used, 0 B / 0 B avail
+ pgs:
+ ```
+
+# 部署 osd
+## 逻辑卷 osd
+- 操作简单,推荐
+- 直接创建并启动逻辑卷 osd
+ ```bash
+ ceph-volume lvm create --bluestore --data /dev/sdb
+ ceph-volume lvm create --bluestore --data /dev/sdc
+ ```
+
+- 上一步执行成功后,每个 ceph-osd 服务都已启动,且开机自动启动
+
+## 裸设备 osd
+- 操作麻烦,不推荐
+- 在全部节点上执行如下操作
+- 准备 osd
+ ```bash
+ ceph-volume raw prepare --bluestore --data /dev/sdb
+ ceph-volume raw prepare --bluestore --data /dev/sdc
+ ```
+
+- 查看 osd 的 id
+ ```bash
+ ceph-volume raw list
+ ```
+
+- 激活 osd,**使用裸设备创建 osd 时不支持 systemd,需要单独配置开机自动启动**
+ ```bash
+ ceph-volume raw activate --device /dev/sdb --no-systemd
+ ceph-volume raw activate --device /dev/sdc --no-systemd
+ ```
+
+- 在 ceph41 上启动 osd 服务
+ ```bash
+ systemctl start ceph-osd@0
+ systemctl start ceph-osd@1
+ ```
+
+- 配置开机自动启动
+ ```bash
+ chmod 0755 /etc/rc.d/rc.local
+ echo 'ceph-volume raw activate --device /dev/sdb --no-systemd
+ ceph-volume raw activate --device /dev/sdc --no-systemd
+ systemctl start ceph-osd@0
+ systemctl start ceph-osd@1
+ ' >> /etc/rc.d/rc.local
+ ```
+
+- 在 ceph42 上启动 osd 服务
+ ```bash
+ systemctl start ceph-osd@2
+ systemctl start ceph-osd@3
+ ```
+
+- 配置开机自动启动
+ ```bash
+ chmod 0755 /etc/rc.d/rc.local
+ echo 'ceph-volume raw activate --device /dev/sdb --no-systemd
+ ceph-volume raw activate --device /dev/sdc --no-systemd
+ systemctl start ceph-osd@2
+ systemctl start ceph-osd@3
+ ' >> /etc/rc.d/rc.local
+ ```
+
+- 在 ceph43 上启动 osd 服务
+ ```bash
+ systemctl start ceph-osd@4
+ systemctl start ceph-osd@5
+ ```
+
+- 配置开机自动启动
+ ```bash
+ chmod 0755 /etc/rc.d/rc.local
+ echo 'ceph-volume raw activate --device /dev/sdb --no-systemd
+ ceph-volume raw activate --device /dev/sdc --no-systemd
+ systemctl start ceph-osd@4
+ systemctl start ceph-osd@5
+ ' >> /etc/rc.d/rc.local
+ ```
+
+## 查看集群状态
+- 在任一节点执行如下操作
+ ```bash
+ ceph -s
+ ```
+
+- 集群状态如下
+ ```
+ cluster:
+ id: aaaa0000-bbbb-1111-cccc-2222dddd3333
+ health: HEALTH_OK
+
+ services:
+ mon: 3 daemons, quorum ceph41,ceph42,ceph43 (age ...)
+ mgr: ceph41(active, since ...), standbys: ceph42, ceph43
+ osd: 6 osds: 6 up (since ...), 6 in (since ...)
+
+ data:
+ pools: 1 pools, 1 pgs
+ objects: 0 objects, 0 B
+ usage: ... GiB used, ... GiB / ... GiB avail
+ pgs: 1 active+clean
+ ```
+
+# 部署 mds
+- 只有 cephfs 会用到 mds
+- 在 ceph41 上执行如下操作
+- 创建密钥文件 ...... 照搬官网
+ ```bash
+ sudo -u ceph mkdir -p /var/lib/ceph/mds/ceph-ceph41
+ sudo -u ceph ceph-authtool --create-keyring /var/lib/ceph/mds/ceph-ceph41/keyring --gen-key -n mds.ceph41
+ ceph auth add mds.ceph41 osd "allow rwx" mds "allow *" mon "allow profile mds" -i /var/lib/ceph/mds/ceph-ceph41/keyring
+ ```
+
+- 启动 mds 服务,并配置开机自动启动
+ ```bash
+ systemctl start ceph-mds@ceph41
+ systemctl enable ceph-mds@ceph41
+ ```
+
+- 在 ceph42 上执行如下操作
+- 创建密钥文件 ...... 照搬官网
+ ```bash
+ sudo -u ceph mkdir -p /var/lib/ceph/mds/ceph-ceph42
+ sudo -u ceph ceph-authtool --create-keyring /var/lib/ceph/mds/ceph-ceph42/keyring --gen-key -n mds.ceph42
+ ceph auth add mds.ceph42 osd "allow rwx" mds "allow *" mon "allow profile mds" -i /var/lib/ceph/mds/ceph-ceph42/keyring
+ ```
+
+- 启动 mds 服务,并配置开机自动启动
+ ```bash
+ systemctl start ceph-mds@ceph42
+ systemctl enable ceph-mds@ceph42
+ ```
+
+- 在 ceph43 上执行如下操作
+- 创建密钥文件 ...... 照搬官网
+ ```bash
+ sudo -u ceph mkdir -p /var/lib/ceph/mds/ceph-ceph43
+ sudo -u ceph ceph-authtool --create-keyring /var/lib/ceph/mds/ceph-ceph43/keyring --gen-key -n mds.ceph43
+ ceph auth add mds.ceph43 osd "allow rwx" mds "allow *" mon "allow profile mds" -i /var/lib/ceph/mds/ceph-ceph43/keyring
+ ```
+
+- 启动 mds 服务,并配置开机自动启动
+ ```bash
+ systemctl start ceph-mds@ceph43
+ systemctl enable ceph-mds@ceph43
+ ```
+
+## 查看集群状态
+- 在任一节点上执行如下操作
+ ```bash
+ ceph -s
+ ```
+
+- 集群状态如下
+ ```
+ cluster:
+ id: aaaa0000-bbbb-1111-cccc-2222dddd3333
+ health: HEALTH_OK
+
+ services:
+ mon: 3 daemons, quorum ceph41,ceph42,ceph43 (age ...)
+ mgr: ceph41(active, since ...), standbys: ceph42, ceph43
+ mds: cephfs:1 {0=ceph43=up:active} 2 up:standby
+ osd: 3 osds: 3 up, 3 in
+
+ data:
+ pools: 1 pools, 1 pgs
+ objects: 0 objects, 0 B
+ usage: ... GiB used, ... GiB / ... GiB avail
+ pgs: 1 active+clean
+ ```
+
+# 简单使用
+## rbd
+- 创建 rbd 池
+ ```bash
+ ceph osd pool create rbd 128 128
+ ceph osd pool application enable rbd rbd
+ ```
+
+## cephfs
+- 创建 cephfs 池
+ ```bash
+ # 创建 cephfs 元数据池,pg 不用太大,设置 3 个副本
+ ceph osd pool create cephfs_metadata 8 8
+ ceph osd pool set cephfs_metadata size 3
+
+ # 创建 cephfs 数据池,根据数据量配置相应 pg
+ ceph osd pool create cephfs_data 128 128
+ ```
+
+- 创建 cephfs 文件系统
+ ```bash
+ ceph fs new cephfs cephfs_metadata cephfs_data
+ ```
+
+- 查看 mds 状态
+ ```bash
+ ceph mds stat
+ ```
+
+- 在任一 ceph 节点服务器上查看 admin 用户的 key
+ ```bash
+ cat /etc/ceph/ceph.client.admin.keyring | grep key | awk '{print $2}'
+ ```
+
+- 在其他服务器上挂载 cephfs
+ ```bash
+ mount -t ceph 10.0.4.41:6789,10.0.4.42:6789,10.0.4.43:6789:/ /mnt -o name=admin,secret={admin 的 key}
+ ```
+
diff --git a/content/post/ceph.md b/content/post/ceph.md
new file mode 100644
index 0000000..647ddd3
--- /dev/null
+++ b/content/post/ceph.md
@@ -0,0 +1,177 @@
+---
+title: "Ceph 笔记"
+date: 2019-10-30T11:44:37+08:00
+lastmod: 2019-10-30T11:44:37+08:00
+tags: ["ceph"]
+categories: ["storage"]
+---
+
+# 测试环境
+操作系统 | 主机名 | IP | OSD 设备 | OSD ID | 容量 | ceph 版本
+---- | ---- | ---- | ---- | ---- | ---- | ----
+CentOS7 | ceph101 | 192.168.1.101 | /dev/sdb | 0 | 3TB | jewel
+CentOS7 | ceph102 | 192.168.1.102 | /dev/sdb | 1 | 3TB | jewel
+CentOS7 | ceph103 | 192.168.1.103 | /dev/sdb | 2 | 3TB | jewel
+
+- ceph 部署机
+ - 操作系统: CentOS7
+ - 部署用户: cephdeploy
+ - 操作目录: /home/cephdeploy/ceph-cluster
+ - IP: 192.168.1.100
+
+# 新增 OSD
+- 设置 ceph 三个 noflag,禁止 ceph 自动迁移数据
+ ```bash
+ # 在任一节点上执行下面命令即可
+ ceph osd set noout
+ ceph osd set nobackfill
+ ceph osd set norecover
+ ceph -s # 此时能看到这三个 flag,而且集群处于不健康状态
+ ```
+- 关闭这三台 ceph 服务器, 加装新的磁盘,个人认为无需配置多盘 raid
+- 启动这三台 ceph 服务器,此时 ceph 自动启动,那三个 noflag 依旧有效
+- 此时查看每台服务器的存储,应该能看到新增的一个或多个裸磁盘
+ ```bash
+ lsblk
+ ```
+- 这里假设每台服务器新增两个磁盘(/dev/sdc,/dev/sdd),都配置成 osd
+ ```bash
+ # 在 ceph 部署机上执行以下命令
+ su - cephdeploy
+ cd /home/cephdeploy/ceph-cluster
+ # 每次创建一个,别浪 ...
+ ceph-deploy osd create ceph101:/dev/sdc
+ ceph-deploy osd create ceph101:/dev/sdd
+ ceph-deploy osd create ceph102:/dev/sdc
+ ceph-deploy osd create ceph102:/dev/sdd
+ ceph-deploy osd create ceph103:/dev/sdc
+ ceph-deploy osd create ceph103:/dev/sdd
+ ```
+- osd 增加完成后, 取消之前设置的那三个 noflag
+ ```bash
+ # 在任一节点上执行下面命令即可
+ ceph osd unset noout
+ ceph osd unset nobackfill
+ ceph osd unset norecover
+ ```
+- 此时数据向新增的 osd 上均衡,时间不确定 …… 只能等!
+ ```bash
+ # 在任一节点上执行下面命令,观察数据迁移
+ ceph -w
+ ```
+- 直至 ceph 恢复健康状态
+ ```bash
+ # 在任一节点上执行下面命令,查看集群状态
+ ceph -s
+ ```
+
+# 删除 OSD
+- 把指定的 osd 踢出集群
+ ```bash
+ # 在任一节点上执行下面命令即可
+ ceph osd out {osd-id}
+ ceph -s # 此时能看到一个 osd 已经 out
+ ```
+- 此时数据在剩下的几个 osd 上均衡,时间不确定 …… 只能等!
+ ```bash
+ # 在任一节点上执行下面命令,观察数据迁移
+ ceph -w
+ ```
+- 直至 ceph 恢复健康状态
+ ```bash
+ # 在任一节点上执行下面命令,查看集群状态
+ ceph -s
+ ```
+- 停止该被踢出的 osd
+ ```bash
+ # 在运行该 osd 的节点上执行下面命令
+ systemctl stop ceph-osd@{osd-id}
+ ceph -s # 此时能看到一个 osd 已经 down
+ ```
+- 删除该被停止的 osd
+ ```bash
+ # 在任一节点上执行下面命令即可
+ # 删除 CRUSH 图对应的 osd 条目
+ ceph osd crush remove osd.{osd-id}
+ # 删除 osd 认证密钥
+ ceph auth del osd.{osd-id}
+ # 删除 osd
+ ceph osd rm {osd-num}
+ # 删除各节点的 ceph.conf 可能存在的 osd.{osd-id} 配置
+ ```
+- 设置 ceph 三个 noflag,禁止 ceph 自动迁移数据
+ ```bash
+ # 在任一节点上执行下面命令即可
+ ceph osd set noout
+ ceph osd set nobackfill
+ ceph osd set norecover
+ ceph -s # 此时能看到这三个 flag,而且集群处于不健康状态
+ ```
+- 关闭这三台 ceph 服务器,撤掉已被删除 osd 对应的旧磁盘
+- 启动这三台 ceph 服务器,此时 ceph 自动启动,三个 noflag 依旧有效;
+- 取消之前设置的那三个 noflag
+ ```bash
+ # 在任一节点上执行下面命令即可
+ ceph osd unset noout
+ ceph osd unset nobackfill
+ ceph osd unset norecover
+ ```
+- 直至 ceph 恢复健康状态
+ ```bash
+ # 在任一节点上执行下面命令,查看集群状态
+ ceph -s
+ ```
+
+# OSD 动态配置
+- 查看 osd 当前配置
+ ```bash
+ ceph -n osd.0 --show-config
+ ```
+- 动态修改 osd 某个参数
+ ```bash
+ ceph tell osd.* injectargs '--osd_max_backfills 7'
+ ```
+
+# PG 和 PGP
+- 少于 5 个 OSD 时可把 pg_num 设置为 128
+- OSD 数量在 5 到 10 个时,可把 pg_num 设置为 512
+- OSD 数量在 10 到 50 个时,可把 pg_num 设置为 1024
+- OSD 数量大于 50 时, * 100/副本数量(默认3),该值接近的 2 的 N 次方值
+- 存储池的 PG 和 PGP 数量一般相等,都是 2 的 N 次方,只能增加,每次增加为当前的 2 倍
+- 查看存储池的 PG 和 PGP 数量
+ ```bash
+ ceph osd pool get {pool_name} pg_num
+ ceph osd pool get {pool_name} pgp_num
+ ```
+- 增加/设置存储池的 PG 和 PGP 数量
+ ```bash
+ ceph osd pool set {pool_name} *2
+ ceph osd pool set {pool_name} *2
+ ```
+- 获取所有卡在某状态的归置组统计信息
+ ```bash
+ ceph pg dump_stuck inactive|unclean|stale|undersized|degraded
+ #Inactive (不活跃)归置组不能处理读写,因为它们在等待一个有最新数据的 OSD 复活且进入集群
+ #Unclean (不干净)归置组含有复制数未达到期望数量的对象,它们应该在恢复中
+ ```
+- 获取一个具体归置组的归置组图
+ ```bash
+ ceph pg map {pg-id}
+ ```
+
+# CEPH 服务器关机维护
+- 设置 ceph 节点 down 后不自动迁移或恢复数据
+ ```bash
+ ceph osd set noout
+ ceph osd set nobackfill
+ ceph osd set norecover
+ ```
+- 直接关机
+- 下次开机
+- 设置 ceph 节点开始自动迁移或回复数据
+ ```bash
+ ceph osd unset noout
+ ceph osd unset nobackfill
+ ceph osd unset norecover
+ ```
+
diff --git a/content/post/ch-buffer.md b/content/post/ch-buffer.md
new file mode 100644
index 0000000..f6f6877
--- /dev/null
+++ b/content/post/ch-buffer.md
@@ -0,0 +1,39 @@
+---
+title: "ClickHouse 表引擎之 Buffer"
+date: 2020-10-08T18:08:00+08:00
+lastmod: 2020-10-08T18:08:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# Buffer 表引擎简介
+- 只写内存,无持久化存储
+- 缓冲高并发写入,满足条件时,Buffer 表会把数据刷新到目标表
+
+# 创建 Join 引擎表
+- 声明
+ ```sql
+ ENGINE = Buffer(
+ database,
+ table,
+ num_layers,
+ min_time,
+ max_time,
+ min_rows,
+ max_rows,
+ min_bytes,
+ max_bytes
+ )
+ ```
+
+- database: 目标表所在数据库
+- table: 目标表名
+- num_layers: 刷新数据到目标表时开启的线程数,官方建议 16
+- min_time 和 max_time: 时间条件,单位秒
+- min_rows 和 max_rows: 数据行数条件
+- min_bytes 和 max_bytes: 数据体量条件,单位字节
+- 所有最小阈值都满足时,触发刷新
+- 至少一个最大阈值满足时,触发刷新
+- 如果一批数据的行数大于 max_rows,或体量大于 max_bytes,则数据直接写入目标表
+- 各线程(num_layers)单独计算刷新规则
+
diff --git a/content/post/ch-datadict.md b/content/post/ch-datadict.md
new file mode 100644
index 0000000..66d1a22
--- /dev/null
+++ b/content/post/ch-datadict.md
@@ -0,0 +1,255 @@
+---
+title: "ClickHouse 数据字典"
+date: 2020-09-23T13:30:00+08:00
+lastmod: 2020-10-08T18:50:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# 简介
+- 常驻内存,支持动态更新
+- 适合保存常量和经常使用的维度表数据
+- 可通过字典函数访问,也可通过袋里表与其他数据表实现 JOIN 查询
+
+# 内置字典
+- 默认禁用
+- 不想写了,没啥意思
+
+# 外部扩展字典
+## 配置文件
+- 位置: /etc/clickhouse-server/\*\_dictionary.xml
+- 自动感知变更,不停机在线更新
+- 系统表: system.dictionaries
+- 配置结构
+ ```xml
+
+
+
+
+ dict_name
+
+
+
+
+
+ field_name
+
+
+
+
+
+ field_name
+ field_type
+
+
+ ...
+
+
+
+
+
+
+ field_name
+ String
+
+
+
+
+
+ field_name
+
+
+ field_name
+
+
+
+
+
+ field_name
+
+
+ field_type
+
+
+
+
+
+
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 16384
+
+
+
+
+
+
+
+ 16384
+
+
+
+
+
+
+
+
+
+
+ /path/to/data.csv
+ CSV
+
+
+
+
+ cat /path/to/data.csv
+ CSV
+
+
+
+
+ http://192.168.1.2:9080/data.csv
+ CSV
+
+
+
+
+ root
+ 123456
+
+ 192.168.1.3
+ 1
+
+ 3306
+ db_name
+
+
+
+ id=1
+
+
+ sql
+
+
+
+
+ default
+
+ 192.168.1.4
+ 9000
+ default
+
+ id=1
+ sql
+
+
+
+
+
+
+ 192.168.1.5
+ 27017
+ db_name
+ collection_name
+
+
+
+
+
+
+
+ 300
+ 360
+
+
+
+ ...
+
+
+ ```
+
+## 操作
+- 手动更新全部数据字典
+ ```sql
+ SYSTEM RELOAD DICTIONARIES;
+ ```
+
+- 手动更新指定字典
+ ```sql
+ SYSTEM RELOAD DICTIONARY dict_name
+ ```
+
+- 查看所有字典信息
+ ```sql
+ SELECT name, type, key, attribute.names, attribute.types source FROM system.dictionaries;
+ ```
+
+- 字典函数查询
+ ```sql
+ SELECT dictGet('dict_name', 'attr_name', key)
+ ```
+
+- 其他字典函数
+ - 无符号整型: dictGetUInt8、dictGetUInt16、dictGetUInt32、dictGetUInt64
+ - 整型: dictGetInt8、dictGetInt16、dictGetInt32、dictGetInt64
+ - 浮点数: dictGetFloat32、dictGetFloat64
+ - 字符串: ditGetString、dictGetUUID
+ - 日期: dictGetDate、dictGetDateTime
+
+- ddl 创建字典
+ ```sql
+ CREATE DICTIONARY dict_name(
+ ...
+ ) PRIMARY KEY id
+ LAYOUT(FLAT())
+ SOURCE(FILE(PATH '/path/to/data.csv' FORMAT CSV))
+ LIFETIME(1);
+ ```
+
+# Dictionary 表引擎
+- 创建字典表
+ ```sql
+ CREATE TABLE table_name(
+ ...
+ ) ENGINE = Dictionary(dict_name);
+ ```
+
+- dict_name: 已加载的字典名称
+- 创建字典数据库,自动为每个字典创建对应的字典表
+ ```sql
+ CREATE DATABASE dict_db ENGINE = Dictionary;
+ ```
+
diff --git a/content/post/ch-datatype.md b/content/post/ch-datatype.md
new file mode 100644
index 0000000..cbbfe8f
--- /dev/null
+++ b/content/post/ch-datatype.md
@@ -0,0 +1,343 @@
+---
+title: "ClickHouse 数据定义"
+date: 2020-09-23T12:00:00+08:00
+lastmod: 2020-09-23T12:00:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# 基础类型
+## 整数
+声明 | 大小(字节) | 范围
+---- | ---- | ----
+Int8 | 1 | -128 到 127
+UInt8 | 1 | 0 到 255
+Int16 | 2 | -32768 到 32767
+UInt16 | 2 | 0 到 65535
+Int32 | 4 | -2147483648 到 2147483647
+UInt32 | 4 | 0 到 4294967295
+Int64 | 8 | -9223372036854775808 到 9223372036854775807
+UInt64 | 8 | 0 到 18446744073709551615
+
+## 浮点数
+声明 | 大小(字节) | 有效精度(位数)
+---- | ---- | ----
+Float32 | 4 | 7
+Float64 | 8 | 16
+
+- 正无穷: SELECT 0.8/0
+- 负无穷: SELECT -0.8/0
+- 非数字: SELECT 0/0
+
+## 定点数
+- 原生声明: Decimal(P,S)
+ - P: 总位数(整数+小数),取值范围 1~38
+ - S: 小数位数,取值范围 0~P
+
+- 其他声明
+ - Decimal32(S): -10^(9-S) 到 10^(9-S)
+ - Decimal64(S): -10^(18-S) 到 10^(18-S)
+ - Decimal128(S): -10^(18-S) 到 10^(38-S)
+
+## 字符串
+声明 | 备注
+---- | ----
+String | 长度不固定,不限字符集,建议 UTF-8
+FixedString(N) | 长度固定,null 字节填充
+UUID | 格式是 8-4-4-4-12,0 填充
+
+## 时间
+声明 | 精度 | 示例
+---- | ---- | ----
+Datetime | 秒 | 2020-09-18 19:59:00
+Datetime64(N) | 亚秒 | 2020-09-18 19:59:00.00
+Date | 日 | 2020-09-18
+
+# 复合类型
+## 数组
+- 声明: [T], Array(T)
+- 查询时会以最小储存代价为原则推断类型
+- 元素类型可以不同,但必须兼容
+
+## 元组
+- 声明: (T), tuple(T)
+- 查询时会以最小储存代价为原则推断类型
+- 元素类型可以不同,且无须兼容
+
+## 枚举
+声明 | Key 类型 | Value 类型
+---- | ---- | ----
+Enum8('k1'=1,'k2'=2, ... ,'kN'=N) | String | Int8
+Enum16('k1'=1,'k2'=2, ... ,'kN'=N) | String | Int16
+
+- 枚举的所有后续操作,都会使用 Int 类型的 Value 值
+
+## 嵌套
+- 声明
+ ```sql
+ Nested(
+ column1 T,
+ column2 T
+ )
+ ```
+- 嵌套字段中的每一列期望写入的是数组: Array(T)
+- 同一行数据内,嵌套字段中的每一列的数组长度必须相等
+- 访问嵌套字段中的列时用点(.)连接
+
+## 可空类型
+- 声明: Nullable(T)
+- 只能和基础类型搭配使用,不用用于复合类型和索引字段
+- 慎用,会额外生成 [Column].null.bin 文件保存 null 值,导致双倍文件操作,使查询和写入变慢
+
+## 域名类型
+声明 | 封装类型
+---- | ----
+IPv4 | UInt32
+IPv6 | FixedString(16)
+
+- Domain 类型不是字符串,不支持自动类型转换
+- 调用 IPv4NumToString 或 IPv6NumToString 函数返回 IP 的字符串形式
+
+# 数据库
+## 操作
+- 创建
+ ```sql
+ CREATE DATABASE [IF NOT EXISTS] db_name [ENGINE = engine];
+ ```
+
+- 查看数据库列表
+ ```sql
+ SHOW DATABASES;
+ ```
+
+- 切换
+ ```sql
+ USE db_name;
+ ```
+
+- 查看当前数据库中的数据表列表
+ ```sql
+ SHOW TABLES;
+ ```
+
+## 引擎
+- Ordinary: 默认引擎,无须刻意声明,可以使用任意类型表引擎
+- Dictionary: 字典引擎,自动为所有数据字典创建数据表
+- Memory: 内存引擎,存放临时数据,数据只停留在内存中
+- Lazy: 日志引擎,只能使用 Log 系列的表引擎
+- MySQL: MySQL 引擎,自动拉取远端 MySQL 中的数据,并创建 MySQL 表引擎的数据表
+
+# 数据表
+## 操作
+- 常规建表,默认在 default 数据库中创建
+ ```sql
+ CREATE TABLE [IF NOT EXISTS] [db_name.]table_name {
+ column1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
+ column1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
+ ...
+ columnM Nested(
+ column11 [type],
+ column22 [type],
+ ...
+ ),
+ ...
+ } ENGINE = engine;
+ ```
+
+- 复制其他表的结构
+ ```sql
+ CREATE TABLE [IF NOT EXISTS] [db_name.]table_name
+ AS [db_name1.]table_name1
+ [ENGINE = engine];
+ ```
+
+- 通过 SELECT 字句建表,顺带写入 SELECT 子查询的数据
+ ```sql
+ CREATE TABLE [IF NOT EXISTS] [db_name.]table_name
+ ENGINE = engine
+ AS SELECT ... ;
+ ```
+
+- 删表
+ ```sql
+ DROP TABLE [IF EXISTS] [db_name.]table_name;
+ ```
+
+- 增加表字段,默认值补全
+ ```sql
+ ALTER TABLE [db_name.]table_name ADD COLUMN [IF NOT EXISTS]
+ col_name [type] [default_expr] [AFTER col_name_after];
+ ```
+
+- 修改表字段
+ ```sql
+ ALTER TABLE [db_name.]table_name MODIFY COLUMN [IF EXISTS]
+ col_name [type] [default_expr];
+ ```
+
+- 修改备注
+ ```sql
+ ALTER TABLE [db_name.]table_name COMMENT COLUMN [IF EXISTS]
+ name 'some comment';
+ ```
+
+- 删除字段
+ ```sql
+ ALTER TABLE [db_name.]table_name DROP COLUMN [IF EXISTS] name;
+ ```
+
+- 移动数据表,只能在单节点内移动
+ ```sql
+ RENAME TABLE [db_name.]tb_name TO [db_name1.]tb_name1,
+ [db_name2.]tb_name2 TO [db_name3.]tb_name3,
+ ... ;
+ ```
+
+- 清空数据表
+ ```sql
+ TRUNCATE TABLE [IF EXISTS] [db_name.]tb_name;
+ ```
+
+## 表字段默认值
+- 声明: DEFAULT、MATERIALIZED、ALIAS
+- 如果表字段没有明确类型定义,则可根据默认值进行类型推断
+- 写入数据时,只有 DEFAULT 类型字段可以 INSERT
+- 查询数据时,只有 DEFUALT 类型字段可以 SELECT * 返回
+- DEFAULT 和 MATERIALIZED 类型字段可以持久化
+
+# 临时表
+## 操作
+- 创建
+ ```sql
+ CREATE TEMPORARY TABLE [IF NOT EXISTS] table_name {
+ column1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
+ column1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
+ ...
+ columnM Nested(
+ column11 [type],
+ column22 [type],
+ ...
+ ),
+ ...
+ };
+ ```
+
+## 其他
+- 临时表只支持 Memory 表引擎,与会话绑定
+- 临时表不属于任何数据库,创建时无数据库参数和表引擎参数
+- 临时表优先级大于普通表
+
+# 分区表
+## 操作
+- 创建
+ ```sql
+ CREATE TABLE partition_v1 (
+ ID String,
+ URL String,
+ EventTime Date
+ ) ENGINE = MergeTree()
+ PARTITION BY toYYYYMMM(EventTime)
+ GROUP BY ID;
+ ```
+
+- 查看分区状态
+ ```sql
+ SELECT table,partition,path from system.parts WHERE table = 'partition_v1'
+ ```
+
+- 删除分区
+ ```sql
+ ALTER TABLE tb_name DROP PARTITION part_expr;
+ ```
+
+- 复制分区,前提是两表的结构和分区键相同
+ ```sql
+ ALTER TABLE table_name1 REPLACE PARTITION part_expr FROM table_name;
+ ```
+
+- 重置分区数据
+ ```sql
+ ALTER TABLE table_name CLEAR COLUMN col_name IN PARTITION part_expr;
+ ```
+
+- 卸载分区
+ ```sql
+ ALTER TABLE table_name DETACH PARTITION part_expr;
+ ```
+
+- 装载分区
+ ```sql
+ ALTER TABLE table_name ATTACH PARTITION part_expr;
+ ```
+
+## 其他
+- 分区支持删除、替换和重置,只有 MergeTree 系列表引擎支持
+
+# 视图
+## 操作
+- 创建普通视图
+ ```sql
+ CREATE VIEW [IF NOT EXISTS] [db_name.]view_name AS SELECT ... ;
+ ```
+
+- 创建物化视图
+ ```sql
+ CREATE [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]table_name
+ [TO [db.]name] [ENGINE = engine] [POPULATE] AS SELECT ... ;
+ # 如果使用了 POPULATE 修饰符,那么在创建视图时会一并导入 SELECT 结果集
+ ```
+
+- 查看物化视图列表
+ ```sql
+ SHOW TABLES
+ # 输出前缀是 .inner.
+ ```
+
+- 删除视图
+ ```sql
+ DROP TABLE view_name
+ ```
+
+## 其他
+- 普通视图只是查询代理
+- 物化视图有独立存储,不支持同步删除
+
+# 分布式 DDL
+- 使用 ON CLUSTER cluster_name 声明语句
+ ```sql
+ CREATE TABLE table_name ON CLUSTER cluster_name(
+ col1 [type],
+ col2 [type],
+ ...
+ ) ENGINE = engine ... ;
+ ```
+
+# 写入数据
+- INSERT 语句三种语法
+ ```sql
+ INSERT INTO [db_name.]table_name [(c1,c2,c3 ...)]
+ VALUES (v1,v2,v3 ...), (v4,v5,v6 ...) ... ;
+
+ INSERT INTO [db_name.]table_name [(c1,c2,c3 ...)]
+ FORMAT format_name data_set;
+
+ INSERT INTO [db_name.]table_name [(c1,c2,c3 ...)] SELECT ... ;
+ ```
+
+# 修改和删除数据
+## 操作
+- 删除
+ ```sql
+ ALTER TABLE [db_name.]table_name DELETE WHERE filter_expr;
+ ```
+
+- 修改
+ ```sql
+ ALTER TABLE [db_name.]table_name UPDATE col1 = expr1 [, ...]
+ WHERE filter_expr;
+ ```
+
+## 其他
+- mutation 操作很重,后台执行,语句提交后立即返回,不支持事务,不能回滚
+- 通过 system.mutations 系统表查询进度
+
diff --git a/content/post/ch-file.md b/content/post/ch-file.md
new file mode 100644
index 0000000..e80e3f0
--- /dev/null
+++ b/content/post/ch-file.md
@@ -0,0 +1,30 @@
+---
+title: "ClickHouse 表引擎之 File"
+date: 2020-10-08T12:00:00+08:00
+lastmod: 2020-10-08T12:00:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# File 表引擎简介
+- 直接读取本地文件
+- 修改文件 = 数据更新
+- 导出数据到本地文件
+- 数据格式转换
+
+# 创建 FILE 引擎表
+- 声明
+ ```sql
+ ENGINE = File('format')
+ ```
+
+- format 是文件中的数据格式,如 TSV、CSV 和 JSONEachRow 等
+- 数据文件保存在 config.xml 中指定的 path 下,目录名是表名,数据文件名是 data.{format}
+- 可以通过 CREATE 语句建表,也可以直接在 shell 下创建目录文件,再通过 ATTACH 语句挂载
+ ```sql
+ ATTACH TABLE file_table(
+ name String,
+ value UInt32
+ ) ENGINE = file(CSV)
+ ```
+
diff --git a/content/post/ch-install.md b/content/post/ch-install.md
new file mode 100644
index 0000000..710ab64
--- /dev/null
+++ b/content/post/ch-install.md
@@ -0,0 +1,250 @@
+---
+title: "CentOS7 安装 ClickHouse 集群"
+date: 2020-09-23T10:18:00+08:00
+lastmod: 2020-10-10T01:40:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# 环境
+## Zookeeper 服务器
+eth0 IP | eth1 IP | 操作系统 | ZK 版本 | myid
+---- | ---- | ---- | ---- | ----
+10.0.4.101 | 10.1.4.101 | CentOS7.8 | 3.4.14 | 101
+10.0.4.102 | 10.1.4.102 | CentOS7.8 | 3.4.14 | 102
+10.0.4.103 | 10.1.4.103 | CentOS7.8 | 3.4.14 | 103
+
+- eth0 网卡用于向客户端提供服务,eth1 网卡用于 Zookeeper 集群内部通信
+- 配置时间同步,关闭 selinux 和 firewalld
+
+## ClickHouse 服务器
+eth0 IP | eth1 IP | 操作系统 | CH 版本 | shard 值 | replica 值
+---- | ---- | ---- | ---- | ---- | ----
+10.0.4.181 | 10.1.4.181 | CentOS7.8 | 20.3 LTS | 1 | 10.1.4.181
+10.0.4.182 | 10.1.4.182 | CentOS7.8 | 20.3 LTS | 1 | 10.1.4.182
+10.0.4.183 | 10.1.4.183 | CentOS7.8 | 20.3 LTS | 2 | 10.1.4.183
+10.0.4.184 | 10.1.4.184 | CentOS7.8 | 20.3 LTS | 2 | 10.1.4.184
+10.0.4.185 | 10.1.4.185 | CentOS7.8 | 20.3 LTS | 3 | 10.1.4.185
+10.0.4.186 | 10.1.4.186 | CentOS7.8 | 20.3 LTS | 3 | 10.1.4.186
+
+- eth0 网卡用于向客户端提供服务,eth1 网卡用于 ClickHouse 集群内部通信
+- 配置时间同步,关闭 selinux 和 firewalld
+
+# 安装 Zookeeper 集群
+- ClickHouse 集群依赖 zookeeper 管理集群配置
+- 安装过程参考: [CentOS7 安装 zookeeper 集群](/post/zk-install/)
+- 启动 zookeeper 集群,zookeeper 正常运行后,才能进行后续步骤
+
+# 安装 ClickHouse 集群
+## 配置 ClickHouse yum 源
+- 在每台 ClickHouse 服务器上执行如下操作
+- 生成 clickhouse.repo 文件
+ ```bash
+ echo '[clickhouse-lts]
+ name=ClickHouse - LTS Repository
+ baseurl=https://mirrors.tuna.tsinghua.edu.cn/clickhouse/rpm/lts/$basearch/
+ gpgkey=https://mirrors.tuna.tsinghua.edu.cn/clickhouse/CLICKHOUSE-KEY.GPG
+ gpgcheck=1
+ enabled=1
+ EOF
+ ' > /etc/yum.repos.d/clickhouse.repo
+ ```
+
+- 重建 yum 缓存
+ ```bash
+ yum clean all
+ yum makecache fast
+ ```
+
+## 安装 ClickHouse
+- 在每台 ClickHouse 服务器上执行如下操作
+- 安装 clickhouse-server 和 clickhouse-client
+ ```bash
+ yum install clickhouse-server clickhouse-client
+ ```
+
+## 修改 ClickHouse 配置
+- 在每台 ClickHouse 服务器上执行如下操作
+- 我没用 /etc/metrika.xml 和 config.d 子目录,直接修改的 config.xml,先备份
+ ```bash
+ cd /etc/clickhouse-server/
+ cp config.xml config.xml.origin
+ ```
+
+- 编辑 /etc/clickhouse-server/config.xml,修改部分如下
+ ```xml
+
+ 10.1.4.181
+ 10.1.4.182
+ 10.1.4.183
+ 10.1.4.184
+ 10.1.4.185
+ 10.1.4.186
+
+
+ 0.0.0.0
+
+
+ /var/lib/clickhouse/
+
+
+
+
+
+ 1073741824
+
+
+ /clickhouse/disk1/
+
+
+ /clickhouse/disk2/
+
+
+
+
+
+
+ disk1
+ disk2
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+ true
+
+ 10.1.4.181
+ 9000
+
+
+ 10.1.4.182
+ 9000
+
+
+
+ true
+
+ 10.1.4.183
+ 9000
+
+
+ 10.1.4.184
+ 9000
+
+
+
+ true
+
+ 10.1.4.185
+ 9000
+
+
+ 10.1.4.186
+ 9000
+
+
+
+
+
+
+
+
+ 10.0.4.101
+ 2181
+
+
+ 10.0.4.102
+ 2181
+
+
+ 10.0.4.103
+ 2181
+
+
+
+
+
+ 1
+ 10.1.4.181
+
+
+ 1
+ 10.1.4.182
+
+
+ 2
+ 10.1.4.183
+
+
+ 2
+ 10.1.4.184
+
+
+ 3
+ 10.1.4.185
+
+
+ 3
+ 10.1.4.186
+
+ ```
+
+## 启动 ClickHouse
+- 在每台 ClickHouse 服务器上执行如下操作
+- 启动 clickhouse-server 服务
+ ```bash
+ systemctl start clickhouse-server
+ ```
+
+# 查看集群状态
+- 在任一 ClickHouse 服务器上执行如下操作
+- 查询 system.cluster 表
+ ```sql
+ SELECT * FROM system.clusters;
+ ```
+
+# 简单使用
+- 在任意节点上登陆 clickhouse
+ ```bash
+ clickhouse-client -h 127.0.0.1
+ ```
+
+- 创建数据库
+ ```sql
+ CREATE DATABASE db1 ON CLUSTER cluser_3s2r;
+ USE db1;
+ ```
+
+- 创建数据表
+ ```sql
+ CREATE TABLE db1.t1_local
+ ON CLUSTER cluster_3s2r (
+ col1 UInt32,
+ col2 String
+ ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/t1_local', '{replica}')
+ ORDER BY (col1)
+ SETTINGS STORAGE_POLICY='policy_jbod';
+ ```
+
+- 创建数据表对应的分布式代理表
+ ```sql
+ CREATE TABLE db1.t1
+ ON CLUSTER cluster_3s2r
+ AS db1.t1_local
+ ENGINE = Distributed(cluster_3s2r, db1, t1_local, rand());
+ ```
+
+- 通过分布式代理表写入和查询数据
+ ```sql
+ INSERT INTO db1.t1 values(1,'aa');
+ SELECT * FROM db1.t1;
+ ```
+
diff --git a/content/post/ch-join.md b/content/post/ch-join.md
new file mode 100644
index 0000000..105274a
--- /dev/null
+++ b/content/post/ch-join.md
@@ -0,0 +1,27 @@
+---
+title: "ClickHouse 表引擎之 Join"
+date: 2020-10-08T17:40:00+08:00
+lastmod: 2020-10-08T17:40:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# Join 表引擎简介
+- 数据先写内存,再同步到磁盘,服务重启后全量加载到内存
+- 与 Set 表引擎共用大部分处理逻辑
+- 简单封装了一层 JOIN 查询,主要用做 JOIN 查询
+
+# 创建 Join 引擎表
+- 声明
+ ```sql
+ ENGINE = Join(join_strictness, join_type, key1[, key2, ...])
+ ```
+
+- join_strictness: 连接精度,支持 ALL、ANY 和 ASOF
+- join_type: 连接类型,支持 INNRE、OUTER、CROSS
+- key1, key2 ...: 连接键,关联字段
+- 如果连接精度是 ANY,写入数据时会忽略连接键相同的数据
+
+# 参考
+- [ClickHouse 表引擎之 Join](/post/ch-search/#JOIN)
+
diff --git a/content/post/ch-kafka.md b/content/post/ch-kafka.md
new file mode 100644
index 0000000..c0e4e45
--- /dev/null
+++ b/content/post/ch-kafka.md
@@ -0,0 +1,73 @@
+---
+title: "ClickHouse 表引擎之 Kafka"
+date: 2020-10-08T10:42:00+08:00
+lastmod: 2020-10-08T10:42:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# Kafka 表引擎简介
+- 对接 Kafka 系统,订阅 Kafka 主题,接收消息
+
+# 创建 Kafka 引擎表
+- 声明
+ ```sql
+ ENGINE = Kafka()
+ SETTINGS
+ kafka_broker_list = 'host:port, ...',
+ kafka_topic_list = 'topic1, topic2, ...',
+ kafka_group_name = 'consumer_group_name',
+ kafka_format = 'data_format',
+ [kafka_row_delimiter = 'delimiter_symbol',]
+ [kafka_schema = '',]
+ [kafka_num_consumers = N,]
+ [kafka_skip_broken_messages = N,]
+ [kafka_commit_every_batch =N];
+ ```
+
+- kafka_broker_list: kafka 节点地址列表,用逗号分隔
+- kafka_topic_list: 订阅的主题列表,用逗号分隔
+- kafka_group_name: 消费组名称,引擎会依据此名称创建消费组
+- kafka_format: 消息格式,如 TSV、JSONEachRow、CSV 等
+- kafka_row_delimiter: 一行数据的结束符,默认 '\0'
+- kafka_schema: kafka schema 参数
+- kafka_num_consumers: 消费者数量,默认 1
+- kafka_skip_broken_messages: 允许跳过的错误消息数量,默认0
+- kafka_commit_every_batch: kafka commit 频率,默认 0,即整个 Block 完全写入后才 commit
+
+# Kafka 表引擎其他参数
+- stream_poll_timeout_ms: 默认每 500ms 消费一次数据,写入缓存
+- 刷新缓存触发条件:
+ - 一个数据块(kafka_max_block_size,默认 65536)写入完成
+ - 等待 7500 毫秒(stream_flush_interval_ms)
+
+- config.xml 中的 librdkafka 配置,参考 [https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md](https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md)
+ ```xml
+
+ smallest
+
+ ```
+
+# Kafka 引擎表一般用法
+- 创建 Kafka 引擎表,充当数据管道
+- 创建 MergeTree 引擎表,用于查询
+- 创建物化视图,同步 kafka 数据到 MergeTree 引擎表
+ ```sql
+ CREATE MATERIALIZED VIEW kafka_view TO mergetree_table
+ AS SELECT col1, col2, ... FROM kafka_table;
+ ```
+
+- 要停止数据同步,可以删除视图,也可以卸载视图
+ ```sql
+ -- 删除
+ DROP TABLE kafka_view;
+ -- 卸载
+ DETACH TABLE kafka_view;
+ ```
+
+- 恢复数据同步,装载视图
+ ```sql
+ ATTACH MATERIALIZED VIEW kafka_view TO mergetree_table
+ AS SELECT col1, col2, ... FROM kafka_table;
+ ```
+
diff --git a/content/post/ch-liveview.md b/content/post/ch-liveview.md
new file mode 100644
index 0000000..5e92c97
--- /dev/null
+++ b/content/post/ch-liveview.md
@@ -0,0 +1,26 @@
+---
+title: "ClickHouse 视图之 Live View"
+date: 2020-10-08T19:05:00+08:00
+lastmod: 2020-10-08T19:05:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# Live View 视图简介
+- 类似时间监听器
+- 需设置 allow_experimental_live_view 为 1,检查
+ ```sql
+ SELECT name, value FROM system.settings WHERE name LIKE '%live_view%';
+ ```
+
+# 创建 Live View 视图
+- 创建
+ ```sql
+ CREATE LIVE VIEW lv_name AS SELECT count(*) FROM table_name;
+ ```
+
+- 监听
+ ```sql
+ WATCH lv_name
+ ```
+
diff --git a/content/post/ch-log.md b/content/post/ch-log.md
new file mode 100644
index 0000000..008bb1f
--- /dev/null
+++ b/content/post/ch-log.md
@@ -0,0 +1,36 @@
+---
+title: "ClickHouse 表引擎之日志"
+date: 2020-10-08T18:23:00+08:00
+lastmod: 2020-10-08T18:23:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# 日志表引擎简介
+- 数据量 100 万行一下,一次写入多次查询
+- 不支持索引、分区、并发读写
+
+# TinyLog 表引擎
+- 数据文件按列存储
+- 无标记文件,不支持并行读取
+- 声明
+ ```sql
+ ENGINE = TinyLog()
+ ```
+
+# StripeLog 表引擎
+- 只用数据写入一个文件
+- 有数据标记文件,可并行读取
+- 声明
+ ```sql
+ ENGINE = StripeLog()
+ ```
+
+# Log 表引擎
+- 数据文件按列存储
+- 有数据标记文件,可并行读取
+- 声明
+ ```sql
+ ENGINE = Log()
+ ```
+
diff --git a/content/post/ch-memory.md b/content/post/ch-memory.md
new file mode 100644
index 0000000..9d282f4
--- /dev/null
+++ b/content/post/ch-memory.md
@@ -0,0 +1,19 @@
+---
+title: "ClickHouse 表引擎之 Memory"
+date: 2020-10-08T13:00:00+08:00
+lastmod: 2020-10-08T13:00:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# Memory 表引擎简介
+- 数据只存于内存中,无压缩,无格式转换
+- 支持并行查询
+- 一般用于 clickhouse 内部,作为集群间分发数据的载体
+
+# 创建 Memory 引擎表
+- 声明
+ ```sql
+ ENGINE = Memory()
+ ```
+
diff --git a/content/post/ch-merge.md b/content/post/ch-merge.md
new file mode 100644
index 0000000..3302081
--- /dev/null
+++ b/content/post/ch-merge.md
@@ -0,0 +1,24 @@
+---
+title: "ClickHouse 表引擎之 Merge"
+date: 2020-10-08T18:35:00+08:00
+lastmod: 2020-10-08T18:35:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# Merge 表引擎简介
+- 本身不存储数据,只整合其他数据表
+- 不支持数据写入
+- 合并异步查询的结果集
+- 各异步查询的数据表需要在同一个数据库下,且表结构相同,表引擎和分区定义可不同
+
+# 创建 Merge 表引擎
+- 声明
+ ```sql
+ ENGINE = Merge(database, table_name)
+ ```
+
+- database: 数据库名
+- table_name: 数据表名,支持正则表达式
+- Merge 引擎表可以使用虚拟字段 "\_table" 来查询和过滤数据表
+
diff --git a/content/post/ch-mergetree.md b/content/post/ch-mergetree.md
new file mode 100644
index 0000000..b0c2edb
--- /dev/null
+++ b/content/post/ch-mergetree.md
@@ -0,0 +1,388 @@
+---
+title: "ClickHouse 表引擎之 MergeTree(合并树)"
+date: 2020-10-06T19:55:00+08:00
+lastmod: 2020-10-07T22:54:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# 简介
+- 支持主键索引、数据分区、数据副本、数据采样、ALTER 操作
+- 扩展表引擎丰富,生产环境中大多使用该表引擎
+- 数据以片段形式写入磁盘,后台定期合并片段到各分区相应片段
+
+# 数据表
+- 建表语句
+ ```sql
+ CREATE TABLE [IF NOT EXISTS] [db_name.]table_name(
+ ...
+ ) ENGINE = MergeTree()
+ [PARTITION BY expr]
+ [ORDER BY expr]
+ [PRIMARY KEY expr]
+ [SAMPLE BY expr]
+ [SETTINGS name=value, ...];
+ ```
+
+- PARTITION BY: 分区键,选填,支持单字段、多字段和表达式,默认生成一个 all 分区
+- ORDER BY: 排序键,必填,支持单列和元组(包含多列)
+- PRIMARY KEY: 主键,选填,默认与排序键相同,允许重复数据
+- SAMPLE BY: 抽样,选填,该配置需在主键中同时声明
+- SETTINGS: 其他参数,选填,示例如下
+ - index_granularity: 索引粒度,默认 8192,通常不需要修改
+ - index_granularity_bytes: 每批次写入的数据大小,用于自适应索引间隔,默认 10MB,0 表示无视数据大小
+ - enable_mixed_granularity_parts: 自适应索引间隔,默认开启
+ - merge_with_ttl_timeout: TTL 合并间隔时间,默认 86400(1天)
+ - storage_policy: 数据在硬盘上的存储策略
+
+# 数据文件
+- 目录和文件
+ ```
+ table_name # 表名目录
+ |___ partition_1 # 分区目录
+ |___ checksums.txt # 校验文件,二进制,记录该分区目录中其他文件的大小和哈希值
+ |___ columns.txt # 列信息文件,明文,记录该分区下的列字段信息
+ |___ count.txt # 计数文件,明文,记录该分区总行数
+ |___ primary.txt # 一级索引文件,二进制,存放稀疏索引
+ |___ {column_name}.bin # 列数据文件,默认 LZ4 压缩
+ |___ {column_name}.mrk # 列标记文件,二进制,记录对应数据文件(.bin)中的数据偏移量
+ |___ {column_name}.mrk2 # 如果表使用了自适应索引间隔,那么对应的列字段标记文件以 .mrk2 命令
+ |___ partition.dat # 保存当前分区表达式的值,二进制
+ |___ minmax_{column_name}.idx # 保存当前分区字段对应原始数据的最小和最大值,二进制
+ |___ skp_idx_{column_name}.idx # 二级索引(跳数索引)文件
+ |___ skp_idx_{column_name}.mrk # 二级索引(跳数索引)列的标记文件
+ ```
+
+# 数据分区
+## 分区 ID
+- 单字段分区 ID 生成规则
+
+类型 | 样例数据 | 分区表达式 | 分区 ID
+---- | ---- | ---- | ----
+无分区键 | - | 无 | all
+整型 | 18,19,20 | PARTITION BY Age | 分区1: 18,分区2: 19,分区3: 20
+整型 | 'A0', 'A1', 'A2' | PARTITION BY length(Code) | 分区1: 2
+日期 | 2020-10-05, 2020-10-06 | PARTITION BY EventTime | 分区1: 20201005,分区2: 20201006
+日期 | 2020-09-25, 2020-10-06 | PARTITION BY toYYYYMM(EventTime) | 分区1: 202009,分区2: 202010
+其他 | 'www.colben.cn' | PARTITION BY URL | 分区1: {128 位 Hash 算法}
+
+- 多字段(元组)分区时, 先按单字段生成对应 ID,再用 "-" 拼接
+
+## 分区目录
+- 分区目录命名: PartitionID_MinBlockNum_MaxBlockNum_Level,例如 202010_1_1_0
+ - PartitionID: 分区 ID
+ - MinBlockNum: 最小数据块编号,**表内全局累加**,从 1 开始
+ - MaxBlockNum: 最大数据块编号,**表内全局累加**,从 1 开始
+ - Level: 分区合并次数,从 0 开始
+
+- 不同批次写入的数据,即使分区相同,也会存储在不同目录中
+- 后台在默认 10-15 分钟后自动合并分区相同的多个目录,也可以手动执行 optimize 语句
+- 合并成功后,旧分区目录被置为非激活状态,在默认 8 分钟后被后台删除
+- 合并后新目录的命名规则:
+ - MinBlockNum: 所有合并目录中的最小 MinBlockNum
+ - MaxBlockNum: 所有合并目录中的最大 MaxBlockNum
+ - Level: 所有合并目录中的最大 Level 值并加 1
+
+# 数据索引
+- 常驻内存
+- 一级索引是稀疏索引,间隔 index_granularity (默认 8192) 行数据生成一条索引记录
+- 二级索引又称跳数索引,有数据的聚合信息构建而成,在 CREATE 语句中定义如下:
+ ```sql
+ INDEX index_name expr TYPE index_type(...) GRANULARITY granularity
+ -- GRANULARITY 指定一行跳数索引聚合的数据段(index_granularity 区间)的个数
+ ```
+
+- 跳数索引类型
+ - minmax: 记录一段数据内的最小值和最大值
+ ```sql
+ INDEX index_name ID TYPE minmax GRANULARITY 5
+ ```
+
+ - set: 记录字段或表达式的无重复取值
+ ```sql
+ INDEX index_name (length(ID)) TYPE set(100) GRANULARITY 5
+ -- 每个数据段(index_granularity 区间)内最多记录 100 条 set 索引记录
+ ```
+
+ - ngrambf_v1: 只支持 String 和 FixedString,只能提升 in、notIn、like、equals 和 notEquals 性能
+ ```sql
+ INDEX index_name (ID, Code) TYPE ngrambf_v1(3, 256, 2, 0) GRANULARITY 5;
+ -- 3: token 长度,把数据切割成长度为 3 的短语
+ -- 256: 布隆过滤器大小
+ -- 2: 哈希函数个数
+ -- 0: 哈希函数随机种子
+ ```
+
+ - tokenbf_v1: ngrambf_v1 变种,按照非 字母和数字 自动分割
+ ```sql
+ INDEX index_name ID TYPE tokenbf_v1(256, 2, 0) GRANULARITY 5;
+ -- 注意传参时不需要指定 token 长度
+ ```
+
+# 数据存储
+- 按列独立存储
+- 默认 LZ4 压缩
+- 按照 order by 排序
+- 以数据压缩块形式写入 .bin 文件,规则如下:
+ - 单批次数据 < 64KB,继续获取下一批数据
+ - 64KB <= 单批次数据 <= 1MB,直接生成压缩数据块
+ - 单批次数据 > 1MB,按照 1MB 大小截断并生成数据块,剩余数据继续按前面规则执行
+
+# 数据标记
+- 使用 LRU 策略缓存
+- 每一行标记数据记录的是一个数据片段在 .bin 文件中的读取位置
+
+# 数据写入
+- 生成分区目录,合并分区相同的目录
+- 按照 index_granularity 索引粒度,生成一级索引、二级索引、数据标记文件和数据压缩文件
+
+# 数据查询
+- 借助分区、索引、数据标记来缩小扫描范围
+- 如果未指定查询条件,或条件未匹配到索引,MergeTree 仍可借助数据标记多线程读取压缩数据块
+
+# 数据 TTL
+## TTL 机制
+- TTL 信息保存在分区目录中的 ttl.txt 中
+- 支持的时间单位: SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR
+- 触发 TTL 删除过期数据
+ - 后台分区合并
+ - merge_with_ttl_timeout 合并频率,默认 86400 秒
+ - 手动执行 OPTIMIZE 语句
+
+- 合并分区时,TTL 全部到期的数据分区不会参与合并
+- 控制全局 TTL 合并任务
+ ```sql
+ -- 启动
+ SYSTEM START TTL MERGES;
+ -- 停止
+ SYSTEM STOP TTL MERGES;
+ ```
+
+## 列级别 TTL
+- 到达时间时,列数据被还原为对应数据类型的默认值
+- 主键字段不能被声明 TTL
+- 声明列级别 TTL
+ ```sql
+ CREATE TABLE table_name(
+ id String,
+ create_time DateTime,
+ code String TTL create_time + INTERVAL 10 SECOND,
+ type UInt8 TTL create_time + INTERVAL 16 SECOND
+ ) ENGINE = MergeTree()
+ PARTITION BY toYYYYMM(create_time)
+ ORDER BY id;
+ ```
+
+- 修改列级别 TTL
+ ```sql
+ ALTER TABLE table_name MODIFY COLUMN code String TTL create_time + INTERVAL 1 DAY;
+ ```
+
+## 表级别 TTL
+- 到达时间时,删除过期的数据行
+- 声明表级别 TTL
+ ```sql
+ CREATE TABLE table_name(
+ id String,
+ create_time DateTime,
+ code String TTL create_time _ INTERVAL 1 MINUTE,
+ type UInt8
+ ) ENGINE = MergeTree
+ PARTITION BY toYYYYMM(create_time)
+ ORDER BY create_time
+ TTL create_time + INTERVAL 1 DAY;
+ ```
+
+- 修改表级别 TTL
+ ```sql
+ ALTER TABLE table_name MODIFY TTL create_time + INTERVAL 3 DAY;
+ ```
+
+# 存储策略
+- 最小移动单元是数据分区
+- 三大策略: 默认、JBOD、HOT/COLD
+
+## 默认策略
+- 无需配置,所有分区自动保存至 config.xml 中的 path 目录下
+
+## JOB 策略
+- 适用于多磁盘无 RAID 场景
+- INSERT 或 MERGE 产生的新分区轮询写入各磁盘,类似 RAID0
+- 磁盘故障时,丢掉相应数据,需要副本机制保障数据可靠性
+
+## HOT/COLD 策略
+- 适用于已挂载不同类型磁盘的场景
+- 把磁盘划分到 HOT 和 COLD 两个区域,HOT 使用 SSD,注重性能,CODE 使用 HDD,注重经济
+- 单个区域内可应用 JBOD 策略
+
+## 配置策略
+- 配置示例
+ ```xml
+
+
+
+
+
+ /ch/ssd0
+
+ 1073741824
+
+
+ /ch/ssd1
+
+
+ /ch/hdd0
+ 2147483648
+
+
+ /ch/hdd1
+
+
+ /ch/hdd2
+
+
+
+
+
+
+
+
+
+ disk_hot_0
+ disk_hot_1
+
+
+ 1073741824
+
+
+
+
+
+
+ disk_hot_0
+ disk_hot_1
+
+
+ disk_cold_0
+ disk_cold_1
+ disk_cold_2
+
+
+
+
+ 0.2
+
+
+
+ ```
+
+- clickhouse 用户需要有权限读写各存储目录
+- 存储配置不支持动态更新
+- 存储磁盘系统表: system.disks
+- 存储策略系统表: system.storage_policies
+- 移动分区到其他 disk
+ ```sql
+ ALTER TABLE table_name MOVE PART 'part_name' TO DISK 'disk_name';
+ ```
+
+- 移动分区到其他 volume
+ ```sql
+ ALTER TABLE table_name MOVE PART 'part_name' TO VOLUME 'volume_name';
+ ```
+
+# ReplacingMergeTree
+- 依据 ORDER BY 字段去重
+- 合并分区时,**以分区为单位**删除重复数据
+- 声明
+ ```sql
+ ENGINE = ReplacingMergeTree(version_column)
+ ```
+
+- version_column 选填,指定一个 UInt\*、Date 或 DateTime 字段作为版本号
+- 未指定 version_column 时,保留同一组重复数据中的最后一行
+- 指定 version_column 时,保留同一组重复数据中该字段取值最大的一行
+
+# SummingMergeTree
+- 场景: 用户只需要汇总结果,不关心明细
+- 依据 ORDER BY 字段聚合
+- 合并分区时,触发条件聚合,**以分区为单位**把同一分组下的多行数据汇总成一行
+- 声明:
+ ```sql
+ ENGINE = SummingMergeTree((col1,col2, ...))
+ ```
+
+- col1、col2 选填,不可指定主键,指定被 SUM 汇总的数值类型字段
+- 未指定任何汇总字段时,默认汇总所有非主键的数值类型字段
+- 非汇总字段保留同组内的第一行数据
+- 汇总嵌套字段时,字段名需以 Map 为后缀,默认嵌套字段中第一列作为聚合 Key,其他以 \*Key、\*Id、\*Type 未后缀名的列会和第一列组成复合 Key
+
+# AggregatingMergeTree
+- 预先计算聚合数据,二进制格式存入表中,空间换时间,可看成是 SummingMergeTree 的*升级版*
+- 依据 ORDER BY 字段聚合
+- 使用 AggregationFunction 字段类型定义聚合函数和字段
+- 分区合并时,触发**以分区为单位**的合并计算
+- 非汇总字段保留同组内的第一行数据
+- 写数据时调用 \*State 函数,查询时调用 \*Merge 函数
+- 一般用作物化视图的表引擎,与普通 MergeTree 搭配使用,示例如下
+ - 创建明细数据表,俗称底表
+ ```sql
+ CREATE TABLE table_name(
+ id String,
+ city String,
+ code String,
+ value Uint32
+ ) ENGINE = MergeTree()
+ PARTITION BY city
+ ORDER BY (id, city);
+ ```
+
+ - 创建物化视图
+ ```sql
+ CREATE MATERIALIZED VIEW view_name
+ ENGINE = AggregatingMergeTree()
+ PARTITION BY city
+ ORDER BY (id, city)
+ AS SELECT
+ id,
+ city,
+ uniqState(code) AS code,
+ sumState(value) AS value
+ FROM table_name
+ GROUP BY id, city;
+ ```
+
+ - 使用常规 SQL 面向底表增加数据
+ - 面向物化视图查询
+ ```sql
+ SELECT id, sumMerge(value), uniqMerge(code) FROM agg_view GROUP BY id,city;
+ ```
+
+# CollapsingMergeTree
+- 以增代删
+- 声明
+ ```sql
+ ENGINE = CollapsingMergeTree(sign)
+ ```
+
+- 定义 sign 标记字段,Int8 类型,1 代表有效,-1 代表无效
+- 依据 ORDER BY 字段作为数据唯一性依据
+- 规则
+ - 如果 sign=1 比 sign=-1 多一行,则保留最后一行 sign=1 的数据
+ - 如果 sign=-1 比 sign=1 多一行,则保留第一行 sign=-1 的数据
+ - 如果 sign=-1 和 sign=1 一样多,且最后一行是 sign=1,则保留第一行 sign=-1 和最后一行 sign=1 的数据
+ - 如果 sign=-1 和 sign=1 一样多,且最后一行是 sign=-1,则不保留任何数据
+ - 其他情况打印告警日志
+
+- 合并分区时,触发**以分区为单位**的数据折叠
+- 严格要求数据写入顺序,只有先写入 sign=1,再写入 sign=-1,才能正常折叠
+
+# VersionedCollapsingMergeTree
+- 与 CollapsingMergeTree 类似,但对数据写入顺序没有要求
+- 声明
+ ```sql
+ ENGINE = VersionedCollapsingMergeTree(sign, ver)
+ ```
+
+- ver 是 UInt8 类型的版本号字段
+- 每个分区内的数据都以 ORDER BY column_name, ver DESC 排序
+
diff --git a/content/post/ch-mysql.md b/content/post/ch-mysql.md
new file mode 100644
index 0000000..7d6193d
--- /dev/null
+++ b/content/post/ch-mysql.md
@@ -0,0 +1,41 @@
+---
+title: "ClickHouse 表引擎之 MySQL"
+date: 2020-10-08T10:15:00+08:00
+lastmod: 2020-10-08T10:15:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# MySQL 表引擎简介
+- 可以与 MySQL 数据库中的表建立映射
+- 只支持 SELECT 和 INSERT,不支持 UPDATE 和 DELETE
+
+# 创建 MySQL 引擎表
+- 声明
+ ```sql
+ ENGINE = MySQL(
+ 'host:port',
+ 'database',
+ 'table',
+ 'user',
+ 'password'
+ [,
+ replace_query,
+ 'on_duplicate_clause'
+ ]
+ )
+ ```
+
+- host:port: mysql 的地址和端口
+- database: mysql 数据库名
+- table: mysql 表名
+- user: mysql 用户名
+- password: mysql 密码
+- replace_query: 对应 mysql 的 replace into 语法,默认 0,不启用
+- on_duplicate_clause: 对应 mysql 的 on duplicate key 语法,默认空,如果要使用,需设置 replace_query 为 0
+
+# MySQL 引擎表一般用法
+- 在 mysql 中建表
+- 在 clickhouse 中创建对应的 MySQL 引擎表
+- 在 clickhouse 中创建 MergeTree 引擎的物化视图,从 MySQL 引擎表中读取数据
+
diff --git a/content/post/ch-null.md b/content/post/ch-null.md
new file mode 100644
index 0000000..b633f2d
--- /dev/null
+++ b/content/post/ch-null.md
@@ -0,0 +1,18 @@
+---
+title: "ClickHouse 表引擎之 Null"
+date: 2020-10-08T19:11:00+08:00
+lastmod: 2020-10-08T19:11:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# Null 表引擎简介
+- 类似 /dev/null,忽略写入的任何数据,查询时返回空表
+- 如果物化视图不需要保留源表数据,则可设置源表为 Null 引擎
+
+# 创建 Null 表引擎
+- 声明
+ ```sql
+ ENGINE = Null
+ ```
+
diff --git a/content/post/ch-ops.md b/content/post/ch-ops.md
new file mode 100644
index 0000000..902ed60
--- /dev/null
+++ b/content/post/ch-ops.md
@@ -0,0 +1,26 @@
+---
+title: "ClickHouse 维护"
+date: 2020-10-10T01:30:00+08:00
+lastmod: 2020-10-10T01:30:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# 指标
+- system.metrics: 正在执行的概要信息,如正在运行的查询和操作数量
+- system.events: 累积概要信息,如总的查询次数或 SELECT 次数
+- system.asynchronous_metrics: 后台异步运行的概要信息,如分配内存、队列中的人物数量
+
+# 日志
+- system.query: 执行用户、查询语句、执行时间、返回数据量等信息
+ ```xml
+
+ ...
+
+ ```
+
+- query_thread_log: 查询语句、执行线程、执行时间、内存使用量
+- part_log: MergeTree 系列表引擎的操作类型、表名称、分区信息、执行时间
+- text_log: 记录 clickhouse 运行时的终端输出日志
+- metric_log: 汇聚 system.metrics 和 system.events
+
diff --git a/content/post/ch-replicated.md b/content/post/ch-replicated.md
new file mode 100644
index 0000000..118850d
--- /dev/null
+++ b/content/post/ch-replicated.md
@@ -0,0 +1,181 @@
+---
+title: "ClickHouse 集群"
+date: 2020-10-09T22:14:00+08:00
+lastmod: 2020-10-09T22:14:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# 简介
+- 一个集群包含多个逻辑分片,每个逻辑分片包含多个副本节点
+- 向集群内读写数据时,需依赖 Distributed 引擎表做为代理,实现数据的分发、写入、查询和路由
+- ReplicatedMerge 表引擎配合 zookeeper 实现数据的复制
+
+# 集群配置
+- 节点分配
+ ```xml
+
+
+
+ true
+ 1
+
+ 10.1.4.181
+ 9000
+
+
+ 10.1.4.182
+ 9000
+
+
+
+ true
+
+ 10.1.4.183
+ 9000
+
+
+ 10.1.4.184
+ 9000
+
+
+
+ true
+
+ 10.1.4.185
+ 9000
+
+
+ 10.1.4.186
+ 9000
+
+
+
+
+ ```
+
+- 各节点的宏变量
+ - 10.0.4.181
+ ```xml
+
+ 1
+ 10.1.4.181
+
+ ```
+ - 10.0.4.182
+ ```xml
+
+ 1
+ 10.1.4.182
+
+ ```
+ - 10.0.4.183
+ ```xml
+
+ 2
+ 10.1.4.183
+
+ ```
+ - 10.0.4.184
+ ```xml
+
+ 2
+ 10.1.4.184
+
+ ```
+ - 10.0.4.185
+ ```xml
+
+ 3
+ 10.1.4.185
+
+ ```
+ - 10.0.4.186
+ ```xml
+
+ 3
+ 10.1.4.186
+
+ ```
+
+- Zookeeper
+ ```xml
+
+
+ 10.0.4.101
+ 2181
+
+
+ 10.0.4.102
+ 2181
+
+
+ 10.0.4.103
+ 2181
+
+
+ ```
+
+- system.zookeeper: zookeeper 代理表,可通过 sql 查看 zookeeper 信息
+- system.clusters: 集群信息表
+
+# ReplicatedMergeTree 表引擎
+- 引入 zookeeper 实现分布式协同,zookeeper 本身不涉及表数据传输
+- 副本节点是多主架构,可在节点上执行读写操作
+- 数据块: 默认 1048576 行(max_insert_block_size)
+ - 基本基本写入单元
+ - 原子性: 一个块内的数据,要么都写入成功,要么都失败
+ - 唯一性: 记录 hash 信息,相同的数据块会被忽略
+
+
+## 创建 ReplicatedMergeTree 引擎表
+- 声明
+ ```sql
+ CREATE TABLE table_name_local ON CLUSTER cluster_name_2
+ ENGINE = ReplicatedMergeTree(
+ '/clickhouse/tables/{shard}/db_name/table_name_local',
+ '{replica}'
+ )
+ ```
+
+- table_name_local: 本地表名,推荐以 \_local 为后缀
+- cluster_name_2: 在该集群内创建数据库和数据表的分片和副本
+- /clickhouse/tables/ 是约定俗成的固定 zookeeper path 路径
+- {shard}: 分片编号,从各自节点的宏变量中获取
+- db_name: 数据库名
+- {replica}: 节点域名/IP,从各自节点的宏变量中获取
+
+# Distributed 表引擎
+- 又叫分布式表,自身不存储数据,只代理数据分片
+
+## 创建 Distributed 引擎表
+- 声明
+ ```sql
+ CREATE TABLE table_name_all ON CLUSTER cluster_name_1
+ ENGINE = Distributed(cluster_name_2, db, table, [,sharding_key])
+ ```
+
+- table_name_all: 分布式表名,通常以 \_all 为后缀
+- ON CLUSTER: 集群操作
+- cluster_name_1: 在该集群内创建分布式表 table_name_all
+- cluster_name_2: 数据的分片和副本所在集群
+- db: 数据库名
+- table_name_local: 数据表名,即前面创建的 ReplicatedMergeTree 引擎表,通常以 \_local 为后缀
+- sharding_key: 分片键,可以是整型列字段或返回整型的表达式,决定数据分配到哪些节点中
+
+# 分布式查询
+- 分布式表(Distributed)把查询转换为并行的各分片查询
+- 汇总各分片的查询结果
+
+## GlOBAL 优化查询
+- 场景: 涉及到 JOIN 和 IN 时,可能会放大分布式查询
+- GLOBAL 查询过程:
+ - 提出 IN 子句,发起分布式查询
+ - 汇总 IN 子句在各分片的查询结果,存入临时表(内存)
+ - 把这个临时表发送到其他分片节点,**考虑到该表既要内存存储,又要通过网络分发,所以数据量不宜过大**
+ - 在各分片节点执行完整的 SQL 语句,此时 IN 子句直接使用上一步的临时表
+- 使用示例
+ ```sql
+ SELECT ... WHERE ... AND ... GLOBAL IN (...)
+ ```
+
diff --git a/content/post/ch-search.md b/content/post/ch-search.md
new file mode 100644
index 0000000..c9fb1c1
--- /dev/null
+++ b/content/post/ch-search.md
@@ -0,0 +1,192 @@
+---
+title: "ClickHouse 数据查询"
+date: 2020-10-08T19:27:00+08:00
+lastmod: 2020-10-08T22:17:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# 查询注意
+- **避免使用 SELECT * 查询**
+
+# WITH
+- **WITH 子句只能返回一行数据**
+- 定义变量
+ ```sql
+ WITH 10 AS var_name SELECT ...
+ ```
+
+- 调用函数
+ ```sql
+ WITH SUM(column_name) AS with_name SELECT ...
+ ```
+
+- 定义子查询
+ ```sql
+ WITH (
+ SELECT ...
+ ) AS with_name
+ SELECt ...
+ ```
+
+- WITH 子句可在子查询中嵌套使用
+
+# FROM
+- 支持表、表函数和子查询
+- 可用 FINAL 修饰以强制合并,会降低性能,应尽量避免使用
+
+# SAMPLE
+- 返回采样数据,减少查询负载,适用于近似查询
+- 只能用于 MergeTree 系列引擎表,且声明了 SAMPLE BY 抽样表达式
+- 虚拟字段 \_sample_factor 是采样系数
+- 不采样
+ ```sql
+ SELECT ... SAMPLE 0
+ -- 或
+ SELECT ... SAMPLE 1
+ ```
+
+- SAMPLE factor,factor 是采样因子,取值 0~1
+ ```sql
+ SELECT ... SAMPLE 0.1
+ -- 或者
+ SELECT ... SAMPLE 1/10
+ ```
+
+- SAMPLE rows,采样**近似**行数,必须大于 1
+ ```sql
+ SELECT ... SAMPLE 10000
+ ```
+
+- SAMPLE factor OFFSET n,偏移 n\*100% 的数据量后才开始按 factor 因子采样,取值都在 0~1
+ ```sql
+ SELECT ... SAMPLE 0.4 OFFSET 0.5
+ ```
+
+# ARRAY JOIN
+- 允许在数据表内部,与数组或嵌套字段进行 JOIN 操作,操作时把数组或嵌套字段拆成多行
+- 支持 INNER 和 LEFT,默认 INNER
+ ```sql
+ SELECT ... FROM table_name ARRAY JOIN column_name AS alias_name
+ SELECT ... FROM table_name LEFT ARRAY JOIN column_name AS alias_name
+ ```
+
+# JOIN
+## 连接精度
+- ALL: 默认,左表的每行数据,在右表中有多行连接匹配,返回右表全部连接数据
+- ANY: 左表的每行数据,在右表中有多行连接匹配,返回右表第一行连接数据
+- ASOF: 增加模糊连接条件,对应字段必须是整数、浮点数和日期这类有序数据类型
+ ```sql
+ SELECT ... FROM table_a ASOF INNER JOIN table_b USING(key_1, key_2)
+ -- key_1 字段是 join key,key_2 是模糊连接条件字段
+ ```
+
+## 连接类型
+- INNER: 内连接,返回交集部分
+- OUTER: 外链接
+ - LEFT: 左表数据全部返回,右表匹配则返回,不匹配则填充相应字段的默认值
+ - RIGHT: 与 LEFT 相反
+ - FULL: 先 LEFT,右表剩下的数据再 RIGHT
+
+- CROSS: 交叉连接,返回笛卡儿积
+
+## JOIN 查询优化
+- 左大右小,小表放右侧,右表会被加载到内存中
+- JOIN 查询无缓存,应用可考虑实现查询缓存
+- 大量维度属性补全时,建议使用字典表代替 JOIN 查询
+- USING 语法简写
+ ```sql
+ SELECT ... FROM table_1 INNTER JOIN table_2 USING key_1
+ ```
+
+# PREWHERE
+- 只能用于 MergeTree 系列表引擎
+- 与 WHERE 不同之处:
+ - 只读取 PREWHERE 指定的列字段,条件过滤
+ - 根据过滤好的数据再读取 SELECT 指定的列字段
+
+- clickhouse 会在合适条件下自动把 WHERE 替换成 PREWHERE
+
+# GROUP BY
+- WITH ROLLUP,按聚合键从右向左上卷数据,基于聚合函数依次生成分组小计和总计
+ ```sql
+ SELECT table, name, SUM(bytes_on_disk) FROM system.parts
+ GROUP BY table,name
+ WITH ROLLUP
+ ORDER BY table
+ ```
+
+- WITH CUBE,基于聚合键之间的所有组合生成小计信息
+ ```sql
+ SELECT ...
+ GROUP BY key1,key2,key3, ...
+ WITH CUBE
+ ...
+ ```
+
+- WITH TOTALS,常规聚合完成后,增加一行对所有数据的汇总统计
+ ```sql
+ SELECT ...
+ GROUP BY key1
+ WITH TOTALS
+ ...
+ ```
+
+# HAVING
+- 必须与 GROUP BY 配合使用,把聚合结果二次过滤
+ ```sql
+ SELECT ... GROUP BY ... HAVING ...
+ ```
+
+# ORDER BY
+- 默认 ASC(升序)
+- NULLS LAST,默认,其他值 -> NaN -> NULL
+- NULLS FIRST,NULL -> NaN -> 其他值
+
+# LIMIT BY
+- 返回指定分组的最多前 n 行数据
+ ```sql
+ LIMIT n BY key1,key2 ...
+ ```
+
+- 支持 OFFSET
+ ```sql
+ LIMIT n OFFSET m BY key1,key2 ...
+ -- 简写
+ LIMIT m,n BY key1,key2 ...
+ ```
+
+# LIMIT
+- 返回指定的前 n 行数据
+ ```sql
+ LIMIT n
+ LIMIT n OFFSET m
+ LIMIT m,n
+ ```
+
+- 推荐搭配 ORDER BY,保证全局顺序
+
+# SELECT
+- 查询正则匹配的列字段
+ ```sql
+ SELECT COLUMNS('^n'), COLUMNS('p') FROM system.databases
+ ```
+
+# DISTINCT
+- 去重
+- 先 DISTINCT 后 ORDER BY
+
+# UNION ALL
+- 联合左右两边的子查询,一并返回结果,可多次声明使用联合多组查询
+ ```sql
+ SELECT c1, c2 FROM t1 UNION ALL SELECT c3, c4 FROM t2
+ ```
+
+- 两边列字段数量必须一样,类型兼容,查询结果列名以左侧为准
+
+# SQL 执行计划
+- 设置日志到 DEBUG 或 TRACE 级别,可查看 SQL 执行日志
+- SQL 需真正执行后才有日志,如果查询量大,推荐 LIMIT
+- **不要用 SELECT * 查询**
+- 尽可能利用索引,避免全表扫描
+
diff --git a/content/post/ch-set.md b/content/post/ch-set.md
new file mode 100644
index 0000000..87b6cab
--- /dev/null
+++ b/content/post/ch-set.md
@@ -0,0 +1,27 @@
+---
+title: "ClickHouse 表引擎之 Set"
+date: 2020-10-08T17:04:00+08:00
+lastmod: 2020-10-08T17:04:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# Set 表引擎简介
+- 数据先写内存,再同步到磁盘,服务重启后全量加载到内存
+- 支持 INSERT,写入时,重复数据被忽略
+- 不能直接 SELECT,只能作为 IN 查询的右侧条件
+
+# 创建 Set 引擎表
+- 声明
+ ```sql
+ ENGINE = Set()
+ ```
+
+# 使用
+- 创建 Set 引擎表
+- INSERT 写入数据
+- 查询
+ ```sql
+ SELECT arrayJoin([1,2,3]) AS a WHERE a IN set_table;
+ ```
+
diff --git a/content/post/ch-url.md b/content/post/ch-url.md
new file mode 100644
index 0000000..e39b659
--- /dev/null
+++ b/content/post/ch-url.md
@@ -0,0 +1,22 @@
+---
+title: "ClickHouse 表引擎之 URL"
+date: 2020-10-08T19:16:00+08:00
+lastmod: 2020-10-08T19:16:00+08:00
+tags: []
+categories: ["clickhouse"]
+---
+
+# URL 表引擎简介
+- http 客户端,支持 http 和 https 协议
+- SELECT 转换成 GET 请求
+- INSERT 转换成 POST 请求
+
+# 创建 URL 表引擎
+- 声明
+ ```sql
+ ENGINE = URL('url', format)
+ ```
+
+- url: 请求远端服务的 url
+- format: TSV、CSV、JSONEachRow
+
diff --git a/content/post/cloudera-manager.md b/content/post/cloudera-manager.md
new file mode 100644
index 0000000..7946bc0
--- /dev/null
+++ b/content/post/cloudera-manager.md
@@ -0,0 +1,260 @@
+---
+title: "CentOS7 安装 Cloudera Manager"
+date: 2020-08-16T13:48:37+08:00
+lastmod: 2020-08-16T18:27:00+08:00
+keywords: []
+tags: [cloudera cdh]
+categories: ["hadoop"]
+---
+
+# 环境
+
+角色 | IP | 主机名 | 服务
+---- | ---- | ---- | ----
+Utility | 192.168.1.100 | cm0.colben.cn | ClouderaManager
ClouderaManagerManagementService
HiveMetastore
+Gateway | 192.168.1.101 | gw0.colben.cn | GatewayConfiguration
HiveServer2
Zookeeper
+Master | 192.168.1.102 | m0.colben.cn | NameNode
JournalNode
FailoverController
YarnResourceManager
Zookeeper
+Master | 192.168.1.103 | m1.colben.cn | NameNode
JournalNode
FailoverController
YarnResourceManager
Zookeeper
+Worker | 192.168.1.104 | w0.colben.cn | DataNode
NodeManager
+Worker | 192.168.1.105 | w0.colben.cn | DataNode
NodeManager
+Worker | 192.168.1.106 | w0.colben.cn | DataNode
NodeManager
+
+# 配置 ssh 免密登陆
+
+- 在 cm0 上配置 ssh 可免密登陆全部服务器
+ ```
+ ssh-keygen
+ seq -f'192.168.1.%g' 100 106 | xargs -L1 ssh-copy-id
+ ```
+
+- 这里的**私钥会在后面通过界面增加主机时用到**
+
+# 关闭防火墙和 selinux
+
+- 在全部服务器上关闭防火墙
+ ```
+ systemctl stop firewalld
+ systemctl disable firewalld
+ ```
+
+- 在全部服务器上关闭 selinux
+ ```
+ setenforce 0
+ sed -i '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
+ ```
+
+# 配置网络名称
+
+- 在全部服务器上修改 /etc/hosts,增加如下解析记录
+ ```
+ 192.168.1.100 cm0.colben.cn cm0
+ 192.168.1.101 gw0.colben.cn gw0
+ 192.168.1.102 m0.colben.cn m0
+ 192.168.1.103 m1.colben.cn m1
+ 192.168.1.104 w0.colben.cn w0
+ 192.168.1.105 w1.colben.cn w1
+ 192.168.1.106 w2.colben.cn w2
+ ```
+
+- 在全部服务器上修改 /etc/sysconfig/network,增加各自主机名设置
+ ```
+ HOSTNAME=XXXX.colben.cn
+ ```
+
+- 在全部服务器上设置各自主机名
+ ```
+ hostnamectl set-hostname XXXX.colben.cn
+ ```
+
+# 配置时间同步
+
+- 在 cm0 上配置修改 /etc/chrony.conf
+ ```
+ server ntp.aliyun.com iburst
+ allow 192.168.1.0/24
+ ```
+
+- 在其他服务器上修改 /etc/chrony.conf
+ ```
+ server cm0.colben.cn iburst
+ ```
+
+- 在全部服务器上重启 chronyd 服务
+ ```
+ systemctl restart chronyd
+ ```
+
+# 配置 cloudera manager 内网安装源
+
+- 在 cm0 上安装并启动 httpd 服务
+ ```
+ yum install httpd
+ systemctl enable httpd
+ systemctl start httpd
+ ```
+
+- 在 cm0 上下载 cloudera manager yum 仓库
+ ```
+ mkdir -p /var/www/html/cloudera-repos/cm6/6.3.1
+ wget https://archive.cloudera.com/cm6/6.3.1/repo-as-tarball/cm6.3.1-redhat7.tar.gz
+ tar zxf cm6.3.1-redhat7.tar.gz -C /var/www/html/cloudera-repos/cm6/6.3.1 --strip-components=1
+ chmod -R ugo+rX /var/www/html/cloudera-repos/cm6
+ ```
+
+- 在 cm0 上下载 cdh yum 仓库(**体积较小,需手动升级**)
+ ```
+ mkdir -p /var/www/html/cloudera-repos
+ wget --recursive --no-parent --no-host-directories \
+ https://archive.cloudera.com/cdh6/6.3.2/redhat7/ -P /var/www/html/cloudera-repos
+ wget --recursive --no-parent --no-host-directories \
+ https://archive.cloudera.com/gplextras6/6.3.2/redhat7/ -P /var/www/html/cloudera-repos
+ chmod -R ugo+rX /var/www/html/cloudera-repos/cdh6
+ chmod -R ugo+rX /var/www/html/cloudera-repos/gplextras6
+ ```
+
+- 在 cm0 上下载 cdh parcel 仓库(**体积较大,可通过界面操作自动升级**)
+ ```
+ mkdir -p /var/www/html/cloudera-repos
+ wget --recursive --no-parent --no-host-directories \
+ https://archive.cloudera.com/cdh6/6.3.2/parcels/ -P /var/www/html/cloudera-repos
+ wget --recursive --no-parent --no-host-directories \
+ https://archive.cloudera.com/gplextras6/6.3.2/parcels/ -P /var/www/html/cloudera-repos
+ chmod -R ugo+rX /var/www/html/cloudera-repos/cdh6
+ chmod -R ugo+rX /var/www/html/cloudera-repos/gplextras6
+ ```
+
+- 在 cm0 上创建 cloudera.repo,并重建 yum 缓存
+ ```
+ cat > /etc/yum.repos.d/cloudera.repo <<-EOF
+ [cloudera-manager]
+ name=cloudera-manager
+ baseurl=http://cm0.colben.cn/cloudera-repos/cm6/
+ enabled=1
+ gpgcheck=0
+ EOF
+ yum clean all
+ yum makecache fast
+ ```
+
+- 通过界面增加其他主机时,cloudera manager 会自动部署该 repo 文件
+
+# 安装 jdk
+
+- 在 cm0 上安装 jdk
+ ```
+ yum install oracle-j2sdk1.8
+ ```
+
+- 通过界面增加其他主机时,可自动安装 oracle-j2sdk1.8
+
+# 安装 cloudera manager
+
+- 在 cm0 上安装 cloudera-manager
+ ```
+ yum install cloudera-manager-daemons cloudera-manager-agent cloudera-manager-server
+ ```
+
+# 配置 cloudera manager 数据库
+
+- 在 cm0 上安装 mysql,[参考这里](/post/mysql5.7-install/)
+- 官方推荐 my.cnf
+ ```
+ [mysqld]
+ transaction-isolation = READ-COMMITTED
+ symbolic-links = 0
+ sql_mode=STRICT_ALL_TABLES
+
+ key_buffer_size = 32M
+ max_allowed_packet = 32M
+ thread_stack = 256K
+ thread_cache_size = 64
+ query_cache_limit = 8M
+ query_cache_size = 64M
+ query_cache_type = 1
+ max_connections = 600
+
+ log_bin=/var/lib/mysql/mysql_binary_log
+ server_id=1
+ binlog_format = mixed
+
+ read_buffer_size = 2M
+ read_rnd_buffer_size = 16M
+ sort_buffer_size = 8M
+ join_buffer_size = 8M
+
+ innodb_file_per_table = 1
+ innodb_flush_log_at_trx_commit = 2
+ innodb_log_buffer_size = 64M
+ innodb_buffer_pool_size = 4G
+ innodb_thread_concurrency = 8
+ innodb_flush_method = O_DIRECT
+ innodb_log_file_size = 512M
+ ```
+
+- 在 cm0 上启动 mysql
+ ```
+ systemctl enable mysqld
+ systemctl start mysqld
+ ```
+
+- 在 cm0 上的 mysql 中创建数据库和用户
+ ```
+ CREATE DATABASE scm DEFAULT CHARSET utf8 DEFAULT COLLATE utf8_general_ci;
+ CREATE DATABASE amon DEFAULT CHARSET utf8 DEFAULT COLLATE utf8_general_ci;
+ CREATE DATABASE rman DEFAULT CHARSET utf8 DEFAULT COLLATE utf8_general_ci;
+ CREATE DATABASE hue DEFAULT CHARSET utf8 DEFAULT COLLATE utf8_general_ci;
+ CREATE DATABASE metastore DEFAULT CHARSET utf8 DEFAULT COLLATE utf8_general_ci;
+ CREATE DATABASE sentry DEFAULT CHARSET utf8 DEFAULT COLLATE utf8_general_ci;
+ CREATE DATABASE nav DEFAULT CHARSET utf8 DEFAULT COLLATE utf8_general_ci;
+ CREATE DATABASE navms DEFAULT CHARSET utf8 DEFAULT COLLATE utf8_general_ci;
+ CREATE DATABASE oozie DEFAULT CHARSET utf8 DEFAULT COLLATE utf8_general_ci;
+ GRANT ALL ON scm.* to scm@'%' identified by 'Pass-1234';
+ GRANT ALL ON amon.* to amon@'%' identified by 'Pass-1234';
+ GRANT ALL ON rman.* to rman@'%' identified by 'Pass-1234';
+ GRANT ALL ON hue.* to hue@'%' identified by 'Pass-1234';
+ GRANT ALL ON metastore.* to hive@'%' identified by 'Pass-1234';
+ GRANT ALL ON sentry.* to sentry@'%' identified by 'Pass-1234';
+ GRANT ALL ON nav.* to nav@'%' identified by 'Pass-1234';
+ GRANT ALL ON navms.* to navms@'%' identified by 'Pass-1234';
+ GRANT ALL ON oozie.* to oozie@'%' identified by 'Pass-1234';
+ ```
+
+- 在 cm0 上初始化数据库
+ ```
+ /opt/cloudera/cm/schema/scm_prepare_database.sh mysql scm scm
+ /opt/cloudera/cm/schema/scm_prepare_database.sh mysql amon amon
+ /opt/cloudera/cm/schema/scm_prepare_database.sh mysql rman rman
+ /opt/cloudera/cm/schema/scm_prepare_database.sh mysql hue hue
+ /opt/cloudera/cm/schema/scm_prepare_database.sh mysql metastore hive
+ /opt/cloudera/cm/schema/scm_prepare_database.sh mysql sentry sentry
+ /opt/cloudera/cm/schema/scm_prepare_database.sh mysql nav nav
+ /opt/cloudera/cm/schema/scm_prepare_database.sh mysql navms navms
+ /opt/cloudera/cm/schema/scm_prepare_database.sh mysql oozie oozie
+ ```
+
+- 在全部服务器上安装 mysql connector
+ ```
+ wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.46.tar.gz
+ tar zxvf mysql-connector-java-5.1.46.tar.gz
+ mkdir -p /usr/share/java/
+ cp mysql-connector-java-5.1.46/mysql-connector-java-5.1.46-bin.jar \
+ /usr/share/java/mysql-connector-java.jar
+ ```
+
+# 启动 cloudera manager
+- 在 cm0 上启动 cloudera manager
+ ```
+ systemctl start cloudera-scm-server
+ ```
+
+- 日志: /var/log/cloudera-scm-server/cloudera-scm-server.log
+- 日志出现如下信息,表示 cloudera-scm-server 启动完成
+ ```
+ INFO WebServerImpl:com.cloudera.server.cmf.WebServerImpl: Started Jetty server.
+ ```
+
+- 浏览器访问
+ - 地址: http://cm0.colben.cn:7180/
+ - 用户: admin
+ - 密码: admin
+
diff --git a/content/post/configmap.md b/content/post/configmap.md
new file mode 100644
index 0000000..3e497cb
--- /dev/null
+++ b/content/post/configmap.md
@@ -0,0 +1,280 @@
+---
+title: "ConfigMap 笔记"
+date: 2019-12-22T22:04:37+08:00
+lastmod: 2019-12-22T22:04:37+08:00
+tags: ["kubernetes", "configmap"]
+categories: ["container"]
+---
+
+# 概述
+- ConfigMap 通常用于设置环境变量、设置命令行参数、创建配置文件
+- Pod 使用 ConfigMap 前,ConfigMap 必须存在,否则 pod 不能启动
+- ConfigMap 只能被在同一一个命名空间中的Pod所引用
+
+# 创建 ConfigMap
+- 命令如下
+ ```bash
+ kubectl create configmap
+ # 或者
+ kubectl apply -f
+ ```
+- map-name: ConfigMap 名称
+- data-source: 目录、文件或具体值
+
+## 通过目录创建 ConfigMaps
+- 命令如下
+ ```bash
+ kubectl create configmap game-config \
+ --from-file=https://k8s.io/docs/tasks/configure-pod-container/configmap/kubectl
+ ```
+- docs/tasks/configure-pod-container/configmap/kubectl/目录下的文件包括
+ ```bash
+ ls docs/tasks/configure-pod-container/configmap/kubectl/
+ ```
+ - 输出如下
+ ```
+ game.properties
+ ui.properties
+ ```
+- 查看 game-config 信息
+ ```bash
+ kubectl describe configmaps game-config
+ # 或者
+ kubectl get configmaps game-config -o yaml
+ ```
+
+## 通过文件创建 ConfigMaps
+- 通过单个文件创建
+ ```bash
+ kubectl create configmap game-config-2 \
+ --from-file=https://k8s.io/docs/tasks/configure-pod-container/configmap/kubectl/game.properties
+ ```
+- 通过多个文件创建
+ ```bash
+ kubectl create configmap game-config-3 \
+ --from-file=https://k8s.io/docs/tasks/configure-pod-container/configmap/kubectl/game.properties \
+ --from-file=https://k8s.io/docs/tasks/configure-pod-container/configmap/kubectl/ui.properties
+ ```
+- 通过文件创建ConfigMap时可以定义文件的键
+ ```bash
+ kubectl create configmap game-config-4 \
+ --from-file=game-special-key=https://k8s.io/docs/tasks/configure-pod-container/configmap/kubectl/game.properties
+ # key 是 "game-special-key"
+ # value 是 game.properties 文件的内容
+ ```
+
+## 通过具体值创建 ConfigMaps
+- 使用 --from-literal 参数定义具体值
+ ```bash
+ kubectl create configmap special-config \
+ --from-literal=special.how=very \
+ --from-literal=special.type=charm
+ ```
+
+# 使用 ConfigMap
+## 定义 pod 环境变量
+### Pod 环境变量的值来自于单一 ConfigMap
+- 在ConfigMap中定义一个环境变量作为键值对
+ ```bash
+ kubectl create configmap special-config --from-literal=special.how=very
+ ```
+- 指派ConfigMap中定义的special.how的值给Pod中SPECIAL_LEVEL_KEY环境变量
+ ```yaml
+ apiVersion:v1
+ kind:Pod
+ metadata:
+ name:dapi-test-pod
+ spec:
+ containers:
+ - name:test-container
+ image:k8s.gcr.io/busybox
+ command:["/bin/sh","-c","env"]
+ env:
+ # Define the environment variable
+ - name:SPECIAL_LEVEL_KEY
+ valueFrom:
+ configMapKeyRef:
+ # The ConfigMap containing the value you want to assign to SPECIAL_LEVEL_KEY
+ name:special-config
+ # Specify the key associated with the value
+ key:special.how
+ restartPolicy:Never
+ ```
+- 保存Pod规格的变化,Pod将输出SPECIAL_LEVEL_KEY=very
+
+### Pod 环境变量的值来自于多个 ConfigMap
+- 创建两个 ConfigMap
+ ```yaml
+ ---
+ apiVersion:v1
+ kind:ConfigMap
+ metadata:
+ name:special-config
+ namespace:default
+ data:
+ special.how:very
+
+ ---
+ apiVersion:v1
+ kind:ConfigMap
+ metadata:
+ name:env-config
+ namespace:default
+ data:
+ log_level:INFO
+ ```
+- 在Pod规格中定义环境变量
+ ```yaml
+ apiVersion:v1
+ kind:Pod
+ metadata:
+ name:dapi-test-pod
+ spec:
+ containers:
+ - name:test-container
+ image:k8s.gcr.io/busybox
+ command:["/bin/sh","-c","env"]
+ env:
+ - name:SPECIAL_LEVEL_KEY
+ valueFrom:
+ configMapKeyRef:
+ name:special-config
+ key:special.how
+ - name:LOG_LEVEL
+ valueFrom:
+ configMapKeyRef:
+ name:env-config
+ key:log_level
+ restartPolicy:Neverv
+ ```
+- 保存变更后的Pod,Pod将会输出SPECIAL_LEVEL_KEY=very和LOG_LEVEL=info
+
+## 在一个ConfigMap中配置的键值对都作为一个Pod的环境变量
+- **Kubernetes v1.6+可用**
+- 创建包含多个键-值对的ConfigMap
+ ```yaml
+ apiVersion:v1
+ kind:ConfigMap
+ metadata:
+ name:special-config
+ namespace:default
+ data:
+ SPECIAL_LEVEL:very
+ SPECIAL_TYPE:charm
+ ```
+- 使用envFrom定义所有的ConfigMap数据作为Pod的环境变量。来自于Config的键成为Pod中环境变量的名
+ ```yaml
+ apiVersion:v1
+ kind:Pod
+ metadata:
+ name:dapi-test-pod
+ spec:
+ containers:
+ - name:test-container
+ image:k8s.gcr.io/busybox
+ command:["/bin/sh","-c","env"]
+ envFrom:
+ - configMapRef:
+ name:special-config
+ restartPolicy:Never
+ ```
+- Pod的输出包括: SPECIAL_LEVEL=very 和 SPECIAL_TYPE=charm
+
+## 在Pod命令行中使用ConfigMap定义的环境变量
+- 在Pod规范的command 中使用$(VAR_NAME) ,获取ConfigMap定义的环境变量
+ ```yaml
+ apiVersion:v1
+ kind:Pod
+ metadata:
+ name:dapi-test-pod
+ spec:
+ containers:
+ - name:test-container
+ image:k8s.gcr.io/busybox
+ command:["/bin/sh","-c","echo $(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)"]
+ env:
+ - name:SPECIAL_LEVEL_KEY
+ valueFrom:
+ configMapKeyRef:
+ name:special-config
+ key:SPECIAL_LEVEL
+ - name:SPECIAL_TYPE_KEY
+ valueFrom:
+ configMapKeyRef:
+ name:special-config
+ key:SPECIAL_TYPE
+ restartPolicy:Never
+ ```
+- test-container容器的输出: very charm
+
+# 添加ConfigMap数据至存储卷
+- 当通过–from-file创建的ConfigMap时,文件将作为一个键保存在ConfigMap中,而此文件的内容将作为值
+ ```bash
+ apiVersion:v1
+ kind:ConfigMap
+ metadata:
+ name:special-config
+ namespace:default
+ data:
+ special.level:very
+ special.type:charm
+ ```
+
+## 将ConfigMap中的数据传播到指定目录
+- 在Pod的存储卷区域添加ConfigMap的名称
+- 这将添加ConfigMap数据到volumeMounts.mountPath指定的目录下(此例为/etc/config)
+- command区域将引用保存在ConfigMap中的special.level条目
+ ```bash
+ apiVersion:v1
+ kind:Pod
+ metadata:
+ name:dapi-test-pod
+ spec:
+ containers:
+ - name:test-container
+ image:k8s.gcr.io/busybox
+ command:["/bin/sh","-c","ls /etc/config/"]
+ volumeMounts:
+ - name:config-volume
+ mountPath:/etc/config
+ volumes:
+ - name:config-volume
+ configMap:
+ # Provide the name of the ConfigMap containing the files you want
+ # to add to the container
+ name:special-config
+ restartPolicy:Never
+ ```
+- Pod运行时,command (“ls /etc/config/”)将输出: special.level special.type
+- **如果在/etc/config/目录下存在文件,将不会删除**
+
+## 添加ConfigMap数据至存储卷指定的目录
+- 为ConfigMap条目,使用path指定文件路径
+- 此例中,special.level将在config-volume存储卷中被挂接至/etc/config/keys
+ ```yaml
+ apiVersion:v1
+ kind:Pod
+ metadata:
+ name:dapi-test-pod
+ spec:
+ containers:
+ - name:test-container
+ image:k8s.gcr.io/busybox
+ command:["/bin/sh","-c","cat /etc/config/keys"]
+ volumeMounts:
+ - name:config-volume
+ mountPath:/etc/config
+ volumes:
+ - name:config-volume
+ configMap:
+ name:special-config
+ items:
+ - key:special.level
+ path:keys
+ restartPolicy:Never
+ ```
+- Pod运行时,(“cat /etc/config/keys”) 将输出: very
+
+# 参考
+- [Kubernetes-配置字典ConfigMap](https://blog.csdn.net/bbwangj/article/details/81776648)
+
diff --git a/content/post/docker.md b/content/post/docker.md
new file mode 100644
index 0000000..3f6256d
--- /dev/null
+++ b/content/post/docker.md
@@ -0,0 +1,328 @@
+---
+title: "Docker 笔记"
+date: 2019-10-30T13:29:00+08:00
+lastmod: 2021-11-05T11:11:00+08:00
+tags: ["docker"]
+categories: ["container"]
+---
+
+# 安装 docker
+## CentOS7 安装 docker-ce
+- 配置 yum 源
+ ```bash
+ curl -Lo /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo
+ #替换成清华源
+ sed -i 's#download.docker.com#mirrors.tuna.tsinghua.edu.cn/docker-ce#' /etc/yum.repos.d/docker-ce.repo
+ yum clean all
+ yum makecache
+ ```
+
+- 安装 docker
+ ```bash
+ yum install docker-ce
+ ```
+
+- 修改 docker 配置文件,建议选择一个与本地网络不冲突的网段
+ ```bash
+ mkdir -p /etc/docker
+ cat > /etc/docker/daemon.json <<-EOF
+ {
+ "insecure-registries": ["harbor.colben.cn"],
+ "default-address-pools" : [{"base":"10.110.0.0/16", "size": 24}],
+ "log-driver": "json-file",
+ "log-opts": {"max-size":"100m", "max-file":"4"}
+ }
+ EOF
+ ```
+
+- 启动 docker
+ ```bash
+ systemctl start docker
+ ```
+
+## 常用的 linux with systemd 安装 docker
+- 下载 docker 二进制文件
+ ```bsah
+ curl -LO https://download.docker.com/linux/static/stable/x86_64/docker-20.10.10.tgz
+ ```
+
+- 安装
+ ```bash
+ tar zxf docker-20.10.10.tgz
+ mv docker/* /usr/bin/
+ rm -rf docker/ docker-20.10.10.tgz
+ groupadd -g 10110 docker
+ ```
+
+- 创建 /usr/lib/systemd/system/containerd.service,内容如下
+ ```
+ [Unit]
+ Description=containerd container runtime
+ Documentation=https://containerd.io
+ After=network.target local-fs.target
+
+ [Service]
+ ExecStartPre=-/sbin/modprobe overlay
+ ExecStart=/usr/bin/containerd
+
+ Type=notify
+ Delegate=yes
+ KillMode=process
+ Restart=always
+ RestartSec=5
+ # Having non-zero Limit*s causes performance problems due to accounting overhead
+ # in the kernel. We recommend using cgroups to do container-local accounting.
+ LimitNPROC=infinity
+ LimitCORE=infinity
+ LimitNOFILE=1048576
+ # Comment TasksMax if your systemd version does not supports it.
+ # Only systemd 226 and above support this version.
+ TasksMax=infinity
+ OOMScoreAdjust=-999
+
+ [Install]
+ WantedBy=multi-user.target
+ ```
+
+- 创建 /usr/lib/systemd/system/container-getty@.service,内容如下
+ ```
+ [Unit]
+ Description=Container Getty on /dev/pts/%I
+ Documentation=man:agetty(8) man:machinectl(1)
+ After=systemd-user-sessions.service plymouth-quit-wait.service
+ After=rc-local.service getty-pre.target
+ Before=getty.target
+ IgnoreOnIsolate=yes
+ ConditionPathExists=/dev/pts/%I
+
+ [Service]
+ ExecStart=-/sbin/agetty --noclear --keep-baud pts/%I 115200,38400,9600 $TERM
+ Type=idle
+ Restart=always
+ RestartSec=0
+ UtmpIdentifier=pts/%I
+ TTYPath=/dev/pts/%I
+ TTYReset=yes
+ TTYVHangup=yes
+ KillMode=process
+ IgnoreSIGPIPE=no
+ SendSIGHUP=yes
+ ```
+
+- 创建 /usr/lib/systemd/system/docker.socket,内容如下
+ ```
+ [Unit]
+ Description=Docker Socket for the API
+
+ [Socket]
+ ListenStream=/var/run/docker.sock
+ SocketMode=0660
+ SocketUser=root
+ SocketGroup=docker
+
+ [Install]
+ WantedBy=sockets.target
+ ```
+
+- 创建 /usr/lib/systemd/system/docker.service,内容如下
+ ```
+ [Unit]
+ Description=Docker Application Container Engine
+ Documentation=https://docs.docker.com
+ After=network-online.target firewalld.service containerd.service
+ Wants=network-online.target
+ Requires=docker.socket containerd.service
+
+ [Service]
+ Type=notify
+ # the default is not to use systemd for cgroups because the delegate issues still
+ # exists and systemd currently does not support the cgroup feature set required
+ # for containers run by docker
+ ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
+ ExecReload=/bin/kill -s HUP $MAINPID
+ TimeoutSec=0
+ RestartSec=2
+ Restart=always
+
+ # Note that StartLimit* options were moved from "Service" to "Unit" in systemd 229.
+ # Both the old, and new location are accepted by systemd 229 and up, so using the old location
+ # to make them work for either version of systemd.
+ StartLimitBurst=3
+
+ # Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230.
+ # Both the old, and new name are accepted by systemd 230 and up, so using the old name to make
+ # this option work for either version of systemd.
+ StartLimitInterval=60s
+
+ # Having non-zero Limit*s causes performance problems due to accounting overhead
+ # in the kernel. We recommend using cgroups to do container-local accounting.
+ LimitNOFILE=infinity
+ LimitNPROC=infinity
+ LimitCORE=infinity
+
+ # Comment TasksMax if your systemd version does not support it.
+ # Only systemd 226 and above support this option.
+ TasksMax=infinity
+
+ # set delegate yes so that systemd does not reset the cgroups of docker containers
+ Delegate=yes
+
+ # kill only the docker process, not all processes in the cgroup
+ KillMode=process
+ OOMScoreAdjust=-500
+
+ [Install]
+ WantedBy=multi-user.target
+ ```
+
+- 修改 docker 配置文件,建议选择一个与本地网络不冲突的网段
+ ```bash
+ mkdir -p /etc/docker
+ cat > /etc/docker/daemon.json <<-EOF
+ {
+ "insecure-registries": ["harbor.colben.cn"],
+ "default-address-pools" : [{"base":"10.110.0.0/16", "size": 24}],
+ "log-driver": "json-file",
+ "log-opts": {"max-size":"100m", "max-file":"4"}
+ }
+ EOF
+ ```
+
+- 启动 docker
+ ```bash
+ systemctl start docker
+ ```
+
+# 安装 docker-compose
+- 下载 docker-compose
+ ```bash
+ curl -LO https://github.com/docker/compose/releases/download/v2.1.0/docker-compose-linux-x86_64
+ ```
+
+- 安装
+ ```bash
+ mv docker-compose-linux-x86_64 /usr/bin/docker-compose
+ chmod 0755 /usr/bin/docker-compose
+ ```
+
+# 安装 gojq
+- 该工具支持平台较多,无依赖,与 jq 命令操作完全一致,可用于替换 jq 命令
+- 下载
+ ```bash
+ curl -LO https://github.com/itchyny/gojq/releases/download/v0.12.5/gojq_v0.12.5_linux_amd64.tar.gz
+ ```
+
+- 安装
+ ```bash
+ tar zxf gojq_v0.12.5_linux_amd64.tar.gz
+ mv gojq_v0.12.5_linux_amd64/gojq /usr/bin/
+ chmod 0755 /usr/bin/gojq
+ rm -rf gojq_*
+ ```
+
+# 基本命令
+- 镜像
+ ```bash
+ docker pull [选项] [Docker Registry地址]<仓库名>:<标签> #获取镜像
+ docker images [选项] #列出镜像
+ docker images -f dangling=true #列出虚悬镜像
+ docker images -q -f dangling=true | xargs docker rmi #删除全部虚悬镜像
+ docker commit -m "提交的说明信息" -a "更新的用户信息" <容器ID> [地址]<仓库名>:<标签> #根据现有容器创建镜像
+ docker build -t="[地址]<仓库名>:<标签>" #构建镜像
+ docker tag <镜像ID> [地址]<仓库名>:<标签> #修改镜像的标签
+ docker push [地址]<仓库名>:<标签> #上传镜像
+ docker save -o <本地文件名.tar> [地址]<仓库名>:<标签> #保存镜像到本地文件
+ docker load < <本地文件名.tar> #把本地文件加载到镜像库
+ docker rmi <镜像ID> #删除镜像
+ ```
+
+- 容器
+ ```bash
+ docker run [选项] [地址]<仓库名>:<标签> [命令] #从镜像启动一个容器
+ docker stop <容器名|容器ID> #终止运行中的容器
+ docker start <容器名|容器ID> #启动已停止的容器
+ docker retart <容器名|容器ID> #重新启动运行中的容器
+ docker ps [-a] #查看(全部)容器信息
+ docker logs <容器名|容器ID> #获取容器输出信息
+ docker attach <容器名|容器ID> #进入运行中的容器
+ docker export <容器ID> > <本地文件名.tar> #导出容器快照到本地文件
+ cat <本地文件名.tar> | docker import - [地址]<仓库名>:<标签> #从本地文件导入容器快照
+ docker rm -r <容器名|容器ID> #删除(运行中的)容器
+ ```
+
+- 数据卷
+ ```bash
+ docker run [选项] -v /webapp [地址]<仓库名>:<标签> [命令] #启动容器时创建一个数据卷挂载到容器的 /webapp 下
+ docker rm -v <容器名|容器ID> #删除容器时同时删除数据卷
+ docker run [选项] -v <主机绝对目录>:<容器绝对目录>[:ro] [地址]<仓库名>:<标签> [命令] #启动容器时挂载本地目录到容器指定目录下,默认可读写
+ docker run [选项] -v <主机文件>:<容器文件>[:ro] [地址]<仓库名>:<标签> [命令] #启动容器时挂载本地文件到容器指定文件,默认可读写
+ docker inspect <容器名|容器ID> #查看容器信息
+ docker run [选项] --volumes-from <挂载数据卷的容器名> [地址]<仓库名>:<标签> [命令] #在其他容器中挂载指定容器(不必运行)的数据卷
+ ```
+
+- 备份数据卷
+ ```bash
+ docker run [选项] --volumes-from <挂载数据卷的容器名> -v $(pwd):/backup [地址]<仓库名>:<标签> tar cvf /backup/backup.tar <数据卷挂载目录> #备份数据卷到主机当前目录的 backup.tar 文件
+ ```
+
+- 恢复数据卷
+ ```bash
+ docker run [选项] -v <数据卷挂载目录> --name <自定义一个容器名> [地址]<仓库名>:<标签> [命令] #创建一个带空数据卷的容器
+ docker run [选项] --volumes-from <第一步挂载空数据卷的容器名> -v $(pwd):/backup busybox tar xvf /backup/backup.tar #挂载空数据卷和本机备份目录,解压备份文件
+ docker run [选项] --volumes-from <第一步挂载空数据卷的容器名> busybox 'ls <数据卷挂载目录>' #查看恢复的数据
+ ```
+
+- 网络
+ ```bash
+ docker run [选项] -P [地址]<仓库名>:<标签> [命令] #随机映射主机 49000-49900 中的端口到容器开放的端口
+ docker run [选项] -p [/udp] [地址]<仓库名>:<标签> [命令] #映射本机指定tcp(udp)端口到容器指定tcp(udp)端口
+ docker port <容器名> <容器开放的端口> #查看主机被绑定的地址
+ docker run [选项] --link <待链接容器名>:<链接别名> [地址]<仓库名>:<标签> [命令] #创建一个链接到其他容器的新容器
+ ```
+
+# Dockerfile
+- 井号 "#" 后是注释
+- FROM 基础镜像
+- MAINTAINER 维护者信息
+- RUN shell命令
+- ADD 复制本地文件到容器,自动解压 tar 文件,可以增加网络文件
+- COPY 复制本地文件到容器,不自动解压,也不可以增加网络文件
+- LABEL 为镜像添加元数据
+- ENV 设置镜像内环境变量
+- USER 容器运行时的用户和用户组
+- ONBUILD 镜像触发器
+- EXPOSE 向外部开放端口
+- CMD 容器启动后运行的程序
+
+# docker 镜像仓库
+## 官方 registry
+- 直接 docker 启动
+ ```bash
+ docker run -d \
+ --name registry \
+ --net host \
+ -e "REGISTRY_HTTP_ADDR='0.0.0.0:80'" \
+ -v /some/path:/var/lib/registry \
+ registry
+ ```
+
+## VMWare Harbor
+- 安装[参考这里](https://goharbor.io/docs/2.0.0/install-config/)
+- docker registry 采用 http 协议,客户端提示 “server gave HTTP response to HTTPS client”
+ ```bash
+ #在客户端 /etc/docker/daemon.json 中增加 insecure-registries 配置
+ # "insecure-registries":["10.0.2.22:5080"]
+ #重启客户端的 docker 服务
+ systemctl restart docker
+ ```
+
+- docker registry 采用 https 协议,客户端提示 "authority unknown ..."
+ ```bash
+ #在客户端 /etc/docker/ 下创建 registry server 的 domain/ip 目录
+ mkdir -p /etc/docker/certs.d/10.0.2.22:5080/
+ #复制 registry server 的 ca.crt (该文件由 openssl 创建密钥时生成)
+ scp root@10.0.2.22:/opt/harbor/keys/ca.crt /etc/docker/certs.d/10.0.2.22:5080/
+ #重启客户端的 docker 服务
+ systemctl restart docker
+ ```
+
diff --git a/content/post/drone.md b/content/post/drone.md
new file mode 100644
index 0000000..36b7387
--- /dev/null
+++ b/content/post/drone.md
@@ -0,0 +1,215 @@
+---
+title: "Drone 笔记"
+date: 2021-02-08T17:03:13+08:00
+lastmod: 2021-02-08T21:08:00+08:00
+keywords: []
+tags: ["drone", "cicd"]
+categories: ["dev/ops"]
+---
+
+# 环境
+
+- 操作系统 Linux x86_64
+- 这里的 drone 是基于 gitea 配置的
+- 安装 gitea,参考[官方文档](https://docs.gitea.io/zh-cn/install-from-binary/)
+- 安装 docker-ce,参考[我的 docker 笔记](https://www.colben.cn/post/docker/#%E5%AE%89%E8%A3%85)
+
+# 创建 OAuth2 应用
+
+- 登陆 gitea
+- 点击 "个人头像" - "设置" - "应用"
+- 在 "管理 OAuth2 应用程序" 中,输入
+ - 输入应用名称: "drone"
+ - 重定向URI: "http://:/login"
+
+- 点击 "创建应用",在弹出的新页面中
+ - 记录 "客户端 ID"
+ - 记录 "客户端密钥"
+ - 点击 "保存"
+
+# 安装 drone
+
+- 下载 docker 镜像
+ ```bash
+ docker pull drone/drone
+ ```
+
+- 启动容器
+ ```bash
+ docker run -d \
+ --name drone \
+ -e DRONE_GITEA_SERVER=http://: \
+ -e DRONE_GITEA_CLIENT_ID=<客户端 ID> \
+ -e DRONE_GITEA_CLIENT_SECRET=<客户端密钥> \
+ -e DRONE_RPC_SECRET=1111aaaa2222bbbb3333cccc4444dddd \
+ -e DRONE_SERVER_HOST=: \
+ -e DRONE_SERVER_PROTO=http \
+ -e DRONE_GIT_ALWAYS_AUTH=true \
+ -p :80 \
+ -v <外挂的 drone 数据目录>:/data \
+ drone/drone
+ ```
+
+- 参考链接[https://docs.drone.io/server/provider/gitea/](https://docs.drone.io/server/provider/gitea/)
+
+# 安装 drone runner
+## 安装 docker runner
+
+- 下载 drone-runner-docker 镜像
+ ```bash
+ docker pull drone/drone-runner-docker
+ ```
+
+- 启动容器
+ ```bash
+ docker run -d \
+ --name drone_runner_docker \
+ -e DRONE_RPC_PROTO=http \
+ -e DRONE_RPC_HOST=: \
+ -e DRONE_RPC_SECRET=1111aaaa2222bbbb3333cccc4444dddd \
+ -e DRONE_RUNNER_CAPACITY=10 \
+ -e DRONE_RUNNER_NAME=<该 runner 的名字> \
+ -e DRONE_RUNNER_LABELS=: \
+ -p :3000 \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ drone/drone-runner-docker
+ ```
+
+- 参考链接[https://docs.drone.io/runner/docker/installation/linux/](https://docs.drone.io/runner/docker/installation/linux/)
+
+## 安装 exec runner
+
+- 下载
+ ```bash
+ curl -L https://github.com/drone-runners/drone-runner-exec/releases/latest/download/drone_runner_exec_linux_amd64.tar.gz \
+ | tar zxf -C /usr/local/bin
+ chmod 0755 /usr/local/bin/drone-runner-exec
+ ```
+
+- 创建配置文件
+ ```bash
+ cat > /etc/drone-runner-exec/config <<-EOF
+ DRONE_RPC_PROTO=http
+ DRONE_RPC_HOST=:
+ DRONE_RPC_SECRET=1111aaaa2222bbbb3333cccc4444dddd
+ DRONE_HTTP_BIND=:
+ DRONE_LOG_FILE=/var/log/drone-runner-exec/log.txt
+ DRONE_RUNNER_LABELS=:
+ EOF
+ ```
+
+- 创建 systemd service 文件
+ ```bash
+ cat > /etc/systemd/system/drone-runner-exec.service <<-EOF
+ [Unit]
+ Description=Drone Exec Runner
+ ConditionFileIsExecutable=/usr/local/bin/drone-runner-exec
+
+ [Service]
+ ExecStart=/usr/local/bin/drone-runner-exec "service" "run" "--config" "/etc/drone-runner-exec/config"
+ StartLimitInterval=5
+ StartLimitBurst=10
+ Restart=on-failure
+ RestartSec=120
+
+ [Install]
+ WantedBy=multi-user.target
+ EOF
+ ```
+
+- 参考链接[https://docs.drone.io/runner/exec/installation/linux/](https://docs.drone.io/runner/exec/installation/linux/)
+
+# 使用 drone
+
+- 登陆 drone: http://:,此时会跳转到 gitea 登陆界面
+- 登陆成功后,浏览器返回 drone 首页,这里会显示我们创建/参与的 git 项目
+- 选择一个项目,点击对应的 "ACTIVATE",drone 会打开该项目的 "SETTINGS" 页面,这里一般无需设置,默认即可
+- 在该项目的 "ACTIVITY FEED" 页面会显示每次项目提交后触发的 CI/CD 流程
+- 编辑该项目代码,在项目根目录下创建文件 .drone.yml,内容如下
+ - docker pipeline 示例,详细参考[https://docs.drone.io/pipeline/docker/overview/](https://docs.drone.io/pipeline/docker/overview/)
+ ```yaml
+ ---
+ kind: pipeline
+ type: docker
+ name: default
+
+ steps:
+ - name: greeting
+ image: golang:1.12
+ commands:
+ - go build
+ - go test
+
+ node:
+ :
+ ```
+
+ - exec pipeline 示例,详情参考[https://docs.drone.io/pipeline/exec/overview/](https://docs.drone.io/pipeline/exec/overview/)
+ ```yaml
+ ---
+ kind: pipeline
+ type: exec
+ name: default
+
+ platform:
+ os: linux
+ arch: amd64
+
+ steps:
+ - name: greeting
+ commands:
+ - echo hello world
+
+ node:
+ :
+ ```
+
+ - 其他 pipeline 参考[https://docs.drone.io/pipeline/overview/](https://docs.drone.io/pipeline/overview/)
+
+# 适配 sonarqube
+
+- 下载镜像
+ ```bash
+ docker pull sonarqube
+ ```
+
+- 启动 sonarqube 容器
+ ```bash
+ docker run -d \
+ --name sonarqube \
+ -p :9000 \
+ -v sonarqube_data:/opt/sonarqube/data \
+ -v sonarqube_extension:/opt/sonarqube/extensions \
+ -v sonarqube_log:/opt/sonarqube/logs \
+ sonarqube
+ ```
+
+- 登陆 sonarquebe: http://:,创建用户,获取 token
+- 在 gitea 对应项目根目录下创建 .drone.yml,内容如下
+ ```yaml
+ kind: pipeline
+ type: docker
+ name: 代码分析
+
+ steps:
+ - name: 代码分析
+ image: aosapps/drone-sonar-plugin
+ settings:
+ sonar_host: http://:
+ sonar_token:
+
+ node:
+ role: sonarqube
+ ```
+
+- 在 gitea 对应项目根目录下创建 sonar-project.properties,内容如下
+ ```
+ sonar.projectKey={项目名称}
+ sonar.sources=.
+ ```
+
+- 提交代码,在 drone 中对应项目的 "ACTIVITY FEED" 页面下查看 pipeline 执行过程
+- pipeline 执行完成后,浏览器打开 http://:,即可查看刚检测完成的项目
+- sonarqube pipeline 参考[https://hub.docker.com/r/aosapps/drone-sonar-plugin](https://hub.docker.com/r/aosapps/drone-sonar-plugin)
+- sonarqube 配置参考[https://docs.sonarqube.org/latest/analysis/analysis-parameters/](https://docs.sonarqube.org/latest/analysis/analysis-parameters/)
+
diff --git a/content/post/elasticsearch.md b/content/post/elasticsearch.md
new file mode 100644
index 0000000..5ba771e
--- /dev/null
+++ b/content/post/elasticsearch.md
@@ -0,0 +1,187 @@
+---
+title: "Elasticsearch 笔记"
+date: 2019-10-30T11:49:53+08:00
+lastmod: 2019-10-30T11:49:53+08:00
+tags: ["elasticsearch"]
+categories: ["database"]
+---
+
+# 索引
+- 查看某节点的全部索引
+ ```bash
+ curl http://127.0.0.1:9200/_cat/indices?v
+ ```
+- 新建 index
+ ```bash
+ curl -X PUT http://127.0.0.1:9200/index_name
+ ```
+- 删除 index
+ ```bash
+ curl -X DELETE http://127.0.0.1:9200/index_name
+ ```
+
+# 记录
+- 新增记录(指定记录id)
+ ```bash
+ curl -X PUT -H "Content-Type: application/json" http://127.0.0.1:9200/index_name/doc_id -d '
+ {
+ "aa": "11",
+ "bb": "22"
+ }'
+ ```
+- 新增记录(不指定记录id)
+ ```bash
+ curl -X POST -H "Content-Type: application/json" http://127.0.0.1:9200/index_name -d '
+ {
+ "aa": "11",
+ "bb": "22"
+ }'
+ ```
+- 查看记录
+ ```bash
+ curl http://127.0.0.1:9200/index_name/doc_id?pretty=true
+ ```
+- 删除记录
+ ```bash
+ curl -X DELETE http://127.0.0.1:9200/index_name/doc_id
+ ```
+- 更新记录
+ ```bash
+ curl -X PUT -H "Content-Type: application/json" http://127.0.0.1:9200/index_name/doc_id -d '
+ {
+ "aa": "33",
+ "bb": "44"
+ }'
+ ```
+
+# 查询
+- 查询所有记录
+ ```bash
+ curl http://127.0.0.1:9200/index_name/_search
+ ```
+- 查询匹配
+ ```bash
+ curl -H "Content-Type: application/json" http://127.0.0.1:9200/index_name/_search -d '
+ {
+ "query": {"match": {"key_name": "value_pattern"}}
+ }'
+ ```
+- 从位置2(默认0)开始查询8(默认10)条记录
+ ```bash
+ curl -H "Content-Type: application/json" http://127.0.0.1:9200/index_name/_search -d '
+ {
+ "query": {"match": {"key_name": "value_pattern"}},
+ "from": 2,
+ "size": 8
+ }'
+ ```
+- 逻辑 or 查询
+ ```bash
+ curl -H "Content-Type: application/json" http://127.0.0.1:9200/index_name/_search -d '
+ {
+ "query": {"match": {"key_name": "value_pattern_1 value_pattern_2"}}
+ }'
+ ```
+- 逻辑 and 查询
+ ```bash
+ curl -H "Content-Type: application/json" http://127.0.0.1:9200/index_name/_search -d '
+ {
+ "query": {
+ "bool": {
+ "must": [
+ {"match": {"key_name": "value_pattern_1"}},
+ {"match": {"key_name": "value_pattern_2"}}
+ ]
+ }
+ }
+ }'
+ ```
+- 区间查询
+ ```bash
+ set -euo pipefail
+ export START_TIME="$(date +%s -d $1)"
+ export END_TIME="$(date +%s -d $2)"
+ curl -s -H "Content-Type: application/json" -o result.txt \
+ http://127.0.0.1:9200/wangmei_raw/_search?pretty -d @- <] --list-all
+ ```
+
+# 源地址(source)
+- 列出指定zone的所有绑定的source地址
+ ```bash
+ firewall-cmd [--permanent] [--zone=zone] --list-sources
+ ```
+- 查询指定zone是否跟指定source地址进行了绑定
+ ```bash
+ firewall-cmd [--permanent] [--zone=zone] --query-source=ip[/mask]
+ ```
+- 用于将一个source地址绑定到指定的zone(只可绑定一次,第二次绑定到不同的zone会报错)
+ ```bash
+ firewall-cmd [--permanent] [--zone=zone] --add-source=ip[/mask]
+ ```
+- 改变source地址所绑定的zone,如果原来没有绑定则进行绑定
+ ```bash
+ firewall-cmd [--permanent] [--zone=zone] --change-source=ip[/mask]
+ ```
+- 删除source地址跟zone的绑定
+ ```bash
+ firewall-cmd [--permanent] [--zone=zone] --remove-source=ip[/mask]
+ ```
+
+# 网卡(interface)
+- 获取网卡所在的 zone
+ ```bash
+ firewall-cmd --get-zone-of-interface=
+ ```
+- 增加网卡到 zone
+ ```bash
+ firewall-cmd [--zone=] --add-interface=
+ ```
+- 修改网卡到 zone
+ ```bash
+ firewall-cmd [--zone=] --change-interface=
+ ```
+- 从 zone 中删除网卡
+ ```bash
+ firewall-cmd [--zone=] --remove-interface=
+ ```
+- 查看 zone 中是否包含某网卡
+ ```bash
+ firewall-cmd [--zone=] --query-interface=
+ ```
+# target
+- 默认可以取四个值: default、ACCEPT、%%REJECT%%、DROP
+- 查看 taget
+ ```bash
+ firewall-cmd --permanent [--zone=zone] --get-target
+ ```
+- 设置 target
+ ```bash
+ firewall-cmd --permanent [--zone=zone] --set-target=target
+ ```
+- **必须使用参数 --permanent**,而且使用 firewall-cmd 命令不能直接生效,需 reload
+
+# 服务(service)
+- 查看支持的 service
+ ```bash
+ firewall-cmd --get-services [--permanent]
+ ```
+- 查看 zone 启动的 service
+ ```bash
+ firewall-cmd [--zone=] --list-services
+ ```
+- 在 zone 中启动 service
+ ```bash
+ firewall-cmd [--zone=] --add-service= \
+ [ --permanent | --timeout= ]
+ ```
+- 禁用 zone 中的 service
+ ```bash
+ firewall-cmd [--zone=] --remove-service= [--permanent]
+ ```
+- 查看 zone 中是否启动 service
+ ```bash
+ firewall-cmd [--zone=] --query-service=
+ ```
+
+# 端口和协议组合
+- 查看配置的全部端口规则
+ ```bash
+ firewall-cmd [--permanent] [--zone=zone] --list-ports
+ ```
+- 启动 zone 中指定协议的端口
+ ```bash
+ firewall-cmd [--zone=] --add-port=[-]/ \
+ [ --permanent | --timeout= ]
+ ```
+- 禁用 zone 中指定协议的端口
+ ```bash
+ firewall-cmd [--zone=] --remove-port=[-]/ [--permanent]
+ ```
+- 查看 zone 中指定协议的端口
+ ```bash
+ firewall-cmd [--zone=] --query-port=[-]/ [--permanent]
+ ```
+
+# ICMP
+- 查看支持的 icmp 类型
+ ```bash
+ firewall-cmd --get-icmptypes [--permanent]
+ ```
+- 查看全部 icmp 阻塞规则
+ ```bash
+ firewall-cmd [--permanent] [--zone=zone] --list-icmp-blocks
+ ```
+- 启动 zone 中的 icmp 阻塞
+ ```bash
+ firewall-cmd [--zone=] --add-icmp-block= \
+ [ --permanent | --timeout=seconds ]
+ ```
+- 禁用 zone 中的 icmp 阻塞
+ ```bash
+ firewall-cmd [--zone=] --remove-icmp-block= [--permanent]
+ ```
+- 查询 zone 的 icmp 阻塞
+ ```bash
+ firewall-cmd [--zone=] --query-icmp-block= [--permanent]
+ ```
+
+# IPV4 源地址转换
+- 启动 zone 中 ipv4 源地址转换
+ ```bash
+ firewall-cmd [--zone=] --add-masquerade \
+ [ --permanent | --timeout=seconds ]
+ ```
+- 禁用 zone 中 ipv4 源地址转换
+ ```bash
+ firewall-cmd [--zone=] --remove-masquerade [--permanent]
+ ```
+- 查看 zone 中 ipv4 源地址转换
+ ```bash
+ firewall-cmd [--zone=] --query-masquerade [--permanent]
+ ```
+
+# 端口转发
+- 查看全部端口转发规则
+ ```bash
+ firewall-cmd [--permanent] [--zone=zone] --list-forward-ports
+ ```
+- 启动 zone 中端口转发
+ ```bash
+ firewall-cmd [--zone=] --add-forward-port=port=[-]:proto= \
+ { :toport=[-] | :toaddr= | :toport=[-]:toaddr= } \
+ [ --permanent | --timeout=seconds ]
+ ```
+- 禁用 zone 中端口转发
+ ```bash
+ firewall-cmd [--zone=] --remove-forward-port=port=[-]:proto= \
+ { :toport=[-] | :toaddr= | :toport=[-]:toaddr= } \
+ [--permanent]
+ ```
+- 查看 zone 中端口转发
+ ```bash
+ firewall-cmd [--zone=] --query-forward-port=port=[-]:proto= \
+ { :toport=[-] | :toaddr= | :toport=[-]:toaddr= } \
+ [--permanent]
+ ```
+
+# Rich Rules
+- 通用结构
+ ```bash
+ firewall-cmd [--zone=] [ --permanent | --timeout=seconds ] \
+ <--add|--remove>-rich-rule='rule [family= \
+ source address= [invert=] \
+ destination address= [invert=] \
+ service name= \
+ port port=[-] \
+ protocol= \
+ icmp-block name= \
+ masquerade \
+ forward-port port=[-] protocol= to-port=[-] to-address= \
+ log [prefix=] [level=] [limit value=] \
+ accept|reject [type=]|drop'
+ ```
+- 查看全部 rich rule
+ ```bash
+ firewall-cmd [--permanent] [--zone=zone] --list-rich-rules
+ ```
+- 具体参数解释见系统 man 手册
+ ```bash
+ man firewalld.richlanguage 5
+ ```
+
+# 应急模式(panic)
+- 启动 panic,即断网
+ ```bash
+ firewall-cmd --panic-on
+ ```
+- 关闭 panic,即联网
+ ```bash
+ firewall-cmd --panic-off
+ ```
+- 查询应急模式
+ ```bash
+ firewall-cmd --query-panic
+ ```
+
+# 重新载入(reload)
+- 重新载入防火墙,不中断用户连接
+ ```bash
+ firewall-cmd --reload
+ ```
+- 重新载入防火墙并中断用户连接
+ ```bash
+ firewall-cmd --complete-reload
+ ```
+
+# 备注
+- 参数 --timeout 是让规则生效一段时间,过期自动删除,不能与 --permanent 一起使用
+
diff --git a/content/post/ftp.md b/content/post/ftp.md
new file mode 100644
index 0000000..2b94c1b
--- /dev/null
+++ b/content/post/ftp.md
@@ -0,0 +1,186 @@
+---
+title: "Ftp 笔记"
+date: 2019-10-30T17:48:02+08:00
+lastmod: 2019-10-31T21:49:00+08:00
+tags: ["ftp", "vsftp"]
+categories: ["storage"]
+---
+
+# 环境
+- CentOS7
+- vsftpd
+- 关闭 selinux
+
+# 安装 vsftpd 服务
+```bash
+yum install vsftpd
+```
+
+# 常用客户端
+- ftp
+- lftp
+- curl
+
+# 主动模式
+- 建立控制命令连接
+ - 客户端连接服务端 21 号端口
+
+- 建立数据传送连接
+ - 客户端在本地监听一个端口(大于 1024 ),并通过 PORT 命令通知服务端
+ - 服务端从 20 端口连接客户端正在监听的端口,向客户端发送数据
+
+- 相关配置
+ ```ini
+ # 开启主动模式
+ pasv_enable = no
+ ```
+
+# 被动模式
+- 建立控制命令连接
+ - 客户端连接服务端 21 号端口
+
+- 建立数据传送连接
+ - 服务端在本地再次监听一个端口(大于 1024),并通过 PASV 命令通知客户端
+ - 客户端连接服务端新监听的端口,下载服务端数据
+
+- 相关配置
+ ```ini
+ # 开启被动模式
+ pasv_enable = yes
+ # 数据连接可以使用的端口范围的最大端口,0 表示任意端口,默认值为0
+ pasv_min_port=30000
+ # 数据连接可以使用的端口范围的最小端口,0 表示任意端口,默认值为0
+ pasv_max_port=30999
+ ```
+
+# 匿名用户配置
+```ini
+# 控制是否允许匿名用户登入
+# 匿名用户使用的登陆名为 ftp 或 anonymous,口令为空
+# 匿名用户不能离开匿名用户家目录/var/ftp,且只能下载不能上传
+anonymous_enable=YES/NO(YES)
+
+# 匿名登入时,不会询问密码
+no_anon_password=YES/NO(NO)
+
+# 定义匿名登入的使用者名称,默认值为ftp
+ftp_username=ftp
+
+# 是否允许登陆用户有写权限,属于全局设置
+write_enable=YES/NO(YES)
+
+# 使用匿名登入时,所登入的目录,默认值为/var/ftp
+# 注意ftp目录不能是777的权限属性,即匿名用户的家目录不能有777的权限
+anon_root=/var/ftp
+
+# 是否允许匿名者有上传文件(非目录)的权限
+# 只有在write_enable=YES时,此项才有效
+# 匿名用户必须要有对上层目录的写入权
+anon_upload_enable=YES/NO(NO)
+
+# 是否允许匿名者下载可阅读的档案
+anon_world_readable_only=YES/NO(YES)
+
+# 是否允许匿名者有新增目录的权限
+# 只有在write_enable=YES时,此项才有效
+# 匿名用户必须要有对上层目录的写入权
+anon_mkdir_write_enable=YES/NO(NO)
+
+# 是否允许匿名入者拥有其他权限,譬如删除或者重命名
+# 如果anon_upload_enable=NO,则匿名用户不能上传文件,但可以删除或者重命名已经存在的文件
+# 如果anon_mkdir_write_enable=NO,则匿名用户不能上传或者新建文件夹,但可以删除或者重命名已经存在的目录
+anon_other_write_enable=YES/NO(NO)
+
+# 是否改变匿名用户上传文件(非目录)的属主
+chown_uploads=YES/NO(NO)
+
+# 设置匿名用户上传文件(非目录)的属主名
+# 建议不要设置为root
+chown_username=username
+
+# 设置匿名登入者新增或上传档案时的 umask 值,默认值为 077
+anon_umask=077
+```
+
+# 配置
+## 常用配置
+```ini
+#允许匿名用户登陆
+anonymous_enable=YES
+#允许本地用户登陆
+local_enable=YES
+#允许登陆用户写可访问的目录或文件
+write_enable=YES
+#指定用户登陆后直接进入系统的/mnt目录
+local_root=/mnt
+chroot_list_enable=YES
+#限定登陆用户可访问的目录只有自己的家目录或指定的local_root目录
+chroot_list_file=/etc/vsftpd/chroot_list
+```
+
+## 允许 vsftpd 匿名用户上传和下载
+- 创建匿名用户登陆目录
+ ```bash
+ mkdir -p /var/ftp/pub
+ chown -R ftp.ftp /var/ftp/pub
+ chmod o+w /var/ftp/pub
+ ```
+
+- 修改 vsftpd.conf
+ ```ini
+ #允许匿名用户登录FTP
+ anonymous_enable=YES
+ #设置匿名用户的登录目录(如需要,需自己添加并修改)
+ anon_root=/var/ftp/pub
+ #打开匿名用户的上传权限
+ anon_upload_enable=YES
+ #打开匿名用户创建目录的权限
+ anon_mkdir_write_enable=YES
+ #打开匿名用户删除和重命名的权限(如需要,需自己添加)
+ anon_other_write_enable=YES
+ anon_umask=022
+ ```
+
+## 本地用户登陆
+- 修改 vsftpd.conf
+ ```
+ # 不允许匿名用户登入
+ anonymous_enable=no
+ # 允许本地用户登入
+ local_enable=YES
+ # 当本地用户登入时,将被更换到定义的目录下
+ # 默认值为各用户的家目录
+ local_root=/home/username
+ # 是否允许登陆用户有写权限
+ # 属于全局设置,默认值为YES。
+ write_enable=YES/NO(YES)
+ # 本地用户新增档案时的 umask 值,默认值为077
+ local_umask=022
+ # 本地用户上传档案后的档案权限
+ # 与chmod所使用的数值相同,默认值为0666
+ file_open_mode=0755
+ # 指定用户列表文件中的用户不允许切换到上级目录
+ chroot_local_user=YES
+ ```
+
+## 创建 ftp 专用账户
+- 创建用户 ftpuser1
+ ```bash
+ useradd -s /sbin/nologin ftpuser1
+ passwd ftpuser1
+ ```
+
+- 修改 vsftpd.conf
+ ```ini
+ anonymous_enable=no
+ local_enable=YES
+ local_root=/home/ftpuser
+ write_enable=YES
+ local_umask=022
+ file_open_mode=0755
+ chroot_local_user=YES
+ ```
+
+# 参考
+- [vsftpd 详细配置](http://vsftpd.beasts.org/vsftpd_conf.html)
+
diff --git a/content/post/git-http.md b/content/post/git-http.md
new file mode 100644
index 0000000..7022d9d
--- /dev/null
+++ b/content/post/git-http.md
@@ -0,0 +1,114 @@
+---
+title: "搭建 Git http 服务"
+date: 2019-10-30T17:35:56+08:00
+lastmod: 2019-10-30T17:35:56+08:00
+tags: ["git", "nginx", "http", "fcgiwrap", "spawn-fcgi"]
+categories: ["dev/ops"]
+---
+
+# 环境
+- Centos/Redhat 6
+- 安装好 git
+
+# 安装 fcgiwrap
+- 参考 https://github.com/gnosek/fcgiwrap
+
+
+# 安装 spawn-fcgi
+- 参考 https://github.com/lighttpd/spawn-fcgi
+
+
+# 编辑 spawn-fcgi/sbin/fcgiwrap 开机启动脚本:
+```bash
+#! /bin/sh
+DESC="fcgiwrap daemon"
+DEAMON=/opt/spawn-fcgi/bin/spawn-fcgi
+PIDFILE=/tmp/spawn-fcgi.pid
+# fcgiwrap socket
+FCGI_SOCKET=/tmp/fcgiwrap.socket
+# fcgiwrap 可执行文件
+FCGI_PROGRAM=/opt/fcgiwrap/sbin/fcgiwrap
+# fcgiwrap 执行的用户和用户组
+FCGI_USER=nobody
+FCGI_GROUP=nobody
+FCGI_EXTRA_OPTIONS="-M 0770"
+OPTIONS="-u $FCGI_USER -g $FCGI_GROUP -s $FCGI_SOCKET -S $FCGI_EXTRA_OPTIONS -F 1 -P $PIDFILE -- $FCGI_PROGRAM"
+do_start() {
+ $DEAMON $OPTIONS || echo -n "$DESC already running"
+}
+do_stop() {
+ kill -INT `cat $PIDFILE` || echo -n "$DESC not running"
+}
+case "$1" in
+ start)
+ echo -n "Starting $DESC: $NAME"
+ do_start
+ echo "."
+ ;;
+ stop)
+ echo -n "Stopping $DESC: $NAME"
+ do_stop
+ echo "."
+ ;;
+ restart)
+ echo -n "Restarting $DESC: $NAME"
+ do_stop
+ do_start
+ echo "."
+ ;;
+ *)
+ echo "Usage: $SCRIPTNAME {start|stop|restart}" >&2
+ exit 3
+ ;;
+esac
+exit 0
+```
+
+# 启动 fcgiwrap
+```bash
+chmod 0755 /opt/spawn-fcgi/sbin/fcgiwrap
+/opt/spawn-fcgi/sbin/fcgiwrap start
+```
+
+# 安装 nginx
+- 参考 http://nginx.org/en/download.html,启动 nginx
+
+# 在 nginx 前端目录 html 下创建 git 仓库目录 nginx/html/git/
+```bash
+mkdir /opt/nginx/html/git/
+chown nobody.nobody /opt/nginx/html/git/ -R
+```
+
+# 配置 nginx 的 git 服务
+```nginx
+# 在 server section 中添加 git
+location ~ /git(/.*) {
+ gzip off;
+ fastcgi_pass unix:/tmp/fcgiwrap.socket;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME /usr/libexec/git-core/git-http-backend;
+ fastcgi_param GIT_HTTP_EXPORT_ALL "";
+ fastcgi_param GIT_PROJECT_ROOT /opt/nginx/html/git;
+ fastcgi_param PATH_INFO $1;
+ fastcgi_param REMOTE_USER $remote_user;
+ client_max_body_size 500m;
+}
+```
+
+# 重新加载配置文件
+```bash
+/opt/nginx/sbin/nginx -s reload
+```
+
+# 测试,在仓库目录下新建一个空repo
+```bash
+cd /opt/nginx/html/git/
+git init --bare test-repo
+chown nobody.nobody test-repo -R
+cd test-repo
+git config http.reveivepack true
+```
+
+# 完成
+- 在另一台服务器中可以顺利的进行 clone、push 及 pull 等操作。
+
diff --git a/content/post/gnome.md b/content/post/gnome.md
new file mode 100644
index 0000000..ed49bf1
--- /dev/null
+++ b/content/post/gnome.md
@@ -0,0 +1,119 @@
+---
+title: "Gnome 笔记"
+date: 2019-10-30T11:34:41+08:00
+lastmod: 2020-03-14T16:10:00+08:00
+tags: ["gnome", "desktop", "桌面"]
+categories: ["os"]
+---
+
+# 常用设置
+```bash
+# 关闭左上角热响应
+gsettings set org.gnome.desktop.interface enable-hot-corners false
+
+# 缩放系统字体
+gsettings set org.gnome.desktop.interface text-scaling-factor 1.5
+
+# monospace 字体
+gsettings set org.gnome.desktop.interface monospace-font-name 'YaHei Consolas Hybrid 15'
+
+# gtk 主题
+gsettings set org.gnome.desktop.interface gtk-theme 'Vertex-Dark'
+
+# gnome-shell 主题
+gsettings set org.gnome.shell.extensions.user-theme name 'Vertex'
+
+# 图标主题
+gsettings set org.gnome.desktop.interface icon-theme 'Faenza'
+
+# 调整 gnome3 桌面(包括 gdm )放大级别
+# 0 系统自动缩放
+# n > 0 放大 n 倍
+gsettings set org.gnome.desktop.interface scaling-factor 1
+
+# 启用用户扩展
+gsettings set org.gnome.shell disable-user-extensions false
+gsettings set org.gnome.shell enabled-extensions "['user-theme@gnome-shell-extensions.gcampax.github.com', 'workspace-indicator@gnome-shell-extensions.gcampax.github.com', 'dash-to-panel@jderose9.github.com']"
+
+# 工作区
+gsettings set org.gnome.mutter dynamic-workspaces false
+gsettings set org.gnome.desktop.wm.preferences num-workspaces 4
+gsettings set org.gnome.desktop.wm.preferences workspace-names "['乾坤', '巽震', '坎离', '艮兑']"
+
+# 窗口按钮
+gsettings set org.gnome.desktop.wm.preferences button-layout 'appmenu:minimize,maximize,close'
+```
+
+# 在 "活动" 中创建 "文件夹"
+```bash
+# 创建文件夹
+gsettings set org.gnome.desktop.app-folders folder-children "['Office','VirtualBox']"
+
+# 指定文件夹名字
+gsettings set org.gnome.desktop.app-folders.folder:/org/gnome/desktop/app-folders/folders/Office/ name "Office"
+gsettings set org.gnome.desktop.app-folders.folder:/org/gnome/desktop/app-folders/folders/VirtualBox/ name "VirtualBox"
+
+# 指定文件夹包含的应用类别
+gsettings set org.gnome.desktop.app-folders.folder:/org/gnome/desktop/app-folders/folders/Office/ categories "['Office']"
+gsettings set org.gnome.desktop.app-folders.folder:/org/gnome/desktop/app-folders/folders/VirtualBox/ categories "['Emulator']"
+```
+
+# 快捷键
+```bash
+gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-1 "[]"
+gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-last "[]"
+gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-up "['Up']"
+gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-down "['Down']"
+gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-left "['Left']"
+gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-right "['Right']"
+
+gsettings set org.gnome.desktop.wm.keybindings switch-applications "[]"
+gsettings set org.gnome.desktop.wm.keybindings switch-applications-backward "[]"
+gsettings set org.gnome.desktop.wm.keybindings switch-windows "['Tab']"
+gsettings set org.gnome.desktop.wm.keybindings switch-windows-backward "['Tab']"
+gsettings set org.gnome.shell.window-switcher current-workspace-only true
+gsettings set org.gnome.shell.window-switcher app-icon-mode both
+
+gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-down "['Down']"
+gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-up "['Up']"
+gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-last "[]"
+
+gsettings set org.gnome.desktop.wm.keybindings move-to-monitor-up "[]"
+gsettings set org.gnome.desktop.wm.keybindings move-to-monitor-right "[]"
+gsettings set org.gnome.desktop.wm.keybindings move-to-monitor-left "[]"
+gsettings set org.gnome.desktop.wm.keybindings move-to-monitor-down "[]"
+
+gsettings set org.gnome.desktop.wm.keybindings show-desktop "['d']"
+gsettings set org.gnome.desktop.wm.keybindings toggle-maximized "['Up']"
+gsettings set org.gnome.desktop.wm.keybindings minimize "['Down']"
+gsettings set org.gnome.desktop.wm.keybindings maximize "[]"
+```
+
+# 在 CentOS6 下安装 gnome 桌面
+```bash
+yum groupinstall 'X Window System'
+yum groupinstall Desktop
+sed -i '/^id/id:5:initdefault:' /etc/inittab
+```
+
+# 在 CentOS7 下安装 gnome3 桌面
+```bash
+yum groupinstall 'X Window System'
+yum groupinstall 'Gnome Desktop'
+systemctl set-default graphical.target
+systemctl enable gdm
+# 创建一个可登陆的普通用户
+```
+
+# 在 gdm 中隐藏用户名
+- 修改文件/etc/gdm/gdm.schemas,找到这一段:
+ ```xml
+
+ greeter/Exclude
+ s
+ bin,root, daemon,adm,lp,sync,shutdown,halt,mail,news,uucp,operator,
+ nobody,nobody4,noaccess,postgres,pvm,rpm,nfsnobody,pcap
+
+ ```
+- 将用户名添加在与之间即可,用逗号隔开,保存退出即可。
+
diff --git a/content/post/grub.md b/content/post/grub.md
new file mode 100644
index 0000000..c95d873
--- /dev/null
+++ b/content/post/grub.md
@@ -0,0 +1,92 @@
+---
+title: "Grub 笔记"
+date: 2019-10-30T17:39:58+08:00
+lastmod: 2019-10-30T17:39:58+08:00
+tags: ["grub"]
+categories: ["OS"]
+---
+
+# 启动 archlinux 镜像 64位系统
+```
+menuentry "Archlinux-ISO-x86-64" --class iso {
+ set isofile=""
+ set partition=""
+ loopback loop (hd0,$partition)$isofile
+ linux (loop)/arch/boot/x86_64/vmlinuz archisolabel=ARCH_ISO_X86-64 img_dev=/dev/sda$partition img_loop=$isofile earlymodules=loop
+ initrd (loop)/arch/boot/x86_64/archiso.img
+}
+```
+
+# 启动 archlinux 镜像 32位系统
+```bash
+menuentry "Archlinux-ISO-i686" --class iso {
+ set isofile=""
+ set partition="2"
+ loopback loop (hd0,$partition)$isofile
+ linux (loop)/arch/boot/i686/vmlinuz archisolabel=ARCH_ISO_I686 img_dev=/dev/sda$partition img_loop=$isofile earlymodules=loop
+ initrd (loop)/arch/boot/i686/archiso.img
+}
+```
+
+# 启动 ubuntu 镜像
+```bash
+menuentry "ubuntu-ISO" {
+ set isofile=""
+ loopback loop (hd0,1)$isofile
+ linux (loop)/casper/vmlinuz.efi boot=casper iso-scan/filename=$isofile quiet noeject noprompt splash --
+ initrd (loop)/casper/initrd.lz
+}
+```
+
+# 启动安装在第一块硬盘第一个mbr分区的 win7 系统
+```bash
+menuentry "Microsoft Windows Vista/7/8/8.1/10 BIOS-MBR" {
+ insmod part_msdos
+ insmod ntfs
+ insmod search_fs_uuid
+ insmod ntldr
+ #"9AB0287EB028634D" 是 win7 系统分区的 uuid
+ search --fs-uuid --set=root --hint-bios=hd0,msdos1 --hint-efi=hd0,msdos1 --hint-baremetal=ahci0,msdos1 9AB0287EB028634D
+ ntldr /bootmgr
+}
+```
+
+# 启动安装在第一块硬盘的第一个gpt分区的 Win10 EFI
+```bash
+menuentry "Microsoft Windows 10" {
+ insmod part_gpt
+ insmod fat
+ insmod search_fs_uuid
+ insmod chain
+ #"0007-C871" 是 win10 EFI 分区的 uuid
+ search --fs-uuid --set=root --hint-bios=hd0,gpt1 --hint-efi=hd0,gpt1 --hint-baremetal=ahci0,gpt1 0007-C871
+ chainloader /EFI/Microsoft/Boot/bootmgfw.efi
+}
+```
+
+# 关机
+```bash
+menuentry "System shutdown" {
+ echo "System shutting down..."
+ halt
+}
+```
+
+# 重启
+```bash
+menuentry "System restart" {
+ echo "System rebooting..."
+ reboot
+}
+```
+
+# UEFI 配置
+```bash
+menuentry "Firmware setup" {
+ fwsetup
+}
+```
+
+# 详细使用参考
+- [GRUB-ArchWiki](https://wiki.archlinux.org/index.php/GRUB)
+
diff --git a/content/post/haproxy.md b/content/post/haproxy.md
new file mode 100644
index 0000000..b5637ef
--- /dev/null
+++ b/content/post/haproxy.md
@@ -0,0 +1,175 @@
+---
+title: "Haproxy 笔记"
+date: 2019-10-30T11:40:20+08:00
+lastmod: 2019-10-30T11:40:20+08:00
+tags: ["haproxy", "高可用", "负载均衡"]
+categories: ["ha/lb"]
+---
+
+# CentOS7 下安装
+- CentOS7 自带的 haproxy 版本太低,这里通过 cheese 源安装最新版本
+- 安装 cheese repo,详细参考[这里](http://www.nosuchhost.net/~cheese/fedora/packages/epel-7/x86_64/cheese-release.html)
+ ```bash
+ wget http://www.nosuchhost.net/~cheese/fedora/packages/epel-7/x86_64/cheese-release-7-1.noarch.rpm
+ rpm -Uvh cheese-release-7-1.noarch.rpm
+ ```
+- 安装 haproxy
+ ```bash
+ yum install haproxy
+ ```
+- 修改 sysctl.conf
+ ```bash
+ cat >> /etc/sysctl.conf <<-END
+ net.ipv4.ip_forward=1
+ net.ipv4.tcp_syncookies = 1
+ net.ipv4.tcp_tw_reuse = 1
+ net.ipv4.tcp_tw_recycle = 1
+ net.ipv4.tcp_fin_timeout = 8
+ END
+ sysctl -p
+ ```
+- 禁用 selinux
+ ```bash
+ sed -i '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
+ setenforce 0
+ ```
+
+# 全局配置
+```
+global
+ log 127.0.0.1 local2 info
+ chroot /var/lib/haproxy # 如果需要外部检查脚本,则需注释该行
+ #external-check # 如果需要外部检查脚本,则取消注释
+ pidfile /var/run/haproxy.pid
+ maxconn 102400
+ user haproxy
+ group haproxy
+ daemon
+ stats socket /var/lib/haproxy/stats
+```
+
+# 默认配置
+```
+defaults
+ log global
+ option dontlognull
+ option redispatch
+ option abortonclose
+ timeout check 8s
+```
+
+# tcp 连接多个 ceph-radosgw
+```
+frontend ceph-radosgw
+ bind *:7480
+ timeout client 8s
+ default_backend ceph-radosgw
+backend ceph-radosgw
+ mode tcp
+ balance roundrobin
+ timeout connect 8s
+ timeout server 8s
+ retries 2
+ #option external-check
+ #external-check command /var/lib/haproxy/health_check.sh
+ server cpeh240 10.9.10.234:7480 check
+ server ceph241 10.9.10.235:7480 check
+ server ceph243 10.9.10.236:7480 check
+```
+
+# tcp 连接 mysql galera cluster
+```
+frontend mysql
+ bind *:3306
+ timeout client 1800s
+ default_backend mysql
+backend mysql
+ balance source
+ option tcpka
+ timeout connect 8s
+ timeout server 1800s
+ retries 2
+ server mysql231 10.9.10.231:3306 check inter 4s
+ server mysql232 10.9.10.232:3306 check inter 4s
+ server mysql233 10.9.10.233:3306 check inter 4s
+```
+
+# tcp 连接 redis 主库
+```
+frontend redis
+ bind *:6379
+ timeout client 1800s
+ default_backend redis
+backend redis
+ balance roundrobin
+ timeout connect 8s
+ timeout server 1800s
+ retries 2
+ option tcp-check
+ tcp-check connect
+ tcp-check send PING\r\n
+ tcp-check expect string +PONG
+ tcp-check send info\ replication\r\n
+ tcp-check expect string role:master
+ tcp-check send QUIT\r\n
+ tcp-check expect string +OK
+ server redis87 10.1.14.87:6379 check inter 4s
+ server redis88 10.1.14.88:6379 check inter 4s
+ server redis89 10.1.14.89:6379 check inter 4s
+```
+
+# 状态页面
+```
+listen admin_stats
+ bind 0.0.0.0:10080
+ mode http
+ maxconn 100
+ timeout client 1m
+ timeout connect 4s
+ timeout server 4s
+ stats refresh 30s
+ stats uri /
+ stats auth username:password
+ stats realm haproxy for private user, enter username/password
+ stats hide-version
+```
+
+# 通过 rsyslog 生成日志
+```bash
+sed -i -e '/ModLoad imudp/s/^#//' \
+ -e '/UDPServerRun 514/s/^#//' /etc/rsyslog.conf
+cat > /etc/rsyslog.d/haproxy.conf <
+ DAV svn
+ SVNListParentPath off
+ SVNPath /mnt/vdb1/svn_repos/test_prj/
+ #Satisfy Any
+ AuthzSVNAccessFile /mnt/vdb1/svn_repos/test_prj/conf/authz
+ Require valid-user
+
+ ```
+- 增加 apache 用户读写 test_prj 目录的权限
+ ```bash
+ usermod -a -G root apache
+ chmod -R g+w /mnt/vdb1/svn_repos/
+ ```
+- 重启 httpd 服务
+ ```bash
+ systemctl restart httpd
+ ```
+
+# Basic HTTP 认证
+- 生成密码文件(用户名是admin,密码是123456)
+ ```bash
+ htpasswd -c -m /etc/httpd/httpd.auth admin # 按提示输入密码
+ ```
+- 在 Location 中配置如下
+ ```xml
+
+ AuthType Basic
+ AuthName "提示信息"
+ AuthUserFile /etc/httpd/httpd.auth
+
+ ```
+
diff --git a/content/post/ip.md b/content/post/ip.md
new file mode 100644
index 0000000..cb1cb48
--- /dev/null
+++ b/content/post/ip.md
@@ -0,0 +1,33 @@
+---
+title: "IP 地址分类及内网 IP"
+date: 2019-10-29T18:49:31+08:00
+lastmod: 2019-10-29T18:49:31+08:00
+keywords: []
+description: ""
+tags: ["ip"]
+categories: ["network"]
+---
+
+# 私有 IP
+- A类: 10.0.0.1-10.255.255.254
+- B类: 172.16.0.1-172.31.255.254
+- C类: 192.168.0.1-192.168.255.254
+
+# 地址分类
+- A类: 一个A类IP地址仅使用第一个8位位组表示网络地址。剩下的3个8位位组表示主机地址。A类地址的第一个位总为0,这一点在数学上限制了A类地址的范围小于 127,127是64+32+16+8+4+2+1的和。最左边位表示128,在这里空缺。因此仅有127个可能的A类网络。A类地址后面的24位(3个点-十进制数)表示可能的主机地址,A类网络地址的范围从1.0.0.0到126.0.0.0。注意只有第一个8位位组表示网络地址,剩余的3个8位位组用于表示第一个8位位组所表示网络中惟一的主机地址,当用于描述网络时这些位置为0。注意技术上讲,127.0.0.0 也是一个A类地址,但是它已被保留作闭环(look back )测试之用而不能分配给一个网络。每一个A类地址能支持16777214个不同的主机地址,这个数是由2的24次方再减去2得到的。减2是必要的,因为 IP把全0保留为表示网络而全1表示网络内的广播地址。其中10.0.0.0 至10.255.255.255保留,作为局域网地址使用。
+- B类: 设计B类地址的目的是支持中到大型的网络。B类网络地址范围从128.1.0.0到191.254.0.0。B 类地址蕴含的数学逻辑是相当简单的。一个B类IP地址使用两个8位位组表示网络号,另外两个8位位组表示主机号。B类地址的第1个8位位组的前两位总置为 10,剩下的6位既可以是0也可以是1,这样就限制其范围小于等于191,由128+32+16+8+4+2+1得到。最后的16位( 2个8位位组)标识可能的主机地址。每一个B类地址能支持64534 个惟一的主机地址,这个数由2的16次方减2得到。B类网络仅有16382个。其中172.16.0.0至172.31.255.255保留作为局域网地址使用。
+- C类: C类地址用于支持大量的小型网络。这类地址可以认为与A类地址正好相反。A类地址使用第一个8位位组表示网络号,剩下的3个表示主机号,而C类地址使用三个8位位组表示网络地址,仅用一个8位位组表示主机号。C类地址的前3位数为110,前两位和为192(128+64),这形成了C类地址空间的下界。第三位等于十进制数32,这一位为0限制了地址空间的上界。不能使用第三位限制了此8位位组的最大值为255-32等于223。因此C类网络地址范围从 192.0.1.0 至223.255.254.0。最后一个8位位组用于主机寻址。每一个C类地址理论上可支持最大256个主机地址(0~255),但是仅有254个可用,因为0和255不是有效的主机地址。可以有2097150个不同的C类网络地址。其中192.168.0.0至192.168.255.255保留作为局域网地址使用。
+- D类: D类地址用于在IP网络中的组播( multicasting ,又称为多目广播)。D类地址的前4位恒为1110 ,预置前3位为1意味着D类地址开始于128+64+32等于224。第4位为0意味着D类地址的最大值为128+64+32+8+4+2+1为239,因此D类地址空间的范围从224.0.0.0到239. 255. 255.254。
+- E类: E类地址保留作研究之用。因此Internet上没有可用的E类地址。E类地址的前4位恒为1,因此有效的地址范围从240.0.0.0 至255.255.255.255。
+- 总的来说,ip地址分类由第一个八位组的值来确定。任何一个0到127 间的网络地址均是一个A类地址。任何一个128到191间的网络地址是一个B类地址。任何一个192到223 间的网络地址是一个C类地址。任何一个第一个八位组在224到239 间的网络地址是一个组播地址即D类地址。E类保留。
+
+# 特殊 IP
+- 127.0.0.0: 127是一个保留地址,该地址是指电脑本身,主要作用是预留下作为测试使用,用于网络软件测试以及本地机进程间通 信。在Windows系统下,该地址还有一个别名叫“localhost”,无论是哪个程序,一旦使用该地址发送数据,协议软件会立即返回,不进行任何网 络传输,除非出错,包含该网络号的分组是不能够出现在任何网络上的。
+- 10.x.x.x、172.16.x.x~172.31.x.x、192.168.x.x: 私有地址,这些地址被大量用于企业内部网络中。一些宽带路由器,也往往使用192.168.1.1作为缺省地址。私有网络由于不与外部互连,因而可能使 用随意的IP地址。保留这样的地址供其使用是为了避免以后接入公网时引起地址混乱。使用私有地址的私有网络在接入Internet时,要使用地址翻译 (nat),将私有地址翻译成公用合法地址。在Internet上,这类地址是不能出现的。
+- 0.0.0.0: 严格意义上来 说,0.0.0.0已经不是真正意义上的IP地址了。它表示的是这样一个集合,所有不清楚的主机和目的网络。这里的不清楚是指在本机的路由表里没有特定条 目指明如何到达。对本机来说,它就是一个收容所,所有不认识的三无人员,一律送进去。如果你在网络设置中设置了缺省网关,那么Windows系统就会自动 产生一个目地址为0.0.0.0的缺省路由。若IP地址全为0,也就是0.0.0.0,则这个IP地址在IP数据报中只能用作源IP地址,这发生在当设备启动时但又不知道自己的IP地址情况下。在使 用DHCP分配IP地址的网络环境中,这样的地址是很常见的。用户主机为了获得一个可用的IP地址,就给DHCP服务器发送IP分组,并用这样的地址作为 源地址,目的地址为255.255.255.255(因为主机这时还不知道DHCP服务器的IP地址)。
+- 255.255.255.255: 受限制的广播地址,对本机来说,这个地址指本网段内(同一 个广播域)的所有主机,该地址用于主机配置过程中IP数据包的目的地址,这时主机可能还不知道它所在网络的网络掩码,甚至连它的IP地址也还不知道。在任 何情况下,路由器都会禁止转发目的地址为受限的广播地址的数据包,这样的数据包仅会出现在本地网络中。
+- 224.0.0.1: 组播地址,注意它和广播的区别。从224.0.0.0到239.255.255.255都是这样的地址。224.0.0.1特指所有主 机,224.0.0.2特指所有路由器。这样的地址多用于一些特定的程序以及多媒体程序。如果你的主机开启了IRDP(Internet路由发现协议,使 用组播功能)功能,那么你的主机路由表中应该有这样一条路由。
+- 169.254.*.*: 如果你的主机使用了DHCP功能自动获 得一个IP地址,那么当你的DHCP服务器发生故障或响应时间太长而超出系统规定的一个时间,Windows系统会为你分配这样一个地址。如果你发现你的 主机IP地址是个诸如此类的地址,很不幸,十有八九是你的网络不能正常运行了。
+- 直接广播地址: 一个网络中的最后一个地址为直接广播地址,也就是HostID全为1的地址。主机使用这种地址把一个IP数据报发送到本地网段的所有设备上,路由器会转发这种数据报到特定网络上的所有主机。**注意:这个地址在IP数据报中只能作为目的地址。另外,直接广播地址使一个网段中可分配给设备的地址数减少了1个**。
+- NetID为0的IP地址: 当某个主机向同一网段上的其他主机发送报文时就可以使用这样的地址,分组也不会被路由器转发。比如12.12.12.0/24这个网络中的一台主机12.12.12.2/24在与同一网络中的另一台主机12.12.12.8/24通信时,目的地址可以是0.0.0.8。
+
diff --git a/content/post/ipv6.md b/content/post/ipv6.md
new file mode 100644
index 0000000..ed39581
--- /dev/null
+++ b/content/post/ipv6.md
@@ -0,0 +1,32 @@
+---
+title: "Ipv6 笔记"
+date: 2019-10-29T21:07:06+08:00
+lastmod: 2019-10-29T21:07:06+08:00
+keywords: []
+tags: ["ipv6", "centos"]
+categories: ["network"]
+---
+
+# CentOS7 禁用 ipv6
+## 方法1
+- 编辑 /etc/sysctl.conf,增加如下内容
+ ```
+ net.ipv6.conf.all.disable_ipv6 =1
+ net.ipv6.conf.default.disable_ipv6 =1
+ ```
+- 如果你想要为特定的网卡禁止IPv6,比如,对于enp0s3,添加下面的行
+ ```
+ net.ipv6.conf.enp0s3.disable_ipv6 =1
+ ```
+- 生效
+ ```bash
+ sysctl -p
+ ```
+
+## 方法2
+- 执行下面命令
+ ```bash
+ echo 1>/proc/sys/net/ipv6/conf/all/disable_ipv6
+ echo 1>/proc/sys/net/ipv6/conf/default/disable_ipv6
+ ```
+
diff --git a/content/post/k3s-install.md b/content/post/k3s-install.md
new file mode 100644
index 0000000..9651223
--- /dev/null
+++ b/content/post/k3s-install.md
@@ -0,0 +1,159 @@
+---
+title: "CentOS7 安装 K3S"
+date: 2020-09-25T14:21:00+08:00
+lastmod: 2020-09-25T14:21:00+08:00
+keywords: []
+tags: ["rancher", "k3s"]
+categories: ["container"]
+---
+
+# 环境
+
+角色 | 主机名 | 操作系统 | 软件
+---- | ---- | ---- | ----
+数据库 | - | - | docker-ce 19.03
+k3s server | k3s-server0 | CentOS7.8 | docker-ce 19.03, k3s v1.18.9
+k3s server | k3s-server1 | CentOS7.8 | docker-ce 19.03, k3s v1.18.9
+k3s agent | k3s-agent0 | CentOS7.8 | docker-ce 19.03, k3s v1.18.9
+k3s agent | k3s-agent1 | CentOS7.8 | docker-ce 19.03, k3s v1.18.9
+
+- **全部服务器关闭 firewalld、selinux 和 swap,设置时间同步**
+- **全部 k3s 服务器(除了数据库)必须设置唯一主机名**
+
+# 安装数据库
+- 在数据库服务器上执行如下操作
+- 启动 docker 容器
+ ```bash
+ docker run -d \
+ --name mariadb \
+ -p 3306:3306 \
+ -v /data/mariadb/binlog:/var/lib/mysql-bin \
+ -v /data/mariadb/db:/var/lib/mysql \
+ -v /data/mariadb/log:/var/log/mysql \
+ harbor.colben.cn/general/alpine-mariadb
+ ```
+
+- 创建 k3s 数据库
+ ```bash
+ docker exec mariadb mysql -e "
+ CREATE DATABASE k3s DEFAULT CHARSET UTF8MB4;
+ CREATE USER k3s@'%' IDENTIFIED BY 'Password_1234';
+ GRANT ALL ON k3s.* TO k3s@'%';
+ FLUSH PRIVILEGES;
+ "
+ ```
+
+- 生产环境建议配置 mysql 主从高可用,参考[MariaDB 主从复制](/post/mariadb-replication/)
+
+# 安装 k3s server
+- 在每台 k3s server 服务器上执行如下操作
+- 下载并安装 k3s
+ ```bash
+ cd /usr/local/bin/
+ curl -LO https://github.com/rancher/k3s/releases/download/v1.18.9%2Bk3s1/k3s
+ chmod 0755 k3s
+ ln -s k3s kubectl
+ ```
+
+- 创建 systemd 服务文件 /etc/systemd/system/k3s-server.service,内容如下
+ ```ini
+ [Unit]
+ Description=Lightweight Kubernetes
+ Documentation=https://k3s.io
+ Wants=network-online.target
+
+ [Install]
+ WantedBy=multi-user.target
+
+ [Service]
+ Type=notify
+ KillMode=process
+ Delegate=yes
+ LimitNOFILE=infinity
+ LimitNPROC=infinity
+ LimitCORE=infinity
+ TasksMax=infinity
+ TimeoutStartSec=0
+ Restart=always
+ RestartSec=5s
+ SuccessExitStatus=1
+ ExecStartPre=-/sbin/modprobe br_netfilter
+ ExecStartPre=-/sbin/modprobe overlay
+ ExecStart=/usr/local/bin/k3s \
+ server \
+ --docker \
+ --datastore-endpoint 'mysql://k3s:Password_1234@tcp({mysql 地址}:{mysql 端口})/k3s' \
+ --disable 'coredns,servicelb,traefik,local-storage,metrics-server' \
+ --pause-image 'harbor.colben.cn/k3s/pause:3.2'
+ ```
+
+- 重载 systemd 系统服务,启动 k3s-server 服务
+ ```bash
+ systemctl daemon-reload
+ systemctl start k3s-server
+ ```
+
+- 获取 token 信息(同一集群内各 server 上该文件完全一样),该信息用于 agent 连接
+ ```bash
+ cat /var/lib/rancher/k3s/server/token
+ ```
+
+- 多个 k3s-server 服务可通过 keepalived 配置高可用,参考[keepalived 笔记](/post/keepalived/)
+
+# 安装 k3s agent
+- 在每台 k3s agent 服务器上执行如下操作
+- 下载并安装 k3s
+ ```bash
+ cd /usr/local/bin/
+ curl -LO https://github.com/rancher/k3s/releases/download/v1.18.9%2Bk3s1/k3s
+ chmod 0755 k3s
+ ```
+
+- 创建 systemd 服务文件 /etc/systemd/system/k3s-agent.service,内容如下
+ ```ini
+ [Unit]
+ Description=Lightweight Kubernetes
+ Documentation=https://k3s.io
+ Wants=network-online.target
+
+ [Install]
+ WantedBy=multi-user.target
+
+ [Service]
+ Type=notify
+ KillMode=process
+ Delegate=yes
+ LimitNOFILE=infinity
+ LimitNPROC=infinity
+ LimitCORE=infinity
+ TasksMax=infinity
+ TimeoutStartSec=0
+ Restart=always
+ RestartSec=5s
+ SuccessExitStatus=1
+ ExecStartPre=-/sbin/modprobe br_netfilter
+ ExecStartPre=-/sbin/modprobe overlay
+ ExecStart=/usr/local/bin/k3s \
+ agent \
+ --docker \
+ --server 'https://{任一 server 地址或 server 高可用地址}:6443' \
+ --pause-image 'harbor.boyachain.cn:20443/k3s/pause:3.2' \
+ --token '{server token 信息}'
+ ```
+
+- 重载 systemd 系统服务,启动 k3s-agent 服务
+ ```bash
+ systemctl daemon-reload
+ systemctl start k3s-agent
+ ```
+
+# 查看节点信息
+- 在任一 k3s server 服务器上执行如下操作
+- 查看节点信息
+ ```bash
+ kubectl get nodes
+ ```
+
+# 注意事项
+- k3s 内部 ssl 证书有效期一年,可在到期前重启 k3s 集群轮换证书
+
diff --git a/content/post/k8s-coredns.md b/content/post/k8s-coredns.md
new file mode 100644
index 0000000..5264780
--- /dev/null
+++ b/content/post/k8s-coredns.md
@@ -0,0 +1,276 @@
+---
+title: "K8s 部署 Coredns 组件"
+date: 2019-10-30T01:06:56+08:00
+lastmod: 2020-02-10T14:36:00+08:00
+keywords: []
+tags: ["kubernetes", "k8s", "coredns"]
+categories: ["container"]
+---
+
+# 环境
+
+- [二进制部署的 kubernetes v1.17.2 集群](https://colben.cn/post/k8s-install/)
+- coreDNS 1.6.6
+
+# 生成 service account 文件
+- 创建 0.coredns-sa.yml
+ ```bash
+ cat > 0.coredns-sa.yml < 1.coredns-rbac.yml < 2.coredns-configmap.yml < 3.coredns-deployment.yml < 4.coredns-service.yml < -n kube-system
+ ```
+
+# 参考
+- [coredns 部署](https://github.com/coredns/deployment/tree/master/kubernetes)
+
diff --git a/content/post/k8s-install.md b/content/post/k8s-install.md
new file mode 100644
index 0000000..5bab88f
--- /dev/null
+++ b/content/post/k8s-install.md
@@ -0,0 +1,832 @@
+---
+title: "K8s 二进制安装"
+date: 2019-10-30T01:09:48+08:00
+lastmod: 2020-02-03T11:43:30+08:00
+keywords: []
+tags: ["kubernetes", "k8s", "二进制"]
+categories: ["container"]
+---
+
+# 环境
+
+角色 | 主机名 | 内网 IP | 集群 IP | 操作系统 | 服务 | 执行目录
+---- | ---- | ---- | ---- | ---- | ---- | ----
+部署机 k8s-master | master120 | 10.0.4.120 | - | CentOS | kube-apiserver kube-scheduler kube-controller-manager | /opt/kubernetes/
+etcd-node | etcd121 | 10.0.4.121 | 10.10.10.121 | CentOS | etcd | /opt/etcd/
+etcd-node | etcd122 | 10.0.4.122 | 10.10.10.122 | CentOS | etcd | /opt/etcd/
+etcd-node | etcd123 | 10.0.4.123 | 10.10.10.123 | CentOS | etcd | /opt/etcd/
+k8s-node | node124 | 10.0.4.124 | - | CentOS | docker flannel kubelet kube-proxy | /opt/kubernetes/
+k8s-node | node125 | 10.0.4.125 | - | CentOS | docker flannel kubelet kube-proxy | /opt/kubernetes/
+k8s-node | node126 | 10.0.4.126 | - | CentOS | docker flannel kubelet kube-proxy | /opt/kubernetes/
+
+- 全部服务器关闭 firewalld 和 selinux,禁用 swap,部署机(master120)可免密 ssh 登陆其他服务器
+- 软件版本
+ - CentOS: 7.7
+ - etcd: 3.3.18
+ - docker: ce-19.03.5
+ - flannel: 0.11.0
+ - kubernetes: 1.17.2
+- k8s牵扯到多个网段,这里说明下
+ - 10.0.4.0/24 该网段是服务器物理网卡 IP 地址段,通过该地址访问互联网
+ - 10.10.10.0/24 该网段是杜撰的,但也配置在服务器物理网卡上,用于 etcd 集群节点间通信,与 k8s 集群无关
+ - 10.10.9.0/24 该网段是杜撰的,分配 k8s service 的 clusterIP
+ - 172.17.0.0/24 该网段是杜撰的,是 docker 网桥自带网段,也是 flannel 提供的网段,实现不同节点间的容器互通
+ - 172.16.0.0/24 该网段是杜撰的,是 k8s pod 的 IP 地址区间,用于区别流量来源
+
+# 部署 etcd 集群
+
+- 在部署机(master120)上操作下面步骤
+- 创建 etcd 部署目录
+ ```bash
+ mkdir /home/deploy/etcd/{bin,cfg,ssl} -p
+ mkdir /home/deploy/ssl/etcd -p
+ ```
+
+- 安装 cfssl 工具
+ ```bash
+ curl -o /usr/local/bin/cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
+ curl -o /usr/local/bin/cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
+ curl -o /usr/local/bin/cfssl-certinfo https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
+ chmod 0755 /usr/local/bin/cfssl*
+ ```
+
+- 创建 ca-config.json 文件
+ ```bash
+ cat > /home/deploy/ssl/etcd/ca-config.json <<< '
+ {
+ "signing": {
+ "default": {
+ "expiry": "87600h"
+ },
+ "profiles": {
+ "etcd": {
+ "expiry": "87600h",
+ "usages": [
+ "signing",
+ "key encipherment",
+ "server auth",
+ "client auth"
+ ]
+ }
+ }
+ }
+ }
+ '
+ ```
+
+- 创建 ca-csr.json 文件
+ ```bash
+ cat > /home/deploy/ssl/etcd/ca-csr.json <<< '
+ {
+ "CN": "etcd",
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "names": [
+ {
+ "C": "CN",
+ "L": "Beijing",
+ "ST": "Beijing"
+ }
+ ]
+ }
+ '
+ ```
+
+- 创建 server-csr.json 文件
+ ```bash
+ # 注意修改 "10.0.4.*" 和 "10.10.10.*" 为自己环境地址
+ cat > /home/deploy/ssl/etcd/server-csr.json <<< '
+ {
+ "CN": "etcd",
+ "hosts": [
+ "10.0.4.121",
+ "10.0.4.122",
+ "10.0.4.123",
+ "10.10.10.121",
+ "10.10.10.122",
+ "10.10.10.123",
+ "127.0.0.1"
+ ],
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "names": [
+ {
+ "C": "CN",
+ "L": "BeiJing",
+ "ST": "BeiJing"
+ }
+ ]
+ }
+ '
+ ```
+
+- 生成证书
+ ```bash
+ cd /home/deploy/ssl/etcd/
+ # 生成 ca.pem ca-key.pem
+ cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
+ # 生成 server.pem server-key.pem
+ cfssl gencert \
+ -ca=ca.pem \
+ -ca-key=ca-key.pem \
+ -config=ca-config.json \
+ -profile=etcd \
+ server-csr.json | cfssljson -bare server
+ # 复制证书到部署目录
+ scp *.pem /home/deploy/etcd/ssl/
+ ```
+
+- 下载 etcd 二进制包
+ ```bash
+ cd /home/deploy/
+ curl -L -O https://github.com/etcd-io/etcd/releases/download/v3.3.18/etcd-v3.3.18-linux-amd64.tar.gz
+ tar zxf etcd-v3.3.18-linux-amd64.tar.gz
+ scp etcd-v3.3.18-linux-amd64/{etcd,etcdctl} etcd/bin/
+ ```
+
+- 创建 etcd 配置文件
+ ```bash
+ # 这里的 etcd 虚拟机都有两个网卡,一个用于提供服务,另一个用于集群通信
+ # 注意修改 "10.0.4.*" 和 "10.10.10.*" 为自己环境地址
+ cat > /home/deploy/etcd/cfg/etcd <<<'
+ # [Member]
+ ETCD_NAME="etcdXXX"
+ ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
+ ETCD_LISTEN_PEER_URLS="https://10.10.10.XXX:2380"
+ ETCD_LISTEN_CLIENT_URLS="https://10.0.4.XXX:2379"
+
+ # [Clustering]
+ ETCD_INITIAL_ADVERTISE_PEER_URLS="https://10.10.10.XXX:2380"
+ ETCD_ADVERTISE_CLIENT_URLS="https://10.0.4.XXX:2379"
+ ETCD_INITIAL_CLUSTER="etcd121=https://10.10.10.121:2380,etcd122=https://10.10.10.122:2380,etcd123=https://10.10.10.123:2380"
+ ETCD_INITIAL_CLUSTER_TOKEN="fucking-etcd-cluster"
+ ETCD_INITIAL_CLUSTER_STATE="new"
+ '
+ ```
+
+- 创建 etcd.service
+ ```bash
+ cat > /home/deploy/etcd.service <<<'
+ [Unit]
+ Description=Etcd Server
+ After=network.target
+ After=network-online.target
+ Wants=network-online.target
+
+ [Service]
+ Type=notify
+ EnvironmentFile=/opt/etcd/cfg/etcd
+ ExecStart=/opt/etcd/bin/etcd \
+ --name=${ETCD_NAME} \
+ --data-dir=${ETCD_DATA_DIR} \
+ --listen-peer-urls=${ETCD_LISTEN_PEER_URLS} \
+ --listen-client-urls=${ETCD_LISTEN_CLIENT_URLS},http://127.0.0.1:2379 \
+ --advertise-client-urls=${ETCD_ADVERTISE_CLIENT_URLS} \
+ --initial-advertise-peer-urls=${ETCD_INITIAL_ADVERTISE_PEER_URLS} \
+ --initial-cluster=${ETCD_INITIAL_CLUSTER} \
+ --initial-cluster-token=${ETCD_INITIAL_CLUSTER_TOKEN} \
+ --initial-cluster-state=${ETCD_INITIAL_CLUSTER_STATE} \
+ --cert-file=/opt/etcd/ssl/server.pem \
+ --key-file=/opt/etcd/ssl/server-key.pem \
+ --peer-cert-file=/opt/etcd/ssl/server.pem \
+ --peer-key-file=/opt/etcd/ssl/server-key.pem \
+ --trusted-ca-file=/opt/etcd/ssl/ca.pem \
+ --peer-trusted-ca-file=/opt/etcd/ssl/ca.pem
+ Restart=on-failure
+ LimitNOFILE=65536
+
+ [Install]
+ WantedBy=multi-user.target
+ '
+ ```
+
+- 部署到远程三个 etcd 节点(etcd121、etcd122、etcd123)
+ ```bash
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ cd /home/deploy
+ for id in $(seq 121 123); do
+ ip="10.0.4.$id"
+ scp -r etcd $ip:/opt/
+ ssh $ip "sed -i 's/XXX/$id/g' /opt/etcd/cfg/etcd"
+ scp etcd.service $ip:/usr/lib/systemd/system/
+ systemctl -H $ip daemon-reload
+ systemctl -H $ip enable etcd
+ done
+ ```
+
+- 启动三个 etcd 节点的 etcd 服务
+ ```bash
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ for ip in $(seq -f'10.0.4.%g' 121 123); do
+ systemctl -H $ip start etcd
+ done
+ ```
+
+- 查看 etcd 集群状态
+ ```bash
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ cd /home/deploy/etcd/ssl
+ ../bin/etcdctl \
+ --ca-file=ca.pem \
+ --cert-file=server.pem \
+ --key-file=server-key.pem \
+ --endpoints="https://10.0.4.121:2379" \
+ cluster-health
+ ```
+
+# 安装 Docker
+
+- 在每个 k8s node 服务器(node124、node125、node126)上操作下面步骤
+- 安装 docker-ce,[参考这里](https://my.oschina.net/colben/blog/1505141#h1_2)
+
+# 部署 Flannel 网络
+
+- 在部署机(master120)上操作下面步骤
+- 创建 flannel 部署目录
+ ```bash
+ cd /home/deploy
+ mkdir flannel/{bin,cfg,ssl} -p
+ # 复制 etcd 证书到 flannel 证书目录下
+ rm -rf flannel/ssl/etcd && scp -r etcd/ssl flannel/ssl/etcd
+ ```
+
+- 连接 etcd,写入预定义的子网段
+ ```bash
+ # 这里的预定义字段是 "172.17.0.0/16",推荐用这个,与 docker 原生网段一致
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ cd /home/deploy/etcd/ssl
+ ../bin/etcdctl \
+ --ca-file=ca.pem \
+ --cert-file=server.pem \
+ --key-file=server-key.pem \
+ --endpoints="https://10.0.4.122:2379" \
+ set /coreos.com/network/config '{"Network": "172.17.0.0/16", "Backend": {"Type": "vxlan"}}'
+ ```
+
+- 获取 flannel 二进制包
+ ```bash
+ cd /home/deploy
+ curl -L -O https://github.com/coreos/flannel/releases/download/v0.11.0/flannel-v0.11.0-linux-amd64.tar.gz
+ tar zxf flannel-v0.11.0-linux-amd64.tar.gz
+ scp flanneld mk-docker-opts.sh flannel/bin/
+ ```
+
+- 创建 flannel 配置文件
+ ```bash
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ cat > /home/deploy/flannel/cfg/flanneld <<< '
+ FLANNEL_OPTIONS=" \
+ --etcd-endpoints=https://10.0.4.121:2379,https://10.0.4.122:2379,https://10.0.4.123:2379 \
+ -etcd-cafile=/opt/kubernetes/ssl/etcd/ca.pem \
+ -etcd-certfile=/opt/kubernetes/ssl/etcd/server.pem \
+ -etcd-keyfile=/opt/kubernetes/ssl/etcd/server-key.pem \
+ "
+ '
+ ```
+
+- 创建 flanneld.service
+ ```bash
+ cat > /home/deploy/flanneld.service <<< '
+ [Unit]
+ Description=Flanneld overlay address etcd agent
+ After=network-online.target network.target
+ Before=docker.service
+
+ [Service]
+ Type=notify
+ EnvironmentFile=/opt/kubernetes/cfg/flanneld
+ ExecStart=/opt/kubernetes/bin/flanneld --ip-masq $FLANNEL_OPTIONS
+ ExecStartPost=/opt/kubernetes/bin/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/subnet.env
+ Restart=on-failure
+
+ [Install]
+ WantedBy=multi-user.target
+ '
+ ```
+
+- 修改 docker.service,用于从指定的子网段启动 docker
+ ```bash
+ # 关键在 "EnvironmentFile=/run/flannel/subnet.env"
+ # 该文件就是 flanneld 服务生成的 docker 参数
+ # 这里仅作记录,具体实现已移到下一步的部署脚本中
+ ```
+
+- 部署到远程三个 k8s node 节点(node124、node125、node126)
+ ```bash
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ cd /home/deploy
+ for ip in $(seq -f'10.0.4.%g' 124 126); do
+ systemctl -H $ip stop docker
+ ssh $ip "mkdir /opt/kubernetes"
+ scp -r flannel/* $ip:/opt/kubernetes/
+ scp flanneld.service $ip:/usr/lib/systemd/system/
+ ssh $ip 'sed -i \
+ -e "/^Type/aEnvironmentFile=/run/flannel/subnet.env" \
+ -e "/^ExecStart/s/$/\$DOCKER_NETWORK_OPTIONS/" \
+ /usr/lib/systemd/system/docker.service \
+ '
+ systemctl -H $ip daemon-reload
+ systemctl -H $ip enable flanneld
+ done
+ ```
+
+- 启动三个 k8s node 节点的 flanneld 和 docker 服务
+ ```bash
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ for ip in $(seq -f'10.0.4.%g' 124 126); do
+ systemctl -H $ip start flanneld
+ systemctl -H $ip start docker
+ done
+ ```
+
+- 启动完成后,不同节点的 docker0 网卡 ip 互通
+
+# 部署 k8s master 节点
+
+- 部署前确保前面的 etcd 集群、flannel 网络和 docker 都是正常的
+- 在部署机(master120,即当前节点)上操作下面步骤
+- 创建 master 部署目录
+ ```bash
+ cd /home/deploy
+ mkdir master/{bin,cfg,ssl} -p
+ mkdir ssl/master -p
+ # 复制 etcd 证书到 master 证书目录下
+ rm -rf master/ssl/etcd && scp -r etcd/ssl master/ssl/etcd
+ ```
+
+- 创建 ca-config.json 文件
+ ```bash
+ cat > /home/deploy/ssl/master/ca-config.json <<< '
+ {
+ "signing": {
+ "default": {
+ "expiry": "87600h"
+ },
+ "profiles": {
+ "kubernetes": {
+ "expiry": "87600h",
+ "usages": [
+ "signing",
+ "key encipherment",
+ "server auth",
+ "client auth"
+ ]
+ }
+ }
+ }
+ }
+ '
+ ```
+
+- 创建 ca-csr.json 文件
+ ```bash
+ cat > /home/deploy/ssl/master/ca-csr.json <<< '
+ {
+ "CN": "kubernetes",
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "names": [
+ {
+ "C": "CN",
+ "L": "Beijing",
+ "ST": "Beijing",
+ "O": "k8s",
+ "OU": "System"
+ }
+ ]
+ }
+ '
+ ```
+
+- 创建 kube-apiserver-csr.json 文件
+ ```bash
+ # 这里的 10.10.9.1 是 kubernetes service 的集群地址
+ # 该地址默认是下文中的 service-cluster-ip-range 网段的第一个 ip
+ # dns 组件会用到
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ cat > /home/deploy/ssl/master/kube-apiserver-csr.json <<< '
+ {
+ "CN": "kubernetes",
+ "hosts": [
+ "127.0.0.1",
+ "10.0.4.120",
+ "10.10.9.1",
+ "kubernetes",
+ "kubernetes.default",
+ "kubernetes.default.svc",
+ "kubernetes.default.svc.cluster",
+ "kubernetes.default.svc.cluster.local"
+ ],
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "names": [
+ {
+ "C": "CN",
+ "L": "BeiJing",
+ "ST": "BeiJing",
+ "O": "k8s",
+ "OU": "System"
+ }
+ ]
+ }
+ '
+ ```
+
+- 创建 kube-proxy-csr.json 文件
+ ```bash
+ cat > /home/deploy/ssl/master/kube-proxy-csr.json <<< '
+ {
+ "CN": "system:kube-proxy",
+ "hosts": [],
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "names": [
+ {
+ "C": "CN",
+ "L": "BeiJing",
+ "ST": "BeiJing",
+ "O": "k8s",
+ "OU": "System"
+ }
+ ]
+ }
+ '
+ ```
+
+- 生成证书
+ ```bash
+ cd /home/deploy/ssl/master
+ cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
+ cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-apiserver-csr.json | cfssljson -bare kube-apiserver
+ cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy
+ # 复制证书到部署目录
+ scp *.pem /home/deploy/master/ssl/
+ ```
+
+- 获取 kubernetes 二进制包
+ ```bash
+ cd /home/deploy
+ curl -L -O https://dl.k8s.io/v1.17.2/kubernetes-server-linux-amd64.tar.gz
+ tar zxf kubernetes-server-linux-amd64.tar.gz
+ cd kubernetes/server/bin
+ scp kube-apiserver kube-scheduler kube-controller-manager kubectl /home/deploy/master/bin/
+ ```
+
+- 创建 token 文件
+ ```bash
+ # 第一个字符串随机写的,看心情 ……
+ echo '1111222233334444aaaabbbbccccdddd,kubelet-bootstrap,10001,"system:kubelet-bootstrap"' > /home/deploy/master/cfg/token.csv
+ ```
+
+- 创建 kube-apiserver 配置文件
+ ```bash
+ # 这里的 service-cluster-ip-range 是 k8s service 地址区间,提供一个与现有网络不通的网段
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ cat > /home/deploy/master/cfg/kube-apiserver <<< '
+ KUBE_APISERVER_OPTS=" \
+ --logtostderr=true \
+ --v=4 \
+ --etcd-servers=https://10.0.4.121:2379,https://10.0.4.122:2379,https://10.0.4.123:2379 \
+ --bind-address=10.0.4.120 \
+ --secure-port=6443 \
+ --advertise-address=10.0.4.120 \
+ --allow-privileged=true \
+ --service-cluster-ip-range=10.10.9.0/24 \
+ --enable-admission-plugins=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota,NodeRestriction \
+ --authorization-mode=RBAC,Node \
+ --enable-bootstrap-token-auth \
+ --token-auth-file=/opt/kubernetes/cfg/token.csv \
+ --service-node-port-range=30000-50000 \
+ --tls-cert-file=/opt/kubernetes/ssl/kube-apiserver.pem \
+ --tls-private-key-file=/opt/kubernetes/ssl/kube-apiserver-key.pem \
+ --client-ca-file=/opt/kubernetes/ssl/ca.pem \
+ --service-account-key-file=/opt/kubernetes/ssl/ca-key.pem \
+ --etcd-cafile=/opt/kubernetes/ssl/etcd/ca.pem \
+ --etcd-certfile=/opt/kubernetes/ssl/etcd/server.pem \
+ --etcd-keyfile=/opt/kubernetes/ssl/etcd/server-key.pem \
+ "
+ '
+ ```
+
+- 创建 kube-apiserver.service 文件
+ ```bash
+ cat > /home/deploy/kube-apiserver.service <<< '
+ [Unit]
+ Description=Kubernetes API Server
+ Documentation=https://github.com/kubernetes/kubernetes
+
+ [Service]
+ EnvironmentFile=-/opt/kubernetes/cfg/kube-apiserver
+ ExecStart=/opt/kubernetes/bin/kube-apiserver $KUBE_APISERVER_OPTS
+ Restart=on-failure
+
+ [Install]
+ WantedBy=multi-user.target
+ '
+ ```
+
+- 创建 kube-scheduler 配置文件
+ ```bash
+ cat > /home/deploy/master/cfg/kube-scheduler <<< '
+ KUBE_SCHEDULER_OPTS=" \
+ --logtostderr=true \
+ --v=4 \
+ --master=127.0.0.1:8080 \
+ --leader-elect \
+ "
+ '
+ ```
+
+- 创建 kube-scheduler.service
+ ```bash
+ cat > /home/deploy/kube-scheduler.service <<< '
+ [Unit]
+ Description=Kubernetes Scheduler
+ Documentation=https://github.com/kubernetes/kubernetes
+
+ [Service]
+ EnvironmentFile=-/opt/kubernetes/cfg/kube-scheduler
+ ExecStart=/opt/kubernetes/bin/kube-scheduler $KUBE_SCHEDULER_OPTS
+ Restart=on-failure
+
+ [Install]
+ WantedBy=multi-user.target
+ '
+ ```
+
+- 创建 kube-controller-mananger 配置文件
+ ```bash
+ # 注意这里设置了 cluster-name 为 "my_k8s_cluster"
+ # 这个名字在后面会用到
+ # 这里的 service-cluster-ip-range 是 k8s service 地址区间,与之前配置的网段相同
+ cat > /home/deploy/master/cfg/kube-controller-manager <<< '
+ KUBE_CONTROLLER_MANAGER_OPTS=" \
+ --logtostderr=true \
+ --v=4 \
+ --master=127.0.0.1:8080 \
+ --leader-elect=true \
+ --address=127.0.0.1 \
+ --service-cluster-ip-range=10.10.9.0/24 \
+ --cluster-name=my_k8s_cluster \
+ --cluster-signing-cert-file=/opt/kubernetes/ssl/ca.pem \
+ --cluster-signing-key-file=/opt/kubernetes/ssl/ca-key.pem \
+ --root-ca-file=/opt/kubernetes/ssl/ca.pem \
+ --service-account-private-key-file=/opt/kubernetes/ssl/ca-key.pem \
+ "
+ '
+ ```
+
+- 创建 kube-controller-manager.service
+ ```bash
+ cat > /home/deploy/kube-controller-manager.service <<< '
+ [Unit]
+ Description=Kubernetes Controller Manager
+ Documentation=https://github.com/kubernetes/kubernetes
+
+ [Service]
+ EnvironmentFile=-/opt/kubernetes/cfg/kube-controller-manager
+ ExecStart=/opt/kubernetes/bin/kube-controller-manager $KUBE_CONTROLLER_MANAGER_OPTS
+ Restart=on-failure
+
+ [Install]
+ WantedBy=multi-user.target
+ '
+ ```
+
+- 部署到执行目录
+ ```bash
+ cd /home/deploy
+ mkdir /opt/kubernetes
+ scp -r master/* /opt/kubernetes/
+ scp kube-apiserver.service kube-scheduler.service kube-controller-manager.service /usr/lib/systemd/system/
+ systemctl daemon-reload
+ systemctl enable kube-apiserver kube-scheduler kube-controller-manager
+ ln -sf /opt/kubernetes/bin/kubectl /usr/local/bin/
+ ```
+
+- 启动 k8s master 各组件
+ ```bash
+ systemctl start kube-apiserver
+ systemctl start kube-scheduler
+ systemctl start kube-controller-manager
+ ```
+
+- 查看集群各组件状态
+ ```bash
+ kubectl get cs
+ ```
+
+# 部署 k8s node 节点
+
+- 在部署机(master120)上操作下面步骤
+- 创建 node 部署目录
+ ```bash
+ cd /home/deploy
+ mkdir node/{bin,cfg,ssl} -p
+ scp kubernetes/server/bin/{kubelet,kube-proxy} node/bin/
+ ```
+
+- 将kubelet-bootstrap用户绑定到系统集群角色
+ ```bash
+ kubectl create clusterrolebinding kubelet-bootstrap \
+ --clusterrole=system:node-bootstrapper \
+ --user=kubelet-bootstrap
+ ```
+
+- 生成 bootstrap.kubeconfig 文件
+ ```bash
+ export BOOTSTRAP_TOKEN=1111222233334444aaaabbbbccccdddd
+ export KUBE_APISERVER="https://10.0.4.120:6443"
+ cd /home/deploy/master/ssl
+ # 设置集群参数,这里的指定的集群就是前面设置的 "my_k8s_cluster"
+ kubectl config set-cluster my_k8s_cluster \
+ --certificate-authority=ca.pem \
+ --embed-certs=true \
+ --server=${KUBE_APISERVER} \
+ --kubeconfig=/home/deploy/node/cfg/bootstrap.kubeconfig
+ # 设置客户端认证参数
+ kubectl config set-credentials kubelet-bootstrap \
+ --token=${BOOTSTRAP_TOKEN} \
+ --kubeconfig=/home/deploy/node/cfg/bootstrap.kubeconfig
+ # 设置上下文参数
+ kubectl config set-context default \
+ --cluster=my_k8s_cluster \
+ --user=kubelet-bootstrap \
+ --kubeconfig=/home/deploy/node/cfg/bootstrap.kubeconfig
+ # 设置默认上下文
+ kubectl config use-context default --kubeconfig=/home/deploy/node/cfg/bootstrap.kubeconfig
+ ```
+
+- 生成 kube-proxy.kubeconfig 文件
+ ```bash
+ export KUBE_APISERVER="https://10.0.4.120:6443"
+ cd /home/deploy/master/ssl
+ # 设置集群参数,这里的指定的集群就是前面设置的 "my_k8s_cluster"
+ kubectl config set-cluster my_k8s_cluster \
+ --certificate-authority=ca.pem \
+ --embed-certs=true \
+ --server=${KUBE_APISERVER} \
+ --kubeconfig=/home/deploy/node/cfg/kube-proxy.kubeconfig
+ kubectl config set-credentials kube-proxy \
+ --client-certificate=kube-proxy.pem \
+ --client-key=kube-proxy-key.pem \
+ --embed-certs=true \
+ --kubeconfig=/home/deploy/node/cfg/kube-proxy.kubeconfig
+ kubectl config set-context default \
+ --cluster=my_k8s_cluster \
+ --user=kube-proxy \
+ --kubeconfig=/home/deploy/node/cfg/kube-proxy.kubeconfig
+ kubectl config use-context default --kubeconfig=/home/deploy/node/cfg/kube-proxy.kubeconfig
+ ```
+
+- 创建 kubelet 配置文件
+ ```bash
+ # --kubeconfig 指定 kubeconfig 文件位置,会自动生成
+ # --cert-dir 颁发证书待存放位置
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ # 这里保留 "XXX",后面部署命令会统一替换
+ cat > /home/deploy/node/cfg/kubelet <<< '
+ KUBELET_OPTS=" \
+ --logtostderr=true \
+ --v=4 \
+ --hostname-override=10.0.4.XXX \
+ --kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig \
+ --bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig \
+ --config=/opt/kubernetes/cfg/kubelet.config \
+ --cert-dir=/opt/kubernetes/ssl \
+ --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 \
+ "
+ '
+ # registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 该镜像可以提前导入本地局域网中的私有 docker 仓库中
+ ```
+
+- 创建 kubelet.config 配置文件
+ ```bash
+ # 这里的 clusterDNS 是 DNS service 的集群地址,这里分配 10.10.9.2
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ # 这里保留 "XXX",后面部署命令会统一替换
+ cat > /home/deploy/node/cfg/kubelet.config <<< '
+ kind: KubeletConfiguration
+ apiVersion: kubelet.config.k8s.io/v1beta1
+ address: 10.0.4.XXX
+ port: 10250
+ readOnlyPort: 10255
+ cgroupDriver: cgroupfs
+ clusterDNS: ["10.10.9.2"]
+ clusterDomain: cluster.local.
+ failSwapOn: false
+ authentication:
+ anonymous:
+ enabled: true
+ '
+ ```
+
+- 创建 kubelet.service
+ ```bash
+ cat > /home/deploy/kubelet.service <<< '
+ [Unit]
+ Description=Kubernetes Kubelet
+ After=docker.service
+ Requires=docker.service
+
+ [Service]
+ EnvironmentFile=/opt/kubernetes/cfg/kubelet
+ ExecStart=/opt/kubernetes/bin/kubelet $KUBELET_OPTS
+ Restart=on-failure
+ KillMode=process
+
+ [Install]
+ WantedBy=multi-user.target
+ '
+ ```
+
+- 创建 kube-proxy 配置文件
+ ```bash
+ # 这里的 cluster-cidr 是 pod ip 地址区间
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ # 这里保留 "XXX",后面部署命令会统一替换
+ cat > /home/deploy/node/cfg/kube-proxy <<< '
+ KUBE_PROXY_OPTS=" \
+ --logtostderr=true \
+ --v=4 \
+ --hostname-override=10.0.4.XXX \
+ --cluster-cidr=172.16.0.0/16 \
+ --kubeconfig=/opt/kubernetes/cfg/kube-proxy.kubeconfig \
+ "
+ '
+ ```
+
+- 创建 kube-proxy.service
+ ```bash
+ cat > /home/deploy/kube-proxy.service <<< '
+ [Unit]
+ Description=Kubernetes Proxy
+ After=network.target
+
+ [Service]
+ EnvironmentFile=-/opt/kubernetes/cfg/kube-proxy
+ ExecStart=/opt/kubernetes/bin/kube-proxy $KUBE_PROXY_OPTS
+ Restart=on-failure
+
+ [Install]
+ WantedBy=multi-user.target
+ '
+ ```
+
+- 部署到远程三个 k8s node 节点(node124、node125、node126)
+ ```bash
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ cd /home/deploy
+ for id in $(seq 124 126); do
+ ip="10.0.4.$id"
+ scp -r node/* $ip:/opt/kubernetes/
+ ssh $ip "sed -i 's/XXX/$id/g' /opt/kubernetes/cfg/kubelet"
+ ssh $ip "sed -i 's/XXX/$id/g' /opt/kubernetes/cfg/kubelet.config"
+ ssh $ip "sed -i 's/XXX/$id/g' /opt/kubernetes/cfg/kube-proxy"
+ scp kubelet.service kube-proxy.service $ip:/usr/lib/systemd/system/
+ systemctl -H $ip daemon-reload
+ systemctl -H $ip enable kubelet kube-proxy
+ done
+ ```
+
+- 启动三个 k8s node 节点的 kubelet 和 kube-proxy 服务
+ ```bash
+ # 注意修改 "10.0.4.*" 为自己环境地址
+ for ip in $(seq -f'10.0.4.%g' 124 126); do
+ systemctl -H $ip start kubelet
+ systemctl -H $ip start kube-proxy
+ done
+ ```
+
+- 审批 node 加入集群
+ ```bash
+ kubectl get csr
+ # "XXXX" 上一命令输出的 NAME 列
+ kubectl certificate approve XXXX
+ kubectl get node
+ ```
+
+- 查看集群状态
+ ```bash
+ kubectl get node
+ kubectl get cs
+ ```
+
+# 部署其他组件
+
+- [coreDNS](https://colben.cn/post/k8s-coredns/)
+
+# 运行一个测试示例
+
+- 暂无
+
diff --git a/content/post/kafka-install.md b/content/post/kafka-install.md
new file mode 100644
index 0000000..7c45c31
--- /dev/null
+++ b/content/post/kafka-install.md
@@ -0,0 +1,104 @@
+---
+title: "CentOS7 安装 Kafka 集群"
+date: 2019-10-30T01:22:25+08:00
+lastmod: 2019-10-30T01:22:25+08:00
+keywords: []
+tags: ["kafka", "centos"]
+categories: ["MQ"]
+---
+
+# 环境
+
+主机名 | IP | 操作系统 | kafka 版本
+---- | ---- | ---- | ----
+kafka224 | 192.168.1.224 | CentOS7.5 | 2.12
+kafka225 | 192.168.1.225 | CentOS7.5 | 2.12
+kafka226 | 192.168.1.226 | CentOS7.5 | 2.12
+
+- 下载 [kafka_2.12-2.1.0.tgz](http://mirrors.tuna.tsinghua.edu.cn/apache/kafka/2.1.0/kafka_2.12-2.1.0.tgz)
+- zookeeper 连接: 192.168.1.221:2181,192.168.1.222:2181,192.168.1.223:2181
+
+# 各节点初始配置
+
+- 关闭 selinux、防火墙
+- 部署 java 运行环境
+- 创建数据目录
+ ```
+ mkdir -p /var/lib/kafka
+ ```
+- 创建日志目录
+ ```
+ mkdir -p /var/log/kafka
+ ```
+
+# 部署 kafka
+
+- 登陆 kafka224,下载 kafka,解压至 /opt/ 下
+- 修改日志目录
+ ```
+ cd /opt/kafka/
+ rm -rf logs && ln -s /var/log/kafka logs
+ ```
+- 修改 /opt/kafka/config/server.properties
+ ```
+ broker.id=224
+ listeners=PLAINTEXT://192.168.1.224:9092
+ log.dirs=/var/lib/kafka
+ zookeeper.connect=192.168.1.221:2181,192.168.1.222:2181,192.168.1.223:2181
+ ```
+- 启动脚本(/opt/kafka/bin/kafka-server-start.sh)太繁琐,我精简了下
+ ```bash
+ #!/bin/bash
+ base_dir=$(dirname $0)
+ if [ "x$KAFKA_LOG4J_OPTS" = "x" ]; then
+ export KAFKA_LOG4J_OPTS="-Dlog4j.configuration=file:$base_dir/../config/log4j.properties"
+ fi
+ if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
+ # jvm 堆内存根据实际情况修改
+ export KAFKA_HEAP_OPTS="-Xmx2G -Xms2G"
+ # 开启 jmx
+ export JMX_PORT=1099
+ fi
+ EXTRA_ARGS=${EXTRA_ARGS-'-name kafkaServer -loggc'}
+ EXTRA_ARGS="-daemon "$EXTRA_ARGS
+ exec $base_dir/kafka-run-class.sh $EXTRA_ARGS kafka.Kafka "$base_dir/../config/server.properties"
+ ```
+- 打包 kafka 目录,复制到 kafka225 和 kafka226 上,并修改 server.properties
+ ```
+ # kafka225
+ broker.id=225
+ listeners=PLAINTEXT://192.168.1.225:9092
+ # kafka226
+ broker.id=226
+ listeners=PLAINTEXT://192.168.1.226:9092
+ ```
+
+# 启动集群(两种启动方式)
+
+- 直接启动二进制
+ - 在每个节点上启动 kafka 服务
+ ```bash
+ /opt/kafka/bin/kafka-server-start.sh
+ ```
+- systemd 启动
+ - 创建 /usr/lib/systemd/system/kafka.service
+ ```
+ [Unit]
+ Description=Kafka
+ Requires=network.service
+ After=network.service
+ [Service]
+ Environment=JAVA_HOME=/opt/jre
+ ExecStart=/opt/kafka/bin/kafka-server-start.sh
+ ExecStop=/opt/kafka/bin/kafka-server-stop.sh
+ Type=forking
+ KillMode=none
+ [Install]
+ WantedBy=multi-user.target
+ ```
+ - 启动 kafka 服务
+ ```bash
+ systemctl daemon-reload
+ systemctl start kafka
+ ```
+
diff --git a/content/post/keepalived-install.md b/content/post/keepalived-install.md
new file mode 100644
index 0000000..abe0e16
--- /dev/null
+++ b/content/post/keepalived-install.md
@@ -0,0 +1,195 @@
+---
+title: "Keepalived 基础"
+date: 2019-10-30T11:26:18+08:00
+lastmod: 2019-10-30T11:26:18+08:00
+tags: ["keepalived"]
+categories: ["ha/lb"]
+---
+
+# VRRP 简介
+- VRRP通过一种竞选协议动态地将路由任务交给LAN中**虚拟路由器**中的某台**VRRP路由器**
+- VRRP路由器是一台实现了VRRP协议(运行VRRPD程序)的物理路由器
+- 虚拟路由器是由多台VRRP物理路由器组成的逻辑路由器,对外看起来就像一台路由器
+- 虚拟路由器中,只有一台MASTER物理路由器工作,其他都是BACKUP
+- MASTER路由器一直发送VRRP广播,MASTER不可用后(BACKUP收不到广播),其余BACKUP会根据优先级竞选出一台MASTER
+- MASTER拥有虚拟路由器IP地址及其它路由配置
+
+# 全局配置
+- 全局定义
+ ```
+ global_defs {
+ notification_email {
+ admin@exammple.com #keepalived发生事件时通知该email
+ ...
+ }
+ notification_email_from admin@example.com
+ smtp_server 127.0.0.1 #smtp服务器
+ smtp_connect_timeout 30
+ router_id my_hostname #机器标识
+ default_interface eth0 #设置静态地址默认绑定的端口,默认是eth0
+ vrrp_mcast_group4 #VRRP 的组播IPV4地址,默认224.0.0.18
+ vrrp_mcast_group6 #VRRP 的组播IPV4地址,默认ff02::12
+ vrrp_version 2|3 #设置默认的VRRP版本,默认是2
+ script_user [groupname] #设置运行脚本默认用户和组,如果没有指定,则默认用户为keepalived_script(需要该用户存在),否则为root用户,默认groupname同username
+ enable_script_security #如果脚本路径的任一部分对于非root用户来说,都具有可写权限,则不会以root身份运行脚本
+ }
+ ```
+- 静态地址和路由,不随 vrrpd instance 的开/关变化
+ ```
+ static_ipaddress {
+ $ip/$mask dev $interface #ip命令规则
+ ...
+ }
+ static_routes {
+ $dest_ip/$dest_mask via $dest_gateway dev $interface #ip命令规则
+ ...
+ }
+ ```
+- 一般服务器都配置了网络信息,所以通常无需配置静态地址和路由,如果不指定 dev,则使用 default_interface
+
+# VRRPD 配置
+- VRRP检查脚本(vrrp_script)
+ ```
+ vrrp_script {
+ scrip "/path/to/script-file" #可执行的脚本的绝对路径
+ interval #脚本执行的间隔,单位是秒,默认为1s
+ timeout #指定在多少秒后,脚本被认为执行失败
+ weight <-254 --- 254> #调整优先级,默认为2
+ #如果脚本执行成功(退出状态码为0),weight大于0,则priority增加
+ #如果脚本执行失败(退出状态码为非0),weight小于0,则priority减少
+ #其他情况下,priority不变
+ rise #执行成功多少次才认为是成功
+ fall #执行失败多少次才认为失败
+ user [GROUPNAME] #运行脚本的用户和组
+ init_fail #假设脚本初始状态是失败状态
+ }
+ ```
+- VRRP同步组(sync group)
+ ```
+ vrrp_sync_group VG_1 {
+ group {
+ inside_network #实例名
+ ...
+ }
+ notify_master /path/to/master.sh #切换到master时执行该脚本
+ notify_backup /path/to/backup.sh #切换到backup时执行该脚本
+ notify_fault /path/to/fault.sh #出错时执行该脚本
+ notify /path/to/notify.sh #该脚本会在notify_*脚本后执行,默认3个参数:$1(GROUP|INSTANCE),$2(group或instance名字),$3(MASTER|BACKUP|FAULT)
+ smtp_alert #发送邮件通知
+ }
+ ```
+- VRRP实例(instance)配置
+ ```
+ vrrp_instance inside_network {
+ state MASTER #初始状态
+ interface eth0 #绑定的网卡
+ dont_track_primary #忽略VRRP的interface错误(默认不设置)
+ track_interface { #这里的任一网卡出现问题,都会进入FAULT状态
+ eth0
+ eth1 weight <-254 - 254>
+ ...
+ }
+ track_script { #这里的任一脚本返回码非0,都会进入FAULT状态
+
+ weight <-254-254>
+ ...
+ }
+ mcast_src_ip #多播包发送源地址,默认网卡当前ip
+ garp_master_delay 10 #切换到MASTER后,延迟arp请求
+ virtual_router_id 1 #VRID标记(0..255)
+ priority 100 #高优先级竞选为MASTER,MASTER高于BACKUP至少50
+ advert_int 1 #检查间隔,默认1秒
+ authentication {
+ auth_type PASS #密码认证
+ auth_pass 1111
+ }
+ virtual_ipaddress { #漂移地址,符合ip命令规则
+ $vip/$vmask dev $interface
+ ...
+ }
+ virtual_routes { #随地址一同漂移的路由,符合ip命令规则
+ $dest_ip/$dest_mask via $dest_gateway dev $interface
+ ...
+ }
+ nopreempt #BACKUP配置,且优先级比其他高
+ preempt_delay 300 #抢占延迟,默认5分钟
+ debug #Debug级别
+ lvs_sync_daemon_interface #lvn syncd绑定的网卡
+ }
+ ```
+
+# LVS配置(不涉及lvs时无需下面配置)
+- 虚拟主机组
+ ```
+ virtual_server_group {
+ #VIP VPORT
+
+
+ ...
+ fwmark
+ }
+ ```
+- 虚拟主机3中配置
+ ```
+ #virtual_server IP port
+ #virtual_server fwmark int
+ #virtual_server group string
+ virtual_server 192.168.1.229 80 { #配置一个virtual server
+ delay_loop 3 #每隔3秒检查一次RealServer是否可用
+ lb_algo rr|wrr|lc|wlc|lblc|sh|dh #LVS调度算法
+ lb_kind NAT|DR|TUN #LVS集群模式
+ persistence_timeout 120 #同一个客户端IP在120秒内分到同一个RealServer
+ persistence_granularity #会话保持粒度,默认255.255.255.255,即根据每个客户端IP做会话保持
+ protocol TCP #协议
+ ha_suspend
+ virtualhost #HTTP_GET健康检查时使用的HOST}
+ sorry_server #所有RS失效后连接该备用机
+ real_server {
+ weight 1 #默认1,0失效
+ inhibit_on_failure #健康检查失败后将weight置0,不从IPVS中删除
+ notify_up #检测到service up后执行的脚本
+ notify_down #检测到service down后执行的脚本
+ #检查方式,HTTP_GET|SSL_GET|TCP_CHECK
+ HTTP_GET|SSL_GET {
+ url {
+ path /
+ digest #SSL检查返回的摘要信息
+ status_code 200 #HTTP检查返回的状态码
+ }
+ connect_port 80 #检查端口
+ bindto #使用该地址发送健康检查
+ connect_timeout #连接超时时间
+ nb_get_retry 3 #重连次数
+ delay_before_retry 2 #重连间隔(秒)
+ }
+ TCP_CHECK {
+ connect_port 80
+ bindto
+ connect_timeout 4
+ }
+ }
+ }
+ ```
+
+# 配置日志文件
+- 修改服务启动参数
+ ```bash
+ sed -i '/^KEEPALIVED_OPTIONS/d' /etc/sysconfig/keepalived
+ echo 'KEEPALIVED_OPTIONS="-D -d -S 2"' >> /etc/sysconfig/keepalived
+ ```
+- 修改 rsyslog 配置文件
+ ```bash
+ echo "local2.* /var/log/keepalived.log" >> /etc/rsyslog.conf
+ ```
+- 重启 rsyslog 服务
+ ```bash
+ systemctl restart rsyslog
+ ```
+- 重启 keepalived 服务
+ ```bash
+ systemctl restart keepalived
+ ```
+
+# 其他参考
+- [详细解释](https://blog.csdn.net/wos1002/article/details/56483325)
+
diff --git a/content/post/keepalived.md b/content/post/keepalived.md
new file mode 100644
index 0000000..bb71ae6
--- /dev/null
+++ b/content/post/keepalived.md
@@ -0,0 +1,285 @@
+---
+title: "Keepalived 笔记"
+date: 2019-10-30T11:22:03+08:00
+lastmod: 2019-10-30T11:22:03+08:00
+tags: ["keepalived", "高可用", "负载均衡"]
+categories: ["ha/lb"]
+---
+
+# 两个 haproxy 不抢占
+- 环境
+ - haproxy 服务器
+ - haproxy101: 10.1.1.101
+ - haproxy102: 10.1.1.102
+ - 虚拟地址
+ - ip: 10.1.1.100
+- 在全部 haproxy 服务器上安装 keepalived
+ ```bash
+ yum install keepalived
+ ```
+- 在全部 haproxy 服务器上配置 haproxy 和 keepalived 自启动
+ ```bash
+ systemctl enable haproxy
+ systemctl enable keepalived
+ ```
+- MASTER/BACKUP 完整配置
+ ```
+ global_defs {
+ router_id haproxy101 #BACKUP 这里是 haproxy102
+ script_user root
+ enable_script_security
+ }
+ vrrp_script chk_haproxy {
+ script "/usr/bin/systemctl status haproxy"
+ interval 2
+ weight 0
+ fall 2
+ rise 2
+ }
+ vrrp_instance VI_1 {
+ state BACKUP #MASTER 和 BACKUP 这里都是 BACKUP
+ virtual_router_id 1
+ priority 150 #BACKUP 这里是 100
+ advert_int 2
+ nopreempt #BACKUP 优先级低,需注释此行
+ interface eth0
+ track_script {
+ chk_haproxy
+ }
+ authentication {
+ auth_type PASS
+ auth_pass 1011100
+ }
+ virtual_ipaddress {
+ 10.1.1.100/24 dev eth0
+ }
+ }
+ ```
+
+# 两个 LVS-DR 调度器不抢占均衡后端 MySQL 和 Ceph 负载
+- 环境
+ - keepalived 服务器
+ - ka101: 10.1.1.101
+ - ka102: 10.1.1.102
+ - 虚拟地址
+ - ip: 10.1.1.100
+ - mysqld 服务器
+ - mysql103: 10.1.1.103
+ - mysql104: 10.1.1.104
+ - mysql105: 10.1.1.105
+ - ceph-radosgw 服务器
+ - ceph106: 10.1.1.106
+ - ceph107: 10.1.1.107
+ - ceph108: 10.1.1.108
+- 在全部 keepalived 服务器上安装 keepalived
+ ```bash
+ yum install keepalived
+ ```
+- 在全部 keepalived 服务器上配置 keepalived 自启动
+ ```bash
+ systemctl enable keepalived
+ ```
+- MASTER/BACKUP 完整配置
+ - 全局和实例配置
+ ```
+ global_defs {
+ router_id keepalive101 #BACKUP 配置 keepalive102
+ }
+ vrrp_instance V1_1 {
+ state BACKUP #BACKUP 也配置 BACKUP
+ interface eth0
+ virtual_router_id 1
+ priority 150 #BACKUP 配置 100
+ advert_int 1
+ nopreempt #BACKUP 优先级低,需注释此行
+ authentication {
+ auth_type PASS
+ auth_pass 1011100
+ }
+ virtual_ipaddress {
+ 10.1.1.100/24 dev eth0
+ }
+ }
+ ```
+ - LVS 均衡 mysql galera cluser 负载
+ ```
+ virtual_server 10.1.1.100 3306 {
+ delay_loop 16
+ lb_algo sh
+ lb_kind DR
+ protocol TCP
+ real_server 10.1.1.103 3306 {
+ weight 1
+ TCP_CHECK {
+ connect_timeout 4
+ delay_before_retry 2
+ connect_port 3306
+ }
+ }
+ real_server 10.1.1.104 3306 {
+ weight 1
+ TCP_CHECK {
+ connect_timeout 4
+ delay_before_retry 2
+ connect_port 3306
+ }
+ }
+ real_server 10.1.1.105 3306 {
+ weight 1
+ TCP_CHECK {
+ connect_timeout 4
+ delay_before_retry 2
+ connect_port 3306
+ }
+ }
+ }
+ ```
+ - LVS 均衡 ceph radosgw 负载
+ ```
+ virtual_server 10.1.1.100 7480 {
+ delay_loop 16
+ lb_algo sh
+ lb_kind DR
+ protocol TCP
+ real_server 10.1.1.106 7480 {
+ weight 1
+ HTTP_GET {
+ url {
+ path /
+ status_code 200
+ }
+ connect_timeout 8
+ nb_get_retry 2
+ delay_before_retry 2
+ connect_port 7480
+ }
+ }
+ real_server 10.1.1.107 7480 {
+ weight 1
+ HTTP_GET {
+ url {
+ path /
+ status_code 200
+ }
+ connect_timeout 8
+ nb_get_retry 2
+ delay_before_retry 2
+ connect_port 7480
+ }
+ }
+ real_server 10.1.1.108 7480 {
+ weight 1
+ HTTP_GET {
+ url {
+ path /
+ status_code 200
+ }
+ connect_timeout 8
+ nb_get_retry 2
+ delay_before_retry 2
+ connect_port 7480
+ }
+ }
+ }
+ ```
+- 在全部 mysql 和 ceph-radosgw 服务器上配置虚拟 ip
+ ```bash
+ echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
+ echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
+ echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
+ echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce
+ ip addr add 10.1.1.100/32 brd 10.1.1.100 dev lo
+ ip route add 10.1.1.100 dev lo
+ ```
+
+# 不抢占自动切换两台 Redis 主从状态
+- 环境
+ - Redis 服务器
+ - redis101: 10.1.1.101
+ - redis102: 10.1.1.102
+ - 虚拟地址
+ - ip: 10.1.1.100
+- 在全部 redis 服务器上安装 keepalived
+ ```bash
+ yum install keepalived
+ ```
+- 在全部 redis 服务器上配置 redis 和 keepalived 自启动
+ ```bash
+ systemctl enable redis
+ systemctl enable keepalived
+ ```
+- MASTER/BACKUP 完整配置
+ ```
+ global_defs {
+ router_id redis101 #BACKUP 这里是 redis102
+ script_user root
+ enable_script_security
+ }
+ vrrp_script chk_redis {
+ script "/usr/bin/systemctl status redis"
+ interval 2
+ weight 0
+ fall 2
+ rise 2
+ }
+ vrrp_instance VI_1 {
+ state BACKUP #MASTER 和 BACKUP 这里都是 BACKUP
+ virtual_router_id 51
+ priority 150 #BACKUP 这里是 100
+ advert_int 1
+ nopreempt #BACKUP 优先级低,需注释此行
+ interface eth0
+ notify_master /etc/keepalived/scripts/master.sh
+ notify_backup /etc/keepalived/scripts/backup.sh
+ track_script {
+ chk_redis
+ }
+ authentication {
+ auth_type PASS
+ auth_pass 123456
+ }
+ virtual_ipaddress {
+ 10.1.1.100/24 dev eth0
+ }
+ }
+ ```
+- 在全部 redis 服务器上创建 /etc/keepalived/scripts/master.sh 脚本,内容如下
+ ```bash
+ #!/bin/bash
+ #
+ /usr/bin/sed -i '/^slaveof/d' /etc/redis.conf
+ /usr/bin/systemctl restart redis
+ ```
+- 在 redis101 上创建 /etc/keepalived/scripts/backup.sh 脚本,内容如下
+ ```bash
+ #!/bin/bash
+ #
+ /usr/bin/sed -i '/^slaveof/d' /etc/redis.conf
+ echo 'slaveof 10.1.1.102 6379' >> /etc/redis.conf
+ /usr/bin/systemctl restart redis
+ ```
+- 在 redis102 上创建 /etc/keepalived/scripts/backup.sh 脚本,内容如下
+ ```bash
+ #!/bin/bash
+ #
+ /usr/bin/sed -i '/^slaveof/d' /etc/redis.conf
+ echo 'slaveof 10.1.1.101 6379' >> /etc/redis.conf
+ /usr/bin/systemctl restart redis
+ ```
+- 在全部 redis 服务器上赋予脚本可执行权限
+ ```bash
+ chmod 0755 /etc/keepalived/scripts/*.sh
+ ```
+- 修改 redis 配置
+ ```
+ requirepass redis_password
+ maxclients 1000
+ maxmemory 4294967296
+ maxmemory-policy volatile-lru
+ ```
+- 在全部 redis 服务器上启动 redis 和 keepalived 服务
+ ```bash
+ systemctl start redis
+ systemctl start keepalived
+ ```
+
diff --git a/content/post/kickstart-centos7.md b/content/post/kickstart-centos7.md
new file mode 100644
index 0000000..2527cff
--- /dev/null
+++ b/content/post/kickstart-centos7.md
@@ -0,0 +1,208 @@
+---
+title: "Kickstart 安装 CentOS7"
+date: 2019-10-29T21:00:25+08:00
+lastmod: 2019-10-29T21:00:25+08:00
+keywords: []
+tags: ["kickstart", "centos"]
+categories: ["os"]
+---
+
+# 环境
+- CentOS7.6
+- genisoimage 1.1.11
+- CentOS-7-x86_64-Minimal-1810.iso
+
+# 复制 iso 内容到本地磁盘
+```bash
+mount -o loop CentOS-7-x86_64-Minimal-1810.iso /mnt/
+mkdir -p /home/iso/centos7
+cd /mnt && cp -af * .* /home/iso/centos7/
+```
+
+# 创建 isolinux/ks.cfg
+- mbr 启动,/home/iso/centos7/isolinux/ks.cfg 内容如下
+ ```
+ # Install OS instead of upgrade
+ install
+ # Reboot after installation
+ reboot
+ # System authorization information
+ auth --enableshadow --passalgo=sha512
+ # Use CDROM installation media
+ cdrom
+ # Use graphical install
+ graphical
+ # Run the Setup Agent on first boot
+ firstboot --enable
+ ignoredisk --only-use=sda
+ # Keyboard layouts
+ keyboard --vckeymap=cn --xlayouts='cn'
+ # System language
+ lang zh_CN.UTF-8
+ # Firewall configuration
+ firewall --disabled
+ # SELinux configuration
+ selinux --disabled
+
+ # Network information
+ #network --bootproto=dhcp --device=eth0 --onboot=off --ipv6=auto --no-activate
+ #network --hostname=localhost.localdomain
+
+ # Root password(111111)
+ rootpw --iscrypted $6$kD.hMvv5nCY8a/SM$Gnmb4zspkuyL75BP2Gj.1SGUaWBugXkd/zMFhoDndp9CSi8VP7R5JP7rfWzL4y7fy8crH3ryDT4PFkKCc7/xM.
+ # System services
+ services --enabled="chronyd"
+ # System timezone
+ timezone Asia/Shanghai --isUtc
+ # Clear the Master Boot Record
+ zerombr
+ # System bootloader configuration
+ bootloader --location=mbr --boot-drive=sda
+ # Partition clearing information
+ clearpart --none --initlabel
+ # Disk partitioning information
+ part /boot --fstype="xfs" --ondisk=sda --size=512
+ part / --fstype="xfs" --ondisk=sda --grow --size=1
+
+ %packages
+ @^minimal
+ @core
+ chrony
+
+ %end
+
+ %post
+ lsblk > /root/lsblk
+ %end
+
+ %addon com_redhat_kdump --disable --reserve-mb='auto'
+
+ %end
+
+ %anaconda
+ pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
+ pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
+ pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
+ %end
+ ```
+- efi 启动,/home/iso/centos7/isolinux/ks.cfg 内容如下
+ ```
+ # Install OS instead of upgrade
+ install
+ # Reboot after installation
+ reboot
+ # System authorization information
+ auth --enableshadow --passalgo=sha512
+ # Use CDROM installation media
+ cdrom
+ # Use graphical install
+ graphical
+ # Run the Setup Agent on first boot
+ firstboot --enable
+ ignoredisk --only-use=sda
+ # Keyboard layouts
+ keyboard --vckeymap=cn --xlayouts='cn'
+ # System language
+ lang zh_CN.UTF-8
+ # Firewall configuration
+ firewall --disabled
+ # SELinux configuration
+ selinux --disabled
+
+ # Network information
+ #network --bootproto=dhcp --device=eth0 --onboot=off --ipv6=auto --no-activate
+ #network --hostname=localhost.localdomain
+
+ # Root password(111111)
+ rootpw --iscrypted $6$kD.hMvv5nCY8a/SM$Gnmb4zspkuyL75BP2Gj.1SGUaWBugXkd/zMFhoDndp9CSi8VP7R5JP7rfWzL4y7fy8crH3ryDT4PFkKCc7/xM.
+ # System services
+ services --enabled="chronyd"
+ # System timezone
+ timezone Asia/Shanghai --isUtc
+ # Clear the Master Boot Record
+ zerombr
+ # System bootloader configuration
+ bootloader --location=mbr --boot-drive=sda
+ # Partition clearing information
+ clearpart --none --initlabel
+ # Disk partitioning information
+ part /boot --fstype="xfs" --ondisk=sda --size=512
+ part /boot/efi --fstype="xfs" --ondisk=sda --size=512
+ part / --fstype="xfs" --ondisk=sda --grow --size=1
+
+ %packages
+ @^minimal
+ @core
+ chrony
+
+ %end
+
+ %post
+ %end
+
+ %addon com_redhat_kdump --disable --reserve-mb='auto'
+
+ %end
+
+ %anaconda
+ pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
+ pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
+ pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
+ %end
+ ```
+
+# 修改启动项文件
+- mbr 启动,只需修改 isolinux/isolinux.cfg
+ - 删除 "label check" 下的 "menu default" 一行
+ - 在 "label linux" 一行上方添加如下内容
+ ```
+ label auto
+ menu label ^Auto install CentOS 7
+ menu default
+ kernel vmlinuz
+ append initrd=initrd.img inst.stage2=hd:LABEL=CentOS7 inst.ks=cdrom:/isolinux/ks.cfg quiet
+ ```
+- efi 启动,只需修改 EFI/BOOT/grub.cfg
+ - 修改第一行
+ ```
+ set default="0"
+ ```
+ - 在 "### BEGIN /etc/grub.d/10_linux ###" 一行下添加如下内容
+ ```
+ menuentry 'Auto Install CentOS 7' --class fedora --class gnu-linux --class gnu --class os {
+ linuxefi /images/pxeboot/vmlinuz inst.ks=cdrom:/isolinux/ks.cfg inst.stage2=hd:LABEL=CentOS7 quiet
+ initrdefi /images/pxeboot/initrd.img
+ }
+ ```
+
+# 生成 ISO 镜像
+- mbr 启动,执行如下命令
+ ```bash
+ genisoimage -v -R -J -T -V CentOS7 \
+ -b isolinux/isolinux.bin \
+ -c isolinux/boot.cat \
+ -cache-inodes \
+ -joliet-long \
+ -no-emul-boot \
+ -boot-load-size 4 \
+ -boot-info-table \
+ -o /home/centos7.iso \
+ /home/iso/centos7
+ ```
+- efi 启动,执行如下命令
+ ```bash
+ genisoimage -v -R -J -T -V CentOS7 \
+ -b images/efiboot.img \
+ -c isolinux/boot.cat \
+ -cache-inodes \
+ -joliet-long \
+ -no-emul-boot \
+ -boot-load-size 4 \
+ -boot-info-table \
+ -o /home/centos7-efi.iso \
+ /home/iso/centos7
+ ```
+
+# 参考
+- [https://boke.wsfnk.com/archives/382.html](https://boke.wsfnk.com/archives/382.html)
+
diff --git a/content/post/kubeadm.md b/content/post/kubeadm.md
new file mode 100644
index 0000000..91c90c4
--- /dev/null
+++ b/content/post/kubeadm.md
@@ -0,0 +1,163 @@
+---
+title: "Kubeadm"
+date: 2019-10-30T11:19:06+08:00
+lastmod: 2019-10-30T11:19:06+08:00
+tags: ["kubernetes", "k8s", "kubeadm"]
+categories: ["container"]
+---
+
+# kubeadm 安装 kubernetes
+
+
+### 全部服务器配置
+ IP 地址 | 主机名 | 操作系统 | 内存 | swap | 硬盘 | Internet | firewalld | selinux | /etc/hosts 增加行
+ --------- | -------- | -------- | ---- | ---- | ---- | -------- | --------- | ------- | ------------------
+ 10.0.2.80 | master80 | CentOS7 | 4GB | 关闭 | 20GB | 可达 | 关闭 | 关闭 | 127.0.0.1 master80
+ 10.0.2.81 | node81 | CentOS7 | 2GB | 关闭 | 20GB | 可达 | 关闭 | 关闭 | 127.0.0.1 node81
+ 10.0.2.82 | node82 | CentOS7 | 2GB | 关闭 | 20GB | 可达 | 关闭 | 关闭 | 127.0.0.1 node82
+
+- 确认各服务器工作网卡的 MAC 和 UUID 均不相同
+ ```bash
+ #Ovirt 从模板创建虚拟机可忽略此步骤
+ #不确定时可参考如下命令
+ rm -rf /etc/udev/rules.d/70-*
+ sed -i -e '/HWADDR/d' -e '/UUID/d' /etc/sysconfig/network-scripts/ifcfg-{eth,enp}*
+ ```
+
+- 配置好 IP,确认各服务器网络互连,且可连互联网
+ ```bash
+ #10.0.2.80
+ sed -i 's/10.0.2.127/10.0.2.80/' /etc/sysconfig/network-scripts/ifcfg-eth0
+ #10.0.2.81
+ sed -i 's/10.0.2.127/10.0.2.81/' /etc/sysconfig/network-scripts/ifcfg-eth0
+ #10.0.2.82
+ sed -i 's/10.0.2.127/10.0.2.82/' /etc/sysconfig/network-scripts/ifcfg-eth0
+ ```
+
+- 关闭各服务器的防火墙
+ ```bash
+ systemctl stop firewalld
+ systemctl disable firewalld
+ ```
+
+- 关闭各服务器的 selinux
+ ```bash
+ setenforce 0
+ sed -i '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
+ ```
+
+- 关闭各服务器的 swap
+ ```bash
+ swapoff -a
+ sed -i '/swap/s/^/#/' /etc/fstab
+ ```
+
+- 安装 ebtables 和 ethtool
+ ```bash
+ yum install ebtables ethtool
+ ```
+
+- 配置各服务器的 hostname
+ ```bash
+ #10.0.2.80
+ hostnamectl set-hostname master80
+ echo '127.0.0.1 master80' >> /etc/hosts
+ #10.0.2.81
+ hostnamectl set-hostname node81
+ echo '127.0.0.1 node81' >> /etc/hosts
+ #10.0.2.82
+ hostnamectl set-hostname node82
+ echo '127.0.0.1 node82' >> /etc/hosts
+ ```
+
+- 重启各服务器
+ ```bash
+ reboot
+ ```
+
+
+### 安装 docker
+- 各服务器安装 docker
+ ```bash
+ yum install docker
+ ```
+
+- 各服务器配置 iptables 转发
+ ```bash
+ cat < /etc/sysctl.d/k8s.conf
+ net.bridge.bridge-nf-call-ip6tables = 1
+ net.bridge.bridge-nf-call-iptables = 1
+ EOF
+ sysctl --system
+ ```
+
+- 各服务器配置 docker 本地仓库(可选)
+ ```bash
+ cat < /etc/docker/daemon.json
+ {
+ "insecure-registries":["10.0.16.125:5080"]
+ }
+ EOF
+ ```
+
+- 各服务器启动 docker
+ ```bash
+ systemctl enable docker && systemctl start docker
+ ```
+
+
+### 安装 kubernetes
+- 各服务器配置 kubernetes yum 源
+ ```bash
+ cat < /etc/yum.repos.d/kubernetes.repo
+ [kubernetes]
+ name=Kubernetes
+ baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
+ 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
+ EOF
+ ```
+
+- 各服务器安装 kubeadm
+ ```bash
+ yum install -y kubelet kubeadm kubectl
+ ```
+
+- 各服务器启动 kubelet
+ ```bash
+ systemctl enable kubelet && systemctl start kubelet
+ ```
+
+
+### 在 master80 服务器上安装 kubernetes master 服务组件
+- 初始化 kubeadm
+ ```bash
+ kubeadm init --pod-network-cidr=192.168.0.0/16 --token-ttl 0
+ mkdir -p /root/.kube
+ cp -i /etc/kubernetes/admin.conf /root/.kube/config
+ #记录下输出的最后一行,类似如下
+ #kubeadm join --token : --discovery-token-ca-cert-hash sha256:
+ ```
+
+- 安装 Calico 网络插件
+ ```bash
+ kubectl apply -f http://docs.projectcalico.org/v2.4/getting-started/kubernetes/installation/hosted/kubeadm/1.6/calico.yaml
+ ```
+
+
+### 加入其他节点
+- 在 node81 和 node82 服务器上执行以下命令,即 master80 服务器 'kuberadm init' 命令的最后一行输出
+ ```bash
+ kubeadm join --token : --discovery-token-ca-cert-hash sha256:
+ ```
+
+- 在 master80 服务器查看节点和 pod 情况
+ ```bash
+ kubectl get pods --all-namespaces
+ kubectl get nodes
+ ```
+
+
diff --git a/content/post/letsencrypt.md b/content/post/letsencrypt.md
new file mode 100644
index 0000000..de56a47
--- /dev/null
+++ b/content/post/letsencrypt.md
@@ -0,0 +1,84 @@
+---
+title: "Letsencrypt 笔记"
+date: 2021-11-06T20:52:00+08:00
+lastmod: 2021-11-06T20:52:00+08:00
+keywords: []
+tags: ["letsencrypt", "certbot", "ssl"]
+categories: ["web"]
+---
+
+# 安装 certbot
+- 在 alpine linux 中安装 certbot
+ ```bash
+ apk add --no-cache certbot openssl
+ ```
+
+- 注册
+ ```bash
+ certbot register --register-unsafely-without-email --agree-tos
+ ```
+
+# 普通域名证书
+- 申请 ssl 证书,有效期 90 天
+ ```bash
+ certbot certonly -n -d x.x.com --standalone
+
+ # 证书文件生成到 /etc/letsencrypt/live/x.x.com/ 下
+ # 参数 -d 可以使用多次来指定多个域名,也可以在一个 -d 参数中使用逗号分隔多个域名
+ # 参数 --cert-name 可以指定证书文件的父级目录名字(替换默认的 x.x.com)
+ ```
+
+- 续签 ssl 证书
+ ```bash
+ cerbot renew --force-renewal
+ ```
+
+- 生成 2048 位的交换密钥文件
+ ```bash
+ openssl dhparam -out /etc/letsencrypt/dhparam.pem 2048
+ ```
+
+# 通配域名证书
+- 申请 ssl 证书,有效期 90 天
+ ```bash
+ certbot certonly --manual -d '*.x.com' \
+ --preferred-challenges dns \
+ --server https://acme-v02.api.letsencrypt.org/directory
+
+ # 证书文件生成到 /etc/letsencrypt/live/x.com/ 下
+ # 参数 --cert-name 可以指定证书文件的父级目录名字(替换默认的 x.com)
+ ```
+
+- 续签 ssl 证书,使用 certonly 子命令指定域名单独更新
+ ```bash
+ certbot certonly --force-renewal --manual -d '*.x.com' \
+ --preferred-challenges dns \
+ --server https://acme-v02.api.letsencrypt.org/directory
+ ```
+
+- 生成 2048 位的交换密钥文件
+ ```bash
+ openssl dhparam -out /etc/letsencrypt/dhparam.pem 2048
+ ```
+
+# 使用证书
+- nginx 配置 ssl
+ ```
+ server {
+ listen 443 ssl;
+ server_name x.x.x;
+ ssl_certificate /etc/letsencrypt/live/x.x.x/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/x.x.x/privkey.pem;
+ ssl_session_cache shared:le_nginx_SSL:10m;
+ ssl_session_timeout 1440m;
+ ssl_session_tickets off;
+ ssl_prefer_server_ciphers off;
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
+ ssl_dhparam /etc/letsencrypt/dhparam.pem;
+ location / {
+ return 404;
+ }
+ }
+ ```
+
diff --git a/content/post/linux-io-random.md b/content/post/linux-io-random.md
new file mode 100644
index 0000000..4ddb76b
--- /dev/null
+++ b/content/post/linux-io-random.md
@@ -0,0 +1,188 @@
+---
+title: "Linux 硬盘随机读写"
+date: 2019-10-29T21:29:26+08:00
+lastmod: 2019-10-29T21:29:26+08:00
+keywords: []
+tags: ["linux", "随机读", "随机写"]
+categories: ["storage"]
+---
+
+# 前言
+- 随机写会导致磁头不停地换道,造成效率的极大降低
+- 顺序写磁头几乎不用换道,或者换道的时间很短
+
+# fio 介绍
+- fio的输出报告中的几个关键指标:
+ - slat: 是指从 I/O 提交到实际执行 I/O 的时长(Submission latency)
+ - clat: 是指从 I/O 提交到 I/O 完成的时长(Completion latency)
+ - lat: 指的是从 fio 创建 I/O 到 I/O 完成的总时长
+ - bw : 吞吐量
+ - iops: 每秒 I/O 的次数
+
+# 同步写测试
+### 同步随机写
+- 使用strace工具查看系统调用
+ ```bash
+ strace -f -tt -o /tmp/randwrite.log -D fio -name=randwrite -rw=randwrite \
+ -direct=1 -bs=4k -size=1G -numjobs=1 -group_reporting -filename=/tmp/test.db
+ ```
+- 提取关键信息
+ ```
+ root@wilson-ubuntu:~# strace -f -tt -o /tmp/randwrite.log -D fio -name=randwrite -rw=randwrite \
+ > -direct=1 -bs=4k -size=1G -numjobs=1 -group_reporting -filename=/tmp/test.db
+ randwrite: (g=0): rw=randwrite, bs=4K-4K/4K-4K/4K-4K, ioengine=sync, iodepth=1
+ fio-2.2.10
+ Starting 1 process
+ ...
+ randwrite: (groupid=0, jobs=1): err= 0: pid=26882: Wed Aug 14 10:39:02 2019
+ write: io=1024.0MB, bw=52526KB/s, iops=13131, runt= 19963msec
+ clat (usec): min=42, max=18620, avg=56.15, stdev=164.79
+ lat (usec): min=42, max=18620, avg=56.39, stdev=164.79
+ ...
+ bw (KB /s): min=50648, max=55208, per=99.96%, avg=52506.03, stdev=1055.83
+ ...
+
+ Run status group 0 (all jobs):
+ WRITE: io=1024.0MB, aggrb=52525KB/s, minb=52525KB/s, maxb=52525KB/s, mint=19963msec, maxt=19963msec
+
+ Disk stats (read/write):
+ ...
+ sda: ios=0/262177, merge=0/25, ticks=0/7500, in_queue=7476, util=36.05%
+ ```
+- 重点关注的信息
+ - clat: 平均时长56ms左右
+ - lat: 平均时长56ms左右
+ - bw: 吞吐量,大概在52M左右
+- 内核调用信息,随机读每一次写入之前都要通过lseek去定位当前的文件偏移量
+ ```
+ root@wilson-ubuntu:~# more /tmp/randwrite.log
+ ...
+ 26882 10:38:41.919904 lseek(3, 665198592, SEEK_SET) = 665198592
+ 26882 10:38:41.919920 write(3, "\220\240@\6\371\341\277>\0\200\36\31\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ 26882 10:38:41.919969 lseek(3, 4313088, SEEK_SET) = 4313088
+ 26882 10:38:41.919985 write(3, "\220\240@\6\371\341\277>\0\200\36\31\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ 26882 10:38:41.920032 lseek(3, 455880704, SEEK_SET) = 455880704
+ 26882 10:38:41.920048 write(3, "\220\240@\6\371\341\277>\0\200\36\31\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ 26882 10:38:41.920096 lseek(3, 338862080, SEEK_SET) = 338862080
+ 26882 10:38:41.920112 write(3, "\220\240@\6\371\341\277>\0\2402\24\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ 26882 10:38:41.920161 lseek(3, 739086336, SEEK_SET) = 739086336
+ 26882 10:38:41.920177 write(3, "\220\240@\6\371\341\277>\0\2402\24\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ 26882 10:38:41.920229 lseek(3, 848175104, SEEK_SET) = 848175104
+ 26882 10:38:41.920245 write(3, "\220\240@\6\371\341\277>\0\2402\24\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ 26882 10:38:41.920296 lseek(3, 1060147200, SEEK_SET) = 1060147200
+ 26882 10:38:41.920312 write(3, "\220\240@\6\371\341\277>\0\2402\24\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ 26882 10:38:41.920362 lseek(3, 863690752, SEEK_SET) = 863690752
+ 26882 10:38:41.920377 write(3, "\220\240@\6\371\341\277>\0\2402\24\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ 26882 10:38:41.920428 lseek(3, 279457792, SEEK_SET) = 279457792
+ 26882 10:38:41.920444 write(3, "\220\240@\6\371\341\277>\0\2402\24\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ 26882 10:38:41.920492 lseek(3, 271794176, SEEK_SET) = 271794176
+ 26882 10:38:41.920508 write(3, "\220\240@\6\371\341\277>\0\2402\24\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ 26882 10:38:41.920558 lseek(3, 1067864064, SEEK_SET) = 1067864064
+ 26882 10:38:41.920573 write(3, "\220\240@\6\371\341\277>\0\2402\24\0\0\0\0\202\2\7\320\343\6H\26P\340\277\370\330\30e\30"..., 4096) = 4096
+ ...
+ ```
+
+### 同步顺序写
+- 测试顺序写
+ ```bash
+ strace -f -tt -o /tmp/write.log -D fio -name=write -rw=write \
+ -direct=1 -bs=4k -size=1G -numjobs=1 -group_reporting -filename=/tmp/test.db
+ ```
+- 可以看到,吞吐量提升至70M左右
+ ```
+ write: (g=0): rw=write, bs=4K-4K/4K-4K/4K-4K, ioengine=sync, iodepth=1
+ fio-2.2.10
+ Starting 1 process
+ Jobs: 1 (f=1): [W(1)] [100.0% done] [0KB/70432KB/0KB /s] [0/17.7K/0 iops] [eta 00m:00s]
+ write: (groupid=0, jobs=1): err= 0: pid=27005: Wed Aug 14 10:53:02 2019
+ write: io=1024.0MB, bw=70238KB/s, iops=17559, runt= 14929msec
+ clat (usec): min=43, max=7464, avg=55.95, stdev=56.24
+ lat (usec): min=43, max=7465, avg=56.15, stdev=56.25
+ ...
+ bw (KB /s): min=67304, max=72008, per=99.98%, avg=70225.38, stdev=1266.88
+ ...
+
+ Run status group 0 (all jobs):
+ WRITE: io=1024.0MB, aggrb=70237KB/s, minb=70237KB/s, maxb=70237KB/s, mint=14929msec, maxt=14929msec
+
+ Disk stats (read/write):
+ ...
+ sda: ios=0/262162, merge=0/10, ticks=0/6948, in_queue=6932, util=46.49%
+ ```
+- 内核调用
+ ```
+ root@wilson-ubuntu:~# more /tmp/write.log
+ ...
+ 27046 10:54:28.194508 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\360\t\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.194568 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.194627 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.194687 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.194747 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.194807 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.194868 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.194928 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.194988 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195049 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195110 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195197 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195262 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195330 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195426 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195497 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195567 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195637 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195704 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195757 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195807 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195859 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195910 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.195961 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196012 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196062 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0\220\24\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196112 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0 \26\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196162 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0 \26\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196213 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0 \26\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196265 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0 \26\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196314 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0 \26\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196363 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0 \26\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196414 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0 \26\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196472 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0 \26\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196524 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0 \26\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ 27046 10:54:28.196573 write(3, "\0\0\23\0\0\0\0\0\0\300\16\0\0\0\0\0\0 \26\0\0\0\0\0\0\320\17\0\0\0\0\0"..., 4096) = 4096
+ ...
+ ```
+- 顺序读不需要反复定位文件偏移量,所以能够专注于写操作
+
+# 异步顺序写
+- 前面 fio 的测试报告中没有发现slat,那是由于上述都是同步操作,对同步 I/O 来说,由于 I/O 提交和 I/O 完成是一个动作,所以 slat 实际上就是 I/O 完成的时间
+- 异步顺序写测试命令
+ ```bash
+ # 同步顺序写的命令添加 -ioengine=libaio
+ fio -name=write -rw=write -ioengine=libaio -direct=1 -bs=4k -size=1G -numjobs=1 -group_reporting -filename=/tmp/test.db
+ ```
+- slat指标出现,lat 近似等于 slat + clat 之和(avg平均值);并且换成异步io之后,吞吐量得到了极大的提升,120M左右
+ ```
+ write: (g=0): rw=write, bs=4K-4K/4K-4K/4K-4K, ioengine=libaio, iodepth=1
+ fio-2.2.10
+ Starting 1 process
+ Jobs: 1 (f=1): [W(1)] [100.0% done] [0KB/119.3MB/0KB /s] [0/30.6K/0 iops] [eta 00m:00s]
+ write: (groupid=0, jobs=1): err= 0: pid=27258: Wed Aug 14 11:14:36 2019
+ write: io=1024.0MB, bw=120443KB/s, iops=30110, runt= 8706msec
+ slat (usec): min=3, max=70, avg= 4.31, stdev= 1.56
+ clat (usec): min=0, max=8967, avg=28.13, stdev=55.68
+ lat (usec): min=22, max=8976, avg=32.53, stdev=55.72
+ ...
+ bw (KB /s): min=118480, max=122880, per=100.00%, avg=120467.29, stdev=1525.68
+ ...
+
+ Run status group 0 (all jobs):
+ WRITE: io=1024.0MB, aggrb=120442KB/s, minb=120442KB/s, maxb=120442KB/s, mint=8706msec, maxt=8706msec
+
+ Disk stats (read/write):
+ ...
+ sda: ios=0/262147, merge=0/1, ticks=0/6576, in_queue=6568, util=74.32%
+ ```
+
+# 参考
+- [https://www.linuxidc.com/Linux/2019-08/160097.htm](https://www.linuxidc.com/Linux/2019-08/160097.htm)
+
diff --git a/content/post/lvm.md b/content/post/lvm.md
new file mode 100644
index 0000000..a4b365c
--- /dev/null
+++ b/content/post/lvm.md
@@ -0,0 +1,198 @@
+---
+title: "Lvm 笔记"
+date: 2019-10-29T21:44:02+08:00
+lastmod: 2019-10-29T21:44:02+08:00
+keywords: []
+tags: ["lvm"]
+categories: ["storage"]
+---
+
+# LVM 一览
+
+
+# LVM 结构
+- PE 物理扩展
+- PV 物理卷,在设备起始处放置一个标签,包括 uuid,lvm 配置元数据位置以及剩余空间
+ - PV 可以由分区创建,也可以直接用磁盘创建
+- VG 卷组
+- LV 逻辑卷
+ - Linear 线性卷
+ - Stripe 条带卷
+ - RAID raid 逻辑卷
+ - Mirror 镜像卷
+ - Thinly-Provision 精简配置逻辑卷
+ - Snapshot 快照卷
+ - Thinly-Provisioned Snapshot 精简配置快照卷
+ - Cache 缓存卷
+
+# LVM 相关命令
+
+## PV
+- pvchange 更改物理卷属性
+- pvck 检查物理卷元数据
+- pvcreate 初始化磁盘或分区以供lvm使用
+- pvdisplay 显示物理卷的属性
+- pvmove 移动物理Exent
+- pvremove 删除物理卷
+- pvresize 调整lvm2使用的磁盘或分区的大小
+- pvs 报告有关物理卷的信息
+- pvscan 扫描物理卷的所有磁盘
+
+## VG
+- vgcfgbackup 备份卷组描述符区域
+- vgcfgrestore 恢复卷组描述符区域
+- vgchange 更改卷组的属性
+- vgck 检查卷组元数据
+- vgconvert 转换卷组元数据格式
+- vgcreate 创建卷组
+- vgdisplay 显示卷组的属性
+- vgexport 使卷组对系统不了解(这是个什么)
+- vgextend 将物理卷添加到卷组
+- vgimportclone 导入并重命名重复的卷组(例如硬件快照)
+- vgmerge 合并两个卷组
+- vgmknodes 重新创建卷组目录和逻辑卷特殊文件
+- vgreduce 通过删除一个或多个物理卷来减少卷组(将物理卷踢出VG)
+- vgremove 删除卷组
+- vgrename 重命名卷组
+- vgs 报告有关卷组信息
+- vgscan 扫描卷组的所有磁盘并重建高速缓存
+- vgsplit 将卷组拆分为两个,通过移动整个物理卷将任何逻辑卷从一个卷组移动到另一个卷组
+
+## LV
+- lvchange 更改逻辑卷属性
+- lvconvert 将逻辑卷从线性转换为镜像或快照
+- lvcreate 将现有卷组中创建逻辑卷
+- lvdisplay 显示逻辑卷的属性
+- lvextend 扩展逻辑卷的大小
+- lvmconfig 在加载lvm.conf和任何其他配置文件后显示配置信息
+- lvmdiskscan 扫描lvm2可见的所有设备
+- lvmdump 创建lvm2信息转储以用于诊断目的
+- lvreduce 减少逻辑卷的大小
+- lvremove 删除逻辑卷
+- lvrename 重命名逻辑卷
+- lvresize 调整逻辑卷大小
+- lvs 报告有关逻辑卷的信息
+- lvscan 扫描所有的逻辑卷
+
+# 创建 LVM
+- 将磁盘创建为 pv(物理卷),其实物理磁盘被条带化为 pv,划成了一个一个的 pe,默认每个 pe 大小是 4MB
+- 创建 vg,其实它是一个空间池,不同PV加入同一 vg
+- 创建 lv,组成 lv 的 pe 可能来自不同的物理磁盘
+- 格式化 lv,挂载使用
+
+# PV 管理
+- 制作 pv
+ ```bash
+ pvcreate /dev/sdb1
+ ```
+- 删除 pv(需先踢出 vg)
+ ```bash
+ pvremote /dev/sdb1
+ ```
+
+# VG 管理
+- 制作 vg
+ ```bash
+ # vgcreate vg_name 磁盘设备或分区
+ vgcreate datavg /dev/sdb1
+ vgcreate datavg /dev/sdb1 /dev/sdb2
+ # -s 指定pe的大小为16M,默认不指定是4M
+ vgcreate -s 16M datavg2 /dev/sdb3
+ ```
+- 从 vg 中移除缺失的磁盘
+ ```bash
+ vgreduce --removemissing datavg
+ vgreduce --removemissing datavg --force # 强制移除
+ ```
+- 扩展 vg 空间
+ ```bash
+ vgextend datavg /dev/sdb3 /dev/sdc
+ ```
+- 踢出 vg 中的某个成员
+ ```bash
+ vgreduce datavg /dev/sdb3
+ ```
+- 删除 vg
+ ```bash
+ vgremove VG
+ ```
+- 重命名 vg
+ ```bash
+ vgrename xxxx-vgid-xxxx-xxxx new_name
+ ```
+
+# LV 管理
+- 制作 lv
+ ```bash
+ # -n lv_name,-L lv_size,datavg(vg name)
+ lvcreate -n lvdata1 -L 1.5G datavg
+ ```
+- 激活修复后的逻辑卷
+ ```bash
+ lvchange -ay /dev/datavg/lvdata1
+ lvchange -ay /dev/datavg/lvdata1 -K # 强制激活
+ ```
+- 创建 lvm 快照
+ ```bash
+ # 数据一致性备份
+ # 先做一个快照,冻结当前系统,这样快照里面的内容可暂时保持不变
+ # 系统本身继续运行,通过重新挂载备份快照卷,实现不中断服务备份。
+ lvcreate -s -n kuaizhao01 -L 100M /dev/datavg/lvdata1
+ ```
+- 删除 lv
+ ```bash
+ lvremove /dev/mapper/VG-mylv
+ ```
+- 扩大一个 lv
+ - 用vgdisplay查看vg还有多少空余空间
+ - 扩充逻辑卷
+ ```bash
+ lvextend -L +1G /dev/VG/LV01
+ # -r 表示在扩展的同时也更新文件系统,不是所有的发行版本都支持
+ lvextend -L +1G /dev/VG/LV01 -r
+ ```
+ - 扩充操作后,df -h 发现大小并没有变,需更新文件系统
+ ```bash
+ # 不同文件系统更新的命令不一样
+ e2fsck -f /dev/datavg/lvdata1 # ext4 文件系统,检查 lv 的文件系统
+ resize2fs /dev/VG/LV01 # ext4 文件系统命令,该命令后面接 lv 的设备名就行
+ xfs_growfs /nas # xfs 文件系统,该命令后面直接跟的是挂载点
+ ```
+ - 更新文件系统后,df -h 正常
+- 缩小一个 lv
+ - umount 卸载
+ - 缩小文件系统
+ ```bash
+ resize2fs /dev/VG/LV01 2G
+ ```
+ - 缩小 lv
+ ```bash
+ lvreduce -L -1G /dev/VG/LV01
+ ```
+ - 查看 lvs,mount 挂载
+
+# lvm 灾难恢复
+- 场景: 三块盘做 lvm,现有一物理盘损坏,将剩下两块放到其他linux服务器上
+- 恢复步骤
+ - 查看磁盘信息,lvm信息,确认能查到lvm相关信息,找到VG组的名字
+ ```bash
+ pvs
+ lvs
+ vgs
+ fidsk
+ blkid
+ ```
+ - 删除 lvm 信息中损坏的磁盘角色,强制提出故障磁盘
+ ```bash
+ vgreduce --removemissing VG_name
+ ```
+ - 强制激活 vg 组
+ ```bash
+ vgchange -ay
+ ```
+ - 强制激活 lvm
+ ```bash
+ lvchange -ay /dev/VG_name
+ ```
+ - 挂载
+
diff --git a/content/post/lvs.md b/content/post/lvs.md
new file mode 100644
index 0000000..62cd301
--- /dev/null
+++ b/content/post/lvs.md
@@ -0,0 +1,44 @@
+---
+title: "Lvs 笔记"
+date: 2019-10-30T12:55:50+08:00
+lastmod: 2019-10-30T12:55:50+08:00
+tags: ["lvs", "负载均衡"]
+categories: ["ha/lb"]
+---
+
+# 环境
+角色 | 地址
+---- | ----
+负载分配服务器 | 192.168.1.209
+tomcat 服务器1 | 192.168.1.207
+tomcat 服务器2 | 192.168.1.208
+VIP | 192.168.1.250
+
+# LVS-DR
+- 配置负载分配服务器(192.168.1.209)
+ ```bash
+ #在与 tomcat 服务器连通的网卡(eth0)上配置虚拟 IP(192.168.1.250)
+ ip addr add 192.168.1.250/32 brd 192.168.1.250 dev eth0
+ ip route add 192.168.1.250 dev eth0
+ #开启转发
+ echo 1 > /proc/sys/net/ipv4/ip_forward
+ #安装 ipvsadm
+ yum install ipvsadm
+ ipvsadm -C
+ ipvsadm -A -t 192.168.1.250:80 -s rr
+ ipvsadm -a -t 192.168.1.250:80 -r 192.168.1.207:80 -g
+ ipvsadm -a -t 192.168.1.250:80 -r 192.168.1.208:80 -g
+ ```
+- 在两台 tomcat 服务器上都做如下配置
+ ```bash
+ #禁用 arp 响应
+ echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
+ echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
+ echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
+ echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce
+ #添加虚拟 IP
+ ip addr add 192.168.1.250/32 brd 192.168.1.250 dev lo
+ ip route add 192.168.1.250 dev lo
+ #在个人浏览器中访问 http://192.168.1.250/webapp/
+ ```
+
diff --git a/content/post/makefile.md b/content/post/makefile.md
new file mode 100644
index 0000000..fc8ca41
--- /dev/null
+++ b/content/post/makefile.md
@@ -0,0 +1,1750 @@
+---
+title: "Makefile 笔记"
+date: 2019-10-30T14:24:58+08:00
+lastmod: 2019-10-30T14:24:58+08:00
+tags: ["makefile"]
+categories: ["dev/ops"]
+---
+
+# Makefile 介绍
+### 介绍
+- make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。
+- 首先,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册,在这个示例中,我们的工程有8个C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
+ - 如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
+ - 如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
+ - 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
+- 只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。
+
+### Makefile的规则
+- 在讲述这个Makefile之前,还是让我们先来粗略地看一看Makefile的规则。
+ ```makefile
+ target... : prerequisites ...
+ command
+ ...
+ ...
+ ```
+ - target 也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的"伪目标"章节中会有叙述。
+ - prerequisites 就是要生成那个target所需要的文件或是目标。
+ - command 也就是make需要执行的命令(任意的Shell命令)。
+- 这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。
+
+### 一个示例
+- 正如前面所说的,如果一个工程有3个头文件,和8个C文件,我们为了完成前面所述的那三个规则,我们的Makefile应该是下面的这个样子的。
+ ```makefile
+ edit : main.o kbd.o command.o display.o \
+ insert.o search.o files.o utils.o
+ cc -o edit main.o kbd.o command.o display.o \
+ insert.o search.o files.o utils.o
+
+ main.o : main.c defs.h
+ cc -c main.c
+ kbd.o : kbd.c defs.h command.h
+ cc -c kbd.c
+ command.o : command.c defs.h command.h
+ cc -c command.c
+ display.o : display.c defs.h buffer.h
+ cc -c display.c
+ insert.o : insert.c defs.h buffer.h
+ cc -c insert.c
+ search.o : search.c defs.h buffer.h
+ cc -c search.c
+ files.o : files.c defs.h buffer.h command.h
+ cc -c files.c
+ utils.o : utils.c defs.h
+ cc -c utils.c
+ clean :
+ rm edit main.o kbd.o command.o display.o \
+ insert.o search.o files.o utils.o
+ ```
+ - 我们可以把这个内容保存在文件为"Makefile"或"makefile"的文件中,然后在该目录下直接输入命令"make"就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下"make clean"就可以了。
+ - 在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件(\*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。
+ - 在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。
+ - 这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
+
+### make 是如何工作的
+- 在默认的方式下,也就是我们只输入make命令。那么,
+ - make会在当前目录下找名字叫"Makefile"或"makefile"的文件。
+ - 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到"edit"这个文件,并把这个文件作为最终的目标文件。
+ - 如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
+ - 如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
+ - 当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件声明make的终极任务,也就是执行文件edit了。
+- 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
+- 通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令 "make clean",以此来清除所有的目标文件,以便重编译。
+- 于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如file.c,那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改时间要比edit要新,所以edit也会被重新链接了(详见edit目标文件后定义的命令)。
+而如果我们改变了"command.h",那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。
+
+### makefile中使用变量
+- 在上面的例子中,先让我们看看edit的规则:
+ ```makefile
+ edit : main.o kbd.o command.o display.o \
+ insert.o search.o files.o utils.o
+ cc -o edit main.o kbd.o command.o display.o \
+ insert.o search.o files.o utils.o
+ ```
+ - 我们可以看到[.o]文件的字符串被重复了两次,如果我们的工程需要加入一个新的[.o]文件,那么我们需要在两个地方加(应该是三个地方,还有一个地方在clean中)。当然,我们的makefile并不复杂,所以在两个地方加也不累,但如果makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。
+- 比如,我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能够表示obj文件就行了。我们在makefile一开始就这样定义:
+ ```makefile
+ objects = main.o kbd.o command.o display.o \
+ insert.o search.o files.o utils.o
+ ```
+- 于是,我们就可以很方便地在我们的makefile中以"$(objects)"的方式来使用这个变量了,于是我们的改良版makefile就变成下面这个样子:
+ ```makefile
+ objects = main.o kbd.o command.o display.o \
+ insert.osearch.o files.o utils.o
+ edit : $(objects)
+ cc -o edit $(objects)
+ main.o : main.c defs.h
+ cc -c main.c
+ kbd.o : kbd.c defs.h command.h
+ cc -c kbd.c
+ command.o : command.c defs.h command.h
+ cc -c command.c
+ display.o : display.c defs.h buffer.h
+ cc -c display.c
+ insert.o : insert.c defs.h buffer.h
+ cc -c insert.c
+ search.o : search.c defs.h buffer.h
+ cc -c search.c
+ files.o : files.c defs.h buffer.h command.h
+ cc -c files.c
+ utils.o : utils.c defs.h
+ cc -c utils.c
+ clean :
+ rm edit $(objects)
+ ```
+ - 如果有新的 .o 文件加入,我们只需简单地修改一下 objects 变量就可以了。
+
+### 让make自动推导
+- GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。
+- 只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。
+ ```makefile
+ objects = main.o kbd.o command.o display.o \
+ insert.o search.o files.o utils.o
+
+ edit : $(objects)
+ cc -o edit $(objects)
+
+ main.o : defs.h
+ kbd.o : defs.h command.h
+ command.o : defs.h command.h
+ display.o : defs.h buffer.h
+ insert.o : defs.h buffer.h
+ search.o : defs.h buffer.h
+ files.o : defs.h buffer.h command.h
+ utils.o : defs.h
+
+ .PHONY : clean
+ clean :
+ rm edit $(objects)
+ ```
+- 这种方法,也就是make的"隐晦规则"。上面文件内容中,".PHONY"表示,clean是个伪目标文件。
+
+### 另类风格的makefile
+- 既然我们的make可以自动推导命令,那么我看到那堆[.o]和[.h]的依赖就有点不爽,那么多的重复的[.h],能不能把其收拢起来,好吧,没有问题,这个对于make来说很容易,谁叫它提供了自动推导命令和文件的功能呢?来看看最新风格的makefile吧。
+ ```makefile
+ objects = main.o kbd.o command.o display.o \
+ insert.o search.o files.o utils.o
+
+ edit : $(objects)
+ cc -o edit $(objects)
+
+ $(objects) : defs.h
+ kbd.o command.o files.o : command.h
+ display.o insert.o search.o files.o : buffer.h
+
+ .PHONY : clean
+ clean :
+ rm edit $(objects)
+ ```
+- 这种风格,让我们的makefile变得很简单,但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了
+
+### 清空目标文件的规则
+- 每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个"修养"(呵呵,还记得我的《编程修养》吗)。一般的风格都是:
+ ```makefile
+ clean:
+ rm edit $(objects)
+ ```
+- 更为稳健的做法是:
+ ```makefile
+ .PHONY : clean
+ clean :
+ -rm edit $(objects)
+ ```
+- 前面说过,.PHONY意思表示clean是一个"伪目标",而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是 "clean从来都是放在文件的最后"。
+
+
+# Makefile 总述
+### Makefile里有什么?
+- Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
+- 显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
+- 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
+- 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
+- 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
+- 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用"#"字符,这个就像C/C++中的"//"一样。如果你要在你的Makefile中使用"#"字符,可以用反斜框进行转义,如:"\#"。
+- 最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。
+
+### Makefile的文件名
+- 默认的情况下,make命令会在当前目录下按顺序找寻文件名为"GNUmakefile"、"makefile"、"Makefile"的文件,找到了解释这个文件。在这三个文件名中,最好使用"Makefile"这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用"GNUmakefile",这个文件是GNU的make识别的。有另外一些make只对全小写的"makefile"文件名敏感,但是基本上来说,大多数的make都支持"makefile"和"Makefile"这两种默认文件名。
+- 当然,你可以使用别的文件名来书写Makefile,比如:"Make.Linux","Make.Solaris","Make.AIX"等,如果要指定特定的Makefile,你可以使用make的"-f"和"--file"参数,如:make -f Make.Linux或make --file Make.AIX。
+
+### 引用其它的Makefile
+- 在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:
+ ```makefile
+ includefilename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)
+ ```
+- 在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和可以用一个或多个空格隔开。举个例子,你有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:
+ ```makefile
+ include foo.make *.mk $(bar)
+ ```
+ - 等价于:
+ ```makefile
+ include foo.make a.mk b.mk c.mk e.mk f.mk
+ ```
+- make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:
+ - 如果make执行时,有"-I"或"--include-dir"参数,那么make就会在这个参数所指定的目录下去寻找。
+ - 如果目录/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。
+- 如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号"-"。如:
+ ```makefile
+ -include
+ ```
+- 其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。
+
+### 环境变量 MAKEFILES
+- 如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的Makefile的"目标"不会起作用,如果环境变量中定义的文件发现错误,make也会不理。
+- 但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。
+
+### make的工作方式
+- GNU的make工作时的执行步骤入下
+ - 读入所有的Makefile。
+ - 读入被include的其它Makefile。
+ - 初始化文件中的变量。
+ - 推导隐晦规则,并分析所有规则。
+ - 为所有的目标文件创建依赖关系链。
+ - 根据依赖关系,决定哪些目标要重新生成。
+ - 执行生成命令。
+- 步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
+- 当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。
+
+# Makefile书写规则
+### 规则
+- 规则包含两个部分,一个是依赖关系,一个是生成目标的方法。
+- 在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make所完成的也就是这个目标。
+- 好了,还是让我们来看一看如何书写规则。
+
+### 规则举例
+ ```makefile
+ foo.o: foo.c defs.h # foo模块
+ cc -c -g foo.c
+ ```
+- 看到这个例子,各位应该不是很陌生了,前面也已说过,foo.o是我们的目标,foo.c和defs.h是目标所依赖的源文件,而只有一个命令"cc -c -g foo.c"(以Tab键开头)。这个规则告诉我们两件事
+ - 文件的依赖关系,foo.o依赖于foo.c和defs.h的文件,如果foo.c和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那么依赖关系发生。
+ - 如果生成(或更新)foo.o文件。也就是那个cc命令,其说明了,如何生成foo.o这个文件。(当然foo.c文件include了defs.h文件)
+
+### 规则的语法
+ ```makefile
+ targets : prerequisites
+ command
+ ...
+ ```
+ - 或是这样:
+ ```makefile
+ targets : prerequisites ; command
+ command
+ ...
+ ```
+- targets 是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。
+- command 是命令行,如果其不与"target:prerequisites"在一行,那么,必须以[Tab键]开头,如果和prerequisites在一行,那么可以用分号做为分隔。(见上)
+- prerequisites 也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是"过时的",被认为是需要重生成的。这个在前面已经讲过了。
+- 如果命令太长,你可以使用反斜框(‘\’)作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事,文件的依赖关系和如何成成目标文件。
+- 一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。
+
+### 在规则中使用通配符
+- 如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make支持三各通配符:"\*","?"和"[...]"。这是和Unix的B-Shell是相同的。
+- "~" 波浪号("~")字符在文件名中也有比较特殊的用途。如果是"~/test",这就表示当前用户的$HOME目录下的test目录。而"~hchen/test"则表示用户hchen的宿主目录下的test目录。(这些都是Unix下的小知识了,make也支持)而在Windows或是MS-DOS下,用户没有宿主目录,那么波浪号所指的目录则根据环境变量"HOME"而定。
+- "\*" 通配符代替了你一系列的文件,如"\*.c"表示所以后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:"\*",那么可以用转义字符"\",如"\*"来表示真实的"\*"字符,而不是任意长度的字符串。
+- 先来看几个例子
+ ```makefile
+ clean:
+ rm -f *.o
+ ```
+- 上面这个例子我不不多说了,这是操作系统Shell所支持的通配符。这是在命令中的通配符。
+ ```makefile
+ print: *.c
+ lpr -p $?
+ touch print
+ ```
+- 上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的[.c]文件。其中的"$?"是一个自动化变量,我会在后面给你讲述。
+ ```makefile
+ objects = *.o
+ ```
+- 上面这个例子,表示了,通符同样可以用在变量中。并不是说[*.o]会展开,不!objects的值就是"*.o"。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的文件名的集合,那么,你可以这样:
+ ```makefile
+ objects := $(wildcard *.o)
+ ```
+- 这种用法由关键字"wildcard"指出,关于Makefile的关键字,我们将在后面讨论。
+
+### 文件搜寻
+- 在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。
+- Makefile文件中的特殊变量"VPATH"就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。
+ ```makefile
+ VPATH = src:../headers
+ ```
+- 上面的的定义指定两个目录,"src"和"../headers",make会按照这个顺序进行搜索。目录由"冒号"分隔。(当然,当前目录永远是最高优先搜索的地方)
+- 另一个设置文件搜索路径的方法是使用make的"vpath"关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:
+ - vpath # 为符合模式的文件指定搜索目录。
+ - vpath # 清除符合模式的文件的搜索目录。
+ - vpath # 清除所有已被设置好了的文件搜索目录。
+- vapth使用方法中的需要包含"%"字符。"%"的意思是匹配零或若干字符,例如,"%.h"表示所有以".h"结尾的文件。< pattern>指定了要搜索的文件集,而< directories>则指定了的文件集的搜索的目录。例如:
+ ```makefile
+ vpath %.h ../headers
+ ```
+- 该语句表示,要求make在"../headers"目录下搜索所有以".h"结尾的文件。(如果某文件在当前目录没有找到的话)
+- 我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的,或是被重复了的,那么,make会按照vpath语句的先后顺序来执行搜索。如
+ ```makefile
+ vpath %.c foo
+ vpath % blish
+ vpath %.c bar
+ ```
+- 其表示".c"结尾的文件,先在"foo"目录,然后是"blish",最后是"bar"目录。
+ ```makefile
+ vpath %.c foo:bar
+ vpath % blish
+ ```
+- 而上面的语句则表示".c"结尾的文件,先在"foo"目录,然后是"bar"目录,最后才是"blish"目录。
+
+### 伪目标
+- 最早先的一个例子中,我们提到过一个"clean"的目标,这是一个"伪目标",
+ ```makefile
+ clean:
+ rm *.o temp
+ ```
+- 正像我们前面例子中的"clean"一样,即然我们生成了许多文件编译文件,我们也应该提供一个清除它们的"目标"以备完整地重编译而用。 (以"make clean"来使用该目标)
+- 因为,我们并不生成"clean"这个文件。"伪目标"并不是一个文件,只是一个标签,由于"伪目标"不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个"目标"才能让其生效。当然,"伪目标"的取名不能和文件名重名,不然其就失去了"伪目标"的意义了。
+- 当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记".PHONY"来显示地指明一个目标是"伪目标",向make说明,不管是否有这个文件,这个目标就是"伪目标"。
+ ```makefile
+ .PHONY : clean
+ ```
+- 只要有这个声明,不管是否有"clean"文件,要运行"clean"这个目标,只有"make clean"这样。于是整个过程可以这样写:
+ ```makefile
+ .PHONY: clean
+ clean:
+ rm *.o temp
+ ```
+- 伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为"默认目标",只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用"伪目标"这个特性:
+ ```makefile
+ all : prog1 prog2 prog3
+ .PHONY : all
+
+ prog1 : prog1.o utils.o
+ cc -o prog1 prog1.o utils.o
+
+ prog2 : prog2.o
+ cc -o prog2 prog2.o
+
+ prog3 : prog3.o sort.o utils.o
+ cc -o prog3 prog3.o sort.o utils.o
+ ```
+- 我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个"all"的伪目标,其依赖于其它三个目标。由于伪目标的特性是,总是被执行的,所以其依赖的那三个目标就总是不如"all"这个目标新。所以,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。".PHONY : all"声明了"all"这个目标为"伪目标"。
+- 随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子:
+ ```makefile
+ .PHONY: cleanall cleanobj cleandiff
+
+ cleanall : cleanobj cleandiff
+ rm program
+
+ cleanobj :
+ rm *.o
+
+ cleandiff :
+ rm *.diff
+ ```
+- "makeclean"将清除所有要被清除的文件。"cleanobj"和"cleandiff"这两个伪目标有点像"子程序"的意思。我们可以输入"makecleanall"和"make cleanobj"和"makecleandiff"命令来达到清除不同种类文件的目的
+
+### 多目标
+- Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令是同一个,这可能会可我们带来麻烦,不过好在我们的可以使用一个自动化变量"$@"(关于自动化变量,将在后面讲述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。
+ ```makefile
+ bigoutput littleoutput : text.g
+ generate text.g -$(subst output,,$@) > $@
+ ```
+ - 上述规则等价于:
+ ```makefile
+ bigoutput : text.g
+ generate text.g -big > bigoutput
+ littleoutput : text.g
+ generate text.g -little > littleoutput
+ ```
+- 其中,-$(subst output,,$@)中的"$"表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是截取字符串的意思,"$@"表示目标的集合,就像一个数组,"$@"依次取出目标,并执于命令。
+
+### 静态模式
+- 静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:
+ ```makefile
+ : :
+
+ ...
+ ```
+ - targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
+ - target-parrtern是指明了targets的模式,也就是的目标集模式。
+ - prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。
+- 这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们的定义成"%.o",意思是我们的集合中都是以".o"结尾的,而如果我们的定义成"%.c",意思是对所形成的目标集进行二次定义,其计算方法是,取模式中的"%"(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。
+- 所以,我们的"目标模式"或是"依赖模式"中都应该有"%"这个字符,如果你的文件名中有"%"那么你可以使用反斜杠"\"进行转义,来标明真实的"%"字符。
+看一个例子:
+ ```makefile
+ objects = foo.o bar.o
+
+ all: $(objects)
+
+ $(objects): %.o: %.c
+ $(CC) -c $(CFLAGS) $< -o $@
+ ```
+- 上面的例子中,指明了我们的目标从$object中获取,"%.o"表明要所有以".o"结尾的目标,也就是"foo.o bar.o",也就是变量$object集合的模式,而依赖模式"%.c"则取模式"%.o"的"%",也就是"foobar",并为其加下".c"的后缀,于是,我们的依赖目标就是"foo.cbar.c"。而命令中的"$<"和"$@"则是自动化变量,"$<"表示所有的依赖目标集(也就是"foo.c bar.c"),"$@"表示目标集(也褪恰癴oo.o bar.o")。于是,上面的规则展开后等价于下面的规则:
+ ```makefile
+ foo.o : foo.c
+ $(CC) -c $(CFLAGS) foo.c -o foo.o
+ bar.o : bar.c
+ $(CC) -c $(CFLAGS) bar.c -o bar.o
+ ```
+- 试想,如果我们的"%.o"有几百个,那种我们只要用这种很简单的"静态模式规则"就可以写完一堆规则,实在是太有效率了。"静态模式规则"的用法很灵活,如果用得好,那会一个很强大的功能。再看一个例子:
+ ```makefile
+ files = foo.elc bar.o lose.o
+
+ $(filter %.o,$(files)): %.o: %.c
+ $(CC) -c $(CFLAGS) $< -o $@
+ $(filter %.elc,$(files)): %.elc: %.el
+ emacs -f batch-byte-compile $<
+ ```
+- $(filter%.o,$(files))表示调用Makefile的filter函数,过滤"$filter"集,只要其中模式为"%.o"的内容。其的它内容,我就不用多说了吧。这个例字展示了Makefile中更大的弹性。
+
+### 自动生成依赖性
+- 在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句"#include "defs.h"",那么我们的依赖关系应该是:
+ ```makefile
+ main.o : main.c defs.h
+ ```
+- 但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个"-M"的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:
+ ```bash
+ cc -M main.c
+ ```
+ - 其输出是:
+ ```
+ main.o : main.c defs.h
+ ```
+- 于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用"-MM"参数,不然,"-M"参数会把一些标准库的头文件也包含进来。
+ ```makefile
+ gcc -M main.c
+ ```
+ - 输出是:
+ ```
+ main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
+ /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
+ /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
+ /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
+ /usr/include/bits/sched.h /usr/include/libio.h \
+ /usr/include/_G_config.h /usr/include/wchar.h \
+ /usr/include/bits/wchar.h /usr/include/gconv.h \
+ /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
+ /usr/include/bits/stdio_lim.h
+ ```
+ ```bash
+ gcc-MM main.c
+ ```
+ - 输出则是:
+ ```
+ main.o: main.c defs.h
+ ```
+- 那么,编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来,我们的Makefile也要根据这些源文件重新生成,让Makefile自已依赖于源文件?这个功能并不现实,不过我们可以有其它手段来迂回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个"name.c"的文件都生成一个"name.d"的Makefile文件,[.d]文件中就存放对应[.c]文件的依赖关系。
+- 于是,我们可以写出[.c]文件和[.d]文件的依赖关系,并让make自动更新或自成[.d]文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。
+- 这里,我们给出了一个模式规则来产生[.d]文件:
+ ```makefile
+ %.d: %.c
+ @set -e; rm -f $@; \
+ $(CC) -M $(CPPFLAGS) $< > $@.; \
+ sed 's,$*\.o[ :]*,\1.o $@ : ,g' < $@. > $@; \
+ rm -f $@.
+ ```
+- 这个规则的意思是,所有的[.d]文件依赖于[.c]文件,"rm-f $@"的意思是删除所有的目标,也就是[.d]文件,第二行的意思是,为每个依赖文件"$<",也就是[.c]文件生成依赖文件,"$@"表示模式"%.d"文件,如果有一个C文件是name.c,那么"%"就是"name",""意为一个随机编号,第二行生成的文件有可能是"name.d.12345",第三行使用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。
+- 总而言之,这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖,即把依赖关系:
+ ```bash
+ main.o : main.c defs.h
+ ```
+- 转成:
+ ```bash
+ main.o main.d : main.c defs.h
+ ```
+- 于是,我们的[.d]文件也会自动更新了,并会自动生成了,当然,你还可以在这个[.d]文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个[.d]文件都包含一个完赖的规则。一旦我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的"include"命令,来引入别的Makefile文件(前面讲过),例如:
+ ```makefile
+ sources = foo.c bar.c
+ include $(sources:.c=.d)
+ ```
+- 上述语句中的"$(sources:.c=.d)"中的".c=.d"的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d],关于这个"替换"的内容,在后面我会有更为详细的讲述。当然,你得注意次序,因为include是按次来载入文件,最先载入的[.d]文件中的目标会成为默认目标
+
+# Makefile 书写命令
+### 命令
+- 每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make会认为其是一个空命令。
+- 我们在UNIX下可能会使用不同的Shell,但是make的命令默认是被"/bin/sh" UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中,"#"是注释符,很像C/C++中的"//",其后的本行字符都被注释。
+
+### 显示命令
+- 通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用"@"字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:
+ ```makefile
+ @echo 正在编译XXX模块......
+ ```
+- 当make执行时,会输出"正在编译XXX模块......"字串,但不会输出命令,如果没有"@",那么,make将输出:
+ ```makefile
+ echo 正在编译XXX模块......
+ 正在编译XXX模块......
+ ```
+- 如果make执行时,带入make参数"-n"或"--just-print",那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。
+- make参数"-s"或"--slient"则是全面禁止命令的显示。
+
+### 命令执行
+- 当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如:
+ ```makefile
+ # 示例一:
+ exec:
+ cd /home/hchen
+ pwd
+
+ # 示例二:
+ exec:
+ cd /home/hchen; pwd
+ ```
+- 当我们执行"make exec"时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第二个例子中,cd就起作用了,pwd会打印出"/home/hchen"。
+- make 一般是使用环境变量SHELL中所定义的系统Shell来执行命令,默认情况下使用UNIX的标准Shell /bin/sh来执行命令。但在MS-DOS下有点特殊,因为MS-DOS下没有SHELL环境变量,当然你也可以指定。如果你指定了UNIX风格的目录形式,首先,make会在SHELL所指定的路径中找寻命令解释器,如果找不到,其会在当前盘符中的当前目录中寻找,如果再找不到,其会在PATH环境变量中所定义的所有路径中寻找。MS-DOS中,如果你定义的命令解释器没有找到,其会给你的命令解释器加上诸如".exe"、".com"、".bat"、".sh"等后缀。
+
+### 命令出错
+- 每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。
+- 有些时候,命令的出错并不表示就是错误的。例如mkdir命令,我们一定需要建立一个目录,如果目录不存在,那么mkdir就成功执行,万事大吉,如果目录存在,那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录,于是我们就不希望mkdir出错而终止规则的运行。
+- 为了做到这一点,忽略命令的出错,我们可以在Makefile的命令行前加一个减号"-"(在Tab键之后),标记为不管命令出不出错都认为是成功的。如:
+ ```makefile
+ clean:
+ -rm -f *.o
+ ```
+- 还有一个全局的办法是,给make加上"-i"或是"--ignore-errors"参数,那么,Makefile中所有命令都会忽略错误。而如果一个规则是以".IGNORE"作为目标的,那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。
+- 还有一个要提一下的make的参数的是"-k"或是"--keep-going",这个参数的意思是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。
+
+### 嵌套执行make
+- 在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。
+- 例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:
+ ```makefile
+ subsystem:
+ cd subdir && $(MAKE)
+ ```
+ - 其等价于:
+ ```makefile
+ subsystem:
+ $(MAKE) -C subdir
+ ```
+- 定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入"subdir"目录,然后执行make命令。
+- 我们把这个Makefile叫做"总控Makefile",总控Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了"-e"参数。
+- 如果你要传递变量到下级Makefile中,那么你可以使用这样的声明:
+ ```makefile
+ export
+ ```
+- 如果你不想让某些变量传递到下级Makefile中,那么你可以这样声明:
+ ```makefile
+ unexport
+ ```
+- 如:
+ ```makefile
+ # 示例一:
+ export variable = value
+ # 其等价于:
+ variable = value
+ export variable
+ # 其等价于:
+ export variable := value
+ # 其等价于:
+ variable := value
+ export variable
+
+ # 示例二:
+ export variable += value
+ # 其等价于:
+ variable += value
+ export variable
+ ```
+- 如果你要传递所有的变量,那么,只要一个export就行了。后面什么也不用跟,表示传递所有的变量。
+- 需要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别是MAKEFILES变量,其中包含了make的参数信息,如果我们执行"总控Makefile"时有make参数或是在上层Makefile中定义了这个变量,那么MAKEFILES变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。
+- 但是make命令中的有几个参数并不往下传递,它们是"-C","-f","-h""-o"和"-W"(有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:
+ ```makefile
+ subsystem:
+ cd subdir && $(MAKE) MAKEFLAGS=
+ ```
+- 如果你定义了环境变量MAKEFLAGS,那么你得确信其中的选项是大家都会用到的,如果其中有"-t","-n",和"-q"参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。
+- 还有一个在"嵌套执行"中比较有用的参数,"-w"或是"--print-directory"会在make的过程中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级make目录是"/home/hchen/gnu/make",如果我们使用"make -w"来执行,那么当进入该目录时,我们会看到:
+ ```
+ make: Entering directory `/home/hchen/gnu/make'.
+ ```
+- 而在完成下层make后离开目录时,我们会看到:
+ ```
+ make: Leaving directory `/home/hchen/gnu/make'
+ ```
+- 当你使用"-C"参数来指定make下层Makefile时,"-w"会被自动打开的。如果参数中有"-s"("--slient")或是"--no-print-directory",那么,"-w"总是失效的。
+
+### 定义命令包
+- 如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以"define"开始,以"endef"结束,如:
+ ```makefile
+ define run-yacc
+ yacc $(firstword $^)
+ mv y.tab.c $@
+ endef
+ ```
+- 这里,"run-yacc"是这个命令包的名字,其不要和Makefile中的变量重名。在"define"和"endef"中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序,因为Yacc程序总是生成"y.tab.c"的文件,所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看看吧。
+ ```makefile
+ foo.c : foo.y
+ $(run-yacc)
+ ```
+- 我们可以看见,要使用这个命令包,我们就好像使用变量一样。在这个命令包的使用中,命令包"run-yacc"中的"$^"就是"foo.y","$@"就是"foo.c"(有关这种以"$"开头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。
+- 在 Makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在Makefile中改变其值。在Makefile中,变量可以使用在"目标","依赖目标","命令"或是 Makefile的其它部分中。变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有":"、"#"、"="或是空字符(空格、回车等)。变量是大小写敏感的,"foo"、"Foo"和"FOO"是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式,但我推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。有一些变量是很奇怪字串,如"$<"、"$@"等,这些是自动化变量。
+
+# 变量
+### 变量的基础
+- 变量在声明时需要给予初值,而在使用时,需要给在变量名前加上"$"符号,但最好用小括号"()"或是大括号"{}"把变量给包括起来。如果你要使用真实的"$"字符,那么你需要用"$$"来表示。变量可以使用在许多地方,如规则中的"目标"、"依赖"、"命令"以及新的变量中。
+- 先看一个例子:
+ ```makefile
+ objects = program.o foo.o utils.o
+ program : $(objects)
+ cc -o program $(objects)
+
+ $(objects) : defs.h
+ ```
+- 变量会在使用它的地方精确地展开,就像C/C++中的宏一样,例如:
+ ```makefile
+ foo = c
+ prog.o : prog.$(foo)
+ $(foo)$(foo) -$(foo) prog.$(foo)
+ ```
+- 展开后得到:
+ ```makefile
+ prog.o : prog.c
+ cc -c prog.c
+ ```
+- 当然,千万不要在你的Makefile中这样干,这里只是举个例子来表明Makefile中的变量在使用处展开的真实样子。可见其就是一个"替代"的原理。另外,给变量加上括号完全是为了更加安全地使用这个变量,在上面的例子中,如果你不想给变量加上括号,那也可以,但我还是强烈建议你给变量加上括号。
+
+### 变量中的变量
+- 在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来在用变量定义变量的值。
+- 先看第一种方式,也就是简单的使用"="号,在"="左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好
+的值,其也可以使用后面定义的值。如:
+ ```makefile
+ foo = $(bar)
+ bar = $(ugh)
+ ugh = Huh?
+ all:
+ echo $(foo)
+ ```
+- 我们执行"make all"将会打出变量$(foo)的值是"Huh?"( $(foo)的值是$(bar),$(bar)的值是$(ugh),$(ugh)的值是"Huh?")可见,变量是可以使用后面的变量来定义的。
+- 这个功能有好的地方,也有不好的地方,好的地方是,我们可以把变量的真实值推到后面来定义,如:
+ ```makefile
+ CFLAGS = $(include_dirs) -O
+ include_dirs = -Ifoo -Ibar
+ ```
+- 当"CFLAGS"在命令中被展开时,会是"-Ifoo -Ibar -O"。但这种形式也有不好的地方,那就是递归定义,如:
+ ```makefile
+ CFLAGS = $(CFLAGS) -O
+ ```
+- 或:
+ ```makefile
+ A = $(B)
+ B = $(A)
+ ```
+- 这会让make陷入无限的变量展开过程中去,当然,我们的make是有能力检测这样的定义,并会报错。还有就是如果在变量中使用函数,那么,这种方式会让我们的make运行时非常慢,更糟糕的是,他会使用得两个make的函数"wildcard"和"shell"发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。
+- 为了避免上面的这种方法,我们可以使用make中的另一种用变量来定义变量的方法。这种方法使用的是":="操作符,如:
+ ```makefile
+ x := foo
+ y := $(x) bar
+ x := later
+ ```
+- 其等价于:
+ ```makefile
+ y := foo bar
+ x := later
+ ```
+- 值得一提的是,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这样:
+ ```makefile
+ y := $(x) bar
+ x := foo
+ ```
+- 那么,y的值是"bar",而不是"foo bar"。
+- 上面都是一些比较简单的变量使用了,让我们来看一个复杂的例子,其中包括了make的函数、条件表达式和一个系统变量"MAKELEVEL"的使用:
+ ```makefile
+ ifeq (0,${MAKELEVEL})
+ cur-dir := $(shell pwd)
+ whoami := $(shell whoami)
+ host-type := $(shell arch)
+ MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
+ endif
+ ```
+- 关于条件表达式和函数,我们在后面再说,对于系统变量"MAKELEVEL",其意思是,如果我们的make有一个嵌套执行的动作(参见前面的"嵌套使用make"),那么,这个变量会记录了我们的当前Makefile的调用层数。
+- 下面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来:
+ ```makefile
+ nullstring :=
+ space := $(nullstring) # end of the line
+ ```
+- nullstring 是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个 Empty变量来标明变量的值开始了,而后面采用"#"注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意这里关于"#"的使用,注释符"#"的这种特性值得我们注意,如果我们这样定义一个变量:
+ ```makefile
+ dir := /foo/bar # directory to put the frobs in
+ ```
+- dir这个变量的值是"/foo/bar",后面还跟了4个空格,如果我们这样使用这样变量来指定别的目录 "$(dir)/file"那么就完蛋了。
+- 还有一个比较有用的操作符是"?=",先看示例:
+ ```makefile
+ FOO ?= bar
+ ```
+- 其含义是,如果FOO没有被定义过,那么变量FOO的值就是"bar",如果FOO先前被定义过,那么这条语将什么也不做,其等价于:
+ ```makefile
+ ifeq ($(origin FOO), undefined)
+ FOO = bar
+ endif
+ ```
+
+### 变量高级用法
+- 这里介绍两种变量的高级使用方法,第一种是变量值的替换。
+- 我们可以替换变量中的共有的部分,其格式是"$(var:a=b)"或是"${var:a=b}",其意思是,把变量"var"中所有以"a"字串"结尾"的"a"替换成"b"字串。这里的"结尾"意思是"空格"或是"结束符"。
+- 还是看一个示例吧:
+ ```makefile
+ foo := a.o b.o c.o
+ bar := $(foo:.o=.c)
+ ```
+- 这个示例中,我们先定义了一个"$(foo)"变量,而第二行的意思是把"$(foo)"中所有以".o"字串"结尾"全部替换成".c",所以我们的"$(bar)"的值就是"a.c b.c c.c"。
+- 另外一种变量替换的技术是以"静态模式"(参见前面章节)定义的,如:
+ ```makefile
+ foo := a.o b.o c.o
+ bar := $(foo:%.o=%.c)
+ ```
+- 这依赖于被替换字串中的有相同的模式,模式中必须包含一个"%"字符,这个例子同样让$(bar)变量的值为"a.c b.c c.c"。
+- 第二种高级用法是 "把变量的值再当成变量"。先看一个例子:
+ ```makefile
+ x = y
+ y = z
+ a := $($(x))
+ ```
+- 在这个例子中,$(x)的值是"y",所以$($(x))就是$(y),于是$(a)的值就是"z"。(注意,是"x=y",而不是"x=$(y)")
+我们还可以使用更多的层次:
+ ```makefile
+ x = y
+ y = z
+ z = u
+ a := $($($(x)))
+ ```
+- 这里的$(a)的值是"u",相关的推导留给读者自己去做吧。
+- 让我们再复杂一点,使用上"在变量定义中使用变量"的第一个方式,来看一个例子:
+ ```makefile
+ x = $(y)
+ y = z
+ z = Hello
+ a := $($(x))
+ ```
+- 这里的$($(x))被替换成了$($(y)),因为$(y)值是"z",所以,最终结果是:a:=$(z),也就是"Hello"。
+- 再复杂一点,我们再加上函数:
+ ```makefile
+ x = variable1
+ variable2 := Hello
+ y = $(subst 1,2,$(x))
+ z = y
+ a := $($($(z)))
+ ```
+- 这个例子中,"$($($(z)))"扩展为"$($(y))",而其再次被扩展为"$($(subst 1,2,$(x)))"。$(x)的值是"variable1",subst函数把"variable1"中的所有"1"字串替换成"2"字串,于是,"variable1"变成"variable2",再取其值,所以,最终,$(a)的值就是$(variable2)的值 "Hello"。(喔,好不容易)
+- 在这种方式中,或要可以使用多个变量来组成一个变量的名字,然后再取其值:
+ ```makefile
+ first_second = Hello
+ a = first
+ b = second
+ all = $($a_$b)
+ ```
+- 这里的"$a_$b"组成了"first_second",于是,$(all)的值就是"Hello"。
+- 再来看看结合第一种技术的例子:
+ ```makefile
+ a_objects := a.o b.o c.o
+ 1_objects := 1.o 2.o 3.o
+ sources := $($(a1)_objects:.o=.c)
+ ```
+- 这个例子中,如果$(a1)的值是"a"的话,那么,$(sources)的值就是"a.c b.c c.c";如果$(a1)的值是"1",那么$(sources)的值是"1.c 2.c 3.c"。
+- 再来看一个这种技术和"函数"与"条件语句"一同使用的例子:
+ ```makefile
+ ifdef do_sort
+ func := sort
+ else
+ func := strip
+ endif
+
+ bar := a d b g q c
+
+ foo := $($(func) $(bar))
+ ```
+- 这个示例中,如果定义了"do_sort",那么:foo := $(sort a d b g q c),于是$(foo)的值就是"a b c d g q",而如果没有定义"do_sort",那么:foo := $(sort a d bg q c),调用的就是strip函数。
+- 当然,"把变量的值再当成变量"这种技术,同样可以用在操作符的左边:
+ ```makefile
+ dir = foo
+ $(dir)_sources := $(wildcard $(dir)/*.c)
+ define $(dir)_print
+ lpr $($(dir)_sources)
+ endef
+ ```
+- 这个例子中定义了三个变量:"dir","foo_sources"和"foo_print"。
+
+### 追加变量值
+- 我们可以使用"+="操作符给变量追加值,如:
+ ```makefile
+ objects = main.o foo.o bar.o utils.o
+ objects += another.o
+ ```
+- 于是,我们的$(objects)值变成:"main.o foo.o bar.o utils.o another.o"(another.o被追加进去了)
+- 使用"+="操作符,可以模拟为下面的这种例子:
+ ```makefile
+ objects = main.o foo.o bar.o utils.o
+ objects := $(objects) another.o
+ ```
+- 所不同的是,用"+="更为简洁。
+- 如果变量之前没有定义过,那么,"+="会自动变成"=",如果前面有变量定义,那么"+="会继承于前次操作的赋值符。如果前一次的是":=",那么"+="会以":="作为其赋值符,如:
+ ```makefile
+ variable := value
+ variable += more
+ ```
+- 等价于:
+ ```makefile
+ variable := value
+ variable := $(variable) more
+ ```
+- 但如果是这种情况:
+ ```makefile
+ variable = value
+ variable += more
+ ```
+- 由于前次的赋值符是"=",所以"+="也会以"="来做为赋值,那么岂不会发生变量的递补归定义,这是很不好的,所以make会自动为我们解决这个问题,我们不必担心这个问题。
+
+### override 指示符
+- 如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用"override"指示符。其语法是:
+ ```makefile
+ override =
+ override :=
+ ```
+- 当然,你还可以追加:
+ ```makefile
+ override +=
+ ```
+- 对于多行的变量定义,我们用define指示符,在define指示符前,也同样可以使用ovveride指示符,如:
+ ```makefile
+ override define foo
+ bar
+ endef
+ ```
+
+### 多行变量
+- 还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令(前面我们讲过"命令包"的技术就是利用这个关键字)。
+- define 指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束。其工作方式和"="操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你用define定义的命令变量中没有以[Tab]键开头,那么make就不会把其认为是命令。
+- 下面的这个示例展示了define的用法:
+ ```makefile
+ define two-lines
+ echo foo
+ echo $(bar)
+ endef
+ ```
+
+### 环境变量
+- make 运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了"-e"参数,那么,系统环境变量将覆盖Makefile中定义的变量)
+- 因此,如果我们在环境变量中设置了"CFLAGS"环境变量,那么我们就可以在所有的Makefile中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS,那么则会使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像"全局变量"和"局部变量"的特性。 当make嵌套调用时(参见前面的"嵌套调用"章节),上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层 Makefile传递,则需要使用exprot关键字来声明。(参见前面章节)
+- 当然,我并不推荐把许多的变量都定义在系统环境中,这样,在我们执行不用的Makefile时,拥有的是同一套系统变量,这可能会带来更多的麻烦。
+
+### 目标变量
+- 前面我们所讲的在Makefile中定义的变量都是"全局变量",在整个文件,我们都可以访问这些变量。当然,"自动化变量"除外,如"$<"等这种类量的自动化变量就属于"规则型变量",这种变量的值依赖于规则的目标和依赖目标的定义。
+- 当然,我样同样可以为某个目标设置局部变量,这种变量被称为"Target-specific Variable",它可以和"全局变量"同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。
+- 其语法是:
+ ```makefile
+ :
+ : overide
+ ```
+- 可以是前面讲过的各种赋值表达式,如"="、":="、"+="或是"?="。第二个语法是针对于make命令行带入的变量,或是系统环境变量。
+- 这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如:
+ ```makefile
+ prog : CFLAGS = -g
+ prog : prog.o foo.o bar.o
+ $(CC) $(CFLAGS) prog.o foo.o bar.o
+ prog.o : prog.c
+ $(CC) $(CFLAGS) prog.c
+
+ foo.o : foo.c
+ $(CC) $(CFLAGS) foo.c
+
+ bar.o : bar.c
+ $(CC) $(CFLAGS) bar.c
+ ```
+- 在这个示例中,不管全局的$(CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是"-g"
+
+### 模式变量
+- 在GNU的make中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种"模式",可以把变量定义在符合这种模式的所有目标上。
+- 我们知道,make的"模式"一般是至少含有一个"%"的,所以,我们可以以如下方式给所有以[.o]结尾的目标定义目标变量:
+ ```makefile
+ %.o : CFLAGS = -O
+ ```
+- 同样,模式变量的语法和"目标变量"一样:
+ ```makefile
+ :
+ : override
+ ```
+- override同样是针对于系统环境传入的变量,或是make命令行指定的变量。
+
+# 条件判断
+### 一个例子
+- 下面的例子,判断$(CC)变量是否"gcc",如果是的话,则使用GNU函数编译目标。
+ ```makefile
+ libs_for_gcc = -lgnu
+ normal_libs =
+
+ foo: $(objects)
+ ifeq ($(CC),gcc)
+ $(CC) -o foo $(objects) $(libs_for_gcc)
+ else
+ $(CC) -o foo $(objects) $(normal_libs)
+ endif
+ ```
+- 可见,在上面示例的这个规则中,目标"foo"可以根据变量"$(CC)"值来选取不同的函数库来编译程序。
+- 我们可以从上面的示例中看到三个关键字:ifeq、else和endif。ifeq的意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起。else表示条件表达式为假的情况。endif表示一个条件语句的结束,任何一个条件表达式都应该以endif结束。
+- 当我们的变量$(CC)值是"gcc"时,目标foo的规则是:
+ ```makefile
+ foo: $(objects)
+ $(CC) -o foo $(objects) $(libs_for_gcc)
+ ```
+- 而当我们的变量$(CC)值不是"gcc"时(比如"cc"),目标foo的规则是:
+ ```makefile
+ foo: $(objects)
+ $(CC) -o foo $(objects) $(normal_libs)
+ ```
+- 当然,我们还可以把上面的那个例子写得更简洁一些:
+ ```makefile
+ libs_for_gcc = -lgnu
+ normal_libs =
+
+ ifeq ($(CC),gcc)
+ libs=$(libs_for_gcc)
+ else
+ libs=$(normal_libs)
+ endif
+
+ foo: $(objects)
+ $(CC) -o foo $(objects) $(libs)
+ ```
+
+### 语法
+- 条件表达式的语法为:
+ ```makefile
+
+
+ endif
+ ```
+- 以及:
+ ```makefile
+
+
+ else
+
+ endif
+ ```
+- 其中表示条件关键字,如"ifeq"。这个关键字有四个。
+- 第一个是我们前面所见过的"ifeq"
+ ```makefile
+ ifeq (, )
+ ifeq '' ''
+ ifeq "" ""
+ ifeq "" ''
+ ifeq '' ""
+ ```
+- 比较参数"arg1"和"arg2"的值是否相同。当然,参数中我们还可以使用make的函数。如:
+ ```makefile
+ ifeq ($(strip $(foo)),)
+
+ endif
+ ```
+- 这个示例中使用了"strip"函数,如果这个函数的返回值是空(Empty),那么就生效。
+- 第二个条件关键字是"ifneq"。语法是:
+ ```makefile
+ ifneq (, )
+ ifneq '' ''
+ ifneq "" ""
+ ifneq "" '