响应式流的生命周期

这里所说的响应式流的生命周期是说我们从定义响应式流到触发这个流的处理所经历的不同阶段。
总的来说,就是三个阶段。组装时、订阅时、运行时。

组装时

这个阶段就是我们建立处理模型的阶段。基本上是解决了下面 3 个方面的问题。

1)定义这个 reactive stream 的数据来源是什么。Publisher

这个阶段我们通过使用诸如 just(), range(), fromArray(), push(), create(), generate()等方法来设置数据源头。

2)这个数据流的最终订阅者是谁?Subscriber

我们在最下游 publisher 上通过调用 subscribe()方法把 subscriber 从下游到上游依次传递到最上游的包含数据源的 publisher。

3)数据到达最终订阅者那里时需要做哪些转换、处理? Operator

以上两个调用解决了数据来源于哪里、被谁最终接收的问题。但是在数据从上游被传递到下游的过程中,我们往往需要对数据做各种处理。这个阶段就各种 operator 大显身手的地方,比如 map(), flatMap(), filter(), reduce(), scan(), concatMap(), usingWhen()… …
实际上每经历一个 operator,就生成了一个新的中间级别的 publisher,这就是 reactive stream 的不可变性。

组装时基本就是我们写出的代码定义出的静态逻辑这部分。
程序运行起来,才会进入下面的两个阶段:订阅时、运行时,所以说这两个阶段是程序的动态表现。

订阅时

通过组装时我们定义了什么数据(publisher)经过怎样的处理(operator)最后传递给谁(subscriber)。而订阅时这个阶段,解决的问题是把调用下游 publisher 时传入的 subscriber 依次传递给上游的的 publisher。可以想象 Project Reactor 在传递 subscriber 的时候是依次进行封装之后传递的而不会是直接把下游的 subscriber 传递上去,因为以 operator 串联起来的各个 publisher 都需要“自己”的 subscriber。可以体会下这里“Subscriber 链”的概念。
这个传递过程很重要,因为 subscriber 中的 onSubscribe(Subscription s)提供了上游 publisher 把 Subscription 传递给下游 Subscriber 的机制。
因为 Subscriber 只有通过 subsciption 的 request 方法才能启动数据的流动。

运行时

经过组装时、订阅时,数据流已经“一触即发”。我们只有通过上游传递给我们的 Subscription 调用 request()方法就可以触发数据的流动了。
通过(在 Subscirberd 的 onSubscription(), onNext()被回调时)向 Subscription 的 request()传入不同的数字,我们分别可以实现拉模型、推模型、拉-推模型。
响应式流的运行时所强调的就是这种 Publisher 和 Subscriber 之间的信号交换。

与加密相关的一些名词

1) 密码学

机密性、真实性、完整性

plantext 明文 cipher 密钥 ciphertext 密文

散列函数

将任意长度的输入转化为定长输出的算法。抗原像性、抗第二原像性、强抗碰撞性。指纹、摘要。

消息验证代码:MAC HMAC(Keyed-hash Message Authentication Code)

MAC:核心是带密钥的 Hash 算法。
HMAC:两轮 Hash,其中一个是密钥是“预共享密钥”。
Msg + K –>MAC 算法 –> MAC 随消息发送
参考:https://blog.csdn.net/pz641/article/details/110876060

对称加密(私钥加密)

对称密钥算法相关主流协议:DES、3DES、AES、RC4。
序列密码(Stream Cipher)、分组密码(Block Cipher)
RC4 属于序列密码,AES 算是最流行的分组密码,无线安全技术 WPA2 就是使用 AES。
分组密码:ECB、CBC。CBC 引入了初始向量 IV 让每即使相同的输入输出也不同。(IV 与明文第一块进行异或过后加密,加密后的密文作为下一个块的 IV)

非对称加密(公钥加密)

公钥加密只有私钥可以解密。
私钥加密公钥解密。无私密性、可做数字签名。
RAS 使用最多的非对称加密算法。 RAS 推荐强度 2048,相当于 112 的对称加密。

数字签名

MAC 是一种电子签名,但需要双方预先设定一样的加密密码。
RSA 情况下的数字签名。
发送方
1)求文档 Hash 值
2)对 结果散列 + hash 算法 + 其它元数据 进行编码
3)用私钥加密编码后的数据,形成签名
接收方
1)使用相同 Hash 算法计算文档散列
2)用公钥解密发送方的签名,并与自己计算的进行对比。
RAS 只是公钥加密的一种,不同的公钥加密有不同的签名流程。

密钥交换

密钥交换的目的是先得到 预主密钥。
密钥交换算法:RSA、DHE_RSA、。。。
RSA 不支持前向保密

下图是 Diffie-Hellman 密钥交换示意图:

证书

证书是包含公钥、订阅人信息、证书颁发者数字签名的数字文件,是存储、传递、使用公钥的容器。

