使用Ansible自动安装Docker

准备虚拟机

checkout 用于创建 KVM Guest 的 Ansible 脚本。

cd ~/devenv_bootstrap/
git clone git@github.com:dhyuan/virt-infra-ansible.git

cd ~/devenv_bootstrap/virt-infra-ansible/roles
git clone https://github.com/dhyuan/ansible-role-virt-infra.git
cd ansible-role-virt-infra
git checkout -b devenv
git branch --set-upstream-to origin/devenv
git pull

基于自己的情况修改在 inventory 中定义的虚拟机参数

vi ~/devenv_bootstrap/virt-infra-ansible/k8s_masters.yml
vi ~/devenv_bootstrap/virt-infra-ansible/k8s_nodes.yml

如果我们希望利用 DHCP 服务让这些机器拥有固定的 IP,那么我们这里可以给虚拟机设置 mac 地址。

创建虚拟机

ansible-playbook ./virt-infra.yml \
--limit kvmhost,k8s_masters,k8s_nodes

启动虚拟机

ansible-playbook ./virt-infra.yml \
--limit kvmhost,k8s_masters,k8s_nodes \
--extra-vars virt_infra_state=running

停止虚拟机

ansible-playbook ./virt-infra.yml \
--limit kvmhost,k8s_masters,k8s_nodes \
--extra-vars virt_infra_state=shutdown

删除虚拟机

强烈建议用 ansible 删除虚拟机,因为 KVM HOST 上的 hosts 文件、ssh 的一些配置也要修改。

ansible-playbook ./virt-infra.yml \
--limit kvmhost,k8s_masters,k8s_nodes \
--extra-vars virt_infra_state=undefined

为安装 K8S 更新虚拟机

更新虚拟机

ansible-playbook --ask-become-pass --inventory ./inventory ansible/update-vms.yaml

在 KVMHost 上安装 docker role

ansible-galaxy install \
--roles-path ~/.ansible/roles/ \
git+https://github.com/haxorof/ansible-role-docker-ce.git,2.7.0

#
ansible-galaxy list

其代码保存在 ~/.ansible/roles/ansible-role-docker-ce

创建安装 docker 的 playbook

[devops@192 virt-infra-ansible]$ cat install_docker.yml
---
- hosts: all,!kvmhost
  vars:
    docker_version: 19.03.8
    docker_remove_pre_ce: false
    docker_users: [devops]
    docker_daemon_config:
      exec-opts: ["native.cgroupdriver=systemd"],
      registry-mirrors:
        [
          "http://ovfftd6p.mirror.aliyuncs.com",
          "http://registry.docker-cn.com",
          "http://docker.mirrors.ustc.edu.cn",
          "http://hub-mirror.c.163.com",
        ]
      insecure-registries: ["docker.mirrors.ustc.edu.cn"]

  roles:
    - role: ansible-role-docker-ce
[devops@192 virt-infra-ansible]$

其中,exec-opts: [“native.cgroupdriver=systemd”], 是为了避免以后使用 kubeadm 安装 k8s 集群时警告:

[WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/

在所有 k8s 节点上安装 docker engine。

ansible-playbook ./install_docker.yml \
--limit k8s_masters,k8s_nodes -v \
-e '{"docker_repository_url": {"CentOS": "http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo"}}'

登录到 master、nodes 节点确认 docker 安装成功。

CentOS7科学上网

安装 VPN

sudo yum install -y epel-release
sudo yum install -y vpnc

配置 vpnc

sudo cp /etc/vpnc/default.conf /etc/vpnc/config.conf

根据你的 VPN 服务供应商提供的参数修改/etc/vpnc/config.conf:

[root@osnode ~]# vi /etc/vpnc/config.conf
IPSec gateway my.vpn.gateway
IPSec ID my.ipsec.id
IPSec secret mysecret
# your username goes here:
Xauth username
[root@osnode ~]#

运行 vpnc

sudo vpnc /etc/vpnc/config.conf --local-port 0

停止 vpnc

sudo vpnc-disconnect

在CentOS7上创建持久化的桥接网络及Guests

上篇文章中用 ip link add name br0 type bridge 创建网桥重启之后就没有了。

添加网桥设备文件

[devops@192 ~]$ touch /etc/sysconfig/network-scripts/ifcfg-br0

配置网桥

[devops@192 ~]$ cat /etc/sysconfig/network-scripts/ifcfg-br0
TYPE=Bridge
BOOTPROTO=static
IPADDR=192.168.1.10
PREFIX=24
GATEWAY=192.168.1.1
DNS1=192.168.1.1
NM_CONTROLLED=no

DEFROUTE=yes
PEERDNS=yes
PEERROUTES=yes
IPV4_FAILURE_FATAL=no

DEVICE=br0
ONBOOT=yes
[devops@192 ~]$

把网卡挂到网桥
把 em1 网卡 IP 地址配置的部分注释掉(或者 BOOTPROTO=none),并加入 BRIDGE=br0。

[devops@192 ~]$ cat /etc/sysconfig/network-scripts/ifcfg-em1
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
IPV6_PRIVACY=no
NAME=em1
UUID=48f1a196-a6ff-4966-8b93-fb0d7b707e47
DEVICE=em1
ONBOOT=yes

#BOOTPROTO=static
#IPADDR=192.168.1.10
#PREFIX=24
#GATEWAY=192.168.1.1
#DNS1=192.168.1.1

NM_CONTROLLED=no
#BRIDGE=br0
[devops@192 ~]$

当前主机路由及网桥接口

[devops@192 ~]$ route -v
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         192.168.1.1     0.0.0.0         UG    0      0        0 br0
link-local      0.0.0.0         255.255.0.0     U     1002   0        0 em1
link-local      0.0.0.0         255.255.0.0     U     1004   0        0 br0
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 br0
192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0
[devops@192 ~]$
[devops@192 ~]$
[devops@192 ~]$ brctl show
bridge name    bridge id        STP enabled    interfaces
br0        8000.1866daa0cb22    no        em1
virbr0        8000.5254002a0526    yes        virbr0-nic
[devops@192 ~]$

重启网络服务

sudo systemctl restart network

如果再重启网络失败,出现这样的错误 Failed to start LSB: Bring up/down networking。需要检查一下 /etc/sysconfig/network-scripts/ 下面的网络接口文件定义的是不是有问题。我这里因为有双网卡,其中一个的配置出现问题,导致网络服务启动失败。

创建使用 br0 的虚拟机们

有了上面的网桥 br0,我们就可以通过下面的命令分分钟创建出使用 br0 的三台虚拟机。

ansible-playbook  ./virt-infra.yml \
  --limit kvmhost,k8shosts \
  -e virt_infra_root_password=password \
  -e virt_infra_disk_size=100 \
  -e virt_infra_ram=4096 \
  -e virt_infra_ram_max=8192 \
  -e virt_infra_cpus=8 \
  -e virt_infra_cpus_max=16 \
  -e '{ "virt_infra_networks": [{ "name": "br0", "type": "bridge" }] }' \
  -e virt_infra_state=running

登录到创建出来的 k8s_host 虚拟机 ping 局域网及 internet 网络都是通的,证明 br0 桥接工作正常。以后,我们就可以基于这些虚拟机来折腾 K8S 了。。。

ping veth不通的原因

今天这篇给自己解释了一下为什么 Linux veth0/1 + bridge ping 不通。
原因都在 ip route show table local 输出里。 直接上步骤:

[dahui@192 ~]$ route -v
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         192.168.1.1     0.0.0.0         UG    100    0        0 em1
192.168.1.0     0.0.0.0         255.255.255.0   U     100    0        0 em1
[dahui@192 ~]$

# 创建veth对。
sudo ip link add veth0 type veth peer name veth1

# 给veth设置IP地址
sudo ip addr add 192.168.2.10/24 dev veth0
sudo ip addr add 192.168.2.20/24 dev veth1

# 激活接口
sudo ip link set veth0 up
sudo ip link set veth1 up


# 直连路由被自动添加了路由表
[dahui@192 ~]$ route -v
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         192.168.1.1     0.0.0.0         UG    100    0        0 em1
192.168.1.0     0.0.0.0         255.255.255.0   U     100    0        0 em1
192.168.2.0     0.0.0.0         255.255.255.0   U     0      0        0 veth0
192.168.2.0     0.0.0.0         255.255.255.0   U     0      0        0 veth1
[dahui@192 ~]$


# 创建一个linux网桥 br0
sudo ip link add name br0 type bridge
sudo ip link set br0 up

# 把veth0接到br0,veth0有ip的情况下虽然能接收来自协议栈的数据但是reponse只能走br0了。
sudo ip link set dev veth0 master br0

# veth0 拥有IP已经没有意义,把这个IP给br0
sudo ip addr del 192.168.2.10/24 dev veth0
sudo ip addr add 192.168.2.10/24 dev br0

# 期待通过 br0接口ping  veth2能成功。 但是失败了。
[dahui@192 ~]$  ping -c 1 -I br0 192.168.2.20
PING 192.168.2.20 (192.168.2.20) from 192.168.2.10 br0: 56(84) bytes of data.

--- 192.168.2.20 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

[dahui@192 ~]$

下面是由系统维护的 local 路由表:

[dahui@192 ~]$ ip route show table local
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
broadcast 192.168.1.0 dev em1 proto kernel scope link src 192.168.1.10
local 192.168.1.10 dev em1 proto kernel scope host src 192.168.1.10
broadcast 192.168.1.255 dev em1 proto kernel scope link src 192.168.1.10
broadcast 192.168.2.0 dev veth1 proto kernel scope link src 192.168.2.20
broadcast 192.168.2.0 dev br0 proto kernel scope link src 192.168.2.10
local 192.168.2.10 dev br0 proto kernel scope host src 192.168.2.10
local 192.168.2.20 dev veth1 proto kernel scope host src 192.168.2.20
broadcast 192.168.2.255 dev veth1 proto kernel scope link src 192.168.2.20
broadcast 192.168.2.255 dev br0 proto kernel scope link src 192.168.2.10
[dahui@192 ~]$

Reference:
https://unix.stackexchange.com/questions/205708/linux-does-not-reply-to-arp-request-messages-if-requested-ip-address-is-associat

用Ansible在KVM上创建虚拟机

此文是对尝试 https://github.com/csmart/virt-infra-ansible 的记录。

我们的目标是用 Ansible 快速搭建出一个 miniCloud 环境,以方便以后部署基于 K8S 的各种生产力工具。
虽然 VMware vSphere 很好用,但因为 lisence 及价格的因素,KVM 是最适合拿来使用的 Hypervisor。

在宿主机上需要以下软件:

  • Ansible
  • KVM
  • KVM 用户空间工具
  • SSH keys
  • Guest Image

可运行以下命令准备环境:

  # Create SSH key if you don't have one
  ssh-keygen

  # libvirtd
  sudo yum groupinstall -y "Virtualization Host"
  sudo systemctl enable --now libvirtd

  # Ansible
  sudo yum install -y epel-release
  sudo yum install -y python36
  pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple --user ansible

  # Other deps
  sudo yum install -y \
  git \
  genisoimage \
  libguestfs-tools-c \
  libosinfo \
  python3 \
  python3-libvirt \
  python3-lxml \
  python3-pip \
  libselinux-python3 \
  qemu-img \
  virt-install

国内用户为了使用 yum 时有更快的下载速度,可以设置 yum 国内镜像源。

mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup

wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

pip 使用 -i https://pypi.tuna.tsinghua.edu.cn/simple 参数使用国内源。

确定要操作的网络接口及名称:

export NET_DEV=em1
sudo nmcli con |egrep -w "${NET_DEV}"
export NM_NAME=em1

通过 ip 命令创建网桥:

sudo ip link add name br0 type bridge
sudo ip addr add 192.168.1.10/24 dev br0
sudo ip link set dev em1 master br0 ; sudo ip addr del 192.168.1.10/24 dev em1 ; sudo ip link set br0 up

如果修改之后 ping baidu.com 不通,可以坚持一下路由表。确认是否有 default 路由、em1 是否还在路由表里等。

route -v
sudo ip route add default via 192.168.1.10 dev br0
sudo ip route del 192.168.1.0/24 dev em1

也可以用 nmci 创建网桥 br0 并把物理网卡 em1 插到网桥上。

sudo nmcli con add ifname br0 type bridge con-name br0
sudo nmcli con add type bridge-slave ifname "${NET_DEV}" master br0

也可以通过 brctl 创建网桥:

brctl addbr br0
brctl addif br0 em1

接下来就可以运行一下命令自动创建 KVM Guests 了。

ansible-playbook --limit kvmhost,simple ./virt-infra.yml

Reference:
https://github.com/csmart/virt-infra-ansible

虚拟化的分类

虚拟化是云计算的底层支撑技术。以前一台主机的各种硬件设备只能被操作系统管理、使用。有了虚拟化技术(VMM/Hypervisor 虚拟机监控器、QEMU、Intel-VT…),一台主机的各种硬件设备可以“分割成”几个部分分别被运行在主机上的不同的(Guest)操作系统使用。

从不同的视角,虚拟化技术有不同的分类。

软硬件实现的角度

软件虚拟化

QEMU,完全由软件模拟 VMM 层。它实际是通过软件仿真出目标平台。目标平台指令–>QEMU 翻译–>宿主机平台指令。

硬件虚拟化

计算机硬件自身就提供让 guest os 使用能力,而无需(特殊情况需要)VMM 截获处理。 2005,Intel VT。

Guest 是否与 VMM 协作

全虚拟化

Guest 环境里无需针对虚拟化安装任何程序/驱动,虚拟化的工作完全由 VMM/Hypervisor 截获并处理。Guest 完全不知道自己运行在虚拟硬件之上。

半虚拟化

需要在 Guest 环境里安装驱动与 VMM 协同工作来完成虚拟化,就是半虚拟化。因为你需要在 Guest 里运行特殊的用于虚拟化的程序,所以对 guest 来说是有侵入的。这不是很理想,但是好处是可以减轻 VMM 的复杂度。代表技术:virtio。

是否依赖操作系统

Type1

VMM/Hypervisor 直接运行在硬件之上,不依赖其它操作系统。代表产品 VMware ESXi、Xen。(其实 ESXi 就是一个精简的 linux…)

Type2

VMM/Hypervisor 运行在操作系统之上。比如 VMware Workstation。

有的文章把 KVM 算作 Type2。实际上它是以内核模块的方式实现了 VMM 的功能,同时硬件的管理也是依靠宿主操作系统的内核模块管理。从这个角度看其实 ESXi 只是定制得更狠些的 KVM 。。。

ansible basic concepts

安装

Ansible 从 2.5 版开始就已经支持 python3。如果你在使用 conda 管理你的 python 环境,那么切换到你的环境,通过使用 pip 直接安装就好。

pip3 install ansible
或者
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple ansbile

第一原理

ansbile 的基本工作原理就是通过(controll node) ssh 连接到远程主机(managed node),使用类似 K8S 里“控制器模式”的思想以一种状态声明的方式让你需要管理的主机到达你期望的状态:比如,拥有哪些软件、主机上服务运行在什么状态等。幂等性对我来说是最有吸引力的特性之一。

基础配置

正如每个软件总需要根据不同参数运行一样,ansible 的基础配置信息放在 ansible.cfg 文件中。而此文件的位置依次根据$ANSIBLE_CONFIG, 当前目录、Home 目录, /etc/ansible/ansible.cfg 的顺序查询。其内容大致如下:

[defaults]
hostfile = hosts
remote_uesr = devop
private_key_file = ./rsa/dell430/private_key_rsa
host_key_checking = False

hostfile 定义的文件保存了当你运行 ansbile playbook 时需要操作的主机的连接信息。这个文件就是 inventory 文件,如果没有 ansible.cfg 的配置存在其内容大致如下:

kvmhost ansible_ssh_host=192.168.1.10 ansbile_ssh_port=22 ansible_ssh_user=devop ansbile_ssh_private_key_file=./rsa/dellT430.rsa

有了 ansible.cfg 的配置,其中 ssh 连接使用的用户名、私钥这些信息就可以从 inventory 文件中去掉,仅定义主机别名、ip、port 就可以了。

kvmhost ansible_ssh_host=192.168.1.10 ansbile_ssh_port=22

小试 ansible:

managed node 是一台安装了 CentOS7.8 的 Dell T430,ip 地址 192.168.1.10,用户名 devop。
本地 Mac 做为 controll node,当前目录下有 ansible.cfg 及 inventory 文件 hosts。
当然,在这之前你要参考上一篇记录的方法,先要用 ssh-keygen 制作私钥及证书,并用 ssh-copy-id 把证书 copy 到 managed node 实现免密 ssh 登录。

ansible.cfg:

(base) ➜  kvm git:(master) ✗ cat ansible.cfg
[defaults]
hostfile = hosts
remote_uesr = devop
private_key_file = /Users/dahui/.ssh/homeDellT430_rsa
host_key_checking = False

Inventory file:

(base) ➜  kvm git:(master) ✗ cat hosts
kvmhost ansible_ssh_host=192.168.1.10 ansbile_ssh_port=22

尝试两个简单命令

(base) ➜  kvm git:(master) ✗ ansible kvmhost -m ping
kvmhost | success >> {
    "changed": false,
    "ping": "pong"
}

(base) ➜  kvm git:(master) ✗ ansible kvmhost -m command -a uptime
kvmhost | success | rc=0 >>
00:48:22 up 20 min,  1 user,  load average: 0.05, 0.03, 0.05

(base) ➜  kvm git:(master) ✗

Reference:
https://docs.ansible.com/

网站环境设置(1)

1) 免密登录服务区。

