写点什么

实战攻略:利用 GitOps 在 Kubernetes 上实现持续交付

  • 2019-09-12
  • 本文字数:8299 字

    阅读完需:约 27 分钟

实战攻略:利用GitOps在Kubernetes上实现持续交付

GitOps 是 Weaveworks 提出的一种持续交付方式。其工作原理,是利用 Git 作为声明基础设施与应用程序的单一事实来源。


本文我们将通过一个简单的项目,了解如何设置典型的 CI/CD 流水线,而后通过修改将 GitOps 添加到其中。同时,我们还将演示 Flux——GitOps 的核心组件。几周之前,Flux 已经被 CNCF 正式接纳为沙箱培养项目。

我们要做什么

下面,我们先来看看整个流程中的具体操作步骤:


  • 对 GitOps 进行简单介绍

  • 设置一个简单的项目,并在 GitLab 之内进行管理

  • 集成一个 Kubernetes 集群

  • 设置一条典型的 CI/CD 流水线

  • 利用 GItOps 处理其中的 CD 部分

什么是 GitOps?

GitOps 是一种持续交付实现方式。其将 Git 作为声明基础设施与应用程序的事实来源。当对 Git 进行变更时,自动交付流水线也会对您的基础设施进行相应变更。

将变更部署至集群:push 与 pull

在一条典型的 CI/CD 流水线当中,CI 工具负责运行测试、构建镜像、检查 CVE 并将新镜像重新部署至集群当中,具体如下图所示。



典型的 CI/CD 流水线(图片来源:Weaveworks)


GitOps 方法的区别在于,其中的部署部分不再由 CI 工具完成,而是由操作程序通过集群内 Pod 中的运行进程完成(由 Flux 负责实现)。



包含 GitOps 的 CI/CD 流水线 (图片来源:Weaveworks)

相关组件

下图所示为在 Kubernetes 集群当中使用 GitOps 时所需要用到的各组件。



在 Kubernetes 集群当中的各 GitOps 组件 (图片来源:Weaveworks)


为了简单起见,Flux 守护程序会不断运行并检查是否存在新的 Docker 镜像。检测到新镜像之后,它会调用 API Server 对当前正在运行的部署加以更新。


在本文的最后一部分中,我们将设置 Flux 并利用它部署一款简单的应用程序。

我们的项目

在这里,我们使用一个非常简单的 Flask 应用程序。项目的复杂程度并不重要,真正重要的是理解整个 CI/CD 流程的实现方式。

源代码

我们只需要考虑以下文件:


  • app.py 用于公开一个单独的 HTTP 端点并返回一个字符串


from flask import Flaskapp = Flask(__name__)
@app.route("/")def hello(): return "Hello World!"
if __name__ == "__main__": app.run(host='0.0.0.0', port=8000)

复制代码


  • requirements.txt 用于定义 app.py 所需要的依赖性,即 Flask 库


