sdk开发思考
背景:
为了将一个进程纳入平台管理,对其做了一个代理进程,代理进程负责与sc通信,并负责其生命周期的管理。现为了加强安全管理,对组件进程进行安全加固,接口一律使用https访问。为了其他进程能够与组件进程正常访问,为其他进程提供了用户名,其他进程要去代理进程获得密码然后结合自己的用户名再与组件进程通信。
学习到的几点:
面向接口开发
之间的开发,其实对于接口,抽象类等都没有很具象的概念,对于用法什么的体会都不是很深。也有读过spring的源码,但是对于面向接口开发总是没有抓到最最主要的点。这次开发总算是抓到了。对于功能点,首先要定义好相应的接口,然后搞一个实现出来;一个接口,一个实现,这样搞起来。某个类涉及到一些不是很深很细的细节,那么就可以用接口的组合来实现。那么什么时候用接口的实现类呢?例如,在一个类中,我们用接口的组合实现了这个类的几个方法。在实例化这个类的对象时,就可以传入接口的实现了。在spring源码中,有着大量的DefaultXXXX类,这些类就是接口的实现,然后当真正需要实例化类的时候,再去传入这些实现类。这样的开发方式,就要求在开发伊始,就要站在一个较为抽象的高度对整个工程进行抽象化,而不是一开始就纠结于细节;因为细节然后去调整方法,然后陷入烦人的循环当中。
关于线程
这个sdk开发过程中涉及到2种线程。一个线程的工作结果会涉及到另外一个线程的启动,线程工作结果还涉及到当前线程是继续循环执行还是结束。起初我的思路:信息线程开始工作,如果正常工作结束,那么就结束了。如果中间工作出错,那么就retry;何为retry,其实retry方法的主要作用是,起一个新的任务,然后放入仅有一个线程的线程池的工作队列中(之前开始工作的那个线程就是这个线程池里的唯一线程);但是经过代码检视,这个新任务对象是新建的,不如直接将本任务对象直接加到队列里面去。通过观摩以前别人的代码,实现的sdk是,将仅有一个线程的线程池对象作为任务对象的一个属性,当任务对象被实例化并启动的时候,这个线程池也就被实例化了,然后在这个线程池里面进行任务对象run方法的执行,执行时如果某一个出错需要重新执行,那么就把这个对象(this引用)再提交到线程池里面。如果方法顺利执行完了,关闭线程池即可。这样就实现了一个暂驻内存的线程。
控制权:线程池控制线程生死,是否常驻内存还是暂存,而线程的工作结果却要控制本身是否常驻暂存,也就是说,线程要控制自身生死,那么只能交由线程池控制。如何控制?把线程池作为本身的一个属性,想活就往里面放一个任务,想死就直接调用线程池的shutdown方法。
启示:
线程池想要把自己的周期交由自己的工作结果来控制,那么就借用线程池吧!
一些迷惑点
最大的迷惑点就是,防御式编程对于判空,参数不合法等的一些十分频繁的判断,没进行一步都要进行不信任处理和校验,对于异常结果使用null表示还是异常表示?异常是应该抛出还是由自身捕获?之前写过这样一篇总结,但是没有过进行最佳实践。
- 读取文件内容负责对外返回一定的处理信息。调用方和被调用方之间如果具有一定的契约(例如,约定返回
Null就是出现异常),那么此时调用方可以对异常进行处理而不对外抛出。如果没有明确的约定,那么最佳实践就是抛出异常,在语法层面要求调用方对异常进行处理。
如果有约定,那么可以使用约定,没有约定就在语法层面要求调用方对异常进行处理。
k8s中的节点
###Master
是集群的控制节点,每个集群都有一个master节点来负责整个集群的管理和控制,基本上所有的k8s的控制命令都发给他,他来负责具体的执行过程。占据一个独立的服务器,高可用部署建议用3台服务器,是整个集群的首脑,如果宕机或者不可用,整个集群就会失效。
运行着一组关键进程:
kube-apiserver:提供http rest接口的关键服务进程,是所有资源增删改查的唯一入口,唯一入口,也是集群控制的入口进程。kube-controller-manager,自动化控制中心,可以理解为资源对象的大总管。kube-scheduler,资源调度进程,调度室。
另外,在master上还要启动一个etcd服务,因为所有的资源对象数据都是保存在etcd数据库中的。Node
如果宕机,工作负载会被master自动转移到其他节点上去。
运行着一组进程:kubelet,负责pod对应的容器的创建和启停,同时与master节点密切合作,实现集群管理的基本功能。kube-proxy:实现通信与负载均衡机制的重要组件。docker engine,本机的容器创建和管理工作。
一些命令
查看node
1 | kubectl get nodes |
k8s中的others
volume
定义:pod中能够被多个容器访问的共享目录,共享目录,共享目录。
几个特点:
- 定义在
pod中,然后被一个pod里的多个容器挂在到具体的目录下; - 生命周期与
pod相同,与容器的生命周期不相关; - 容器重启时,
volume中的数据不会丢失;(容器重启,pod不会重启吗?这里存疑)
使用方法:
大多数情况下,在pod上声明一个volume,然后在容器里引用这个volume并mount到容器里的某个目录上;
一个示例:解读:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
volumes:
- name: datavol
emptyDir: {}
containers:
- name: tomcat-demo
image: tomcat
volumeMounts:
- mountPath: /mydata-data
name: datavol
imagePollPolicy: IfNotPresent
先声明了一个volume为emptyDir类型,名字为datavol。然后在容器里面引用,挂在容器内的/mydata-data,通过名字连接起来。关于
volume的类型
这种类型的挂载卷是在emptyDirpod分配到Node时创建的。初始内容为空,无需对应宿主机上对应的目录文件,当pod从node上移除时,emptyDir中的数据也会被永久删除。
一些用途如下: - 临时空间,例如一些临时目录等等。
- 多容器共享。
跟hostPathempty很像,只是不会被删除罢了,然后需要指定一个path属性,这个属性是宿主机上的目录。重点总结以下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25volumes:
- name: pstorage
hostPath:
path: /data
还有一些其他类型的挂载卷,一些网络文件系统等,不再一一列举。
# `PV`
`volume`是定义在`pod`上的,属于计算资源的一部分。实际上,网络存储是独立于计算资源之外存在的一种实体资源。
`pv`和`volume`的区别:
- `pv`只能是网络存储,不属于任何`node`,但在每个`node`上都可以访问;
- `pv`不是定义在`pod`上的,而是独立于`pod`之外定义;
- `pv`支持多种类型;
另外,`pv`作为一种独立的实体资源,也是需要独立的`yaml`文件来定义。
```shell
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWritesOnce
nfs:
path: /somepath
server: 172.17.0.2accessModes属性,目前类型有: ReadWriteOnce: 读写权限,并且只能被单个node挂载;ReadOnlyMany:只读权限,允许被多个node挂载;ReadWriteMany:读写权限,允许被多个node挂载;
如果某个pod想申请某种类型的pv,则需要先定义一个PVC对象:个人思考:这里可以看出来,
PVC和RC对标,pod和pv对标。
1 |
|
引用上述pvc:
1 | volumes: |
pv也是有状态的:
available,空闲可用;Bound:已经绑定到某个pvc上;Released:对应的pvc已经删除,但是资源还没有被集群收回;(???)Failed:pv自动回收失败;
Namespace
用于实现多租户。
查看:
1 | kubectl get namespaces |
对应的资源描述文件:
1 | apiVersion: v1 |
使用:
1 | apiVersion: v1 |
此时查看这个pod时需要加参数:
1 | kubectl get pods --namespace=development |
Annotation
跟标签差不多,不过标签作用于标签选择器,在调度中起很大的作用,注解只是单纯的备注作用了。可以记录一些比如release信息啊,时间戳,镜像信息,团队联系信息,电话号码等等。
k8s中的service简要介绍
service是k8s中最核心的资源对象之一。
RC的作用是维持pod集群的一系列数量指标,保证service的服务能力和服务质量始终处于预期的标准范围内。
k8s中的service定义了一个服务的访问入口地址,这个地址是提供为集群内的其他service使用的。service提供的cluster ip,只在k8s内部有意义。
每个service背后对应的都是一个pod集群,所以访问一个服务的时候,必定需要一个负载均衡算法来支撑。
k8s中服务通信
运行在node上的kube-proxy进程其实就是一个智能的软件负载均衡器,它负责把本node上对service的请求转发到后端的某个pod上,并在内部实现服务的负载均衡和会话保持机制。与service通信的时候,肯定是不能直接使用ip地址(因为未知),会用服务名来进行通信,因此就需要一种机制来做ip到服务名的映射。显然,dns正好符合要求(环境变量注入的方式也可以提供相同的功能)。
一个典型的service定义文件如下:
1 | apiVersion: v1 |
可以看到,服务tomcat-service的服务端口为8080,拥有tier:frontend标签的pod都属于他。
创建命令:
1 | kubectl create -f tomcat-server.yaml |
服务发现机制
一句话概括,服务发现的过程,就是把服务名替换为ip:port的过程。
环境变量
在pod启动的时候,按照一定规范和约定,在pod 的环境变量中,注入所有的服务的信息。pod要访问某个服务时,就从自己本地的环境变量取出。
考虑到环境变量的方式获取service的ip和port的方式仍然不太方便,不够直观,后来k8s通过Add-on增值包的方式引入了DNS系统,把服务名作为DNS域名,这样以来,程序就可以直接使用服务名来建立通信连接了。
外部系统访问service的问题
三种ip:
Node IP: 实打实的网卡的ip;Pod IP: 根据docker0网桥的ip地址段进行分配的,通常是一个虚拟的二层网络。另外,k8s要求位于不同node上的pod能够彼此直接通信,所以k8s里的pod能够直接访问另外一个pod。Cluster IP:虚拟的IP。属于k8s集群这样一个封闭的空间。如果需要提供外部访问,要做一些额外的工作。
某些服务模块就是用来提供给外部使用的,那么用户如何访问?service定义扩展:
1 | apiVersion: v1 |
这样,服务31002就暴露出来了。NodePort的实现方式是,在k8s集群的每个NOde上为需要外部访问的service开启一个对应的tcp监听端口,外部系统只要用任意一个node的IP地址+具体的NodePort端口号就可以访问该服务。
问题:这种方式仍未解决所有问题,比如负载均衡问题,这是就需要一个集群之外的负载均衡组件来实施负载均衡。
k8s入门
今天开始学习k8s。
学习k8s是一个要持续坚持很长时间的事情。
定义
k8s是一个全新的,基于容器技术的分布式架构领先方案。
由Google开源。
基本知识
核心
k8s中,service是分布式集群架构的核心,一个service对象拥有如下关键特征:
- 拥有一个唯一的指定的名字;
- 拥有一个虚拟
IP和端口号; - 能够提供某种远程服务能力;
- 被映射到了提供这种能力的一组容器上;
关于
serviceservice的服务进程目前都基于socket通信方式对外提供服务,或者是实现了某个具体业务的特定的tcp server进程。
一个service通常由多个相关的服务进程来提供服务,每个服务进程都有一个独立的EndPoint,但是k8s能够让我们通过service连接到指定的service上。
与pod的关系:
一个pod是一个服务进程的包装,一组pod组成一个service。pod和service之间的关系的建立,是由label来实现的。通过标签选择器选择。很显然,一个service对应一个标签选择器。关于
一个podpod是一组容器组成的,里面有一个特殊的pause容器,k8s用pause容器代表整个容器组的状态。这一组容器内的多个业务容器都共享pause容器的IP,共享pause容器挂接的volume。k8s要求底层网络支持集群内任意两个pod之间的tcp/ip直接通信。因此需要牢记一点:在k8s里,一个pod里的容器与另外主机的pod容器能够直接通信。pod有2种类型:普通pod和静态pod,后者比较特殊,不存放在etcd里,而是存放在某个具体node上的某个具体文件里,并且这个静态pod总是存在这个node上。普通的pod,一旦被创建,就会被放到etcd中存储,随后被k8s调度到某个具体的node上并进行绑定,随后该pod被对应的node上的kubelet进程实例化成一组相关的容器并启动起来。在默认情况下,pod里的某个容器如果出现死亡,整个pod就会重启,当然,整个pod里的所有容器都会被重启。关于资源描述
k8s里用yaml和json描述资源,下面以pod为例讲述:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17apiVersion: v1 #k8s版本
kind: Pod #表明资源类型是一个pod
metadata: #元数据,里面的数据最丰富
name: mywebserver #pod的名字
labels: #标签,用于选择,k8s选择具备这些标签node进行这个pod的创建
name: myweb #具体的标签的内容
spec: #一些spec设置,这是次于metadata的次丰富的一项
containers: #描述容器
- name: myweb 容器的名字
image: kubeguide/tomcat-app:v1 #容器创建时使用的镜像
ports: # 描述容器的端口
- containerPort: 8080 #容器暴露的端口,相当于dockerfile中使用EXPOSE 8080
env: #设置一些环境变量,容器要依赖的
- name: MYSQL_SERVICE_HOST
value: 'mysql'
- name: MYSQL_SERVICE_PORT
value: '3306'关于资源限额
在k8s中,通常以千分之一个CPU为一个m;内存配额也是一个绝对值,单位是内存字节数,下面是一个示例:1
2
3
4
5
6
7
8
9
10
11spec:
containers:
- name: db
image: db/centos
resources: #对容器的资源进行描述
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"关于标签选择
这个标签选择涉及到一些集合,正则一类的,这里先不做深究,上面示例描述文件中可以看到label描述项,这里只来讲述下他是怎么用的。这里使用具有标签1
2
3
4
5
6
7
8
9apiVersion: v1
kind: Service
metadata:
name: webserver
spec:
selector:
app: myweb
ports:
- port: 8080app:myweb的一些资源(包括pod等等)来工作。
标签选择器的几个重要的使用场景: kube-controller进程通过资源对象RC上定义的label来筛选要监控的pod的副本的数量。kube-proxy通过Service描述文件中定义的标签选择器来建立对pod的路由。- 通过对
node进行打标签,在pod中使用NodeSelector这种标签调度策略,kube-scheduler实现定向调度。RC(副本控制)
简单来讲,RC定义了一个期望的场景:即声明某种pod的数量要符合一个期望值,为了实现这个目的,RC的定义包括以下几点: pod期望的数量;- 标签选择器来监控
pod; pod的副本数量小于期望的数量时,重新创建pod使用的模板;
以下是一个示例描述文件:这里多讲一点,上面的格式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23apiVersion: v1
kind: RelicationController
metadata:
name: frontend
spec:
replicas: 2
selector:
tier: frontend
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
containers:
- name: tomcat-demo
image: tomcat/centos
imagePullPolicy: IfNotPresent
env:
- name: GET_HOTS_FROM
value: dns
ports:
- containerPort: 80-标示数组。也就是说,containers下面可以有多个镜像;同样地,env下面也可以有好多个,ports下面也可以有好多个。RC提交之后,是由master节点上的controller manager组件来使用的,他会根据这个RC描述文件进行定期巡检以保证pod满足描述文件的要求。
另外,可以通过执行kubectl scale命令来动态修改RC实现动态扩容:这样就把1
kubectl scale rc redis-slave --replicas=3
redis-slave这个pod的副本数量改为3。
下一代RC
在k8sv1.2时,升级成RS,即replica set。与上一代的RC存在的唯一区别是,RS支持基于集合的标签选择,RC只支持等式。
Relica Set和Deployment这个更高层的对象一起逐步替换了之前RC的作用,是k8sv1.3里pod自动扩容和伸缩这个告警功能实现的基础。
关于RC的总结:
RC实现pod的创建过程及副本数量的自动控制;RC里包含完整的pod模板;RC通过标签选择器来实现对pod副本的控制;- 通过对数量的控制,实现
pod的扩缩容; - 改变
RC里面模板的版本信息,可以实现滚动升级;
HPA
前面讲过,可以使用kubectl scale命令来实现扩缩容,但是不够自动化,hpa的作用是设定一个指标,根据指标的变化情况实现自动扩缩容。
一个具体例子:
1 | apiVersion: autoscaling/v1 |
上面这个例子是说,这个hpa控制的目标对象是由名叫php-apache的Deployment里的pod副本。
StatefulSet
之前的pod,deployment等等这些资源对象都是无状态的,但是一些db类的服务是有状态的。
statefulset作为deployment/rc的一个变种,用来控制有状态的pod。
特性:
pod具有稳定的唯一网络标识,可以用来发现集群内的其他成员。pod副本的启停顺序是受控的。- 每个
pod具有稳定的持久化存储卷,pod被删除的时候,存储卷不会被删除。
与headless service配合使用。headless service与普通的service的区别在于,headless service没有cluster ip,如果解析headless service的域名,那么会返回该service对应的全部pod的endpoint列表。
Docker基本原理
Docker是C/S架构。Docker网络接口默认都是虚拟的接口,最大的优势是转发效率极高。因为是通过内核中进行内存复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包将直接复制到接收接口的接收缓存中。
Docker网络基础配置
端口映射实现容器访问
这种方式其实在前面的http服务器创建时已经搞过。在Dockerfile中使用EXPOSE暴露一个端口,然后启动容器的时候使用命令:
1 | docker run -d -p 8080:80 --name httpserver2 http:centos |
这样,就把本机的8080和容器的80联系起来了。
当然这里-p有很多中用法,以下是一些例子:
1 | 1234:1234 |
另外,如果是用大写的-P参数,那么Docker会随机映射一个49000-49900之间的端口到容器内指定的端口上去。
容器互联
容器互联是使用--link实现的。
以下面命令为例说明:
1 | docker run -d -P --name http2server --link desservername:linkname http:centos |
desservername:linkname形式是:`要连的目标容器的名字:链接名称。
TIPS
Docker通过2种方式为容器公开连接信息。
- 环境变量
- 更新
/etc/hosts文件
这里是一个示例env命令的输出。
1 | HTTPCONNECTION_NAME=/http2server/httpconnection |
如果查看/etc/host文件,会有以下输出:
1 | 127.0.0.1 localhost |
Dockerfile方式创建一个http服务器
这个http服务器镜像的创建真是一波三折。
首先看一下最终的目录结构:
1 | . |
只有2个文件。
现在查看Dockerfile文件的内容。
1 | FROM centos:latest |
现在解读一下指令:
FROM
该指令指出,即将创建的镜像是以什么镜像为基础创建的。
MAINTAINER
该指令是负责给该镜像进行注释的,添加一些信息。
RUN
该指令负责在创建镜像的时候,运行后面的指令。注意是镜像创建的时候,而不是容器创建时。
ADD
该指令复制一个文件到镜像中。与COPY不同的是,该指令会对tar和gz这种自行解压解包。
CMD
该指令会在容器创建的时候执行,注意,是容器,而不是镜像。这个指令会被容器创建时传入的指令覆盖(当然,如果没有的话,就不会覆盖)。
然后来看run-httpd.sh。
1 | !/bin/bash |
可以看到,只是单纯的将安装的http服务启动起来。
最后用下面这个命令启动:
1 | docker run -d -p 8080:80 http:centos |
上面的命令将本机的8080端口映射到容器暴露的80端口。
然后通过curl命令可以访问本机的8080端口得到Hello,Docker的返回结果。
关于个人发展的一些思考
今天晚上跟同事又一起吃了个饭,席间聊天,感触颇深。
最大的体会总结起来,就是一个成语-井底之蛙。
这个词语用来形容我本身。
工作篇
这个是体会最深的一点。席间keshuai谈起部门内部一些人员的情况,让我真的很吃惊。
刚离职的某同事(未曾谋面过),校招进入蚂蚁金服不到半年,被我司挖过来,然后在2个月的时间里面,把nginx架构梳理了一遍,完全是一个架构师的角色。
在我司任职至今(应该四年左右),被阿里返聘,P8,240W股票。
某同事,去年一年,连升2级。
某同事,去年校招半年,即升一级。
…
身边全是大牛。
而我自己,至今可以说是一无所长,一无所有,并没有一个能拿得出的经历,或者说是在职业生涯上有一个比较好的表现。最可怕的是,之前的我虽然有所察觉,但是并没有重视。
结论:请走出舒适区,请对自己充满信心,请一心一意搞好技术!
生活篇
另一同事,骑行海南,新疆,西藏。这让我意识到自己以前好像就是在虚度时间。
结论:以后虽然重点的重点,在于学习,在于工作,在于技术。但是也要生活。希望自己以后慢慢丰富自己的生活,<手动伤心>。
人的一生跟程序一样。在计算机科学中有一个闭包的概念,人的一生也类似一个闭包,每个人都有自己的生活环境,关键在于你的环境是怎样的。我的奋斗过程,我要定义成不断引入新环境变量的样子,希望自己以后,坚持,自律,加油!