图片服务器minio+thumbor

公司一直使用 seaweedfs 做文件存储与图片服务器,seaweedfs 很好用且性能不错,但是缺乏文件管理方案、大文件存储、对k8s的支持,为了满足以上需求我准备用OSS替换现有的文件存储方案,MINIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

MINIO 本身并不支持图片处理,需要 Thumbor 这类型的图片处理服务配合才能构成一个图片服务器, Thumbor 是一个小型的图片处理服务,具有缩放、裁剪、翻转、滤镜等功能。

MINIO

Minio 的安装和使用都非常简单,提供完善各平台安装示例与JAVA、JavaScript、Python、Golang、.NET SDK文档,有完整的中文文档对我这种英文渣非常友好,而且它是GO语言写的,正好我有学习GO的计划有空还可以研究一下源码

安装

k8s 安装官方提供了 Helm Chart ,配制 value.yaml

image:
  repository: minio/minio
  tag: RELEASE.2018-10-18T00-28-58Z
  pullPolicy: IfNotPresent

mode: standalone

accessKey: "admin"
secretKey: "123456"
configPath: "/root/.minio/"
mountPath: "/export"

persistence:
  enabled: true

  ## A manually managed Persistent Volume and Claim
  ## Requires persistence.enabled: true
  ## If defined, PVC must be created manually before volume will be bound
  # existingClaim:

  ## minio data Persistent Volume Storage Class
  ## If defined, storageClassName: <storageClass>
  ## If set to "-", storageClassName: "", which disables dynamic provisioning
  ## If undefined (the default) or set to null, no storageClassName spec is
  ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
  ##   GKE, AWS & OpenStack)
  ##
  ## Storage class of PV to bind. By default it looks for standard storage class.
  ## If the PV uses a different storage class, specify that here.
  storageClass: minio
  accessMode: ReadWriteOnce
  size: 20Gi

  ## If subPath is set mount a sub folder of a volume instead of the root of the volume.
  ## This is especially handy for volume plugins that don't natively support sub mounting (like glusterfs).
  ##
  subPath: ""

ingress:
  enabled: true
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  path: /
  hosts:
    - minio.domain.vc
  tls: 
  - secretName: minio-oss-tls
    hosts:
      - minio.domain.vc

## Node labels for pod assignment
## Ref: https://kubernetes.io/docs/user-guide/node-selection/
##
nodeSelector: {}

resources:
  requests:
    memory: 256Mi
    cpu: 250m

执行安装命令

helm install --name my-release -f values.yaml stable/minio

Helm 官方 Chart 库存在墙的问题可能无法安装,可以去 Github 把源码 clone 下来使用 https://github.com/helm/charts/tree/master/stable/minio

Ingress Https 参考 https://jamesdeng.github.io/2018/09/17/K8s-cert-manager.html 进行处理

使用

官网例子:

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;

import org.xmlpull.v1.XmlPullParserException;

import io.minio.MinioClient;
import io.minio.errors.MinioException;

public class FileUploader {
  public static void main(String[] args) throws NoSuchAlgorithmException, IOException, InvalidKeyException, XmlPullParserException {
    try {
      // 使用Minio服务的URL,端口,Access key和Secret key创建一个MinioClient对象
      MinioClient minioClient = new MinioClient("https://play.minio.io:9000", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG");

      // 检查存储桶是否已经存在
      boolean isExist = minioClient.bucketExists("asiatrip");
      if(isExist) {
        System.out.println("Bucket already exists.");
      } else {
        // 创建一个名为asiatrip的存储桶,用于存储照片的zip文件。
        minioClient.makeBucket("asiatrip");
      }

      // 使用putObject上传一个文件到存储桶中。
      minioClient.putObject("asiatrip","asiaphotos.zip", "/home/user/Photos/asiaphotos.zip");
      System.out.println("/home/user/Photos/asiaphotos.zip is successfully uploaded as asiaphotos.zip to `asiatrip` bucket.");
    } catch(MinioException e) {
      System.out.println("Error occurred: " + e);
    }
  }
}

Thumbor

安装

Github 上的 Thumbor Docker Image 都没有最新6.6.0版本的,我 fork 一个项目改了一下 https://github.com/jamesDeng/thumbor

使用 Docker-compose examples

version: '3'
services:
  thumbor:
    image: registry.cn-shenzhen.aliyuncs.com/thinker-open/thumbor:6.6.0
    environment:
      #- ALLOW_UNSAFE_URL=False
      - DETECTORS=['thumbor.detectors.feature_detector','thumbor.detectors.face_detector']
      - AWS_ACCESS_KEY_ID=admin # put your AWS_ACCESS_KEY_ID here
      - AWS_SECRET_ACCESS_KEY=123456 # put your AWS_SECRET_ACCESS_KEY here
      # Is needed for buckets that demand the new signing algorithm (v4)
      # - S3_USE_SIGV4=true
      # - TC_AWS_REGION=eu-central-1
      - TC_AWS_REGION=us-east-1
      - TC_AWS_ENDPOINT=https://minio.domain.vc
      - TC_AWS_ENABLE_HTTP_LOADER=False
      - TC_AWS_ALLOWED_BUCKETS=False
      # loader
      - LOADER=tc_aws.loaders.s3_loader
      - TC_AWS_LOADER_BUCKET=test-images
      # STORAGE
      - STORAGE=tc_aws.storages.s3_storage
      - TC_AWS_STORAGE_BUCKET=thumbor-storage
      - TC_AWS_STORAGE_ROOT_PATH=storage
      #RESULT_STORAGE
      - RESULT_STORAGE=tc_aws.result_storages.s3_storage
      - TC_AWS_RESULT_STORAGE_BUCKET=thumbor-storage
      - TC_AWS_RESULT_STORAGE_ROOT_PATH=result_storage
      - RESULT_STORAGE_STORES_UNSAFE=True
      - STORAGE_EXPIRATION_SECONDS=None
      - RESULT_STORAGE_EXPIRATION_SECONDS=None
    restart: always
    ports:
      - "8002:8000" 
    volumes:
      # mounting a /data folder to store cached images
      - /Users/james/james-docker/thumbor/data:/data
    restart: always
    networks:
      - app
  nginx:
    image: registry.cn-shenzhen.aliyuncs.com/thinker-open/thumbor-nginx:1.0.1
    links:
      - thumbor:thumbor
    ports:
      - "8001:80" # nginx cache port (with failover to thumbor)
    hostname: nginx
    restart: always
    networks:
      - app
volumes:
  data:
    driver: local
networks:
  app:
    driver: bridge

启动

docker-compose -f thumbor-prod.yaml up -d

配制中 RESULT_STORAGE 也是采用 tc_aws.result_storages.s3_storage,我看大部分文章的 RESULT_STORAGE 都是 thumbor.result_storages.file_storage,然后配制Nginx进行去读取本地存储,我是考虑是如果集群部署那么每个图片的处理结果在所有实例上都有可能存在,这样很浪费空间。

注意例子中的 TC_AWS_STORAGE_BUCKET=thumbor-storage、TC_AWS_RESULT_STORAGE_BUCKET=thumbor-storage 的 bucket 都需要手动去 Minio 创建,安装 mc后执行

mc config host add minio-oss https://minio.domain.vc admin 123456 S3v4
mc mb minio-oss/thumbor-storage

注意例子中的 TC_AWS_LOADER_BUCKET 必须指定,该 docker 镜像有问题并不支持在访问URL中写入Bucket的方式,我按网上方案并没有解决

nginx 说明

对 Thumbor url 语法进行改进,去掉 unsafe,加入自定的尾部截取语法