Flask==1.0.2```


  • Dockerfile 用于通过源代码构建起一套镜像


FROM python:3-alpineCOPY . /appWORKDIR /appRUN pip install -r requirements.txtCMD python /app/app.py
复制代码

确保一切正常

我们先通过以下命令为我们的应用程序创建第一套 Docker 镜像:


$ docker image build -t hello:1.0 .
复制代码


在镜像构建完毕之后,我们可以运行容器以使用该镜像。


$ docker container run -p 8000:8000 hello:1.0 * Serving Flask app “app” (lazy loading) * Environment: production
复制代码


注意:不要在生产环境当中使用该开发服务器,请使用生产 WSGI 服务器作为替代。


* Debug mode: off
* Running on http://0.0.0.0:8000/ (Press CTRL+C to quit)
复制代码


我们的服务器在监听端口 8000,如下图所示。


GitLab 项目

我们将利用 GitLab 管理这款应用程序,下面创建一个名为 hello 的新项目:



在 GitLab 当中创建一个新项目


接下来,我们可以为该应用程序文件夹进行 git 初始化,并将一切 push 至 GitLab 项目当中:


$ git init$ git remote add origin git@gitlab.com:lucj/hello.git$ git add .$ git commit -m "Initial commit"$ git push -u origin master
复制代码


几秒之后,我们可以通过 GitLab 的 Web 界面看到该项目中的三个文件。



代码的首次 commit

迎接 Kubernetes

由于需要在 Kubernetes 集群上部署我们的应用程序,所以这里我们将使用 GitLab 的 Kubernetes 集成功能将外部集群的配置导入项目当中。

创建一个托管集群

DOKS (DigitalOcean 托管 Kubernetes 集群) 是我个人最喜爱的解决方案,其易于设置及使用。我们可以通过 DigitalOcean Web 界面或者使用专门的 doctl 命令行界面进行创建。在本示例中,我们将设置一套包含 3 个工作节点的集群,其中管理器节点由 DIgitalOcean 替我们管理。



通过 DigitalOcean 的 Web 界面创建托管 Kubernetes 集群


配置基础设施以及创建集群大概需要几分钟的时间。完成之后,我们需要检索 kubeconfig 文件,以确保我们的 kubectl 客户端能够与集群的 API 服务器通信。我们将使用 doctl 命令并将该配置保存在 k8s-demo.cfg 文件当中:


$ doctl k8s cluster cfg show k8s-demo > k8s-demo.cfg
复制代码


接下来,我们配置 kubectl 以使其与我们的集群进行通信,从而设置 KUBECONFIG 环境变量:


$ export KUBECONFIG=$PWD/k8s-demo.cfg
复制代码


搞定。下面我们来看看目前的集群状态:


$ kubectl get nodesNAME            STATUS   ROLES    AGE     VERSIONk8s-demo-rlf5   Ready    <none>   2m10s   v1.15.2k8s-demo-rlfh   Ready    <none>   2m40s   v1.15.2k8s-demo-rlfk   Ready    <none>   2m33s   v1.15.2
复制代码

与 GitLab 项目相集成

通过 GitLab 的 Web 界面,我们可以轻松将外部 Kubernetes 集群集成至项目当中。我们只需要进入 Operations > Kubernetes,而后点击 Add Kubernetes cluster 即可:



Kubernetes 集群的集成操作


接下来,我们需要选择 Add existing cluster 选项卡。在这里,我们需要填写几个字段,其中第一个字段可以从配置文件当中轻松检索到:



需要在 Kubernetes 集群集成过程中填写的字段


  • 集群名称

  • API Server 的 URL

  • 集群的 CA 证书


要向 GitLab 当中添加集群 CA 证书,我们需要解码配置中指定的证书(以 base64 形式编码)。


$ kubectl config view --raw \-o=jsonpath='{.clusters[0].cluster.certificate-authority-data}' \| base64 --decode
复制代码


  • 服务令牌


整个令牌获取过程分为几个步骤。我们首先需要创建一个 ServiceAccount,并为其提供 cluster-admin 角色。具体操作命令如下:


$ cat <<EOF | kubectl apply -f -apiVersion: v1kind: ServiceAccountmetadata:  name: gitlab-admin  namespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1beta1kind: ClusterRoleBindingmetadata:  name: gitlab-adminroleRef:  apiGroup: rbac.authorization.k8s.io  kind: ClusterRole  name: cluster-adminsubjects:- kind: ServiceAccount  name: gitlab-admin  namespace: kube-systemEOF
复制代码


在 ServiceAccount 创建完成之后,我们开始检索相关 Secret:


$ SECRET=$(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}')add extract its JWT token, the one we need to enter in the Service Token field in the GitLab interface:$ TOKEN=$(kubectl -n kube-system get secret $SECRET -o jsonpath='{.data.token}' | base64 --decode) && echo $TOKEN
复制代码


在对集群集成进行验证之前,我们取消原本被选中的 GitLab-managed-cluster 复选框,这代表着我们将自行管理命名空间。



在集群集成完毕后,GitLab 即可通过 Helm 图表一键安装多款应用程序。不过这不是今天讨论的重点,因此不再赘述。



Kubernetes 集群与我们的 GitLab 项目顺利集成

设置一条典型的 CI/CD 流水线


我们首先在项目的 root 目录处添加一个.gitlab-ci.yml 文件。该文件用于定义每当有新代码被提交至代码库时,所应触发的具体操作。


在文件开头,我们首先定义流水线中的不同阶段:


stages:  - package  - test  - push  - deploy
复制代码


在各个阶段当中,我们进一步定义需要执行的操作:


  • 其中的 package 阶段负责利用源代码创建一套 Docker 镜像,并使用一个临时标签(我们稍后将详加解释)将其推送至项目的 GitLab 镜像库。


build:  image: docker:stable  stage: package  services:    - docker:dind  script:   - docker build -t $CI_REGISTRY_IMAGE:tmp .   - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY   - docker push $CI_REGISTRY_IMAGE:tmp  only:  - master
复制代码


  • 而 test 阶段则负责利用新创建的镜像运行一套容器,并确保返回的消息以“Hello”为开头。


test:  image: docker:stable  stage: test  services:    - docker:dind  script:    - docker run -d --name hello $CI_REGISTRY_IMAGE:tmp    - sleep 10s    - TEST=$(docker run --link hello lucj/curl -s http://hello:8000)    - $([ "${TEST:0:5}" = "Hello" ])  only:  - master
复制代码


接下来的 push 阶段向该镜像中 push 新的标签,第一个标签基于该 git 提交的 hash,第二个为当前分支的名称(在本示例中为 master,因为我们只需要在主分支上进行操作)。最后,将这些新标签 push 回 GitLab 库。


push:  image: docker:stable  stage: push  services:    - docker:dind  script:   - docker image pull $CI_REGISTRY_IMAGE:tmp   - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_BUILD_REF   - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME   - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY   - docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF   - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME  only:  - master
复制代码


最后,deploy 的阶段负责在我们的 Kubernetes 集群之内创建/更新应用程序。我们将在 k8s 文件夹当中定义 2 个 manifest 文件:Deployment 用于我们我们 Web 服务器的 Pod,而 Service 则用于将其面向外部公开。


我们首先定义以下 k8s/deploy.tpl 模板。它将被用于在 deploy 阶段生成用于指定 Deployment 资源的 k8s/deploy.yml 文件。这套模板将定义 Deployment,用于管理根据registry.gitlab.com/lucj/hello镜像建立的Pod的一套单独副本。


在这套模板中,我们使用名为 GIT_COMMIT 的占位符替换实际提交的 hash,具体如下所示。


apiVersion: apps/v1kind: Deploymentmetadata:  name: hello  labels:    app: hellospec:  selector:    matchLabels:      app: hello  template:    metadata:      labels:        app: hello    spec:      containers:      - name: hello        image: registry.gitlab.com/lucj/hello:GIT_COMMIT
复制代码


我们还在 k8s/service.yml 当中定义了 Service 资源,用以向外部公开我们的应用程序。Service 的类型为 LoadBalancer。


apiVersion: v1kind: Servicemetadata:  name: hellospec:  type: LoadBalancer  ports:    - name: hello      port: 80      targetPort: 8000      protocol: TCP  selector:    app: hello
复制代码


需要在 deploy 阶段执行的操作如下:


deploy:  stage: deploy  image: lucj/kubectl:1.15.2  environment: test  script:    - kubectl config set-cluster my-cluster --server=${KUBE_URL} --certificate-authority="${KUBE_CA_PEM_FILE}"    - kubectl config set-credentials admin --token=${KUBE_TOKEN}    - kubectl config set-context my-context --cluster=my-cluster --user=admin --namespace default    - kubectl config use-context my-context    - cat k8s/deploy.tpl | sed 's/GIT_COMMIT/'"$CI_BUILD_REF/" > k8s/deploy.yml    - kubectl apply -f k8s  only:  - master
复制代码


另外几点注意事项:


  • 这一阶段需要在包含 kubectl 客户端的镜像上下文中运行

  • 从 GitLab 自动设置的环境变量当中检索集群信息,这些信息将用于设置 Kubernetes 的上下

  • Deployment 资源根据模板文件创建而成,其中的 CIT_COMMIT 占位符将被替换为 $CI_BUILD_REF 环境变量当中的实际提交信息

  • Service 与 Deployment 资源分别位于 k8s/service.yml 与 k8s/deploy.yml 当中,通过常用的“kubectl apply”命令进行创建/更新


备注:这条流水线非常简单,但并不是最优方案。我们只是利用它来展示不同的流程。

项目测试

下面,让我们把这些变更 push 到 GitLab 项目当中,而后检查由此触发的 CI/CD 流水线:


$ git add k8s$ git commit -m ‘Add K8s resources’$ git add .gitlab-ci.yml$ git commit -m ‘Add GitLab pipeline’$ git push origin master
复制代码


这会触发 GitLab 流水线,具体如下图中的 Web 界面所示:



在该流水线的 deploy 阶段(最终阶段),Deployment 与 Service 需要进行首次创建(因为之前并不存在)。由于 Service 的类型为 LoadBalancer,因此我们可以从下图中看到 DigitalOcean 基础设施上会创建对应的负载均衡器资源。



利用与该 Load Balancer 相关联的外部 IP 地址,我们可以在指向运行有应用程序的底层 Pod 的端口 80 上访问自己的应用程序。



这证明 Service 与 Deployment 都已经正确创建完成。


下面,我们需要对 app.py 做出一点调整,把返回内容由“Hello World!”改为“Hello from Kube”。


from flask import Flaskapp = Flask(__name__)@app.route("/")def hello():    return "Hello from Kube"if __name__ == "__main__":    app.run(host='0.0.0.0', port=8000)
复制代码


我们对这些变更进行 commit 与 push:


$ git add app.py$ git commit -m 'change message to Hello from Kube'$ git push origin master
复制代码


新的 CI/CD 流水线由此触发,我们可以刷新浏览器并看到如下结果:



当然,我们在这里设置的只是一条简单的流水线。对于真实场景中的应用程序,还需要添加一些额外的增强功能。例如,我们可能需要考虑以下步骤:


  • 额外的测试

  • 镜像扫描,用于确保该镜像不包含任何 CVE 漏洞(或者至少不存在高危漏洞)


若需了解更多与镜像扫描相关的细节信息,请参阅:


https://medium.com/better-programming/adding-cve-scanning-to-a-ci-cd-pipeline-d0f5695a555a

添加 GitOps

现在,我们需要再次修改这条 CI/CD 流水线,以利用 GItOps 方法处理其中的 CD 部分。以下结构展示了 GitOps Deployment 工作流中所涉及的组件。



GitOps Deployment 工作流(图片来源:Weaveworks)


基本上,每当系统在镜像注册表中检测到新的镜像标签,我们就要利用 Flux 操作程序(运行在 Pod 内的集群当中)对应用程序进行重新部署。

安装 Flux

Flux 可以通过 Deployment 或者 Helm 进行手动安装。在本文中,我们也使用手动方案。第一步,就是对 fluxcd 库进行 clone:


$ git clone https://github.com/fluxcd/flux && cd flux
复制代码


接下来,在 Deployment 规范之内(deploy/flux-deployment.yaml)变更以下参数:


  • --git-url=git@gitlab.com:lucj/hello, 用于告知 Flux 检测哪个 Git 库

  • –git-path=k8s, 在此库当中只考虑 k8s 文件夹(我们的 Kubernetes manifests 文件就位于该文件夹内)

  • –git-ci-skip, 此选项允许我们在 Flux 完成对 GitLab 项目库的更新之后(包括标签与 Deployment 资源更新),跳过 CI 流水线


现在,我们可以将 Flux 部署至集群当中了:


$ kubectl apply -f deployserviceaccount/flux createdclusterrole.rbac.authorization.k8s.io/flux createdclusterrolebinding.rbac.authorization.k8s.io/flux createddeployment.apps/flux createdsecret/flux-git-deploy createddeployment.apps/memcached createdservice/memcached created
复制代码


由此创建的几种资源:


  • ServiceAccount、ClusterRole 以及 ClusterRoleBoinding,用于为 Flux Pod 提供运行所需的验证/授权

  • Flux 操作程序

  • 用于 memcached 的 Service 与 Deployment,由 Flux 用于缓存镜像元数据


$ kubectl get podsNAMESPACE NAME                       READY  STATUS   RESTARTS  AGEdefault   flux-dcb965db7-pn97k       1/1    Running  0         56sdefault   memcached-554f994578-t2tss 1/1    Running  0         56s...
复制代码


查看 Flux Pod 日志,我们会看到一条错误消息,因为 Flux 无法读取项目的 Git 库。


“权限被拒绝(公钥)。严重:无法从远程库中读取。请确保您具有正确的访问权限,且目标库存在。”


为了解决这个问题,我们可以使用 fluxctl 实用程序检索安装期间所公开 ssh 密钥。


$ fluxctl identityssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCx4fk4YjcM7cP1FL/AKWtHpN+cg9/Qz1p5dzAlsFLMKilUUy0uCQQmaptXDZQGaZrbvNSyezgT5/yH6qau6W6ICoLYAzBku47PoWlqbUfcbPhMxHSfivjv7s4lSeUE+u3kR2opROxdyHHL+VQMI6n9Xc7qnTq6YC+VJ+RkoUUd0bgBC+Rg/aMURLD9mkAVzmWw6+Y8QAJMVNMzNDgId+8iSHKtOYsHqoxg4GqexdB1R5goE0ChBU9DPsiqLfk8jzuD2I3xuZeGW6or+/JHxa/6vO8lX+of1ZGZGZKr5i3E4OIehSwFUP2A/ypeqXEEI5gmO1s2YrM49jpS+jW4oUMP
复制代码


接下来,将该密钥添加至我们的 GitLab 库,为其提供创建/更新所需要的读取/写入访问权限。


修改原有流水线

由于 Flux 负责对项目作出的变更进行部署,因此我们需要删除之前创建的.gitlab-ci.yml 文件中的 Deployment 阶段,其它内容则保持不变。现在的.gitlab-ci.yml 如下所示,其中与集群 API Server 交互的 kubectl 已经被删除:


stages:  - package  - test  - pushbuild:  image: docker:stable  stage: package  services:    - docker:dind  script:   - docker build -t $CI_REGISTRY_IMAGE:tmp .   - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY   - docker push $CI_REGISTRY_IMAGE:tmp  only:  - mastertest:  image: docker:stable  stage: test  services:    - docker:dind  script:    - docker run -d --name hello $CI_REGISTRY_IMAGE:tmp    - sleep 10s    - TEST=$(docker run --link hello lucj/curl -s http://hello:8000)    - $([ "${TEST:0:5}" = "Hello" ])  only:  - masterpush:  image: docker:stable  stage: push  services:    - docker:dind  script:   - docker image pull $CI_REGISTRY_IMAGE:tmp   - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_BUILD_REF   - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME   - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY   - docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF   - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME  only:  - master
复制代码


此外,我们也可以删除 k8s/deploy.tpl 模板文件,因为我们不再需要利用该文件对 Deployment manifest 进行更新。相反,我们将在 Deployment 中使用以下 k8s/deploy.yml,确保 Flux 在每次检测到新的镜像标签时都会执行更新。


apiVersion: apps/v1kind: Deploymentmetadata:  name: hello  annotations:    flux.weave.works/automated: "true"    flux.weave.works/tag.hello: regexp:^((?!tmp).)*$  labels:    app: hellospec:  selector:    matchLabels:      app: hello  template:    metadata:      labels:        app: hello    spec:      containers:      - name: hello        image: registry.gitlab.com/lucj/hello:master
复制代码


用于该 Deployment 的 Flux 配置在 annotations 键内完成:


  • flux.weave.works/automated: “true”, 用于激活该资源的自动重新部署

  • flux.weave.works/tag.hello: regexp:^((?!tmp).)*$, 确保具有 tmp 标签的临时镜像不会被纳入使用

实际测试

我们对 app.py 中的代码进行了如下变更,因此其现在会返回“Hello from Flux”。


from flask import Flaskapp = Flask(__name__)@app.route("/")def hello():    return "Hello from Flux"if __name__ == "__main__":    app.run(host='0.0.0.0', port=8000)
复制代码


接下来将修改后的内容 push 至 GitLab。


$ git rm k8s/deploy.tpl$ git add k8s/deploy.yml .gitlab-ci.yml app.py$ git commit -m 'CD with Flux'$ git push origin master
复制代码


查看 GitLab 界面,我们会看到该流水线已经被触发了多次。



已经创建的多条流水线(其中几条被直接跳过)


有条流水线的触发原因是我们做出了变更,其它几条则由 Flux 在对 master 分支上的 Deployment manifest(k8s/deploy.yml)以及 flux-sync 分支上的标签进行更新时触发。除了这两项操作之外的其它被触发流水线被直接跳过(相关操作并未执行),这是因为我们在 Flux 配置当中使用了—git-ci-skip 选项(如果不这样,流水线将一直循环运行)。


然后,我们可以再次刷新浏览器以查看应用程序的最新版本。



可以看到,当 Flux 操作程序定期检查新的镜像标签时,其会发现 CI 流水线执行期间出现的代码变更,并据此自动更新 Deployment。

总结

在本文当中,我希望向大家介绍 GitOps,并通过一个简单的示例说明它如何与 GitLab CI 流水线配合起效。大家也可以根据需求增强其中某些功能,例如在流水线当中定义更多阶段,使用 sermver 命名镜像标签等……总之,我希望这篇简单的文章能够让大家对整个方法拥有基本的了解。


GitOps 在很长一段时间内得到了行业的高度关注,感兴趣的朋友可以点击此处通过官方文档了解更多细节信息。


您已经开始使用 GitOps 方案了吗?希望在评论中看到您的分享心得。


2019-09-12 16:584976

评论

发布
暂无评论
发现更多内容

神马操作!Kafka 竟然宣布弃用 Java 8

收到请回复

Java kafka 后端 java8

刚上岸字节年薪60W的Java架构师,耗时半年总结的24W字面试手册

Java 程序员 架构 面试 后端

面试多次被拒,“两个月”61天,我收到了蚂蚁金服P7级的offer

Java spring 程序员 架构 编程语言

想不到吧!这本字节算法大佬562页《算法中文手册》,在Gihub上排名第一!

Java 架构 面试 程序人生 编程语言

飞桨与海光人工智能加速卡DCU系列完成互证,助力国产AI加速 卡人工智能应用创新

百度大脑

人工智能 深度学习 飞桨

横空出世!IDEA画图神器来了,比Visio快10倍

收到请回复

Java IDEA idea插件

Java高级、架构师必备!Lucene+ElasticStack入门至项目实战!

Java 架构 面试 程序人生 编程语言

这么卷吗?大三学生喜获阿里提前批

Java 程序员 架构 后端

发布两小时,霸榜GitHub!Spring Boot实战文档

Java 编程 程序员 后端 计算机

凌晨加班回家路上捡到阿里技术人限产的MySQL高级笔记及面试宝典,从此我的人生像开挂一样!

Java 架构 面试 程序人生 编程语言

总结出这份学习笔记,帮助朋友成功跳槽!六年阿里工作,苦熬到 P7经验分享!

Java 程序员 架构 后端 工程师

4年CRUD小职员,五面阿里艰苦经历(定薪45K),回馈一波心得体会

收到请回复

Java 程序员 面试 后端 面经

ToB产品如何自传播(上)

石云升

产品经理 产品设计 产品思维 10月月更

一女程序员因薪酬问题离职,rm -f * 删库,瘫痪6个小时,被判9个月

收到请回复

Java 程序员 面试 面经

互动视频和5G的相互成就

脑极体

真香!兜兜转转还是得看你“阿里面试参考指南”

Java 程序员 架构 面试 后端

没想到!阿里技术大佬独家收藏的pring全家桶小册,竟被我意外发现!

Java 架构 面试 程序人生 编程语言

Leetcode题目解析:274. H 指数

程序员架构进阶

面试 算法 LeetCode 10月月更

自定义View:如何绘制一个饼图

Changing Lin

10月月更

TypeScript 中的 Index Signatures

Regan Yue

typescript ReganYue 10月月更

升级了 Windows 11 正式版,有坑吗?

王磊

区块链通证经济的意义

CECBC

怒肝 Linux 学习路线,这回不难

程序员鱼皮

Linux 编程 后端 开发 java

字节总监毕生心血总结:收获,不止SQL优化抓住SQL的本质

Java 程序员 架构 面试 计算机

内卷破坏者!“阿里爸爸”全新出品SpringBoot高级笔记(全彩版)

Java 编程 架构 IT 计算机

谁说GitHub才能出经典?出自牛客网的Java程序员逆袭手册才是YYDS

Java 程序员 架构 面试 计算机

双非本科猛斩6个offer,秘籍公开!

Java 程序员 架构 面试 后端

被疫情“带飞”的家庭健身市场,是时候卷起来了

脑极体

无敌!学透美团老哥的这套微服务进阶学习手册拿个P7还是so easy!

Java 架构 面试 程序人生 编程语言

通关宝典!Java 面试核心知识让你面试过,过,过!

Java 程序员 面试 后端 构架

2021金九银十Java面试经历:腾讯5面(已拿offer)

Java 编程 程序员 架构 面试

实战攻略:利用GitOps在Kubernetes上实现持续交付_开源_Luc Juggery_InfoQ精选文章