RSA 公钥、私钥的生成

https://blog.csdn.net/qq_41259576/article/details/91356419

2)SSL/TLS

TLS 是一种密码学协议以保证双方会话安全。TLS 是在 TCP 协议之上、HTTP 协议之下–处于表示层。
TLS 主要分为两层,底层的是 TLS 记录协议,主要负责使用对称密码对消息进行加密。上层的是 TLS 握手协议,主要分为握手协议,密码规格变更协议和应用数据协议 4 个部分。

SSL 1.0, 2.0 1994-11 月
SSL 3.0 1995 年底
TLS 1.0 1999-1
TLS 1.1 2006-4
TLS 1.2 2008-8
TLS 1.3 2018-8 1)握手更快;2)淘汰了一些基元 RC4, SHA-1, DES, MD5 …
https://zhuanlan.zhihu.com/p/133375078

握手流程

有三种握手流程:对服务器进行身份验证的完整握手、用于恢复会话的简短握手、对服务器客户端进行双向验证的握手。
这里仅简短描述下“对服务器进行身份验证的完整握手”,如下图:

1)ClientHello

很明显,第一条消息始于 Client。客户端需要提供自己 TLS 版本、随机数、加密套件、压缩方法、扩展信息给服务端,让服务端进行选择。

2)ServerHello

这条消息是服务器根据 Client 上报的选项并结合自己的的情况把选择告诉 Client。这个消息中也包括一个随机数。注意 ClientHello、ServerHello 中的随机数不是用于生成预主密钥,而是用于防止消息伪造。

3)Certificate

这是服务器给客户端的证书链。注意,这里是证书链而不仅仅是服务器的主证书。主证书排第一个、随后依次是中间证书,根证书无需在此链中。
因为客户端发送的密码套件、服务器最终选择的密码套件未必需要身份认证,同时不是所有身份认证都需要证书,所以,这个消息是可选的。

4)ServerKeyExchange

这个消息是为服务器为密钥交换向客户端提供数据。如是 DH 算法,那么这个参数就是 如上“Diffie-Hellman 密钥交换示意图”中所示的 g、p、A。
微观到代码,就是大致如下的定义:

struct {
          opaque dh_p<1..2^16-1>;
          opaque dh_g<1..2^16-1>;
          opaque dh_Ys<1..2^16-1>;
      } ServerDHParams;     /* Ephemeral DH parameters */

对于需要两次消息确定预主密钥的算法,首先由服务器发起也很容易理解。因为是 Client 首先提供选项而 Server 进行选择,那么 Server 自然是首先发出 DH 密钥交换消息就是很正常的了。
如果协商的密钥交换算法是 RSA 密钥交换算法,那么服务器就无需发送这条消息,而是由 Client 通过在 ClientKeyExchange 中携带一个 46 字节的随机数作为预主密钥即可。

5)ServerHelloDone

这个消息表示 Server 这边的握手相关的消息都发送完了。

6)ClientKeyExchange

这个消息是为了 Sever 提供密钥交换的数据。
如果是 RSA 密钥交换算法,Client 用 46 字节的随机数作为预主密钥发送给 Server 即可。如果是 DH 算法,就是如“Diffie-Hellman 密钥交换示意图”中所示的数字 B。
至此,预主密钥(premaster secret)就协商完毕了。真正用于加密的 master secret 是由预主密钥经过进一步变化而生成。

7)ChangeCipherExchange

这个消息表示已经生成加密密钥了,即将切换到的加密模式。

8)Finished

这个消息表示握手结束,消息内容即将加密。这个消息中很重要的一个字段是 verify_data, 其值是用协商得到的主密钥结合协商确定的 MAC 算法对所有按序排列的握手消息进行计算得到的散列值。这里可以体会下 CilentHello、ServerHello 这些握手消息中的随机数的作用。

Reference

《HTTPS 权威指南》
《IPSec VPN 实战指南》
https://tools.ietf.org/html/rfc5246

Nginx以HTTP反向代理HTTPS服务

简单记录一下 Nginx 作为反向代理以 HTTP 协议向下游客户端代理使用 Spring Security 实现的上游 HTTPS 服务时遇到的问题及解决办法。

背景

有个基于 Spring Security、Spring MVC 实现的 HTTPS Web 应用,通过 Nginx 作为反向代理向外提供服务。
Nginx 和 Web 应用部署在同一台机器,IP 为 10.115.6.165。Web 应用以 HTTPS 协议监听在端口 19026。
如果是 Windows 环境,推荐在 http://nginx-win.ecsds.eu/ 下载编译带入了更多模块的 Nginx,因为在下面我们会需要 headers more 模块中的 more_set_headers 指令。如果是 Linux 环境,也请确认 headers more 模块的 more_set_headers 指令可用。

Nginx 以 HTTP 的方式反向代理。