 server {
        listen 80 default;
        server_name localhost;
        
        # This CORS configuration will be deleted if envvar THUMBOR_ALLOW_CORS != true
        add_header 'Access-Control-Allow-Origin' '*';                                   # THUMBOR_ALLOW_CORS
        add_header 'Access-Control-Allow-Credentials' 'true';                           # THUMBOR_ALLOW_CORS
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';    # THUMBOR_ALLOW_CORS
        add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With'; # THUMBOR_ALLOW_CORS
         
        location / {
            proxy_pass   http://thumbor/unsafe$request_uri;
            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        }

        location ~* ^([\s\S]+)_([\d-]+x[\d-]+)$ {
            proxy_pass   http://thumbor;
            rewrite ~*/([\s\S]+)_([\d-]+x[\d-]+)$ /unsafe/$2/$1 break;
            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        }
        
        location = /healthcheck {
            proxy_pass http://thumbor$request_uri;
            access_log off;
        }

        location ~ /\.ht { deny  all; access_log off; error_log off; }
        location ~ /\.hg { deny  all; access_log off; error_log off; }
        location ~ /\.svn { deny  all; access_log off; error_log off; }
        location = /favicon.ico { deny  all; access_log off; error_log off; }
    }

使用

如 bucket test-images 中有图片 test1.jpg,进行裁剪

官方方式

http://localhost:8001/200x200/test1.jpg

自定的尾部截取语法

http://localhost:8001/test1.jpg_200x200

其它相关 Thumbor 语法去官网查询

k8s 安装

写了一个 helm chart https://github.com/jamesDeng/helm-charts/tree/master/thumbor,配制 value.yaml

# Default values for thumbor.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

thumbor:
  image:
    repository: registry.cn-shenzhen.aliyuncs.com/thinker-open/thumbor
    tag: 6.6.0
    pullPolicy: Always
  resources: 
    limits:
      cpu: 1000m
      memory: 1024Mi
    requests:
      cpu: 500m
      memory: 128Mi

nginx:
  image:
    repository: registry.cn-shenzhen.aliyuncs.com/thinker-open/thumbor-nginx
    tag: 1.0.1
    pullPolicy: Always
  resources: 
    limits:
      cpu: 500m
      memory: 256Mi
    requests:
      cpu: 100m
      memory: 128Mi

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: true
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  path: /
  hosts:
    - image.domain.vc
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

nodeSelector: {}

thumborConfig: 
  THUMBOR_PORT: 8000
  ALLOW_UNSAFE_URL: "True"
  LOG_LEVEL: "DEBUG"
  DETECTORS: "['thumbor.detectors.feature_detector','thumbor.detectors.face_detector']"
  AWS_ACCESS_KEY_ID: admin
  AWS_SECRET_ACCESS_KEY: 123456
  # aws:eu-central-1 minio:us-east-1
  TC_AWS_REGION: us-east-1  
  TC_AWS_ENDPOINT: https://minio.domain.vc
  TC_AWS_ENABLE_HTTP_LOADER: "False"
  LOADER: "tc_aws.loaders.s3_loader"
  TC_AWS_LOADER_BUCKET: test-images
  TC_AWS_LOADER_ROOT_PATH: ""
  STORAGE: "tc_aws.storages.s3_storage"
  TC_AWS_STORAGE_BUCKET: thumbor-storage
  TC_AWS_STORAGE_ROOT_PATH: storage
  RESULT_STORAGE: "tc_aws.result_storages.s3_storage"
  TC_AWS_RESULT_STORAGE_BUCKET: thumbor-storage
  TC_AWS_RESULT_STORAGE_ROOT_PATH: result_storage
  RESULT_STORAGE_STORES_UNSAFE: "True"
  STORAGE_EXPIRATION_SECONDS: None
  RESULT_STORAGE_EXPIRATION_SECONDS: None

clone 下来进入thumbor目录执行

helm install --name thumbor -f values.yaml .

部署成功使用 http://image.domain.vc/200x200/test1.jpg 访问

参考文档

点我阅读更多...

K8s cert manager

cert-manager 是k8s的本地证书管理控制器,它可以自动注册和续签 Let’s Encrypt, HashiCorp Vault 的https证书

安装

使用 helm 安装,这里可以查找charts repository

helm install \
    --name cert-manager \
    --namespace kube-system \
    stable/cert-manager

有墙的问题,建议把源码下载后安装

git clone https://github.com/helm/charts.git
cd charts/stable/cert-manager
helm install \
    --name cert-manager \
    --namespace kube-system \
    ./

使用

设置 Issuer

先添加用于设定从那里取得证书的 Issuer,k8s 提供了Issuer和ClusterIssuer两种,前者在单个namespace使用,后者是可以在整个集里中使用的

cat>issuer.yaml<<EOF
apiVersion: certmanager.k8s.io/v1alpha1 
# Issuer/ClusterIssuer
kind: Issuer 
metadata:   
    name: cert-issuer
    namespace: default
spec:
    acme:
        # The ACME server URL
        server: https://acme-v02.api.letsencrypt.org/directory
        # 用于AGME的注册
        email: my@163.com
        # 用于存储向AGME注册的 private key
        privateKeySecretRef:
            name: cert-issuer-key
        # 启用 http-01 challenge
        http01: {}
EOF
kubectl apply -f issuer.yaml

如果要设定 ClusterIssuer 把yaml中的kind设置成ClusterIssuer即可

添加 secret

生成签名密钥并创建 k8s secret

# Generate a CA private key
$ openssl genrsa -out ca.key 2048

# Create a self signed Certificate, valid for 10yrs with the 'signing' option set
$ openssl req -x509 -new -nodes -key ca.key -subj "/CN=kube-ingress" -days 3650 -reqexts v3_req -extensions v3_ca -out ca.crt

$ kubectl create secret tls my-tls \
   --cert=ca.crt \
   --key=ca.key \
   --namespace=default

取得 Certificate

cat>my-cert.yaml<<EOF
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: my-cert
  namespace: default
spec:
  secretName: my-tls
  issuerRef:
    name: cert-issuer
    ## 如果使用 ClusterIssuer 需要添加 kind: ClusterIssuer
    #kind: ClusterIssuer
  commonName: ""
  dnsNames:
  - www.example.com
  acme:
    config:
    - http01:
        ingress: ""
      domains:
      - www.example.com
EOF

#创建
kubectl apply -f my-cert.yaml

#查看进度
kubectl describe certificate my-cert -n default

参考文档

点我阅读更多...

Gitlab ci:cd

在devops开发流程中,ci/cd是很非常重要的步骤,它赋予了我们快速迭代的可能:

