Google Cloud部署web app到kubernetes并使用let’s encrypt和nginx-ingress

环境配置

连接cluster

1
gcloud container clusters get-credentials CLUSER_NAME --zone us-central1-a --project PROJECT_NAME

安装helm和tiller

1
2
3
kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
helm init --service-account tiller

安装nginx-ingress

1
helm install --name nginx-ingress stable/nginx-ingress --set rbac.create=true --set controller.publishService.enabled=true

安装let’s encrypt

1
2
kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.8/deploy/manifests/00-crds.yaml
kubectl create -f issuer.yaml

issuer.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
# The ACME server URL
server: https://acme-v02.api.letsencrypt.org/directory
# Email address used for ACME registration
email: youremail@yourdomain.com
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-prod
# Enable the HTTP-01 challenge provider
http01: {}

配置DNS

获取nginx-ingress-controller的IP

1
kubectl get svc -n default

output:

1
2
3
4
NAME                            TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
kubernetes ClusterIP 10.64.0.1 <none> 443/TCP 64m
nginx-ingress-controller LoadBalancer 10.64.11.170 35.188.93.188 80:30393/TCP,443:30515/TCP 59m
nginx-ingress-default-backend ClusterIP 10.64.5.162 <none> 80/TCP 59m

将你的DNS记录指向 EXTERNAL-IP -> 35.188.93.188

部署web项目

创建namespace

1
2
kubectl create ns demo
kubectl config set-context $(kubectl config current-context) --namespace=demo

部署项目

Deployment

1
kubectl apply -f deployment.yaml

deplyment.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: api-deployment
labels:
app: api
spec:
serviceName: api-app
replicas: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
terminationGracePeriodSeconds: 30
containers:
- image: tomcat:8.5.45-jdk8-openjdk-slim
imagePullPolicy: "Always"
name: api
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 150
periodSeconds: 5
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 150
periodSeconds: 5

Service

1
kubectl apply -f service.yaml

service.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: api-service
labels:
app: api
spec:
type: ClusterIP
selector:
app: api
ports:
- protocol: TCP
port: 80
targetPort: 8080

Ingress

1
kubectl apply -f ingress.yaml

ingress.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: name-virtual-host-ingress
annotations:
kubernetes.io/ingress.class: nginx
certmanager.k8s.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls: # < placing a host in the TLS config will indicate a cert should be created
- hosts:
- demo.w2x.me
secretName: letsencrypt-prod
rules:

- host: demo.w2x.me
http:
paths:
- backend:
serviceName: api-service
servicePort: 80

Links

Zsh命令行历史同步插件推荐

公司和家里用的是两台电脑,经常遇到一些用过却记不住的命令,恰巧这些命令在另外台电脑执行过,就想找个能在不同机器上同步命令行历史的插件。

尝试过将用户目录文件夹下的.zsh_history文件软链接到dropbox,发现还是有点问题,比如多台机器同步是会发生文件冲突,一些情况下zsh会重新创建新的.zsh_history文件,导致数据丢失。期间又尝试了下history-sync这个插件,发现有会将历史文件清空的BUG。

无意中发现了zsh-histdb,这个插件正好满足了我的要求:在多台电脑之间同步历史记录,自动合并冲突文件。配合zsh-autosuggestions简直完美。

Docker环境访问fontello.com无限重定向问题

前阵子要把公司几个年代比较久远的前端项目迁移到新的jenkins环境,由于这些项目打包依赖库的版本比较老,要配置好环境会比较花时间,以后再迁移环境的话还要在搭建一遍,于是就想着用docker来打包项目。

本地搭建好docker环境后,测试打包的时候发现项目从fontello.com下载字体是老是会报一个超出最大重定向次数的异常,ssh到docker环境发现 wget -vdS fontello.com 返回的Response状态码一直是302,而本机环境和服务器都没这问题,docker环境wget其他网址也没有问题,一开始以为是fontello网站配置的问题,今天突然冒出一个想法会不会是我docker环境配置了代理的原因,于是将 fontello.com 域名加到了代理白名单里,发现就没有重定向异常了,字体文件能正常下载了。

这个问题的根本原因很有可能是docker代理功能和 fontello.com 两边的问题,在这里记一下,以防下次再遇到类似问题。

计算机网络(第七版)习题4-13计算过程

网上计算机网络的习题答案不是复制黏贴就是排版糟糕,简直不是给人看的。今天做这题目花了不少时间,就顺便把计算过程记录下来,希望帮助到对这题有疑惑的人。

包含IP部分,首部长度正好为为5个4字节。

  1. 现将数据转换成二进制格式