用以下配置运行 Ngnix, 使其用 HTTP 协议在 9080 端口反向代理 19026 上的 HTTPS 服务。

server {
    listen       9080;
    server_name  10.115.6.165;

    location /databoard/ {
        proxy_pass  https://10.115.6.165:19026/databoard/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header REMOTE-HOST        $remote_addr;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_redirect off;
    }
}

但是如果我们用浏览器访问http://10.115.6.165:9080/databoard/login,就会发现如下图所示的两问题:

1)后端服务使用 redirect 重定向导致的问题

浏览器地址栏上显示被重定向到了https://10.115.6.165/databoard/dataCmder。这是因为后端Web应用执行了redirect重定向语句,而重定向的协议、地址是基于web应用上下文的,而nginx并没有做特别的处理就转发给了浏览器,浏览器自然不能访问到这个地址。解决办法如下:

map $upstream_http_Location $location {
  ~https://10.115.6.165/(?<param>.*) http://10.115.6.165:9080/$param;
  default $upstream_http_Location;
}

server {
    ... ...
    location /databoard/ {
        ... ...
        more_set_headers -s '301 302' 'Location $location';

2)Cookie 携带 Secure 属性导致浏览器不能在请求中携带 SessionID 的问题。

有 Secure 属性的 Cookie 意味着如果浏览器不是用 HTTPS 与服务建立链接,那么这个 cookie 里的值不会随请求一起向服务器发送。所以要解决这个问题就需要在 Nginx 中把 cookie 中的 Secure 属性去掉再传给浏览器。解决办法如下:

map $sent_http_set_cookie $resp_cookie {
    ~*(?<CK_WITHOUT_SECURE>.+)Secure $CK_WITHOUT_SECURE;
}

server {
    ... ...
    location /databoard/ {
        ... ...
        more_set_headers 'Set-Cookie: $resp_cookie';

完整的相关配置

map $upstream_http_Location $location {
  ~https://10.115.6.165/(?<param>.*) http://10.115.6.165:9080/$param;
  default $upstream_http_Location;
}

map $sent_http_set_cookie $resp_cookie {
    ~*(?<CK_WITHOUT_SECURE>.+)Secure $CK_WITHOUT_SECURE;
}

server {
    listen       9080;
    server_name  10.115.6.165;

    location /databoard/ {
        proxy_pass  https://10.115.6.165:19026/databoard/;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header REMOTE-HOST        $remote_addr;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_redirect off;

        more_set_headers -s '301 302' 'Location $location';
        more_set_headers 'Set-Cookie: $resp_cookie';
    }
}

Nginx 以 HTTPS 的方式反向代理。

如果 nginx 是以 HTTPS 协议向外提供反向代理,那么无论使用七层代理还是四层代理配置起来都很简单,如下:

3)四层反向代理。

stream {
    upstream databoardServer {
        hash $remote_addr consistent;
        server 10.115.6.165:19026 weight=5;
    }

    server {
        listen 9082;
        proxy_connect_timeout 1s;
        proxy_timeout 3s;
        proxy_pass databoardServer;
    }
}

4)七层反向代理。

server {
    listen       443 ssl;
    server_name  10.115.6.165;
    ssl_certificate D:\\tmp\\opensslCrt\\demoAppChain.crt;
    ssl_certificate_key D:\\tmp\\opensslCrt\\demoApp.key;

    location / {
        proxy_pass https://10.115.6.165:19026;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Referer https://10.115.6.165;
    }
}

当时想直接配置 Spring Web 应用不让 Cookie 带上 Secure 属性,这样就不用在 Nginx 上处理 Cookie 了。于是直接修改配置 application.properties 如下,但是返回给 nginx 的 Cookie 还是带 Secure 属性。

server.session.cookie.secure=true

通过跟踪 Spring 源码发现,只要应用是运行在 HTTPS 协议下的,那么就会让生成的 Cookie 是 Secure 的。

创建基于HTTPS的服务

关于私钥、公钥的生成

https://www.cnblogs.com/charlesblc/p/6130433.html
https://blog.csdn.net/qq_41259576/article/details/91356419

KeyStore vs OpenSSL

KeyStore with keytool

KeyStor 有三种类型(格式):JKS, PKCS12 。。

1. 创建一个自签名证书和密钥

keytool -genkeypair -keystore demoAppKeyStore.jks \
-alias demoAppServer \
-keyalg RSA \
-keysize 3072 \
-validity 36500 \
-storepass demoApp

2. 导出证书

keytool -export -alias demoAppServer -file demoApp.crt -keystore demoAppKeyStore.keystore

1. 创建一个自签名证书和密钥

1. 创建一个自签名证书和密钥

1. 创建一个自签名证书和密钥

docker run –name nginx -v ~/devEnv/dockerSrvStorage/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx

什么是跨站请求伪造

例子:A 是受害用户,B 是银行,C 是恶意代码。C 利用浏览器里存在的有效 A 到 B 的 session(cookie)来伪造请求。再加上利用 XSS 漏洞,用户可以子在无感知的情况下被伤害。
Cross-Site Scripting (XSS)

https://docs.spring.io/spring-security/site/docs/5.4.5/reference/html5/#csrf-explained

Reference:

What is keystore: https://stackoverflow.com/questions/23202046/what-is-keystore
openssl vs keystore: https://security.stackexchange.com/questions/98282/difference-between-openssl-and-keytool

文件管理服务

这个服务基于 FastDFS 实现文件的上传、下载、删除,文件元数据维护的功能。

FastDFS 官方客户端地址:https://github.com/happyfish100/fastdfs-client-java
简单的关于 fastdfs-client-java 使用 demo:https://cloud.tencent.com/developer/article/1407660

一些关于使用 FastDFS 的文档:https://blog.csdn.net/dingcai12003/article/details/96046404
有些库又做了二次封装(比如 com.github.tobato:fastdfs-client),但是感觉官方客户端库里已经有了简单的连接管理,所以小并发量使用看不到再池化的意义。

项目使用 Gradle 做构建工具,在 Gradle 仓库中找不到 FastDFS 的库。因此要把 mavenLocal()设置到 repositories 里。
如果手工安装特定 FastDFS 库到本地 maven 仓库,可以执行如下命令:

git clone git@github.com:happyfish100/fastdfs-client-java.git
cd fastdfs-client-java
git fetch --all --tags
git checkout tags/V1.28 -b V1.28
mvn clean source:jar install

mkdir -p /home/devops/dockerSrvStorage/mysql
docker run –name mysql -e MYSQL_ROOT_PASSWORD=password -p 3306:3306
–restart=always
-v /home/devops/dockerSrvStorage/mysql/conf.d:/etc/mysql/conf.d
-v /home/devops/dockerSrvStorage/mysql/logs:/logs
-v /home/devops/dockerSrvStorage/mysql/data/mysql:/var/lib/mysql
-d mysql:8.0.22
–character-set-server=utf8mb4
–collation-server=utf8mb4_unicode_ci

官方 MySQL docker 文档:https://hub.docker.com/_/mysql

在Ubuntu20上安装FastDFS环境

应用中一般会有文件上传、下载相关业务。这里通过使用 FastDFS 构建一个简单的分布式文件系统满足一般的需求。实际项目中分布式文件系统的选择根据业务需求、团队技术栈具体选择。

安装环境依旧是利用 KVM 虚拟机完成。开发环境使用 FastDFS 的单机部署就可以。生成环境使用分布式部署提高性能及可靠性。
单机模式就是把 tracker 和 storage 都安装在同一台机器上,虽然 IP 相同但是可以通过使用不同 port 来在单机实习集群功能。
分布式部署就是在不同机器安装安装不同的 Tracker、Storage 节点。

1. 构建 Ubuntu20 虚拟机,用于安装 FastDFS。

虚拟机设置

如前几篇文章一样,在 virt-infra-ansible/inventory 目录下设置 FastDFS 服务器配置。因为这是用于原型验证,所以这里硬盘配置仅仅是 20G。给每个虚拟机设置 mac 是为了对 IP 进行绑定、DNS 设置。
因为安装 FastDFS 需要编译环境,所以通过 virt_infra_disk_cmd 让虚拟机预先执行必要安装:

apt -y install git gcc g++ make automake autoconf libtool pcre2-utils libpcre2-dev libpcre3 libpcre3-dev  zlib1g zlib1g-dev openssl libssh-dev wget vim

所以

fastDfsServers:
  hosts:
    dfsServer0:
      ansible_python_interpreter: /usr/bin/python3
      virt_infra_networks:
        - name: br0
          type: bridge
          mac: "52:54:00:81:45:10"
    dfsServer1:
      ansible_python_interpreter: /usr/bin/python3
      virt_infra_networks:
        - name: br0
          type: bridge
          mac: "52:54:00:81:45:11"
    dfsServer2:
      ansible_python_interpreter: /usr/bin/python3
      virt_infra_networks:
        - name: br0
          type: bridge
          mac: "52:54:00:81:45:12"
  vars:
    virt_infra_state: running
    virt_infra_distro: ubuntu
    virt_infra_distro_image: focal-server-cloudimg-amd64.img
    virt_infra_autostart: yes
    ansible_python_interpreter: /usr/bin/python3
    virt_infra_timezone: "Asia/Shanghai"
    virt_infra_root_password: password
    virt_infra_disk_size: 20
    virt_infra_ram: 4094
    virt_infra_ram_max: 8192
    virt_infra_cpus: 4
    virt_infra_cpus_max: 8
    virt_infra_networks:
      - name: br0
        type: bridge
    virt_infra_disk_cmd:
      - apt update
      - apt install -y net-tools
      - apt -y install git gcc g++ make automake autoconf libtool pcre2-utils libpcre2-dev libpcre3 libpcre3-dev  zlib1g zlib1g-dev openssl libssh-dev wget vim

一键创建虚拟机

依旧是按照老套路创建虚拟机。登录 KVM HOST,执行:

cd virt-infra-ansible
ansible-playbook ./virt-infra.yml --limit kvmhost,fastDfsServers

设置 DHCP 服务绑定 mac 和 IP,设置 DNS 服务绑定 IP 和域名

因为这里是直接利用无线路由提供的 DHCP 服务,为了绑定 MAC、IP 直接登录无线网关通过 Web 页面进行。

如上篇所述,我们的环境中已经安装了 CoreDNS 服务,所以我们可以为 fastDFS 服务器们设置域名。
因为 virt-infra-ansible 工具会在成功创建虚拟机后把 IP 写入 hosts 文件,所以可通过 KVM HOST 上的 hosts 文件查看刚刚创建出虚拟机的 IP。

登录 repositoryServer,

cd /home/devops/dockerSrvStorage/coredns
vi hostsfile

添加 IP 到机器名/域名的映射。

192.168.0.116 dfsServer0 dfsTracker
192.168.0.104 dfsServer1 dfsStorage0
192.168.0.117 dfsServer2 dfsStorage1

2. 安装 FastDFS

参照官方安装指南 https://github.com/happyfish100/fastdfs/wiki 很容易安装。

创建文件夹 /home/fastdfs 用于保存 fastdfs 管理的文件和日志。
利用目录 /usr/local/src 保存需要编译的源码。
这里 checkout v6.07 版本的 fastDFS 进行安装,其它步骤与官方文档基本一直。

cd /usr/local/src
git clone https://github.com/happyfish100/fastdfs.git --depth 1
cd fastdfs/
git fetch --all --tags
git checkout tags/V6.07 -b V6.07
git pull
./make.sh && ./make.sh install

3. 启动 FastDFS

启动 tracke、storage 服务:

/etc/init.d/fdfs_trackerd start
/etc/init.d/fdfs_storaged start

查看 fastDFS 服务状态:

/usr/bin/fdfs_monitor /etc/fdfs/storage.conf

设置开机自启动

vi /lib/systemd/system/rc-local.service 并添加如下内容:

[Install]
WantedBy=multi-user.target
Alias=rc-local.servic

创建/etc/rc.local,并添加如下内容:

touch /etc/rc.local
chmod +x /etc/rc.local

#!/bin/sh

/etc/init.d/fdfs_trackerd start
/etc/init.d/fdfs_storaged start

4. 测试 fastDFS

修改 /etc/fdfs/client.conf 中如下配置:

base_path=/home/fastdfs
tracker_server=dfsServer0

测试上传

fdfs_upload_file /etc/fdfs/client.conf /usr/local/src/nginx-1.15.4.tar.gz

Reference:

官网:https://github.com/happyfish100/fastdfs/
一般性介绍:https://www.jianshu.com/p/1c71ae024e5e
FastDFS 在 Ubuntu 的安装配置: https://zhuanlan.zhihu.com/p/29133587

安装CoreDNS、GitLab、Jenkins

在之前创建的 repository server 上安装下面的服务。

1)搭建 CoreDNS 服务

