手動搭建kubernetes集羣

手動搭建kubernetes集羣

探索kubernetes系列的第三篇,主要記錄手動搭建k8s集羣的過程,部署dashboard, 部署DNS用做服務發現。順便記錄一下k8s中的一些資源的概念。node

配置環境

這個步驟能夠參考《Flannel with Docker》文中的步驟,不想贅述了。用了 centos/7 這個鏡像,須要多作一點工做。nginx

安裝 Guest Additions

vagrant plugin install vagrant-vbguest ,這時在 Vagrantfile 中不要設置目錄映射, 添加如下配置git

config.vbguest.auto_update = false
# do NOT download the iso file from a webserver
config.vbguest.no_remote = true

vagrant up
vagrant vbguest
這時會自動安裝 Guest Additions, 再關閉vm,配置上目錄映射,再up,就能夠了. 家裏的網絡鏈接 centos 官方的源,速度還行, 能夠不用改爲國內源。github

國內源

sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak

wget http://mirrors.163.com/.help/CentOS7-Base-163.repo -O CentOS-Base.repo
mv CentOS-Base.repo /etc/yum.repos.d/

sudo yum makecache
sudo yum update

Install Docker

sudo tee /etc/yum.repos.d/docker.repo <<-'EOF'
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/7/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF

sudo yum install docker-engine

sudo systemctl enable docker.service

sudo systemctl start docker

配置 http_proxy, 這步極爲重要,一些必要的鏡像要從谷歌的源下載, 感謝萬惡的GWF:web

vim /lib/systemd/system/docker.service 在[Service]中添加:
EnvironmentFile=-/etc/docker/docker.conf
這裏的 減號 表示若是文件不存在,則忽略錯誤redis

vim /etc/docker/docker.conf 添加:docker

http_proxy=192.168.0.2:7777
https_proxy=192.168.0.2:7777
no_proxy=localhost,127.0.0.1,192.168.0.2

重啓 dockerdvim

Start etcd

etcd --listen-client-urls 'http://0.0.0.0:2379,http://0.0.0.0:4001' --advertise-client-urls 'http://192.168.0.2:2379,http://192.168.0.2:4001' > /dev/null 2>&1 &segmentfault

Install Kubernetes

去 github release page 下載最新的版本. 大約 1G.centos

Start Master

啓動 apiserver
hyperkube apiserver --address=0.0.0.0 --etcd_servers=http://192.168.0.2:2379 --service-cluster-ip-range=10.10.0.0/16 --v=0 >apiserver.log 2>&1 &

啓動 controller-manager
hyperkube controller-manager --master=127.0.0.1:8080 --logtostderr=true >cm.log 2>&1 &

日誌有有報錯,pem 文件找不到,可能和下面這兩個配置有關,須要搜索
--cluster-signing-cert-file string                                  Filename containing a PEM-encoded X509 CA certificate used to issue cluster-scoped certificates (default "/etc/kubernetes/ca/ca.pem")
--cluster-signing-key-file string                                   Filename containing a PEM-encoded RSA or ECDSA private key used to sign cluster-scoped certificates (default "/etc/kubernetes/ca/ca.key")

hyperkube scheduler --master=127.0.0.1:8080 > scheduler.log 2>&1 &

提示 Could not construct reference... due to: 'selfLink was empty, can't make reference'

Start Node

啓動 proxy:
hyperkube proxy --master=192.168.0.2:8080 --logtostderr=true >proxy.log 2>&1 &

安裝DNS的部分有提到,kubelet 要添加兩個啓動參數, 完整的啓動命令爲:

hyperkube kubelet --api_servers=192.168.0.2:8080 --address=0.0.0.0 --hostname_override=bq-node1 --healthz-bind-address=0.0.0.0 --logtostderr=true --cluster-dns=10.10.0.10 --cluster-domain=cluster.local >kubelet.log 2>&1 &

基本操做

建議走一遍官網的 tutorial, 基本能瞭解經常使用的 資源類型, 我在github的倉庫作了筆記,能夠參考個人 筆記

Dashboard

在 kubernetes-src/cluster/addons/dashboard 中有 yaml 文件,使用 kubectl create -f dashboard.yaml 便可建立 dashboard deployment.

啓動 dashboard 的以前,須要 打開一段註釋,args: - --apiserver-host=http://192.168.0.2:8080,
不然 dashboard 沒法啓動

kubectl describe pods/kubernetes-dashboard-3985220203-j043h --namespace=kube-system
看到event信息報錯, 啓動其餘 image 的時候也有這個錯,須要查找, 
MissingClusterDNS, kubelet does not have ClusterDNS IP configured and cannot create Pod using "ClusterFirst" policy. Falling back to DNSDefault policy.

kubelet log顯示 CPUAccounting not allowed ,
這個問題估計是 systemd 控制的

