EKS的实施,点点滴滴,学到了就是赚到了。这也是我们的趟坑记录。团队兄弟们确实也辛苦了。
安装
本人使用的是Apple M1所以,需要找到arm64的安装。所以有一些东西和原文档不一样。
参考文档:
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /
Getting started with Amazon EKS - Amazon EKS
curl -LO "https://dl.k8s.io/release/v1.21.2/bin/darwin/arm64/kubectl"
curl --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_arm64.tar.gz" -o eksctl.tgz
chmod +x kubectl
tar -xzvf eksctl.tgz
sudo mv kubectl eksctl /usr/local/bin/
安全
AWS Inspector 做镜像安全
kube-bench做K8S安全基线审核
GitHub - aws/aws-eks-best-practices: A best practices guide for day 2 operations, including operational excellence, security, reliability, performance efficiency, and cost optimization.https://github.com/aws/aws-eks-best-practicesGitHub - aquasecurity/kube-bench: Checks whether Kubernetes is deployed according to security best practices as defined in the CIS Kubernetes BenchmarkChecks whether Kubernetes is deployed according to security best practices as defined in the CIS Kubernetes Benchmark - GitHub - aquasecurity/kube-bench: Checks whether Kubernetes is deployed according to security best practices as defined in the CIS Kubernetes Benchmarkhttps://github.com/aquasecurity/kube-bench
由于Kube-bench的安全基线检查是使用CIS的安全极限作为指导的。所以,我们可以参考以下的文档,来对kube-bench的输出进行解读。并且找到合适的方案。
最小镜像
在进行容器化系统的安全加固过程中,比较简单重要的方面就是如何减少攻击面。所以,最小镜像就是一个比较有效的选择。
Distroless
Google提供了一个比较完整的体系。来支持最小景象。
GitHub - GoogleContainerTools/distroless: 🥑 Language focused docker images, minus the operating system.🥑 Language focused docker images, minus the operating system. - GitHub - GoogleContainerTools/distroless: 🥑 Language focused docker images, minus the operating system.https://github.com/GoogleContainerTools/distroless对于go, rust等编译执行的程序,一来比较少。可以使用下面的集成方式。
# Start by building the application.
FROM golang:1.18 as build
WORKDIR /go/src/app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 go build -o /go/bin/app
# Now copy it into our base image.
FROM gcr.io/distroless/static-debian11
COPY --from=build /go/bin/app /
CMD ["/app"]
而对于java,python,nodejs这种需要预装虚拟机,解释其的语言,可以选择相关镜像来做。下面是java的例子。
FROM openjdk:11-jdk-slim-bullseye AS build-env
COPY . /app/examples
WORKDIR /app
RUN javac examples/*.java
RUN jar cfe main.jar examples.HelloJava examples/*.class
FROM gcr.io/distroless/java11-debian11
COPY --from=build-env /app /app
WORKDIR /app
CMD ["main.jar"]
在该项目,根据不同的技术栈,提供了相应的基础镜像。我们可以选用。具体可以查看项目自带的examples.
由于 Destroless 的镜像都提供了 nonroot 用户,这对容器的权限控制也非常方便。
Destroless也列出了使用它的重量级系统。对于要求严格的系统软件,还是建议使用Destroless.
- Kubernetes, since v1.15
- Knative
- Tekton
Alpine
个人觉得 destroless 是直接面对生产环境的配置。所以限制非常严格。而如果我们需要对系统进行一定的定制化。比方说,我们希望能够选择java的小版本。我们就需要有 apk, bash 等这些基础的命令。
Alpine 使用上非常方便,我们可以通过Alpine随时产生一个我们需要的小景象,分配给具体任务。
# This took 28 seconds to build and yields a 169 MB image. Start doing this:
FROM alpine:latest
RUN apk add --no-cache mysql-client
ENTRYPOINT ["mysql"]
Alpine一直是docker hub上下载量最大的项目。所以我们也相信他表现不俗。也确实比Distroless要方便使用。
如果安装jdk或者jre的话。基本上就是这样做就可以了。
FROM alpine:latest
RUN apk add --no-cache openjdk8-jre
对于某些因为缺少依赖库无法启动的情况。Alpine 可以使用 apk 安装相应的依赖包。至于缺少什么?c/c++,rust 这种编译性质的语言基本上就是使用ldd来找到是否缺少哪些文件。可以在 https://pkgs.org/ 查询需要相应的包进行安装。
对于Alpine配置结束以后如果我们需要上线生产环境。我们还是建议把 apk 彻底删掉。
#!/bin/sh
docker_remove_apk() {
# Might as well upgrade everything
apk upgrade
# Remove apk
apk del --repositories-file /dev/null apk-tools alpine-keys libc-utils
# Delete apk installation data
rm -rf /var/cache/apk /lib/apk /etc/apk
}
docker_remove_apk
如果我们需要在Alpine里面使用apk命令,这里有一个非常好的文档做参考。
13 Apk Commands for Alpine Linux Package Management
如果需要把shell也删除掉。保证系统无法登陆执行。我们要替换一下命令(/bin/sh 是busybox的组建,alpine-baselayout 依赖busybox:
apk del --repositories-file /dev/null apk-tools alpine-keys libc-utils busybox alpine-baselayout
JDK升级到17
为了减少我们在生产环境的JDK版本,减少由于维护多个基础镜像带来的安全和运维成本。我们决定统一将JDK升级到最新的LTS版本,JAVA17。
在我们升级到JDK17的时候。Spring Boot 会因为无法启动Bean而出问题。我们使用的Spring Boot版本是 2.2.6,版本比较老。怎么办呢?这里日志报出的问题的最终原因如下:
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field protected java.lang.reflect.InvocationHandler java.lang.reflect.Proxy.h accessible: module java.base does not "opens java.lang.reflect" to unnamed module @6aceb1a5
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) ~[na:na]
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) ~[na:na]
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178) ~[na:na]
at java.base/java.lang.reflect.Field.setAccessible(Field.java:172) ~[na:na]
at com.thoughtworks.xstream.core.util.Fields.locate(Fields.java:40) ~[xstream-1.4.11.1.jar!/:1.4.11.1]
at com.thoughtworks.xstream.converters.extended.DynamicProxyConverter.<clinit>(DynamicProxyConverter.java:42) ~[xstream-1.4.11.1.jar!/:1.4.11.1]
... 81 common frames omitted
我们在需要在代码段里添加相关的add-opens。
--add-opens=java.base/java.util=ALL-UNNAMED
由于java17的模块化属性,还会有其他的模块报出相同的问题。所以我们有可能进一步添加相关的命令行参数,直到程序能够运行。
更加详细的讨论可以放在这里。
ZGC
由于G1GC对内存的管理过于贪婪。不能及时释放内存给系统。所以我们在系统环境使用了ZGC。ZGC的效果非常好。不仅GC Pause降到了17微秒。而且,对内存会频繁交还给系统。这大大节省了系统资源的占用。
而且ZGC非常智能,基本上不用进行调优。这对容器化应用就非常有利。如果我们需要投入精力对所有的POD都进行手工调优的话,花费的人力也是非常可观的。所以,我建议大家在容器环境使用Java的时候,尽量使用ZGC。
排查JAVA内存增长
HPA (Horizontal Pod Autoscaling)
-XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=30
由于默认的这两个值比较大,Max = 79, Min= 40,所以我们通常情况下看不到GC对内存的归还操作。所以在使用内存作为 JVM Pod进行横向扩展的时候,都会有类似的问题出现。
开启swap分区
为了对付突发的内存占用,减少问了应付峰值需求过度分配内存,SWAP分区还是有必要的。Kubernetes在 1.22 版本上已经支持了 SWAP 分区的使用。
New in Kubernetes v1.22: alpha support for using swap memory | Kubernetes