docker

Kubernetes 定时任务

参照官方文档,直接比着写就行了:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"  # 也支持 @hourly 这些语法
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

列出所有 cronjob

kubectl get cronjob [JOB_NAME]

NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   */1 * * * *   False     0        <none>          10s

列出 cronjob 所有运行过的实例:

kubectl get jobs --watch

NAME               COMPLETIONS   DURATION   AGE
hello-4111706356   0/1                      0s
hello-4111706356   0/1           0s         0s
hello-4111706356   1/1           5s         5s

需要注意的一点,也是 Linux 上的 cron 命令忽视的一点,K8s 提供了 .spec.concurrentPolicy 选项,
用来选择当上一任务还没有执行完毕时,如何处理并发。

  • Allow,默认选项,只要到了时间就触发,并发执行
  • Forbid,如果上一个任务没有执行完毕,忽略本次任务
  • Replace,如果上一个任务没有执行完毕,用新任务替换掉老任务

参考

  1. https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs/

K3s

k3s 是 rancher 出品的一个 kubernetes 的衍生版,特点是单二进制文件,非常小巧,可以在树莓派上部署。虽然他对好多组件做了替换,比如把 etcd 替换成了 sqlite3,但是他依然是一个通过了官方认证的 Kubernetes 发行版。

除了 k3s 以外,还有一些其他的精简的 k8s 发行版,比如 microk8s, kind, minikube 等等,但是都远远没有 k3s 轻量。详细资料可以看参考资料。

安装完成之后要设置:export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

K3s 还是得用 docker 运行,这样本地使用 docker build 打包出来的镜像才能用上。

加入 Worker 节点

其中 K3S_TOKEN 在服务器的 /var/lib/rancher/k3s/server/node-token 路径中

curl -sfL https://get.k3s.io | K3S_URL=https://myserver:6443 K3S_TOKEN=mynodetoken sh -

存储

k3s 默认带了自己的 local-path provider,所以直接就能使用。

可以使用 rancher 家的 longhorn,安装非常简单

网络

内置的 traefik 竟然会劫持掉 nginx 的 80 端口并且不报错,真是牛逼惨了。好像是利用到了 SO_REUSEPORT 这个选项。是使用了 iptables 属性拦截了 nginx 的流量

禁用 traefik,切换到 nginx

如何禁用内置的 traefik:

k3s 好像无论如何弄都在监听 0.0.0.0,只好用安全组禁用掉端口了。

参考资料

  1. k3s 官方文档。https://rancher.com/docs/k3s/latest/en/quick-start/
  2. https://www.reddit.com/r/kubernetes/comments/be0415/k3sminikubeor_microk8s/
  3. https://brennerm.github.io/posts/minikube-vs-kind-vs-k3s.html
  4. 非常全的一份文档 https://kauri.io/38-install-and-configure-a-kubernetes-cluster-with/418b3bc1e0544fbc955a4bbba6fff8a9/a
  5. https://github.com/k3s-io/k3s/issues/2403
  6. https://blog.oddbit.com/post/2018-02-27-listening-for-connections-on-a/

如何导出 Docker 镜像

可以使用 docker save 和 docker export 导出 docker 镜像。那么两者有什么区别呢?

  • export 是用来导出一个容器的,也就是运行中的镜像。
  • save 是用来导出镜像的,也就是还没有运行的镜像。

这里我们需要用的显然是 docker save。

语法是:

docker save [OPTIONS] IMAGE [IMAGE...]

其中的 options 也就一个参数 -o 文件名。如果不指定 -o 的话直接输出到 stdout,方便使用管道串联。

如果需要压缩的话还可以这样

docker save myimage:latest | gzip > myimage_latest.tar.gz

导出之后,可以使用 docker load 导入镜像。不使用 -i 的话直接从 stdin 读取。

docker load -i FILE

kubernetes 初探——服务治理

服务治理有两种方式:

  • 一种是直接在服务中集成熔断降级等操作
  • 一种是使用 sidecar 模式,有公共组件来处理

两种模式的区别如图:

相关概念

Service. 如果一个 Deployment 对外(pod 外)提供服务的话,可以暴露为 Service。它是服务的抽象,通过 kube-proxy 和 DNS 等提供负载均衡给后端 RC 定义的 Pod。服务一共有三种类型:

  • clusterIP. 服务暴露在集群内部的一个虚拟 IP,生命周期和服务相同
  • nodePort. 暴露在所有 Node 上的服务端口,不建议在生产环境使用。
  • LoadBalancer,通过外部的 LB,暴露对应的服务。