1
2
3
4
5
0100 0101 0000 0000 0000 0000 0001 1100
0000 0000 0000 0001 0000 0000 0000 0000
0000 0100 0001 0001
0000 1010 0000 1100 0000 1110 0000 0101
0000 1100 0000 0110 0000 0111 0000 1001
  1. 计算 0100 0101 0000 0000 + 0000 0000 0001 1100
1
2
3
4
5
0100 0101 0000 0000
+
0000 0000 0001 1100
=
0100 0101 0001 1100
  1. 计算 0100 0101 0001 1100 + 0000 0000 0000 0001
1
2
3
4
5
0100 0101 0001 1100
+
0000 0000 0000 0001
=
0100 0101 0001 1101
  1. 计算 0100 0101 0001 1101 + 0000 0000 0000 0000
1
2
3
4
5
0100 0101 0001 1101
+
0000 0000 0000 0000
=
0100 0101 0001 1101
  1. 计算0100 0101 0001 1101 + 0000 0100 0001 0001
1
2
3
4
5
0100 0101 0001 1101
+
0000 0100 0001 0001
=
0100 1001 0010 1110
  1. 计算 0100 1001 0010 1110 + 0000 1010 0000 1100
1
2
3
4
5
0100 1001 0010 1110
+
0000 1010 0000 1100
=
0101 0011 0011 1010
  1. 计算 0101 0011 0011 1010 + 0000 1110 0000 0101
1
2
3
4
5
0101 0011 0011 1010
+
0000 1110 0000 0101
=
0110 0001 0011 1111
  1. 计算 0110 0001 0011 1111 + 0000 1100 0000 0110
1
2
3
4
5
0110 0001 0011 1111
+
0000 1100 0000 0110
=
0110 1101 0100 0101
  1. 计算 0110 1101 0100 0101 + 0000 0111 0000 1001
1
2
3
4
5
0110 1101 0100 0101
+
0000 0111 0000 1001
=
0111 0100 0100 1110
  1. 将上面的结果取反得出:
1
1000 1011 1011 0001

AWS Cloudformation 踩坑记录

最近用cloudformation时遇到了一些坑,在此记录一下。

Conditions变量无法直接当布尔类型使用

Conditions可以动态控制一些资源的创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Conditions: 
CreateProdResources: !Equals [ !Ref EnvType, prod ]
Resources:
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
MountPoint:
Type: "AWS::EC2::VolumeAttachment"
Condition: CreateProdResources
Properties:
InstanceId:
!Ref EC2Instance
VolumeId:
!Ref NewVolume
Device: /dev/sdh
NewVolume:
Type: "AWS::EC2::Volume"
Condition: CreateProdResources
Properties:
Size: 100
AvailabilityZone:
!GetAtt EC2Instance.AvailabilityZone

但是如果在其他地方像这样使用会报Unresolved resource dependencies CreateResources错误

1
2
3
4
5
6
7
8
/xxx/xx/xx.conf:
source:
!Sub https://${bucket}.s3.amazonaws.com/${prefix}/xx.conf
context:
create_resources: !Ref CreateResources
mode: "000644"
owner: root
group: root

解决办法就时将create_resources: !Ref CreateResources 改成 create_resources: !If [CreateResources, "true output ", "false output"]

aws-resource-init-files 传递布尔变量

AWS::CloudFormation::Init中 files属性可以用来创建文件,filescontext属性可以传递变量,来动态生成文件内容.
AWS文档里说文件是通过类似mustache的方式来处理的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
A typical Mustache template:

