├── traefik-middleware.md ├── .gitignore ├── htop.md ├── linux-awk.md ├── iptables.md ├── assets ├── buy.jpg ├── git.jpg ├── ctop.png ├── drone.jpg ├── htop.jpg ├── htop.png ├── lock.png ├── vim-ag.gif ├── dev-env.png ├── grafana.jpg ├── graphql.jpg ├── traefik.jpg ├── vim-dark.png ├── vim-git.gif ├── vim-goyo.png ├── alioss-cdn.png ├── cdn-cache.jpg ├── ctop-view.png ├── deploy-blog.jpg ├── dev.drawio.png ├── netlify-2.jpg ├── netlify-ok.png ├── sentry-ok.png ├── tmux-help.jpg ├── tmux-split.gif ├── vim-ctrlp.gif ├── action-result.png ├── action-secret.png ├── action-select.jpg ├── action-setup.jpg ├── action-start.png ├── alioss-cname.png ├── alioss-domain.png ├── alioss-https.png ├── alioss-proxy.png ├── netlify-step2.jpg ├── node_modules.jpeg ├── node_modules.jpg ├── sentry-memory.png ├── vim-nerdtree.gif ├── action-workflow.png ├── alioss-rewrite.png ├── alioss-rewrites.png ├── netlify-new-site.jpg ├── traefik-dashboard.png ├── linux_perf_tools_full.png ├── netlify-build-options.jpg ├── netlify-custom-domain.jpg └── netlify-repo-access.jpg ├── dev-log.md ├── compose ├── dns │ ├── dnsmasq.conf │ ├── resolv.conf │ └── docker-compose.yaml ├── redis │ └── docker-compose.yaml ├── openvpn │ └── docker-compose.yaml ├── whoami │ └── docker-compose.yml ├── postgres │ └── docker-compose.yaml ├── chromeless │ └── docker-compose.yaml └── traefik │ ├── docker-compose.yml │ └── traefik.toml ├── ansible-problem.md ├── ctop.md ├── deploy-redis.md ├── linux-tcpdump.md ├── docker-compose-arch.md ├── deploy-postgres.md ├── traefik-https.md ├── git.md ├── init.md ├── Readme.md ├── ssh-setting.md ├── dnsmasq.md ├── system-info.md ├── docker-compose.md ├── jq.md ├── when-server-2019.md ├── linux-sed.md ├── deploy-sentry.md ├── dockerfile-practice.md ├── when-server.md ├── deploy-drone.md ├── openvpn.md ├── wechat-interview.md ├── deploy-fe-with-docker.md ├── ansible-guide.md ├── jq-sed-case.md ├── traefik.md ├── vim-setting.md ├── tmux-setting.md ├── blog-to-wechat.md ├── docker.md ├── deploy-fe.md └── linux-monitor.md /traefik-middleware.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.ovpn 3 | *.json 4 | *-data 5 | log -------------------------------------------------------------------------------- /htop.md: -------------------------------------------------------------------------------- 1 | # 使用 htop 监控进程 2 | 3 | ``` bash 4 | htop 5 | ``` -------------------------------------------------------------------------------- /linux-awk.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: awk 命令使用及示例 3 | 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /iptables.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: iptables 详解以及示例 3 | 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/buy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/buy.jpg -------------------------------------------------------------------------------- /assets/git.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/git.jpg -------------------------------------------------------------------------------- /assets/ctop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/ctop.png -------------------------------------------------------------------------------- /assets/drone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/drone.jpg -------------------------------------------------------------------------------- /assets/htop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/htop.jpg -------------------------------------------------------------------------------- /assets/htop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/htop.png -------------------------------------------------------------------------------- /assets/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/lock.png -------------------------------------------------------------------------------- /assets/vim-ag.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/vim-ag.gif -------------------------------------------------------------------------------- /assets/dev-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/dev-env.png -------------------------------------------------------------------------------- /assets/grafana.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/grafana.jpg -------------------------------------------------------------------------------- /assets/graphql.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/graphql.jpg -------------------------------------------------------------------------------- /assets/traefik.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/traefik.jpg -------------------------------------------------------------------------------- /assets/vim-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/vim-dark.png -------------------------------------------------------------------------------- /assets/vim-git.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/vim-git.gif -------------------------------------------------------------------------------- /assets/vim-goyo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/vim-goyo.png -------------------------------------------------------------------------------- /assets/alioss-cdn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/alioss-cdn.png -------------------------------------------------------------------------------- /assets/cdn-cache.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/cdn-cache.jpg -------------------------------------------------------------------------------- /assets/ctop-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/ctop-view.png -------------------------------------------------------------------------------- /assets/deploy-blog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/deploy-blog.jpg -------------------------------------------------------------------------------- /assets/dev.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/dev.drawio.png -------------------------------------------------------------------------------- /assets/netlify-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/netlify-2.jpg -------------------------------------------------------------------------------- /assets/netlify-ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/netlify-ok.png -------------------------------------------------------------------------------- /assets/sentry-ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/sentry-ok.png -------------------------------------------------------------------------------- /assets/tmux-help.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/tmux-help.jpg -------------------------------------------------------------------------------- /assets/tmux-split.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/tmux-split.gif -------------------------------------------------------------------------------- /assets/vim-ctrlp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/vim-ctrlp.gif -------------------------------------------------------------------------------- /assets/action-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/action-result.png -------------------------------------------------------------------------------- /assets/action-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/action-secret.png -------------------------------------------------------------------------------- /assets/action-select.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/action-select.jpg -------------------------------------------------------------------------------- /assets/action-setup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/action-setup.jpg -------------------------------------------------------------------------------- /assets/action-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/action-start.png -------------------------------------------------------------------------------- /assets/alioss-cname.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/alioss-cname.png -------------------------------------------------------------------------------- /assets/alioss-domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/alioss-domain.png -------------------------------------------------------------------------------- /assets/alioss-https.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/alioss-https.png -------------------------------------------------------------------------------- /assets/alioss-proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/alioss-proxy.png -------------------------------------------------------------------------------- /assets/netlify-step2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/netlify-step2.jpg -------------------------------------------------------------------------------- /assets/node_modules.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/node_modules.jpeg -------------------------------------------------------------------------------- /assets/node_modules.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/node_modules.jpg -------------------------------------------------------------------------------- /assets/sentry-memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/sentry-memory.png -------------------------------------------------------------------------------- /assets/vim-nerdtree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/vim-nerdtree.gif -------------------------------------------------------------------------------- /assets/action-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/action-workflow.png -------------------------------------------------------------------------------- /assets/alioss-rewrite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/alioss-rewrite.png -------------------------------------------------------------------------------- /assets/alioss-rewrites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/alioss-rewrites.png -------------------------------------------------------------------------------- /assets/netlify-new-site.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/netlify-new-site.jpg -------------------------------------------------------------------------------- /assets/traefik-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/traefik-dashboard.png -------------------------------------------------------------------------------- /assets/linux_perf_tools_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/linux_perf_tools_full.png -------------------------------------------------------------------------------- /assets/netlify-build-options.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/netlify-build-options.jpg -------------------------------------------------------------------------------- /assets/netlify-custom-domain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/netlify-custom-domain.jpg -------------------------------------------------------------------------------- /assets/netlify-repo-access.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfshanyue/op-note/HEAD/assets/netlify-repo-access.jpg -------------------------------------------------------------------------------- /dev-log.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## IP 4 | 5 | ``` bash 6 | # server 7 | $ dig shanyue.tech 8 | 9 | $ curl ifconfig.me 10 | ``` -------------------------------------------------------------------------------- /compose/dns/dnsmasq.conf: -------------------------------------------------------------------------------- 1 | log-queries 2 | log-dhcp 3 | 4 | address=/docker.localhost/172.18.0.1 5 | address=/shanyue.local/172.18.0.1 6 | 7 | -------------------------------------------------------------------------------- /compose/dns/resolv.conf: -------------------------------------------------------------------------------- 1 | options timeout:2 attempts:3 rotate single-request-reopen 2 | nameserver 100.100.2.136 3 | nameserver 100.100.2.138 4 | -------------------------------------------------------------------------------- /compose/redis/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | redis: 5 | image: redis:5-alpine 6 | restart: always 7 | ports: 8 | - 6379:6379 9 | labels: 10 | - traefik.http.routers.db.rule=Host(`redis.shanyue.local`) 11 | 12 | # 使用已存在的 traefik 的 network 13 | networks: 14 | default: 15 | external: 16 | name: traefik_default 17 | -------------------------------------------------------------------------------- /compose/dns/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | dns: 5 | image: jpillora/dnsmasq 6 | restart: always 7 | ports: 8 | - "53:53/udp" 9 | volumes: 10 | - ./dnsmasq.conf:/etc/dnsmasq.conf 11 | - ./resolv.conf:/etc/resolv.conf 12 | 13 | # 使用已存在的 traefik 的 network 14 | networks: 15 | default: 16 | external: 17 | name: traefik_default 18 | -------------------------------------------------------------------------------- /compose/openvpn/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | openvpn: 4 | cap_add: 5 | - NET_ADMIN 6 | image: kylemanna/openvpn 7 | container_name: openvpn 8 | ports: 9 | - "1194:1194/udp" 10 | restart: always 11 | volumes: 12 | - ./openvpn-data/conf:/etc/openvpn 13 | 14 | networks: 15 | default: 16 | external: 17 | name: traefik_default 18 | -------------------------------------------------------------------------------- /compose/whoami/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | whoami: 5 | image: containous/whoami 6 | labels: 7 | - traefik.http.routers.whoami.rule=Host(`whoami.shanyue.tech`) 8 | - traefik.http.routers.whoami.tls=true 9 | - traefik.http.routers.whoami.tls.certresolver=le 10 | # environments: 11 | # TMUX 12 | 13 | networks: 14 | default: 15 | external: 16 | name: traefik_default 17 | -------------------------------------------------------------------------------- /compose/postgres/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | db: 5 | image: postgres:12-alpine 6 | restart: always 7 | ports: 8 | - 5432:5432 9 | volumes: 10 | - ./pg-data:/var/lib/postgresql/data 11 | labels: 12 | - "traefik.http.routers.db.rule=Host(`db.shanyue.local`)" 13 | 14 | # 使用已存在的 traefik 的 network 15 | networks: 16 | default: 17 | external: 18 | name: traefik_default 19 | 20 | -------------------------------------------------------------------------------- /compose/chromeless/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | chrome: 5 | image: browserless/chrome 6 | restart: always 7 | ports: 8 | - "3000:3000" 9 | labels: 10 | - "traefik.http.routers.chrome.rule=Host(`chrome.shanyue.tech`)" 11 | - traefik.http.routers.chrome.tls=true 12 | - traefik.http.routers.chrome.tls.certresolver=le 13 | 14 | # 使用已存在的 traefik 的 network 15 | networks: 16 | default: 17 | external: 18 | name: traefik_default 19 | -------------------------------------------------------------------------------- /compose/traefik/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | reverse-proxy: 5 | image: traefik:v2.2 6 | ports: 7 | - "80:80" 8 | - "443:443" 9 | - "8080:8080" 10 | volumes: 11 | - ./traefik.toml:/etc/traefik/traefik.toml 12 | - ./acme.json:/acme.json 13 | - ./log:/log 14 | - /var/run/docker.sock:/var/run/docker.sock 15 | container_name: traefik 16 | env_file: .env 17 | labels: 18 | - "traefik.http.routers.api.rule=Host(`traefik.shanyue.local`)" 19 | - "traefik.http.routers.api.service=api@internal" 20 | 21 | -------------------------------------------------------------------------------- /ansible-problem.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 带着问题学习 ansible 3 | tags: 4 | - devops 5 | - linux 6 | 7 | --- 8 | 9 | # ansible 中的细节问题 10 | 11 | 如何更快地学习某门技术? 12 | 13 | + 学习示例,比如 ansible 可以查看 [ansible/ansible-examples](https://github.com/ansible/ansible-examples) 14 | + 带着问题来思考 15 | 16 | 于是我总结了在我初学 ansible 时所带的一些疑问 17 | 18 | ### 当某个 task 执行错误时不中断操作 19 | 20 | 添加参数 `ignore_errors: true` 21 | 22 | ```yaml 23 | - name: install pip 24 | register: pip 25 | yum: 26 | name: python-pip 27 | ignore_errors: true 28 | ``` 29 | 30 | ### 如何根据 task 执行结果来作为分支条件 31 | 32 | 使用 `register` 监听当前任务执行结果,`when` 作为分支条件 33 | 34 | ### 使用 git,file 等模块比直接使用 shell 模块的优势在哪里 35 | 36 | 幂等性。如使用 shell 的话, `git clone` 两次会有报错,而 git,file 诸多模块很好地保证了特定操作的幂等性。 37 | 38 | ### 如何在 task 中根据 linux 的发行版不同而做不同的操作 39 | -------------------------------------------------------------------------------- /ctop.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 ctop 监控容器指标 3 | keywords: docker, container, docker监控 4 | date: 2019-12-16 17:00 5 | 6 | --- 7 | 8 | # 使用 ctop 监控容器指标 9 | 10 | 如果你经常在 `linux` 下工作,那你肯定对 `top` 以及它的增强版 `htop` 非常熟悉。如果说 `top` 是管理进程的,而 `ctop` 则是管理容器的。 11 | 12 | 你可以使用它监控容器的 `CPU`/`MEM`/`IO` 以及查看容器日志,查看容器状态等。相对于 `portainer` 等工具来说,它相当轻量,极其适合在个人服务器中使用 13 | 14 | ## 安装 15 | 16 | 在 `centos` 下安装 `ctop` 17 | 18 | ``` bash 19 | $ wget https://github.com/bcicen/ctop/releases/download/v0.7.2/ctop-0.7.2-linux-amd64 -O /usr/local/bin/ctop 20 | 21 | $ chmod +x /usr/local/bin/ctop 22 | ``` 23 | 24 | ## 命令使用 25 | 26 | 直接使用 `ctop` 进入容器指标可视化界面 27 | 28 | ![容器视图](./assets/ctop.png) 29 | 30 | ``` bash 31 | ctop 32 | ``` 33 | 34 | 当进入指标可视化界面后,可以使用快捷键进行筛选,排序,检索 35 | 36 | + `q`,退出或者返回上一级 37 | + `j/k`,同vi,上下移动 38 | + `a`,展示所有容器 39 | + `l`,查看某一容器日志 40 | + `f`,对所有容器进行筛选 41 | + `s`,查看单个容器状态 42 | + `S`: 保存当前配置 43 | 44 | 以下是查看单个容器状态的截图 45 | 46 | ![单个容器状态](./assets/ctop-view.png) 47 | -------------------------------------------------------------------------------- /deploy-redis.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 docker-compose 部署 postgres 3 | date: 2019-01-04 20:37 4 | 5 | --- 6 | 7 | # 部署 redis 8 | 9 | ## 部署 10 | 11 | 使用官方镜像 `redis:5-alpine` 进行部署 12 | 13 | `docker-compose.yaml` 配置文件如下,由于我们仅仅把 `redis` 作为一个缓存服务,因此不做持久化的处理 14 | 15 | > 关于配置文件,我维护在我的 github 仓库 [shfshanyue/op-note:compose](https://github.com/shfshanyue/op-note/tree/master/compose) 中 16 | 17 | ``` yaml 18 | version: '3' 19 | 20 | services: 21 | redis: 22 | image: redis:5-alpine 23 | restart: always 24 | ports: 25 | - 6379:6379 26 | labels: 27 | - traefik.http.routers.db.rule=Host(`redis.shanyue.local`) 28 | 29 | # 使用已存在的 traefik 的 network 30 | networks: 31 | default: 32 | external: 33 | name: traefik_default 34 | ``` 35 | 36 | `docker-compose up` 启动服务 37 | 38 | ``` bash 39 | $ docker-compose up -d 40 | ``` 41 | 42 | ## 连接 Redis 43 | 44 | 使用 `docker-compose exec` 测试是否能够正常连接redis 45 | 46 | ``` bash 47 | $ docker-compose exec redis redis-cli 48 | ``` 49 | 50 | 连接正常 51 | -------------------------------------------------------------------------------- /linux-tcpdump.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: tcpdump 命令详解及示例 3 | keywords: tcpdump,linux抓包 4 | description: sed 是一个用来筛选与转换文本内容的工具。一般用来批量替换,删除某行文件。如果想在 mac 中使用 sed,请使用 gsed 代替,不然会被坑 5 | date: 2019-11-14 20:00 6 | sidebarDepth: 3 7 | tags: 8 | - linux 9 | 10 | --- 11 | 12 | # tcpdump 命令详解及示例 13 | 14 | Q: `tcpdump` 是干吗的\ 15 | A: 抓包的\ 16 | Q: 除了 `tcpdump` 还有啥能抓包\ 17 | A: `wireshark`\ 18 | Q: 为啥不讲 `wireshark` 抓包\ 19 | A: `wireshark` 在 linux 命令行上不能用 20 | 21 | 22 | 23 | + 原文链接: [tcpdump 命令详解及示例](https://github.com/shfshanyue/op-note/blob/master/linux-tcpdump.md) 24 | + 系列文章: [当我有台服务器时我做了什么](https://github.com/shfshanyue/op-note) 25 | 26 | ## tcpdump 命令详解 27 | 28 | ### 关键选项 29 | 30 | + `-c count`: 指定打印条数 31 | + `-i interface`: 指定网络接口,如常见的 `eth0`,`lo`,可以通过 `ifconfig` 打印所有网络接口 32 | + `-vv`: 尽可能多地打印信息 33 | 34 | ### 过滤器 35 | 36 | 过滤器,顾名思义,过滤一部分数据包,**而过滤器使用 `pcap-filter` 的语法** 37 | 38 | 所以你可以查看 `pcap-filter` 手册 39 | 40 | ``` bash 41 | # 查看所有过滤器 42 | $ man pcap-fliter 43 | ``` 44 | 45 | 过滤器可以简单分为三类 46 | 47 | + `type`: 有四种类型 `host`,`net`,`port`,`portrange` 48 | + `tcpdump port 22` 49 | + `tcpdump port ssh` 50 | + `dir`: 源地址和目标地址,主要有 `src` 和 `dst` 51 | + `tcpdump src port ssh` 52 | + `proto`: 协议,有 `ip`,`arp`,`rarp`,`tcp`,`udp`,`icmp` 等 53 | + `tcpdump icmp` 54 | 55 | ## tcpdump examples 56 | 57 | + 命令: `netstat -i`\ 58 | 解释: 打印所有网络接口 59 | 60 | + 命令: `tcpdump -i eth0`\ 61 | 解释: 监视网络接口 `eth0` 的数据包 62 | 63 | + 命令: `tcpdump host 172.18.0.10`\ 64 | 解释: 监视主机地址 `172.18.0.10` 的数据包 65 | 66 | + 命令: `tcpdump net 172.18.0.1/24`\ 67 | 解释: 监视网络 `172.10.0.1/24` 的所有数据包 68 | 69 | + 命令: `tcpdump tcp port 443`\ 70 | 解释: 监听 https 请求 71 | 72 | + 命令: `tcpdump tcp port 443 and host 172.18.0.10`\ 73 | 解释: 监听目标地址或源地址是 172.18.0.10 的 https 请求 74 | 75 | + 命令: `tcpdump icmp`\ 76 | 解释: 监听 ICMP 协议 (比如典型的 PING 命令) 77 | 78 | + 命令: `tcpdump arp`\ 79 | 解释: 监听 ARP 协议 80 | 81 | + 命令: `tcpdump 'tcp[tcpflags] == tcp-syn'`\ 82 | 解释: 监听 TCP 协议中 `flag` 带 `SYN` 的,可以用来监听三次握手 83 | 84 | + 命令: `tcpdump -vv tcp port 80 | grep 'Host:'`\ 85 | 解释: 找到 http 中所有的 Host 86 | 87 | ## 相关文章 88 | 89 | + [A tcpdump Tutorial with Examples — 50 Ways to Isolate Traffic](https://danielmiessler.com/study/tcpdump/) 90 | -------------------------------------------------------------------------------- /docker-compose-arch.md: -------------------------------------------------------------------------------- 1 | # docker-compose 编排架构简介 2 | 3 | 首先需要说一下服务器配置,只要你的服务器配置高于1核2G,均可使用该基础设施来管理你的个人服务器,你可以在服务中部署几个应用,并且把该服务器作为你的测试服务器来使用。但是你的应用过多,或者QPS稍大,可以相应地加强服务器配置。 4 | 5 | 现在来简述下一个低配置的个人服务器的基础设施架构图 6 | 7 | ![docker compose architecture](https://raw.githubusercontent.com/shfshanyue/graph/master/draw/docker-compose.jpg) 8 | 9 | 10 | 11 | ## Traefik 12 | 13 | 架构图的最核心位置是 `traefik`,一个 `Edge Router`,它也是该基础设施的重点。 14 | 15 | 服务器内的所有应用服务,如 `postgres`,`redis` 以及自己所写的应用服务,都与 `traefik` 置于一个网络下,**它们共同组成了一个集群**。 16 | 17 | 以下是我使用它的理由 18 | 19 | + 热更新配置,无需重启 20 | + 自动服务发现与负载均衡 21 | + TLS/SSL 证书的自动生成 22 | + 与 `docker` 的完美集成,基于 `container label` 的配置 23 | + 漂亮的 `dashboard` 界面 24 | 25 | 关于 `traefik` 可以参考文章 [Traefik 简易配置及入门](https://github.com/shfshanyue/op-note/blob/master/traefik.md) 26 | 27 | ## 请求路由 28 | 29 | 从图左侧可以看出,请求路由分为两大类。当然他们的路由规则都是通过 `traefik` 来控制 30 | 31 | + `shanyue.tech` 通过公有的域名提供公有的、可供互联网访问的服务。如博客,微信公众号开发,个人网站及后端服务等等 32 | + `shanyue.local` 通过私有的域名提供私有的、只供内部进群访问的服务,保证安全性。如数据库,redis等等 33 | 34 | ## 应用与服务 35 | 36 | 在图右侧,都是个人服务器里的应用,它们均是通过 `docker-compose` 部署。你可以在 [shfshanyue/op-note:compose](https://github.com/shfshanyue/op-note/tree/master/compose) 中找到所有的配置文件 37 | 38 | 只需要 `docker-compose up -d` 即可启动 39 | 40 | + `whoami`: 一个用以测试的服务,测试负载均衡及路由匹配等 41 | + `dns`: 集群内 DNS 服务,给自己的私有域名 `shanyue.local` 做解析 42 | + `openvpn`:与个人笔记本搭建局域网,方便访问个人服务器上服务以及调试页面 43 | + `postgres` 44 | + `redis` 45 | 46 | 本章将会讲述如何部署它们以及它们的配置文件。不过在此之前,你需要对 `docker` 以及 `docker-compose` 了解一些,参考以前文章 47 | 48 | 1. [docker 简易入门](https://github.com/shfshanyue/op-note/blob/master/docker.md) 49 | 1. [docker compose 简易入门](https://github.com/shfshanyue/op-note/blob/master/docker-compose.md) 50 | 51 | ## 私有服务 52 | 53 | `shanyue.local` 路由所提供的服务都是私有服务,通过 `dnsmasq` 与 `openvpn` 在个人笔记本上进行访问。 54 | 55 | + [搭建集群内部 DNS 服务器](https://github.com/shfshanyue/op-note/blob/master/dnsmasq.md) 56 | + [使用 openvpn 访问集群私有服务](https://github.com/shfshanyue/op-note/blob/master/openvpn.md) 57 | 58 | ## 监控 59 | 60 | 个人服务器自然也需要监控,如负载,CPU,内存,网络,磁盘等。如果你服务器配置较高,且中有重要应用在跑时可以使用 `prometheus` 与 `grafana` 搭建一套监控系统,但是作为一个轻量的个人服务器,这样就显得小题大做,而且消耗极其资源。 61 | 62 | 关于该云服务器的 `metric` 可以直接在阿里云的监控面板上进行查看。但对于轻量的个人服务器,可以使用 `htop` 与 `ctop` 两个简单的命令行工具来搞定 63 | 64 | + `htop` 监控进程指标 65 | + `ctop` 监控容器指标 66 | 67 | ![htop](./assets/htop.jpg) 68 | ![ctop](./assets/ctop.png) -------------------------------------------------------------------------------- /deploy-postgres.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 docker-compose 快速部署 postgres 3 | date: 2019-01-04 20:37 4 | 5 | --- 6 | 7 | # 部署 postgres 8 | 9 | `postgres` 是一个功能强大的开源对象关系型数据库系统,被业界誉为“最先进的开源数据库“。 10 | 11 | `postgres` 不仅功能强大,而且免费开源,你可以在 github 上学习并研究它的源码: [postgres/postgres](https://github.com/postgres/postgres)。另外,它在数据类型,内置函数,事务的支持相比 `mysql` 都要好一些。 12 | 13 | 但我在个人服务器里选择 `postgres` 的终极原因是: 我们生产环境中大部分数据库都采用了 `postgres`。 14 | 15 | 关于 `mysql` 与 `postgres` 的优劣,可以参考知乎上的一个问题: [PostgreSQL 与 MySQL 相比,优势何在?](https://www.zhihu.com/question/20010554)。 16 | 17 | ## 部署 18 | 19 | 在部署之前,你需要对 `docker-compose` 以及 `traefik` 有所了解,可以参考我以前的文章: 20 | 21 | + [docker compose 简易入门](https://shanyue.tech/op/docker-compose.html) 22 | + [使用 traefik 做反向代理](https://shanyue.tech/op/traefik.html) 23 | 24 | 这里采用官方镜像 `postgres:12-alpine` 进行数据库的部署,之所以采用 `alpine` 作为基础镜像,源于它体积较小。 25 | 26 | 我们使用 `docker-compose` 进行数据库的部署, 如果你对它不了解,可以参考我以前写的系列文章 [个人服务器运维指南](https://github.com/shfshanyue/op-note)。 27 | 28 | 对于数据库的存储,我放置于当前目录 `./pg-data` 之下,方便迁移。关于 `docker-compose.yaml` 配置文件如下: 29 | 30 | > 关于我个人服务器下所有服务的配置文件,均维护在我的 github 仓库 [shfshanyue/op-note:compose](https://github.com/shfshanyue/op-note/tree/master/compose) 中 31 | 32 | ``` yaml 33 | version: '3' 34 | 35 | services: 36 | db: 37 | image: postgres:12-alpine 38 | restart: always 39 | ports: 40 | - 5432:5432 41 | volumes: 42 | - ./pg-data:/var/lib/postgresql/data 43 | labels: 44 | - "traefik.http.routers.db.rule=Host(`db.shanyue.local`)" 45 | 46 | # 使用已存在的 traefik 的 network 47 | networks: 48 | default: 49 | external: 50 | name: traefik_default 51 | ``` 52 | 53 | `docker-compose up` 启动服务,数据库部署完成 54 | 55 | ``` bash 56 | $ docker-compose up -d 57 | ``` 58 | 59 | ## 连接数据库 60 | 61 | 使用 `docker-compose exec` 测试是否能够正常连接数据库,通过测试,我们已经正确部署并且连接上了数据库 62 | 63 | ``` bash 64 | $ docker-compose exec db psql -U postgres 65 | psql (12.1) 66 | Type "help" for help. 67 | 68 | postgres=# 69 | ``` 70 | 71 | 在宿主机中可以通过 `docker-compose` 与 `psql` 来连接数据库,那如何在整个局域网集群中连接数据库呢? 72 | 73 | ## 使用 pgcli 连接数据库 74 | 75 | 如果把 `psql` 比作记事本,那么 `pgcli` 则是带有代码高亮功能的 IDE。在日常开发中,使用 `pgcli` 足以应付生产环境多个数据库的配置管理。 76 | 77 | 使用 `brew` 安装 `pgcli`: 78 | 79 | ``` bash 80 | $ brew install pgcli 81 | ``` 82 | 83 | 使用 `pgcli` 得以成功连接数据库: 84 | 85 | ``` bash 86 | $ pgcli -h db.shanyue.local -U postgres 87 | postgres@db:postgres> \d 88 | +----------+--------+--------+---------+ 89 | | Schema | Name | Type | Owner | 90 | |----------+--------+--------+---------| 91 | +----------+--------+--------+---------+ 92 | SELECT 0 93 | Time: 0.030s 94 | 95 | ``` 96 | -------------------------------------------------------------------------------- /traefik-https.md: -------------------------------------------------------------------------------- 1 | # 在 traefik 中为服务开通 https 2 | 3 | `https` 已经成为一个现代网站的标配,以至于当一个网站没有 `https` 时,某些浏览器都会把它标识为不安全。而除了安全方面,`https` 对网站的SEO也影响很多,而对于某些新型的浏览器 API,也只有在 `https` 下才能使用。不管怎么说,`https` 也成为一个网站的刚需。 4 | 5 | 而当你使用了 `traefik` 作为反向代理时,你可以配置 `ACME` 自动为域名提供证书,只需几行即可解决问题。免费的证书,当然是通过 `Let's Encrypt` 来解决。 6 | 7 | ## ACME 配置 8 | 9 | 通过它可以很方便地自动签发证书并且自动续期,我们在 `traefik.toml` 中进行相关配置 10 | 11 | ``` toml 12 | [certificatesResolvers.le.acme] 13 | email = "xianger94@qq.com" 14 | storage = "acme.json" 15 | 16 | [certificatesResolvers.le.acme.tlsChallenge] 17 | ``` 18 | 19 | 其中,`storage` 指存放证书的位置 20 | 21 | ## Traefik 容器配置 22 | 23 | 在配置好 `traefik.toml` 配置完成后,我们需要修改 `traefik` 容器启动的相关配置 24 | 25 | 1. 暴露 443 端口 26 | 1. 挂载 acme.json,持久化证书 27 | 28 | 由于 `acme.json` 是一个文件,我们现在宿主机中创建它 29 | 30 | ``` bash 31 | $ touch acme.json 32 | $ docker-compose up 33 | ``` 34 | 35 | 随后启动容器,配置文件如下 36 | 37 | ``` yaml 38 | version: '3' 39 | 40 | services: 41 | reverse-proxy: 42 | image: traefik:v2.0 43 | ports: 44 | - "80:80" 45 | - "443:443" 46 | - "8080:8080" 47 | volumes: 48 | - ./traefik.toml:/etc/traefik/traefik.toml 49 | - ./acme.json:/acme.json 50 | - ./log:/log 51 | - /var/run/docker.sock:/var/run/docker.sock 52 | container_name: traefik 53 | env_file: .env 54 | labels: 55 | - "traefik.http.routers.api.rule=Host(`traefik.shanyue.local`)" 56 | - "traefik.http.routers.api.service=api@internal" 57 | ``` 58 | 59 | ## 服务配置 60 | 61 | 如果你需要为你的服务提供 https 流量,只需要添加两行代码 62 | 63 | ``` yaml 64 | labels: 65 | - traefik.http.routers.whoami.tls=true 66 | - traefik.http.routers.whoami.tls.certresolver=le 67 | ``` 68 | 69 | 我们依然使用 `whoami` 做测试,`docker-compose.yaml` 文件内容如下 70 | 71 | ``` yaml 72 | version: '3' 73 | 74 | services: 75 | whoami: 76 | image: containous/whoami 77 | labels: 78 | - traefik.http.routers.whoami.rule=Host(`whoami.shanyue.tech`) 79 | - traefik.http.routers.whoami.tls=true 80 | - traefik.http.routers.whoami.tls.certresolver=le 81 | # environments: 82 | # TMUX 83 | 84 | networks: 85 | default: 86 | external: 87 | name: traefik_default 88 | ``` 89 | 90 | 服务启动后,使用 `curl` 测试服务是否正常工作,我们可以看到 `X-Forwarded-Proto` 为 `https`,配置成功 91 | 92 | ``` bash 93 | $ curl https://whoami.shanyue.tech 94 | Hostname: c9c3cc850e2b 95 | IP: 127.0.0.1 96 | IP: 172.18.0.2 97 | RemoteAddr: 172.18.0.3:35320 98 | GET / HTTP/1.1 99 | Host: whoami.shanyue.tech 100 | User-Agent: curl/7.29.0 101 | Accept: */* 102 | Accept-Encoding: gzip 103 | X-Forwarded-For: 59.110.159.217 104 | X-Forwarded-Host: whoami.shanyue.tech 105 | X-Forwarded-Port: 443 106 | X-Forwarded-Proto: https 107 | X-Forwarded-Server: 9d783174aca9 108 | X-Real-Ip: 59.110.159.217 109 | ``` 110 | -------------------------------------------------------------------------------- /git.md: -------------------------------------------------------------------------------- 1 | # 服务器上 git 的安装及基本配置 2 | 3 | `git` 对于开发者来说属于必备工具中的必备工具了。何况,没有 `git` 的话,**面向github编程** 从何说起,如同一个程序员断了左膀右臂。 4 | 5 | 本篇文章将介绍如何在服务器(centos)上安装最新版本的 git 及其基本配置 6 | 7 | + 使用源码编译安装 git 8 | + 使用 ansible 自动化安装 git 9 | + git 基本配置 10 | 11 | > 你对流程熟悉后,特别是了解 ansible 后,只需要一分钟便可以操作完成 12 | 13 | 14 | 15 | + 原文地址: [服务器上 git 的安装及基本配置](https://github.com/shfshanyue/op-note/blob/master/git.md) 16 | + 系列文章: [山月的服务器运维笔记](https://github.com/shfshanyue/op-note) 17 | 18 | ## 安装 19 | 20 | ``` bash 21 | $ yum install git 22 | ``` 23 | 24 | 如果使用 `yum` 来安装 `git` 的话,那实在没有必要单开一篇文章了,但好事多磨。那使用 `yum` 的弊端在哪里?我们知道,`yum` 为了保证它的软件的稳定性,往往软件的版本都会很老,以至于非常不好用。 25 | 26 | **而且最重要的是 `yum` 安装的 `git` 没有语法高亮!** 27 | 28 | ## 安装最新版本 29 | 30 | 安装最新版本,或者说稳定版本,可以充分体验新版本带来的特性,从而使自己更舒服一些。从源码安装 `git` 是最无拘无束最灵活的安装方法,但同时也是最繁琐的方法。 31 | 32 | 关于源码编译安装详细教程可以参考 [https://github.com/git/git](https://github.com/git/git)。 33 | 34 | 在编译之前需要先安装依赖如下 35 | 36 | ``` bash 37 | gettext-devel 38 | expat-devel 39 | curl-devel 40 | zlib-devel 41 | perl-devel 42 | openssl-devel 43 | subversion-perl 44 | make 45 | gcc 46 | ``` 47 | 48 | 随后根据文档进行源码编译安装: 49 | 50 | ``` bash 51 | # 使用旧版本 git 下载源码 52 | $ git clone https://github.com/git/git.git 53 | 54 | $ git checkout v2.26.2 55 | 56 | $ make prefix=/usr all 57 | 58 | $ make prefix=/usr install 59 | ``` 60 | 61 | 查看版本号,安装成功 62 | 63 | `git version`,查看版本号,此时为 `2.26.2` 64 | 65 | ``` bash 66 | $ git version 67 | git version 2.26.2 68 | ``` 69 | 70 | 再用它 `git status`,查看下语法高亮效果 71 | 72 | ![git 高亮效果](./assets/git.jpg) 73 | 74 | ## 使用 ansible 自动化安装 75 | 76 | > 如果你对 ansible 不够了解,可以参考我的文章 [ansible 入门指南](https://mp.weixin.qq.com/s/t2fpzPJk3pCK3qBgo_SdyQ)。 77 | 78 | 而对于安装 git,如果采用以上源码编译方法则过于耗时及繁琐,而如果我们使用 `ansible` 进行自动化运维的话,选择一个好用的 `Ansible Role` 就可以了,此处我们选择 [geerlingguy.git](https://github.com/geerlingguy/ansible-role-git)。 79 | 80 | ``` bash 81 | $ ansible-galaxy install geerlingguy.git 82 | ``` 83 | 84 | 配置 `ansible playbook`,指定变量,从源码安装,并安装最新版本。 85 | 86 | ``` yaml 87 | hosts: all 88 | roles: 89 | - role: geerlingguy.git 90 | vars: 91 | # 从源码安装 92 | git_install_from_source: true 93 | # 安装最新版本 94 | git_install_from_source_force_update: true 95 | ``` 96 | 97 | 使用 `ansible-playbook` 对服务器进行批量安装 98 | 99 | ``` bash 100 | $ ansible-playbook -i hosts git.yaml 101 | ``` 102 | 103 | > 关于我服务器所有的 ansible role 配置,可以参考我的配置文件 [shfshanyue/ansible-op](https://github.com/shfshanyue/ansible-op) 104 | 105 | ## 基本配置 106 | 107 | 在服务器中安装完 git 后,即可对它进行基础配置。全局配置邮箱及用户名,此时就可以愉快地在服务器中使用 `git` 管理代码了 108 | 109 | ``` bash 110 | $ git config --global user.name shfshanyue 111 | $ git config --global user.email xianger94@gmail.com 112 | ``` 113 | 114 | ## 面向 github 编程 115 | 116 | 但是现在就可以面向 `github` 编程了吗?不! 117 | 118 | 使用 `ssh -T` 测试连通性 119 | 120 | ``` bash 121 | $ ssh -T git@github.com 122 | Permission denied (publickey). 123 | ``` 124 | 125 | 此时需要配置 `ssh key` 来保证正确地面向 github 编程,请关注并查看下篇文章 [服务器上 ssh key 管理及 github 配置](https://github.com/shfshanyue/op-note/blob/master/ssh-setting.md) 126 | -------------------------------------------------------------------------------- /init.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 服务器登录配置 3 | date: 2019-10-10 23:00 4 | keywords: 云服务器登录,ssh-config,ssh免密登录,ssh及服务器登录配置,ssh禁止密码登录 5 | description: 当刚拥有服务器后,首先需要登录服务器,本节主要有以下三个实践操作:快速登录,免密登录,禁用密码。 6 | tags: 7 | - linux 8 | 9 | --- 10 | 11 | # 高效简单的服务器登录配置 12 | 13 | 当你拥有了属于自己的一台云服务器后,首先需要做的事情就是登录服务器。 14 | 15 | 而登录服务器,作为新手可以通过云厂商提供的 dashboard 进行登录操作。但是,最简单及最方便的方式还是通过终端,使用 `ssh` 命令快速登录 16 | 17 | 本节主要涉及以下四个实践操作,这也是山月关于个人服务器管理的第一篇文章,欢迎持续关注 18 | 19 | 1. 快速登录: 配置客户端 ssh-config 20 | 1. 免密登录: 配置 public key 21 | 1. 禁用密码:配置服务器 ssh-config 22 | 1. 保持连接:控制ssh不被断开 23 | 24 | > 你对流程熟悉后,只需要一分钟便可以操作完成 25 | 26 | 27 | 28 | + 原文地址: [云服务器初始登录配置](https://shanyue.tech/op/init.html) 29 | + 系列文章: [服务器运维笔记](https://shanyue.tech/op/) 30 | 31 | ## 登录服务器: ssh 32 | 33 | 把以下 IP 地址替换为你云服务器的公网地址,并提供密码即可登录。但记住一个 IP 地址,这是一个反人性的操作,如果你有多个服务器呢?此时 `ssh-config` 就派上了用场 34 | 35 | ``` bash 36 | $ ssh root@172.16.3.2 37 | ``` 38 | 39 | ## 快速登录:ssh-config 40 | 41 | 在本地客户端环境 (个人电脑) 上配置 ssh-config,对个人服务器起别名,可以更方便地登录云服务器,以下是关于 ssh-config 的配置文件 42 | 43 | + `/etc/ssh/ssh_config` 44 | + `~/.ssh/config` 45 | 46 | 以下是快速登录山月两个服务器 `shanyue` 和 `shuifeng` 的配置 47 | 48 | ```config 49 | # 修改 ssh 配置文件 ~/.ssh/config 50 | 51 | Host shanyue 52 | HostName 59.110.216.155 53 | User root 54 | Host shuifeng 55 | HostName 56 | User root 57 | ``` 58 | 59 | 配置成功之后直接 ssh host 名称就可以,是不是很方便呢? 60 | 61 | ``` bash 62 | $ ssh shanyue 63 | The authenticity of host '59.110.216.155 (59.110.216.155)' can't be established. 64 | ECDSA key fingerprint is SHA256:WXULVpZcrX6kENrR5GH0mqRi49Djj22UXba0dRXCVKo. 65 | Are you sure you want to continue connecting (yes/no)? yes 66 | Warning: Permanently added '59.110.216.155' (ECDSA) to the list of known hosts. 67 | 68 | Welcome to Alibaba Cloud Elastic Compute Service ! 69 | 70 | [root@shanyue ~]# 71 | [root@shanyue ~]# 72 | [root@shanyue ~]# 73 | ``` 74 | 75 | ## 免密登录:public-key 与 ssh-copy-id 76 | 77 | 不过仅仅有了别名,每次输入密码也是足够麻烦的。**那如何实现远程服务器的免密登录?** 78 | 79 | 1. 两个文件: 本地环境的 `~/.ssh/id_rsa.pub` 与 远程服务器的 `~/.ssh/authorized_keys` 80 | 1. 一个动作:把本地文件中的内容复制粘贴到远程服务器中 81 | 82 | > 如果本地环境中 ~/.ssh/id_rsa.pub 文件不存在,请参考下一章节使用 ssh-keygen 生成 ssh keys: [ssh key 及 git 配置](https://shanyue.tech/op/ssh-setting.html) 83 | 84 | **总结成一句话,即把自己的公钥放在远程服务器。** 85 | 86 | 简单来说,就是 `Ctrl-C` 与 `Ctrl-V` 操作,不过具体实施起来较为琐碎。 **更为重要的是对于新人还有一个门槛:vim 的使用**。 87 | 88 | > 关于 vim,可以参考后续文章 [vim 基本操作及其配置](https://shanyue.tech/op/vim-setting.html) 89 | 90 | 此时一个解决生产力的命令行工具应运而生: `ssh-copy-id` 91 | 92 | ```ssh 93 | # 在本地环境进行操作 94 | 95 | # 提示你输入密码,成功之后可以直接 ssh 登录,无需密码 96 | $ ssh-copy-id shanyue 97 | 98 | # 登陆成功,无需密码 99 | $ ssh shanyue 100 | ``` 101 | 102 | ## 禁用密码登录 103 | 104 | 为了更大保障服务器的安全性,这里禁止密码登录。修改云服务器的 `sshd` 配置文件:`/etc/ssh/sshd_config`。其中 `PasswordAuthentication` 设置为 `no`,以此来禁用密码登录。 105 | 106 | ```config 107 | # 编辑服务器端的 /etc/ssh/sshd_config 108 | # 禁用密码登录 109 | Host * 110 | PasswordAuthentication no 111 | ``` 112 | 113 | ## 保持连接 114 | 115 | 此时仿佛一切都顺心遂意,心满意足,于是,山月趁空接了杯水喝。然而回来发现,ssh 超时断开连接,并因此 hang 住了,这怎么能忍? 116 | 117 | 在客户端的 ssh-config 配置文件中,加两行配置搞定。 118 | 119 | ``` config 120 | Host * 121 | ServerAliveInterval 60 122 | ``` 123 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # 当我有服务器时我做了什么 · 个人服务器运维指南 2 | 3 | > 该仓库已迁移至我的博客 4 | 5 | ## 预览 6 | 7 | ### 窗口管理 8 | 9 | ![窗口管理](./assets/dev-env.png) 10 | 11 | ### 服务管理 12 | 13 | ![服务管理](https://raw.githubusercontent.com/shfshanyue/graph/master/draw/docker-compose.jpg) 14 | 15 | ## 目录 16 | 17 | > 本系列文章所有容器的配置文件在 [compose目录](https://github.com/shfshanyue/op-note/tree/master/compose) 18 | 19 | 1. 序 20 | 1. [序·当我有一台服务器时我做了什么](https://shanyue.tech/op/when-server.html) 21 | 1. [序·当我有一台服务器时我做了什么(2019)](https://shanyue.tech/op/when-server-2019.html) 22 | 1. 服务器初始化配置 23 | 1. [高效简单的服务器登录配置](./init.md) 24 | 1. [服务器上 git 安装及基本配置](./git.md) 25 | 1. [服务器上 ssh key 管理及 github 配置](./ssh-setting.md) 26 | 1. [服务器基本指标信息查看及命令](./system-info.md) 27 | 1. [tmux 与服务器终端多窗口管理](https://shanyue.tech/op/tmux-setting.html) 28 | 1. [vim 基本操作及配置](https://shanyue.tech/op/vim-setting.html) 29 | 1. 自动化运维 30 | 1. [ansible 简易入门](https://shanyue.tech/op/ansible-guide.html) 31 | 1. 了解 docker 32 | 1. [docker 简易入门](https://shanyue.tech/op/docker.html) 33 | 1. [Dockerfile 最佳实践](https://shanyue.tech/op/dockerfile-practice.html) 34 | 1. [案例: 使用 docker 高效部署前端应用](https://shanyue.tech/op/deploy-fe-with-docker.html) 35 | 1. 使用 docker compose 编排容器 36 | 1. [docker compose 编排架构简介](https://shanyue.tech/op/docker-compose-arch.html) 37 | 1. [docker compose 简易入门](https://shanyue.tech/op/docker-compose.html) 38 | 1. [使用 traefik 做反向代理](https://shanyue.tech/op/traefik.html) 39 | 1. [使用 traefik 自动生成 https 的证书](https://shanyue.tech/op/traefik-https.html) 40 | 1. [使用 dnsmasq 搭建本地 DNS 服务](https://shanyue.tech/op/dnsmasq.html) 41 | 1. [使用 openvpn 访问内部集群私有服务](https://shanyue.tech/op/openvpn.html) 42 | 1. [使用 postgres 做数据存储](https://shanyue.tech/op/deploy-postgres.html) 43 | 1. [使用 redis 做缓存服务](https://shanyue.tech/op/deploy-redis.html) 44 | 1. [使用 sentry 做异常监控](https://shanyue.tech/op/deploy-sentry.html) 45 | 1. [案例:黑客增长 - 从博客向公众号引流](https://shanyue.tech/op/blog-to-wechat.html) 46 | 1. [案例:黑客增长 - 使用公众号开发模拟面试](https://shanyue.tech/op/wechat-interview.html) 47 | 1. 服务器及容器监控 48 | 1. [linux 各项监控指标](https://shanyue.tech/op/linux-monitor.html) 49 | 1. [使用 htop 监控进程指标](https://shanyue.tech/op/htop.html) 50 | 1. [使用 ctop 监控容器指标](https://shanyue.tech/op/ctop.html) 51 | 1. 高频 linux 命令 52 | 1. [sed 命令详解及示例](https://shanyue.tech/op/linux-sed.html) 53 | 1. [awk 命令详解及示例](https://shanyue.tech/op/linux-awk.html) 54 | 1. [jq 命令详解及示例](https://shanyue.tech/op/jq.html) 55 | 1. [iptables 命令详解及示例](https://shanyue.tech/op/iptables.html) - TODO 56 | 1. [tcpdump 命令详解及示例](https://shanyue.tech/op/linux-tcpdump.html) 57 | 1. [htop 命令详解及示例](https://shanyue.tech/op/htop.html) - TODO 58 | 1. [案例: 使用jq与sed制作掘金面试文章榜单](https://shanyue.tech/op/jq-sed-case.html) 59 | 60 | ### TODO 61 | 62 | 1. 为何需要一套 Linux 环境 63 | 64 | ## 关注我 65 | 66 | 我是山月,我会定期分享文章在个人公众号【全栈成长之路】中。你可以添加我微信 `shanyue94` 或者在公众号中联系我,添加好友时回复 **个人服务器** 可以拉你进个人服务器运维交流群。 67 | 68 | 如果你没有服务器,可以在华为云或者阿里云新购一台服务器作为实践,对于新手有以下优惠 69 | 70 | + [阿里云](https://www.aliyun.com/1111/pintuan-share?ptCode=MTY5MzQ0Mjc1MzQyODAwMHx8MTE0fDE%3D&userCode=4sm8juxu) 71 | 72 | ![](https://shanyue.tech/wechat.jpeg) 73 | -------------------------------------------------------------------------------- /ssh-setting.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 云服务器中 ssh key 管理 与 github 的配置 3 | date: 2019-10-11 23:00 4 | keywords: ssh-keygen,permission denied (publickey),git clone 的配置,ssh -T 5 | description: ssh keygen 生成非对称加密中的 public-key 与 private-key,并把 publick-key 扔到 github 上。与上篇文章配置服务器免登陆一样的步骤。 6 | tags: 7 | - linux 8 | 9 | --- 10 | 11 | # 云服务器中 ssh key 管理与 github 配置 12 | 13 | 程序员经常挂在嘴边的一句话是: 面向 github 编程。如果没有 github 对程序员而言万古如长夜,github 对程序员的重要性可见一斑。 14 | 15 | 与 `github` 进行协同的工具是 `git`,在上一章 [云服务器上 git 安装及基本配置](./git.md) 对它在服务器上按照也有了基本介绍。虽然 `git` 可以工作在 `ssh` 与 `https` 两种协议上,但为了安全性及便利性,更多时候会选择 `ssh`。 16 | 17 | > 如果采用 https,则每次 git push 都需要验证身份 18 | 19 | 此篇文章的主要内容是: 20 | 21 | 1. `ssh keygen` 生成非对称加密中的 public-key 与 private-key 22 | 1. 把 publik-key 扔到 github 上,与上篇文章 [服务器高效登录配置](https://shanyue.tech/op/init.html) 一样的步骤,不过上文是如何把 key 扔到云服务器,而此处是扔到 github。 23 | 24 | > 你对流程熟悉后,只需要一分钟便可以操作完成 25 | 26 | 27 | 28 | + 原文地址: [云服务器 ssh key 以及 git 的配置](https://shanyue.tech/op/ssh-setting.html) 29 | + 系列文章: [服务器运维笔记](https://shanyue.tech/op/) 30 | 31 | ## Permission denied (publickey). 32 | 33 | 如果没有在 github 设置 public key 而直接执行 `git clone` 命令的话,会有权限问题。 34 | 35 | 使用 `ssh -T` 测试连通性如下,会有一个 `Permission denied` 的异常。 36 | 37 | ``` bash 38 | $ git clone git@github.com:vim/vim.git 39 | Cloning into 'vim'... 40 | Warning: Permanently added the RSA host key for IP address '13.229.188.59' to the list of known hosts. 41 | Permission denied (publickey). 42 | fatal: Could not read from remote repository. 43 | 44 | Please make sure you have the correct access rights 45 | and the repository exists. 46 | 47 | # 不过有一个更直接的命令去查看是否有权限 48 | $ ssh -T git@github.com 49 | Permission denied (publickey). 50 | ``` 51 | 52 | ## 生成新的 ssh key 53 | 54 | 使用命令 `ssh-keygen` 可以生成配对的 `id_rsa` 与 `id_rsa.pub` 文件,生成之后只需把 `id_rsa.pub` 扔到 github 即可。 55 | 56 | ``` bash 57 | # 生成一个 ssh-key 58 | # -t: 可选择 dsa | ecdsa | ed25519 | rsa | rsa1,代表加密方式 59 | # -C: 注释,一般写自己的邮箱 60 | $ ssh-keygen -t rsa -C "shanyue" 61 | 62 | # 生成 id_rsa/id_rsa.pub: 配对的私钥与公钥 63 | $ ls ~/.ssh 64 | authorized_keys config id_rsa id_rsa.pub known_hosts 65 | ``` 66 | 67 | ## 在 github 设置里新添一个 ssh key 68 | 69 | 在云服务器中复制 `~/.ssh/id_rsa.pub` 中文件内容,并粘贴到github 的配置中。 70 | 71 | ``` bash 72 | $ cat ~/.ssh/id_rsa.pub 73 | ssh-rsa AAAAB3SSSSSSSSSSSSSSSSSSSSSBAQDcM4aOo9qlrHOnh0+HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHuM9cYmdKq5ZMfO0dQ5PB53nqZQ1YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc1w7bC0PD02M706ZdQm5M9Q9VFzLY0TK1nz19fsh2I2yuKwHJJeRxsFAUJKgrtNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN7nm6B/9erp5n4FDKJFxdnFWuhqqUwMzRa9rUfhOX1qJ1SYAWUryQ90rpxOwXt9Pfq0Y13VsWk3QQ8nyaEJzytEXG7OR9pf9zDQph4r4rpJbXCwNjXn/ThL shanyue 74 | ``` 75 | 76 | 在 github 的 ssh keys 设置中: 点击 `New SSH key` 添加刚才生成的 public key。 77 | 78 | 更多图文指引可以参照官方文档: 79 | 80 | ## 设置成功 81 | 82 | 使用 `ssh -T` 测试成功, 此时可以成功愉快地面向 github 编程了,再也不愁没地抄代码了。 83 | 84 | ```shell 85 | $ ssh -T git@github.com 86 | Hi shfshanyue! You've successfully authenticated, but GitHub does not provide shell access. 87 | 88 | $ git clone git@github.com:shfshanyue/vim-config.git 89 | Cloning into 'vim-config'... 90 | remote: Enumerating objects: 183, done. 91 | remote: Total 183 (delta 0), reused 0 (delta 0), pack-reused 183 92 | Receiving objects: 100% (183/183), 411.13 KiB | 55.00 KiB/s, done. 93 | Resolving deltas: 100% (100/100), done. 94 | ``` 95 | -------------------------------------------------------------------------------- /dnsmasq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 dnsmasq 为内部集群搭建本地 DNS 服务器 3 | thumbnail: http://www.tcpipguide.com/free/diagrams/dnsresolution.png 4 | tags: 5 | - linux 6 | 7 | --- 8 | 9 | # 搭建集群内部 DNS 服务器 10 | 11 | 当我们使用 `traefik` 反向代理和自动服务发现后,我们对集群内部的服务分为两类 12 | 13 | 1. 公有服务。如我的博客,网站,以及为它们提供服务的 API。我们可以通过公有的域名去映射服务使得外网能够访问,如通过我自己的域名 `shanyue.tech` 与 `xiange.tech`。 14 | 1. 私有服务。如 `gitlab`,`traefik Dashboard`,`redis`,`postgres` 以及自己实现的不公开的私有服务。我们可以通过自建 DNS 服务器,来对这些域名进行访问。如 `*.shanyue.local` 做 `A记录` 来映射到内部集群的网关入口 (当然也要做白名单,BasicAuth,DigestAuth,限制端口号转发等安全措施) 15 | 16 | 17 | 18 | 我们先来看一看 `DNS Lookup` 的流程 19 | 20 | ![dns lookup](http://www.tcpipguide.com/free/diagrams/dnsresolution.png) 21 | 22 | 而当有了 `dnsmasq` 后,请求私有服务会先去 `dnsmasq` 解析 IP 地址。而请求互联网,如百度,则会由 `dnsmasq` 转发至上游 DNS 服务器进行解析。 23 | 24 | + 原文链接: [搭建集群内部 DNS 服务器](https://github.com/shfshanyue/op-note/blob/master/dnsmasq.md) 25 | + 系列文章: [个人服务器运维指南](https://github.com/shfshanyue/op-note) 26 | 27 | ## dnsmasq 部署 28 | 29 | `dnsmasq` 部署自然也是使用 `docker compose`,配置文件如下 30 | 31 | ``` yaml 32 | version: '3' 33 | 34 | services: 35 | dns: 36 | image: jpillora/dnsmasq 37 | restart: always 38 | ports: 39 | - "53:53/udp" 40 | volumes: 41 | - ./dnsmasq.conf:/etc/dnsmasq.conf 42 | - ./resolv.conf:/etc/resolv.conf 43 | 44 | # 使用已存在的 traefik 的 network 45 | networks: 46 | default: 47 | external: 48 | name: traefik_default 49 | ``` 50 | 51 | 其中自然也是与 `traefik` 使用同一网络,挂载两个文件,关于文件配置如下所示 52 | 53 | + `dnsmasq.conf`: 关于 `dnsmasq` 的配置文件,可以配置关于内部集群的域名映射规则 54 | + `resolv.conf`: 关于上游DNS服务器的配置 55 | 56 | ## dnsmasq 配置 57 | 58 | 在 `dnsmasq` 中需要配置 `*.shanyue.local` 映射到内部集群,`./dnsmasq.conf` 配置文件如下所示。`172.18.0.1` 是 `traefik` 网络入口,详情参照我的文章 [traefik 简易介绍](https://shanyue.tech/op/traefik.html) 59 | 60 | ``` conf 61 | log-queries 62 | log-dhcp 63 | 64 | # 配置域名映射 65 | address=/docker.localhost/172.18.0.1 66 | address=/shanyue.local/172.18.0.1 67 | ``` 68 | 69 | 当访问 `www.baidu.com` 还是要通过公共的 DNS 服务的,如谷歌的 `8.8.8.8`,这里使用阿里云默认的 `nameserver`。`./resolv.conf` 配置文件如下所示 70 | 71 | ``` conf 72 | options timeout:2 attempts:3 rotate single-request-reopen 73 | nameserver 100.100.2.136 74 | nameserver 100.100.2.138 75 | ``` 76 | 77 | 由于在服务器中使用 `0.0.0.0:53` 作为 DNS 服务器,此时也需要更改服务器内部的 `/etc/resolv.conf`,修改如下 78 | 79 | ``` conf 80 | nameserver 127.0.0.1 81 | ``` 82 | 83 | 在本地局域网中,可以使用该服务器的 IP 地址作为 DNS 服务器。可以使用 `openvpn` 来连接本地环境与服务器集群。详情参考 [使用 openvpn 与集群内部服务通信](https://shanyue.tech/op/openvpn.html) 84 | 85 | ## DNS lookup 测试 86 | 87 | 此时使用 `host` 或 `dig` 对内部服务进行测试,均能返回正确的 IP 地址 88 | 89 | ``` bash 90 | $ host whoami.docker.localhost 91 | whoami.docker.localhost has address 172.18.0.1 92 | 93 | $ dig whoami.docker.localhost 94 | 172.18.0.1 95 | ``` 96 | 97 | 此时,`dnsmasq` 解析的日志显示如下 98 | 99 | ``` txt 100 | dnsmasq: query[A] whoami.docker.localhost from 172.18.0.1 101 | dnsmasq: config whoami.docker.localhost is 172.18.0.1 102 | ``` 103 | 104 | 再测试下 `www.baidu.com`,测试外部域名是否能够正常解析 105 | 106 | ``` bash 107 | $ dig www.baidu.com +short 108 | www.a.shifen.com. 109 | 220.181.38.149 110 | 220.181.38.150 111 | ``` 112 | 113 | 正常工作,`dnsmasq` 日志如下 114 | 115 | ``` txt 116 | dnsmasq: query[A] www.baidu.com from 172.18.0.1 117 | dnsmasq: forwarded www.baidu.com to 100.100.2.136 118 | dnsmasq: forwarded www.baidu.com to 100.100.2.138 119 | dnsmasq: reply www.baidu.com is 120 | dnsmasq: reply www.a.shifen.com is 220.181.38.149 121 | dnsmasq: reply www.a.shifen.com is 220.181.38.150 122 | ``` 123 | -------------------------------------------------------------------------------- /system-info.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 云服务器基本指标信息查看及命令 3 | keywords: 服务器系统信息,平均负载,系统监控,linux内存与CPU,linux版本,centos版本 4 | description: "关于云服务器系统的基础信息一般在购买时就有标明,至于一些资源的使用在云服务器服务商的控制台上也有相应的监控,如果你对监控有更细致化的需求,采用 node exporter + cadvisor + prometheus + grafana 也可以做地更为精细。但是最重要的是: 你要了解哪些指标,以及它们如何在服务器上用命令敲出来" 5 | date: 2019-10-02 14:00 6 | tags: 7 | - linux 8 | 9 | --- 10 | 11 | # 云服务器基本指标信息查看及命令 12 | 13 | 通过前几章内容的学习,我们已经可以很容易地进入云服务器及从 github 拉取代码。本章内容将会为你介绍如何查看云服务的基本配置。 14 | 15 | 关于云服务器系统的基础信息,在购买时就会有标明,至于一些资源的使用情况在云服务器服务商的控制台上也有相应的监控。 16 | 17 | > 如果你对监控有更细致化的需求,也可以采用 `node exporter` + `cadvisor` + `prometheus` + `grafana` 做更为精细的掌控。 18 | 19 | 但是最重要的是: **你要了解哪些指标,以及它们如何在服务器上用命令敲出来**,举例如下 20 | 21 | + 如何查看 linux 版本和 centos 版本号 22 | + 如何查看内存配额及使用情况 23 | + 如何查看CPU核心数量及CPU使用率 24 | + 如何查看磁盘使用情况 25 | + 如何查看服务器的平均负载 26 | + 如何获取服务器的公网 IP 以及私网 IP 27 | + 如何查看服务器登录的所有用户 28 | + 如何查看服务器登录的所有用户 29 | 30 | > 关于监控更多内容可以参考以下章节: [linux 各项监控指标](https://shanyue.tech/op/linux-monitor.html) 31 | 32 | 33 | 34 | + 原文地址: [linux 基础信息查看](https://shanyue.tech/op/system-info.html) 35 | + 系列文章: [服务器运维笔记](https://shanyue.tech/op/) 36 | 37 | ## linux 版本和 centos 版本 38 | 39 | ``` bash 40 | # 查看 linux 版本 41 | $ uname -a 42 | Linux shanyue 3.10.0-957.21.3.el7.x86_64 #1 SMP Tue Jun 18 16:35:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux 43 | 44 | # 查看 centos 版本号 45 | $ cat /etc/centos-release 46 | CentOS Linux release 7.6.1810 (Core) 47 | ``` 48 | 49 | ## 内存配额及使用情况 50 | 51 | 查看还有多少内存,available 指还有多少可用内存 52 | 53 | ```bash 54 | # -h 指打印可视化信息 55 | $ free -h 56 | total used free shared buff/cache available 57 | Mem: 3.7G 154M 2.1G 512K 1.5G 3.3G 58 | Swap: 0B 0B 0B 59 | ``` 60 | 61 | ## CPU 核心数量及使用率 62 | 63 | ``` bash 64 | # 查看 cpu 的核心数 65 | $ cat /proc/cpuinfo 66 | 67 | # 查看 68 | $ top 69 | 70 | $ htop 71 | ``` 72 | 73 | ## 磁盘使用情况 74 | 75 | ``` bash 76 | $ df -h 77 | ``` 78 | 79 | ## 平均负载 80 | 81 | `load average` 指单位时间内运行态进程及不可中断进程的平均进程数,运行态进程指正在使用或者等待使用 CPU 的进程,不可中断进程指正等待一些 IO 操作的进程。可使用 `uptime` 查看此指标。 82 | 83 | ```bash 84 | $ uptime 85 | 16:48:09 up 2 days, 23:43, 2 users, load average: 0.01, 0.21, 0.20 86 | ``` 87 | 88 | ## IP 89 | 90 | ```bash 91 | # 公网IP 92 | $ curl ifconfig.me 93 | 59.110.216.155 94 | 95 | # 公网IP,上个地址的网络在国内不太好 96 | $ curl icanhazip.com 97 | 59.110.216.155 98 | 99 | # 私网IP 100 | $ ifconfig eth0 101 | eth0: flags=4163 mtu 1500 102 | inet 172.17.68.39 netmask 255.255.240.0 broadcast 172.17.79.255 103 | ether 00:16:3e:0e:01:d8 txqueuelen 1000 (Ethernet) 104 | RX packets 416550 bytes 505253322 (481.8 MiB) 105 | RX errors 0 dropped 0 overruns 0 frame 0 106 | TX packets 194374 bytes 67561825 (64.4 MiB) 107 | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 108 | ``` 109 | 110 | ## 登录用户 111 | 112 | ```bash 113 | $ who -u 114 | who -u 115 | root pts/0 Oct 18 15:04 04:25 16860 (124.200.184.74) 116 | root pts/2 Oct 18 18:10 01:22 2545 (124.200.184.74) 117 | root pts/5 Oct 18 19:33 . 24952 (124.200.184.74) 118 | 119 | $ last -a | head -6 120 | root pts/5 Fri Oct 18 19:33 still logged in 124.200.184.74 121 | root pts/2 Fri Oct 18 18:10 still logged in 124.200.184.74 122 | root pts/2 Fri Oct 18 18:10 - 18:10 (00:00) 124.200.184.74 123 | root pts/2 Fri Oct 18 17:54 - 18:10 (00:16) 124.200.184.74 124 | root pts/2 Fri Oct 18 17:49 - 17:53 (00:03) 124.200.184.74 125 | root pts/2 Fri Oct 18 16:49 - 17:25 (00:36) 124.200.184.74 126 | ``` 127 | -------------------------------------------------------------------------------- /docker-compose.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: docker compose 简易入门 3 | keywords: docker 4 | date: 2019-11-19T20:02:20+08:00 5 | categories: 6 | - 运维 7 | - 后端 8 | thumbnail: https://docs.docker.com/engine/images/architecture.svg 9 | tags: 10 | - devops 11 | --- 12 | 13 | # docker compose 简易入门 14 | 15 | 当我们通过了解 [docker 简易入门](https://github.com/shfshanyue/op-note/blob/master/docker.md) 本篇文章后,想必此时我们已经可以基于 `nginx` 镜像创建一个最简单的容器:启动一个最简单的 http 服务 16 | 17 | ``` bash 18 | $ docker run -d --name nginx -p 8888:80 nginx:alpine 19 | 20 | $ docker ps -l 21 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 22 | 404e88f0d90c nginx:alpine "nginx -g 'daemon of…" 4 minutes ago Up 4 minutes 0.0.0.0:8888->80/tcp nginx 23 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 24 | ``` 25 | 26 | 其中有诸多参数命令 27 | 28 | + `-d`: 启动一个 `daemon` 进程 29 | + `--name`: 为容器指定名称 30 | + `-p host-port:container-port`: 宿主机与容器端口映射,方便容器对外提供服务 31 | + `nginx:alpine`: 基于该镜像创建容器 32 | 33 | 这还只是一个简单的 `nginx` 的容器,如果有更多的容器那应该如何管理呢? 34 | 35 | **使用 `docker-compose` 来编排应用** 36 | 37 | 38 | 39 | + 原文链接: [docker compose 简易入门](https://github.com/shfshanyue/op-note/blob/master/docker-compose.md) 40 | + 系列文章: [个人服务器运维指南](https://github.com/shfshanyue/op-note) 41 | 42 | ## 快速开始 43 | 44 | 使用 `docker-compose` 创建一个最简单的容器,创建 `docker-compose.yaml` 文件。它使用配置文件的方式代替以前传参数的方式启动容器 45 | 46 | ``` yaml 47 | version: '3' 48 | 49 | services: 50 | nginx: 51 | image: nginx:alpine 52 | container_name: nginx-service 53 | restart: always 54 | ports: 55 | - "8888:80" 56 | ``` 57 | 58 | 使用 `docker-compose up` 启动容器,它会自动查找当前目录下的 `docker-compose.yaml` 文件作为配置文件 59 | 60 | ``` bash 61 | # 启动 62 | $ docker-compose up 63 | 64 | # 启动三个实例 65 | $ docker-compose up --scale nginx=3 66 | 67 | # 查看日志,而不退出 68 | $ docker-compose logs -f 69 | 70 | # 停止 71 | $ docker-compose stop 72 | 73 | # 删除 74 | $ docker-compose rm 75 | 76 | # 在某个 Service 下的容器中执行命令 77 | $ docker-compose exec nginx sh 78 | ``` 79 | 80 | ## 配置文件 81 | 82 | 关于 `compose` 的所有的配置请参考官方文档 [compose file](https://docs.docker.com/compose/compose-file/),大部分配置与 `dockerfile` 配置相类似 83 | 84 | 配置文件管理三种资源,`services`,`networks` 以及 `volumes`,我们可以结合 `docker-compose` 与 `traefik` 来管理应用。如以下配置文件将可以以域名 `whiami.docker.localhost` 来访问应用,详情可参考 [traefik 简易入门](https://github.com/shfshanyue/op-note/blob/master/traefik.md) 85 | 86 | ``` yaml 87 | version: '3' 88 | 89 | # 配置 service 90 | services: 91 | whoami: 92 | image: containous/whoami 93 | restart: always 94 | labels: 95 | - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" 96 | 97 | # 配置 network 98 | networks: 99 | default: 100 | external: 101 | name: traefik_default 102 | ``` 103 | 104 | ### image 105 | 106 | 指定镜像 107 | 108 | ``` yaml 109 | image: nginx 110 | ``` 111 | 112 | ### build 113 | 114 | 可以直接根据当前目录构建,而无需镜像 115 | 116 | ``` yaml 117 | version: "3" 118 | services: 119 | webapp: 120 | build: . 121 | ``` 122 | 123 | ### ports 124 | 125 | 主机与容器的端口映射,但是在 `trafik` 代理下往往不需要指定 126 | 127 | ``` yaml 128 | ports: 129 | - "8080:80" 130 | ``` 131 | 132 | ### labels 133 | 134 | 用以筛选容器,在结合 `traefik` 或者 `k8s` 使用时,用以控制流量 135 | 136 | ``` yaml 137 | labels: 138 | com.example.description: "Accounting webapp" 139 | com.example.department: "Finance" 140 | com.example.label-with-empty-value: "" 141 | 142 | labels: 143 | - "com.example.description=Accounting webapp" 144 | - "com.example.department=Finance" 145 | - "com.example.label-with-empty-value" 146 | ``` 147 | 148 | ### container_name 149 | 150 | 指定容器名称,但是指定后不能够横向扩展,往往不会用到 151 | 152 | ## 容器管理 153 | 154 | 当使用 `docker-compose` 编排应用时,同时也可以选择 `ctop` 来管理容器。 155 | -------------------------------------------------------------------------------- /jq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: jq 命令详解及示例 3 | keywords: jq,json命令行工具,jq examples,json格式化工具 4 | description: "jq 是一款命令行的 json 处理工具。类似于 lodash 一样,它可以对 json 做各种各样的处理,如 pick,get,filter,sort,map" 5 | date: 2019-10-24 08:00 6 | sidebarDepth: 3 7 | tags: 8 | - linux 9 | 10 | --- 11 | 12 | # jq 命令详解及示例 13 | 14 | `jq` 是一款命令行的 `json` 处理工具。类似于 `lodash` 一样,它可以对 `json` 做各种各样的处理: `pick`,`get`,`filter`,`sort`,`map`... 15 | 16 | 由于 `jq` 本身比较简单,以下总结一些经常用到的示例。如果需要更多的细节,可以参考 [jq 官方文档](https://stedolan.github.io/jq/manual/) 17 | 18 | 先创建一个样例 `demo.jsonl`,`jsonl` 即每行都是一个 `json`,常用在日志格式中 19 | 20 | ```json 21 | {"name": "shanyue", "age": 24, "friend": {"name": "shuifeng"}} 22 | {"name": "shuifeng", "age": 25, "friend": {"name": "shanyue"}} 23 | ``` 24 | 25 | 由于在后端 API 中会是以 `json` 的格式返回,再次创建一个样例 `demo.json` 26 | 27 | ```json 28 | [ 29 | {"name": "shanyue", "age": 24, "friend": {"name": "shuifeng"}}, 30 | {"name": "shuifeng", "age": 25, "friend": {"name": "shanyue"}} 31 | ] 32 | ``` 33 | 34 | 35 | 36 | + 原文链接: [jq命令使用及示例](https://shanyue.tech/op/jq) · [github](https://github.com/shfshanyue/op-note/blob/master/jq.md) 37 | + 系列文章: [当我有台服务器时我做了什么](https://shanyue.tech/op) · [github](https://github.com/shfshanyue/op-note) 38 | 39 | ## jq 命令详解 40 | 41 | `jq` 主要可以分作两部分,options 即选项,filter 即各种转换操作,类似于 `lodash` 的各种函数 42 | 43 | ```shell 44 | jq [options...] filter [files] 45 | ``` 46 | 47 | > 强烈建议参考 [jq 官方手册](https://stedolan.github.io/jq/manual/),命令示例一应俱全 48 | 49 | ### option 50 | 51 | 我仅常用以下几个选项 52 | 53 | + `-s`: 把读取的 `jsonl` 视作数组来处理 (如 group, sort 只能以数组作为输入) 54 | + `-c`: 不对输出的 `json` 做格式化,一行输出 55 | 56 | ### filter 57 | 58 | filter 各种转换操作就很多了,如 `get`,`map`,`filter`,`map`,`pick`,`uniq`,`group` 等操作 59 | 60 | + `.`: 代表自身 61 | + `.a.b`: 相当于 `_.get(input, 'a.b')` 62 | + `select(bool)`: 相当于 `_.filter(boolFn)` 63 | + `map_values`: 相当于 `_.map`,不过 `jq` 无法单独操作 `key` 64 | + `sort` 65 | + `group_by` 66 | 67 | > 更多 filter 参考 [jq 官方手册](https://stedolan.github.io/jq/manual/) 68 | 69 | ## jq examples 70 | 71 | 虽然 `jq` 的功能很强大,但平时使用最为频繁的也就以下几个示例。当然复杂的情形也会有,参考我过去一篇使用 `jq` 改 `ts` 类型错误的一篇文章: [sequelize 升级记录](https://shanyue.tech/post/sequelize-upgrade.html#_07-%E5%BD%92%E5%B9%B6%E4%B8%8E%E5%88%86%E7%B1%BB%EF%BC%8C%E9%80%90%E4%B8%AA%E5%87%BB%E7%A0%B4) 72 | 73 | ### json to jsonl 74 | 75 | ```shell 76 | $ cat demo.json | jq '.[]' 77 | { 78 | "name": "shanyue", 79 | "age": 24, 80 | "friend": { 81 | "name": "shuifeng" 82 | } 83 | } 84 | { 85 | "name": "shuifeng", 86 | "age": 25, 87 | "friend": { 88 | "name": "shanyue" 89 | } 90 | } 91 | ``` 92 | 93 | ### jsonl to json 94 | 95 | ```shell 96 | # -s: 代表把 jsonl 组成数组处理 97 | $ cat demo.jsonl | jq -s '.' 98 | [ 99 | { 100 | "name": "shanyue", 101 | "age": 24, 102 | "friend": { 103 | "name": "shuifeng" 104 | } 105 | }, 106 | { 107 | "name": "shuifeng", 108 | "age": 25, 109 | "friend": { 110 | "name": "shanyue" 111 | } 112 | } 113 | ] 114 | ``` 115 | 116 | ### . (_.get) 117 | 118 | ```shell 119 | $ cat demo.jsonl | jq '.name' 120 | "shanyue" 121 | "shuifeng" 122 | ``` 123 | 124 | ### {} (_.pick) 125 | 126 | ```shell 127 | $ cat demo.jsonl| jq '{name, friendname: .friend.name}' 128 | { 129 | "name": "shanyue", 130 | "friendname": "shuifeng" 131 | } 132 | { 133 | "name": "shuifeng", 134 | "friendname": "shanyue" 135 | } 136 | ``` 137 | 138 | ### select (_.filter) 139 | 140 | ```shell 141 | $ cat demo.jsonl| jq 'select(.age > 24) | {name}' 142 | { 143 | "name": "shuifeng" 144 | } 145 | ``` 146 | 147 | ### map_values (_.map) 148 | 149 | ```shell 150 | $ cat demo.jsonl| jq '{age} | map_values(.+10)' 151 | { 152 | "age": 34 153 | } 154 | { 155 | "age": 35 156 | } 157 | ``` 158 | 159 | ### sort_by (_.sortBy) 160 | 161 | `sort_by` 需要先把 `jsonl` 转化为 `json` 才能进行 162 | 163 | ```shell 164 | # 按照 age 降序排列 165 | # -s: jsonl to json 166 | # -.age: 降序 167 | # .[]: json to jsonl 168 | # {}: pick 169 | $ cat demo.jsonl | jq -s '. | sort_by(-.age) | .[] | {name, age}' 170 | { 171 | "name": "shuifeng", 172 | "age": 25 173 | } 174 | { 175 | "name": "shanyue", 176 | "age": 24 177 | } 178 | 179 | # 按照 age 升序排列 180 | $ cat demo.jsonl | jq -s '. | sort_by(.age) | .[] | {name, age}' 181 | { 182 | "name": "shanyue", 183 | "age": 24 184 | } 185 | { 186 | "name": "shuifeng", 187 | "age": 25 188 | } 189 | ``` 190 | -------------------------------------------------------------------------------- /compose/traefik/traefik.toml: -------------------------------------------------------------------------------- 1 | [global] 2 | checkNewVersion = true 3 | sendAnonymousUsage = true 4 | 5 | ################################################################ 6 | # Entrypoints configuration 7 | ################################################################ 8 | 9 | # Entrypoints definition 10 | # 11 | # Optional 12 | # Default: 13 | [entryPoints] 14 | [entryPoints.web] 15 | address = ":80" 16 | 17 | [entryPoints.web.http] 18 | [entryPoints.web.http.redirections] 19 | [entryPoints.web.http.redirections.entryPoint] 20 | to = "websecure" 21 | scheme = "https" 22 | 23 | [entryPoints.websecure] 24 | address = ":443" 25 | 26 | ################################################################ 27 | # Traefik logs configuration 28 | ################################################################ 29 | 30 | # Traefik logs 31 | # Enabled by default and log to stdout 32 | # 33 | # Optional 34 | # 35 | [log] 36 | 37 | # Log level 38 | # 39 | # Optional 40 | # Default: "ERROR" 41 | # 42 | # level = "DEBUG" 43 | 44 | # Sets the filepath for the traefik log. If not specified, stdout will be used. 45 | # Intermediate directories are created if necessary. 46 | # 47 | # Optional 48 | # Default: os.Stdout 49 | # 50 | filePath = "log/traefik.log" 51 | 52 | # Format is either "json" or "common". 53 | # 54 | # Optional 55 | # Default: "common" 56 | # 57 | format = "json" 58 | 59 | ################################################################ 60 | # Access logs configuration 61 | ################################################################ 62 | 63 | # Enable access logs 64 | # By default it will write to stdout and produce logs in the textual 65 | # Common Log Format (CLF), extended with additional fields. 66 | # 67 | # Optional 68 | # 69 | [accessLog] 70 | 71 | # Sets the file path for the access log. If not specified, stdout will be used. 72 | # Intermediate directories are created if necessary. 73 | # 74 | # Optional 75 | # Default: os.Stdout 76 | # 77 | filePath = "log/traefik-access.json" 78 | 79 | # Format is either "json" or "common". 80 | # 81 | # Optional 82 | # Default: "common" 83 | # 84 | format = "json" 85 | 86 | [accessLog.fields] 87 | defaultMode = "keep" 88 | 89 | [accessLog.fields.headers] 90 | defaultMode = "keep" 91 | 92 | 93 | ################################################################ 94 | # API and dashboard configuration 95 | ################################################################ 96 | 97 | # Enable API and dashboard 98 | [api] 99 | 100 | # Enable the API in insecure mode 101 | # 102 | # Optional 103 | # Default: true 104 | # 105 | insecure = true 106 | 107 | # Enabled Dashboard 108 | # 109 | # Optional 110 | # Default: true 111 | # 112 | dashboard = true 113 | 114 | ################################################################ 115 | # Ping configuration 116 | ################################################################ 117 | 118 | # Enable ping 119 | [ping] 120 | 121 | # Name of the related entry point 122 | # 123 | # Optional 124 | # Default: "traefik" 125 | # 126 | # entryPoint = "traefik" 127 | 128 | ################################################################ 129 | # Docker configuration backend 130 | ################################################################ 131 | 132 | # Enable Docker configuration backend 133 | [providers.docker] 134 | 135 | # Docker server endpoint. Can be a tcp or a unix socket endpoint. 136 | # 137 | # Required 138 | # Default: "unix:///var/run/docker.sock" 139 | # 140 | # endpoint = "tcp://10.10.10.10:2375" 141 | 142 | # Default host rule. 143 | # 144 | # Optional 145 | # Default: "Host(`{{ normalize .Name }}`)" 146 | # 147 | 148 | # Expose containers by default in traefik 149 | # 150 | # Optional 151 | # Default: true 152 | # 153 | # exposedByDefault = false 154 | 155 | [metrics.prometheus] 156 | buckets = [0.1,0.3,1.2,5.0] 157 | entryPoint = "metrics" 158 | 159 | [certificatesResolvers.le.acme] 160 | email = "xianger94@qq.com" 161 | storage = "acme.json" 162 | 163 | [certificatesResolvers.le.acme.tlsChallenge] 164 | 165 | [certificatesResolvers.le.acme.httpChallenge] 166 | entryPoint = "web" 167 | -------------------------------------------------------------------------------- /when-server-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 当我有一台服务器时我做了什么 3 | keywords: linux,docker,next,graphql,grafana,prometheus,elk,服务器 4 | date: 2019-12-16T09:56:29+08:00 5 | categories: 6 | - 运维 7 | tags: 8 | - devops 9 | - linux 10 | hot: 10 11 | 12 | --- 13 | 14 | # 当我有一台服务器时我做了什么 15 | 16 | 当一八年末的时候,我写了一篇文章 [当我有一台服务器时我做了什么](https://github.com/shfshanyue/op-note/blob/master/when-server.md) 17 | 18 | 又是年末,我服务器的架构也发生了一些变化,因此总结一番 19 | 20 | 21 | 22 | + 原文地址: [当我有一台服务器时我做了什么](https://github.com/shfshanyue/op-note/blob/master/when-server-2019.md) 23 | + 系列文章: [当我有一台服务器时我做了什么](https://github.com/shfshanyue/op-note) 24 | 25 | ## 概览 26 | 27 | 去年服务器有两台,一台 2C4G,一台 1C2G 28 | 29 | 今年服务器有三台,以以下名称作为 `hostname`,配置如下 30 | 31 | + `dev`: 1C2G,不到一百块钱。用以日常编码,简单的反向代理以及项目部署 32 | + `shanyue`: 2C4G,k8s master node 33 | + `shuifeng`: 4C16G,k8s work node 34 | 35 | 由于 `dev` 的机器与去年列举出来的事情相似,这里只介绍下在这台1C2G的服务器上做了什么 36 | 37 | 简单画了这台服务器的架构图(不太会画,所以建了一个仓库 [shfshanyue/graph](https://github.com/shfshanyue/graph) 用以学习各种架构图画法) 38 | 39 | ![](https://raw.githubusercontent.com/shfshanyue/graph/master/draw/docker-compose.jpg) 40 | 41 | ## 博客与编码 42 | 43 | 基本上自己的博客以及个人编码都在这台测试服务器上完成,至于为什么要在服务器下开发: 44 | 45 | 1. 在公司 Mac 及我自己的笔记本间同步博客实在太痛苦了,而使用服务器作为中介则方便很多 46 | 47 | 由于在服务器下写博客以及一些个人的代码,因此我新买的 MBP 也变成了一个显示器 48 | 49 | ### 开发环境 50 | 51 | `zsh` + `tmux` + `vim`,截图如下 52 | 53 | ![](./assets/dev-env.png) 54 | 55 | 大部分时间都在这个模式下,如果写博客过程中需要截图,则先下载到随便一个目录,然后使用 `rsync` 复制到目标路径 56 | 57 | ``` bash 58 | $ rsync ~/Documents/tmux.png dev:/path/Documents/blog/op/assets/dev-env.png 59 | ``` 60 | 61 | `vscode remote` 62 | 63 | 如果需要调试代码,或者在写 `typescript`,则使用 `vscode remote` 来完成工作 64 | 65 | 在 vscode 插件中关键字搜索,安装下载最多的三个插件就是了 66 | 67 | ### 开发调试 68 | 69 | 如果调试前端页面需要在浏览器中打开地址,比如 `IP:8000`,一般采用两种方案 70 | 71 | 1. `nginx`镜像 + `volume`挂载 + `docker-compose` + `traefik`服务发现。略微麻烦 72 | 1. `npm run dev` + `openvpn`。在本地环境中的浏览器通过 `openvpn` 连接局域网 73 | 74 | 如果调试后端接口,需要打断点直接使用 `vscode remote` 75 | 76 | ## 对外服务 77 | 78 | 有几个在公网下可访问的服务,如 79 | 80 | + [公众号开发](https://github.com/shfshanyue/wechat): 主要用以给我的公众号导流 -> 如果想知道流程是什么,请转到这篇文章 [两年前端头条面试记](https://q.shanyue.tech/interviews/2018.html),从中的隐藏部分你便能知道大概。过几天,我将写一篇文章作为总结。 81 | + : 用以测试 `traefik` 的负载均衡及服务发现 82 | + 若干 `reveal.js` 页面 83 | 84 | ## 对内服务 85 | 86 | 主要以数据库为主,使用 `local DNS` + `traefik` + `openvpn` 暴露在本地环境,使用禁掉公网端口以及仅在内网访问的IP白名单保证安全 87 | 88 | + `postgres`,主要是一个关于诗词的数据库 89 | + `redis` 90 | + `traefik dashboard`,管理流量 91 | 92 | 另外,这些对内对外的服务均是通过 `docker` 以及 `docker-compose` 部署 93 | 94 | ## 博客去了哪里? 95 | 96 | 以下是我博客的历程 97 | 98 | 1. 个人服务器,后来服务器部署了 `k8s` 就把博客挪出了 99 | 1. `netlify`,但是网络不好 100 | 1. `alioss` + `github actions`,速度挺好,但是对 `http rewrite` 支持的不是很好 101 | 102 | 以后将会考虑 `serverless` 103 | 104 | 你可以发现,我现在更多的转向了一些免费的云服务,如 105 | 106 | 1. `serverless` 可以写后端服务,我将把我的公众号的服务迁移过来。国内可用阿里云以及腾讯云,国外 aws 107 | 1. `dynomodb` 与 `tablestore` 免费的数据存储 108 | 1. `oss` 很便宜的对象存储服务 109 | 1. `netlify` 免费的静态网站托管托管服务 110 | 1. `github actions` 免费的CICD及构建服务器 111 | 1. `sentry` 免费的错误日志收集系统 112 | 1. `github` 免费的私有仓库服务 113 | 1. `prerender.io` 免费的预渲染服务 114 | 115 | 嗯,有了这些都可以做一个自由开发者了 (自惭形秽中...) 116 | 117 | ## openVPN 118 | 119 | 数据库放在公网访问有点危险,用docker建了vpn在本地开发访问。使用了以下镜像 120 | 121 | [docker-openvpn](https://github.com/kylemanna/docker-openvpn) 122 | 123 | ## traefik 124 | 125 | ![traefik dashboard](./assets/traefik-dashboard.png) 126 | 127 | 前后端需要做一个反向代理,选择了 traefik,更方便的服务配置以及服务发现,只需要配置容器的 `labels` 就可以部署成功 128 | 129 | 另外 `traefik` 可以很方便的自动生成 ssl/tls 证书,为你提供 https 服务 130 | 131 | ## DNS server 132 | 133 | 有了这么多的服务,但有的东西不好放在公网,如 `redis`,`postgres` 一些私有服务以及开发待调试的服务,又记不住端口号,所以又搭了一个 `dns server`,方便在本地访问 134 | 135 | ## 自动化运维 136 | 137 | 初期折腾服务器的时候经常需要重装系统,并且我有三台服务器,自动化运维是必不可少的了。 138 | 139 | 必备工具如 `docker`,`git`,`vim`,`tmux`,`jq` 都是通过 `ansible` 进行的安装 140 | 141 | 可以参考我的配置 142 | 143 | + 144 | 145 | 当你有了一台新服务器时,你可以遵循以下步骤 146 | 147 | 1. 使用 ansible-role 预配置环境 148 | 1. 如果没有 ansible-role,则自己写 role 149 | 1. 对于一些服务使用 docker 进行安装 150 | 1. 如果以上都无法解决,手动安装 151 | 152 | ## 监控 153 | 154 | 没有像去年那样使用 `prometheus` 一套,只简单了使用了两个命令以及阿里云自带的监控 155 | 156 | + `ctop`: 监控容器 157 | + `htop`: 监控进程 158 | 159 | ## 对比 160 | 161 | 如果说与去年有对比的话,体现在两方面 162 | 163 | 1. 更彻底的容器化 164 | 1. 更加拥抱云服务,如 `github actions`,serverless,netlify 等 165 | 166 | 另外,还有一方面是自建了 k8s 集群 (真是烧钱),将会在另一个仓库中介绍它的体系。但是如果你对 k8s 没有什么兴趣的话,**这一台1C2G的服务器完全满足你的要求** 167 | -------------------------------------------------------------------------------- /linux-sed.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: sed 命令详解及示例 3 | keywords: sed examples,mac中的sed,sed删除文件,sed替换文件 4 | description: sed 是一个用来筛选与转换文本内容的工具。一般用来批量替换,删除某行文件。如果想在 mac 中使用 sed,请使用 gsed 代替,不然会被坑 5 | date: 2019-10-18 22:00 6 | sidebarDepth: 3 7 | tags: 8 | - linux 9 | 10 | --- 11 | 12 | # sed 命令详解及示例 13 | 14 | `sed` 是一个用来筛选与转换文本内容的工具。一般用来批量替换,删除某行文件 15 | 16 | 17 | 18 | + 原文链接: [sed命令使用及示例](https://shanyue.tech/op/linux-sed) · [github](https://github.com/shfshanyue/op-note/blob/master/linux-sed.md) 19 | + 系列文章: [当我有台服务器时我做了什么](https://shanyue.tech/op) · [github](https://github.com/shfshanyue/op-note) 20 | 21 | ## sed 命令详解 22 | 23 | 每个 sed 命令,基本可以由选项,匹配与对应的操作来完成 24 | 25 | ```shell 26 | # 打印文件第三行到第五行 27 | # -n: 选项,代表打印 28 | # 3-5: 匹配,代表第三行到第五行 29 | # p: 操作,代表打印 30 | $ sed -n '3,5p' file 31 | 32 | # 删除文件第二行 33 | # -i: 选项,代表直接替换文件 34 | # 2: 匹配,代表第二行 35 | # d: 操作,代表删除 36 | $ sed -i '2d' file 37 | ``` 38 | 39 | ### 关键选项 40 | 41 | + `-n`: 打印匹配内容行 42 | + `-i`: 直接替换文本内容 43 | + `-f`: 指定 sed 脚本文件,包含一系列 sed 命令 44 | 45 | ### 匹配 46 | 47 | + `/reg/`: 匹配正则 48 | + `3`: 数字代表第几行 49 | + `$`: 最后一行 50 | + `1,3`: 第一行到第三行 51 | + `1,+3`: 第一行,并再往下打印三行 (打印第一行到第四行) 52 | + `1, /reg/` 第一行,并到匹配字符串行 53 | 54 | ### 操作 55 | 56 | + `a`: append, 下一行插入内容 57 | + `i`: insert, 上一行插入内容 58 | + `p`: print,打印,通常用来打印文件某几行,通常与 `-n` 一起用 59 | + `s`: replace,替换,与 vim 一致 60 | 61 | ## sed examples 62 | 63 | ### 查看手册 64 | 65 | ``` 66 | $ man sed 67 | ``` 68 | 69 | ### 打印特定行 70 | 71 | `p` 指打印 72 | 73 | ```shell 74 | # 1p 指打印第一行 75 | $ ps -ef | sed -n 1p 76 | UID PID PPID C STIME TTY TIME CMD 77 | 78 | 79 | # 2,5p 指打印第2-5行 80 | $ ps -ef | sed -n 2,5p 81 | root 1 0 0 Sep29 ? 00:03:42 /usr/lib/systemd/systemd --system --deserialize 15 82 | root 2 0 0 Sep29 ? 00:00:00 [kthreadd] 83 | root 3 2 0 Sep29 ? 00:00:51 [ksoftirqd/0] 84 | root 5 2 0 Sep29 ? 00:00:00 [kworker/0:0H] 85 | ``` 86 | 87 | ### 打印最后一行 88 | 89 | `$` 指最后一行 90 | 91 | > 注意需要使用单引号 92 | 93 | ```shell 94 | $ ps -ef | sed -n '$p' 95 | ``` 96 | 97 | ### 删除特定行 98 | 99 | `d` 指删除 100 | 101 | ```shell 102 | $ cat hello.txt 103 | hello, one 104 | hello, two 105 | hello, three 106 | 107 | # 删除第三行内容 108 | $ sed '3d' hello.txt 109 | hello, one 110 | hello, two 111 | ``` 112 | 113 | ### 过滤字符串 114 | 115 | 与 `grep` 类似,不过 `grep` 可以高亮关键词 116 | 117 | ```shell 118 | $ ps -ef | sed -n /ssh/p 119 | root 1188 1 0 Sep29 ? 00:00:00 /usr/sbin/sshd -D 120 | root 9291 1188 0 20:00 ? 00:00:00 sshd: root@pts/0 121 | root 9687 1188 0 20:02 ? 00:00:00 sshd: root@pts/2 122 | root 11502 9689 0 20:08 pts/2 00:00:00 sed -n /ssh/p 123 | root 14766 1 0 Sep30 ? 00:00:00 ssh-agent -s 124 | 125 | $ ps -ef | grep ssh 126 | root 1188 1 0 Sep29 ? 00:00:00 /usr/sbin/sshd -D 127 | root 9291 1188 0 20:00 ? 00:00:00 sshd: root@pts/0 128 | root 9687 1188 0 20:02 ? 00:00:00 sshd: root@pts/2 129 | root 12200 9689 0 20:10 pts/2 00:00:00 grep --color=auto ssh 130 | root 14766 1 0 Sep30 ? 00:00:00 ssh-agent -s 131 | ``` 132 | 133 | ### 删除匹配字符串的行 134 | 135 | ```shell 136 | $ cat hello.txt 137 | hello, one 138 | hello, two 139 | hello, three 140 | 141 | $ sed /one/d hello.txt 142 | hello, two 143 | hello, three 144 | ``` 145 | 146 | ### 替换内容 147 | 148 | `s` 代表替换,与 vim 类似 149 | 150 | ```shell 151 | $ echo hello | sed s/hello/world/ 152 | world 153 | ``` 154 | 155 | ### 添加内容 156 | 157 | `a` 与 `i` 代表在新一行添加内容,与 vim 类似 158 | 159 | ```shell 160 | # i 指定前一行 161 | # a 指定后一行 162 | # -e 指定脚本 163 | $ echo hello | sed -e '/hello/i hello insert' -e '/hello/a hello append' 164 | hello insert 165 | hello 166 | hello append 167 | ``` 168 | 169 | ### 替换文件内容 170 | 171 | ```shell 172 | $ cat hello.txt 173 | hello, world 174 | hello, world 175 | hello, world 176 | 177 | # 把 hello 替换成 world 178 | $ sed -i s/hello/world/g hello.txt 179 | 180 | $ cat hello.txt 181 | world, world 182 | world, world 183 | world, world 184 | ``` 185 | ## 注意事项 186 | 187 | 如果想在 mac 中使用 `sed`,请使用 `gsed` 替代,不然在正则或者某些格式上扩展不全。 188 | 189 | 使用 `brew install gnu-sed` 安装 190 | 191 | ```shell 192 | $ echo "hello" | sed "s/\bhello\b/world/g" 193 | hello 194 | $ brew install gnu-sed 195 | $ echo "hello" | gsed "s/\bhello\b/world/g" 196 | world 197 | ``` 198 | 199 | ## 参考 200 | 201 | + [Linux Sed Command](https://www.computerhope.com/unix/used.htm) 202 | 203 | -------------------------------------------------------------------------------- /deploy-sentry.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 docker 部署异常监控服务 Sentry 3 | keywords: docker,sentry,异常监控服务 4 | date: 2019-06-20T20:02:20+08:00 5 | categories: 6 | - 运维 7 | - 后端 8 | tags: 9 | - devops 10 | --- 11 | 12 | # 异常监控服务 Sentry 部署 13 | 14 | [Sentry](https://github.com/getsentry/sentry) 是一个使用 python 写的异常监控服务,并有支持很多语言的 SDK。 15 | 16 | 这里有官方部署文档和 compose file: [getsentry/onpremise](https://github.com/getsentry/onpremise) 17 | 18 | 在当前部署 sentry 时 (2019/06/20),文档处于这个 commit 的位置 [ae39a6](https://github.com/getsentry/onpremise/tree/ae39a61d4d8a8ec8b9fd7af9c1d64e80c9bdd640) 19 | 20 | **虽然每次部署服务时的首选是看看有没有官方以及社区 star 较多的 compose file,但是他们的 compose file 也是要大致看一看的** 21 | 22 | 23 | 24 | 本文链接: [使用 docker 部署异常监控服务 sentry](https://shanyue.tech/op/deploy-sentry) 25 | 26 | > 虽然这篇文章介绍 Sentry 的部署,但还是推荐 Saas 版,省了运维麻烦,而且功能也更加齐全 27 | 28 | ## Requirements 29 | 30 | + Docker 1.10.0+ 31 | + Compose 1.17.0+ (optional) 32 | + 最少 3G 的内存 (这是官方要求,以下经测试,消耗了 600MB 左右) 33 | 34 | ## 反向代理 (可选) 35 | 36 | **由于我使用了 `traefik` 作为方向代理,直接修改 `docker-compose.yml`,添加 `label`,并把端口映射给隐去** 37 | 38 | 你需要把 `sentry.hostname.com` 替换为你将要设置的域名 39 | 40 | ```yaml 41 | web: 42 | <<: *defaults 43 | expose: 44 | - 9000 45 | labels: 46 | - "traefik.frontend.rule=Host:sentry.hostname.com" 47 | ``` 48 | 49 | 如果使用 `nginx` 作为反向代理,设置 `proxy-pass` 即可,不细讲。 50 | 51 | ## 部署 52 | 53 | 这里有官方部署文档和 compose file: ,以下是我的部署命令 54 | 55 | ``` bash 56 | # 安装之前,先看一下内存使用情况 57 | $ free -h 58 | total used free shared buff/cache available 59 | Mem: 3.7G 1.0G 204M 140M 2.5G 2.2G 60 | Swap: 0B 0B 0B 61 | 62 | $ git clone git@github.com:getsentry/onpremise.git 63 | $ cd onpremise 64 | 65 | # 创建 name volume,方便持久化,其实挂载的时候指定目录也是一样的。 66 | $ docker volume create --name=sentry-data && docker volume create --name=sentry-postgres 67 | sentry-data 68 | sentry-postgres 69 | 70 | # `-n` 代表不覆盖文件,可以理解为 `redis` 的 `setnx` 命令 71 | $ cp -n .env.example .env 72 | 73 | # 生成 key 放到 .env 文件中 74 | $ docker-compose run --rm web config generate-secret-key 75 | 76 | # 生成数据库,并在这一步设置超级用户 77 | $ docker-compose run --rm web upgrade 78 | 79 | # 启动服务 80 | $ docker-compose up -d 81 | 82 | # docker ps 查看,启动成功,如果你没有设置 traefik,PORTS 那一列会把端口号映射到 Host 83 | $ docker ps 84 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 85 | 0acc4dedf59f onpremise_web "/entrypoint.sh run …" 4 seconds ago Up 3 seconds 9000/tcp onpremise_web_1 86 | 8eebadc9e2ff onpremise_worker "/entrypoint.sh run …" 2 minutes ago Up 2 minutes 9000/tcp onpremise_worker_1 87 | 9cce91ae40d3 onpremise_cron "/entrypoint.sh run …" 2 minutes ago Up 2 minutes 9000/tcp onpremise_cron_1 88 | 89 | # 查看 Host 的内存消耗,与刚开始大约 600MB 90 | $ free -h 91 | total used free shared buff/cache available 92 | Mem: 3.7G 1.6G 1.0G 165M 1.1G 1.6G 93 | Swap: 0B 0B 0B 94 | 95 | # 单独查看 sentry 的内存消耗,大约加起来 600MB,与刚才的数据相一致 96 | $ docker stats | head -7 97 | CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 98 | 0acc4dedf59f onpremise_web_1 0.00% 331MiB / 3.702GiB 8.73% 328kB / 4.99MB 7.68MB / 0B 19 99 | 8eebadc9e2ff onpremise_worker_1 0.00% 138.3MiB / 3.702GiB 3.65% 2.69MB / 9.54MB 34.5MB / 0B 7 100 | 9cce91ae40d3 onpremise_cron_1 0.00% 97.52MiB / 3.702GiB 2.57% 869kB / 1.24MB 37.8MB / 156kB 3 101 | 01788eef014f onpremise_memcached_1 0.00% 8.871MiB / 3.702GiB 0.23% 261kB / 154kB 11.2MB / 0B 10 102 | 66a27f681af3 onpremise_postgres_1 0.00% 5.297MiB / 3.702GiB 0.14% 1.94MB / 1.45MB 83MB / 96.8MB 8 103 | 645fd1e25d78 onpremise_smtp_1 0.00% 800KiB / 3.702GiB 0.02% 672B / 0B 5.27MB / 57.3kB 2 104 | ``` 105 | 106 | 107 | 108 | 从十五分钟的内存使用图表中可以看出部署完成后有一个尖峰,后逐渐平稳了下来。 109 | 110 | ![部署成功](https://raw.githubusercontent.com/shfshanyue/op-note/master/assets/sentry-ok.jpg) 111 | 112 | 进入反向代理设置的域名 ,部署成功 113 | -------------------------------------------------------------------------------- /dockerfile-practice.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: dockerfile 最佳实践及示例 3 | keywords: docker,docker最佳实践 4 | date: 2019-12-14T20:02:20+08:00 5 | description: Dockerfile 最佳实践已经出现在官方文档中,地址在 Best practices for writing Dockerfiles。如果再写一份最佳实践,倒有点关公门前耍大刀之意。因此本篇文章是对官方文档的翻译,理解,扩展与示例补充 6 | categories: 7 | - 运维 8 | - 后端 9 | thumbnail: https://docs.docker.com/engine/images/architecture.svg 10 | tags: 11 | - devops 12 | --- 13 | 14 | # Dockerfile 最佳实践 15 | 16 | Dockerfile 最佳实践已经出现在官方文档中,地址在 [Best practices for writing Dockerfiles](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)。如果再写一份最佳实践,倒有点关公门前耍大刀之意。因此本篇文章是对官方文档的翻译,理解,扩展与示例补充 17 | 18 | 19 | 20 | + 原文地址: [dockerfile 最佳实践](https://github.com/shfshanyue/op-note/blob/master/dockerfile-practice.md) 21 | + 系列文章: [个人服务器运维指南](https://github.com/shfshanyue/op-note) 22 | 23 | > 如果本篇文章能够对你有所帮助,可以帮我在 [shfshanyue/op-note](https://github.com/shfshanyue/op-note) 上点个 star 24 | 25 | ## 容器应该是短暂的 26 | 27 | 通过 `Dockerfile` 构建的镜像所启动的容器应该尽可能短暂 (ephemeral)。短暂意味着可以很快地启动并且终止 28 | 29 | ## 使用 .dockerignore 排除构建无关文件 30 | 31 | `.dockerignore` 语法与 `.gitignore` 语法一致。使用它排除构建无关的文件及目录,如 `node_modules` 32 | 33 | ## 使用多阶段构建 34 | 35 | 多阶段构建可以有效减小镜像体积,特别是对于需编译语言而言,一个应用的构建过程往往如下 36 | 37 | 1. 安装编译工具 38 | 1. 安装第三方库依赖 39 | 1. 编译构建应用 40 | 41 | 而在前两步会有大量的镜像体积冗余,使用多阶段构建可以避免这一问题 42 | 43 | 这是构建 `Go` 应用的一个示例 44 | 45 | ``` dockerfile 46 | FROM golang:1.11-alpine AS build 47 | 48 | # Install tools required for project 49 | # Run `docker build --no-cache .` to update dependencies 50 | RUN apk add --no-cache git 51 | RUN go get github.com/golang/dep/cmd/dep 52 | 53 | # List project dependencies with Gopkg.toml and Gopkg.lock 54 | # These layers are only re-built when Gopkg files are updated 55 | COPY Gopkg.lock Gopkg.toml /go/src/project/ 56 | WORKDIR /go/src/project/ 57 | # Install library dependencies 58 | RUN dep ensure -vendor-only 59 | 60 | # Copy the entire project and build it 61 | # This layer is rebuilt when a file changes in the project directory 62 | COPY . /go/src/project/ 63 | RUN go build -o /bin/project 64 | 65 | # This results in a single layer image 66 | FROM scratch 67 | COPY --from=build /bin/project /bin/project 68 | ENTRYPOINT ["/bin/project"] 69 | CMD ["--help"] 70 | ``` 71 | 72 | 这是构建前端应用的一个示例,可以参考 [如何使用 docker 高效部署前端应用](https://github.com/shfshanyue/op-note/blob/master/deploy-fe-with-docker.md) 73 | 74 | ``` dockerfile 75 | FROM node:10-alpine as builder 76 | 77 | ENV PROJECT_ENV production 78 | ENV NODE_ENV production 79 | 80 | # http-server 不变动也可以利用缓存 81 | WORKDIR /code 82 | 83 | ADD package.json /code 84 | RUN npm install --production 85 | 86 | ADD . /code 87 | RUN npm run build 88 | 89 | # 选择更小体积的基础镜像 90 | FROM nginx:10-alpine 91 | COPY --from=builder /code/public /usr/share/nginx/html 92 | ``` 93 | 94 | ## 避免安装不必要的包 95 | 96 | 减小体积,减少构建时间。如前端应用使用 `npm install --production` 只装生产环境所依赖的包。 97 | 98 | ## 一个容器只做一件事 99 | 100 | 如一个web应用将会包含三个部分,web 服务,数据库与缓存。把他们解耦到多个容器中,方便横向扩展。如果你需要网络通信,则可以将他们至于一个网络下。 101 | 102 | 如在我的个人服务器中,我使用 `traefik` 做负载均衡与服务发现,所有应用及数据库都在 `traefik_default` 网络下,详情参考 [使用 traefik 做负载均衡与服务发现](https://github.com/shfshanyue/op-note/blob/master/traefik.md) 103 | 104 | ``` yaml 105 | version: '3' 106 | 107 | services: 108 | # 该镜像会暴露出自身的 `header` 信息 109 | whoami: 110 | image: containous/whoami 111 | restart: always 112 | labels: 113 | # 设置Host 为 whoami.docker.localhost 进行域名访问 114 | - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" 115 | 116 | # 使用已存在的 traefik 的 network 117 | networks: 118 | default: 119 | external: 120 | name: traefik_default 121 | ``` 122 | 123 | ## 减少镜像层数 124 | 125 | + 只有 `RUN`, `COPY`, `ADD` 会创建层数, 其它指令不会增加镜像的体积 126 | + 尽可能使用多阶段构建 127 | 128 | 使用以下方法安装依赖 129 | 130 | ``` dockerfile 131 | RUN yum install -y node python go 132 | ``` 133 | 134 | 错误的方法安装依赖,这将增加镜像层数 135 | 136 | ``` dockerfile 137 | RUN yum install -y node 138 | RUN yum install -y python 139 | RUN yum install -y go 140 | ``` 141 | 142 | ## 将多行参数排序 143 | 144 | 便于可读性以及不小心地重复装包 145 | 146 | ``` dockerfile 147 | RUN apt-get update && apt-get install -y \ 148 | bzr \ 149 | cvs \ 150 | git \ 151 | mercurial \ 152 | subversion 153 | ``` 154 | 155 | ## 充分利用构建缓存 156 | 157 | 在镜像的构建过程中 `docker` 会遍历 `Dockerfile` 文件中的所有指令,顺序执行。对于每一条指令,`docker` 都会在缓存中查找是否已存在可重用的镜像,否则会创建一个新的镜像 158 | 159 | 我们可以使用 `docker build --no-cache` 跳过缓存 160 | 161 | + `ADD` 和 `COPY` 将会计算文件的 `checksum` 是否改变来决定是否利用缓存 162 | + `RUN` 仅仅查看命令字符串是否命中缓存,如 `RUN apt-get -y update` 可能会有问题 163 | 164 | 如一个 `node` 应用,可以先拷贝 `package.json` 进行依赖安装,然后再添加整个目录,可以做到充分利用缓存的目的。 165 | 166 | ``` dockerfile 167 | FROM node:10-alpine as builder 168 | 169 | WORKDIR /code 170 | 171 | ADD package.json /code 172 | # 此步将可以充分利用 node_modules 的缓存 173 | RUN npm install --production 174 | 175 | ADD . /code 176 | 177 | RUN npm run build 178 | ``` 179 | -------------------------------------------------------------------------------- /when-server.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 当我有一台服务器时我做了什么 3 | keywords: linux,docker,next,graphql,grafana,prometheus,elk,服务器 4 | date: 2019-01-21T09:56:29+08:00 5 | categories: 6 | - 运维 7 | tags: 8 | - devops 9 | - linux 10 | hot: 10 11 | 12 | --- 13 | 14 | > 记于一八年末 15 | 16 | + 原文地址: [当我有一台服务器时我做了什么](https://shanyue.tech/op/when-server.html) 17 | + 系列文章: [当我有一台服务器时我做了什么](https://shanyue.tech/op) 18 | 19 | ## 前端调研 20 | 21 | 刚开始调研服务器渲染写了一个 demo,正好对诗词感兴趣,就做了一个关于诗词的站点,越写越大,demo 变成了 DEMO,总的来说还是特别简单。随手挂到了服务器下。 22 | 23 | 24 | 25 | 技术栈 👉 `Next.js` + `React` + `ApolloClient` + `Docker` + `Docker Compose` 26 | 27 | 网址如 👉 [诗词弦歌](https://shici.xiange.tech) 28 | 29 | 后来又把自己的博客挂了上去 👉 [山月的博客](https://shanyue.tech) 30 | 31 | ## 后端支持 32 | 33 | 有了前端自然需要服务器端支持,遵循着简单方便好用易于上手的原则,在 parse-server 与 graphql 直接做抉择,最后选了 graphql。并做了一个半成品的脚手架。支持以下功能 34 | 35 | > 半成品脚手架挂到了github https://github.com/shfshanyue/graphql-sequelize-starter 36 | 37 | + N+1 query 38 | + 对数据库字段的按需加载 39 | + 对特定 field 缓存的中间件 40 | + 接入 Sentry 41 | + 接入 Consul kev/value store 42 | + jwt 做身份认证 43 | + 结构化错误信息,并使用 sentry 报警 44 | + 结构化日志信息,方便 elk 分析 45 | + Docker 与 Docker Compose 46 | + Traefik 做负载均衡(并没有)与反向代理 47 | 48 | > 这个脚手架在这里有一个活的示例 49 | > 你需要先使用 shanyue/shanyue 做账号密码登录,登录的 mutation 是 createUserToken 50 | 51 | 那时候顺手写了一个前端的半半半半半成品脚手架,[shfshanyu/react-apollo-starter](https://github.com/shfshanyue/react-apollo-starter),不知道现在还能不能跑得起来...... 52 | 53 | 技术栈 👉 Graphql + ApolloServer + Sequelize + Docker + JWT + (Traefik + Sentry + Consul) 54 | 55 | ![graphql](./assets/graphql.jpg) 56 | 57 | ## Docker 58 | 59 | 部署的时候使用 docker + docker-compose,拉代码重启解决。也经常在上边做一些 docker 的测试 60 | 61 | ## 数据库 62 | 63 | 后端依赖于数据,于是又用 docker 部署了 redis 与 postgres 64 | 65 | 当你接入监控后你会发现 postgres 从刚开始到使用所占内存越来越大,这是有一部分数据从磁盘走到了内存。 66 | 67 | ## VPN 68 | 69 | 数据库放在公网访问有点危险,于是又用docker建了vpn在本地开发访问。使用了以下镜像 70 | 71 | [docker-openvpn](https://github.com/kylemanna/docker-openvpn) 72 | 73 | 虽然配好了,但还是很懵逼,目前只控制了某个 cidr 段走 vpn 74 | 75 | ## 配置服务 76 | 77 | 后端需要配置服务,用 docker 部署了 consul,只用它的 key-value 存储,它的服务发现功能就这么被浪费掉了 78 | 79 | 直接裸机安装下载 80 | 81 | ``` shell 82 | $ consul agent -data-dir=/consul/data -config-dir=/consul/config -server -data-dir=/consul/data -advertise 172.17.68.39 -bootstrap-expect=1 -node=consul-server -bind=0.0.0.0 -client=0.0.0.0 -ui 83 | ``` 84 | 85 | ## 错误收集系统 86 | 87 | 无论前端还是后端都需要一个错误收集系统,于是又用 docker 部署了 sentry 88 | 89 | sentry 依赖于 redis,postgres,我直接把依赖指向了与我的站点公用的 redis 和 postgres。一来以后迁移是一个问题,二来错误日志过多甚至有可能把数据库给弄爆掉。不过不管了,我的站点也就我一个用户,没有人用就没有错误,没有错误就没有问题。 90 | 91 | ## 反向代理 92 | 93 | 前后端需要做一个反向代理,偶然选择了 traefik,至少比 nginx 多个漂亮的界面,更方便的服务配置,还可以做服务发现,缺点就是文档少 94 | 95 | traefik 也直接在裸机安装,docker 起的直接配置 label,代理端口号直接使用 file。后来写了篇文章简单介绍了它 96 | 97 | [Traefik 入手及简单配置](https://github.com/shfshanyue/blog/blob/master/Articles/Traefik/Readme.md) 98 | 99 | ![traefik](./assets/traefik.jpg) 100 | 101 | 至于图上为什么有这么多请求,那是因为 `gitlab runner` 每秒请求一次 gitlab。 102 | 103 | ## 日志系统 104 | 105 | 搭建了 elk。但鉴于目前访问最多的三个小伙伴分别是谷歌小蜘蛛,百度小蜘蛛以及我自己,自从搭建起来就没有使用过 106 | 107 | > 参考搭建: 108 | 109 | 有可能还需要一个 file beats,但是还没弄过 110 | 111 | ## 代码仓库 112 | 113 | 一些个人代码,学习记录,以及自己一些关于山水花草的笔记需要一个地方放,搭了一个 gitlab,不过 gitlab 吃了我两个多G的内存... 114 | 115 | 注意关掉 gitlab 的 `prometheus` 等依赖,不然会吃很多内存 116 | 117 | ## CI 118 | 119 | 搭建了 gitlab 以后,为了配套 gitlab,后又搭建了 gitlab-runner,做 CI/CD。 120 | 121 | 不过目前只有博客接入了 CI,因为只有博客是 CI 之后才搭建的,感觉最先开始的前后端项目要废掉... 122 | 123 | ## DNS server 124 | 125 | 有了这么多的服务,但有的东西不好放在公网,如 consul,redis,postgres以及gitlab,又记不住端口号,所以又搭了一个 dns server,方便在本地访问 126 | 127 | ```shell 128 | yum install dnsmasq 129 | ``` 130 | 131 | ## 文件编辑与窗口管理 132 | 133 | vim 和 tmux 在linux上是标配,提高在服务器的工作效率,建议使用源码编译安装。yum即使配置了epel,装的包版本也过低,且缺少很多功能。如 vim 打开文件目录。以下是我自己 vim 和 tmux 的配置 134 | 135 | + https://github.com/shfshanyue/vim-config 136 | + https://github.com/shfshanyue/tmux-config 137 | 138 | ![tmux](https://cloud.githubusercontent.com/assets/553208/9890797/8dffe542-5c02-11e5-9c06-a25b452e6fcc.gif) 139 | 140 | ## 自动化运维 141 | 142 | 初期折腾服务器的时候经常需要重装系统,并且我有两台服务器,自动化运维是必不可少的了。一般我用 ansible 做一些服务器的预配置,一些必备工具如 docker,git,vim,tmux,jq,auto-jump 的安装。 143 | 144 | 由于我的服务器都是 centos,playbook 写的有点糟糕。 145 | 146 | + https://github.com/shfshanyue/ansible-op 147 | 148 | 不过服务器里大部分服务通过 docker-compose 管理,小部分工具通过 ansible role 管理,实在没有再自己写 ansible-playbook。 149 | 150 | 当你有了一台新服务器时,你可以遵循以下步骤 151 | 152 | 1. 使用 ansible-role 预配置环境 153 | 1. 如果没有 ansible-role,则自己写 ansible-playbook 154 | 1. 对于一些服务使用 docker 进行安装 155 | 1. 如果以上都无法解决,手动安装 156 | 157 | ## 监控 158 | 159 | 使用 `prometheus` + `node-exporter` + `cadvisor` 监控主机以及容器,使用 `grafana` 做可视化 160 | 161 | 当你需要监控主机,容器或者数据库时,可以采用以下步骤 162 | 163 | 1. 在 grafana 找一个 star 多的 Dashboard 164 | 2. 根据需要微改一下 165 | 166 | ![grafana](./assets/grafana.jpg) 167 | 168 | 自从用上了时序数据库,我的磁盘空间也日渐缩小 169 | 170 | ## 小结 171 | 172 | 简而言之,服务器还是以测试,学习和实践居多,后续应该会加入 k8s。 173 | -------------------------------------------------------------------------------- /deploy-drone.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "github 上持续集成方案 drone 的简介及部署" 3 | keywords: github,helm,k8s,drone,CI 4 | date: 2019-11-01 07:00 5 | tags: 6 | - devops 7 | 8 | --- 9 | 10 | # drone.ci 简介及部署 11 | 12 | 一般小型公司的持续集成方案会选择: `gitlab` + `gitlab CI`,当然部分公司也会选择 `jenkins`。 13 | 14 | 选择 `gitlab CI` 的原因很简单,因为使用了 `gitlab CE` 作为代码托管平台。那为什么选择了 `gitlab` 作为代码托管呢, `gitlab CE` 是免费版(社区版),对于昂贵的 toB 软件来说,一家公司至少省了几十万的开销,而且支持自建平台,搭在自家的服务器中,安全性得到了保证。 15 | 16 | 而对比 `gitlab` 的同一类产品,世界最大的同性社交网站 `github` 来说,随着微软的收购,`github` 也越来越开放了,它不仅免费开放了私有仓库,现在也可以通过 `github action` 来做简单的 CI。 17 | 18 | 对于个人,自有开发者以及小型公司来说,拥有免费仓库的 `github` 也是一个不错的选择。 19 | 20 | [drone](https://drone.io/) 是基于容器的构建服务,配置简单且免费,在 github 上也有 20K star。如果你的仓库主要都在 github,你会喜欢上它的 21 | 22 | > 随着 github action 的发展,github + github-action 也是个人以及小型公司可选的持续集成方案,不过由于它属于公共构建服务的缘故,镜像构建以及镜像拉取速度会是一个问题,这要取舍 23 | 24 | 本篇文章单单介绍 `drone.ci` 的部署 25 | 26 | + 原文地址: [drone.ci 简介以及部署](https://shanyue.tech/op/deploy-drone) 27 | + 系列文章: [个人服务器运维指南](https://shanyue.tech/op) 28 | 29 | ## 环境 30 | 31 | `kubernetes` 集群,并使用 `helm` 部署。如果不具备这两个条件可以参考我以前的文章 32 | 33 | + [k8s 集群搭建](https://github.com/shfshanyue/learn-k8s) 34 | + [k8s 中 helm 安装以及使用指南](https://github.com/shfshanyue/learn-k8s/blob/master/helm.md) 35 | 36 | ## 部署 37 | 38 | > 为了更好地真实环境效果,在命令演示过程中我会使用我真实的域名: `drone.xiange.tech`,你需要替换成你自己的域名 39 | 40 | 部署时采用 helm 的官方 chart: [stable/drone](https://github.com/helm/charts/tree/master/stable/drone) 41 | 42 | 当我们选择结合 `github` 做CI,此时需要两个参数 43 | 44 | 1. `github oauth2 client-secret` 45 | 1. `github oauth2 client-id` 46 | 47 | ``` bash 48 | # 根据 github oauth2 的 client-secret 创建一个 secret 49 | # generic: 指从文件或者字符串中创建 50 | # --form-literal: 根据键值对字符串创建 51 | $ kubectl create secret generic drone-server-secrets --from-literal=clientSecret="${github-oauth2-client-secret}" 52 | 53 | # 使用 helm v3 部署 stable/drone 54 | $ helm install drone stable/drone 55 | ``` 56 | 57 | 此时部署会提示部署失败,我们还需要一些必要的参数: `Ingress` 以及 `github oauth2` 58 | 59 | 我们使用 `Ingress` 配置域名 `drone.xiange.tech`,并开启 `https`,关于如何使用 `Ingress` 并自动开启 `https`,可以参考我以前的文章: 60 | 61 | 1. [通过外部域名访问你的应用: Ingress](https://shanyue.tech/k8s/ingress) 62 | 1. [自动为你的域名添加 https](https://shanyue.tech/k8s/https) 63 | 64 | 同时你也需要配置好默认 pv/pvc,可以参考我以前的文章 65 | 66 | 1. [k8s 中的永久存储: PersistentVolume]() 67 | 68 | 配置相关的参数,存储为 `drone-values.yaml`,其中 `drone.xiange.tech` 是在 github 上为 `drone` 设置的回调域名 69 | 70 | ``` yaml 71 | ingress: 72 | ## If true, Drone Ingress will be created. 73 | ## 74 | enabled: true 75 | 76 | ## Drone Ingress annotations 77 | ## 78 | annotations: 79 | kubernetes.io/ingress.class: nginx 80 | kubernetes.io/tls-acme: 'true' 81 | 82 | ## Drone hostnames must be provided if Ingress is enabled 83 | ## 84 | hosts: 85 | - drone.xiange.tech 86 | 87 | ## Drone Ingress TLS configuration secrets 88 | ## Must be manually created in the namespace 89 | ## 90 | tls: 91 | - secretName: drone-tls 92 | hosts: 93 | - drone.xiange.tech 94 | 95 | server: 96 | ## If not set, it will be autofilled with the cluster host. 97 | ## Host shoud be just the hostname. 98 | ## 99 | host: drone.xiange.tech 100 | 101 | sourceControl: 102 | ## your source control provider: github,gitlab,gitea,gogs,bitbucketCloud,bitbucketServer 103 | provider: github 104 | ## secret containing your source control provider secrets, keys provided below. 105 | ## if left blank will assume a secret based on the release name of the chart. 106 | secret: drone-server-secrets 107 | ## Fill in the correct values for your chosen source control provider 108 | ## Any key in this list with the suffix will be fetched from the 109 | ## secret named above, if not provided the secret it will be created as 110 | ## using for the key "ClientSecretKey" and 111 | # "clientSecretValue" for the value. Be awere to not leak shis file with your password 112 | github: 113 | clientSecretKey: clientSecret 114 | server: https://github.com 115 | ``` 116 | 117 | 准备好 `values` 之后,`helm upgrade` 更新 chart 再次部署 118 | 119 | ``` bash 120 | $ helm upgrade drone --reuse-values --values drone-values.yaml \ 121 | --set 'sourceControl.github.clientID={github-oauth2-client-id}' stable/drone 122 | Release "drone" has been upgraded. Happy Helming! 123 | NAME: drone 124 | LAST DEPLOYED: 2019-10-31 15:27:53.284739572 +0800 CST 125 | NAMESPACE: default 126 | STATUS: deployed 127 | NOTES: 128 | ********************************************************************************* 129 | *** PLEASE BE PATIENT: drone may take a few minutes to install *** 130 | ********************************************************************************* 131 | From outside the cluster, the server URL(s) are: 132 | http://drone.xiange.tech 133 | ``` 134 | 135 | 查看 `deployment` 状态,部署成功 136 | 137 | ``` bash 138 | $ kubectl get deploy drone-drone-server 139 | NAME READY UP-TO-DATE AVAILABLE AGE 140 | drone-drone-server 1/1 1 1 6h44 141 | ``` 142 | 143 | 打开浏览器,查看域名 `drone.xiange.tech`,经过 `github` 授权后可以看到 `drone.ci` 的管理页面 144 | 145 | ![drone部署成功](./assets/drone.jpg) 146 | -------------------------------------------------------------------------------- /openvpn.md: -------------------------------------------------------------------------------- 1 | # 使用 openvpn 与集群内部服务通信 2 | 3 | 当我们访问集群内部服务,如`postgres`,`redis`,`traefik Dashboard`,`gitlab` 时,如果直接暴露在公网中,会造成很大的安全隐患,而使用 `Basic Auth`,`WhiteList` 等也稍微有些繁琐 4 | 5 | 此时在开发环境使用 `VPN` 连接集群是一个不错的选择 6 | 7 | > Q: [在集群中如何保证私有服务的安全性](https://github.com/shfshanyue/Daily-Question/issues/125) 8 | 9 | 10 | 11 | + 原文链接: [使用 openvpn 与集群内部服务通信](https://github.com/shfshanyue/op-note/blob/master/openvpn.md) 12 | + 系列文章: [当我有台服务器时我做了什么](https://github.com/shfshanyue/op-note) 13 | 14 | ## 部署 openvpn 15 | 16 | 我们使用 `docker-compose` 部署 `openvpn`。 17 | 18 | 准备 `docker-compose.yaml` 如下,我们同样把它置于网络 `traefik-default` 下。`traefik-default` 是 `traefik` 用以服务发现的所有应用的入口网关,详情可参考上一节 [traefik 简易入门](https://github.com/shfshanyue/op-note/blob/master/traefik.md) 19 | 20 | > 关于配置文件,我维护在我的 github 仓库 [shfshanyue/op-note:compose](https://github.com/shfshanyue/op-note/tree/master/compose) 中 21 | 22 | ``` yaml 23 | version: '3' 24 | services: 25 | openvpn: 26 | cap_add: 27 | - NET_ADMIN 28 | image: kylemanna/openvpn 29 | container_name: openvpn 30 | ports: 31 | - "1194:1194/udp" 32 | restart: always 33 | volumes: 34 | - ./openvpn-data/conf:/etc/openvpn 35 | 36 | networks: 37 | default: 38 | external: 39 | name: traefik_default 40 | ``` 41 | 42 | 初始化配置文件,注意替换掉你真实的公网IP,并且需要在 `iptables` 或者阿里云安全组中 1194 端口接收端口转发 43 | 44 | ``` bash 45 | $ docker run -v $(pwd)/openvpn-data/conf:/etc/openvpn --log-driver=none --rm kylemanna/openvpn ovpn_genconfig -u udp:// 46 | ``` 47 | 48 | 初始化证书,此时需要交互式确认密码 49 | 50 | ``` bash 51 | $ docker run -v $(pwd)/openvpn-data/conf:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_initpki 52 | ``` 53 | 54 | 生成客户端证书 55 | 56 | ``` bash 57 | $ docker run -v $(pwd)/openvpn-data/conf:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn easyrsa build-client-full shanyue nopass 58 | ``` 59 | 60 | 生成客户端配置文件 `shanyue.ovpn`,成功生成后需要把该文件传到本地。 61 | 62 | ``` bash 63 | $ docker run -v $(pwd)/openvpn-data/conf:/etc/openvpn --log-driver=none --rm kylemanna/openvpn ovpn_getclient shanyue > shanyue.ovpn 64 | ``` 65 | 66 | ## 客户端连接 67 | 68 | 使用 `rsync` 把生成的配置文件传到本地环境 69 | 70 | ``` bash 71 | rsync -avhzP dev:/path/openvpn/shanyue.ovpn . 72 | ``` 73 | 74 | 下载并使用工具 [tunnelblick](https://tunnelblick.net/) 打开生成的配置文件,点击连接 vpn,连接成功 75 | 76 | ## 访问内部集群 77 | 78 | 在 [traefik 简易入门](https://github.com/shfshanyue/op-note/blob/master/traefik.md) 中,我们在集群中配置了 `whoami.docker.localhost` 用以访问 `whoami` 服务。 79 | 80 | 查看 `traefik` 的网络 IP 81 | 82 | ``` bash 83 | $ docker network inspect traefik-default 84 | 85 | "IPAM": { 86 | "Driver": "default", 87 | "Options": null, 88 | "Config": [ 89 | { 90 | "Subnet": "172.18.0.0/16", 91 | "Gateway": "172.18.0.1" 92 | } 93 | ] 94 | }, 95 | ``` 96 | 97 | 找到 IP 后,我们通过 `curl` 查看服务是否可以正常访问,服务正常 98 | 99 | ``` bash 100 | $ curl -H Host:whoami.docker.localhost http://172.18.0.1 101 | Hostname: eb290c85a325 102 | IP: 127.0.0.1 103 | IP: 172.18.0.2 104 | RemoteAddr: 172.18.0.3:34012 105 | GET / HTTP/1.1 106 | Host: whoami.docker.localhost 107 | User-Agent: curl/7.54.0 108 | Accept: */* 109 | Accept-Encoding: gzip 110 | X-Forwarded-For: 172.18.0.1 111 | X-Forwarded-Host: whoami.docker.localhost 112 | X-Forwarded-Port: 80 113 | X-Forwarded-Proto: http 114 | X-Forwarded-Server: 11e1f03c87ef 115 | X-Real-Ip: 172.18.0.1 116 | ``` 117 | 118 | ## 关于 Host 119 | 120 | 此时我们访问 `whoami.docker.localhost` 仍然是通过指定 Host 的方式 121 | 122 | ``` bash 123 | $ curl -H Host:whoami.docker.localhost http://172.18.0.1 124 | ``` 125 | 126 | 那我们为什么不能直接 `curl whoami.docker.localhost` 来访问服务呢? 127 | 128 | 因为 DNS 无法解析到正确的地址 129 | 130 | 那我们使用 IP 地址又不是很方便,这时应该怎么办? 131 | 132 | [使用 dnsmasq 为内部集群搭建本地 DNS 服务器](https://github.com/shfshanyue/op-note/blob/master/dnsmasq.md) 133 | 134 | ## 本地 DNS 设置 135 | 136 | [使用 dnsmasq 为内部集群搭建本地 DNS 服务器](https://github.com/shfshanyue/op-note/blob/master/dnsmasq.md),搭建好本地 DNS 服务器后,此时我们需要做的是在**连接上openVPN时自动修改/etc/resolv.conf** 137 | 138 | 这里有关于 `openvpn` 配置文件的官方文档: [Openvpn How To](https://openvpn.net/community-resources/how-to/#pushing-dhcp-options-to-clients) 139 | 140 | 修改服务器中的配置文件 `openvpn-data/conf/openvpn.conf`,添加一行,如下所示。它会修改客户端的 DNS nameserver 地址为 `172.18.0.1` 141 | 142 | ``` conf 143 | # 在所有客户端设置 DNS 服务器为 172.18.0.1 144 | push "dhcp-option DNS 172.18.0.1" 145 | ``` 146 | 147 | 重启服务 148 | 149 | ``` bash 150 | docker-compose restart 151 | ``` 152 | 153 | ## 客户端 DNS 测试 154 | 155 | 当客户端连接 `openvpn` 时,会自动修改 `/etc/resolv.conf` 文件,查看文件,修改成功 156 | 157 | ``` conf 158 | search openvpn 159 | nameserver 172.18.0.1 160 | ``` 161 | 162 | 此时可以直接 `curl` 该地址,连接成功 163 | 164 | ``` bash 165 | $ curl whoami.docker.localhost 166 | Hostname: fe78f4e43924 167 | IP: 127.0.0.1 168 | IP: 172.18.0.2 169 | RemoteAddr: 172.18.0.3:51600 170 | GET / HTTP/1.1 171 | Host: whoami.docker.localhost 172 | User-Agent: curl/7.54.0 173 | Accept: */* 174 | Accept-Encoding: gzip 175 | X-Forwarded-For: 172.18.0.1 176 | X-Forwarded-Host: whoami.docker.localhost 177 | X-Forwarded-Port: 80 178 | X-Forwarded-Proto: http 179 | X-Forwarded-Server: 9d783174aca9 180 | X-Real-Ip: 172.18.0.1 181 | ``` 182 | -------------------------------------------------------------------------------- /wechat-interview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 微信公众号开发模拟面试功能 3 | path: wechat-interview 4 | description: 最近在整理我在大厂面试以及平时工作中遇到的问题,记录在 [shfshanyue/Daily-Question](https://github.com/shfshanyue/Daily-Question) 中,但觉得对于时时回顾,常常复习仍然做的不够。 5 | keywords: 公众号开发,devops,traefik,serverless 6 | date: 2020-01-16 20:00 7 | tags: 8 | - node 9 | - devops 10 | 11 | --- 12 | 13 | # 使用微信公众号开发模拟面试功能 14 | 15 | 最近在整理我在大厂面试以及平时工作中遇到的问题,记录在 [shfshanyue/Daily-Question](https://github.com/shfshanyue/Daily-Question) 中,但觉得对于时时回顾,常常复习仍然做的不够。 16 | 17 | 于是在微信公众号中开发了随机生成模拟面试的功能,由于觉得比较简单且有趣,于是分享了出来 18 | 19 | ## 需求 20 | 21 | 先来谈一谈需求点: 22 | 23 | 1. 在公众号中回复面试,随机生成 N 道大厂面试题 24 | 1. 每道面试题指向一个超链接,可以查看答案 25 | 26 | 需求很简单,如图下所示。你也可以去我的公众号 `全栈成长之路` 查看实现效果 27 | 28 | ![](https://camo.githubusercontent.com/c6aefa9d0e275fa39f3447f62267c191c25a1da2/68747470733a2f2f6330312e676169747562616f2e6e65742f676169747562616f5f466c4f4d423637704b58552d444e6b6d4862455449714c6b655363332e706e673f696d6167654d6f6772322f7175616c6974792f3930) 29 | 30 | 31 | 32 | ## 内容 33 | 34 | 在大部分行业中,内容是至为重要的,有内容才会有好的服务,而技术只是整合内容的一种手段。 35 | 36 | 在本次功能开发中也是如此:**一个面试题库才是至关重要**。 37 | 38 | 为此,我在 github 上新建了一个仓库,使用 `Issue` 来记录我在大厂面试中所遇到的面试题及答案 39 | 40 | > [每天一道面试题,有关前端,后端,devops以及软技能,促进职业成长,敲开大厂之门。](https://github.com/shfshanyue/Daily-Question) 41 | 42 | **到此一步,我拥有了自己的内容,并且拥有了开箱即用的后台管理系统: github issues** 43 | 44 | ## 数据 45 | 46 | 此时我们已经拥有了一个特殊的后台管理系统,但很遗憾,由于该管理系统的特殊性,我们并不是数据映射管理系统,而需要根据 Github Issues 来生成结构化的数据,好在我们可以使用 Github API。 47 | 48 | Github API 现在已经全部变成了 `GraphQL` 接口,看来大家又需要学习一门新的技术了。关于 Github API 的文档可以在这里找到: [Github API Explorer](https://developer.github.com/v4/explorer/) 49 | 50 | 以下 Query 就是我们所需要的数据 51 | 52 | ``` graphql 53 | query ISSUES ($after: String) { 54 | repository (name: "Daily-Question", owner: "shfshanyue") { 55 | id 56 | issues (first: 100, after: $after, states: OPEN) { 57 | pageInfo { 58 | hasNextPage 59 | endCursor 60 | } 61 | nodes { 62 | id 63 | number 64 | title 65 | body 66 | comments (first: 10) { 67 | nodes { 68 | id 69 | body 70 | star: reactions (content: THUMBS_UP) { 71 | totalCount 72 | } 73 | author { 74 | login 75 | url 76 | } 77 | } 78 | } 79 | labels (first: 5) { 80 | nodes { 81 | id 82 | name 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | ## 微信开发 92 | 93 | 在微信开发中,定义一条路由,用来处理对关键字 `面试` 的回复 94 | 95 | ``` js 96 | const routes = [{ 97 | default: true, 98 | handle: handleDefault 99 | }, { 100 | text: /面试/, 101 | handle: handleInterview, 102 | }] 103 | ``` 104 | 105 | 根据封装好的 Issue SDK 随机选取八个问题,更多代码可以前往 [shfshanyue/wechat](https://github.com/shfshanyue/wechat) 中 106 | 107 | ``` js 108 | function handleInterview () { 109 | return issue.randomIssues(8).map((issue, i) => 110 | `${i+1}. ${issue.title.slice(6)}` 111 | ).join('\n\n') 112 | } 113 | ``` 114 | 115 | 自此微信开发结束,开始部署项目 116 | 117 | ## 部署 118 | 119 | 开发完成之后使用 `docker` 及 `docker-compose` 部署,`traefik` 做服务发现及负载均衡。 120 | 121 | 如果你对它们不够了解,可以查看我的系列文章 [个人服务器运维指南](https://github.com/shfshanyue/op-note) 的案例篇,关于 `docker`,`compose` 及 `traefik` 等基础设施的搭建均在本系列中有所介绍。 122 | 123 | 在生产环境中,通过 `https://we.shanyue.tech` 暴露服务。 124 | 125 | 在测试环境中,需要监听文件重启。在测试环境通过挂载目录的方式在 `https://we.dev.shanyue.tech` 暴露服务。 126 | 127 | `Dockerfile` 较为简单,配置文件如下 128 | 129 | ``` dockerfile 130 | FROM node:10-alpine 131 | 132 | WORKDIR /code 133 | 134 | ADD package.json /code 135 | RUN npm install --production 136 | 137 | ADD . /code 138 | 139 | CMD npm start 140 | ``` 141 | 142 | `docker-compose.yaml` 配置文件如下 143 | 144 | ``` yaml 145 | version: '3' 146 | 147 | services: 148 | wechat: 149 | build: . 150 | restart: always 151 | labels: 152 | - traefik.http.routers.wechat.rule=Host(`we.shanyue.tech`) 153 | - traefik.http.routers.wechat.tls=true 154 | - traefik.http.routers.wechat.tls.certresolver=le 155 | expose: 156 | - 3000 157 | 158 | networks: 159 | default: 160 | external: 161 | name: traefik_default 162 | ``` 163 | 164 | ## 测试环境与生产环境 165 | 166 | 当我们需要测试微信公众号时,直接使用自己的公众号不太合适,特别是当已有上线内容时。微信官方提供了测试公众号,我们可以重新填写 `域名` 以及 `token`。在测试环境使用域名 `https://we.dev.shanyue.tech` 167 | 168 | 我们在 `docker-compose` 中使用 `service` 中的 `wechat` 代表生产环境,`wechat-dev` 代表测试环境 169 | 170 | `wechat-dev` 通过文件挂载提供服务,可以更新重启应用,便可以做到实时更新代码,并实时在测试公众号中看到效果。 171 | 172 | `docker-compose.yaml` 配置文件如下 173 | 174 | ``` yaml 175 | version: '3' 176 | 177 | services: 178 | wechat: 179 | build: . 180 | restart: always 181 | labels: 182 | - traefik.http.routers.wechat.rule=Host(`we.shanyue.tech`) 183 | - traefik.http.routers.wechat.tls=true 184 | - traefik.http.routers.wechat.tls.certresolver=le 185 | expose: 186 | - 3000 187 | 188 | wechat-dev: 189 | image: 'node:10-alpine' 190 | restart: always 191 | volumes: 192 | - .:/code 193 | working_dir: /code 194 | command: npm run dev 195 | labels: 196 | - traefik.http.routers.wechat-dev.rule=Host(`we.dev.shanyue.tech`) 197 | - traefik.http.routers.wechat-dev.tls=true 198 | - traefik.http.routers.wechat-dev.tls.certresolver=le 199 | expose: 200 | - 3000 201 | 202 | networks: 203 | default: 204 | external: 205 | name: traefik_default 206 | ``` 207 | 208 | 关于后端代码,托管在 [shfshanyue/wechat](https://github.com/shfshanyue/wechat) 中 209 | 210 | -------------------------------------------------------------------------------- /deploy-fe-with-docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何使用 docker 高效部署前端应用 3 | description: docker 变得越来越流行,它可以轻便灵活地隔离环境,进行扩容,方便运维管理。对开发者也更方便开发,测试与部署。这里介绍如何使用 Docker 部署前端应用。千里之行,始于足下,足下的意思就是,先让它能够跑起来。 4 | keywords: docker,前端,使用docker部署前端,优化dockerfile 5 | date: 2019-03-09 6 | hot: 11 7 | tags: docker,devops,前端部署 8 | categories: 9 | - 前端 10 | - 运维 11 | --- 12 | 13 | # 使用 docker 高效部署前端应用 14 | 15 | docker 变得越来越流行,它可以轻便灵活地隔离环境,进行扩容,方便运维管理。对开发者也更方便开发,测试与部署。 16 | 17 | 最重要的是,**当你面对一个陌生的项目,你可以照着 Dockerfile,甚至不看文档(文档也不一定全,全也不一定对)就可以很快让它在本地跑起来。** 18 | 19 | 20 | 21 | 现在很强调 `devops` 的理念,我把 devops 六个大字贴在电脑屏幕上,格物致知了一天。豁然开朗:`devops` 的意思就是写一个 Dockerfile 去跑应用 22 | 23 | 这里将介绍如何使用 Docker 部署前端应用。 24 | 25 | 千里之行,始于足下。始于足下的意思就是,先让它能够跑起来。 26 | 27 | + 原文地址: [如何使用 docker 高效部署前端](https://shanyue.tech/op/deploy-fe-with-docker/) 28 | + 系列文章: [当我有一台云服务器时我做了什么](https://shanyue.tech/op) 29 | 30 | ## 先让它跑起来 31 | 32 | 首先,简单介绍一下一个典型的前端应用部署流程 33 | 34 | 1. `npm install`, 安装依赖 35 | 1. `npm run build`,编译,打包,生成静态资源 36 | 1. 服务化静态资源 37 | 38 | 介绍完部署流程后,简单写一个 Dockerfile 39 | 40 | ``` docker 41 | FROM node:10-alpine 42 | 43 | # 代表生产环境 44 | ENV PROJECT_ENV production 45 | # 许多 package 会根据此环境变量,做出不同的行为 46 | # 另外,在 webpack 中打包也会根据此环境变量做出优化,但是 create-react-app 在打包时会写死该环境变量 47 | ENV NODE_ENV production 48 | WORKDIR /code 49 | ADD . /code 50 | RUN npm install && npm run build && npm install -g http-server 51 | EXPOSE 80 52 | 53 | CMD http-server ./public -p 80 54 | ``` 55 | 56 | 现在这个前端服务已经跑起来了,接下来你可以完成部署的其它阶段了。 57 | 58 | 一般情况下,以下就成了运维的工作了,不过,拓展自己的知识边界总是没错的。其它阶段介绍如下 59 | 60 | + 使用 `nginx` 或者 `traefik` 做反向代理。在我内部集群中使用了 `traefik`,详见 [traefik 简易入门](https://github.com/shfshanyue/op-note/blob/master/traefik.md) 61 | + 使用 `kubernetes` 或者 `docker compose` 做容器编排。在我内部集群中使用了 `compose`,详见 [docker compose 简易入门](https://github.com/shfshanyue/op-note/blob/master/traefik-compose.md) 62 | + 使用 `gitlab ci`,`drone ci` 或者 `github actions` 等做 CI/CD。在我内部集群中使用了 `github actions`,详见 [github actions 简易入门](https://github.com/shfshanyue/op-note/blob/master/github-action-guide.md) 63 | 64 | 这时镜像存在两个问题,导致每次部署时间过长,不利于产品的快速交付,没有快速交付,也就没有敏捷开发 (Agile) 65 | 66 | + 构建镜像时间过长 67 | + 构建镜像大小过大,多时甚至 1G+ 68 | 69 | ## 从 dependencies 和 devDependencies 下手 70 | 71 | > 一个前端程序员若是每天工作八个小时,至少有两个小时是白白浪费了的:一个小时用来 `npm install`,另一个小时用来 `npm run build`。 72 | 73 | 对于每次部署,如果能够减少无用包的下载,便能够节省很多镜像构建时间。`eslint`,`mocha`,`chai` 等代码风格测试模块可以放到 `devDependencies` 中。在生产环境中使用 `npm install --production` 装包。 74 | 75 | ``` docker 76 | FROM node:10-alpine 77 | 78 | ENV PROJECT_ENV production 79 | ENV NODE_ENV production 80 | WORKDIR /code 81 | ADD . /code 82 | RUN npm install --production && npm run build && npm install -g http-server 83 | EXPOSE 80 84 | 85 | CMD http-server ./public -p 80 86 | ``` 87 | 88 | 好像是快了那么一点点。 89 | 90 | 我们注意到,相对于项目的源文件来讲,`package.json` 是相对稳定的。如果没有新的安装包需要下载,则再次构建镜像时,无需重新装包。则可以在 npm install 上节省一半的时间。 91 | 92 | ## 利用镜像缓存 93 | 94 | 对于 `ADD` 来讲,如果需要添加的文件内容的 `checksum` 没有发生变化,则可以利用缓存。把 `package.json` 与源文件分隔开写入镜像是一个很好的选择。目前,如果没有新的安装包更新的话,可以节省一半时间 95 | 96 | ``` docker 97 | FROM node:10-alpine 98 | 99 | ENV PROJECT_ENV production 100 | ENV NODE_ENV production 101 | 102 | # http-server 不变动也可以利用缓存 103 | RUN npm install -g http-server 104 | 105 | WORKDIR /code 106 | 107 | ADD package.json /code 108 | RUN npm install --production 109 | 110 | ADD . /code 111 | RUN npm run build 112 | EXPOSE 80 113 | 114 | CMD http-server ./public -p 80 115 | ``` 116 | 117 | 关于利用缓存有更多细节,需要特别注意一下。如 `RUN git clone `,如果命令字符串没有更新,则将使用缓存,当命令是非幂等性时,这将有可能导致问题 118 | 119 | > 参考官方文档 [Dockerfile 最佳实践](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache) 120 | 121 | ## 多阶段构建 122 | 123 | 得益于缓存,现在镜像构建时间已经快了不少。但是,此时镜像的体积依旧过于庞大,这也将会导致部署时间的加长。原因如下 124 | 125 | 考虑下每次 CI/CD 部署的流程 126 | 127 | 1. 在构建服务器构建镜像 128 | 1. 把镜像推至镜像仓库服务器 129 | 1. 在生产服务器拉取镜像,启动容器 130 | 131 | 显而易见,镜像体积过大会造成传输效率低下,增加每次部署的延时 132 | 133 | 即使,构建服务器与生产服务器在同一节点下,没有延时的问题。减少镜像体积也能够节省磁盘空间 134 | 135 | 关于镜像体积的过大,很大一部分是因为node_modules 臭名昭著的体积 136 | 137 | ![node_modules的体积](./assets/node_modules.jpg) 138 | 139 | 但最后我们只需要 public 文件夹下的内容,对于源文件以及 `node_modules` 下文件,占用体积过大且不必要,造成浪费。 140 | 141 | 此时可以利用 Docker 的多阶段构建,仅来提取编译后文件 142 | 143 | > 参考官方文档 [多阶段构建](https://docs.docker.com/develop/develop-images/multistage-build/) 144 | 145 | ``` docker 146 | FROM node:10-alpine as builder 147 | 148 | ENV PROJECT_ENV production 149 | ENV NODE_ENV production 150 | 151 | # http-server 不变动也可以利用缓存 152 | WORKDIR /code 153 | 154 | ADD package.json /code 155 | RUN npm install --production 156 | 157 | ADD . /code 158 | RUN npm run build 159 | 160 | # 选择更小体积的基础镜像 161 | FROM nginx:10-alpine 162 | COPY --from=builder /code/public /usr/share/nginx/html 163 | ``` 164 | 165 | 此时,镜像体积从 1G+ 变成了 50M+ 166 | 167 | ## 使用文件存储服务 168 | 169 | 分析一下 50M+ 的镜像体积,`nginx:10-alpine` 的镜像是16M,剩下的40M是静态资源。 170 | 171 | **如果把静态资源给上传到文件存储服务,即OSS,并使用 CDN 对 OSS 进行加速。则没有必要打入镜像了**,此时镜像大小会控制在 20M 以下 172 | 173 | 关于静态资源,可以分类成两部分 174 | 175 | + `/static`,此类文件在项目中直接引用根路径,打包时复制进 /public 下,需要被打入镜像 176 | + `/build`,此类文件需要 require/import 引用,会被 webpack 打包并加 hash 值,并通过 publicPath 修改资源地址。可以把此类文件上传至 oss,并加上永久缓存,不需要打入镜像 177 | 178 | ``` docker 179 | FROM node:10-alpine as builder 180 | 181 | ENV PROJECT_ENV production 182 | ENV NODE_ENV production 183 | 184 | # http-server 不变动也可以利用缓存 185 | WORKDIR /code 186 | 187 | ADD package.json /code 188 | RUN npm install --production 189 | 190 | ADD . /code 191 | 192 | # npm run uploadOss 是把静态资源上传至 oss 上的脚本文件 193 | RUN npm run build && npm run uploadOss 194 | 195 | # 选择更小体积的基础镜像 196 | FROM nginx:10-alpine 197 | COPY --from=builder code/public/index.html code/public/favicon.ico /usr/share/nginx/html/ 198 | COPY --from=builder code/public/static /usr/share/nginx/html/static 199 | ``` 200 | -------------------------------------------------------------------------------- /ansible-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ansible 自动化运维指南 3 | keywords: linux,ansible,自动化运维,ansible安装 4 | desription: 使用 ansible 可以进行批量配置,批量安装软件,省了一大部分繁琐的重复工作,提高了管理服务器的效率。本章介绍如何使用 ansible 的安装以及关于 ansible 的基本功能。建议拥有云服务器的同学都可以学习一下 ansible 5 | recommend: 6 | - 对ansible各个组件进行简单介绍,通俗易懂 7 | - 一个小的实例讲解如何使用ansible做自动化运维 8 | date: 2019-10-23 22:00 9 | tags: 10 | - devops 11 | - linux 12 | 13 | --- 14 | 15 | # ansible 简易入门 16 | 17 | 使用 ansible 可以进行批量配置服务器,批量安装软件,省了一大部分繁琐的重复工作,提高了管理服务器的效率。 18 | 19 | 简单点说,使用 ansible 可以一键配置好你所有服务器的一切配置及软件安装,如 `vim`,`git`, `tmux`,`python`,`c++`,`nginx`,`docker` 等。如果你对它情有所钟,你还可以使用它自动部署应用至多台服务器。(目前建议通过 k8s 及 CI 做自动部署) 20 | 21 | 本章介绍如何使用 `ansible` 的安装以及关于 `ansible` 的基本功能,建议拥有云服务器的同学都可以尝试一下 `ansible`,你会发现宝藏的。 22 | 23 | 24 | 25 | + 原文链接: [使用 ansible 做自动化运维](https://github.com/shfshanyue/op-note/blob/master/ansible-guide.md) 26 | + 系列文章: [服务器运维笔记](https://github.com/shfshanyue/op-note) 27 | 28 | ## 自动化运维的必要性 29 | 30 | 我现在有两个云服务器用来瞎折腾,装的都是 centos 系统。而我在两个服务器上都会装上 `tmux`,用作多窗口管理工具。 31 | 32 | 但在有了服务器的早期有可能各种乱折腾,又需要多次重装系统,而每次重装系统,又需要重装一遍 `tmux`。 33 | 34 | 这就会造成一件重复度极高的事情: 安装 `tmux`。 35 | 36 | 如果在 centos 中安装 `tmux` 能够直接使用 `yum install tmux` 也就罢了,但是安装 tmux 也是一件极为琐碎的事情。 37 | 38 | 根据我在本系列文章 [窗口复用与 tmux](https://shanyue.tech/op/tmux-setting) 中提到一个 `tmux` 的安装步骤 39 | 40 | 1. 安装依赖 package 41 | 1. 在 github 下载源代码,编译安装 42 | 1. 在 github 下载配置文件 43 | 44 | **而且,在多个服务器和多次重装过程中,有可能重复以上安装步骤 N 次。** 45 | 46 | 于是自动化运维存在的意义就体现了出来,它可以直接使用一条命令便完成所有服务器的安装过程 47 | 48 | ## ansible 安装及配置 49 | 50 | ansible 是使用 python 写的一个做自动化运维的工具。在使用 ansible 之前需要明白以下两个概念 51 | 52 | + 本地环境: 即你的 PC,mac 或者是跳板机,在本地环境需要安装 ansible 53 | + 远程服务器: 在远程服务器会部署自己的服务,跑应用,也是需要被管理的服务器。在远程服务器中不需要装任何应用 54 | 55 | ansible 工作在 ssh 协议上,它只需要满足两个条件 56 | 57 | ### 1. 在本地环境安装 ansible 58 | 59 | 在 mac 上,直接通过 `brew install ansible` 就可以完成安装。 60 | 61 | 如果不是 mac,可以参考 [官方安装指南](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#intro-installation-guide) 62 | 63 | 不过本地环境大多都是 `mac` 或者 `windows` 64 | 65 | ### 2. 在本地能够 ssh 到远程服务器 66 | 67 | 通过配置 `~/.ssh/config` 与 `ssh key` 可以达到直连免密的效果,具体参考本系列的第一篇文章 [云服务器初始登录配置](https://shanyue.tech/op/init) 68 | 69 | `~/.ssh/config` 文件如下 70 | 71 | ```config 72 | Host shanyue 73 | HostName 172.17.68.39 74 | User root 75 | Host shuifeng 76 | HostName 172.17.68.40 77 | User root 78 | ``` 79 | 80 | ## ansible inventory 81 | 82 | 通过配置 `~/.ssh/config` 后,我们为远程服务器起了别名。此时可以通过 `inventory` 进行分组管理。 83 | 84 | `ansible` 默认的 `inventory` 配置文件为 `/etc/ansible/hosts`。 85 | 86 | ```ini 87 | [prod] 88 | shanyue 89 | shuifeng 90 | 91 | [dev] 92 | proxy 93 | jumper ansible_port=5555 ansible_host=192.0.2.50 94 | ``` 95 | 96 | 配置释义如下 97 | 98 | 1. 总共有四台服务器,shanyue,shuifeng,proxy,jumper,所有的服务器都在分组 `all` 下 99 | 1. shanyue 与 shuifeng 在分组 `prod` 下,而 proxy 与 jumper 在分组 `dev` 下 100 | 1. 在 `inventory` 中同样可以设置 `hostname`, `port` 与别名,但是建议在 ssh-config 中进行设置 101 | 102 | ## 一个简单的 ad-hoc 命令 103 | 104 | `ad-hoc` 命令指去特定一组服务器上执行一个命令。而一个命令实际上指的是 `module`,而最常用的 `module` 是 `ping`,用以查看服务器是否正常连通 105 | 106 | 所有的module可以参考 [ansible modules](https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html) 107 | 108 | ```shell 109 | # 查看所有服务器是否能够正常连通 110 | $ ansible all -m ping 111 | shuifeng | SUCCESS => { 112 | "changed": false, 113 | "ping": "pong" 114 | } 115 | shanyue | SUCCESS => { 116 | "changed": false, 117 | "ping": "pong" 118 | } 119 | ``` 120 | 121 | ## Ansible playbook 122 | 123 | `ansible ad-hoc` 执行的命令过于简单,一般用于服务器的测试工作以及一些简单的小操作。而一些复杂的事情,如上述所说的 `tmux` 的安装则需要一系列脚本来完成。 124 | 125 | `ad-hoc` 是指定服务器执行指定命令, **而 `playbook` 是指定服务器执行一系列命令。** 126 | 127 | + hosts,用以指定服务器分组。如 prod 128 | + role, 用以指定一系列命令的集合。如 tmux,方便复用 129 | 130 | ```yaml 131 | - hosts: prod 132 | roles: 133 | - tmux 134 | ``` 135 | 136 | + [Ansible playbook 最佳实践](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html) 137 | 138 | ### Role 139 | 140 | role 指定了一系列命令,或者称做 `tasks`。每个 `task` 都可以看做一个 `ad-hoc`,由 [ansible module](https://docs.ansible.com/ansible/latest/modules/modules_by_category.html) 组成 141 | 142 | 但是在 `task` 执行的过程中,一定会有一些变量,配置文件的设置,这就是 role 的其它组成部分。如 `defaults`,`vars`,`files` 和 `templates`。`role` 的文件结构组织如下 143 | 144 | ```txt 145 | site.yml 146 | roles/ 147 | tmux/ 148 | tasks/ 149 | handlers/ 150 | files/ 151 | templates/ 152 | vars/ 153 | defaults/ 154 | meta/ 155 | ``` 156 | 157 | 比如一个 tmux 的 role 做了以下 `tasks` 158 | 159 | 1. 安装依赖 package 160 | 1. 在 github 下载源代码,编译安装 161 | 1. 在 github 下载配置文件 162 | 163 | 配置文件参考我的 ansible 配置: [shfshanyue/ansible-op](https://github.com/shfshanyue/ansible-op/blob/master/roles/tmux/tasks/main.yml) 164 | 165 | ```yaml 166 | - name: prepare 167 | yum: 168 | name: "{{item}}" 169 | with_items: 170 | - gcc 171 | - automake 172 | - libevent-devel 173 | - ncurses-devel 174 | - glibc-static 175 | 176 | - name: install tmux 177 | git: 178 | repo: https://github.com/tmux/tmux.git 179 | dest: ~/Documents/tmux 180 | version: 2.8 181 | 182 | - name: make tmux 183 | shell: sh autogen.sh && ./configure && make 184 | args: 185 | chdir: ~/Documents/tmux/ 186 | 187 | - name: copy tmux 188 | copy: 189 | src: ~/Documents/tmux/tmux 190 | dest: /usr/bin/tmux 191 | remote_src: yes 192 | mode: 0755 193 | 194 | - name: clone config file 195 | when: USE_ME 196 | git: 197 | repo: https://github.com/shfshanyue/tmux-config.git 198 | dest: ~/Documents/tmux-config 199 | 200 | - name: clone config file (from .tmux) 201 | git: 202 | repo: https://github.com/gpakosz/.tmux.git 203 | dest: ~/Documents/tmux-config 204 | when: not USE_ME 205 | 206 | - name: copy config file (from .tmux) 207 | copy: 208 | src: ~/Documents/tmux-config/.tmux.conf.local 209 | dest: ~/.tmux.conf.local 210 | remote_src: yes 211 | when: not USE_ME 212 | 213 | - name: copy config file 214 | copy: 215 | src: ~/Documents/tmux-config/.tmux.conf 216 | dest: ~/.tmux.conf 217 | remote_src: yes 218 | 219 | - name: delete tmux-config 220 | file: 221 | name: ~/Documents/tmux-config 222 | state: absent 223 | ``` 224 | 225 | ## Ansible galaxy 226 | 227 | 即 `Role` 的仓库。 228 | 229 | 有一些高频的可复用的服务组件的部署,如 `docker`,`redis` 之类,可以在 [ansible-galaxy](https://galaxy.ansible.com) 找到,而免了自己写 `role` 的麻烦。 230 | 231 | 如 [ansible-redis](https://github.com/DavidWittman/ansible-redis) 232 | 233 | ```shell 234 | # 查找关于 redis 的所有 Role 235 | $ ansible-galaxy search redis 236 | Found 387 roles matching your search: 237 | 238 | Name Description 239 | ---- ----------- 240 | 0x5a17ed.ansible_role_netbox Installs and configures NetBox, a DCIM suite, in a production setting. 241 | 1it.sudo Ansible role for managing sudoers 242 | 75629fce.ufw High-level, service-based interface for configuring UFW 243 | aalaesar.install_nextcloud Add a new Nextcloud instance in your infrastructure. The rol 244 | ... 245 | $ ansible-galaxy install davidwittman.redis 246 | ``` 247 | 248 | ### 列出本地安装的所有 Role 249 | 250 | ``` bash 251 | $ ansible-galaxy list 252 | ``` 253 | 254 | ## 小结 255 | 256 | `ansible` 以批量配置以及软件管理见长,如果你有一台自己的服务器的话,非常建议学习 `ansible`。 257 | -------------------------------------------------------------------------------- /jq-sed-case.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 jq 与 sed 制作掘金面试文章榜单 3 | keywords: jq,sed,面试,前端面试 4 | description: "jq 是一款命令行的 json 处理工具。类似于 lodash 一样,它可以对 json 做各种各样的处理,如 pick,get,filter,sort,map" 5 | date: 2019-11-17 08:00 6 | tags: 7 | - linux 8 | --- 9 | 10 | # 使用 jq 与 sed 制作掘金面试文章榜单 11 | 12 | 月: 听说你这次分享 `jq`\ 13 | 我: 恩,是了\ 14 | 月:这有啥好分享的,现在大家都用 react/vue 了\ 15 | 我: ......\ 16 | 我: 给你官网链接 \ 17 | 月: 原来是命令行下的 json 美化工具,好像也没啥可分享的\ 18 | 我: ......\ 19 | 我: 可以用它分析 `nginx` 的日志,比如用 `json` 表示 `nginx` 的日志\ 20 | 月: 线上的ELK它不好用吗?还是分布式的,又有索引,还快,就连查询语句都有自动补全\ 21 | 我: 好像有点道理...\ 22 | 我: 我还可以用它做爬虫!!! 23 | 24 | 谨以此篇文章讲述 `jq` 的实际使用案例,如果能提高你使用命令行的乐趣那更再好不过了。关于 `jq` 的用法可以参考我以前的文章: [jq命令详解及示例](https://github.com/shfshanyue/op-note/blob/master/jq.md) 25 | 26 | 27 | 28 | + 原文地址: [使用 jq 与 sed 制作掘金面试文章榜单](https://github.com/shfshanyue/op-note/blob/master/jq-sed-case.md) 29 | + 系列文章: [个人服务器运维指南](https://github.com/shfshanyue/op-note) 30 | 31 | ## 预览 32 | 33 | 使用以下命令,可以直接获取前端面试榜单 34 | 35 | ``` bash 36 | $ curl -s 'https://web-api.juejin.im/query' -H 'Content-Type: application/json' -H 'X-Agent: Juejin/Web' --data-binary '{"operationName":"","query":"","variables":{"tags":["55979fe6e4b08a686ce562fe"],"category":"5562b415e4b00c57d9b94ac8","first":100,"after":"","order":"HOTTEST"},"extensions":{"query":{"id":"653b587c5c7c8a00ddf67fc66f989d42"}}}' --compressed | \ 37 | jq -c '.data.articleFeed.items.edges | .[].node | { likeCount, title, originalUrl } | select(.likeCount > 600) ' | jq -cs '. | sort_by(-.likeCount) | .[] | "+ 【👍 \(.likeCount)】[\(.title)](\(.originalUrl))"' | sed s/\"//g 38 | 39 | + 【👍 5059】[一个合格(优秀)的前端都应该阅读这些文章](https://juejin.im/post/5d387f696fb9a07eeb13ea60) 40 | + 【👍 4695】[2018前端面试总结,看完弄懂,工资少说加3K | 掘金技术征文](https://juejin.im/post/5b94d8965188255c5a0cdc02) 41 | + 【👍 4425】[中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)](https://juejin.im/post/5c64d15d6fb9a049d37f9c20) 42 | + 【👍 3013】[2018春招前端面试: 闯关记(精排精校) | 掘金技术征文](https://juejin.im/post/5a998991f265da237f1dbdf9) 43 | + 【👍 2493】[前端面试考点多?看这些文章就够了(2019年6月更新版)](https://juejin.im/post/5aae076d6fb9a028cc6100a9) 44 | ``` 45 | 46 | ## 获取掘金列表接口 47 | 48 | 先来看看 http 的 url: `https://web-api.juejin.im/query`,用的 `POST` 49 | 50 | 再看看 body: 51 | 52 | ``` json 53 | { 54 | "operationName": "", 55 | "query": "", 56 | "variables": { 57 | "first": 20, 58 | "after": "1.0168277174789", 59 | "order": "POPULAR" 60 | }, 61 | "extensions": { 62 | "query": { 63 | "id": "21207e9ddb1de777adeaca7a2fb38030" 64 | } 65 | } 66 | } 67 | ``` 68 | 69 | 最后再看看 http 的 response: 70 | 71 | ![response](https://raw.githubusercontent.com/shfshanyue/blog/master/assets/response.jpg) 72 | 73 | 眼前一亮,多么熟悉的数据结构!!! 74 | 75 | 这不是 `graphql` 吗,我上个月(2019/10)还写了一系列文章介绍它: [使用 graphql 构建 web 应用](https://github.com/shfshanyue/graphql-guide),一年前(2018)还用 `graphql` 写了一套诗词的前后端: [shfshanyue/shici](https://github.com/shfshanyue/shici) 与 [shfshanyue/shici-server](https://github.com/shfshanyue/apollo-server-starter) 76 | 77 | 怎么看出来是 `graphql` 的呢? 78 | 79 | + `/query` 这是统一的入口 80 | + `extensions.query.id` 这是 `APQ`,为了缓存 `gql`,减少传输体积,减短网络时延,有利于缓存,当然也减少了安全性问题 81 | + `variables` 这是 `graphql variables` 82 | + `data.items[].edges` 这是 `graphql` 典型的分页 (虽然我不太喜欢,嵌套数据太多...) 83 | 84 | 恩,好像跑偏了 85 | 86 | 总之,拿到了数据--关于前端面试的数据 87 | 88 | ``` bash 89 | $ curl -s 'https://web-api.juejin.im/query' -H 'Content-Type: application/json' -H 'X-Agent: Juejin/Web' --data-binary '{"operationName":"","query":"","variables":{"tags":["55979fe6e4b08a686ce562fe"],"category":"5562b415e4b00c57d9b94ac8","first":100,"after":"","order":"HOTTEST"},"extensions":{"query":{"id":"653b587c5c7c8a00ddf67fc66f989d42"}}}' --compressed 90 | ``` 91 | 92 | ## ETL 93 | 94 | 还是 `etl` 这个词高大上啊 95 | 96 | 先用 `jq` 取几个数吧: 标题与点赞数。更多用法可以参考我以前的文章: [jq命令详解及示例](https://github.com/shfshanyue/op-note/blob/master/jq.md) 97 | 98 | 为了更容易看到 `jq` 的用法,把 `jq` 另起一行,其中 99 | 100 | + `-c`: 一整行显示 101 | + `.[]`: json-array to jsonl 102 | + `{}`: 类似于 `lodash.pick` 103 | 104 | **我们此时根据命令获取到所有高赞文章**,但它此时是无序的 105 | 106 | ``` bash 107 | $ curl -s 'https://web-api.juejin.im/query' -H 'Content-Type: application/json' -H 'X-Agent: Juejin/Web' --data-binary '{"operationName":"","query":"","variables":{"tags":["55979fe6e4b08a686ce562fe"],"category":"5562b415e4b00c57d9b94ac8","first":100,"after":"","order":"HOTTEST"},"extensions":{"query":{"id":"653b587c5c7c8a00ddf67fc66f989d42"}}}' --compressed | \ 108 | jq -c '.data.articleFeed.items.edges | .[].node | {title, likeCount}' 109 | 110 | {"title":"中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)","likeCount":4423} 111 | {"title":"2018前端面试总结,看完弄懂,工资少说加3K | 掘金技术征文","likeCount":4690} 112 | {"title":"一个合格(优秀)的前端都应该阅读这些文章","likeCount":5052} 113 | {"title":"前端面试考点多?看这些文章就够了(2019年6月更新版)","likeCount":2492} 114 | {"title":"2018春招前端面试: 闯关记(精排精校) | 掘金技术征文","likeCount":3013} 115 | ``` 116 | 117 | ![response](https://raw.githubusercontent.com/shfshanyue/blog/master/assets/jq-juejin.jpg) 118 | 119 | ### 数据筛选与排序 120 | 121 | 再来筛选下点赞数大于 600 的 122 | 123 | ``` bash 124 | select(.likeCount > 600) 125 | ``` 126 | 127 | 再来倒排个序 128 | 129 | ``` bash 130 | jq -s '. | sort_by(-.likeCount) | .[]' 131 | ``` 132 | 133 | **搞定,此时榜单上全是点赞数大于600且排好序的** 134 | 135 | ``` bash 136 | $ curl -s 'https://web-api.juejin.im/query' -H 'Content-Type: application/json' -H 'X-Agent: Juejin/Web' --data-binary '{"operationName":"","query":"","variables":{"tags":["55979fe6e4b08a686ce562fe"],"category":"5562b415e4b00c57d9b94ac8","first":100,"after":"","order":"HOTTEST"},"extensions":{"query":{"id":"653b587c5c7c8a00ddf67fc66f989d42"}}}' --compressed | \ 137 | jq -c '.data.articleFeed.items.edges | .[].node | {title, likeCount, originalUrl } | select(.likeCount > 600) ' | jq -s '. | sort_by(-.likeCount) | .[]' 138 | 139 | { 140 | "title": "一个合格(优秀)的前端都应该阅读这些文章", 141 | "likeCount": 5052, 142 | "originalUrl": "https://juejin.im/post/5d387f696fb9a07eeb13ea60" 143 | } 144 | { 145 | "title": "2018前端面试总结,看完弄懂,工资少说加3K | 掘金技术征文", 146 | "likeCount": 4690, 147 | "originalUrl": "https://juejin.im/post/5b94d8965188255c5a0cdc02" 148 | } 149 | { 150 | "title": "中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)", 151 | "likeCount": 4423, 152 | "originalUrl": "https://juejin.im/post/5c64d15d6fb9a049d37f9c20" 153 | } 154 | ``` 155 | 156 | ![response](https://raw.githubusercontent.com/shfshanyue/blog/master/assets/jq-juejin-sort.jpg) 157 | 158 | ### 使用 sed 处理生成 markdown 159 | 160 | 我们此时已经生成了结构化的数据,如果我们使用 `react` 渲染数据的话,`json` 自然不错。但我们现在需要生成 `markdown`,所以再处理处理 161 | 162 | 先使用 `jq` 生成链接样式 163 | 164 | ``` bash 165 | "+ 【👍 \(.likeCount)】[\(.title)](\(.originalUrl))" 166 | ``` 167 | 168 | 在使用 `sed` 删除全部双引号,关于 `sed`,可以参考我的文章: [sed 命令详解及示例](https://github.com/shfshanyue/op-note/blob/master/linux-sed.md) 169 | 170 | ``` bash 171 | sed s/\"//g 172 | ``` 173 | 174 | 此时,成功生成了 `markdown` 数据 175 | 176 | ``` bash 177 | $ curl -s 'https://web-api.juejin.im/query' -H 'Content-Type: application/json' -H 'X-Agent: Juejin/Web' --data-binary '{"operationName":"","query":"","variables":{"tags":["55979fe6e4b08a686ce562fe"],"category":"5562b415e4b00c57d9b94ac8","first":100,"after":"","order":"HOTTEST"},"extensions":{"query":{"id":"653b587c5c7c8a00ddf67fc66f989d42"}}}' --compressed | \ 178 | jq -c '.data.articleFeed.items.edges | .[].node | { likeCount, title, originalUrl } | select(.likeCount > 600) ' | jq -cs '. | sort_by(-.likeCount) | .[] | "+ 【👍 \(.likeCount)】[\(.title)](\(.originalUrl))"' | sed s/\"//g 179 | 180 | + 【👍 5059】[一个合格(优秀)的前端都应该阅读这些文章](https://juejin.im/post/5d387f696fb9a07eeb13ea60) 181 | + 【👍 4695】[2018前端面试总结,看完弄懂,工资少说加3K | 掘金技术征文](https://juejin.im/post/5b94d8965188255c5a0cdc02) 182 | + 【👍 4425】[中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)](https://juejin.im/post/5c64d15d6fb9a049d37f9c20) 183 | + 【👍 3013】[2018春招前端面试: 闯关记(精排精校) | 掘金技术征文](https://juejin.im/post/5a998991f265da237f1dbdf9) 184 | + 【👍 2493】[前端面试考点多?看这些文章就够了(2019年6月更新版)](https://juejin.im/post/5aae076d6fb9a028cc6100a9) 185 | + 【👍 2359】[「中高级前端面试」JavaScript手写代码无敌秘籍](https://juejin.im/post/5c9c3989e51d454e3a3902b6) 186 | ``` 187 | 188 | 189 | -------------------------------------------------------------------------------- /traefik.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: traefik 简易入门 3 | date: 2019-12-07T16:31:01+08:00 4 | thumbnail: https://docs.traefik.io/assets/img/traefik-architecture.png 5 | categories: 6 | - 后端 7 | - 运维 8 | tags: 9 | - devops 10 | --- 11 | 12 | # Traefik 简易入门 13 | 14 | `traefik` 与 `nginx` 一样,是一款优秀的反向代理工具,或者叫 `Edge Router`。至于使用它的原因则基于以下几点 15 | 16 | + 无须重启即可更新配置 17 | + 自动的服务发现与负载均衡 18 | + 与 `docker` 完美集成,基于 `container label` 的配置 19 | + 漂亮的 `dashboard` 界面 20 | + `metrics` 的支持,支持对 `prometheus` 和 `k8s` 集成 21 | 22 | 接下来讲一下 `traefik` 的安装,基本功能以及配置,以及如何基于 `Traefik` 搭建一套 `CaaS` 的架构。 23 | 24 | 25 | 26 | + 原文链接: [Traefik: 更好用更简单的反向代理](https://github.com/shfshanyue/op-note/blob/master/traefik.md) 27 | + 系列文章: [个人服务器运维指南](https://github.com/shfshanyue/op-note) 28 | 29 | ![traefik quickstart](https://docs.traefik.io/assets/img/quickstart-diagram.png) 30 | 31 | ## 快速开始 32 | 33 | > 本篇文章基于版本号 `traefik:v2.2` 进行讲解 34 | 35 | 我们使用 `traefik:v2.2` 作为镜像启动 `traefik` 服务。`docker-compose.yaml` 配置文件如下 36 | 37 | ``` yaml 38 | version: '3' 39 | 40 | services: 41 | traefik: 42 | image: traefik:v2.2 43 | command: --api.insecure=true --providers.docker 44 | ports: 45 | - "80:80" 46 | - "8080:8080" 47 | volumes: 48 | - /var/run/docker.sock:/var/run/docker.sock 49 | ``` 50 | 51 | 此时我们使用命令 `docker-compose up -d` 开启 `traefik` 服务,此时一个反向代理器已经部署成功。 52 | 53 | 接下来我们使用 `docker-compose` 借助 `whoami` 镜像启动一个简单的 `http` 服务,`docker-compose.yaml` 配置文件如下 54 | 55 | ``` yaml 56 | version: '3' 57 | 58 | services: 59 | # 改镜像会暴露出自身的 `header` 信息 60 | whoami: 61 | image: containous/whoami 62 | labels: 63 | # 设置Host 为 whoami.docker.localhost 进行域名访问 64 | - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" 65 | 66 | # 使用已存在的 traefik 的 network 67 | networks: 68 | default: 69 | external: 70 | name: traefik_default 71 | ``` 72 | 73 | 那 `whoami` 这个 `http` 服务做了什么事情呢 74 | 75 | 1. 暴露了一个 `http` 服务,主要提供一些 `header` 以及 `ip` 信息 76 | 1. 配置了容器的 `labels`,设置该服务的 `Host` 为 `whoami.docker.localhost`,给 `traefik` 提供标记 77 | 78 | 此时我们可以通过主机名 `whoami.docker.localhost` 来访问 `whoami` 服务,我们使用 `curl` 做测试 79 | 80 | ``` bash 81 | $ curl -H Host:whoami.docker.localhost http://127.0.0.1 82 | Hostname: bc3e8f1a5066 83 | IP: 127.0.0.1 84 | IP: 172.21.0.2 85 | RemoteAddr: 172.21.0.1:37852 86 | GET / HTTP/1.1 87 | Host: whoami.docker.localhost 88 | User-Agent: curl/7.29.0 89 | Accept: */* 90 | Accept-Encoding: gzip 91 | X-Forwarded-For: 127.0.0.1 92 | X-Forwarded-Host: whoami.docker.localhost 93 | X-Forwarded-Port: 80 94 | X-Forwarded-Proto: http 95 | X-Forwarded-Server: 8.8.8.8 96 | X-Real-Ip: 127.0.0.1 97 | ``` 98 | 99 | 服务正常访问。此时如果把 `Host` 配置为自己的域名,则已经可以使用自己的域名来提供服务 100 | 101 | ## 配置文件 102 | 103 | ![](https://docs.traefik.io/assets/img/static-dynamic-configuration.png) 104 | 105 | `traefik` 一般需要一个配置文件来管理路由,服务,证书等。我们可以通过 `docker` 启动 `traefik` 时来挂载配置文件,`docker-compose.yaml` 初始文件如下 106 | 107 | ``` yaml 108 | version: '3' 109 | 110 | services: 111 | traefik: 112 | image: traefik:v2.2 113 | ports: 114 | - "80:80" 115 | - "8080:8080" 116 | volumes: 117 | - ./traefik.toml:/etc/traefik/traefik.toml 118 | - /var/run/docker.sock:/var/run/docker.sock 119 | ``` 120 | 121 | 其中 `traefik.toml` 通过挂载文件的方式作为 `traefik` 的基本配置文件,基本配置文件可以通过 [traefik.sample.toml](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml) 获取 122 | 123 | 一个简单的配置文件及释义如下 124 | 125 | ### docker 126 | 127 | ``` toml 128 | [providers.docker] 129 | endpoint = "unix:///var/run/docker.sock" 130 | defaultRule = "Host(`{{ normalize .Name }}.shanyue.local`)" 131 | ``` 132 | 133 | 如果没有配置 `Rule`,将默认通过 `.shanyue.local` 的形式发现路由 134 | 135 | ### 日志 136 | 137 | 日志极为重要,当某个路由配置不成功,或者 https 配置失败时,可以通过日志文件找到蛛丝马迹。 138 | 139 | ``` toml 140 | # Writing Logs to a File, in JSON 141 | [log] 142 | filePath = "/path/to/log-file.log" 143 | format = "json" 144 | ``` 145 | 146 | ### 请求日志 147 | 148 | ``` toml 149 | [accessLog] 150 | 151 | filePath = "./traefik-access.json" 152 | 153 | format = "json" 154 | ``` 155 | 156 | 请求日志文件配置为 `json` 格式,结构化数据方便调试 157 | 158 | ### entryPoint 159 | 160 | ``` toml 161 | [entryPoints] 162 | [entryPoints.web] 163 | address = ":80" 164 | 165 | [entryPoints.web-secure] 166 | address = ":443" 167 | ``` 168 | 169 | 考虑到隐私以及安全,不对外公开的服务可以配置 `Basic Auth`,`Digest Auth` 或者 `WhiteList`,或者直接搭建 VPN,在内网内进行访问。至于 `Basic Auth` 等,可以参考 [traefik middlewares](https://docs.traefik.io/middlewares/overview/) 170 | 171 | ### prometheus metrics 172 | 173 | ``` toml 174 | [metrics.prometheus] 175 | buckets = [0.1,0.3,1.2,5.0] 176 | entryPoint = "metrics" 177 | ``` 178 | 179 | [Prometheus](https://prometheus.io/) 作为时序数据库,可以用来监控 traefik 的日志,支持更加灵活的查询,报警以及可视化。traefik 默认设置 prometheus 作为日志收集工具。另外可以使用 `grafana` 做为 `prometheus` 的可视化工具。 180 | 181 | ## Docker Provider,Router and Service 182 | 183 | ![traefik architecture](https://docs.traefik.io/assets/img/architecture-overview.png) 184 | 185 | + `Providers` 服务提供者,如 docker,如一个 http service 186 | + `Routers` 分析请求的 `Host`,`Header` 或者 `Path` 187 | + `Services` 选择合适的 `Provider` (负载均衡等) 188 | 189 | 我们使用 `Docker` 作为 `Provider`,而 `Router` 与 `Service` 可以通过 `container labels` 来进行配置,我们一般使用 `docker-compose.yaml` 中的 `labels` 来配置 190 | 191 | 我们可以通过 `traefik.http.routers..rule` 来配置路由规则,类似与 `nginx` 中的 `location` 192 | 193 | ``` yaml 194 | labels: 195 | - "traefik.http.routers.blog.rule=Host(`traefik.io`) || (Host(`containo.us`) && Path(`/traefik`))" 196 | ``` 197 | 198 | ### 负载均衡 199 | 200 | 如果要为 `docker provider` 进行负载均衡怎么办? 201 | 202 | 只需要使用 `docker-compose up --scale` 对容器横向扩容即可完成 203 | 204 | ``` bash 205 | $ docker-compose up --scale whoami=3 206 | WARNING: The scale command is deprecated. Use the up command with the --scale flag instead. 207 | Starting whoami_whoami_1 ... done 208 | Creating whoami_whoami_2 ... done 209 | Creating whoami_whoami_3 ... done 210 | ``` 211 | 212 | 在 `traefik dashboard` 中查看该 `service`时,已负载到三个容器 213 | 214 | ## Traefik Dashboard 215 | 216 | ![traefik dashboard](./assets/traefik-dashboard.png) 217 | 218 | `traefik` 默认有一个 `dashboard`,通过 `:8080` 端口暴露出去。我们可以在浏览器中直接通过 `:8080` 访问,但是 219 | 220 | 1. 使用 `IP` 地址肯定不是特别方便,此时我们可以配置 `Host` 221 | 1. 在公网环境下访问有安全性问题,此时可以配置 `basicAuth`,`digestAuth`,`IpWhiteList` 或者 `openVPN` 等中间件 222 | 223 | 再次更改 `traefik` 的 `docker-compose.yaml` 文件如下: 224 | 225 | ``` yaml 226 | version: '3' 227 | 228 | services: 229 | reverse-proxy: 230 | image: traefik:v2.2 231 | restart: always 232 | ports: 233 | - "80:80" 234 | - "8080:8080" 235 | volumes: 236 | - ./traefik.toml:/etc/traefik/traefik.toml 237 | - /var/run/docker.sock:/var/run/docker.sock 238 | container_name: traefik 239 | labels: 240 | - "traefik.http.routers.api.rule=Host(`traefik.shanyue.local`)" 241 | - "traefik.http.routers.api.service=api@internal" 242 | ``` 243 | 244 | 此时可以通过 `traefik.shanyue.local` 来访问 `dashboard` 245 | 246 | > Q: 我们如何配置 DNS 服务器来使得 `traefik.shanyue.local` 可供集群内部使用 247 | 248 | ## 总结 249 | 250 | 使用 `docker-compose.yaml` 部署 `traefik`,部署文件文件如下 251 | 252 | ``` yaml 253 | version: '3' 254 | 255 | services: 256 | reverse-proxy: 257 | image: traefik:v2.2 258 | restart: always 259 | ports: 260 | - "80:80" 261 | - "8080:8080" 262 | volumes: 263 | - ./traefik.toml:/etc/traefik/traefik.toml 264 | - /var/run/docker.sock:/var/run/docker.sock 265 | container_name: traefik 266 | labels: 267 | - "traefik.http.routers.api.rule=Host(`traefik.shanyue.local`)" 268 | - "traefik.http.routers.api.service=api@internal" 269 | ``` 270 | 271 | `traefik` 的配置文件可以通过 [traefik.sample.toml](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml) 获取 272 | 273 | 当使用 `docker` 部署完成 `traefik` 并且配置好配置文件后。如果想要使用 `docker-compose` 部署一个新的应用时只需要 274 | 275 | 1. 添加几行 `container labels` 276 | 1. 添加 `traefik` 容器所使用的网络 277 | 278 | ``` yaml 279 | version: '3' 280 | 281 | services: 282 | # 该镜像会暴露出自身的 `header` 信息 283 | whoami: 284 | image: containous/whoami 285 | restart: always 286 | labels: 287 | # 设置Host 为 whoami.docker.localhost 进行域名访问 288 | - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" 289 | 290 | # 使用已存在的 traefik 的 network 291 | networks: 292 | default: 293 | external: 294 | name: traefik_default 295 | ``` 296 | 297 | ## 下一步 298 | 299 | 当我们访问集群内部服务,如数据库,缓存,`traefik Dashboard`,`gitlab` 时,如果直接暴露在公网中,则会造成很大安全隐患,此时我们应该如何处理呢? 300 | -------------------------------------------------------------------------------- /vim-setting.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: vim 高频操作,常用配置与插件简介 3 | keywords: vim,linux,vim插件,vim前端,vim常用快捷键,vim高级技巧,vim配置 4 | description: 当你有一个服务器,或者运维若干服务器时,没有什么比不够熟练 vim 更让人难受和窝心的事情了。而在各种编辑器中 vim 模式也大受欢迎。因此,学习 vim 是很有必要的。 5 | date: 2019-10-12 23:00 6 | sidebarDepth: 3 7 | tags: 8 | - linux 9 | 10 | --- 11 | 12 | 如果你是一个运维,当你拥有一个服务器,或者运维若干服务器时,没有什么比不够熟练 vim 更让人难受和窝心的事情了。 13 | 14 | 而如果你是一个开发,而在各种编辑器中 vim 模式也大受欢迎。因此,学习 vim 是很有必要的。 15 | 16 | 本章旨在如何快速的上手 vim,主要体现在以下三个方面 17 | 18 | 1. 无插件零配置时如何灵活使用 (即各种编辑器的 vim mode) 19 | 1. 常用配置简介 20 | 1. 常用插件简介 21 | 22 | 掌握了vim 无插件零配置的操作,能大大提高程序员在各种编辑器中敲代码的效率。 23 | 24 | 了解了 vim 常用的配置,你将把 `vim` 用的很舒服,如沐春风。 25 | 26 | 而了解了常用插件,你将可以把它打造成一个适合自己的 IDE 工具。但往往来说,它对于服务器运维好处有限,对敲代码的程序员,往往也不如一个专用语言的 IDE 工具强大。不过,它有一个最大的好处,就是可以满足马斯洛需求层次理论中的自我需求: 装逼。这也是我会使用它的原因。 27 | 28 | 29 | 30 | + 原文地址: [使用 vim 及其配置](https://shanyue.tech/op/vim-setting.html) 31 | + 系列文章: [我的服务器运维笔记](https://shanyue.tech/op/) 32 | 33 | ## 高频操作 34 | 35 | 以下是我在使用 vim 过程中每天都会使用无数次的高频操作 36 | 37 | + `:w` 快速保存 38 | + `` 退出 insert 模式,与 `esc` 类似 39 | + `0` 快速移动到行首 40 | + `gg` 快速移动到文件首 41 | + `G` 快速移动至文件尾 42 | + `` 移动至最近一次位置 43 | + `zz` 把光标移动至本屏中间 44 | + `:12` 快速移动至特定行 45 | + `dd` 剪切本行 46 | + `yy` 复制本行 47 | + `yi{` 复制括号中内容 48 | + `=i{` 自动缩进 49 | + `` 自动补全 50 | + `"*yy` 复制到系统剪切板 51 | + `*` 快速查找关键字,类似于sublime/vscode 的 `Command + d` 52 | + `:noh` 取消关键字高亮 53 | + `o` 快速进入 insert 模式,并定位到下一行 54 | + `u` 撤销 55 | 56 | ## 无插件零配置操作 57 | 58 | ### 快速移动 59 | 60 | **快速移动是 vim 的重中之重,比一切插件都要重要**。也是下编辑和修改的基础。 61 | 62 | + **上(k)下(j)左(h)右(l)** 移动,需要注意,禁止使用上下左右箭头 63 | 64 | 如果需要移动数行,可以在操作前加数字。如 `10j` 代表往下移动十行。 **通过数字与操作结合,这是 vim 的思想。** 65 | 66 | + 减少上一步的左右移动,效率太低,使用 `b, B, w, W` 代替 67 | 68 | `b` 指 back a word,退回一个单词。`w` 指 forward a word,前进一个单词。 69 | `B` 指 back a WORD,退回一个大单词。`w` 指 forward a WORD,前进一个大单词。 70 | 71 | > 其中,word 以及 WORD 的区别,以一个示例说明。 hello.world 有三个 word ('hello', '.', 'world'),却只有一个 WORD。 72 | 73 | + 使用 `f, F, t, T` 进行更为精细的左右移动控制 74 | 75 | `f` 指 find a character,快速移动到下一个字符的位置,`F` 指向前查找。结合 `b, w` 实现快速左右移动。 76 | `t` 指 tail a character,快速移动到下一个字符位置的前一个字符,`T` 指向前查找。 77 | 78 | + 使用 `0, $` 进行行首行尾移动 79 | 80 | + 使用 `%` 快速移动到配对字符 81 | 82 | 如从左括号快速移动到右括号,左引号快速移动到右引号,在编码中最为常用! 83 | 84 | + 使用 `` 进行大范围上下移动 85 | 86 | `` 往下移动半页,`` 往上移动半页。 87 | 88 | > 也可以使用 `, ` 进行整页移动。 89 | 90 | + 使用 `gg, G` 进行首行尾行移动 91 | 92 | + `:128` 表示快速定位到 128 行,目前只在 debug 中使用 93 | 94 | + `zz` 快速定位当前光标至当前屏幕中间,`zb` 定位当前光标至屏尾,`zt` 定位当前光标至屏首 95 | 96 | + `*` 定位当前光标下的单词,并指向下一个,`#` 指向上一个 97 | 98 | + `gd` 在编码中常用,定位当前变量的申明位置,`gf` 定位到当前路径所指向的文件。 99 | 100 | + 最后如果你发现定位错了,可以使用 `-o` 回到光标的上一位置 101 | 102 | ### 编辑 103 | 104 | vim 的编辑在 `Insert Mode`,以上的快速移动是在 `Normal Mode`。编辑文本需要首先进入 `Insert Mode`。 105 | 106 | `i, I, a, A, o, O` 进入 `Insert Mode`。 107 | 108 | `i` 指 insert text,在该光标前进行编辑,`I` 指在行首进行编辑。 109 | `a` 指 append text,在该光标后进行编辑,`A` 指在行尾进行编辑。 110 | `o` 指 append text,在该光标后一行进行编辑,`O` 指在光标前一行进行编辑。 111 | 112 | 个人习惯,`i, A, o, O` 用的多一些,`I, a` 基本不用。 113 | 114 | `Esc` 以及 `` 都可以退出 `Insert Mode`。 115 | 116 | 个人习惯使用 ``,一来 `Esc` 过远,二来在一些编辑器中 `Esc` 容易与其它热键冲突。 117 | 118 | ### 修改 119 | 120 | 删除也可以在 `Insert Mode` 使用 `delete` 键进行手动删除,不过效率太低,建议一般在 `Normal Mode` 进行删除,刚进入 vim 的状态便是 `Normal Mode`。 121 | 122 | + 使用 `x(dl)` 删除特定字符 123 | 124 | 可以结合 `x` 以及上述所讲的快速移动,删掉光标下的特定字符 125 | 126 | 在括号里标注 `l`,意指 `x` 为 `dl` 的简写。 127 | 128 | **`d` 指 `delete`,表示删除,是所有修改操作的基础。`dl` 由 `d` 和 `l` 两个操作组成,代表删掉光标右侧的字符,同理,`dh` 代表删掉光标左侧的字符,这是所有删除的基本形式,也是 vim 的核心思想。** 129 | 130 | + 使用 `daw` 删除特定单词 131 | 132 | `daw` 指 `delete a word`,表示删除特定单词。同样也可以使用 `db, dw` 来删除单词。 133 | 134 | + 使用 `dt, df` 加特定字符,删掉字符前的文本 135 | 136 | + 使用 `di(, da(` 删除特定符号内的文本,如删除括号,引号中的文本 137 | 138 | `di(` 指 `delete in (`,不会删掉括号。`da(` 指 `delete a (`,会连同括号一同删掉。同理还有 `di'`,`di"` 等,在编码中最为常用! 139 | 140 | + 使用 `D (d$)` 删除掉该字符以后的所有文本 141 | 142 | + 使用 `dd` 删掉整行 143 | 144 | + **把以上操作的所有 d 替换为 c,表示删除后进入编辑模式** 145 | 146 | `c` 指 `change`,表示删除,如 `d` 一样,是 vim 的基本动词 147 | 148 | + 使用 `r` 加特定字符,表示使用特定字符代替原有字符 149 | 150 | ### 文件以及多窗口 151 | 152 | + 使用 `:Ex (Explore)` 浏览目录 153 | 154 | 定位到文件所在行,回车进入指定文件 155 | 156 | + 使用 `:ls` 列出缓冲列表 157 | 158 | 缓冲列表中保存最近使用文件,行头有标号 159 | 160 | + 使用 `:bn` 进入最近使用文件 161 | 162 | `bn` 指 `buffer next`,进入缓冲列表的下个缓冲,即最近一次使用文件 163 | 164 | + 使用 `:b[N]` 进入缓冲列表中标号为 N 的文件 165 | 166 | `b 10` 指 `buffer 10`,进入缓冲列表,即最近一次使用文件 167 | 168 | + 使用 `:sbn, :vbn` 在新窗口打开最近使用文件 169 | 170 | `s` 指 `split`,水平方向。 171 | `v` 指 `vertical`,垂直方向。 172 | 173 | + 使用 `:on(ly)` 只保留当前窗口 174 | 175 | ### 基本操作 176 | 177 | 基本操作指查找,替换,撤销,重做,复制,粘贴,保存等 178 | 179 | + `/{pattern}` 查找 180 | 181 | `/` 后加需要查找的词或者正则表达式进行查询,`n` 向下查询,`N` 向上查询。 182 | 183 | + `:s/aa/bb/g` 替换 184 | 185 | `s` 指 `substitute` 的缩写,替换,`g` 代表全局替换。 186 | 187 | + `u` 撤销 188 | 189 | `u` 指 `undo` 的缩写,撤销。可与数字结合进行多次撤销。 190 | 191 | + `` 重做 192 | 193 | + `yy` 复制整行 194 | 195 | `y` 指 `yank`,复制。使 `y` 与快速移动结合起来,可以使用多种情况的复制,如复制括号中内容,复制引号中内容。 196 | 197 | 复制时,会把当前内容置入寄存器,使用 `:reg` 查看寄存器列表。 198 | 199 | + `p` 粘贴 200 | 201 | `p` 指 `paste`,粘贴。 202 | 203 | + `"*y` 复制内容至系统剪切板 204 | 205 | `:reg` 会列出寄存器列表,`"*` 寄存器代表系统剪切板(),所以以上就是把内容放到系统剪切板。 206 | 207 | 如果寄存器列表中没有该寄存器,则 vim 不支持系统剪切板,也可以使用命令 `vim --version | grep clipboard`。 208 | 209 | + `"*p` 粘贴系统剪切板中内容 210 | 211 | ## 常用配置 212 | 213 | ### 1tab == 2space 214 | 215 | ```vim 216 | set expandtab 217 | set smarttab 218 | set shiftwidth=2 219 | set tabstop=2 220 | ``` 221 | 222 | ### 保留操作记录 223 | 224 | 当关闭文件并再次进入时,可以使用 `u` 进行撤销动作 225 | 226 | ```vim 227 | set undofile 228 | set undodir=~/.vim-config/undo_dirs 229 | ``` 230 | 231 | ### 不生成交换文件 232 | 233 | ```vim 234 | " 不产生交换文件(当打开一个文件未正常关闭时会产生交换文件) 235 | set noswapfile 236 | ``` 237 | 238 | ## 常用插件 239 | 240 | 以下是在 [我的vim配置](https://github.com/shfshanyue/vim-config) 中所使用的插件,关于快捷键有可能经过我自定义。 241 | 242 | ### [nerdtree](https://github.com/scrooloose/nerdtree) 243 | 244 | 245 | 246 | 文件管理器 247 | 248 | + `,nn` 切换文件管理器窗口,类似于sublime的 `Command + k + b` 249 | + `,nf` 定位当前文件的位置 250 | 251 | 在文件管理窗口 252 | 253 | + `ma` 新建文件或文件夹 254 | + `md` 删除文件或文件夹 255 | + `I` 切换隐藏文件显示状态 256 | 257 | ### [ctrlp.vim](https://github.com/kien/ctrlp.vim) 258 | 259 | 260 | 261 | ctrlp,类似于sublime的ctrlp 262 | 263 | + `` 在当前项目下查找文件 264 | + `,b` 在buffer中查找文件 265 | + `,f` 在最近打开文件中查找 266 | 267 | 在ctrlp窗口中,`` 和 `` 控制上下移动。 268 | 269 | ### [ag.vim](https://github.com/rking/ag.vim) 270 | 271 | 272 | 273 | 查找关键字,类似于sublime的 `Command + Shift + f` 274 | 275 | + `Ag key *.js` 在特定文件下查找关键字 276 | 277 | 注:首先需要安装 [the_silver_searcher](https://github.com/ggreer/the_silver_searcher) 278 | 279 | ### [vim-commentary](https://github.com/tpope/vim-commentary) 280 | 281 | 注释命令 282 | 283 | + `:gcc` 注释当前行,类似于sublime的 `` 284 | 285 | ### [vim-fugitive](https://github.com/tpope/vim-fugitive) 286 | 287 | 288 | 289 | git扩展 290 | 291 | + `:Gblame` 查看当前行的归属 292 | + `:Gdiff` 查看与工作区文件的差异 293 | + `:Gread` 相当于 `git checkout -- file` 294 | + `:Gwrite` 相当于 `git add file` 295 | 296 | ### [syntastic](https://github.com/vim-syntastic/syntastic) 297 | 298 | 语法检查插件,设置eslint 299 | 300 | + `:SyntasticCheck` 语法检查,默认会在保存时进行语法检查,不过会有卡顿 301 | + `:lne[xt]` 下一处语法错误 302 | + `:lp[revious]` 上一处语法错误 303 | + `:! eslint %:p --fix` 自动修正错误 304 | 305 | ### [emmet-vim](https://github.com/mattn/emmet-vim) 306 | 307 | + `,` 类似于sublime的 `` 308 | 309 | ### [delimitMate](https://github.com/Raimondi/delimitMate) 310 | 311 | 括号,引号自动补全 312 | 313 | ### [goyo](https://github.com/junegunn/goyo.vim) 314 | 315 | 316 | 317 | + `:Goyo` 切换至 gotyo 模式 318 | 319 | ### [vim-colors-solarized](https://github.com/altercation/vim-colors-solarized) 320 | 321 | 322 | 323 | 可更改配置文件中 background 为 `dark` 和 `light` 切换主题 324 | 325 | ## 小结 326 | 327 | 通过本章你可以很熟练地在服务器中使用 vim 编辑文本,如果有必要的话还可以在 linux 中使用 vim 进行编程。但是在服务器中除了需要熟练地使用 vim 外,更需要应付多窗口管理,可以参考下一章 [tmux 与多窗口管理](https://shanyue.tech/op/tmux-setting)。 328 | -------------------------------------------------------------------------------- /tmux-setting.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 终端复用神器 tmux 简介配置及高频操作 3 | keywords: linux,tmux,ansible,centos中安装tmux,linux多窗口管理,终端复用,安装tmux,ansible安装tmux 4 | description: tmux 是一个终端复用器,这也是它命名的由来 t(terminal) mux(multiplexer),你可以在一个屏幕上管理多个终端! 5 | date: 2019-10-23 20:00 6 | sidebarDepth: 3 7 | tags: 8 | - linux 9 | 10 | --- 11 | 12 | # 窗口复用与 tmux 13 | 14 | > tmux is a terminal multiplexer 15 | 16 | `tmux` 是一个终端复用器,这也是它命名的由来 `t(terminal) mux(multiplexer)`,你可以在一个屏幕上管理多个终端! 17 | 18 | 在 `mac` 上得益于 [iterm2](https://www.iterm2.com/),你管理多个终端窗口相当方便。但是在 linux 上,`iterm2` 就鞭长莫及了,`tmux` 的优势就显出来了。 19 | 20 | 就我理解,`tmux` 在 linux 上或者 mac 上有诸多好处 21 | 22 | 1. 分屏 23 | 24 | 诚然,`iterm2` 也是可以做到分屏的,但是 `iterm2` 有一个缺点便是 `iterm for Mac`。如果结合 `iterm2` 与 `ssh` 的话,`iterm2` 分屏需要不断地 `ssh `,导致的后果就是有多个用户连接,使用 `who` 命令查看登录用户数。 25 | 26 | 1. attach 27 | 28 | `attach` 可以起到保护现场的作用,不至于因 `ssh timeout`,而丧失了工作环境。 29 | 30 | 1. 操作简单,可配置化 31 | 32 | 你可以使用快捷键很快地在多个窗口,面板间切换,粘贴复制,无限滚屏。 33 | 34 | 本章将介绍以下内容 35 | 36 | + contos/mac 上如何安装 `tmux` 37 | + 使用 ansible 自动化批量安装 `tmux` 38 | + `tmux` 常用操作 39 | 40 | 至于说它高颜值,体现在可定制化样式的状态栏下,可以设置状态栏的样式, 位置,当前window的样式,状态栏信息的丰富度。比如 [gpakosz/.tmux](https://github.com/gpakosz/.tmux) 的配置 41 | 42 | ![tmux](https://cloud.githubusercontent.com/assets/553208/9890797/8dffe542-5c02-11e5-9c06-a25b452e6fcc.gif) 43 | 44 | 45 | 46 | + 原文地址: [窗口复用与 tmux](https://shanyue.tech/op/tmux-setting.html) 47 | + 系列文章: [服务器运维笔记](https://shanyue.tech/op/) 48 | 49 | ## 安装 50 | 51 | 如果你在 mac 上,那么你可以使用 `brew install tmux` 安装 tmux,简单,快捷。 52 | 53 | 但是你在 centos 上,如果直接使用 `yum` 来安装软件,会安装特别旧的版本,且很多实用的功能无法使用。那么直接通过 [tmux源码](https://github.com/tmux/tmux) 自己编译安装则是一个不错的注意 54 | 55 | ```shell 56 | # 安装软件依赖 57 | $ yum install -y gcc automake libevent-devel ncurses-devel glibc-static 58 | 59 | # 下载源代码 60 | $ git clone git@github.com:tmux/tmux.git 61 | 62 | # 切换到 2.8 版本 63 | $ git checkout 2.8 64 | $ cd tmux 65 | 66 | # 编译源码 67 | $ sh autogen.sh && ./configure && make 68 | 69 | # 查看版本号 70 | $ ./tmux -V 71 | tmux 2.8 72 | ``` 73 | 74 | ### 使用 ansible 自动化安装 75 | 76 | **使用源码编译安装过于琐碎且易错,又有可能需要在若干台服务器上安装 `tmux`,此时使用 `ansible` 进行自动化安装是一个不错的选择。** 关于 `ansible` 可参考本系列文章 [使用 ansible 做自动化运维](https://shanyue.tech/op/ansible-guide)。 77 | 78 | ```shell 79 | $ git clone git@github.com:shfshanyue/ansible-op.git 80 | 81 | # 一次性给多服务器上安装 tmux 82 | $ ansible-playbook -i hosts tmux.yml 83 | ``` 84 | 85 | `tmux` 这个 ansible role 的配置在 [我的tmux配置](https://github.com/shfshanyue/ansible-op/blob/master/roles/tmux/tasks/main.yml) 上。配置文件如下 86 | 87 | ```yaml 88 | # 安装依赖软件 89 | - name: prepare 90 | yum: 91 | name: "{{item}}" 92 | with_items: 93 | - gcc 94 | - automake 95 | - libevent-devel 96 | # 字符终端处理库 97 | - ncurses-devel 98 | - glibc-static 99 | 100 | # 下载 tmux 源文件 101 | - name: install tmux 102 | git: 103 | repo: https://github.com/tmux/tmux.git 104 | dest: ~/Documents/tmux 105 | version: 2.8 106 | 107 | - name: make tmux 108 | shell: sh autogen.sh && ./configure && make 109 | args: 110 | chdir: ~/Documents/tmux/ 111 | 112 | # 使 tmux 全局可执行 113 | - name: copy tmux 114 | copy: 115 | src: ~/Documents/tmux/tmux 116 | dest: /usr/bin/tmux 117 | remote_src: yes 118 | mode: 0755 119 | 120 | # 使用我的配置文件 121 | - name: clone config file 122 | when: USE_ME 123 | git: 124 | repo: https://github.com/shfshanyue/tmux-config.git 125 | dest: ~/Documents/tmux-config 126 | 127 | - name: copy config file 128 | copy: 129 | src: ~/Documents/tmux-config/.tmux.conf 130 | dest: ~/.tmux.conf 131 | remote_src: yes 132 | 133 | - name: delete tmux-config 134 | file: 135 | name: ~/Documents/tmux-config 136 | state: absent 137 | ``` 138 | 139 | ## 快速开始 140 | 141 | ### 术语 142 | 143 | + `server` 包含多个 `session` 144 | + `session` 包含多个 `window` 145 | + `window` 类似于 `iterm2` 的 `Tab`,包含多个 `pane`,以下中文会翻译为窗口 146 | + `pane` 类似于 `iterm2` 的 `Pane`,以下中文会翻译为面板 147 | 148 | ### 启动 149 | 150 | ```shell 151 | # 新建一个 tmux session 152 | $ tmux 153 | ``` 154 | 155 | ### 分屏 156 | 157 | 158 | 159 | 在 `tmux` 环境下使用快捷键 `prefix %` 与 `prefix "` 完成分屏 160 | 161 | ### 查看所有快捷键 162 | 163 | 164 | 165 | `prefix ?` 166 | 167 | ## tmux 高频操作 168 | 169 | ### 向终端发送 prefix key 170 | 171 | 我习惯 `` 来作为前缀键,默认前缀建为 ``。`send-prefix` 代表向终端发送前缀键,`send-prefix -2` 代表新增一个快捷键代表前缀键。 172 | 173 | ```shell 174 | # 以下命令直接在 tmux 命令模式执行,或者加关键字 `tmux` 在 shell 中执行,或者写入配置文件 ~/.tmux.conf 中生效 175 | # `prefix :` 可以进入 tmux 命令模式 176 | 177 | $ set -g prefix2 C-s 178 | $ bind C-s send-prefix -2 179 | ``` 180 | 181 | ### 保持 ssh 连接 182 | 183 | 每次新建 `session` 的时候带上名字,方便下次 `attach` 。 184 | 185 | 稍微提一个命令 `detach`,默认快捷键 ` d`,会先 `detach` 掉当前 `session`。 186 | 187 | > prefix 默认为 `` 188 | 189 | ```shell 190 | $ tmux new -s shanyue 191 | 192 | # 或者使用快捷键 prefix + d 193 | $ tmux detach 194 | $ tmux attach -t shanyue 195 | ``` 196 | 197 | ### 快速移动面板 198 | 199 | 移动面板命令为 `select-pane`,可配置为 `vim` 式的移动命令。 200 | 201 | ```shell 202 | # 以下命令直接在 tmux 命令模式执行,或者加关键字 `tmux` 在 shell 中执行,或者写入配置文件 ~/.tmux.conf 中生效 203 | # `prefix :` 可以进入 tmux 命令模式 204 | 205 | # bind 绑定快捷键 206 | # -r 可重复按键 207 | # select-pane 选择面板 208 | $ bind -r h select-pane -L 209 | $ bind -r l select-pane -R 210 | $ bind -r j select-pane -D 211 | $ bind -r k select-pane -U 212 | ``` 213 | 214 | 其中,参数 `-r` 代表可重复按键,比如 `prefix r r` 表示 `prefix r, prefix r`。其中按键时间需要通过 `repeat-time` 来设置,一般为500ms。 215 | 216 | 另外,也可以开启鼠标支持,通过鼠标快速移动面板。 217 | 218 | ### 重命名窗口名 219 | 220 | `rename-window` 为重命名窗口名的命令,默认快捷键 `prefix ,` 221 | 222 | 但是有一个小问题,每当重命名窗口名后,敲几个空格又会自动重命名,自己的辛勤工作又被破坏了... 223 | 224 | 需要配置以下两个配置把它俩给关了,终于可以重命名了 225 | 226 | ```shell 227 | # 以下命令直接在 tmux 命令模式执行,或者加关键字 `tmux` 在 shell 中执行,或者写入配置文件 ~/.tmux.conf 中生效 228 | # `prefix :` 可以进入 tmux 命令模式 229 | 230 | set -wg allow-rename off 231 | set -wg automatic-rename off 232 | ``` 233 | 234 | ### 开启鼠标支持 235 | 236 | ```shell 237 | $ tmux set -g mouse on 238 | ``` 239 | 240 | 鼠标支持默认是关闭的,开启鼠标后,支持复制,翻屏,切换面板,切换窗口,resize。 241 | 242 | 鼠标支持的功能很强大,至此已经成功打造了一个 `iterm2` 了。不过鼠标模式我不大喜欢,所以还是禁了。 243 | 244 | 何况,开启鼠标支持后,谁都可以操作我的终端了,一点逼格也没有了 245 | 246 | ### 保留当前路径 247 | 248 | 新开 `pane` 和 `window` 时,保持当前路径。为以前的命令添加参数 `-c`,表明新建窗口或者面板的路径。 249 | 250 | 新开面板的命令为 `split-window` 251 | 252 | ```shell 253 | # 以下命令直接在 tmux 命令模式执行,或者加关键字 `tmux` 在 shell 中执行,或者写入配置文件 ~/.tmux.conf 中生效 254 | # `prefix :` 可以进入 tmux 命令模式 255 | 256 | bind c new-window -c "#{pane_current_path}" 257 | bind % split-window -h -c "#{pane_current_path}" 258 | bind '"' split-window -c "#{pane_current_path}" 259 | ``` 260 | 261 | ### 最大化当前面板 262 | 263 | 命令为 `tmux resize-pane -Z`,默认快捷键为 `prefix z`。需要查看更加详细的信息时可以按 `prefix z` 进入全屏,完毕之后,再按一次恢复。相当酷的一个功能。 264 | 265 | ### 翻屏 266 | 267 | 第一次使用 `tmux` 时, 使用`webpack`,输出信息很多,而有用的错误信息被覆盖。此时,往上翻屏就很重要了。此时要说下 `tmux window` 下的两种模式, 268 | 269 | + `default-mode` 270 | 271 | 就是刚进入 `tmux` 默认的模式。 272 | 273 | + `copy-mode` 274 | 275 | 按 `prefix [` 键进入此模式,类似于 `vi(emacs)` 的 `normal mode`,支持复制,粘贴,查找,以及翻页。具体是 `vi` 还是 `emacs` 可以根据以下命令探知,表明查看全局窗口设置 `mode-keys`,默认会是 `vi`,如果不是,那就请设置为 `vi` 吧~ 276 | ``` shell 277 | $ tmux show-window-options -g mode-keys 278 | ``` 279 | 与 `vi` 命令相同,如上下翻页(半屏)可使用 `C-d` 以及 `C-u`,当然你也可以使用 `hjkl`。 280 | 281 | 另外,也可以开启鼠标支持,使用滚轮来翻屏。 282 | 283 | ### 复制与粘贴 284 | 285 | 上边说到 `copy-mode`,接下来是复制与粘贴。进入 `copy-mode` 后,`v` 开始选中,`y` 来进行复制并会退出 `copy-mode`。使用 `prefix ]` 来进行粘贴。 286 | 287 | **在 linux 下复制粘贴是最重要最实用的功能了** 288 | 289 | `v & y` 为自定义配置,配置如下 290 | 291 | ``` 292 | bind -t vi-copy v begin-selection 293 | bind -t vi-copy y copy-selection 294 | ``` 295 | 296 | 复制操作会把内容存进 `buffer` 里,熟悉以下几个命令能够更熟练地操作 buffer 297 | 298 | ```shell 299 | $ tmux list-buffers # 列出所有 buffer 300 | $ tmux show-buffer -b [name] # 显示最近 buffer,也可指定 buffer name 301 | $ tmux choose-buffer    # 进入选择 buffer 界面,更加灵活 302 | ``` 303 | 304 | 另外,也可以开启鼠标支持,用鼠标来选择文字。 305 | 306 | ### 查找关键字 307 | 308 | 既然进入 `copy-mode`,熟悉 `vi` 的朋友一定知道查找是 `/` 与 `?`。 309 | 310 | ### 快速定位窗口 311 | 312 | 假设你新建了多个窗口,需要快速定位到某一个窗口,而你虽知道那个窗口中的内容,却忘了窗口号,这样如何解决呢? 313 | 314 | 有一个很好的解决方案的命令便是 `find-window`,更好用的便是默认的快捷键 `prefix f`。输入窗口内容的关键字,便可以快速定位到窗口,不过有一个小小的缺点,便是**不能定位到面板!** 315 | 316 | ### Last but not least 317 | 318 | **man tmux !** 不看文档不足以熟练,不看源码不足以精通。所以,平常需要多看几眼文档,多瞧几个命令。 319 | 320 | ## 小结 321 | 322 | 通过本章你可以很熟练地在服务器中使用 tmux 同时管理多个窗口,再结合 vim 的使用就可以使在服务器里工作达到随心所欲的地步了。关于 vim 的使用,配置以及插件,可以参考上一章 [vim 快速上手,配置以及插件](https://shanyue.tech/op/vim-setting)。 323 | -------------------------------------------------------------------------------- /blog-to-wechat.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: '黑客增长: 如何把用户从博客引流到公众号' 3 | path: blog-to-wechat 4 | description: 鉴于我自己也有一个博客,并且日均UV在200左右,决定来试一试。整理了一下思路,差不多与短信验证码的逻辑相似,于是花了一天时间搞定。 5 | keywords: 公众号开发,博客引流,devops,traefik,serverless,博客引流到公众号 6 | date: 2019-12-30 7 | tags: 8 | - node 9 | - devops 10 | 11 | --- 12 | 13 | # 黑客增长: 从博客引流到公众号 14 | 15 | > 本篇文章被移至: 16 | 17 | 前几日在朋友圈刷到一篇文章 [我是怎么把博客粉丝转到公众号的](https://cuiqingcai.com/7463.html),觉得相当有创意。最近一年在做 toC 产品,一直在谈拉新留存转化。**这刚好可以作为一个黑客增长的成功案例。** 18 | 19 | 鉴于我自己也有一个博客,并且日均UV在200左右,决定来试一试。整理了一下思路,差不多与短信验证码的逻辑相似,于是花了一天时间搞定。 20 | 21 | 另外,在此之前我也花了一天时间调研了 `serverless`。所以你完全可以**零成本实现从博客到公众号引流的功能**。 22 | 23 | ## 需求 24 | 25 | 先来谈一谈需求点: 26 | 27 | 1. 博客的内容被二维码弹框遮挡,弹框上有口令及公众号二维码 28 | 1. 扫码关注二维码并回复口令,回复口令后刷新博客弹框消失 29 | 30 | 需求很简单,如图下所示。你也可以去我的网站 [每天学习一点点](https://q.shanyue.tech) 查看实现效果 31 | 32 | ![](./assets/lock.png) 33 | 34 | ### 用户体验 35 | 36 | 不得不说,弹窗实在是一件伤用户体验的事情了。但也有一些措施,能够让用户体验变得稍微优化一点 37 | 38 | 1. 简单的口令,如只有四位数字 39 | 1. 非强制的弹窗,浏览内容时只有一定几率会出现弹窗,及时出现弹窗,刷新一下即可跳过 40 | 41 | ## 实现 42 | 43 | 简单过一遍技术栈,博客采用了 `vuepress`,前端的技术栈就是 `vue` 了。 44 | 45 | 由于是一个小小的服务,后端则尽可能轻量,于是我选择了 `koa`。为了方便后端迁移,如我以后将会迁移到 `serverless`,则使用无状态服务,即不依赖数据存储。如果没有状态,那怎么认证用户呢?使用 `jwt` 46 | 47 | 关于部署,则使用 `docker`,`docker-compose` 以及 `traefik`。至于部署这块,我有一个基础设施很完善的服务器环境,可以参考文章 [当我有一台服务器时我做了什么](https://shanyue.tech/op/when-server-2019.html)。 48 | 49 | 这下子实现思路就很清晰了: 50 | 51 | + css 部分使用选择器控制只显示文章的前两个标签,其余隐藏 52 | + js 部分有两个请求,一个根据口令请求 jwt,一个根据jwt判断是否合法,口令是四位数字随机生成 53 | + 后端,使用koa做一个简单的服务,使用jwt免掉存储状态,且可以使用四位简短数字,避免冲突 54 | + 存储,一个 lru,存在内存里,只留三分钟,用来交换 jwt 55 | + 部署,使用 `docker compose` 和 `docker` 由于无状态,很容易迁移到 serverless。且在微信环境下很容易制作测试环境与生产环境 56 | + 网关 `traefik`,方便自动服务发现与证书管理 57 | 58 | ## CSS 59 | 60 | 在前端控制内容被二维码遮挡的主要实现在于 CSS,而CSS主要控制两点 61 | 62 | 1. 弹框 63 | 1. 只显示博客内容前N段 64 | 65 | 我们博客大部分使用静态生成器,从 `markdown` 生成,生成的 `html` 大致长这个样子。 66 | 67 | ``` html 68 |
69 |