另外还可以使用 Ingress Controller. Service 只是对集群内部暴露了服务,Ingress Controller 用于把 Service 再对外暴露。就相当于一个反向代理。

如果一个 deployment 需要对集群内部或者是外部提供服务的话,可以使用 service。

这时将创建一个 clusterIP,需要特别注意的是,这个 clusterIP 也是虚拟的,并没有和任何 pod 绑定,而是绑定到了服务上,可以理解为绑定到了这个服务对应的内部负载均衡上,并且是不变的。即使你的 Deployment 中指定了多个副本,那么也只有这一个 clusterIP,pod 的创建和销毁也都不会影响到 clusterIP。

使用 ingres 部署一个服务

ClusterIP + Proxy

ClusterIP 是集群内部提供服务的默认方式。可以采用 kubelet proxy + ClusterIP 的方式临时提供对外服务。

NodePort IP

NodePort 方式在每个 host 上都开一个端口来对外提供服务,适合一些临时性的服务。

ClusterIP + LoadBalancer

使用云厂商提供的 LoadBalancer 来提供服务,唯一的缺点是每一个服务会消耗一个 IP,比较贵。

除此之外,还可以使用 MetalLB 等等第三方的 LB。

Ingress + LB

Ingress 并不是 K8S 的一种服务类型,实际上他本身是一个 Service,然后路由到不同的服务。实际上 Ingress 就是反向代理。

但是要暴露出去 Ingress 服务本身,可能还是要使用 NodePort 或者 LoadBalancer。

Internet -> Loadbalancer -> Ingress Controller -> Ingress Rules -> K8s-Services -> Replicas

Internet -> NodePort -> Ingress Controller -> Ingress Rules -> K8s-Services -> Replicas

在 K3S 中自带了一个 service lb,所以只要确保 ingress controller 第一个暴露出 loadbalancer 类型的 80 端口,就能自动通过 80 端口访问了。

Traefik Ingress Controller

Traefik 在 2.0 中不止支持了 K8S 默认的 ingress,还自己定义了一种 IngressRoute 类型的 CRD。千万不要用这个 IngressRoute,他还不支持证书,简直就是废物。

尽量不要使用 Traefik,bug 太多了。

nginx Ingress Controller

nginx 毕竟是官方支持的 ingress,比较稳定,而且性能也比较好。

Cert Manager

Cert-Manager 用于管理 k8s 集群内部的证书。

Cert-Manager 中最重要的三个概念是:

  • Issuer, 只能在当前命名空间颁发证书
  • ClusterIssuer,可以在所有 namespace 颁发证书
  • Certificate, 颁发的证书

实战

安装 cert-manager

kubectl create namespace cert-manager
kubectl apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml
kubectl get pods -n cert-manager  # 可以看到几个在运行的 pod

安装一个 clusterissuer

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: <your>@<email>.<com> # 替换掉这个
    privateKeySecretRef:
      name: prod-issuer-account-key
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
      - http01:
          ingress:
            class: traefik
        selector: {}

然后,kubectl apply -f letsencrypt.yaml

安装一个服务

# 创建一个简单的 deployment,这里我们用了 rancher logos 这个服务
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rancher-logo-app
  namespace: default
spec:
  selector:
    matchLabels:
      name: rancher-logo-backend
  template:
    metadata:
      labels:
        name: rancher-logo-backend
    spec:
      containers:
        - name: backend
          image: ruanbekker/logos:rancher
          ports:
            - containerPort: 80
---
# 创建一个 service,暴露了 80 端口,注意这里不需要暴露 443 端口
apiVersion: v1
kind: Service
metadata:
  name: rancher-logo-service
  namespace: default
spec:
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 80
  selector:
    name: rancher-logo-backend
---
# 创建一个 ingress,并在 annotation 中指定了使用 cluster-issuer
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: rancher-logo-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: traefik  # 使用 traefik 做 ingress
    cert-manager.io/cluster-issuer: letsencrypt-prod  # 使用 cluster-issuer
    traefik.ingress.kubernetes.io/redirect-entry-point: https  # TODO 貌似没用
spec:
  tls:
    - secretName: logos-ezly-tv-tls
      hosts:
        - logos.ezly.tv
  rules:
  - host: logos.ezly.tv  # ingress 的配置
    http:
      paths:
        - path: /
          backend:
            serviceName: rancher-logo-service  # 指向的 service
            servicePort: 80