  • ci(持续构建) 代码提交后触发自动化的单元测试,代码预编译,构建镜像,上传镜像等.
  • cd(持续发布) 持续发布则指将构建好的程序发布到各种环境,如预发布环境,正式环境.

GitLab CI/CD 通过在项目内 .gitlab-ci.yaml 配置文件读取 CI 任务并进行相应处理;GitLab CI 通过其称为 GitLab Runner 的 Agent 端进行 build 操作,结构如下:

GitLab Runner

gitlab 的CI/CD任务需要GitLab Runner运维,Runner可以是虚拟机、VPS、裸机、docker容器、甚至一堆容器。GitLab和Runners通过API通信,所以唯一的要求就是运行Runners的机器可以联网,,参考官方文档进行安装

#机器为 CentOS 7.4
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash

yum install gitlab-runner

gitlab-runner status

Runner 通过注册的方式添加需要服务的Project、Group或者整个gitlab,在Project/Group->CI/CD->Runners settings查找需要的registration token 执行官方文档的设置步骤

$ gitlab-runner register

Running in system-mode.                            
                                                   
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
# 输入 URL http://gitlab.domain.com/
Please enter the gitlab-ci token for this runner:
#输入 registration token
Please enter the gitlab-ci description for this runner:
#输入描述
Please enter the gitlab-ci tags for this runner (comma separated):
#输入tags
Registering runner... succeeded                     runner=9jHBE4P6
Please enter the executor: ssh, virtualbox, docker+machine, docker-ssh+machine, docker, docker-ssh, parallels, shell, kubernetes:
#输入执行类型,我输入的是 docker
Please enter the default Docker image (e.g. ruby:2.1):
#默认运行的镜像
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

#查看注册
$ gitlab-runner list

在 Project/Group->CI/CD->Runners settings 查看是否可以使用

.gitlab-ci.yml

.gitlab-ci.yml是用来配置CI在我们的项目中做些什么工作,它位于项目的根目录。

语法参考官方文档的翻译

我司项目目前有 java,react,android,ios 类型项目,该篇介绍 java

java build

项目使用maven构建,发布需要打包成docker images,采用dockerfile-maven-plugin方式做 docker images bulid and push

image: maven:3.5.4-jdk-8

# 注意使用 docker:dind 需要设置 /etc/gitlab-runner/config.toml 的 privileged = true,
services:
  - docker:dind

variables:
  # This will supress any download for dependencies and plugins or upload messages which would clutter the console log.
  # `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
  # As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used
  # when running from the command line.
  # `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"
  #用于支持 docker bulid
  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2

# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
  paths:
    - .m2/repository

stages:
  - build
  - deploy

# Validate JDK8
validate:jdk8:
  script:
    - 'mvn $MAVEN_CLI_OPTS test-compile'
  only:
    - dev-4.0.1
  stage: build

# 目前只做到打包
deploy:jdk8:
  # Use stage test here, so the pages job may later pickup the created site.
  stage: deploy
  script:
    - 'mvn $MAVEN_CLI_OPTS deploy'
  only:
    - dev-4.0.1

注意使用docker:dind 需要在 docker run 加入 –privileged,Runner 设置方式为修改 /etc/gitlab-runner/config.toml 的 privileged = true

concurrent = 1
check_interval = 0

[[runners]]
  name = "kms"
  url = "http://example.org/ci"
  token = "3234234234234"
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "alpine:3.4"
    privileged = true
    disable_cache = false
    volumes = ["/cache"]
  [runners.cache]
    Insecure = false

dockerfile-maven-plugin push 支持

dockerfile-maven-plugin 的 push 需要在 maven settings.xml 文件中设置 docker 的用户名和密码,这里需要重做 maven images ,为了安全还需要对 settings.xml 中的密码进行加密,并且 dockerfile-maven-plugin 高于 1.4.3 才支持这个设置

进行 settings-security.xml settings.xml 文件准备

#生成 master 密码
$ mvn --encrypt-master-password 123456
{40hwd7X9T1wHfRTKWlUIbHyjacbV4iV/hSfODlRcH/E=}

$ cat>settings-security.xml<<EOF
<settingsSecurity>
  <master>{40hwd7X9T1wHfRTKWlUIbHyjacbV4iV/hSfODlRcH/E=}</master>
</settingsSecurity>
EOF

#生成 docker 密码 
$ mvn --encrypt-password 123456
{QdP5NFvxYhYHILE6k8tDEff+CZzWq2N3mCkPPUcljSA=}

#修改 settings.xml 中 docker 密码
<server>
  <id>registry.cn-shenzhen.aliyuncs.com</id>
  <username>username</username>
  <password>{QdP5NFvxYhYHILE6k8tDEff+CZzWq2N3mCkPPUcljSA=}</password>
</server>

完成 dockerfile

FROM maven:3.5.4-jdk-8

COPY settings.xml /root/.m2/

COPY settings-security.xml /root/.m2/

docker build 成自己的 maven images,并修改 .gitlab-ci.yml 文件中使用的 images

私有 docker images 支持

如果 docker images 是需要login的,先生成 username:password 的 base64 值

echo -n "my_username:my_password" | base64

# Example output to copy
bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=

在 Project/Group->CI/CD->Variable添加 DOCKER_AUTH_CONFIG,内容如下:

{
    "auths": {
        "registry.example.com": {
            "auth": "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ="
        }
    }
}

k8s helm deploy

公司项目基于 k8s helm 进行发布,这里介绍怎样基于 k8s helm 做 CD

首先需要做一个 kubectl and helm 的镜像

FROM alpine:3.4

#注意 kubectl 和 helm 的版本
ENV KUBE_LATEST_VERSION=v1.10.7
ENV HELM_VERSION=v2.9.1
ENV HELM_FILENAME=helm-${HELM_VERSION}-linux-amd64.tar.gz

RUN apk add --update ca-certificates \
 && apk add --update -t deps curl  \
 && apk add --update gettext tar gzip \
 && curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \
 && curl -L https://storage.googleapis.com/kubernetes-helm/${HELM_FILENAME} | tar xz && mv linux-amd64/helm /bin/helm && rm -rf linux-amd64 \
 && chmod +x /usr/local/bin/kubectl \
 && apk del --purge deps \
 && rm /var/cache/apk/*

运行 build 需要使用代理解决墙的问题

#HTTP_PROXY=http://192.168.1.22:1087 为 vpn 代理
docker build --build-arg HTTP_PROXY=http://192.168.1.22:1087 -t helm-kubectl:v2.9.1-1.10.7 .

kubectl 执行指令需要 k8s api 的config,找一台 k8s master 执行指令将 config 转换成 base64,创建 gitlab ci/cd 的 “kube_config” variable 值为 config base64

cat ~/.kube/config | base64 

加入 deploy-test job 用于发布测试环境,考虑目前开发模式下测试环境不会更新版本号,在 deploy.yaml 的 spec/template/metadata/annotations 下添加 update/version 注解,并在 chart 配制 values

spec:
  selector:
    matchLabels:
      app: admin
  replicas: 1
  revisionHistoryLimit: 3
  template:
    metadata:
      labels:
        app: admin
      annotations:
        update/version : ""

在 job 使用 helm upgrade –set admin.updateVersion= 可以达到重新部署测试环境的目的

deploy-test:
  # Use stage test here, so the pages job may later pickup the created site.
  stage: deploy-test
  image: registry.cn-shenzhen.aliyuncs.com/thinker-open/helm-kubectl:v2.9.1-1.10.7
  before_script:
    - mkdir -p /etc/deploy
    - echo ${kube_config} | base64 -d > ${KUBECONFIG}
    - kubectl config use-context kubernetes-admin@kubernetes
    - helm init --client-only --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
    - helm repo add my-repo http://helm.domain.com/
    - helm repo update
  script:
    - helm upgrade cabbage-test --reuse-values --set admin.updateVersion=`date +%s` thinker/cabbage
  environment:
    name: test
    url: https://test.example.com
  only:
  - dev-4.0.1

加入 deploy-prod job 用于发布生产环境,拉 tag 时执行,使用 tag name 更新 image 的版本

deploy-prod:
  # Use stage test here, so the pages job may later pickup the created site.
  stage: deploy-prod
  image: registry.cn-shenzhen.aliyuncs.com/thinker-open/helm-kubectl:v2.9.1-1.10.7
  before_script:
    - mkdir -p /etc/deploy
    #使用 kube_config variable
    - echo ${kube_config} | base64 -d > /etc/deploy/config
    #注意 kubernetesContextName 为 ~/.kube/config 文件中的 context name
    - kubectl config use-context kubernetesContextName
    #墙问题使用阿里云源
    - helm init --client-only --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
    #加入需要的 helm repo 
    - helm repo add my-repo http://helm.domain.com/
    - helm repo update
  script:
    # 这里只做 upgrade,CI_COMMIT_REF_NAME 是  branch or tag name 
    - helm upgrade cabbage-test --reuse-values --set admin.image.tag=${CI_COMMIT_REF_NAME} thinker/cabbage
  environment:
    name: test
    url: https://prod.example.com
  only:
  - tags

完整例子

image: maven:3.5.4-jdk-8

services:
  - docker:dind

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"
  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2
  KUBECONFIG: /etc/deploy/config

# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
  paths:
    - .m2/repository

stages:
  - build 
  - deploy-test
  - deploy-prod

# mvn deploy,生成 images 并 push 到阿里云
build:jdk8:
  script:
    - 'mvn $MAVEN_CLI_OPTS deploy'
  only:
    - dev-4.0.1
  stage: build

deploy-test:
  # Use stage test here, so the pages job may later pickup the created site.
  stage: deploy-test
  image: registry.cn-shenzhen.aliyuncs.com/thinker-open/helm-kubectl:v2.9.1-1.10.7
  before_script:
    - mkdir -p /etc/deploy
    - echo ${kube_config} | base64 -d > ${KUBECONFIG}
    - kubectl config use-context kubernetes-admin@kubernetes
    - helm init --client-only --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
    - helm repo add my-repo http://helm.domain.com/
    - helm repo update
  script:
    - helm upgrade cabbage-test --reuse-values --set admin.updateVersion=`date +%s` thinker/cabbage
  environment:
    name: test
    url: https://test.example.com
  only:
  - dev-4.0.1

deploy-prod:
  # Use stage test here, so the pages job may later pickup the created site.
  stage: deploy-prod
  image: registry.cn-shenzhen.aliyuncs.com/thinker-open/helm-kubectl:v2.9.1-1.10.7
  before_script:
    - mkdir -p /etc/deploy
    - echo ${kube_config} | base64 -d > ${KUBECONFIG}
    - kubectl config use-context kubernetes-admin@kubernetes
    - helm init --client-only --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
    - helm repo add my-repo http://helm.domain.com/
    - helm repo update
  script:
   #CI_COMMIT_REF_NAME 是  branch or tag name 
    - helm upgrade cabbage-test --reuse-values --set admin.image.tag=${CI_COMMIT_REF_NAME} thinker/cabbage
  environment:
    name: test
    url: https://prod.example.com
  only:
  - tags

总结

采用 helm 有几个问题:

  • 项目第一次 install 应该由运维统一执行,如果放在 job 里做怎样处理配制文件是个问题
  • 如果迭代过程中出现在配制文件项的添加和更新要怎么处理
  • helm chart 文件的编写与项目开发怎么协调

参考文档

点我阅读更多...

K8s日志处理

k8s集群的日志方案

Kubernetes 没有为日志数据提供原生存储方案,需要集成现有的日志解决方案到 Kubernetes 集群,首先要介绍一下官方文档对日志架构说明:

节点级日志

容器化应用写入 stdout 和 stderr 的任何数据,都会被容器引擎捕获并被重定向到某个位置。例如,Docker 容器引擎将这两个输出流重定向到 日志驱动 ,该日志驱动在 Kubernetes 中配置为以 json 格式写入文件。

集群级别日志架构

官方文档推荐以下选项:

  • 使用运行在每个节点上的节点级的日志代理。
  • 在应用程序的 pod 中,包含专门记录日志的伴生容器。
  • 在应用程序中将日志直接推送到后台。

使用节点级日志代理

您可以在每个节点上使用 节点级的日志代理 来实现集群级日志记录。日志代理是专门的工具,它会暴露出日志或将日志推送到后台。通常来说,日志代理是一个容器,这个容器可以访问这个节点上所有应用容器的日志目录。

因为日志代理必须在每个节点上运行,所以通常的实现方式为,DaemonSet 副本,manifest pod,或者专用于本地的进程。但是后两种方式已被弃用并且不被推荐。

使用伴生容器和日志代理

您可以通过以下方式之一使用伴生容器:

  • 伴生容器将应用程序日志传送到自己的标准输出。
  • 伴生容器运行一个日志代理,配置该日志代理以便从应用容器收集日志。

从应用中直接暴露日志目录

通过暴露或推送每个应用的日志,您可以实现集群级日志记录;然而,这种日志记录机制的实现已超出 Kubernetes 的范围。

实践

根据我司集群规模用到节点级日志代理就够了,这里使用官方推荐 EFK(Elasticsearch+Fluentd+Kibana) 解决方案,过程中尝试了Fluentd后觉得并不是特别好用(文档、资料、社区活跃都不够),最终选择用 elastic 家族的 Filebeat 替换掉 Fluentd。

目前方案为:

  • Filebeat
  • Elasticsearch
  • Kibana

安装

Elasticsearch:

wget https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.10/cluster/addons/fluentd-elasticsearch/es-service.yaml

kubectl apply -f es-service.yaml

wget https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.10/cluster/addons/fluentd-elasticsearch/es-statefulset.yaml

#修改images为国内,原数据存储方式为emptyDir,修改为网盘

# Elasticsearch deployment itself
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch-logging
  namespace: kube-system
  labels:
    k8s-app: elasticsearch-logging
    version: v5.6.4
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  serviceName: elasticsearch-logging
  replicas: 2
  selector:
    matchLabels:
      k8s-app: elasticsearch-logging
      version: v5.6.4
  template:
    metadata:
      labels:
        k8s-app: elasticsearch-logging
        version: v5.6.4
        kubernetes.io/cluster-service: "true"
    spec:
      serviceAccountName: elasticsearch-logging
      containers:
      - image: registry.cn-hangzhou.aliyuncs.com/google_containers/elasticsearch:v5.6.4 #国内阿里云镜像
        name: elasticsearch-logging
        resources:
          # need more cpu upon initialization, therefore burstable class
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        ports:
        - containerPort: 9200
          name: db
          protocol: TCP
        - containerPort: 9300
          name: transport
          protocol: TCP
        volumeMounts:
        - name: elasticsearch-logging
          mountPath: /data
        env:
        - name: "NAMESPACE"
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
      initContainers:
      - image: alpine:3.6
        command: ["/sbin/sysctl", "-w", "vm.max_map_count=262144"]
        name: elasticsearch-logging-init
        securityContext:
          privileged: true
  volumeClaimTemplates: #阿里云盘存储
  - metadata:
      name: "elasticsearch-logging"
    spec:
      accessModes:
      - ReadWriteOnce
      storageClassName: alicloud-disk-efficiency
      resources:
        requests:
          storage: 20Gi

kubectl apply -f es-statefulset.yaml

Filebeat:

wget https://raw.githubusercontent.com/elastic/beats/6.4/deploy/kubernetes/filebeat-kubernetes.yaml

#由于我们安装的es没有权限,清理掉es用户名和密码相关
ConfigMap
  output.elasticsearch:
    hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
    username: ${ELASTICSEARCH_USERNAME}  # 删除
    password: ${ELASTICSEARCH_PASSWORD}  # 删除

DaemonSet 
    env:
        - name: ELASTICSEARCH_HOST
          value: elasticsearch
        - name: ELASTICSEARCH_PORT
          value: "9200"
        - name: ELASTICSEARCH_USERNAME  # 删除
          value: elastic
        - name: ELASTICSEARCH_PASSWORD  # 删除
          value: changeme
        - name: ELASTIC_CLOUD_ID
          value:
        - name: ELASTIC_CLOUD_AUTH
          value:

kubectl apply -f filebeat-kubernetes.yaml

Kibnan:

wget https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.10/cluster/addons/fluentd-elasticsearch/kibana-service.yaml

kubectl apply -f kibana-service.yaml

#添加kibana的ingress
cat>kibana-ingress.yaml<<EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: kibana-logging
  namespace: kube-system
spec:
  rules:
  - host: kibana.domain.com
    http:
      paths:
      - backend:
          serviceName: kibana-logging
          servicePort: 5601
EOF

kubectl apply -f kibana-ingress.yaml

wget https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.10/cluster/addons/fluentd-elasticsearch/kibana-deployment.yaml

#修改 SERVER_BASEPATH 为 ""( 原本是一串地址不知道有什么用,如不修改为“”或者删除,域名是没有办法正常访问的)
          - name: SERVER_BASEPATH
            value: /api/v1/namespaces/kube-system/services/kibana-logging/proxy #修改为 ""

kubectl apply -f kibana-deployment.yaml

使用

安装都完成后访问 kibana.domain.com 查看 kibana,添加 filebeat-* 的 index

访问权限

kibana 目前还是裸奔状态,这样肯定不能在生产环境使用,尝试通过 elastic 的 X-Pack 方式解决并未成功,主要还是官方的Elasticsearch安装文件未对X-Pack支持,而我许久没玩 Elasticsearch 感觉功力不够,最终选择通过为Ingress添加basic-auth认证的方式做权限

安装 htpasswd,创建用户密码

#安装
yum -y install httpd-tools

#产生密码文件
htpasswd -c auth  kibana

New password: 
Re-type new password: 
Adding password for user kibana

创建secret存储密码

kubectl -n <namespace> create secret generic kibana-basic-auth --from-file=auth

修改 Kibnan Ingress

cat>kibana-ingress.yaml<<EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: kibana-logging
  namespace: kube-system
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: kibana-basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Authentication Required - kibana"
spec:
  rules:
  - host: kibana.domain.com
    http:
      paths:
      - backend:
          serviceName: kibana-logging
          servicePort: 5601
EOF
kubectl apply -f kibana-ingress.yaml

再访问 kibana.domain.com 就会要求输入用户名和密码拉~~~~

参考文档

点我阅读更多...

K8s监控 prometheus Operator

Prometheus Operator 介绍、安装、使用

介绍

Prometheus 为k8s 1.11推荐的集群监控和警报平台,Operator是CoreOS开源的一套用于维护Kubernetes上Prometheus的控制器,目标是简化部署与维护Prometheus。

架构如下:

主要分为五个部分:

  • Operate: 系统主要控制器,根据自定义的资源(Custom Resource Definition,CRDs)来负责管理与部署;
  • Prometheus Server: 由Operator 依据一个自定义资源Prometheus类型中所描述的内容而部署的Prometheus Server集,可以将这个自定义资源看作是一种特别用来管理Prometheus Server的StatefulSet资源;
  • ServiceMonitor: 一个Kubernetes自定义资料,该资源描述了Prometheus Server的Target列表,Operator会监听这个资源的变化来动态更新 Prometheus Server的Scrape targets。而该资源主要透过 Selector 来依据 Labels 选取对应的 Service Endpoint,并让 Prometheus Serve 透过 Service 进行拉取 Metrics 资料;
  • Service: kubernetes 中的 Service 资源,这边主要用来对应 Kubernetes 中 Metrics Server Pod,然后提供给 ServiceMonitor 选取让 Prometheus Server 拉取资料,在 Prometheus 术语中可以称为 Target,即被 Prometheus 监测的对象,如一個部署在 Kubernetes 上的 Node Exporter Service。
  • Alertmanager: 接收从 Prometheus 来的 event,再根据定义的 notification 组决定要通知的方法。

安装

采用 helm chart 方式安装

helm repo add coreos https://s3-eu-west-1.amazonaws.com/coreos-charts/stable/
helm install coreos/prometheus-operator --name prometheus-operator --namespace monitoring
#设置安装 CoreDNS
cat>kube-prometheus-values.yaml<<EOF
# Select Deployed DNS Solution
deployCoreDNS: true
deployKubeDNS: false
deployKubeEtcd: true
EOF

helm install coreos/kube-prometheus --name kube-prometheus --namespace monitoring -f kube-prometheus-values.yaml

# 采用域名方式访问,添加 grafana 的 ingress
cat>grafana-ingress.yaml<<EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: kube-prometheus-grafana
  namespace: monitoring
spec:
  rules:
    - host: grafana.domain.com
      http:
        paths:
          - path: /
            backend:
              serviceName: kube-prometheus-grafana
              servicePort: 80
EOF
kubectl apply -f grafana-ingress.yaml

#deployments kube-prometheus-exporter-kube-state 的images无法下载,改为国内镜像
#将 images 地址前缀为 registry.cn-hangzhou.aliyuncs.com/google_containers
kubectl edit deploy kube-prometheus-exporter-kube-state -n monitoring

#关闭 grafana 匿名身份验证
kubectl edit deploy kube-prometheus-grafana -n monitoring
#将env GF_AUTH_ANONYMOUS_ENABLED改到false
        - name: GF_AUTH_ANONYMOUS_ENABLED
          value: "false" 

使用

访问 grafana.domain.com,用户密码都为 admin

参考文档

点我阅读更多...

K8s 1.11 阿里云安装

阿里云安装k8s 1.11.2 HA版本

架构说明

由于阿里云Ecs无法安装keepalived,我们采用阿里内部loadbalancer做master的负载,阿里内部loadbalancer无法由vpc内部ecs负载到自己机器,所以还需要另外两台服务器安装haproxy负载到三台master

cn-shenzhen.i-wz9jeltjohlnyf56uco2 : etcd master haproxy 172.16.0.196
cn-shenzhen.i-wz9jeltjohlnyf56uco4 : etcd master haproxy 172.16.0.197
cn-shenzhen.i-wz9jeltjohlnyf56uco1 : etcd master haproxy 172.16.0.198
cn-shenzhen.i-wz9jeltjohlnyf56uco3 : node  172.16.0.199
cn-shenzhen.i-wz9jeltjohlnyf56uco0 : node  172.16.0.199

(loadblancer ip): 172.16.0.201

准备工作

  • 修改ECS节点的名称为regionId.instanceId,这个名称阿里云的相关插件都需要使用
  • master节点做ssh打通
  • 配置hosts解析
    #master , k8s-master-lb 指向本机通过 haproxy 负载
    cat >>/etc/hosts<<EOF
    172.16.0.196    cn-shenzhen.i-wz9jeltjohlnyf56uco2
    172.16.0.197    cn-shenzhen.i-wz9jeltjohlnyf56uco4
    172.16.0.198    cn-shenzhen.i-wz9jeltjohlnyf56uco1
    127.0.0.1 k8s-master-lb
    EOF
    #node,k8s-master-lb 指向 loadblancer ip
    cat >>/etc/hosts<<EOF
    172.16.0.196    cn-shenzhen.i-wz9jeltjohlnyf56uco2
    172.16.0.197    cn-shenzhen.i-wz9jeltjohlnyf56uco4
    172.16.0.198    cn-shenzhen.i-wz9jeltjohlnyf56uco1
    172.16.0.201 k8s-master-lb
    EOF
    

安装docker

#卸载安装指定版本docker-ce
yum remove -y docker-ce docker-ce-selinux container-selinux
#配制docker源
sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

yum install -y --setopt=obsoletes=0 \
  docker-ce-17.03.1.ce-1.el7.centos \
  docker-ce-selinux-17.03.1.ce-1.el7.centos

#配制阿里云docker加速
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://8dxol81m.mirror.aliyuncs.com"]
}
EOF

sed -i '$a net.bridge.bridge-nf-call-iptables = 1' /usr/lib/sysctl.d/00-system.conf
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
#开启forward
#Docker从1.13版本开始调整了默认的防火墙规则
#禁用了iptables filter表中FOWARD链
#这样会引起Kubernetes集群中跨Node的Pod无法通信
iptables -P FORWARD ACCEPT
sed -i "/ExecStart=/a\ExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT" /lib/systemd/system/docker.service

systemctl daemon-reload
systemctl enable docker 
systemctl restart docker

配制haproxy

所有master都安装haproxy

#拉取haproxy镜像
docker pull haproxy:1.7.8-alpine
mkdir /etc/haproxy
cat >/etc/haproxy/haproxy.cfg<<EOF
global
  log 127.0.0.1 local0 err
  maxconn 50000
  uid 99
  gid 99
  #daemon
  nbproc 1
  pidfile haproxy.pid

defaults
  mode http
  log 127.0.0.1 local0 err
  maxconn 50000
  retries 3
  timeout connect 5s
  timeout client 30s
  timeout server 30s
  timeout check 2s

listen admin_stats
  mode http
  bind 0.0.0.0:1080
  log 127.0.0.1 local0 err
  stats refresh 30s
  stats uri     /haproxy-status
  stats realm   Haproxy\ Statistics
  stats auth    will:will
  stats hide-version
  stats admin if TRUE

frontend k8s-https
  bind 0.0.0.0:8443
  mode tcp
  #maxconn 50000
  default_backend k8s-https

backend k8s-https
  mode tcp
  balance roundrobin
  server lab1 172.16.0.196:6443 weight 1 maxconn 1000 check inter 2000 rise 2 fall 3
  server lab2 172.16.0.196:6443 weight 1 maxconn 1000 check inter 2000 rise 2 fall 3
  server lab3 172.16.0.196:6443 weight 1 maxconn 1000 check inter 2000 rise 2 fall 3
EOF

#启动haproxy
docker run -d --name my-haproxy \
-v /etc/haproxy:/usr/local/etc/haproxy:ro \
-p 8443:8443 \
-p 1080:1080 \
--restart always \
haproxy:1.7.8-alpine

#查看日志
docker logs my-haproxy

安装 kubeadm, kubelet 和 kubectl

#配置源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

#安装
yum install -y kubelet kubeadm kubectl ipvsadm

配置系统相关参数

#临时禁用selinux
#永久关闭 修改/etc/sysconfig/selinux文件设置
setenforce 0
sed -i "s/^SELINUX=enforcing/SELINUX=disabled/g" /etc/sysconfig/selinux 
sed -i "s/^SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config 
sed -i "s/^SELINUX=permissive/SELINUX=disabled/g" /etc/sysconfig/selinux 
sed -i "s/^SELINUX=permissive/SELINUX=disabled/g" /etc/selinux/config

#临时关闭swap
#永久关闭 注释/etc/fstab文件里swap相关的行
swapoff -a
sed -i 's/.*swap.*/#&/' /etc/fstab

systemctl stop firewalld
systemctl disable firewalld

# 配置转发相关参数,否则可能会出错
cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
vm.swappiness=0
EOF
sysctl --system

#加载ipvs相关内核模块
#如果重新开机,需要重新加载
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
modprobe nf_conntrack_ipv4
lsmod | grep ip_vs

配置启动kubelet

#配置kubelet使用国内pause镜像
#配置kubelet的cgroups
#获取docker的cgroups
DOCKER_CGROUPS=$(docker info | grep 'Cgroup' | cut -d' ' -f3)
echo $DOCKER_CGROUPS
cat >/etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--cgroup-driver=$DOCKER_CGROUPS --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.1"
EOF

#启动
systemctl daemon-reload
systemctl enable kubelet && systemctl restart kubelet

配置第一个master节点

#生成配置文件
CP0_IP="172.16.0.196"
CP0_HOSTNAME="cn-shenzhen.i-wz9jeltjohlnyf56uco2"
cat >kubeadm-master.config<<EOF
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.2
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers

apiServerCertSANs:
- "cn-shenzhen.i-wz9jeltjohlnyf56uco2"
- "cn-shenzhen.i-wz9jeltjohlnyf56uco4"
- "cn-shenzhen.i-wz9jeltjohlnyf56uco1"
- "172.16.0.196"
- "172.16.0.197"
- "172.16.0.198"
- "172.16.0.201"
- "127.0.0.1"
- "k8s-master-lb"

api:
  advertiseAddress: $CP0_IP
  controlPlaneEndpoint: k8s-master-lb:8443

etcd:
  local:
    extraArgs:
      listen-client-urls: "https://127.0.0.1:2379,https://$CP0_IP:2379"
      advertise-client-urls: "https://$CP0_IP:2379"
      listen-peer-urls: "https://$CP0_IP:2380"
      initial-advertise-peer-urls: "https://$CP0_IP:2380"
      initial-cluster: "$CP0_HOSTNAME=https://$CP0_IP:2380"
    serverCertSANs:
      - $CP0_HOSTNAME
      - $CP0_IP
    peerCertSANs:
      - $CP0_HOSTNAME
      - $CP0_IP

controllerManagerExtraArgs:
  node-monitor-grace-period: 10s
  pod-eviction-timeout: 10s

networking:
  podSubnet: 10.244.0.0/16
  
kubeProxy:
  config:
    mode: ipvs
EOF

#提前拉取镜像
#如果执行失败 可以多次执行
kubeadm config images pull --config kubeadm-master.config

#初始化
#注意保存返回的 join 命令
kubeadm init --config kubeadm-master.config

#打包ca相关文件上传至其他master节点
cd /etc/kubernetes && tar cvzf k8s-key.tgz admin.conf pki/ca.* pki/sa.* pki/front-proxy-ca.* pki/etcd/ca.*
scp k8s-key.tgz cn-shenzhen.i-wz9jeltjohlnyf56uco4:~/
scp k8s-key.tgz cn-shenzhen.i-wz9jeltjohlnyf56uco1:~/
ssh cn-shenzhen.i-wz9jeltjohlnyf56uco4 'tar xf k8s-key.tgz -C /etc/kubernetes/'
ssh cn-shenzhen.i-wz9jeltjohlnyf56uco1 'tar xf k8s-key.tgz -C /etc/kubernetes/'

配制第二个master

#生成配置文件
CP0_IP="172.16.0.196"
CP0_HOSTNAME="cn-shenzhen.i-wz9jeltjohlnyf56uco2"
CP1_IP="172.16.0.197"
CP1_HOSTNAME="cn-shenzhen.i-wz9jeltjohlnyf56uco4"
cat >kubeadm-master.config<<EOF
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.2
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers

apiServerCertSANs:
- "cn-shenzhen.i-wz9jeltjohlnyf56uco2"
- "cn-shenzhen.i-wz9jeltjohlnyf56uco4"
- "cn-shenzhen.i-wz9jeltjohlnyf56uco1"
- "172.16.0.196"
- "172.16.0.197"
- "172.16.0.198"
- "172.16.0.201"
- "127.0.0.1"
- "k8s-master-lb"

api:
  advertiseAddress: $CP0_IP
  controlPlaneEndpoint: k8s-master-lb:8443

etcd:
  local:
    extraArgs:
      listen-client-urls: "https://127.0.0.1:2379,https://$CP1_IP:2379"
      advertise-client-urls: "https://$CP1_IP:2379"
      listen-peer-urls: "https://$CP1_IP:2380"
      initial-advertise-peer-urls: "https://$CP1_IP:2380"
      initial-cluster: "$CP0_HOSTNAME=https://$CP0_IP:2380,$CP1_HOSTNAME=https://$CP1_IP:2380"
      initial-cluster-state: existing
    serverCertSANs:
      - $CP1_HOSTNAME
      - $CP1_IP
    peerCertSANs:
      - $CP1_HOSTNAME
      - $CP1_IP

controllerManagerExtraArgs:
  node-monitor-grace-period: 10s
  pod-eviction-timeout: 10s

networking:
  podSubnet: 10.244.0.0/16
  
kubeProxy:
  config:
    mode: ipvs
EOF

#配置kubelet
kubeadm alpha phase certs all --config kubeadm-master.config
kubeadm alpha phase kubelet config write-to-disk --config kubeadm-master.config
kubeadm alpha phase kubelet write-env-file --config kubeadm-master.config
kubeadm alpha phase kubeconfig kubelet --config kubeadm-master.config
systemctl restart kubelet

#添加etcd到集群中
CP0_IP="172.16.0.196"
CP0_HOSTNAME="cn-shenzhen.i-wz9jeltjohlnyf56uco2"
CP1_IP="172.16.0.197"
CP1_HOSTNAME="cn-shenzhen.i-wz9jeltjohlnyf56uco4"
KUBECONFIG=/etc/kubernetes/admin.conf kubectl exec -n kube-system etcd-${CP0_HOSTNAME} -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file /etc/kubernetes/pki/etcd/peer.key --endpoints=https://${CP0_IP}:2379 member add ${CP1_HOSTNAME} https://${CP1_IP}:2380
kubeadm alpha phase etcd local --config kubeadm-master.config

#提前拉取镜像
#如果执行失败 可以多次执行
kubeadm config images pull --config kubeadm-master.config

#部署
kubeadm alpha phase kubeconfig all --config kubeadm-master.config
kubeadm alpha phase controlplane all --config kubeadm-master.config
kubeadm alpha phase mark-master --config kubeadm-master.config

配制第三个master

#生成配置文件
CP0_IP="172.16.0.196"
CP0_HOSTNAME="cn-shenzhen.i-wz9jeltjohlnyf56uco2"
CP1_IP="172.16.0.197"
CP1_HOSTNAME="cn-shenzhen.i-wz9jeltjohlnyf56uco4"
CP2_IP="172.16.0.198"
CP2_HOSTNAME="cn-shenzhen.i-wz9jeltjohlnyf56uco1"
cat >kubeadm-master.config<<EOF
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.2
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers

apiServerCertSANs:
- "cn-shenzhen.i-wz9jeltjohlnyf56uco2"
- "cn-shenzhen.i-wz9jeltjohlnyf56uco4"
- "cn-shenzhen.i-wz9jeltjohlnyf56uco1"
- "172.16.0.196"
- "172.16.0.197"
- "172.16.0.198"
- "172.16.0.201"
- "127.0.0.1"
- "k8s-master-lb"

api:
  advertiseAddress: $CP0_IP
  controlPlaneEndpoint: k8s-master-lb:8443

etcd:
  local:
    extraArgs:
      listen-client-urls: "https://127.0.0.1:2379,https://$CP2_IP:2379"
      advertise-client-urls: "https://$CP2_IP:2379"
      listen-peer-urls: "https://$CP2_IP:2380"
      initial-advertise-peer-urls: "https://$CP2_IP:2380"
      initial-cluster: "$CP0_HOSTNAME=https://$CP0_IP:2380,$CP1_HOSTNAME=https://$CP1_IP:2380,$CP2_HOSTNAME=https://$CP2_IP:2380"
      initial-cluster-state: existing
    serverCertSANs:
      - $CP2_HOSTNAME
      - $CP2_IP
    peerCertSANs:
      - $CP2_HOSTNAME
      - $CP2_IP

controllerManagerExtraArgs:
  node-monitor-grace-period: 10s
  pod-eviction-timeout: 10s

networking:
  podSubnet: 10.244.0.0/16
  
kubeProxy:
  config:
    mode: ipvs
EOF

#配置kubelet
kubeadm alpha phase certs all --config kubeadm-master.config
kubeadm alpha phase kubelet config write-to-disk --config kubeadm-master.config
kubeadm alpha phase kubelet write-env-file --config kubeadm-master.config
kubeadm alpha phase kubeconfig kubelet --config kubeadm-master.config
systemctl restart kubelet

#添加etcd到集群中
CP0_IP="172.16.0.196"
CP0_HOSTNAME="cn-shenzhen.i-wz9jeltjohlnyf56uco2"
CP2_IP="172.16.0.198"
CP2_HOSTNAME="cn-shenzhen.i-wz9jeltjohlnyf56uco1"
KUBECONFIG=/etc/kubernetes/admin.conf kubectl exec -n kube-system etcd-${CP0_HOSTNAME} -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file /etc/kubernetes/pki/etcd/peer.key --endpoints=https://${CP0_IP}:2379 member add ${CP2_HOSTNAME} https://${CP2_IP}:2380
kubeadm alpha phase etcd local --config kubeadm-master.config

#提前拉取镜像
#如果执行失败 可以多次执行
kubeadm config images pull --config kubeadm-master.config

#部署
kubeadm alpha phase kubeconfig all --config kubeadm-master.config
kubeadm alpha phase controlplane all --config kubeadm-master.config
kubeadm alpha phase mark-master --config kubeadm-master.config

配置使用kubectl

rm -rf $HOME/.kube
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

#查看node节点
kubectl get nodes

#只有网络插件也安装配置完成之后,才能会显示为ready状态
#设置master允许部署应用pod,参与工作负载,现在可以部署其他系统组件
#如 dashboard, heapster, efk等
kubectl taint nodes --all node-role.kubernetes.io/master-

配置使用网络插件

wget https://raw.githubusercontent.com/coreos/flannel/v0.10.0/Documentation/kube-flannel.yml

#修改配置
#此处的ip配置要与上面kubeadm的pod-network一致
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan"
      }
    }