啓動後界面以下:
圖片描述

安裝 skyDNS

進入 kubernetes/cluster/addons/dns/ 目錄, 須要使用到 skydns-rc.yaml.in, skydns-svc.yaml.in, 這兩個文件。

  1. rc 須要替換的變量:

    replica = 1
    dns_domain = cluster.local

    kube-dns 啓動參數須要指定 master 的接口

    args:
    # command = "/kube-dns"
    - --kube-master-url=http://192.168.0.2:8080
  2. svc 須要替換的變量:

    dns_server = 10.10.0.10 // 這個ip須要在 apiserver 的啓動參數--service-cluster-ip-range設置的ip段 裏面,隨意定義一個.

    用kubectl create -f 啓動 rc , svc.

    kubelet 啓動參數須要加入 --cluster-dns=10.10.0.10 --cluster-domain=cluster.local

完整的啓動命令爲:

hyperkube kubelet --api_servers=192.168.0.2:8080 --address=0.0.0.0 --hostname_override=bq-node1 --healthz-bind-address=0.0.0.0 --logtostderr=true --cluster-dns=10.10.0.10 --cluster-domain=cluster.local >kubelet.log 2>&1 &

觀察啓動結果:

kubectl get rc --namespace=kube-system
kubectl get svc --namespace=kube-system

最後在 node機 上測試 DNS:
dig @10 .10.0.10 hello.default.svc.cluster.local

;; ANSWER SECTION:
hello.default.svc.cluster.local. 30 IN  A   10.10.83.26

這裏 hello 是 以前起的一個deploy,配置文件以下。建議仍是分開兩個文件寫,hello-service.yaml, hello-deploy.yaml

kind: Service
apiVersion: v1
metadata:
  labels:
    app: hello
  name: hello
spec:
  ports:
  - port: 9090
  selector:
    run: hello
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hello
spec:
  replicas: 1
  template:
    metadata:
      labels:
        run: hello
    spec:
      containers:
      - name: hello
        image: silentred/alpine-hello:v1
        ports:
        - containerPort: 9090

若是想升級 image:
kubectl set image deploy/hello hello=silentred/alpine-hello:v2

其實還有更好的辦法: kubectl replace -f , 後面會提到.

這裏有一個點須要注意:
只有service會被註冊到 kube-dns 中,按照上面的service的定義,每一個類型的服務都須要建立一個service, 所以每一個類型的服務都會建立一個 clusterIP,clusterIP 的 backends 就是 EndPoints 的服務,默認是 round-robin 輪詢。

這樣雖然省事,可是clusterIP在任意node節點上都是能夠訪問的,有些內部的RPC只但願在集羣container內部訪問,而不想暴露到外部。還有有些服務,就只起一個實例, --replicas永遠爲1,那麼就不須要再分配一個 clusterIP了, 減小耦合。 這樣的話,可使用 Headless Service, 就是把 spec.clusterIP 設爲 None, 這樣就不會給service分配clusterIP, 若是使用了 selector, dns裏查到的就是容器的ip。
詳情看文檔

服務發現

進入容器內部:

docker exec -it CONTAINER_ID bash

// resolv.conf 的內容,估計是 dns 插件自動生成的
bash-4.4# cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local pek2.qingcloud.com.
nameserver 10.10.0.10
options ndots:5

安裝完kube-dns插件後,在容器內部使用DNS查找到的ip爲該 service 的 clusterIP, 在容器內部ping自身的name(hello)能夠看到解析出來的ip, 可是ping的包所有丟失了,文檔解釋是隻支持 tcp/udp 通訊。 doc
這表示,在程序中直接使用 dial("default.svc.cluster.local"), 就能經過 service 去輪詢各個 container, 能夠不用實現 grpc 的 LB 策略了。

結合 Flannel

若是使用了 Headless Service, 那麼就必須保證容器間的網絡連通,能夠採用 flannel。
flannel 配置的子網範圍 不能和 apiserver 的 clusterIP 一致。

資源類型

ConfigMap

用於容器鏡像和配置文件之間的解耦, 能夠用 kubectl create configmaps 來建立,也能夠用yaml來建立,貼一個文檔上的例子:

apiVersion: v1
data:
  game.properties: |-
    enemies=aliens
    lives=3
  ui.properties: |
    color.good=purple
    color.bad=yellow
kind: ConfigMap
metadata:
  creationTimestamp: 2016-02-18T18:34:05Z
  name: game-config
  namespace: default
  resourceVersion: "407"-
  selfLink: /api/v1/namespaces/default/configmaps/game-config
  uid: 30944725-d66e-11e5-8cd0-68f728db1985

在容器中使用有多種方式:
1 定義爲環境變量,下面有例子,定義了環境變量,還能夠用做啓動參數

apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: gcr.io/google_containers/busybox
      command: [ "/bin/sh", "-c", "echo $(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)" ]
      env:
        - name: SPECIAL_LEVEL_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.how
        - name: SPECIAL_TYPE_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.type
  restartPolicy: Never

2 做爲volume使用,mount到鏡像的指定目錄:

apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: gcr.io/google_containers/busybox
      command: [ "/bin/sh", "-c", "cat /etc/config/special.how" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: special-config
  restartPolicy: Never

測試

定義一個 ConfigMap:

kind: ConfigMap
apiVersion: v1
metadata:
  creationTimestamp: 2016-02-18T19:14:38Z
  name: my-config
  namespace: default
data:
  example.foo: bar
  example.long_file: |-
    test.1=value-1
    test.2=value-2

修改 hello-deploy.yaml:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hello
spec:
  replicas: 1
  template:
    metadata:
      labels:
        run: hello
    spec:
      containers:
      - name: hello
        image: silentred/alpine-hello:v2
        ports:
        - containerPort: 9090
        env:
          - name: CONFIG_FOO
            valueFrom:
              configMapKeyRef:
                name: my-config
                key: example.foo

替換原來的deployment, kubectl replace -f hello-deploy.yaml, 在運行這個命令以前能夠在 node 機上 用 docker ps 觀察一下 hello container的 id, 運行後在看一下,會發現二者是不同的,說明container 重啓了。

這時,再次進入 hello container 內部,env | grep FOO 能夠看到效果。

bash-4.4# env | grep FOO
CONFIG_FOO=bar

Secret

和 configmap 相似,只是 value 是 base64 encoded. 建立:

apiVersion: v1
data:
  password: MWYyZDFlMmU2N2Rm
  username: YWRtaW4=
kind: Secret
metadata:
  creationTimestamp: 2016-01-22T18:41:56Z
  name: mysecret
  namespace: default
  resourceVersion: "164619"
  selfLink: /api/v1/namespaces/default/secrets/mysecret
  uid: cfee02d6-c137-11e5-8d73-42010af00002
type: Opaque

一樣能夠選擇 mount 到 path或者 環境變量:

apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
    - name: mycontainer
      image: redis
      env:
        - name: SECRET_USERNAME
          valueFrom:
            secretKeyRef:
              name: mysecret
              key: username
        - name: SECRET_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysecret
              key: password
  restartPolicy: Never

imagePullSecrets

在從私有registry 拉取鏡像時,能夠用 secret 來設定 username, password, 參考文檔

限制

  1. 對於依賴secret 的pod,必須先設定secret

  2. secret跨namespace不可見

  3. 單個secret 1MB 大小限制

  4. kubelets只支持從API server 建立的pod使用 secret. 不支持經過 kubelets --manifest-url, --config 建立的pod // 須要查下有什麼區別

對於第一點:pod在被建立前,不會檢查 secret是否存在,當pod被調度建立時,會先去apiserver取secret,若是失敗(網絡,不存在)則會重複嘗試,直到獲得secret,並mount成功。

Ingress

能夠看作是把服務暴露給外網的配置,借用文檔中的圖:

internet
        |
   [ Ingress ]
   --|-----|--
   [ Services ]

舉個例子

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: s1
          servicePort: 80
      - path: /bar
        backend:
          serviceName: s2
          servicePort: 80


$ kubectl get ing
NAME      RULE          BACKEND   ADDRESS
test      -
          foo.bar.com
          /foo          s1:80
          /bar          s2:80

backend 就是service name 和 port.

這只是配置,必須運行相應的 Ingress Controller Pod, 纔有效果,有幾個現成的controller,
github
其中一個比較簡單的例子就是 controller 不停 query ingress 配置,發現有變化就更新conf配置,再reload nginx,從而實現反向代理.

DaemonSet

DaemonSet 能保證在每一個node上都運行一個pod。好比存儲集羣 ceph, 日誌收集 fluentd, 節點監控 node_exporter(prometheus), 等 都適用這種策略。

幾種替代方案:

  1. init腳本,例如 systemd等

  2. pods, 指定pod運行在某臺機器上

  3. static pods, kubelet 能夠watch某個目錄,運行其中定義的pod,這些pod不受apiserver調度。

比較一下 DaemonSet 和 ReplicationController:

Use a replication controller for stateless services, like frontends, where scaling up and down the number of replicas and rolling out updates are more important than controlling exactly which host the pod runs on. Use a Daemon Controller when it is important that a copy of a pod always run on all or certain hosts, and when it needs to start before other pods.

rc適用於無狀態服務,scaling up/down, rolling out update,pods和node之間沒有固定的關係
daemon set 適用於 pods和node之間關係固定的場景,而且須要在其餘pod以前存在。

ReplicaSet

待續

相關文章
相關標籤/搜索