先从 Docer Hub 上拉取 CoreDNS 镜像。

docker pull coredns/coredns:1.8.3

停止 Ubuntu 上默认启动的 DNS 服务。

在下载 coredns 镜像之前先不要停止 DNS 服务,否则解析不到 docker 镜像仓库服务器。

sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved

在目录 /home/devops/dockerSrvStorage/coredns 下创建 2 个文件 Corefile hostsfile,内容如下:

192.168.0.1 是家里无线路由器的 IP 地址。我这里用的是 TL-WDR7650 千兆易展无线路由。

# ls
Corefile  hostsfile
# cat Corefile
.:53 {
    hosts /etc/coredns/hostsfile {
        fallthrough
    }
    forward . 192.168.0.1:53
    log
}

把之前创建的 Guest 虚拟机 IP 都加进来。

# cat hostsfile
devops@repositoryServer:~/dockerSrvStorage/coredns$ cat hostsfile
192.168.0.114 repositoryServer dnsServer gitlab jenkins gitlab.telbox.cn jenkins.telbox.cn
192.168.0.112 devopServer0
192.168.0.113 devopServer1
192.168.0.115 devopServer2

192.168.0.106 k8s-master-0
192.168.0.107 k8s-node-0
192.168.0.109 k8s-node-1
192.168.0.108 k8s-node-2