#修改镜像
image: registry.cn-shanghai.aliyuncs.com/gcr-k8s/flannel:v0.10.0-amd64

#启动
kubectl apply -f kube-flannel.yml

#查看
kubectl get pods --namespace kube-system
kubectl get svc --namespace kube-system

安装 ingress-nginx

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml

#修改 default-http-backend 的images
image: registry.cn-shenzhen.aliyuncs.com/common-images/defaultbackend:1.4

kubectl apply -f mandatory.yaml

#添加service开放 30080与30433端口
cat>ingress-nginx-service.yaml<<EOF
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: 80
    nodePort: 30080
    protocol: TCP
  - name: https
    port: 443
    targetPort: 443
    protocol: TCP
    nodePort: 30443
  selector:
    app: ingress-nginx
~
EOF
kubectl apply -f ingress-nginx-service.yaml

在阿里云新建外网loadblancer,配制tcp的80和443中转到30080和30433

安装dashboard

wget https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml

#修改 images
image: registry.cn-hangzhou.aliyuncs.com/k8sth/kubernetes-dashboard-amd64:v1.8.3

kubectl apply -f kubernetes-dashboard.yaml

#删除老证书
kubectl delete secrets kubernetes-dashboard-certs -n kube-system

#配制dashboard https证书,相关证书可以在阿里云申请免费的
kubectl create secret tls kubernetes-dashboard-certs --key ./214658435890700.key --cert ./214658435890700.pem -n kube-system
#