制作密钥。在本地生成私钥、公钥(比如 server_name.rsa server_name.rsa.pub)。

cd ~/.ssh
ssh-keygen -t rsa

把公钥 copy 到服务器。

ssh-copy-id -i server_name.rsa.pub userId@server_ip

可以打开 server 上的 userId_HOME/.ssh/authorized_keys 查看公钥是否添加成功。

修改本地~/.ssh/config 文件,添加以下内容。

Host Server_IP
HostName Server_Name
IdentityFile YOUR_PRIVATE_KEY_FILE_PATH

2)免密 sudo

visudo 加入类似这样的一句,注意应该放在组的设置之后。
USER_ID ALL=(ALL) NOPASSWD: ALL

3) nginx 虚拟主机设置。

nginx 虚拟主机可以通过 IP、域名、端口这三种方式进行设置。购买了一台阿里云主机,有多个域名,通过域名进行设置就好。

安装 nginx,设置不同域名的虚拟主机。

编辑 /etc/nginx/nginx.conf, 在 http 部分 include 对虚拟主机定义。
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/telbox;
include /etc/nginx/sites-enabled/linkedall;

linkedall 文件内容如下(静态 web):

server {
listen 80;

root /home/devop/linkedall/web;
index index.html index.htm;

server_name www.linkedall.netlinkedall.net *.linkedall.net;

location / {
  try_files $uri $uri/ =404;
}
location ~* \.(html)$ {
  access_log off;
  add_header  Cache-Control  max-age=no-cache;
}

location ~* \.(css|js|png|jpg|jpeg|gif|gz|svg|mp4|ogg|ogv|webm|htc|xml|woff)$ {
  access_log off;
  add_header    Cache-Control  max-age=360000;
}
... ...

telbox 虚拟主机定义如下(作为 proxy 转向后端的运行在 8080 端口的服务):

server {
  listen 80 default_server;
  listen [::]:80 default_server ipv6only=on;
  root /usr/share/nginx/html;
  index index.html index.htm;

  # Make site accessible from http://localhost/
  server_name www.telbox.cn;

  location / {
          proxy_pass http://localhost:8080;
          # First attempt to serve request as file, then
          # as directory, then fall back to displaying a 404.
  # try_files $uri $uri/ =404;
          # Uncomment to enable naxsi on this location
          # include /etc/nginx/naxsi.rules
  }

Reference:
https://juejin.im/post/6844903604034240525

read text file in reactive way

在 Java8 之前,使用 Java,我们可以按字节、按字符、按行来读取文本文件。
Java8 提供了 Files.lines() 方法,可以让我们以 stream 的方式读取,stream 是延迟读取的。
stream 风格之后,现在我们可以更进一步,以 reactive 的方式读取文件。Flux.fromStream 提供了方便的接口,直接把 stream 转为 flux。考虑到对文件这种资源使用之后都要有 close 这种操作,那么直接使用 Flux.using 就再合适不过了,免去了手动释放资源这种模版式的代码。

private static Flux fromPath(Path path) {
return Flux.using(() -> Files.lines(path),
Flux::fromStream,
BaseStream::close
);
}

Reference:
https://simonbasle.github.io/2017/10/file-reading-in-reactor/
这篇文章里有关于的 window 各种用法的解释:https://blog.51cto.com/liukang/2094073。
通过 Jackson 流式解析 Json:https://cassiomolin.com/2019/08/19/combining-jackson-streaming-api-with-objectmapper-for-parsing-json/

Flux 的 create 和 generate 的区别。

Mokito https://semaphoreci.com/community/tutorials/stubbing-and-mocking-with-mockito-2-and-junit

用CountDownLatch做并发练习题

拿CountDownLatch对比着Semaphore来学习会简单很多。
CountDownLatch比起Semaphore来使用简单很多,它只有await和countDown方法。也就是说

1) CountDownLatch初始化之后设置的计数值在被减到0之后就不能被复原了,而Semaphore可以通过release恢复信号/许可的数量,所以CountDownLatch能解决的问题范畴要小于Semaphore。比如在用到了semaphore.release这种操作的代码里,我们基本是没办法用CountDownLatch替换Semaphore来解决的。
2) 另外,从允许、禁行这个方向上讲,Semaphore是信号/许可数量大于0时线程可运行(semapher.acquire不阻塞),对于CoundDownLatch来说则是计数等于0时线程可运行(countDownLatch.await不阻塞)。

所以,对于LeetCode多线程练习题,只有1114题可以用CountDownLatch来完成。 https://leetcode-cn.com/problemset/concurrency/