192.168.0.114 basehost0
192.168.0.111 ubuntu20Server

运行 CoreDNS 镜像

docker run -it -d --net=host \
--name=coredns --restart=always \
-v /home/devops/dockerSrvStorage/coredns:/etc/coredns/ \
coredns/coredns:1.8.3 \
-conf /etc/coredns/Corefile

备注:
–net=host 要指定,如果不指定,在同宿主机的容器中无法查询 DNS。如果指定了该项,则无须指定-p 选项。默认使用宿主机的端口。

Reference:

BIND, Dnsmasq 等常见 DNS Server(却不包含 CoreDNS):
https://computingforgeeks.com/bind-vs-dnsmasq-vs-powerdns-vs-unbound/
安装 Core DNS Docker:
https://blog.csdn.net/weixin_36938307/article/details/105390004

2)GitLab

各种 docker 服务一般都需要持久化映射,以方便配置、数据的保存。本地使用 /home/devops/dockerSrvStorage 作为各种应用存储的根目录。
其中参数 –hostname gitlab.telbox.cn 定义了用户访问 gitlab 服务的主机域名

mkdir -p /home/devops/dockerSrvStorage/gitlab
sudo docker pull gitlab/gitlab-ce:13.9.2-ce.0

sudo docker run --detach \
--hostname gitlab.telbox.cn \
--publish 9181:443 --publish 9180:80 --publish 9182:22 \
--name gitlab \
--restart always \
--volume /home/devops/dockerSrvStorage/gitlab/config:/etc/gitlab \
--volume /home/devops/dockerSrvStorage/gitlab/logs:/var/log/gitlab \
--volume /home/devops/dockerSrvStorage/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:13.9.2-ce.0