#创建管理用户
cat >admin-user.yaml<<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system
EOF
kubectl apply -f admin-user.yaml 

#配制ingress
cat >dashboard-ingress.yaml<<EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/secure-backends: "true"
  name: dashboard-ingress
  namespace: kube-system
spec:
  tls:
  - hosts:
    - k8s.xxxx.xxx
    secretName: kubernetes-dashboard-certs
  rules:
  - host: k8s.xxx.xxx
    http:
      paths:
      - backend:
          serviceName: kubernetes-dashboard
          servicePort: 443
EOF
kubectl apply -f dashboard-ingress.yml 

#获取token用于登录
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')

安装阿里云相关插件

首先所有机器都必须写入配制文件,accessKeyID和accessKeySecret阿里云可取

cat >/etc/kubernetes/cloud-config<<EOF
{
    "global": {
     "accessKeyID": "xxx",
     "accessKeySecret": "xxxx"
   }
}
EOF

存储插件需要修改kubelte的配制文件

cat >/etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--enable-controller-attach-detach=false --cgroup-driver=cgroupfs --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.1"
EOF
systemctl daemon-reload
systemctl restart kubelet
cat > flexvolume.yaml<<EOF
apiVersion: apps/v1 # for versions before 1.8.0 use extensions/v1beta1
kind: DaemonSet
metadata:
  name: flexvolume
  namespace: kube-system
  labels:
    k8s-volume: flexvolume
