zoukankan      html  css  js  c++  java
  • Kubernetes系列之Helm介绍篇

    image

    本次系列使用的所需部署包版本都使用的目前最新的或最新稳定版,安装包地址请到公众号内回复【K8s实战】获取

    介绍


    Helm 是 Deis 开发的一个用于 Kubernetes 应用的包管理工具,主要用来管理 Charts。有点类似于 Ubuntu 中的 APT 或 CentOS 中的 YUM。Helm Chart 是用来封装 Kubernetes 原生应用程序的一系列 YAML 文件。可以在你部署应用的时候自定义应用程序的一些 Metadata,以便于应用程序的分发。对于应用发布者而言,可以通过 Helm 打包应用、管理应用依赖关系、管理应用版本并发布应用到软件仓库。对于使用者而言,使用 Helm 后不用需要编写复杂的应用部署文件,可以以简单的方式在 Kubernetes 上查找、安装、升级、回滚、卸载应用程序。

    Helm 工作原理


    这张图描述了 Helm 的几个关键组件 Helm(客户端)、Tiller(服务器)、Repository(Chart 软件仓库)、Chart(软件包)之间的关系。

    image

    下载解压


    [root@master-01 ~]# mkdir /opt/helm&&cd /opt/helm[root@master-01 helm]#wget https://storage.googleapis.com/kubernetes-helm/helm-v2.12.3-linux-amd64.tar.gz[root@master-01 helm]# wget https://storage.googleapis.com/kubernetes-helm/helm-v2.12.3-linux-amd64.tar.gz[root@master-01 helm]# tar xf helm-v2.12.3-linux-amd64.tar.gz [root@master-01 helm]# cd linux-amd64/[root@master-01 linux-amd64]# mv helm /usr/bin/[root@master-01 linux-amd64]# helm versionClient: &version.Version{SemVer:"v2.12.3", GitCommit:"79d07943b03aea2b76c12644b4b54733bc5958d6", GitTreeState:"clean"}Error: could not find tiller
    

    创建RBAC角色


    创建 Kubernetes 的服务帐号和绑定角色

    [root@master-01 helm]#kubectl create serviceaccount --namespace kube-system tiller[root@master-01 helm]#kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
    

    为 Tiller 设置帐号

    使用 kubectl patch 更新 API 对象

    [root@master-01 helm]# kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'deployment.extensions "tiller-deploy" patched
    

    查看是否授权成功

    [root@master-01 helm]# kubectl get deploy --namespace kube-system tiller-deploy --output yaml|grep serviceAccountserviceAccount: tillerserviceAccountName: tiller
    

    初始化Tiller


    tiller是以Deployment部署在k8s集群中的,使用helm init就可以直接安装

    因为tiller默认是去storage.googleapis.com拉镜像,所以我们要改成国内源

    [root@master-01 ]# helm initCreating /root/.helm Creating /root/.helm/repository Creating /root/.helm/repository/cache Creating /root/.helm/repository/local Creating /root/.helm/plugins Creating /root/.helm/starters Creating /root/.helm/cache/archive Creating /root/.helm/repository/repositories.yaml Adding stable repo with URL: https://kubernetes-charts.storage.googleapis.com Error: Looks like "https://kubernetes-charts.storage.googleapis.com" is not a valid chart repository or cannot be reached: Get https://kubernetes-charts.storage.googleapis.com/index.yaml: dial tcp 216.58.200.16:443: connect: connection refused
    

    更换国内源

    [root@master-01 ~]# helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts"stable" has been added to your repositories
    

    再次初始化

    [root@master-01 ~]# helm init$HELM_HOME has been configured at /root/.helm.Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.To prevent this, run `helm init` with the --tiller-tls-verify flag.For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installationHappy Helming!
    

    查看pod状态

    [root@master-02 ~]# kubectl -nkube-system get po -owideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATEScoredns-5d668bd598-dt4qm 1/1 Running 0 5h1m 172.17.79.3 192.168.209.132 <none> <none>coredns-5d668bd598-f5g96 1/1 Running 1 18h 172.17.44.2 192.168.209.131 <none> <none>kubernetes-dashboard-cb55bd5bd-gc84g 1/1 Running 0 3h25m 172.17.47.2 192.168.209.133 <none> <none>tiller-deploy-58cf8bbc46-ss5nq 0/1 ImagePullBackOff 3 79s 172.17.44.4 192.168.209.131 <none> <none>
    

    解决无法 pull tiller

    默认镜像用的是gcr.io/kubernetes-helm/tiller:v2.12.3,国内无法下载,需要替换成国内地址

    修改tiller pod 镜像地址为registry.cn-beijing.aliyuncs.com/minminmsn/tiller:v2.12.3

    [root@master-01 helm]# kubectl -nkube-system edit deploy tiller-deploydeployment.extensions/tiller-deploy edited[root@master-02 ~]# kubectl -nkube-system get po -owideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATEScoredns-5d668bd598-dt4qm 1/1 Running 0 5h3m 172.17.79.3 192.168.209.132 <none> <none>coredns-5d668bd598-f5g96 1/1 Running 1 18h 172.17.44.2 192.168.209.131 <none> <none>kubernetes-dashboard-cb55bd5bd-gc84g 1/1 Running 0 3h27m 172.17.47.2 192.168.209.133 <none> <none>tiller-deploy-5bd5fcdbbd-zdrnj 1/1 Running 0 44s 172.17.58.2 192.168.209.130 <none> <none>
    

    结果

    [root@master-01 ~]# helm versionClient: &version.Version{SemVer:"v2.12.3", GitCommit:"eecf22f77df5f65c823aacd2dbd30ae6c65f186e", GitTreeState:"clean"}Server: &version.Version{SemVer:"v2.12.3", GitCommit:"eecf22f77df5f65c823aacd2dbd30ae6c65f186e", GitTreeState:"clean"}
    

    示例演示


    新建一个helm chart

    [root@master-01 helm]# helm create mychartCreating mychart
    

    该命令创建了一个 mychart 目录,该目录结构如下所示。这里我们主要关注目录中的 Chart.yaml、values.yaml、NOTES.txt 和 Templates 目录。

    [root@master-01 helm]# tree mychart/mychart/├── charts├── Chart.yaml├── templates│ ├── deployment.yaml│ ├── _helpers.tpl│ ├── ingress.yaml│ ├── NOTES.txt│ ├── service.yaml│ └── tests│ └── test-connection.yaml└── values.yaml3 directories, 8 files
    

    Chart.yaml 用于描述这个 Chart的相关信息,包括名字、描述信息以及版本等。

    values.yaml 用于存储 templates 目录中模板文件中用到变量的值。

    NOTES.txt 用于介绍 Chart 部署后的一些信息,例如:如何使用这个 Chart、列出缺省的设置等。

    Templates 目录下是 YAML 文件的模板,该模板文件遵循 Go template 语法。

    Templates 目录下 YAML 文件模板的值默认都是在 values.yaml 里定义的,比如在 deployment.yaml 中定义的容器镜像。

    image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
    

    其中的 .Values.image.repository 的值就是在 values.yaml 里定义的 nginx,.Values.image.tag 的值就是 stable。

    [root@master-01 helm]#cat mychart/values.yaml|grep repositoryrepository: nginx[root@master-01 helm]#cat mychart/values.yaml|grep tagtag: stable
    

    以上两个变量值是在 create chart 的时候就自动生成的默认值,你可以根据实际情况进行修改。

    修改应用介绍信息

    [root@master-01 helm]# cat mychart/Chart.yamlapiVersion: v1appVersion: "1.0"description: A Helm chart for Kubernetesname: mychartversion: 0.1.0
    

    修改应用具体编排信息

    [root@master-01 helm]# cat mychart/values.yaml # Default values for mychart.# This is a YAML-formatted file.# Declare variables to be passed into your templates.replicaCount: 1image:repository: nginxtag: stablepullPolicy: IfNotPresentnameOverride: ""fullnameOverride: ""service:type: ClusterIPport: 80ingress:enabled: falseannotations: {}# kubernetes.io/ingress.class: nginx# kubernetes.io/tls-acme: "true"paths: []hosts:- chart-example.localtls: []# - secretName: chart-example-tls# hosts:# - chart-example.localresources: {}# We usually recommend not to specify default resources and to leave this as a conscious# choice for the user. This also increases chances charts run on environments with little# resources, such as Minikube. If you do want to specify resources, uncomment the following# lines, adjust them as necessary, and remove the curly braces after 'resources:'.# limits:# cpu: 100m# memory: 128Mi# requests:# cpu: 100m# memory: 128MinodeSelector: {}tolerations: []affinity: {}
    

    检查配置语法

    [root@master-01 helm]# helm lint mychart==> Linting mychart[INFO] Chart.yaml: icon is recommended1 chart(s) linted, no failures
    

    如果文件格式错误,可以根据提示进行修改。

    打包应用

    [root@master-01 helm]# helm package mychartSuccessfully packaged chart and saved it to: /opt/helm/mychart-0.1.0.tgz
    

    mychart 目录会被打包为一个 mychart-0.1.0.tgz 格式的压缩包,该压缩包会被放到当前目录下,并同时被保存到了 Helm 的本地缺省仓库目录中。

    查看本地仓库

    [root@master-01 helm]# helm repo listNAME URL stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/chartslocal http://127.0.0.1:8879/charts
    

    启动本地仓库

    [root@master-01 helm]# helm serve &[1] 25758[root@master-01 helm]# Regenerating index. This may take a moment.Now serving you on 127.0.0.1:8879
    

    默认情况只监听127.0.0.1,如果你要绑定到其它网络接口,可使用以下命令:

    [root@master-01 helm]#helm serve --address 192.168.209.130:8879 &
    

    部署应用

    [root@master-01 helm]# helm install local/mychart --name test-01NAME: test-01LAST DEPLOYED: Tue Mar 12 16:38:04 2019NAMESPACE: defaultSTATUS: DEPLOYEDRESOURCES:==> v1/ServiceNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEtest-01-mychart ClusterIP 10.254.105.20 <none> 80/TCP 2s==> v1/DeploymentNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGEtest-01-mychart 1 0 0 0 1s==> v1/Pod(related)NAME READY STATUS RESTARTS AGEtest-01-mychart-7d84ff968f-76d2l 0/1 Pending 0 1sNOTES:1. Get the application URL by running these commands:export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=mychart,app.kubernetes.io/instance=test-01" -o jsonpath="{.items[0].metadata.name}")echo "Visit http://127.0.0.1:8080 to use your application"kubectl port-forward $POD_NAME 8080:80
    

    完成部署后,现在 Nginx 就已经部署到 Kubernetes 集群上。在本地主机上执行提示中的命令后,就可在本机访问到该 Nginx 实例。

    [root@master-01 helm]# export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=mychart,app.kubernetes.io/instance=test-1" -o jsonpath="{.items[0].metadata.name}")[root@master-01 helm]# echo "Visit http://127.0.0.1:8080 to use your application"Visit http://127.0.0.1:8080 to use your application[root@master-01 helm]# kubectl port-forward $POD_NAME 8081:80Forwarding from 127.0.0.1:8081 -> 80Forwarding from [::1]:8081 -> 80
    

    在本机访问

    [root@master-01 ~]# curl 127.0.0.1:8081<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style>body { 35em;margin: 0 autofont-family: Tahoma, Verdana, Arial, sans-serif;}......
    

    使用helm ls 可以看到已经部署的Release和对应的Chart

    [root@master-01 ~]# helm lsNAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACEtest-1 1 Tue Mar 12 18:19:41 2019 DEPLOYED mychart-0.1.0 1.0 default
    

    使用helm status可以看到Release状态

    [root@master-01 ~]# helm status test-1LAST DEPLOYED: Tue Mar 12 18:19:41 2019NAMESPACE: defaultSTATUS: DEPLOYEDRESOURCES:==> v1/ServiceNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEtest-1-mychart ClusterIP 10.254.184.247 <none> 80/TCP 7m17s==> v1/DeploymentNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGEtest-1-mychart 1 1 1 1 7m17s==> v1/Pod(related)NAME READY STATUS RESTARTS AGEtest-1-mychart-85459fd5cd-z6twn 1/1 Running 0 7m16sNOTES:1. Get the application URL by running these commands:export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=mychart,app.kubernetes.io/instance=test-1" -o jsonpath="{.items[0].metadata.name}")echo "Visit http://127.0.0.1:8080 to use your application"kubectl port-forward $POD_NAME 8080:80
    

    升级和回退一个应用

    从上面 helm ls 输出的结果中我们可以看到有一个 Revision(更改历史)字段,该字段用于表示某一个 Release 被更新的次数,我们可以用该特性对已部署的 Release 进行回退

    修改 Chart.yaml 文件

    将版本号从 0.1.0 修改为 0.2.0, 然后使用 helm package 命令打包并发布到本地仓库。

    [root@master-01 helm]# cat mychart/Chart.yaml apiVersion: v1appVersion: "1.0"description: A Helm chart for Kubernetesname: mychartversion: 0.2.0[root@master-01 helm]# helm package mychartSuccessfully packaged chart and saved it to: /opt/helm/mychart-0.2.0.tgz
    

    查看本地仓库Chart信息

    可以看到本地有两个版本了

    [root@master-01 ~]# helm search mychart -lNAME CHART VERSIONAPP VERSIONDESCRIPTION local/mychart0.2.0 1.0 A Helm chart for Kuberneteslocal/mychart0.1.0 1.0 A Helm chart for Kubernetes
    

    升级应用

    现在用 helm upgrade 命令将已部署的 mike-test 升级到新版本。你可以通过 --version 参数指定需要升级的版本号,如果没有指定版本号,则缺省使用最新版本。

    [root@master-01 ~]# helm upgrade test-1 local/mychartRelease "test-1" has been upgraded. Happy Helming!LAST DEPLOYED: Tue Mar 12 18:33:18 2019NAMESPACE: defaultSTATUS: DEPLOYEDRESOURCES:==> v1/Pod(related)NAME READY STATUS RESTARTS AGEtest-1-mychart-85459fd5cd-z6twn 1/1 Running 0 13m==> v1/ServiceNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEtest-1-mychart ClusterIP 10.254.184.247 <none> 80/TCP 13m==> v1/DeploymentNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGEtest-1-mychart 1 1 1 1 13mNOTES:1. Get the application URL by running these commands:export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=mychart,app.kubernetes.io/instance=test-1" -o jsonpath="{.items[0].metadata.name}")echo "Visit http://127.0.0.1:8080 to use your application"kubectl port-forward $POD_NAME 8080:80
    

    完成后,可以看到已部署的 mike-test 被升级到 0.2.0 版本。

    [root@master-01 ~]# helm lsNAME REVISIONUPDATED STATUS CHART APP VERSIONNAMESPACEtest-1 2 Tue Mar 12 18:33:18 2019DEPLOYEDmychart-0.2.01.0 default
    

    回退应用

    如果更新后的程序由于某些原因运行有问题,需要回退到旧版本的应用。首先我们可以使用 helm history 命令查看一个 Release 的所有变更记录。

    [root@master-01 ~]# helm history test-1REVISIONUPDATED STATUS CHART DESCRIPTION 1 Tue Mar 12 18:19:41 2019SUPERSEDEDmychart-0.1.0Install complete2 Tue Mar 12 18:33:18 2019DEPLOYED mychart-0.2.0Upgrade complete
    

    我们可以使用下面的命令对指定的应用进行回退。

    [root@master-01 ~]# helm rollback test-1 1Rollback was a success! Happy Helming!
    

    我们使用 helm ls 和 helm history 命令都可以看到 mychart 的版本已经回退到 0.1.0版本。

    [root@master-01 ~]# helm lsNAME REVISIONUPDATED STATUS CHART APP VERSIONNAMESPACEtest-1 3 Tue Mar 12 18:35:52 2019DEPLOYEDmychart-0.1.01.0 default [root@master-01 ~]# helm history test-1REVISIONUPDATED STATUS CHART DESCRIPTION 1 Tue Mar 12 18:19:41 2019SUPERSEDEDmychart-0.1.0Install complete2 Tue Mar 12 18:33:18 2019SUPERSEDEDmychart-0.2.0Upgrade complete3 Tue Mar 12 18:35:52 2019DEPLOYED mychart-0.1.0Rollback to 1
    

    删除应用

    如果需要删除一个已部署的 Release,可以利用 helm delete 命令来完成删除。

    [root@master-01 ~]# helm delete test-1release "test-1" deleted
    

    确认应用是否删除,该应用已被标记为 DELETED 状态。

    [root@master-01 ~]# helm ls -a test-1NAME REVISIONUPDATED STATUSCHART APP VERSIONNAMESPACEtest-1 3 Tue Mar 12 18:35:52 2019DELETEDmychart-0.1.01.0 default
    

    也可以使用 --deleted 参数来列出已经删除的 Release

    [root@master-01 ~]# helm ls --deletedNAME REVISIONUPDATED STATUSCHART APP VERSIONNAMESPACEtest-1 3 Tue Mar 12 18:35:52 2019DELETEDmychart-0.1.01.0 default
    

    从上面的结果也可以看出,默认情况下已经删除的 Release 只是将状态标识为 DELETED 了 ,但该 Release 的历史信息还是继续被保存的。

    [root@master-01 ~]# helm hist test-1REVISIONUPDATED STATUS CHART DESCRIPTION 1 Tue Mar 12 18:19:41 2019SUPERSEDEDmychart-0.1.0Install complete 2 Tue Mar 12 18:33:18 2019SUPERSEDEDmychart-0.2.0Upgrade complete 3 Tue Mar 12 18:35:52 2019DELETED mychart-0.1.0Deletion complete
    

    如果要移除指定 Release 所有相关的 Kubernetes 资源和 Release 的历史记录,可以用如下命令:

    [root@master-01 ~]# helm delete --purge test-1release "test-1" deleted
    

    再次查看已删除的 Release,已经无法找到相关信息

    [root@master-01 ~]# helm hist test-1Error: release: "test-1" not found[root@master-01 ~]# helm ls[root@master-01 ~]# helm ls --deleted[root@master-01 ~]# helm ls -a test-1
    

    部署一个应用示例


    部署 Wordpress

    这里以一个典型的三层应用 Wordpress 为例,包括 MySQL、PHP 和 Apache。由于测试环境暂时没有可用的 PersistentVolume(持久卷,简称 PV),这里暂时将其关闭。关于 Persistent Volumes 的相关信息我们会在后续的相关文章进行讲解。

    [root@master-01 helm]# helm install --name wordpress-test --set "persistence.enabled=false,mariadb.persistence.enabled=false,serviceType=NodePort" stable/wordpressNAME: wordpress-testLAST DEPLOYED: Tue Mar 12 19:13:45 2019NAMESPACE: defaultSTATUS: DEPLOYEDRESOURCES:==> v1beta1/DeploymentNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGEwordpress-test-mariadb 1 1 1 0 3swordpress-test-wordpress 1 1 1 0 3s==> v1/Pod(related)NAME READY STATUS RESTARTS AGEwordpress-test-mariadb-59cfd7c475-27chl 0/1 Pending 0 3swordpress-test-wordpress-6fc9b7cc7f-dt7fq 0/1 ContainerCreating 0 3s==> v1/SecretNAME TYPE DATA AGEwordpress-test-mariadb Opaque 2 4swordpress-test-wordpress Opaque 2 4s==> v1/ConfigMapNAME DATA AGEwordpress-test-mariadb 1 4swordpress-test-mariadb-tests 1 4s==> v1/ServiceNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEwordpress-test-mariadb ClusterIP 10.254.153.229 <none> 3306/TCP 4swordpress-test-wordpress NodePort 10.254.154.132 <none> 80:43629/TCP,443:34204/TCP 3sNOTES:1. Get the WordPress URL:Or running:export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services wordpress-test-wordpress)export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")echo http://$NODE_IP:$NODE_PORT/admin2. Login with the following credentials to see your blogecho Username: userecho Password: $(kubectl get secret --namespace default wordpress-test-wordpress -o jsonpath="{.data.wordpress-password}" | base64 --decode)
    

    部署完成后,用以上提示信息生成访问地址和用户名密码

    [root@master-01 helm]# export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services wordpress-test-wordpress)DE_IP:$NODE_PORT/admin[root@master-01 helm]# export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")[root@master-01 helm]# echo http://$NODE_IP:$NODE_PORT/adminhttp://192.168.209.130:43629/admin[root@master-01 helm]# echo Username: userUsername: user[root@master-01 helm]# echo Password: $(kubectl get secret --namespace default wordpress-test-wordpress -o jsonpath="{.data.wordpress-password}" | base64 --decode)Password: aOTe96YaSP
    

    查看服务状态

    [root@master-01 helm]# kubectl get podNAME READY STATUS RESTARTS AGEdnstools-6b77cc4988-b5smz 1/1 Running 0 23hnginx-7899755b7-7s8fl 1/1 Running 0 24htests-1-mychart-7d84ff968f-76d2l 1/1 Running 0 170mwordpress-test-mariadb-59cfd7c475-27chl 1/1 Running 1 14mwordpress-test-wordpress-6fc9b7cc7f-dt7fq 1/1 Running 4 14m
    

    访问测试

    image

    image

    好了,进行到这 hlem基本使用就结束了,敬请期待后续分享,谢谢!

    往期文章一览

    1、Kubernetes集群搭建之系统初始化配置篇

    2、Kubernetes集群搭建之企业级环境中基于Harbor搭建自己的私有仓库

    3、Kubernetes集群搭建之Etcd集群配置篇

    4、Kubernetes集群搭建之CNI-Flanneld部署篇

    5、Kubernetes集群搭建之Master配置篇

    6、Kubernetes系列之Coredns and Dashboard介绍篇

    7、Kubernetes系列之监控Metres-server实战篇

    END

    如果您觉得不错,请别忘了转发、分享、点赞让更多的人去学习, 您的举手之劳,就是对小编最好的支持,非常感谢!

    image

  • 相关阅读:
    Mybatis-Plus02 CRUD
    Idea一直卡在loading archetype list问题解决(或者报Unable to import maven project: See logs for details)
    Mybatis-Plus01 快速开始
    用PS给视频磨皮美颜
    Linux基本内容
    【maven】IDEA工程右边的maven配置中Plugins有重复的命令
    段誉身具凌波微波,动无常则,若危若安,一次能走一级台阶或者两级台阶,他要爬一段30级的山路,问有多少种走法?分析如何计算,然后编程解答。 进阶问题:当他轻功熟练度提升,一次最多可以走三级,那就结果有什么变化?后来走火入魔了,不能走一级,只能走二或三级,又有什么变化?
    小陆每天要写一份工作日报,日报标题含有日期。几年后,他翻开以前的日报,想知道两份日报的日期是否同为星期几,请编程帮助他判断。
    高性能HTML5/JS开发框架DevExtreme 新版——v20.2.7发布
    跨平台UI开发框架DevExpress XAF 拥有超强性能,你Get了吗?
  • 原文地址:https://www.cnblogs.com/guigujun/p/10550256.html
Copyright © 2011-2022 走看看