可修改 /home/devops/dockerSrvStorage/gitlab/config 里的配置文件。如有需要,可进入 GitLab 容器进行配置。

sudo docker exec -it gitlab bash
cd /opt/gitlab/embedded/service/gitlab-rails/config
vi gitlab.yml

执行命令 gitlab-ctl reconfigure 使之生效

sudo docker exec gitlab gitlab-ctl reconfigure

第一次访问安装好的 GitLab 服务http://gitlab.telbox.cn:9180/需要设置root用户的口令。

Reference:
https://www.jianshu.com/p/080a962c35b6
https://zhuanlan.zhihu.com/p/63786567

3)Jenkins

mkdir -p /home/devops/dockerSrvStorage/jenkins


sudo docker run -p 9183:8080 -p 9184:50000 \
--name jenkins -d \
-v /home/devops/dockerSrvStorage/jenkins:/var/jenkins_home \
-v /home/devops/dockerSrvStorage/jdk/java-se-8u41-ri:/var/jenkins_home/tools/jdk/java-se-8u41-ri \
-v /home/devops/dockerSrvStorage/jdk/jdk-11.0.2:/var/jenkins_home/tools/jdk/jdk-11.0.2 \
-v /home/devops/dockerSrvStorage/apache-maven-3.6.3:/var/jenkins_home/tools/apache-maven-3.6.3 \
jenkins/jenkins:lts

上面通过-v 的方式把 JDK 和 Maven mount 给 Jenkins 容器,之后可以通过 Jenkins Tools 配置他们给 Pipline 使用。
启动后,进入 docker 查看初始密码。首次访问 jenkins.telbox.cn:9183 时会用到这个密码。

sudo docker exec -it jenkins bash
jenkins@e061aa64ed7b:/$ cat /var/jenkins_home/secrets/initialAdminPassword

访问 jenkins.telbox.cn:9183 输入初始密码,选择安装建议的插件。
插件安装完毕后,要求输入 Jenkins URL,这里输入 jenkins.telbox.cn:9183

The Jenkins URL is used to provide the root URL for absolute links to various Jenkins resources. That means this value is required for proper operation of many Jenkins features including email notifications, PR status updates, and the BUILD_URL environment variable provided to build steps.
The proposed default value shown is not saved yet and is generated from the current request, if possible. The best practice is to set this value to the URL that users are expected to use. This will avoid confusion when sharing or viewing links.

如果需要,可以给 Jenkins 配置日志

mkdir -p /home/devops/dockerSrvStorage/jenkins/data
cat > /home/devops/dockerSrvStorage/jenkins/data/log.properties <<EOF
handlers=java.util.logging.ConsoleHandler
jenkins.level=FINEST
java.util.logging.ConsoleHandler.level=FINEST
EOF
docker run --name myjenkins -p 8080:8080 -p 50000:50000 --env JAVA_OPTS="-Djava.util.logging.config.file=/var/jenkins_home/log.properties" -v `pwd`/data:/var/jenkins_home jenkins

如果 Jenkins 插件安装比较慢,可以使用国内插件镜像源,参考https://blog.csdn.net/oYinHeZhiGuang/article/details/104867525。

Reference:
https://github.com/jenkinsci/docker/blob/master/README.md
https://www.cnblogs.com/xiaoqi/p/docker-jenkins-cicd.html

设置Ubuntu Guest使用aliyun apt镜像源