spec:
  selector:
    matchLabels:
      name: acs-flexvolume
  template:
    metadata:
      labels:
        name: acs-flexvolume
    spec:
      hostPID: true
      hostNetwork: true
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      containers:
      - name: acs-flexvolume
        image: registry.cn-hangzhou.aliyuncs.com/acs/flexvolume:v1.9.7-42e8198
        imagePullPolicy: Always
        securityContext:
          privileged: true
        env:
        - name: ACS_DISK
          value: "true"
        - name: ACS_NAS
          value: "true"
        - name: ACS_OSS
          value: "true"
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: usrdir
          mountPath: /host/usr/
        - name: etcdir
          mountPath: /host/etc/
        - name: logdir
          mountPath: /var/log/alicloud/
      volumes:
      - name: usrdir
        hostPath:
          path: /usr/
      - name: etcdir
        hostPath:
          path: /etc/
      - name: logdir
        hostPath:
          path: /var/log/alicloud/
EOF
kubectl apply -f flexvolume.yaml
cat >disk-provisioner.yaml<<EOF
---
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: alicloud-disk-common
provisioner: alicloud/disk
parameters:
  type: cloud
---
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: alicloud-disk-efficiency
provisioner: alicloud/disk
parameters:
  type: cloud_efficiency
---
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: alicloud-disk-ssd
provisioner: alicloud/disk
parameters:
  type: cloud_ssd