需要注意的是,https 证书的发放需要时间,可能得等 1 分钟左右才能从 https 协议访问。

参考

  1. https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0
  2. https://stackoverflow.com/questions/45079988/ingress-vs-load-balancer
  3. https://sysadmins.co.za/https-using-letsencrypt-and-traefik-with-k3s/
  4. https://medium.com/@jmrobles/free-ssl-certificate-for-your-kubernetes-cluster-with-rancher-2cf6559adeba
  5. 这个教程有些问题,但是也有参考价值。https://community.hetzner.com/tutorials/howto-k8s-traefik-certmanager
  6. https://stackoverflow.com/questions/58553510/cant-get-certs-working-with-cert-manager
  7. Service Mesh 的本质、价值和应用探索
  8. Istio, K8S 的微服务支持
  9. 微服务之熔断、降级、限流
  10. 微服务化之服务拆分与服务发现
  11. https://sysadmins.co.za/https-using-letsencrypt-and-traefik-with-k3s/

kubernetes 初探——使用 helm 部署服务

在 k8s 上部署一个应用还是有些复杂的,自己的应用当然需要自己一步一步部署,但是对于一些通用的应用,比如说 mysql/grafana 这种就没必要自己手工一步一步部署了。这时候就有了 helm, 通俗的来说他就是 kubernetes 上的 AppStore 或者是 apt-get, 常见的应用都已经在了,而且你也可以按照他的格式打包自己的应用部署。

Helm 是 Kubernetes 的一个包管理器,你可以把他理解为 apt-get 或者 App Store。Helm 中的一个包称为一个 Chart,每次安装后称为一个 Release, Release 还有 revision,可以升级或者回滚到之前的 revision。 Helm 中还可以添加不同的 repo,repos 中有不同的 Chart。

安装

在 helm 的 release 页面下载,然后拷贝到本地的 /usr/local/bin/ 目录就好了。helm 运行的时候会使用 ~/.kube/config 文件,所以本地 kubectl 可以使用就好了。

使用

添加仓库

helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts  # 国内镜像

安装包(Chart)

helm repo update  # 更新仓库
helm install stable/mysql --generate-name  # 安装一个包

列出值

helm list  # 列出当前的 release
helm status RELEASE # 查看状态

自定义安装

helm show values stable/mysql
helm install -f config.yaml stable/mysql --generate-name

升级和回滚

helm upgrade
helm rollback

卸载

helm uninstall

概念

  • Chart, 大概相当于 package 的意思
  • Repository, Helm 的中心仓库
  • Release, 每次运行一个 Chart 都会生成一个 Release, 每个 release 之间是独立的。Chart/Release 的关系就好比 Docker 的 Image/Container 一样。
  • Revision, 每次更新 Release 都会产生一个新的版本,可以回滚

基础命令

helm search 查找相关的软件包 (chart), 现在 stable 仓库中大概有 200 多个包可以安装。

helm install –name NAME PACKAGE 会安装对应的 chart. 如果不指定 name, 会自动生成一个。

helm status NAME 可以查看对应的包的信息,一般包括了如何连接使用这个包等信息,可以当做帮助来用。

在安装包之前更改配置

每个 helm chart 都定义了一些默认的配置,可以在安装之前查看并修改这些值。

helm inspect values stable/mysql 查看 mysql 的默认值。或者去 GitHub 上看这个仓库的 readme.

把需要覆盖的变量写到 OVERRIDE.yml 文件中,helm install -f OVERRIDE.yml stable/mysql 就可以使用自己的配置了

更新 release

如果需要更新一个 release, 可以使用 helm upgrade -f OVERRIDE.yml RELEASE_NAME 命令更新相关的配置。这时就会创建一个新的版本。

使用 helm list 可以查看当前部署的 release, 这时候我们可以看到部署的版本变成了 2 (最初是 1).

-> % helm ls
NAME    REVISION        UPDATED                         STATUS          CHART           NAMESPACE
mysql   1               Sat Oct  6 15:44:25 2018        DEPLOYED        mysql-0.3.0     default

如果当前的更新有误,可以回退到之前的版本,语法是 helm rollback [RELEASE] [REVISION]

管理 repo

helm repo list 列出当前添加的 repo