本文记录如何更改 disk-create.yml 来让 Ubunt Guest 使用阿里云镜像作为 apt source。最终的命令形式如下:

ansible-playbook ./virt-infra.yml --limit kvmhost,ubuntu20Server --extra-vars change_to_ali_ubuntu_source=true

通过在命令行里设置参数 change_to_ali_ubuntu_source=yes 来创建虚拟机的时候进行 ali ubunt apt source 的设置。

1)禁止使用 could init 设置默认的 apt sources

- name: Do not use cloud source
  command: >
    virt-customize
    -a {{ hostvars[groups['kvmhost'][0]].virt_infra_host_image_path | default(virt_infra_host_image_path) }}/{{ inventory_hostname }}-boot.qcow2
    --run-command "echo 'apt_preserve_sources_list: true' >> /etc/cloud/cloud.cfg"
  register: result_disk_cmd
  retries: 2
  delay: 2
  until: result_disk_cmd is succeeded
  become: true
  when:
    - inventory_hostname not in groups['kvmhost']
    - inventory_hostname not in hostvars[groups['kvmhost'][0]].result_all_vms.list_vms
    - virt_infra_state != "undefined"
    - change_to_ali_ubuntu_source is defined and change_to_ali_ubuntu_source
  delegate_to: "{{ groups['kvmhost'][0] }}"

2)使用 aliyun 的 ubuntu apt source 镜像。

- name: Setup Aliyun Source
  command: >
    virt-customize
    -a {{ hostvars[groups['kvmhost'][0]].virt_infra_host_image_path | default(virt_infra_host_image_path) }}/{{ inventory_hostname }}-boot.qcow2
    --run-command 'mv /etc/apt/sources.list /etc/apt/sources.list.bak'
    --run-command 'echo "deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs) main multiverse restricted universe" >> /etc/apt/sources.list'
    --run-command 'echo "deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-backports main multiverse restricted universe" >> /etc/apt/sources.list'
    --run-command 'echo "deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-proposed main multiverse restricted universe" >> /etc/apt/sources.list'
    --run-command 'echo "deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-security main multiverse restricted universe" >> /etc/apt/sources.list'
    --run-command 'echo "deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-updates main multiverse restricted universe" >> /etc/apt/sources.list'
    --run-command 'echo "deb-src http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs) main multiverse restricted universe" >> /etc/apt/sources.list'
    --run-command 'echo "deb-src http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-backports main multiverse restricted universe" >> /etc/apt/sources.list'
    --run-command 'echo "deb-src http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-proposed main multiverse restricted universe" >> /etc/apt/sources.list'
    --run-command 'echo "deb-src http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-security main multiverse restricted universe" >> /etc/apt/sources.list'
    --run-command 'echo "deb-src http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-updates main multiverse restricted universe" >> /etc/apt/sources.list'
  register: result_disk_cmd
  retries: 2
  delay: 2
  until: result_disk_cmd is succeeded
  become: true
  when:
    - inventory_hostname not in groups['kvmhost']
    - inventory_hostname not in hostvars[groups['kvmhost'][0]].result_all_vms.list_vms
    - virt_infra_state != "undefined"
    - change_to_ali_ubuntu_source is defined and change_to_ali_ubuntu_source
  delegate_to: "{{ groups['kvmhost'][0] }}"

3)删除已存在的 Guest 并重新创建。这个虚拟使用 Ali 源并跟随宿主机启动时自动启动。

[devops@localhost virt-infra-ansible]$ ansible-playbook ./virt-infra.yml --limit kvmhost,ubuntu20Server --extra-vars virt_infra_state=undefined
[devops@localhost virt-infra-ansible]$ ansible-playbook ./virt-infra.yml --limit kvmhost,ubuntu20Server --extra-vars change_to_ali_ubuntu_source=true --extra-vars virt_infra_autostart=yes
  1. 安装 Docker 环境

    [devops@localhost virt-infra-ansible]$ ansible-playbook ./install_docker.yml –limit ubuntu20Server -vv –extra-vars docker_version=5:20.10.43-0ubuntu-focal

参考:

关于 virt-customize 命令
https://libguestfs.org/virt-customize.1.html
https://ywnz.com/linuxjc/5680.html

关于 Ansible yaml 中 command
https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html

在基于KVM的环境中添加一组开发服务器

好久没动 K8S,DevOps,准备折腾个 devops 的环境。考虑到 CentOS 未来的命运,这里使用 Ubuntu 20. 先在 KVM Host 上下载 ubuntu 并 copy 到目录/var/lib/libvirt/images/。

在 KVM HOST 上准备 Ubuntu20 的镜像

curl -O http://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
mv -iv focal-server-cloudimg-amd64.img /var/lib/libvirt/images/

在文件 virt-infra-ansible/inventory/devopServers.yml 中定义待创建的 devop servers 虚拟机参数。