---
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: alicloud-disk-available
provisioner: alicloud/disk
parameters:
  type: available
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: alicloud-disk-controller-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: alicloud-disk-controller
  namespace: kube-system
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: run-alicloud-disk-controller
subjects:
  - kind: ServiceAccount
    name: alicloud-disk-controller
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: alicloud-disk-controller-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: alicloud-disk-controller
  namespace: kube-system
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: alicloud-disk-controller
    spec:
      tolerations:
      - effect: NoSchedule
        operator: Exists
        key: node-role.kubernetes.io/master
      - effect: NoSchedule
        operator: Exists
        key: node.cloudprovider.kubernetes.io/uninitialized
      nodeSelector:
         node-role.kubernetes.io/master: ""
      serviceAccount: alicloud-disk-controller
      containers:
        - name: alicloud-disk-controller
          image: registry.cn-hangzhou.aliyuncs.com/acs/alicloud-disk-controller:v1.9.3-ed710ce
          volumeMounts:
            - name: cloud-config
              mountPath: /etc/kubernetes/
            - name: logdir
              mountPath: /var/log/alicloud/
      volumes:
        - name: cloud-config
          hostPath:
            path: /etc/kubernetes/
        - name: logdir
          hostPath:
            path: /var/log/alicloud/
EOF
kubectl apply -f disk-provisioner.yaml

#安装nas盘
cat >nas-controller.yaml<<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: alicloud-nas
provisioner: alicloud/nas
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: alicloud-nas-controller
  namespace: kube-system
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: run-alicloud-nas-controller
subjects:
  - kind: ServiceAccount
    name: alicloud-nas-controller
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: alicloud-disk-controller-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: alicloud-nas-controller
  namespace: kube-system
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: alicloud-nas-controller
    spec:
      tolerations:
      - effect: NoSchedule
        operator: Exists
        key: node-role.kubernetes.io/master
      - effect: NoSchedule
        operator: Exists
        key: node.cloudprovider.kubernetes.io/uninitialized
      nodeSelector:
         node-role.kubernetes.io/master: ""
      serviceAccount: alicloud-nas-controller
      containers:
        - name: alicloud-nas-controller
          image: registry.cn-hangzhou.aliyuncs.com/acs/alicloud-nas-controller:v1.8.4
          volumeMounts:
          - mountPath: /persistentvolumes
            name: nfs-client-root
          env:
            - name: PROVISIONER_NAME
              value: alicloud/nas
            - name: NFS_SERVER
              value: 2b9c84b5cc-vap63.cn-shenzhen.nas.aliyuncs.com
            - name: NFS_PATH
              value: /
      volumes:
        - name: nfs-client-root
          nfs:
            server: 2b9c84b5cc-vap63.cn-shenzhen.nas.aliyuncs.com
            path: /
EOF
kubectl apply -f nas-controller.yaml

小技巧

忘记初始master节点时的node节点加入集群命令怎么办

kubeadm token create --print-join-command

参考文档: ———–

点我阅读更多...

Postgres k8s 集群

postgres集群方案

总结一下在k8s集群上部署一套高可用、读写分离的postgres集群的方案,大体上有以下三种方案可以选择:

Patroni

只了解了一下官网说明,大体上应该是一套可以进行自定义模版

Crunchy

将现有的开源框架分别整理成Dokcer镜像并做了配制的自定义,通过这些镜像组合提供各种类型的postgres集群解决方案。

官方推荐解决方案

stolon

Stolon是专门用于部署在云上的PostgresSql高可用集群,与Kubernetes集成较好。

架构图:

架构图

选择

主要在stolon和Crunchy对比:

stolon

  • 高可用做的很好,故障转移比较靠谱;
  • 并不支持读写分离;
  • 存储是在etcd或者consul,未做性能测试不知道效果怎么样;

Cruuchy

  • 可以自定义集群的方案,比较灵活;
  • 通过pgpool进行读写分离
  • 高可用方案依赖于k8s,Master如启动多个Pod需要采用 Access Modes 为 ReadWriteMany 的Volume,支持ReadWriteMany的Volume类型并不多而且多半是网盘性能指标都不高,再者就依赖k8s对pod的故障转移了;
  • 其它备份、恢复和管理都提供了相应的组件,可以选择性安装
  • 对各类组件都自定义的docker镜像有耦合问题

对比后Cruuchy更加满足我的需求,Master的高可用问题今后我也可以再找更加好的解决方案替换掉Master的部署(或者Cruuchy本身就有解决方案只是我还不会….)

Cruuchy 部署

相关部署都是阿里云的k8s集群上,我的集群部署请参考阿里云 k8s 1.10 版本安装

整个安装做了一个Helm chart,直接使用命令安装

git clone https://github.com/jamesDeng/helm-charts.git

cd pgsql-primary-replica

helm install ./ --name pgsql-primary-replica --set primary.pv.volumeId=xxxx

primary.pv.volumeId 为阿里云盘ID

说明