$ helm repo list
NAME            URL
stable          https://kubernetes-charts.storage.googleapis.com
local           http://localhost:8879/charts
mumoshu         https://mumoshu.github.io/charts

添加新的 repo

$ helm repo add dev https://example.com/dev-charts

基本使用

参考

  1. https://docs.helm.sh/
  2. https://ezmo.me/2017/09/24/helm-quick-toturial/
  3. https://help.aliyun.com/document_detail/58587.html
  4. https://johng.cn/helm-brief/
  5. https://www.lixf.io/2019/06/13/k8s-docker-images-mirrors/

kubernetes 初探——部署有状态服务

为了部署我们自己的应用, 首先需要把基础设施部署上去, 其中数据库就占了大头. 有人说数据库等应用不是和容器化部署, 但是也有人认为所有的应用都适合容器化部署. 在这里我们不讨论这些观点,仅以部署 MySQL 为例说明一下如何在 K8S 上部署有状态服务。

相关概念

  • PersistentVolume(PV) 是集群之中的一块网络存储。跟 Node 一样,也是集群的资源。PV 跟 Volume 类似,不过会有独立于 Pod 的生命周期。这一 API 对象包含了存储的实现细节,例如 NFS、iSCSI 或者其他的云提供商的存储系统。
  • PersistentVolumeClaim (PVC) 是用户的一个请求。他跟 Pod 类似。Pod 消费 Node 的资源,PVC 消费 PV 的资源。Pod 能够申请特定的资源(CPU 和 内存);Claim 能够请求特定的尺寸和访问模式(例如可以加载一个读写,以及多个只读实例)
  • Stateful Set. Deployment 是无状态的服务,而 StatefulSets 旨在与有状态的应用及分布式系统一起使用。
  • ConfigMap 用来保存非密码的配置. configmap 可以以配置文件或者环境变量等方式挂在到 pod 中
  • Secret 用来保存密码等私密数据
  • Init Container 用于初始化的容器. 有点类似于 docker build 的过程

动态 PV vs 静态 PV

使用 Deployment PVC 还是 Stateful Set

可以看出我们即可以使用普通的 Deployment + PVC 来部署 MySQL, 也可以使用 Stateful Set 来部署, 那么哪种方式更好呢?

个人理解:

  • 对于需要使用挂载一定资源的,使用 PVC 就好了,甚至只需要只读挂载就好。
  • 对于强状态依赖的服务,比如数据库,肯定要使用 PVC

Stack Overflow 上的一个问题[2]也很值得参考.

MySQL 主从集群

本文中我们要部署一个一主多从的 MySQL 集群. 关于一主多从的优点不是本文的重点, 这里就不多说了, 可以参考下面:

  1. 扩容解决方案:在多个slave之间扩展负载以提高性能。在这种模式下,所有的写入和更新操作都必须在主服务器上进行。然而,读取操作通过slave镜像。该模型可以提高写入操作的性能,同时,也能够通过增加slave的节点数量,从而显著地提升读取速度。
  2. 数据安全:数据从master被复制到slave,并且slave可以暂停复制过程。因此,可以在不损坏master的情况下,在slave上运行备份服务。
  3. 分析:现场数据可以在master上创建,而对信息的分析可以在slave进行,而不影响master的性能。
  4. 远程数据分发:可以使用复制为远程站点创建本地数据的副本,而不必一直通过访问master。

参考

  1. 使用 PVC 和 Deployment 部署单实例 MySQL 集群
  2. https://stackoverflow.com/questions/41732819/why-statefulsets-cant-a-stateless-pod-use-persistent-volumes
  3. 使用普通 Deployment 部署单节点 mysql
  4. https://kubernetes.io/cn/docs/tutorials/stateful-application/basic-stateful-set/
  5. 如何使用 ConfigMap
  6. Secret 文档
  7. https://stackoverflow.com/questions/41583672/kubernetes-deployments-vs-statefulsets
  8. 阿里云挂载 OSS Volume
  9. https://jimmysong.io/posts/kubernetes-persistent-volume/
  10. https://kubernetes.io/docs/concepts/storage/volumes/

kubernetes 初探——部署无状态应用