进入目录 virt-infra-ansible/inventory,创建文件

参考:https://dhyuan.github.io/2020/09/28/devops/create_k8s_by_kubeadm/ 。对,本文就是在这个环境的基础上再添加 4 个与 degvops 相关的服务器。其中 repositoryServer 用于安装 Nexus、Harbor、GitLab 等。devopsServer0-2 用于其它 devops 相关服务。
需注意的是 focal 的 python 版本是 3,所以设置 /usr/bin/python3,考虑到 repositoryServer 的用途,其磁盘容量设为 500G。
… virt-infra-ansible/inventory/devopServers.yml,内容如下:

devopServers:
    hosts:
        repositoryServer:
        virt_infra_state: running
        virt_infra_distro: ubuntu
        virt_infra_distro_image: focal-server-cloudimg-amd64.img
        ansible_python_interpreter: /usr/bin/python3
        virt_infra_timezone: "Asia/Shanghai"
        virt_infra_disk_size: 500
        virt_infra_disk_cmd:
            - apt update
            - apt install -y net-tools
            - apt install -y lsb
            - useradd -m devops
            - echo "devops:password"| chpasswd
        devopsServer[0:2]:
        virt_infra_state: running
        virt_infra_distro: ubuntu
        virt_infra_distro_image: focal-server-cloudimg-amd64.img
        ansible_python_interpreter: /usr/bin/python3
        virt_infra_timezone: "Asia/Shanghai"
        virt_infra_disk_cmd:
            - apt update
            - apt install -y net-tools
            - apt install -y lsb
            - useradd -m devops
            - echo "devops:password"| chpasswd
    vars:
        virt_infra_root_password: password
        virt_infra_disk_size: 80
        virt_infra_ram: 4094
        virt_infra_ram_max: 8192
        virt_infra_cpus: 4
        virt_infra_cpus_max: 8
        virt_infra_state: running
        virt_infra_networks:
        - name: br0
            type: bridge

一键创建 devop servers 虚拟机

cd virt-infra-ansible
ansible-playbook ./virt-infra.yml --limit kvmhost,devopServers

如果出现问题,可删除刚才创建的 guest 虚拟机

通过使用参数 –extra-vars virt_infra_state=undefined 来删除。比如:

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

一键在 devop servers 节点上安装 docker

在 Ubuntu 环境中安装 docker,
1)添加 Docker 的阿里云源。
在文件 ansible-role-docker-ce/tasks/setup-repository-Ubuntu.yml 尾部设置 aliyun source。

- name: Add Aliyun Docker official GPG key
become: true
apt_key:
url: http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg
state: present
register: \_pkg_result
until: \_pkg_result is succeeded
when:

- docker_network_access | bool
- (\_docker_os_dist == "Ubuntu" and \_docker_os_dist_major_version | int > 14) or
    (\_docker_os_dist == "Debian" and \_docker_os_dist_major_version | int > 7)

- name: Add Aliyun Docker CE repository with correct channels (Ubuntu/Debian)
become: true
lineinfile:
path: /etc/apt/sources.list.d/docker-ce.list
line: "deb [arch={{ _docker_os_arch|lower }}] http://mirrors.aliyun.com/docker-ce/linux/{{ \_docker_os_dist|lower }} {{ ansible_lsb.codename }} stable"
insertbefore: BOF

2)找到适合自己 Ubuntu 版本的 Docker 版本。
可用命令 apt-cache madison docker-ce 查看。

root@basehost0:~# apt-cache madison docker-ce
docker-ce | 5:20.10.4~3-0~ubuntu-focal | http://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
docker-ce | 5:20.10.4~3-0~ubuntu-focal | https://download.docker.com/linux/ubuntu focal/stable amd64 Packages
docker-ce | 5:20.10.3~3-0~ubuntu-focal | http://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
docker-ce | 5:20.10.3~3-0~ubuntu-focal | https://download.docker.com/linux/ubuntu focal/stable amd64 Packages
docker-ce | 5:20.10.2~3-0~ubuntu-focal | http://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
。。。 。。。

3)在 Ubuntu Guest 上安装 Docker。
因为 virt-infra-ansible/install_docker.yml 中 docker 版本默认为 19.03.8,并不适用于 Ubuntu20 的环境。
可以通过在命令行指定参数 docker_version 来安装,如下:

    ansible-playbook ./install_docker.yml --limit devopServers -vv --extra-vars docker_version=5:20.10.4~3-0~ubuntu-focal

之前 CentOS7 Guest 上安装 Docker 的命令:

ansible-playbook ./install_docker.yml \
--limit devopServers -v \
-e '{"docker_repository_url": {"Ubuntu focal": " http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" }}'

登录到一台 Ubuntu Guest 机器,查看已安装的 Docker 版本以确认成功。

root@basehost0:~# docker -v
Docker version 20.10.4, build d3cb89e