Hello {{name}}
You have just won {{value}} dollars!
{{#in_ca}}
Well, {{taxed_value}} dollars, after taxes.
{{/in_ca}}
Given the following hash:

{
"name": "Chris",
"value": 10000,
"taxed_value": 10000 - (10000 * 0.4),
"in_ca": true
}
Will produce the following:

Hello Chris
You have just won 10000 dollars!
Well, 6000.0 dollars, after taxes.

这里有个坑就是布尔类型在cloudformation里时当做字符类型来传递的,AWS官方认为这是一个feature而不是bug: What you have observed is a known behaviour with the CloudFormation servie
例如:

xxx.conf

1
2
3
4
5
6
7
{{#create_resources}}
this is true
{{/create_resources}}

{{^create_resources}}
this is false
{{/create_resources}}

yaml配置文件

1
2
3
4
5
6
7
8
9
/xxx.conf:
authentication: S3BucketAccess
source:
!Sub https://${bucket}.s3.amazonaws.com/${prefix}/xxx.conf
context:
create_resources: !If [CreateResources, true, false]
mode: "000644"
owner: root
group: root

这里无论 CreateResourcestrue 还是 false,文件内容始终会是 this is true,因为配置文件里的 truefalse 都被转成了字符类型。

这里的解决办法比较hack,就是将!If [CreateResources, true, false]改成!If [CreateResources, [1], []]:

1
2
3
4
5
6
7
8
9
/xxx.conf:
authentication: S3BucketAccess
source:
!Sub https://${bucket}.s3.amazonaws.com/${prefix}/xxx.conf
context:
create_resources: !If [CreateResources, [1], []]
mode: "000644"
owner: root
group: root

这里主要参考了 mustache 文档里 Inverted Sections : https://mustache.github.io/mustache.5.html

aws-resource-init 资源更新问题

aws-resource-init-sources可以用来下载压缩文件并解压到EC2指定目录,但这里有个问题就是如果目标文件夹已存在,cloudformation会将两文件夹内容合并而不是替换文件夹!比如AWS部署的项目使用的依赖LibraryA 1.0,将library升级到Library A 1.1并通过cloudformation source下载并解压新本版的项目到目标目录后,目标目录的项目文件夹里会同时存在Library A1.01.1版本。

解决办法也很简单,在用sources下载前将目标文件夹移除。

AWS平台的服务总有一些奇怪的限制或bug,调试时总会花不少时间,在此记录一下,希望能帮助到遇到相同问题的人,节约大家时间。

Update

  • 2019-07-07 添加aws-resource-init资源更新问题

Deskmini 310 黑苹果折腾记

家里的台机装了manjaro,用了一阵子感觉还是不顺手,加上苹果电脑是越来越贵了,所以有了弄一台黑苹果的想法。

期间看了不少有关黑苹果的资料,发现最简单的方法就是按照别人成功的硬件配置和EFI来安装,最终方案是按照 asrock_deskmini310_hackintosh 买的硬件:

型号 价格(RMB)
主板/机箱 H3101 969
CPU I7 8700 2299
内存 金士顿 DDR4 2666 16G 笔记本内存 x2 1228
SSD Intel 760p 512GB 579
无线/蓝牙 DW1560/BCM94352Z WIFI/Bluetooth module mini PCIE/NGFF M2 268
天线 NGFF M2无线网卡转接线天线 IPX4代转SMA线DeskMini华硕H110 X370 18
DP转VGA DP转VGA转换器 75

安装过程中走了不少弯路,这里记载一下:

  1. 由于我买来的主板BIOS版本是3.4版本的,直接安装会报一个nvme错误,想着升级一下BIOS能不能解决这问题,升级到4.1版本后发现还是有这错误,解决方案是要用clover configurator编辑下EFI文件,添加DSDT Patch
  2. 这个方案里视频是通过HDMI和DP接口输出的,家里的显示器是VGA接口,装到一半黑屏还以为是EFI文件有问题,最后还是接了电视机的HDMI完成的安装。显示器后来用的这款 绿联(UGREEN)DP转VGA转换器
  3. 按照tonymacx86上的教程来安装,第二次重启安装会报错,发现还是要先将EFI文件复制到安装镜像里才能安装成功,安装过程中可能会有panic错误,重启下再次安装就好了。
  4. 网卡和蓝牙买的是DW1560 BCM94352Z ,这里有一点要注意的是要买天线,不装天线的话会搜索不到蓝牙设备和WIFI
  5. 天线安装有点麻烦,我是把网卡拆下来才把天线接上去的

安装视频可以参考这个:

整个安装过程花了差不多七八个小时,不走弯路的话估计一两个小时就能搞定。

用了几天发现两个问题:

  1. 休眠和启动时音箱会有爆音
  2. 从休眠恢复会黑一下屏幕

更新日志

  • 2019-06-16: 更新了发现的问题
  • 2019-08-07: 升级到了10.14.6一切正常

Mac OS配置Dnsmasq+Dnscrypt来解决DNS污染问题

天朝的网络是越来越糟糕了,前阵子访问amazon s3域名居然返回了127.0.0.1。与其天天更新DNS列表,不如直接上DNS白名单模式了。

解决方案就是用Dnsmasq+DnscryptDnsmasq负责监听本地53端口,解析国内域名,将除了国内域名以外的解析请求转发到Dnscrypt监听的端口,Dnscrypt就收到的DNS解析请求转发到国外的DNS服务器。

安装

1
2
brew install dnsmasq
brew install dnscrypt-proxy

配置

Dnsmasq

编辑配置文件

1
2
3
4
5
vim /usr/local/etc/dnsmasq.conf

no-resolv
conf-dir=/usr/local/etc/dnsmasq.d
server=127.0.0.1#5300

dnsmasq会将DNS解析请求转发到dnscrypt监听的5300端口

国内域名文件

执行下面脚本将中国域名DNS信息保存到dnsmasq文件夹

1
2
3
#!/bin/sh

/usr/bin/curl -o /usr/local/etc/dnsmasq.d/accelerated-domains.china.conf https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf

这里用的是电信的DNS服务器,如果你是联通的网络,你可以执行下面的脚本将配置文件中的DNS服务器换成联通的

1
sed -i -e 's/114.114.114.114/221.6.4.66/g' /usr/local/etc/dnsmasq.d/accelerated-domains.china.conf

Dnscrypt

Dnscryp配置 vim /usr/local/etc/dnscrypt-proxy.toml

1
2
3
listen_addresses = ['127.0.0.1:5300', '[::1]:5300']
fallback_resolver = '221.6.4.66:53'
server_names = ['google', 'cloudflare', 'cloudflare-ipv6']

这里本来想配5353端口的,但发现这个端口被chrome占用了。

启动服务

1
2
sudo brew services restart dnsmasq
sudo brew services restart dnscrypt-proxy

重启后将本机DNS指向127.0.0.1即可。

Manjaro自动挂载samba文件夹

Manjaro从地址栏访问samba共享的方式总有点问题,会时不时提示没有文件夹修改权限。最近有时间研究了下,发现命令行挂载的方式比较靠谱。

由于路由器刷的是openwrt,软件源里安装的samba服务端版本似乎是比较老的版本,manjaro命令行挂载似乎用的是较新的samba协议,调了半天发现客户端这边要加下版本号。

挂载命令:

1
2
3
cd /mnt
sudo mkdir share0
sudo mount.cifs //192.168.1.1/share0 /mnt/share0 -o user=SAMBA_USERNAME,pass=SAMBA_PASSWORD,uid=1000,gid=1000,sec=ntlmssp,vers=1.0 --verbose

命令行挂载调通后,要实现开机自动挂载就容易的多了,官方文档提供的多种的挂载方式,这里以systemd为例:

  1. /etc/samba/下建立credentials文件夹:sudo mkdir credentials,创建比如名为share0的文件: sudo vim share0,内容如下:
1
2
username=your samba username
password=your samba password
  1. 创建systemd unit文件:sudo /etc/systemd/system/mnt-share0.mount,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [Unit]
    Description=Mount Share at boot

    [Mount]
    What=//192.168.1.1/share0
    Where=/mnt/share0
    Options=x-systemd.automount,credentials=/etc/samba/credentials/share0,iocharset=utf8,uid=1000,gid=1000,sec=ntlmssp,vers=1.0,rw
    Type=cifs
    TimeoutSec=30
    ForceUnmount=true

    [Install]
    WantedBy=multi-user.target

    Note:这里有一点要注意的是你的挂载路径(where)必须与你的文件名(mnt-share0)对应,比如你挂载到/mnt/share, 那你的文件就必须为mnt-share0.mount

  2. 启用服务sudo systemctl enable mnt-share0.mount

  3. 启动服务sudo systemctl start mnt-share0.mount

到这里samba文件夹就能开机自动挂载,官方还提供了其他的自动挂载方式,详情可参考文档。

参考:

https://wiki.archlinux.org/index.php/samba#As_systemd_unit

Spring Boot连接MongoDB

最近在做的项目用到了Mongo DB,发现在开启用户认证的情况下,用URI的方式连接总是提示认证失败,只有在分别设置了username ,password , database等字段才能连接成功。

1
2
3
4
5
6
7
spring:
data:
mongodb:
authentication-database: ${MONGODB_AUTHENTICATION_DATABASE:admin}
username: ${MONGODB_USERNAME:username}
password: ${MONGODB_PASSWORD:password}
database: ${MONGODB_DATABASE:database}

之前一直认为Spring Boot连接Mongo DB的方式和连JDBC差不多,今天抽空研究了下,发现URI中的数据库名其实是存储用户认证信息的数据库,实际数据库要通过database设定:

1
2
3
4
5
spring:
data:
mongodb:
database: ${MONGODB_DATABASE:database}
uri: ${MONGODB_URI:mongodb://username:password@localhost:27017/authentication_database}

官方文档

/database

Optional. The name of the database to authenticate if the connection string includes authentication credentials in the form of username:password@. If /database is not specified and the connection string includes credentials, the driver will authenticate to the admin database. See also authSource.