kubernetes 架构图

  • Master. 用于控制整个集群部署的机器,为了高可用,可以使用多台,一般至少三台为宜。
  • Node. 工作节点,用于部署服务。一台机器可以既是 Master 也是 Worker,当然最好 Master 不要做 Worker。
  • Pod. k8s 部署的最小单元,一个 Pod 中可能包含多个 container. Pod 随时可能挂掉,也可能被替换。
  • Label. Pod 的标签,可以通过这些标签(组合)来选择对应的 Pod。
  • Replica Set. 作为一个高可用的系统,每个服务一般来说可能有多个 Pod. Replication Set 用来创建并保证有足够的 Pod 副本。RS 的名字总是 <Deployment 的名字>-<pod template 的 hash 值> 格式的。
  • Deployment. 用户一般来说不会直接创建 Pod, 而是创建一个 Deployment 来部署服务。(在老版本中是创建 RC)
  • Namespace. 命名空间,默认情况下会有 kube-system 和 default 两个命名空间,kube-system 存放的是 k8s 系统的 pod 等资源,而用户部署的资源会在 default 命名空间中。
  • PersistendVolume. 如果把 Node 理解为抽象的 CPU 和内存资源,那么 PV 就可以理解为抽象的硬盘资源。我们通过 Pod 来使用 Node,因此我们也不直接使用 PV,而是通过 PersistentVolumeClaim 来使用 PV。
  • PersistentVolumeClaim. 存储声明,用来声明需要使用的存储资源。
  • Stateful Set. Deployment 对应的部署语义上没有状态的,而 StatefulSet 会充分运用 PV 和 PVC 实现 Pod 重启之间的状态保持。典型应用场景是数据库。
  • Label 和 Selector. K8S 中的资源全部都通过 Label 来标识的选择。

Deployment

deployment 是使用 k8s 部署服务直接操作的概念。其他的概念往往都是通过 deployment 来间接使用的,因此理解 deployment 至关重要。

一个典型的 deployment 配置文件如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80

deployment 的配置可以发生改变,如果只是 replica 的数目发生了改变,那么这只是一个简单的扩容或者缩容操作,k8s 只会简单的打开或者杀死新的 Pod。如果镜像、命令等参数发生了改变,那么 K8S 会把这次操作视为升级,也就是开始一个 RollOut 操作,创建新的 ReplicaSet。在这个过程中,如果 deployment 中的 spec 指定了保留历史 revision 的次数大于零,那么原有的 ReplicaSet 不会被删除,只是会被 Scale 到 0 而已,方便回滚。

文档:

Note: a Deployment’s rollout is triggered if and only if the Deployment’s pod template (i.e. .spec.template) is changed, e.g. updating labels or container images of the template. Other updates, such as scaling the Deployment, will not trigger a rollout.

https://stackoverflow.com/questions/42561791/right-way-to-update-deployments-on-kubernetes

  1. deployment 和 pod 的区别
  2. Kubernetes 基础概念
  3. 客户端和服务端服务发现
  4. kubernetes 命令表
  5. Kubernetes 之 kubectl 常用命令使用指南:1: 创建和删除
  6. Kubernetes 之 kubectl 常用命令
  7. Kubernetes 基本概念以及术语
  8. kubectl create 和 apply 的区别
  9. https://stackoverflow.com/questions/42561791/right-way-to-update-deployments-on-kubernetes
  10. https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
  11. K8S YAML 文件基础
  12. NodePort/LB/Ingress 三种方式的对比
  13. https://kubernetes.io/docs/tasks/access-application-cluster/service-access-application-cluster/

kubernetes 初探——日志收集

在 K8S 中,默认情况下,日志分散在每个不同的 Pod, 可以使用 kubectl logs 命令查看,但是当 Pod 被删除之后,就无法查看该 Pod 的日志了,所以需要一种永久性的保存方式。

Grafana 公司推出的 Loki 看起来不错,完全为 Kubernetes 设计,直接和 grafana 集成。

kubernetes 初探——部署集群

随着 docker cloud 的关闭,容器的编排工具之争似乎已经结束了,Docker Swarm 算是完了,Kubernetes 笑到了最后。然而 k8s 的组件众多,因此部署起来也很麻烦。为此,网上有不少的网上有不少的部署教程和脚本,包括但不限于:

  • kubesaz
  • minikube
  • kubespray
  • rke
  • kubernetes: the hard way

本文基于 ubuntu 18.04. CentOS 上好多默认设置都需要修改,因此建议大家基于 Ubuntu 部署 k8s。

使用 kubespray 安装 k8s

(零) 假设我们要在三台机器上安装,另外一台机器作为控制节点。其中每台都作为工作节点,两台作为 master 节点。

