微服务架构师封神之路01-利用minikube部署一个最简单的应用
开篇
架构师者,为项目消灾解惑者也。只有骨骼清奇之人,得高人指点,甘于处子之身苦心修炼十年以上者,方可得道成仙。一人之下,万人之上,可谓朝堂之上呼风唤雨,岂不美哉~
但这条道路上也充满凶险,可是却有幸福相伴。各位看官可能会问,要十年甘于处子之身那幸福来自哪里?我想说看来你的层次还是不够,在追求梦想的路上再大的艰辛不也是幸福吗?!有梦想的程序猿时刻要双手打字以示决心!
反正想多了没用。拿上实践和理论这两把上古流传的神兵,趁月黑风高今晚就启航。接下来的几年中我将披荆斩棘…
闲话少叙…
目标
第一天来点简单的吧,部署一个最简单的web应用到minikube上。Hello World,别犹豫就是它了!
如果用Java的话,我觉得最简单的方式应该是使用springboot。简而言之,我们需要做一下几件事情,
- 创建一个springboot为框架的maven project
- 把打好的jar包制成docker image
- 把docker image发布到remote repository上。我使用docker hub
- 使用minikube部署这个image。
我想以上的步骤足够可以模拟一个项目从开发到部署的基本流程了。有了需求也就明确了我们所需要的环境,
- JDK
- maven
- Docker。在build image和push image都需要它
- Kerbernetes。
- Minikube。minikube的基础是Kerbernetes,其实它就是一个single node kubernetes cluster。
环境搭建参考:
- Installing Docker: https://www.docker.com/products/docker-desktop
- Installing kubernetes: https://kubernetes.io/docs/tasks/tools/install-kubectl/
- Installing minikube: https://kubernetes.io/docs/tasks/tools/install-minikube/
在它们的官方网站上有详细的安装说明,把环境弄好以后我们可以进行下一步了。
helloworld maven项目
创建一个maven project,结构如下
很简单,只有三个文件,Dockerfile、AppMain.java、pom.xml。思路也很简单,
- 利用spring-boot制作一个可以独立运行的helloworkd.jar包。它只依赖于jre。
- 用openjdk 8的image作为基础,把helloworld.jar和它结合在一起,再添加加一个启动文件。启动文件会在container加载image的时候被调用。把它们做成一个新的docker image。image的具体细节在Dockerfile中。
- 制作docker image的关键步骤由maven plugin完成。首先maven-resources-plugin把build image需要的Dockerfile拷贝到指定位置,紧接着exec-maven-plugin直接调用docker build命令制作image。
pom.xml
核心步骤是exec-docker-build。需要详细理解命令
docker build --no-cache --tag b5wang/helloworld:1.0 -f ${project.build.directory}/docker-build/Dockerfile ${project.build.directory}
的具体含义。为什么image repository要叫b5wang/helloworld?这个和后面我们要做的docker push有直接关系。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
</parent>
<groupId>com.b5wang.cloudlab</groupId>
<artifactId>helloworld</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>helloworld</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- =========================================================================
== Copy docker config files into target/docker-build folder ==
========================================================================= -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>generate-docker-build</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/docker-build</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/docker</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- =========================================================================
== Create docker image ==
========================================================================= -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>exec-docker-build</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<commandlineArgs>build --no-cache -t b5wang/helloworld:1.0 -f ${project.build.directory}/docker-build/Dockerfile ${project.build.directory}</commandlineArgs>
</configuration>
</execution>
</executions>
</plugin>
<!-- fix error java.lang.ClassNotFoundException: org.apache.maven.doxia.siterenderer.DocumentContent -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.8.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
Dockerfile
FROM openjdk:8-jdk-alpine
COPY helloworld.jar /app/helloworld.jar
ENTRYPOINT ["java","-jar","/app/helloworld.jar"]
AppMain.java
package com.b5wang.cloudlab.helloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableAutoConfiguration
public class AppMain {
@RequestMapping("/")
String home() {
return "minikube: Hello World!";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(AppMain.class, args);
}
}
$ mvn clean install
运行之前确认两件事情:
- 要保证本地的docker daemon处于运行状态。否则有错误提示: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
- docker需要登录docker hub账户。之后的push需要登录。
现在可以运行mvn clean install了。运行成功后通过docker images
检查,
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
b5wang/helloworld 1.0 5c6f1985a2bb 3 hours ago 122MB
openjdk 8-jdk-alpine a3562aa0b991 12 months ago 105MB
$ docker push
项目叫helloworld,所以我想把它的docker image repository也叫做helloworld。但在docker hub上这并不可能,除非这个image是docker certified image,像openjdk、weblogic之类的。一般的docker hub repository都是以账户名称开头的,例如,b5wang/xxx,那我们不妨把现在的项目image repository叫做b5wang/helloworld。在项目pom里面的docker build指令已经指定了repository name就是b5wang/hello,并tag成1.0.
docker build --no-cache --tag b5wang/helloworld:1.0 -f ${project.build.directory}/docker-build/Dockerfile ${project.build.directory}
目前的repository name已经符合docker hub的规范,所以就不需要再docker tag
多一次。直接push
就可以。如果在本地repository name用了其它的名字,需要再tag一次,让的名字符合规范后才能提交到你的docker hub账户下面。
例如,假设本地image repository名字叫helloworld,你想把image提交到b5wang/helloworld repository当中。你需要把image先提交到本地的b5wang/helloworld repository中。
docker tag helloworld:1.0 b5wang/helloworld:1.0
再把b5wang/helloworld repository push到docker hub registry。因为我们在maven build的时候把image repository直接就命名成了b5wang/helloworld,以上的步骤我们是不需要的。
下面发布local repostory到docker hub,
$ docker push b5wang/helloworld:1.0
The push refers to repository [docker.io/b5wang/helloworld]
1c227dd7976a: Layer already exists
ceaf9e1ebef5: Layer already exists
9b9b7f3d56a0: Layer already exists
f1b5933fe4b5: Layer already exists
1.0: digest: sha256:f45d9ff8003effe76157c37107e96b4ee080a128703f67ec613473943a2e16a0 size: 1159
检查minikube的状态
$ minikube start, statue
启动minikube并检查运行状态
$ minikube start
😄 minikube v1.11.0 on Darwin 10.13.6
✨ Using the hyperkit driver based on existing profile
👍 Starting control plane node minikube in cluster minikube
🔄 Restarting existing hyperkit VM for "minikube" ...
🐳 Preparing Kubernetes v1.18.3 on Docker 19.03.8 ...
🔎 Verifying Kubernetes components...
🌟 Enabled addons: default-storageclass, storage-provisioner
🏄 Done! kubectl is now configured to use "minikube"
$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
$ docker login docker.io // 登录docker hub账户
登录docker hub。可能你已经注意到了,你build image使用的和minikube使用的不是同一个docker daemon。其实只用一个docker daemon也是可以的,这取决于你如何安装minikube。但就模拟现实部署流程来说可能这样反而会真实点。
$ minikube ssh _ _
_ _ ( ) ( )
___ ___ (_) ___ (_)| |/') _ _ | |_ __
/' _ ` _ `\| |/' _ `\| || , < ( ) ( )| '_`\ /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )( ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)
$ docker login docker.io
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: b5wang
Password:
WARNING! Your password will be stored unencrypted in /home/docker/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
$ exit
logout
在minikube上部署应用
$ kubectl create deployment // 创建deployment
在minikube上创建一个deployment。Deployment负责管理应用,把image运行在pod当中。pod相当于docker的container。
$ kubectl create deployment helloworld --image=b5wang/helloworld:1.0
deployment.apps/helloworld created
观察当前deployment和pod的状态
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
helloworld 1/1 1 1 5m44s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
helloworld-5889797b45-k6nxr 1/1 Running 0 5m53s
可以通过观察event来理解helloworld应用在kubernetes中部署的过程,
Shufengs-iMac:~ wangbo$ kubectl get events
LAST SEEN TYPE REASON OBJECT MESSAGE
77m Normal Scheduled pod/helloworld-5889797b45-k6nxr Successfully assigned default/helloworld-5889797b45-k6nxr to minikube
77m Normal Pulling pod/helloworld-5889797b45-k6nxr Pulling image "b5wang/helloworld:1.0"
77m Normal Pulled pod/helloworld-5889797b45-k6nxr Successfully pulled image "b5wang/helloworld:1.0"
77m Normal Created pod/helloworld-5889797b45-k6nxr Created container helloworld
77m Normal Started pod/helloworld-5889797b45-k6nxr Started container helloworld
77m Normal SuccessfulCreate replicaset/helloworld-5889797b45 Created pod: helloworld-5889797b45-k6nxr
77m Normal ScalingReplicaSet deployment/helloworld Scaled up replica set helloworld-5889797b45 to 1
注意到到有一个pulling的过程,如果minikube所依赖的docker daemon事先没有登录到docker hub,它就不可能找到指定的image,pulling过程就会失败。
$ minikube dashboard
minikube已经集成了kubernetes dashboard UI,运行以下命令就可以用browser来监控当前minikube中各种应用的运行状态。
$ minikube dashboard
🔌 Enabling dashboard ...
🤔 Verifying dashboard health ...
🚀 Launching proxy ...
🤔 Verifying proxy health ...
🎉 Opening http://127.0.0.1:50551/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser...
$ kubectl expose deployment // 创建Service
此刻helloworld运行在pod中,pod相当于docker container。但是pod只能在kubernetes cluster内部间相互访问,我们要在外部访问到pod还需要创建service。Service相当于运行在pod前端的load balancer。
$ kubectl expose deployment helloworld --type=LoadBalancer --port=8080
service/helloworld exposed
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helloworld LoadBalancer 10.110.57.57 <pending> 8080:30330/TCP 34s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 23h
访问helloworld应用
$ minikube service helloworld
|-----------|------------|-------------|---------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|------------|-------------|---------------------------|
| default | helloworld | 8080 | http://192.168.64.2:30330 |
|-----------|------------|-------------|---------------------------|
🎉 Opening service default/helloworld in default browser...
对我们目前所做的这些步骤的理解
画一张图把上面我们做的所有事情形象的表现出来,
总结
今天实践了如下这些知识点,
- 如何把一个java应用通过maven制作成docker image
- 把本地的docker image发布到远程docker registry上面
- 利用kubernetes和docker image部署应用程序
- 创建kubernetes service,暴露应用给外部
完整的源码:https://github.com/b5wang/cloudlab
当然还需要花一些时间去理解每一步中更细节的东西,不断的提出问题并解决。
今天就到这里。
干就完事了啊!奥利给!
再见~ :)