70 |

71 |

72 |
73 | ``` 74 | 75 | **现在前端的发展趋势是状态即UI**,我们使用一个变量 `isLock` 来控制所有样式。我们在 `vue template` 中加入弹框。 76 | 77 | ``` html 78 |
79 | 80 | 81 | 82 |
83 |
84 |
85 | ``` 86 | 87 | 弹框的显示隐藏容易控制,那 `Content` 即文章内容的呢,如何控制只显示前N段? 88 | 89 | 这肯定难不倒曾经三个月的工作只写 `CSS` 的我,使用 `nth-child`。`stylus` 代码如下 90 | 91 | ``` stylus 92 | .theme-default-content.lock 93 | .content__default 94 | :nth-child(3) 95 | opacity .5 96 | 97 | :nth-child(4) 98 | opacity .2 99 | 100 | :nth-child(n+5) 101 | display none 102 | 103 | .content-lock 104 | display block 105 | ``` 106 | 107 | 另外,我们也加了点渐变效果提升观感:前两段内容显示,第三段第四段渐变,五段以后全部隐藏。具体见 `css` 代码 108 | 109 | ## 解锁状态 110 | 111 | 在 `vue` 中主要控制状态 `isLock`,除了真实解锁逻辑还有一个随机性的弹窗。`this.lock` 代表是否解锁,`this.isLock` 代表是否显示弹窗 112 | 113 | ``` js 114 | { 115 | data () { 116 | return { 117 | lock: false, 118 | code: '' 119 | } 120 | }, 121 | computed: { 122 | isLock () { 123 | return this.lock ? Math.random() > 0.5 : false 124 | } 125 | }, 126 | } 127 | ``` 128 | 129 | ## 口令 130 | 131 | 口令需要是持久化的:保证每次刷新页面口令都是一致的。因此口令存储于 `localStorage` 中,随机生成四位数字,代码如下 132 | 133 | ``` js 134 | function getCode () { 135 | if (localStorage.code) { 136 | return localStorage.code 137 | } 138 | const code = Math.random().toString().slice(2, 6) 139 | localStorage.code = code 140 | return code 141 | } 142 | ``` 143 | 144 | ## 微信开发 145 | 146 | 接下来的逻辑是 147 | 148 | 1. 在微信中把口令传给后端 149 | 1. 前端刷新解锁 150 | 151 | 先看在微信这边的逻辑。我对微信开发封装成了简单的路由形式,核心逻辑如下:**当接收到数字码时存储到 `cache` 中,这里使用了一个简单的内存 lru,只存储口令三分钟** 152 | 153 | `cache` 中存储了 `code: userOpenId` 键值对 154 | 155 | ``` js 156 | function handleCode (message) { 157 | const { FromUserName: from, Content: code } = message 158 | // 对于 code,存储三分钟 159 | cache.set(code, from, 3 * 60 * 1000) 160 | return '您好,在三分钟内刷新网站即可无限制浏览所有文章' 161 | } 162 | 163 | const routes = [{ 164 | default: true, 165 | handle: handleDefault 166 | }, { 167 | text: /\d{4}/, 168 | handle: handleCode 169 | }] 170 | ``` 171 | 172 | ## 用户认证 173 | 174 | 此时的用户认证就很简单了,传统形式的基于 `session` 的用户认证。后端采用 `koa` 开发,代码如下,此时还使用了 `JOI` 做了简单的输入检验 175 | 176 | ``` javascript 177 | exports.verifyCode = async function (ctx) { 178 | const { code } = Joi.attempt(ctx.request.body, Joi.object({ 179 | code: Joi.string().pattern(/\d{4}/) 180 | })) 181 | if (!cache.get(code)) { 182 | ctx.body = '' 183 | return 184 | } 185 | const from = cache.get(code) 186 | ctx.body = jwt.sign({ from }, secret, { expiresIn: '3y' }) 187 | } 188 | ``` 189 | 190 | ## 口令过短会不会造成冲突 191 | 192 | 不会,由于在服务端用户口令只在内存中存在三分钟,所以冲突的可能性很小。那三分钟之后,如何进行用户状态的持久化呢? 193 | 194 | ## 用户状态持久化 195 | 196 | 但是此时有一个问题,`cache` 只能存储维护三分钟数据状态。这个问题如何解决? 197 | 198 | 此时使用 `JWT` 来做用户认证。因此校验口令时返回生成的 `jwt`,在浏览器端持久化,使用它来保持用户状态。关于 `JWT`,可以看我以前的文章 [JWT 深入浅出](https://shanyue.tech/post/jwt-guide.html#session) 199 | 200 | ``` javascript 201 | exports.verifyCode = async function (ctx) { 202 | // ... 203 | ctx.body = jwt.sign({ from }, secret, { expiresIn: '3y' }) 204 | } 205 | ``` 206 | 207 | 持久化用户认证逻辑前端部分 208 | 209 | ``` javascript 210 | async function verifyToken (token) { 211 | const { data: { data: verify } } = await request.post('/api/verifyToken', { 212 | token 213 | }) 214 | return verify 215 | } 216 | 217 | async mounted () { 218 | const code = getCode() 219 | this.code = code 220 | if (!localStorage.token) { 221 | this.lock = true 222 | const token = await verifyCode(code) 223 | if (token) { 224 | localStorage.token = token 225 | this.lock = false 226 | } 227 | } else { 228 | const token = localStorage.token 229 | const verify = await verifyToken(token) 230 | if (!verify) { 231 | this.lock = true 232 | } 233 | } 234 | } 235 | ``` 236 | 237 | 持久化用户认证逻辑后端部分 238 | 239 | ``` js 240 | exports.verifyToken = async function (ctx) { 241 | const { token } = Joi.attempt(ctx.request.body, Joi.object({ 242 | token: Joi.string().required() 243 | })) 244 | ctx.body = jwt.verify(token, secret) 245 | } 246 | ``` 247 | 248 | ## 如何保证用户取消订阅后再次弹框 249 | 250 | 当后端使用 `jwt` 用户认证时,服务器端收到 `openId`,根据 `openId` 获取用户信息,如果没有获取到,说明用户取消了订阅。 251 | 252 | **但是获取用户信息此项权限个人公众号未曾拥有** 253 | 254 | ## 部署 255 | 256 | 开发完成之后使用 `docker` 及 `docker-compose` 部署,`traefik` 做服务发现,通过 `https://we.shanyue.tech` 暴露服务,这三者在本系列文章中有所介绍 257 | 258 | `Dockerfile` 较为简单,配置文件如下 259 | 260 | ``` dockerfile 261 | FROM node:10-alpine 262 | 263 | WORKDIR /code 264 | 265 | ADD package.json /code 266 | RUN npm install --production 267 | 268 | ADD . /code 269 | 270 | CMD npm start 271 | ``` 272 | 273 | `docker-compose.yaml` 配置文件如下 274 | 275 | ``` yaml 276 | version: '3' 277 | 278 | services: 279 | wechat: 280 | build: . 281 | restart: always 282 | labels: 283 | - traefik.http.routers.wechat.rule=Host(`we.shanyue.tech`) 284 | - traefik.http.routers.wechat.tls=true 285 | - traefik.http.routers.wechat.tls.certresolver=le 286 | expose: 287 | - 3000 288 | 289 | networks: 290 | default: 291 | external: 292 | name: traefik_default 293 | ``` 294 | 295 | ## 测试环境与生产环境 296 | 297 | 当我们需要测试微信公众号时,直接使用自己的公众号不太合适,特别是当已有上线内容时。微信官方提供了测试公众号,我们可以重新填写 `域名` 以及 `token`。在测试环境使用域名 `https://we.dev.shanyue.tech` 298 | 299 | 我们在 `docker-compose` 中使用 `service` 中的 `wechat` 代表生产环境,`wechat-dev` 代表测试环境 300 | 301 | `wechat-dev` 通过文件挂载提供服务,可以更新重启应用,便可以做到实时更新代码,并实时在测试公众号中看到效果。 302 | 303 | `docker-compose.yaml` 配置文件如下 304 | 305 | ``` yaml 306 | version: '3' 307 | 308 | services: 309 | wechat: 310 | build: . 311 | restart: always 312 | labels: 313 | - traefik.http.routers.wechat.rule=Host(`we.shanyue.tech`) 314 | - traefik.http.routers.wechat.tls=true 315 | - traefik.http.routers.wechat.tls.certresolver=le 316 | expose: 317 | - 3000 318 | 319 | wechat-dev: 320 | image: 'node:10-alpine' 321 | restart: always 322 | volumes: 323 | - .:/code 324 | working_dir: /code 325 | command: npm run dev 326 | labels: 327 | - traefik.http.routers.wechat-dev.rule=Host(`we.dev.shanyue.tech`) 328 | - traefik.http.routers.wechat-dev.tls=true 329 | - traefik.http.routers.wechat-dev.tls.certresolver=le 330 | expose: 331 | - 3000 332 | 333 | networks: 334 | default: 335 | external: 336 | name: traefik_default 337 | ``` 338 | 339 | ## 代码开源 340 | 341 | 关于前端代码,皆在 [shfshanyue/Daily-Question](https://github.com/shfshanyue/Daily-Question/blob/master/.vuepress/theme/components/Page.vue) 中 342 | 343 | 关于后端代码,皆在 [shfshanyue/wechat](https://github.com/shfshanyue/wechat) 中 344 | 345 | 而本篇文章,属于 [个人服务器运维指南](https://github.com/shfshanyue/op-note) 的案例篇,关于 `docker`,`compose` 及 `traefik` 等基础设施的搭建均在本系列中有所介绍 346 | 347 | ## 获取一个永久 token 348 | 349 | 除了通过扫码回复公众号口令获得文章解锁外,这里也有一个永久 token 可以解锁。复制以下代码到控制台刷新即可解锁全部文章 350 | 351 | ``` js 352 | localStorage.token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1Nzc2MjI1MjEsImV4cCI6MTY3MjI5NTMyMX0.tB-CgK6aIo3whD-mAu3X37XT8q9v2bVXxG6llodznws' 353 | ``` 354 | -------------------------------------------------------------------------------- /docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: docker 简易入门 3 | keywords: docker 4 | date: 2019-11-19T20:02:20+08:00 5 | categories: 6 | - 运维 7 | - 后端 8 | thumbnail: https://docs.docker.com/engine/images/architecture.svg 9 | tags: 10 | - devops 11 | --- 12 | 13 | # docker 简易入门 14 | 15 | `docker` 使应用部署更加轻量,可移植,可扩展。更好的环境隔离也更大程度地避免了生产环境与测试环境不一致的巨大尴尬。由于 `docker` 轻便可移植的特点也极大促进了 `CI/CD` 的发展。 16 | 17 | 18 | 19 | + 原文链接: [docker简易入门](https://github.com/shfshanyue/op-note/blob/master/docker.md) 20 | + 系列文章: [个人服务器运维指南](https://github.com/shfshanyue/op-note) 21 | 22 | ## 术语 23 | 24 | `docker` 的架构图如下 25 | 26 | ![docker architecture](https://docs.docker.com/engine/images/architecture.svg) 27 | 28 | 从图中可以看出几个组成部分 29 | 30 | + `docker client`: 即 `docker` 命令行工具 31 | + `docker host`: 宿主机,`docker daemon` 的运行环境服务器 32 | + `docker daemon`: `docker` 的守护进程,`docker client` 通过命令行与 `docker daemon` 交互 33 | + `container`: 最小型的一个操作系统环境,可以对各种服务以及应用容器化 34 | + `image`: 镜像,可以理解为一个容器的模板配置,通过一个镜像可以启动多个容器 35 | + `registry`: 镜像仓库,存储大量镜像,可以从镜像仓库拉取和推送镜像 36 | 37 | ## 安装 docker 38 | 39 | > 参考在 centos 上安装 docker 的官方文档: 40 | 41 | 以下是在 `centos` 上安装 `docker` 的命令示例过程 42 | 43 | 安装依赖 44 | 45 | ``` bash 46 | $ yum install -y yum-utils device-mapper-persistent-data lvm2 47 | ``` 48 | 49 | 添加 `docker` 的yum镜像源,如果在国内,添加阿里云的镜像源 50 | 51 | ``` bash 52 | # 安装 docker 官方的镜像源 53 | $ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 54 | 55 | # 如果在国内,安装阿里云的镜像 56 | $ yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 57 | ``` 58 | 59 | 安装指定版本的 `docker` 并且启动服务 60 | 61 | ``` bash 62 | # 安装 docker 63 | $ yum install -y docker-ce 64 | 65 | # 安装指定版本号的 docker,以下是 k8s 官方推荐的 docker 版本号 (此时,k8s 的版本号在 v1.16) 66 | $ yum install -y docker-ce-18.06.2.ce 67 | 68 | $ systemctl enable docker 69 | Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service. 70 | 71 | $ systemctl start docker 72 | ``` 73 | 74 | 当 `docker` 安装成功后,可以使用以下命令查看版本号 75 | 76 | ``` shell 77 | $ docker --version 78 | Docker version 18.06.2-ce, build 6d37f41 79 | 80 | # 查看更详细的版本号信息 81 | $ docker version 82 | 83 | # 查看docker的详细配置信息 84 | $ docker info 85 | ``` 86 | 87 | ### 守护进程配置 88 | 89 | `dockerd` 是 `docker` 的守护进程,`dockerd` 可以通过配置文件进行配置,在 linux 下的配置文件位置在 `/etc/docker/daemon.json`,更详细内容可以参考 [官方文档](https://docs.docker.com/engine/reference/commandline/dockerd/)。 90 | 91 | 日志引擎为 `json-file`,对日志结构化,结合合适的日志系统,方便定位日志。 92 | 存储引擎为 `overrlay2` 93 | 94 | ``` bash 95 | $ mkdir /etc/docker 96 | 97 | # 设置 docker daemon 98 | $ cat > /etc/docker/daemon.json < [AS ] 210 | 211 | # 在多阶段构建时会用到 212 | FROM [:] [AS ] 213 | ``` 214 | 215 | ### ADD 216 | 217 | 把目录,或者 url 地址文件加入到镜像的文件系统中 218 | 219 | ``` dockerfile 220 | ADD [--chown=:] ... 221 | ``` 222 | 223 | ### RUN 224 | 225 | 执行命令,由于 `ufs` 的文件系统,它会在当前镜像的顶层新增一层 226 | 227 | ``` dockerfile 228 | RUN 229 | ``` 230 | 231 | ### CMD 232 | 233 | 指定容器如何启动 234 | 235 | **一个 `Dockerfile` 中只允许有一个 CMD** 236 | 237 | ``` dockerfile 238 | # exec form, this is the preferred form 239 | CMD ["executable","param1","param2"] 240 | 241 | # as default parameters to ENTRYPOINT 242 | CMD ["param1","param2"] 243 | 244 | # shell form 245 | CMD command param1 param2 246 | ``` 247 | 248 | ## 容器 249 | 250 | 镜像与容器的关系,类似于代码与进程的关系。 251 | 252 | + `docker run` 创建容器 253 | + `docker stop` 停止容器 254 | + `docker rm` 删除容器 255 | 256 | ### 创建容器 257 | 258 | 基于 `nginx` 镜像创建一个最简单的容器:启动一个最简单的 http 服务 259 | 260 | 使用 `docker run` 来启动容器,`docker ps` 查看容器启动状态 261 | 262 | ``` bash 263 | $ docker run -d --name nginx -p 8888:80 nginx:alpine 264 | 265 | $ docker ps -l 266 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 267 | 404e88f0d90c nginx:alpine "nginx -g 'daemon of…" 4 minutes ago Up 4 minutes 0.0.0.0:8888->80/tcp nginx 268 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 269 | ``` 270 | 271 | 其中: 272 | 273 | + `-d`: 启动一个 `daemon` 进程 274 | + `--name`: 为容器指定名称 275 | + `-p host-port:container-port`: 宿主机与容器端口映射,方便容器对外提供服务 276 | + `nginx:alpine`: 基于该镜像创建容器 277 | 278 | 此时在宿主机使用 `curl` 测试容器提供的服务是否正常 279 | 280 | ``` bash 281 | $ curl localhost:8888 282 | 283 | 284 | 285 | Welcome to nginx! 286 | 293 | 294 | 295 |

Welcome to nginx!

296 |

If you see this page, the nginx web server is successfully installed and 297 | working. Further configuration is required.

298 | 299 |

For online documentation and support please refer to 300 | nginx.org.
301 | Commercial support is available at 302 | nginx.com.

303 | 304 |

Thank you for using nginx.

305 | 306 | 307 | ``` 308 | 309 | 那如果要进入容器环境中呢?使用 `docker exec -it container-name` 命令 310 | 311 | ``` bash 312 | $ docker exec -it nginx sh 313 | / # 314 | / # 315 | / # 316 | ``` 317 | 318 | ### 容器管理 319 | 320 | `docker ps` 列出所有容器 321 | 322 | ``` bash 323 | $ docker ps 324 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 325 | 404e88f0d90c nginx:alpine "nginx -g 'daemon of…" 4 minutes ago Up 4 minutes 0.0.0.0:8888->80/tcp nginx 326 | 498e7d74fb4f nginx:alpine "nginx -g 'daemon of…" 7 minutes ago Up 7 minutes 80/tcp lucid_mirzakhani 327 | 2ce10556dc8f redis:4.0.6-alpine "docker-entrypoint.s…" 2 months ago Up 2 months 0.0.0.0:6379->6379/tcp apolloserverstarter_redis_1 328 | ``` 329 | 330 | `docker port` 查看容器端口映射 331 | 332 | ``` bash 333 | $ docker port nginx 334 | 80/tcp -> 0.0.0.0:8888 335 | ``` 336 | 337 | `docker stats` 查看容器资源占用 338 | 339 | ``` bash 340 | $ docker stats nginx 341 | CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 342 | 404e88f0d90c nginx 0.00% 1.395MiB / 1.796GiB 0.08% 632B / 1.27kB 0B / 0B 2 343 | ``` 344 | -------------------------------------------------------------------------------- /deploy-fe.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 前端部署演化史 3 | keywords: 前端部署,devops,helm,docker,k8s 4 | date: 2019-11-07 21:00 5 | hot: 13 6 | tags: 7 | - devops 8 | 9 | --- 10 | 11 | 前端一说起刀耕火种,那肯定紧随着前端工程化这一话题。随着 `react`/`vue`/`angular`,`es6+`,`webpack`,`babel`,`typescript` 以及 `node` 的发展,前端已经在逐渐替代过去 `script` 引 `cdn` 开发的方式了,掀起了工程化这一大浪潮。得益于工程化的发展与开源社区的良好生态,前端应用的可用性与效率得到了很大提高。 12 | 13 | 前端以前是刀耕火种,那前端应用部署在以前也是刀耕火种。那前端应用部署的发展得益于什么,随前端工程化带来的副产品? 14 | 15 | 这只是一部分,而更重要的原因是 `devops` 的崛起。 16 | 17 | 为了更清晰地理解前端部署的发展史,了解部署时运维和前端(或者更广泛地说,业务开发人员)的职责划分,当每次前端部署发生改变时,可以思考两个问题 18 | 19 | 1. 缓存,前端应用中http 的 `response header` 由谁来配?得益于工程化发展,可以对打包后得到带有 hash 值的文件可以做永久缓存 20 | 1. 跨域,`/api` 的代理配置由谁来配?在开发环境前端可以开个小服务,启用 `webpack-dev-server` 配置跨域,那生产环境呢 21 | 22 | 这两个问题都是前端面试时的高频问题,但话语权是否掌握在前端手里 23 | 24 | 时间来到 `React` 刚刚发展起来的这一年,这时已经使用 `React` 开发应用,使用 `webpack` 来打包。但是前端部署,仍是刀耕火种 25 | 26 | 27 | 28 | + 原文地址: [前端部署演化史](https://shanyue.tech/op/deploy-fe.html) 29 | + 系列文章: [个人服务器运维指南](https://shanyue.tech/op/) 30 | 31 | > 如果本篇文章能够对你有所帮助,可以帮我在 [shfshanyue/op-note](https://github.com/shfshanyue/op-note) 上点个 star 32 | 33 | ## 刀耕火种 34 | 35 | 一台跳板机 36 | 37 | 一台生产环境服务器 38 | 39 | 一份部署脚本 40 | 41 | 前端调着他的 `webpack`,开心地给运维发了部署邮件并附了一份部署脚本,想着第一次不用套后端的模板,第一次前端可以独立部署。想着自己基础盘进一步扩大,前端不禁开心地笑了 42 | 43 | 运维照着着前端发过来的部署邮件,一遍又一遍地拉着代码,改着配置,写着 `try_files`, 配着 `proxy_pass`。 44 | 45 | 这时候,前端静态文件由 `nginx` 托管,`nginx` 配置文件大致长这个样子 46 | 47 | ``` nginx 48 | server { 49 | listen 80; 50 | server_name shanyue.tech; 51 | 52 | location / { 53 | # 避免非root路径404 54 | try_files $uri $uri/ /index.html; 55 | } 56 | 57 | # 解决跨域 58 | location /api { 59 | proxy_pass http://api.shanyue.tech; 60 | } 61 | 62 | # 为带 hash 值的文件配置永久缓存 63 | location ~* \.(?:css|js)$ { 64 | try_files $uri =404; 65 | expires 1y; 66 | add_header Cache-Control "public"; 67 | } 68 | 69 | location ~ ^.+\..+$ { 70 | try_files $uri =404; 71 | } 72 | } 73 | ``` 74 | 75 | 不过...经常有时候跑不起来 76 | 77 | 运维抱怨着前端的部署脚本没有标好 `node` 版本,前端嚷嚷着测试环境没问题 78 | 79 | 这个时候运维需要费很多心力放在部署上,甚至测试环境的部署上,前端也要费很多心力放在运维如何部署上。这个时候由于怕影响线上环境,上线往往选择在深夜,前端和运维身心俱疲 80 | 81 | 不过向来如此 82 | 83 | 鲁迅说,向来如此,那便对么。 84 | 85 | **这个时候,无论跨域的配置还是缓存的配置,都是运维来管理,运维不懂前端。但配置方式却是前端在提供,而前端并不熟悉 nginx** 86 | 87 | ## 使用 docker 构建镜像 88 | 89 | `docker` 的引进,很大程度地解决了部署脚本跑不了这个大BUG。**`dockerfile` 即部署脚本,部署脚本即 `dockerfile`**。这也很大程度缓解了前端与运维的摩擦,毕竟前端越来越靠谱了,至少部署脚本没有问题了 (笑 90 | 91 | 这时候,前端不再提供静态资源,而是提供服务,一个 `http` 服务 92 | 93 | 前端写的 `dockerfile` 大致长这个样子 94 | 95 | ``` dockerfile 96 | FROM node:alpine 97 | 98 | # 代表生产环境 99 | ENV PROJECT_ENV production 100 | # 许多 package 会根据此环境变量,做出不同的行为 101 | # 另外,在 webpack 中打包也会根据此环境变量做出优化,但是 create-react-app 在打包时会写死该环境变量 102 | ENV NODE_ENV production 103 | WORKDIR /code 104 | ADD . /code 105 | RUN npm install && npm run build && npm install -g http-server 106 | EXPOSE 80 107 | 108 | CMD http-server ./public -p 80 109 | ``` 110 | 111 | 单单有 `dockerfile` 也跑不起来,另外前端也开始维护一个 `docker-compose.yaml`,交给运维执行命令 `docker-compose up -d` 启动前端应用。前端第一次写 `dockerfile` 与 `docker-compose.yaml`,在部署流程中扮演的角色越来越重要。想着自己基础盘进一步扩大,前端又不禁开心地笑了 112 | 113 | ``` yaml 114 | version: "3" 115 | services: 116 | shici: 117 | build: . 118 | expose: 119 | - 80 120 | ``` 121 | 122 | 123 | 运维的 `nginx` 配置文件大致长这个样子 124 | 125 | ``` nginx 126 | server { 127 | listen 80; 128 | server_name shanyue.tech; 129 | 130 | location / { 131 | proxy_pass http://static.shanyue.tech; 132 | } 133 | 134 | location /api { 135 | proxy_pass http://api.shanyue.tech; 136 | } 137 | } 138 | ``` 139 | 140 | 运维除了配置 `nginx` 之外,还要执行一个命令: `docker-compose up -d` 141 | 142 | 这时候再思考文章最前面两个问题 143 | 144 | 1. 缓存,由于从静态文件转换为服务,缓存开始交由前端控制 (但是镜像中的 `http-server` 不太适合做这件事情) 145 | 1. 跨域,跨域仍由运维在 `nginx` 中配置 146 | 147 | 前端可以做他应该做的事情中的一部分了,这是一件令人开心的事情 148 | 149 | 当然,前端对于 `dockerfile` 的改进也是一个慢慢演进的过程,那这个时候镜像有什么问题呢? 150 | 151 | 1. 构建镜像体积过大 152 | 1. 构建镜像时间过长 153 | 154 | ## 使用多阶段构建优化镜像 155 | 156 | 这中间其实经历了不少坎坷,其中过程如何,详见我的另一篇文章: [如何使用 docker 部署前端应用](https://juejin.im/post/5c83cbaa6fb9a04a0f65fdaa)。 157 | 158 | 其中主要的优化也是在上述所提到的两个方面 159 | 160 | 1. 构建镜像体积由 1G+ 变为 10M+ 161 | 1. 构建镜像时间由 5min+ 变为 1min (视项目复杂程度,大部分时间在构建时间与上传静态资源时间) 162 | 163 | ``` dockerfile 164 | FROM node:alpine as builder 165 | 166 | ENV PROJECT_ENV production 167 | ENV NODE_ENV production 168 | 169 | WORKDIR /code 170 | 171 | ADD package.json /code 172 | RUN npm install --production 173 | 174 | ADD . /code 175 | 176 | # npm run uploadCdn 是把静态资源上传至 oss 上的脚本文件,将来会使用 cdn 对 oss 加速 177 | RUN npm run build && npm run uploadCdn 178 | 179 | # 选择更小体积的基础镜像 180 | FROM nginx:alpine 181 | COPY --from=builder code/public/index.html code/public/favicon.ico /usr/share/nginx/html/ 182 | COPY --from=builder code/public/static /usr/share/nginx/html/static 183 | ``` 184 | 185 | 那它怎么做的 186 | 187 | 1. 先 `ADD package.json /code`, 再 `npm install --production` 之后 `Add` 所有文件。充分利用镜像缓存,减少构建时间 188 | 1. 多阶段构建,大大减小镜像体积 189 | 190 | 另外还可以有一些小优化,如 191 | 192 | + `npm cache` 的基础镜像或者 `npm` 私有仓库,减少 `npm install` 时间,减小构建时间 193 | + `npm install --production` 只装必要的包 194 | 195 | 前端看着自己优化的 `dockerfile`,想着前几天还被运维吵,说什么磁盘一半的空间都被前端的镜像给占了,想着自己节省了前端镜像几个数量级的体积,为公司好像省了不少服务器的开销,想着自己的基础盘进一步扩大,又不禁开心的笑了 196 | 197 | 这时候再思考文章最前面两个问题 198 | 199 | 1. 缓存,缓存由前端控制,缓存在oss上设置,将会使用 cdn 对 oss 加速。此时缓存由前端写脚本控制 200 | 1. 跨域,跨域仍由运维在 `nginx` 中配置 201 | 202 | ## CI/CD 与 gitlab 203 | 204 | 此时前端成就感爆棚,运维呢?运维还在一遍一遍地上线,重复着一遍又一遍的三个动作用来部署 205 | 206 | 1. 拉代码 207 | 1. `docker-compose up -d` 208 | 1. 重启 nginx 209 | 210 | 运维觉得再也不能这么下去了,于是他引进了 `CI`: 与现有代码仓库 `gitlab` 配套的 `gitlab ci` 211 | 212 | + `CI`,`Continuous Integration`,持续集成 213 | + `CD`,`Continuous Delivery`,持续交付 214 | 215 | 重要的不是 `CI/CD` 是什么,重要的是现在运维不用跟着业务上线走了,不需要一直盯着前端部署了。这些都是 `CI/CD` 的事情了,它被用来做自动化部署。上述提到的三件事交给了 `CI/CD` 216 | 217 | `.gitlab-ci.yml` 是 `gitlab` 的 CI 配置文件,它大概长这个样子 218 | 219 | ``` yaml 220 | deploy: 221 | stage: deploy 222 | only: 223 | - master 224 | script: 225 | - docker-compose up --build -d 226 | tags: 227 | - shell 228 | ``` 229 | 230 | `CI/CD` 不仅仅更解放了业务项目的部署,也在交付之前大大加强了业务代码的质量,它可以用来 `lint`,`test`,`package` 安全检查,甚至多特性多环境部署,我将会在我以后的文章写这部分事情 231 | 232 | 我的一个服务器渲染项目 [shfshanyue/shici](https://github.com/shfshanyue/shici) 以前在我的服务器中就是以 `docker`/`docker-compose/gitlab-ci` 的方式部署,有兴趣的可以看看它的配置文件 233 | 234 | + [shfshanyue/shici:Dockerfile](https://github.com/shfshanyue/shici/blob/master/Dockerfile) 235 | + [shfshanyue/shici:docker-compose.yml](https://github.com/shfshanyue/shici/blob/master/docker-compose.yml) 236 | + [shfshanyue/shici:gitlab-ci.yml](https://github.com/shfshanyue/shici/blob/master/.gitlab-ci.yml) 237 | 238 | 如果你有个人服务器的话,也建议你做一个自己感兴趣的前端应用和配套的后端接口服务,并且配套 `CI/CD` 把它部署在自己的自己服务器上 239 | 240 | 而你如果希望结合 `github` 做 `CI/CD`,那可以试一试 `github` + `github action` 241 | 242 | 另外,也可以试试 `drone.ci`,如何部署可以参考我以前的文章: [github 上持续集成方案 drone 的简介及部署](https://juejin.im/post/5dc0b563f265da4cef190b8a) 243 | 244 | ## 使用 kubernetes 部署 245 | 246 | 随着业务越来越大,镜像越来越多,`docker-compose` 已经不太能应付,`kubernetes` 应时而出。这时服务器也从1台变成了多台,多台服务器就会有分布式问题 247 | 248 | **一门新技术的出现,在解决以前问题的同时也会引进复杂性。** 249 | 250 | k8s 部署的好处很明显: 健康检查,滚动升级,弹性扩容,快速回滚,资源限制,完善的监控等等 251 | 252 | 那现在遇到的新问题是什么? 253 | 254 | **构建镜像的服务器,提供容器服务的服务器,做持续集成的服务器是一台!** 255 | 256 | 需要一个私有的镜像仓库,这是运维的事情,`harbor` 很快就被运维搭建好了,但是对于前端部署来说,复杂性又提高了 257 | 258 | 先来看看以前的流程: 259 | 260 | 1. 前端配置 `dockerfile` 与 `docker-compose` 261 | 1. 生产环境服务器的 `CI runner` 拉代码(可以看做以前的运维),`docker-compose up -d` 启动服务。然后再重启 `nginx`,做反向代理,对外提供服务 262 | 263 | 以前的流程有一个问题: **构建镜像的服务器,提供容器服务的服务器,做持续集成的服务器是一台!**,所以需要一个私有的镜像仓库,一个能够访问 `k8s` 集群的持续集成服务器 264 | 265 | 流程改进之后结合 `k8s` 的流程如下 266 | 267 | 1. 前端配置 `dockerfile`,构建镜像,推到镜像仓库 268 | 1. 运维为前端应用配置 `k8s` 的资源配置文件,`kubectl apply -f` 时会重新拉取镜像,部署资源 269 | 270 | 运维问前端,需不需要再扩大下你的基础盘,写一写前端的 `k8s` 资源配置文件,并且列了几篇文章 271 | 272 | + [使用 k8s 部署你的第一个应用: Pod,Deployment 与 Service](https://juejin.im/post/5db8c2b46fb9a020256692dc) 273 | + [使用 k8s 为你的应用配置域名: Ingress](https://juejin.im/post/5db8da4b6fb9a0204520b310) 274 | + [使用 k8s 为你的域名加上 https](https://juejin.im/post/5db8d94be51d4529f73e2833) 275 | 276 | 前端看了看后端十几个 k8s 配置文件之后,摇摇头说算了算了 277 | 278 | 这个时候,`gitlab-ci.yaml` 差不多长这个样子,配置文件的权限由运维一人管理 279 | 280 | ``` yaml 281 | deploy: 282 | stage: deploy 283 | only: 284 | - master 285 | script: 286 | - docker build -t harbor.shanyue.tech/fe/shanyue 287 | - docker push harbor.shanyue.tech/fe/shanyue 288 | - kubectl apply -f https://k8s-config.default.svc.cluster.local/shanyue.yaml 289 | tags: 290 | - shell 291 | ``` 292 | 293 | 这时候再思考文章最前面两个问题 294 | 295 | 1. 缓存,缓存由前端控制 296 | 1. 跨域,跨域仍由运维控制,在后端 `k8s` 资源的配置文件中控制 `Ingress` 297 | 298 | ## 使用 helm 部署 299 | 300 | 这时前端与运维已不太往来,除了偶尔新起项目需要运维帮个忙以外 301 | 302 | 但好景不长,突然有一天,前端发现自己连个环境变量都没法传!于是经常找运维修改配置文件,运维也不胜其烦 303 | 304 | 于是有了 `helm`,如果用一句话解释它,那它就是一个带有模板功能的 `k8s` 资源配置文件。作为前端,你只需要填参数。更多详细的内容可以参考我以前的文章 [使用 helm 部署 k8s 资源](https://juejin.im/post/5dbf7909f265da4d4b5fe7b4) 305 | 306 | 假如我们使用 [bitnami/nginx](https://hub.helm.sh/charts/bitnami/nginx) 作为 `helm chart`,前端可能写的配置文件长这个样子 307 | 308 | ``` yaml 309 | image: 310 | registry: harbor.shanyue.tech 311 | repository: fe/shanyue 312 | tag: 8a9ac0 313 | 314 | ingress: 315 | enabled: true 316 | hosts: 317 | - name: shanyue.tech 318 | path: / 319 | 320 | tls: 321 | - hosts: 322 | - shanyue.tech 323 | secretName: shanyue-tls 324 | 325 | # livenessProbe: 326 | # httpGet: 327 | # path: / 328 | # port: http 329 | # initialDelaySeconds: 30 330 | # timeoutSeconds: 5 331 | # failureThreshold: 6 332 | # 333 | # readinessProbe: 334 | # httpGet: 335 | # path: / 336 | # port: http 337 | # initialDelaySeconds: 5 338 | # timeoutSeconds: 3 339 | # periodSeconds: 5 340 | ``` 341 | 342 | 这时候再思考文章最前面两个问题 343 | 344 | 1. 缓存,缓存由前端控制 345 | 1. 跨域,跨域由后端控制,配置在后端 Chart 的配置文件 `values.yaml` 中 346 | 347 | 到了这时前端和运维的职责所在呢? 348 | 349 | 前端需要做的事情有: 350 | 351 | 1. 写前端构建的 `dockerfile`,这只是一次性的工作,而且有了参考 352 | 1. 使用 `helm` 部署时指定参数 353 | 354 | 那运维要做的事情呢 355 | 356 | 1. 提供一个供所有前端项目使用的 `helm chart`,甚至不用提供,如果运维比较懒那就就使用 [bitnami/nginx](https://hub.helm.sh/charts/bitnami/nginx) 吧。也是一次性工作 357 | 1. 提供一个基于 `helm` 的工具,禁止业务过多的权限,甚至不用提供,如果运维比较懒那就直接使用 `helm` 358 | 359 | 这时前端可以关注于自己的业务,运维可以关注于自己的云原生,职责划分从未这般清楚 360 | 361 | ## 统一前端部署平台 362 | 363 | 后来运维觉得前端应用的本质是一堆静态文件,较为单一,容易统一化,来避免各个前端镜像质量的参差不齐。于是运维准备了一个统一的 `node` 基础镜像,做了一个前端统一部署平台,而这个平台可以做什么呢 364 | 365 | 1. `CI/CD`: 当你 push 代码到仓库的特定分支会自动部署 366 | 1. `http headers`: 你可以定制资源的 `http header`,从而可以做**缓存优化**等 367 | 1. `http redirect/rewrite`: 如果一个 `nginx`,这样可以配置 `/api`,解决跨域问题 368 | 1. `hostname`: 你可以设置域名 369 | 1. `CDN`: 把你的静态资源推到 CDN 370 | 1. `https`: 为你准备证书 371 | 1. `Prerender`: 结合 `SPA`,做预渲染 372 | 373 | 前端再也不需要构建镜像,上传 CDN 了,他只需要写一份配置文件就可以了,大致长这个样子 374 | 375 | ``` yaml 376 | build: 377 | command: npm run build 378 | dist: /dist 379 | 380 | hosts: 381 | - name: shanyue.tech 382 | path: / 383 | 384 | headers: 385 | - location: /* 386 | values: 387 | - cache-control: max-age=7200 388 | - location: assets/* 389 | values: 390 | - cache-control: max-age=31536000 391 | 392 | redirects: 393 | - from : /api 394 | to: https://api.shanyue.tech 395 | status: 200 396 | ``` 397 | 398 | 此时,前端只需要写一份配置文件,就可以配置缓存,配置 `proxy`,做应该属于前端做的一切,而运维也再也不需要操心前端部署的事情了 399 | 400 | 前端看着自己刚刚写好的配置文件,怅然若失的样子... 401 | 402 | 不过一般只有大厂会有这么完善的前端部署平台,如果你对它有兴趣,你可以尝试下 `netlify`,可以参考我的文章: [使用 netlify 部署你的前端应用](https://shanyue.tech/op/deploy-fe-with-netlify.html) 403 | 404 | ## 服务端渲染与后端部署 405 | 406 | 大部分前端应用本质上是静态资源,剩下的少部分就是服务端渲染了,服务端渲染的本质上是一个后端服务,它的部署可以视为后端部署 407 | 408 | 后端部署的情况更为复杂,比如 409 | 410 | 1. 配置服务,后端需要访问敏感数据,但又不能把敏感数据放在代码仓库。你可以在 `environment variables`, `consul` 或者 `k8s configmap` 中维护 411 | 1. 上下链路服务,你需要依赖数据库,上游服务 412 | 1. 访问控制,限制 IP,黑白名单 413 | 1. RateLimit 414 | 1. 等等 415 | 416 | 我将在以后的文章分享如何在 k8s 中部署一个后端 417 | 418 | ## 小结 419 | 420 | 随着 `devops` 的发展,前端部署越来越简单,可控性也越来越高,建议所有人都稍微学习一下 `devops` 的东西。 421 | 422 | 道阻且长,行则将至。 423 | 424 | ## 相关文章 425 | 426 | + [个人服务器运维指南](https://juejin.im/post/5db7a9e2f265da4cf85d6fb9) 427 | + [如果你想搭建一个博客](https://juejin.im/post/5db78500f265da4d0a68cef7) 428 | + [当我有一台服务器时我做了什么](https://juejin.im/post/5c9232a8e51d45729b3b71e1) 429 | + [使用 k8s 部署你的第一个应用: Pod,Deployment 与 Service](https://juejin.im/post/5db8c2b46fb9a020256692dc) 430 | + [使用 k8s 为你的应用配置域名: Ingress](https://juejin.im/post/5db8da4b6fb9a0204520b310) 431 | + [使用 k8s 为你的域名加上 https](https://juejin.im/post/5db8d94be51d4529f73e2833) 432 | -------------------------------------------------------------------------------- /linux-monitor.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: linux 的 cpu/memory/process 等各项监控指标小记 3 | keywords: linux cput,linux memory,linux process,linux监控 4 | description: 自开始负责生产环境部署,中间遇到了若干线上环境内存以及CPU的问题。由于微服务以及容器的流行,现在已经可以很方便的使用 K8s + prometheus + grafana + alert 的方式进行监控,这足以覆盖大部分场景。 5 | date: 2019-07-05 16:03 6 | hot: 6 7 | tags: 8 | - linux 9 | - devops 10 | 11 | --- 12 | 13 | # linux 指标监控小记 14 | 15 | 自开始负责生产环境部署,中间遇到了若干线上环境内存以及CPU的问题。由于微服务以及容器的流行,现在已经可以很方便的使用 `K8s` + `prometheus` + `grafana` + `alert` 的方式进行监控,这足以覆盖大部分场景。 16 | 17 | 最重要的事情已经交由最适合的组件去做,然而了解一些在裸机上的命令以及指标也是必不可少的: 18 | 19 | 1. 了解监控什么指标 20 | 1. 平时写一些脚本也经常会 OOM 或者 CPU 使用率过高 21 | 22 | 先以一张来自 [linuxperf](http://www.brendangregg.com/linuxperf.html) 的图作为大纲,我试着对一些指标进行整理,以备不时之需。 23 | 24 | ![linux performance tools](./assets/linux_perf_tools_full.png) 25 | 26 | 27 | 28 | + 原文地址: [linux 各项指标监控小记](https://shanyue.tech/op/linux-monitor.html) · [github](https://github.com/shfshanyue/op-note/blob/master/linux-monitor.md) 29 | + 系列文章: [服务器运维笔记](https://shanyue.tech/op/) · [github](https://github.com/shfshanyue/op-note) 30 | 31 | ## htop/top 32 | 33 | ![htop](./assets/htop.png) 34 | 35 | htop 足以覆盖大多数指标,详细直接查看帮助即可。 36 | 37 | > 这里的 TIME 指的是 CPU 时间 38 | > htop 里的 task 数指的是进程树,top 里的 task 数指的是进程树 + 内核线程数,参考文章 39 | 40 | 1. sort: by mem/cpu/state. 根据进程状态排序也至关重要,特别在 load average 过高的时候。根据内存以及CPU使用率排序用以定位高资源占用者。 41 | 1. filter 42 | 1. fields 43 | 1. process/ count 44 | 1. ... 45 | 46 | ## CPU 基本信息 47 | 48 | 在 linux 中一切皆文件,查看 `/proc/cpuinfo` 查看信息。另有衍生问题 49 | 50 | + 如何查看 CPU 个数 51 | + 如何查看 CPU model 52 | + 如何查看 CPU 主频 53 | 54 | ```shell 55 | cat /proc/cpuinfo 56 | cat /proc/stat 57 | ``` 58 | 59 | ## 平均负载 (load average) 60 | 61 | 使用 `uptime` 和 `w` 可打印出系统过去 1, 5, 15 分钟内的平均负载。同时,你可以使用 `sar -q` 查看动态的平均负载。 62 | 63 | ```shell 64 | $ uptime 65 | 19:28:49 up 290 days, 20:25, 1 user, load average: 2.39, 2.64, 1.55 66 | $ w 67 | 19:29:50 up 290 days, 20:26, 1 user, load average: 2.58, 2.63, 1.61 68 | USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT 69 | root pts/0 172.16.0.1 19:27 6.00s 0.05s 0.00s tmux a 70 | ``` 71 | 72 | 在 `uptime` 的 man 手册中这么解释平均负载 73 | 74 | > System load averages is the average number of processes that are either in a runnable or uninterruptable state. 75 | 76 | 翻译过来就是指系统中处于可运行状态和不可中断状态的平均进程数。 77 | 78 | 对于 4 核的 CPU,如果平均负载高于 4 就代表负载过高 79 | 80 | ## 动态平均负载 81 | 82 | ```shell 83 | $ sar -q 1 100 84 | Linux 3.10.0-957.21.3.el7.x86_64 (shanyue) 10/21/19 _x86_64_ (2 CPU) 85 | 86 | 16:55:52 runq-sz plist-sz ldavg-1 ldavg-5 ldavg-15 blocked 87 | 16:55:53 0 464 0.07 0.11 0.13 0 88 | 16:55:54 0 464 0.06 0.10 0.13 0 89 | 16:55:55 0 464 0.06 0.10 0.13 0 90 | 16:55:56 0 464 0.06 0.10 0.13 0 91 | 16:55:57 0 464 0.06 0.10 0.13 0 92 | 16:55:57 0 464 0.06 0.10 0.13 0 93 | Average: 0 464 0.06 0.10 0.13 0 94 | 95 | ``` 96 | 97 | ## CPU 使用率 98 | 99 | 可以直接使用 `htop/top` 命令查看 CPU 使用率,`idle` 的cpu时间也可以直接通过 `top` 显示出来 100 | 101 | `CPU 利用率 = 1 - cpu-idle-time / cpu-time` 102 | 103 | ```shell 104 | $ top 105 | %Cpu(s): 7.4 us, 2.3 sy, 0.0 ni, 90.1 id, 0.0 wa, 0.0 hi, 0.2 si, 0.0 st 106 | ``` 107 | 108 | + user: 用户态,但不包括 nice 109 | + system: 内核态 110 | + nice: 低优先级用户态,nice 值为 1-19 的 CPU 时间 111 | + idle (id) 112 | + iowait (wa) 113 | + irq (hi) 114 | + softirq (si) 115 | + steal (st) 116 | 117 | ## 系统调用 118 | 119 | `strace` 查看系统调用 120 | 121 | + `-p` 指定pid 122 | + `-c` 统计各项系统调用被调用了多少次以及CPU时间 123 | 124 | ```shell 125 | # 用来看一个进程所用到的系统调用 126 | # -p: 指定 7477 号进程 127 | $ strace -p 7477 128 | 129 | # 用来查看某命令需要用到的系统调用 130 | $ strace cat index.js 131 | 132 | # 关于系统调用的统计信息 133 | $ strace -p 7477 -c 134 | ``` 135 | 136 | ## 内存 137 | 138 | `free` 用以查看系统内存。 139 | 140 | 如果查看进程内存,使用 `pidstat -r` 或者 `htop` 141 | 142 | ```shell 143 | $ free -h 144 | total used free shared buff/cache available 145 | Mem: 3.7G 682M 398M 2.1M 2.6G 2.7G 146 | Swap: 0B 0B 0B 147 | ``` 148 | 149 | ## 页大小 150 | 151 | ``` bash 152 | $ getconf -a | grep page 153 | ``` 154 | 155 | ## 进程 156 | 157 | 衍生问题 158 | 159 | + 如何根据命令名找到进程 160 | + 如何根据参数名找到进程 161 | + 进程状态有哪些 162 | + 如何获取进程状态 163 | + 如何获取进程的CPU占用率 164 | + 如何获取进程的内存占用 165 | 166 | ```shell 167 | # 查看 122 PID 进程 168 | $ ps 122 169 | 170 | # 根据命令名(command)找到 PID 171 | $ pgrep -a node 172 | 26464 node /code/node_modules/.bin/ts-node index.ts 173 | 30549 node server.js 174 | 175 | # 根据命令名以及参数找到 PID 176 | $ pgrep -af ts-node 177 | 26464 node /code/node_modules/.bin/ts-node index.ts 178 | 179 | # 查看 122 PID 进程的信息 180 | $ cat /proc/122/status 181 | $ cat /proc/122/* 182 | 183 | # 打印父进程树 184 | # -s --show-parents: 显示父进程 185 | # -a --arguments: 显示参数,如 echo hello 中 hello 为参数 186 | $ pstree 122 -sap 187 | ``` 188 | 189 | ## procfs 190 | 191 | 192 | 193 | ## 进程的状态 194 | 195 | + D uninterruptible sleep (usually IO) 196 | + R running or runnable (on run queue) 197 | + S interruptible sleep (waiting for an event to complete) 198 | + T stopped by job control signal 199 | + t stopped by debugger during the tracing 200 | + W paging (not valid since the 2.6.xx kernel) 201 | + X dead (should never be seen) 202 | + Z defunct ("zombie") process, terminated but not reaped by its parent 203 | 204 | 使用 `htop/top` 可以查看所有进程的状态信息,特别在几种情况下常用 205 | 206 | + 查看过多的僵尸进程 207 | + 当平均负载过大时 208 | 209 | ```shell 210 | # 第二行可以统计所有进程的状态信息 211 | $ top 212 | ... 213 | Tasks: 214 total, 1 running, 210 sleeping, 0 stopped, 3 zombie 214 | ... 215 | ``` 216 | 217 | ## 进程内存 218 | 219 | `ps -O rss` 指定 rss 可以查看进程的内存,另外还有命令 `top/htop` 与 `pidstat -r` 220 | 221 | ```shell 222 | # 查看 2579 PID 的内存 223 | # -O rss 代表附加 RSS 信息进行打印 224 | $ ps -O rss 2579 225 | PID RSS S TTY TIME COMMAND 226 | 2579 19876 S pts/10 00:00:03 node index.js 227 | ``` 228 | 229 | ## 实时查看进程内存 230 | 231 | `pidstat -sr` 232 | 233 | ```shell 234 | # 查看 23097 PID 的内存信息,每隔一秒打印一次 235 | # -r: 查看进程的内存信息 236 | # -s: 查看进程的 stack 信息 237 | # -p: 指定 PID 238 | # 1: 每间隔 1s 打印一次 239 | # 5: 共打印 5 组 240 | $ pidstat -sr -p 23097 1 5 241 | Linux 3.10.0-693.2.2.el7.x86_64 (shanyue) 07/18/19 _x86_64_ (2 CPU) 242 | 243 | 18:56:07 UID PID minflt/s majflt/s VSZ RSS %MEM StkSize StkRef Command 244 | 18:56:08 0 23097 0.00 0.00 366424 95996 2.47 136 80 node 245 | 246 | 18:56:08 UID PID minflt/s majflt/s VSZ RSS %MEM StkSize StkRef Command 247 | 18:56:09 0 23097 0.00 0.00 366424 95996 2.47 136 80 node 248 | 249 | 18:56:09 UID PID minflt/s majflt/s VSZ RSS %MEM StkSize StkRef Command 250 | 18:56:10 0 23097 0.00 0.00 366424 95996 2.47 136 80 node 251 | 252 | 18:56:10 UID PID minflt/s majflt/s VSZ RSS %MEM StkSize StkRef Command 253 | 18:56:11 0 23097 0.00 0.00 366424 95996 2.47 136 80 node 254 | 255 | 18:56:11 UID PID minflt/s majflt/s VSZ RSS %MEM StkSize StkRef Command 256 | 18:56:12 0 23097 0.00 0.00 366424 95996 2.47 136 80 node 257 | 258 | Average: UID PID minflt/s majflt/s VSZ RSS %MEM StkSize StkRef Command 259 | Average: 0 23097 0.00 0.00 366424 95996 2.47 136 80 node 260 | ``` 261 | 262 | ## 页表与缺页异常 263 | 264 | `pidstat -s` 中 `minflt` 与 `majflt` 代表缺页异常 265 | 266 | ```shell 267 | $ pidstat -s -p 23097 1 5 268 | Linux 3.10.0-693.2.2.el7.x86_64 (shanyue) 07/18/19 _x86_64_ (2 CPU) 269 | 270 | 18:56:07 UID PID minflt/s majflt/s VSZ RSS %MEM StkSize StkRef Command 271 | 18:56:08 0 23097 0.00 0.00 366424 95996 2.47 136 80 node 272 | 273 | 18:56:08 UID PID minflt/s majflt/s VSZ RSS %MEM StkSize StkRef Command 274 | 18:56:09 0 23097 0.00 0.00 366424 95996 2.47 136 80 node 275 | ``` 276 | 277 | ## 标准输出定位到文件中 278 | 279 | 280 | 281 | ## 列出打开的文件 282 | 283 | `lsof`, list open files 284 | 285 | ```shell 286 | # 列出打开的文件 287 | $ lsof 288 | COMMAND PID TID USER FD TYPE DEVICE SIZE/OFF NODE NAME 289 | systemd 1 root cwd DIR 253,1 4096 2 / 290 | systemd 1 root rtd DIR 253,1 4096 2 / 291 | ``` 292 | 293 | ## 容器中 namespace PID -> global PID 映射 294 | 295 | 换一个问题就是,**如何找出 docker 容器中的 pid 在宿主机对应的 pid** 296 | 297 | ```shell 298 | # 容器环境 299 | 300 | # 已知容器中该进程 PID 为 122 301 | # 在容器中找到对应 PID 的信息,在 /proc/$pid/sched 中包含宿主机的信息 302 | $ cat /proc/122/sched 303 | node (7477, #threads: 7) 304 | ... 305 | ``` 306 | 307 | ```shell 308 | # 宿主机环境 309 | 310 | # 7477 就是对应的 global PID,在宿主机中可以找到 311 | # -p 代表指定 PID 312 | # -f 代表打印更多信息 313 | $ ps -fp 7477 314 | UID PID PPID C STIME TTY TIME CMD 315 | root 7477 7161 0 Jul10 ? 00:00:38 node index.js 316 | ``` 317 | 318 | ## global PID -> namespace PID 映射 319 | 320 | 换一个问题就是, **已知宿主机的 PID,如何找出对应的容器** 321 | 322 | **常见的场景就是使用 `top/htop` 定位到占用内存/CPU过高的进程,此时需要定位到它所在的容器** 323 | 324 | ```shell 325 | # 通过 docker inspect 查找到对应容器 326 | $ docker ps -q | xargs docker inspect --format '{{.State.Pid}}, {{.ID}}' | grep 22932 327 | 328 | # 通过 cgroupfs 找到对应容器 329 | $ cat /etc/22932/cgroup 330 | ``` 331 | 332 | 幸运地是有人已经在 stackoverflow 上总结出来了 333 | 334 | + [https://stackoverflow.com/questions/24406743/coreos-get-docker-container-name-by-pid](https://stackoverflow.com/questions/24406743/coreos-get-docker-container-name-by-pid) 335 | 336 | ## SWAP 337 | 338 | ```shell 339 | # 查找关于 340 | $ vmstat -s 341 | ``` 342 | 343 | ## inode 344 | 345 | ```shell 346 | # -i: 打印 inode number 347 | $ ls -lahi 348 | ``` 349 | 350 | ## 网络吞吐量 351 | 352 | + 带宽: 指网络链路的最大传输速率 353 | + 吞吐量: 代表单位时间内成功传输的数据量,单位为 b/s (KB/s, MB/s) 354 | + PPS: pck/s (Packet Per Second),以网络包为单位的传输速率 355 | 356 | ```shell 357 | # 查看网卡信息 358 | $ ifconfig eth0 359 | 360 | $ sar -n DEV 1 | grep eth0 361 | # IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s 362 | 16:34:37 eth0 8.00 2.00 0.69 1.90 0.00 0.00 0.00 363 | 16:34:38 eth0 39.00 27.00 2.91 38.11 0.00 0.00 0.00 364 | 16:34:39 eth0 13.00 11.00 0.92 13.97 0.00 0.00 0.00 365 | 16:34:40 eth0 16.00 16.00 1.21 20.86 0.00 0.00 0.00 366 | 16:34:41 eth0 17.00 17.00 1.51 15.27 0.00 0.00 0.00 367 | Average: eth0 18.60 14.60 1.45 18.02 0.00 0.00 0.00 368 | ``` 369 | 370 | ## socket 状态 371 | 372 | ## socket 信息 373 | 374 | 推荐使用 `ss`,不过 `netstat` 仍需要掌握,在特定条件 (docker 中) 有可能没有 `ss` 命令。 375 | 376 | ```shell 377 | # -t TCP 378 | # -a 所有状态 379 | # -n 显示数字地址和端口号 380 | # -p 显示 pid 381 | $ netstat -tanp 382 | Active Internet connections (servers and established) 383 | Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name 384 | tcp 0 0 127.0.0.11:35283 0.0.0.0:* LISTEN - 385 | tcp 0 0 192.168.112.2:37344 172.18.0.1:6379 ESTABLISHED 78/node 386 | tcp 0 0 :::80 :::* LISTEN 78/node 387 | ``` 388 | 389 | + `Recv-Q` 与 `Send-Q` 不为0时,表示网络包堆积,需要注意 390 | 391 | ## 协议信息 392 | 393 | ```shell 394 | # 展示对每个协议的统计信息 395 | $ netstat -s 396 | 397 | # 展示对每个协议的统计信息 398 | $ ss -s 399 | Total: 1468 (kernel 1480) 400 | TCP: 613 (estab 270, closed 315, orphaned 0, synrecv 0, timewait 41/0), ports 0 401 | 402 | Transport Total IP IPv6 403 | * 1480 - - 404 | RAW 0 0 0 405 | UDP 30 22 8 406 | TCP 298 145 153 407 | INET 328 167 161 408 | FRAG 0 0 0 409 | 410 | # 也可以这样统计 estab socket 的数量 411 | $ netstat -tanp | grep ESTAB | wc -l 412 | 413 | ``` 414 | 415 | ## TCP 连接数 416 | 417 | ## PostgresSQL 的最大连接数与当前连接数 418 | 419 | ```sql 420 | -- 最大连接数 421 | show max_connections; 422 | 423 | -- 当前连接数 424 | select count(*) from pg_stat_activity; 425 | ``` 426 | 427 | ## mysql 的最大连接数与当前连接数 428 | 429 | ```sql 430 | -- 最大连接数 431 | show variables like 'max_connections'; 432 | 433 | -- 当前连接数 434 | show full processlist; 435 | ``` 436 | 437 | --------------------------------------------------------------------------------