| IP | 角色 |
| ———– | ————————- |
| 10.4.17.165 | 控制节点,不参与 k8s 集群 |
| 10.4.17.167 | master, node |
| 10.4.17.168 | master, node |
| 10.4.17.169 | node |

(一) 下载 kubespray 安装包,这里我们使用最新版 (2018.10), 可能需要安装 python3, 如果没有安装的话

VERSION=2.7.0

# download kubespray
wget https://github.com/kubernetes-incubator/kubespray/archive/v${VERSION}.tar.gz
tar xzf v${VERSION}.tar.gz

# install dependencies
pip3 install -r kubespray-${VERSION}/requirements.txt

(二) 生成部署的 hosts.ini

kubespray 中有一个脚本叫做 inventory_builder 用来生成部署的 hosts.ini

cd kubespray-v${VERSION}
cp -r inventory/sample inventory/mycluster
declare -a IPS=(10.4.17.167 10.4.17.168 10.4.17.169)
CONFIG_FILE=inventory/mycluster/hosts.ini python3 contrib/inventory_builder/inventory.py ${IPS[@]}

生成之后,可以查看生成的文件

cat inventory/mycluster/host.ini

[k8s-cluster:children]
kube-master·▸   ·
kube-node·▸ ·

[all]
node1 ▸  ansible_host=10.4.17.167 ip=10.4.17.167
node2 ▸  ansible_host=10.4.17.168 ip=10.4.17.168
node3 ▸  ansible_host=10.4.17.169 ip=10.4.17.169

[kube-master]
node1·▸ ·
node2·▸ ·

[kube-node]
node1·▸ ·
node2·▸ ·
node3·▸ ·

[etcd]
node1·▸ ·
node2·▸ ·
node3·▸ ·

[calico-rr]

[vault]
node1·▸ ·
node2·▸ ·
node3·▸ ·

(三) 修改一些配置

代理:

由于众所周知的原因,k8s 依赖的 gcr.io 在中国大陆范围内无法访问,我们可以使用代理访问,关于如何搭建代理超出了本文的范围。
假设我们的代理是 http://proxy.com:10086, 修改 inventory/mycluster/group_vars/all/all.yml 文件,设置 httpproxy 和 httpsproxy 两个变量。

下载 kubectl 到本机:

设置 kubectllocalhost 和 kubeconfiglocalhost 两个变量为 true. 安装完成后会在本机安装 kubectl, 并且可以使用 inventory/mycluster/artifacts/admin.conf 使用 kubectl.

(四) 部署

这里我们需要使用 tiger 用户,这个用户需要满足如下条件:

  1. 可以无密码远程登录
  2. 在远程主机上具有无密码 sudo 权限
ansible-playbook -i inventory/mycluster/hosts.ini cluster.yml --become --user tiger -v

大概过十几分钟就部署好了

(五) 验证

cd inventory/mycluster/artifacts
./kubectl.sh get nodes

NAME      STATUS    ROLES         AGE       VERSION
node1     Ready     master,node   1d        v1.11.3
node2     Ready     master,node   1d        v1.11.3
node3     Ready     node          1d        v1.11.3

参考

  1. HN 上关于 Docker Cloud 关闭的讨论
  2. 使用 Kubespray 部署生产可用的 Kubernetes 集群
  3. https://1byte.io/developer-guide-to-docker-and-kubernetes/

Docker Compose 教程

使用 docker run 运行容器的时候经常需要加很多的参数,每次都输入这么多参数很容易出错。另外我们经常需要同时运行若干个容器来构成一个服务,此时还是涉及到网络的联通等操作。docker compose 可以把 docker 执行时的参数写成 yaml 文件,运行的时候只需要 docker-compose up -d 一下就可以了。

话不多说,下面通过一个例子来学习一下 docker-compose.yml 文件的语法。

version: "3"  # 版本号,目前最新版是 3,不同版本的语法是不太兼容的
services:  # 定义的服务,注意是一个字典
  web:
    build: .
    environment:  # 定义环境变量
      - ENV=prod
    depends_on:  # 该服务依赖下面的两个服务,也就是指定了
      - db
      - redis
    command: python app.py
  redis:
    image: redis  # 使用 dockerhub 上的 redis 镜像,这里也可以填自己的私有地址
    network_mode: host|none
    ports:
     - "3000"
     - "3000-3005"
     - "8000:8000"
     - "9090-9091:8080-8081"
     - "49100:22"
     - "127.0.0.1:8001:8001"
     - "127.0.0.1:5000-5010:5000-5010"
     - "6060:6060/udp"
  db:
    image: postgres
    volumes:
      - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
      - "dbdata:/var/lib/postgresql/data"  # 挂载的库,为了和 yaml 语法兼容,必须用引号