组成结构还不完整,其它部分还需要慢慢完善,目前为:

  • pgpool-II deployment
  • primary deployment
  • replica statefulset

create the following in your Kubernetes cluster:

  • Create a service named primary
  • Create a service named replica
  • Create a service named pgpool
  • Create a deployment named primary
  • Create a statefulset named replica
  • Create a deployment named pgpool
  • Create a persistent volume named primary-pv and a persistent volume claim of primary-pvc
  • Create a secret pgprimary-secret
  • Create a secret pgpool-secrets
  • Create a configMap pgpool-pgconf

自定义配制文件

根目录下:

  • pool-configs
    • pgpool.conf
    • pool_hba.conf
    • pool_passwd
  • primary-configs
    • pg_hba.conf
    • postgresql.conf
    • setup.sql

如果使用repo的方式安装,由于没有找到helm install替换这些文件的方式,目前有两个解决方案:

  1. install完成后手动修改相应的configMap和secret的内容
  2. helm fetch pgsql-primary-replica –untar 下源文件,修改相应的配件文件,采用 helm install ./ 的方式安装

postgres 用户名和密码修改

如果是通过pgpool连接数据库的话,必须在pool_passwd配制postgres的用户名和密码

部署前

可以设置默认用户postgres和PG_USER对应用户的密码

  1. 修改values.yaml中PG_ROOT_PASSWORD或者PG_PASSWORD的值,
  2. 使用pgpool的pg_md5 指令生成密码(pg_md5 –md5auth –username= )
  3. 修改pool-configs/pool_passwd中的用户名和密码

部署后修改

先获取postgres的用户名和md5密码

$ kubectl get pod -n thinker-production
NAME                       READY     STATUS    RESTARTS   AGE
pgpool-67987c5c67-gjr5h    1/1       Running   0          3d
primary-6f9f488df8-6xjjd   1/1       Running   0          3d
replica-0                  1/1       Running   0          3d

# 进入primary pod
$ kubectl exec -it primary-6f9f488df8-6xjjd  -n thinker-production  sh

$ psql

#执行sql查找md5
$ select usename,passwd from pg_shadow;  
   usename   |               passwd                
-------------+-------------------------------------
 postgres    | md5dc5aac68d3de3d518e49660904174d0c
 primaryuser | md576dae4ce31558c16fe845e46d66a403c
 testuser    | md599e8713364988502fa6189781bcf648f
(3 rows)

pool_passwd 的格式为

postgres:md5dc5aac68d3de3d518e49660904174d0c

k8s secret 的values为bash46,转换

$ echo -n 'postgres:md5dc5aac68d3de3d518e49660904174d0c' | base64
cG9zdGdyZXM6bWQ1ZGM1YWFjNjhkM2RlM2Q1MThlNDk2NjA5MDQxNzRkMGM=

编辑 pgpool-secrets 替换 pool_passwd 值

kubectl edit secret pgpool-secrets  -n thinker-production

编辑 pgpool deploy,触发滚动更新

kubectl edit deploy pgpool  -n thinker-production
# 一般可以修改一下 terminationGracePeriodSeconds 的值

自定义Values.yaml

Values.yaml的值请参考源码

helm install ./ --name pgsql-primary-replica -f values.yaml

参考文档 ————–

点我阅读更多...

服务docker化

运维采用k8s后,相关服务需要做到自动生成Docker images并上传到私服,私服采用的阿里云仓库并配合VPC网络

Java环境说明

  1. jdk 1.8
  2. spring-boot 2.0
  3. maven
  4. dockerfile-maven 1.3.6

Java image

项目采用Maven构建,使用dockerfile-maven-plugin来做docker images的构建与上传

插件引用

在pom.xml中添加插件引用

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-deploy-plugin</artifactId>
    <version>2.7</version>
    <configuration>
        <!-- 排除该工程不deploy 到远程服务器上 -->
        <skip>true</skip>
    </configuration>
</plugin>
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <mainClass>vc.thinker.cabbage.web.AdminServerMain</mainClass>
        <layout>JAR</layout>
        <executable>true</executable>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>dockerfile-maven-plugin</artifactId>
    <version>1.3.6</version>
    <executions>
        <execution>
            <id>default</id>
            <goals>
                <goal>build</goal>
                <goal>push</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <!-- 仓库地址 -->
        <repository>registry-vpc.cn-shenzhen.aliyuncs.com/xxx/${project.artifactId}</repository>
        <!-- docker images tag -->
        <tag>${project.version}</tag>
        <!-- 采用Maven Settings进行docker仓库授权 -->
        <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
        <!-- dockerfile ARG,buildArgs可自定义多个配合dockerfile 的ARG指令使用 -->
        <buildArgs>
            <!-- jar地址 -->
            <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
            <!-- 配制文件地址 -->
            <PROPERTIES>target/classes/${project.build.finalName}/application.properties</PROPERTIES>
            <!-- 启动执行命令 -->
            <START>target/classes/${project.build.finalName}/k8s-start.sh</START>
        </buildArgs>
    </configuration>
</plugin>

dockerfile

在项目根目录下添加Dockerfile文件

#built java images,注意本例中没有处理时区问题
FROM openjdk:8-jre

ARG JAR_FILE
ARG PROPERTIES
ARG START

COPY ${JAR_FILE} /JAVA/app.jar

COPY ${PROPERTIES} /JAVA/application.properties

COPY ${START} /JAVA/k8s-start.sh

RUN chmod +x /JAVA/k8s-start.sh && \
    ln -s /JAVA/k8s-start.sh /usr/bin/k8s-start

#web 服务端口
EXPOSE 8080

#CMD ["k8s-start"]
ENTRYPOINT ["k8s-start"]

配制文件与执行命令

src/main/resources 下添加 k8s-start.sh 和 application.properties,这里说明一下k8s-start.sh

#!/bin/sh

#采用环境变量的方式修改配制文档,由于环境变量不能有".-"字符,约定".-"统一转换为下划线_
function change(){
        echo "準備修改配置項 $1"
        local j=`echo "$1"| sed 's/\./_/g'| sed 's/\-/_/g'`
        #echo $j
        local i=`eval echo '$'{"$j"}`
        echo "對應變量為 $j"
        [[ ! $i ]] && echo "$j 環境變量不存在,不進行替換!" && return 0
        sed -i "s/$1=.*/$1=$i/" /ROOT/application.properties && echo "替換變量 $i 成功 "
}


#修改配制文档,
change spring.datasource.url
change spring.datasource.username
change spring.datasource.password

cd /JAVA

if [[ "$1" == 'sh' || "$1" == 'bash' || "$1" == '/bin/sh' ]]; then
  exec "/bin/sh"
fi

#java-conf为jvm参数
echo "配置:java-conf=$java_conf"
if [ "${java_conf}" ]; then
  echo "运行java命令:java $java_conf -jar app.jar "
  exec su-exec java java $java_conf -jar app.jar
fi

echo "运行java命令:java "$@" -jar app.jar"

exec su-exec java java "$@" -jar app.jar

docker仓库权限

在maven settings.xml设置仓库地址与权限

<configuration>
  <repository>docker-repo.example.com:8080/organization/image</repository>
  <tag>latest</tag>
  <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
</configuration>

权限

<servers>
  <server>
    <id>docker-repo.example.com:8080</id>
    <username>me</username>
    <password>mypassword</password>
  </server>
</servers>

使用说明

本例中直接执行 mvn deploy 就可以做到docker images的构建和上传

mvn deploy

本地运行

docker run --name my-server -p 8080:8080 \
-e spring_datasource_username="test"
-d images:tag \

-e spring_datasource_username=”test” 为修改application.properties中的spring.datasource.username配制

Dockerfile Maven的特性

默认,构建Dockerfile的过程包含在mvn package阶段;

默认,为Dockerfile打标签的过程包含在mvn package阶段;

默认,发布Dockerfile的过程包含在mvn deploy阶段;

也可以直接陆续执行:

mvn dockerfile:build

mvn dockerfile:tag

mvn dockerfile:push

如运行出现 “No plugin found for prefix ‘dockerfile’ in the current project and in the plugin groups [org.apache.maven.plugins”我们需要在settings.xml里把这个插件加入白名单

<settings>
    <pluginGroups>
        <pluginGroup>com.spotify</pluginGroup>
    </pluginGroups>
</settings>

跳过Dockerfile相关的构建、发布

如果要临时跳过所有的Dockerfile相关的所有3个goals,执行如下Maven命令:

mvn clean package -Ddockerfile.skip

如果只是想跳过某一个goal,执行如下Maven命令:

mvn clean package -Ddockerfile.build.skip

mvn clean package -Ddockerfile.tag.skip

mvn clean package -Ddockerfile.push.skip

点我阅读更多...