volumes:  # 定义的卷
  dbdata:
  1. version 指定了当前 docker-compose 文件的版本
  2. services 服务列表,是一个 “服务名:配置” 的字典。这里定义了三个服务:
    web/redis/db
  3. build,docker build 命令的参数,用来指定需要构建的 dockerfile
  4. image,如果镜像不需要自己构建,而是使用 dockerhub 上的基础镜像,可以直接使用
    image 指令
  5. depends_on 指定当前的服务依赖的服务,从而确定启动顺序
  6. ports 开放的端口的数组,有三种形式:
    1. “3000” 容器中开放的端口
    2. “3000:3000” 开放容器中的端口到宿主机
    3. “127.0.0.1:3000:3000” 开放容器中的端口到宿主机并绑定 IP
  7. environment 环境变量
  8. volumes 挂载的卷,可以使用 named volume 或者是挂载目录,建议不要使用匿名卷。如果使用 named volume,必须在 volumes 下声明

运行服务

docker-compose 有以下 3 个常用命令:

  1. docker-compose up [-d] [SERVICE] 启动服务,-d 表示以 daemon 形式后台运行,并且会在重启后自动启动。
  2. docker-compose run
  3. docker-compose stop

横向伸缩拓展

docker-compose 提供了 --scale 选项来实现横向伸缩。

--scale SERVICE=NUM        Scale SERVICE to NUM instances. Overrides the
                           `scale` setting in the Compose file if present.

但是前提是不能在宿主机上绑定了同一个端口。比如说这样是不行的:

services:
  web:
    ports:
      - "5000:5000"

那么改成 ports: - "5000" 行吗?实际上这还是在宿主机上暴露了一个随机端口,其实没有必要。在接下来的方法中,我们使用 expose 指令。

我们使用 expose 指令,只在容器网络内部暴露端口而不绑定宿主机任何端口,只有 container 网络内部才能访问。另外需要注意的是 Dockerfile 中的 EXPOSE 命令现在已经变成空指令了,也就是加不加没有区别。

expose:
  - "5000"

那么这时候怎么在外部访问服务呢?我们可以加一个 load balancer, 比如 nginx

user nginx;

events {
  worker_connections   1000;
}
http {
  server {
    listen 4000;
    location / {
      proxy_pass http://web:5000;
    }
  }
}
services:
  web:
    expose:
      - "5000"
  nginx:
    image: nginx:latest
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - web
    ports:
      - "4000:4000"

这样就可以起多个副本啦:

docker-compose up --scale web=5

另外,还可以使用这两个镜像:

  1. https://github.com/nginx-proxy/nginx-proxy
  2. https://nginxproxymanager.com/screenshots/

几个神坑

目录重命名之后

docker-compose 中默认的 project name 就是目录的名字,而 volume 的名字会添加上 project 的前缀,也就是 <project>_<volume>. 这就带来了一个问题,当我们把 docker-compose.yml 移动路径的时候,原来的 volume name 就和新的对应不上了,简直神坑。

docker volume create --name <new_volume>
docker run --rm -it -v <old_volume>:/from -v <new_volume>:/to alpine ash -c "cd /from ; cp -av . /to"
docker volume rm <old_volume>

挂载当前目录的文件

如果要挂载目录或者文件到 docker 中的话,默认是以 docker-compose.yml 文件的路径为依据的,简直奇葩。

volumes:
  - ./deploy/prom.yml:/etc/prometheus/prometheus.yml

要改成 $PWD 才以运行命令的目录为根目录查找文件:

volumes:
  - $PWD/deploy/prom.yml:/etc/prometheus/prometheus.yml

参考

  1. https://github.com/moby/moby/issues/31154
  2. https://stackoverflow.com/questions/50630932/rename-docker-compose-project-without-deleting-volumes
  3. https://pspdfkit.com/blog/2018/how-to-use-docker-compose-to-run-multiple-instances-of-a-service-in-development/
  4. https://stackoverflow.com/questions/40801772/what-is-the-difference-between-docker-compose-ports-vs-expose