k8s 实战篇 - mysql部署 - 1

Posted 彷徨的蜗牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了k8s 实战篇 - mysql部署 - 1相关的知识,希望对你有一定的参考价值。

在项目的开发中肯定是需要使用数据库进行数据化持久操作。在JAVA项目中使用比较多的数据库是mysql,这篇文章主要描述怎么在k8s中安装mysql数据库。

mysql部署

1、获取mysql镜像

可以在dockerhub中搜索镜像地址,首先打开“Docker Hub”,在搜索框中输入mysql,找到mysql官方发布的docker镜像地址为“mysql”,mysql的端口为3306。按照之前部署demo的操作,重写一下执行命令。如下:

kubectl create deployment mysql --image=mysql 
kubectl expose deployment mysql --type=NodePort --port=3306

重写完成之后在控制台执行查看

2、创建mysql pod

执行之后通过get pod查看是否创建成功

C:\\Users\\smy1102>kubectl get pods
NAME                          READY   STATUS              RESTARTS        AGE
docker-demo-864cc5fb9-rrd59   1/1     Running             0               5h45m
mysql-77db55c6db-22lxf        0/1     CrashLoopBackOff   0               47s

生成了一条mysql-77db55c6db-22lxf 记录,pod的状态为CrashLoopBackOff。

3、CrashLoopBackOff问题梳理

我之前发布了一篇文章“minikube 快速使用入门 - pod - 外传”,这里面有详细记录pod的状态信息,找到相关说明。如下:

状态描述
CrashLoopBackOff容器退出,kubelet 正在将它重启

上面显示的状态不是running,这个肯定是部署的时候出现问题才会出现的。这时候应该排查一下问题,如:查看这个pod的执行日志是否正常,查看这个pod的详细情况。这些命令,我在文章“minikube 快速使用入门 - 命令篇 - 4”也有记录。如下:

# 查看 pod 详情
kubectl describe pod PodName
# 查看 log
kubectl logs PodName

首先执行一下“查看pod详情”:

C:\\Users\\smy1102>kubectl describe pod mysql-77db55c6db-22lxf
Name:         mysql-77db55c6db-22lxf
Namespace:    default
Priority:     0
Node:         minikube/192.168.49.2
Start Time:   Fri, 12 Aug 2022 14:04:36 +0800
Labels:       app=mysql
              pod-template-hash=77db55c6db
Annotations:  <none>
Status:       Running
IP:           172.17.0.5
IPs:
  IP:           172.17.0.5
Controlled By:  ReplicaSet/mysql-77db55c6db
Containers:
  mysql:
    Container ID:   docker://df31a04e1ced939cbf238fb9003a1b6057ffbc80b5767f6a31b3ac2e8af50142
    Image:          mysql
    Image ID:       docker-pullable://mysql@sha256:ce2ae3bd3e9f001435c4671cf073d1d5ae55d138b16927268474fc54ba09ed79
    Port:           <none>
    Host Port:      <none>
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       Error
      Exit Code:    1
      Started:      Fri, 12 Aug 2022 14:08:11 +0800
      Finished:     Fri, 12 Aug 2022 14:08:11 +0800
    Ready:          False
    Restart Count:  5
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-x6gj7 (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             False
  ContainersReady   False
  PodScheduled      True
Volumes:
  kube-api-access-x6gj7:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason     Age                    From               Message
  ----     ------     ----                   ----               -------
  Normal   Scheduled  5m41s                  default-scheduler  Successfully assigned default/mysql-77db55c6db-22lxf to minikube
  Normal   Pulled     5m37s                  kubelet            Successfully pulled image "mysql" in 3.559175s
  Normal   Pulled     5m33s                  kubelet            Successfully pulled image "mysql" in 3.5035799s
  Normal   Pulled     5m15s                  kubelet            Successfully pulled image "mysql" in 5.3749663s
  Normal   Created    4m36s (x4 over 5m37s)  kubelet            Created container mysql
  Normal   Started    4m36s (x4 over 5m37s)  kubelet            Started container mysql
  Normal   Pulled     4m36s                  kubelet            Successfully pulled image "mysql" in 8.1586183s
  Normal   Pulling    3m42s (x5 over 5m41s)  kubelet            Pulling image "mysql"
  Warning  BackOff    33s (x22 over 5m33s)   kubelet            Back-off restarting failed container

查看pod详情,在详情最后有“Back-off restarting failed container”,这意思应该是重启一下错误的容器。这个mysql容器出现了异常,需要重启。那到底是什么异常导致的,还不知道。这个时候应该去查看一下容器执行日志看看是否是部署执行的时候出现了什么异常情况。执行命令"kubectl logs "。

C:\\Users\\smy1102>kubectl logs  mysql-77db55c6db-22lxf
2022-08-12 06:16:13+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.30-1.el8 started.
2022-08-12 06:16:13+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2022-08-12 06:16:13+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.30-1.el8 started.
2022-08-12 06:16:13+00:00 [ERROR] [Entrypoint]: Database is uninitialized and password option is not specified
    You need to specify one of the following:
    - MYSQL_ROOT_PASSWORD
    - MYSQL_ALLOW_EMPTY_PASSWORD
    - MYSQL_RANDOM_ROOT_PASSWORD

执行“kubectl logs mysql-77db55c6db-22lxf”之后,日志里面有异常提示“[ERROR] [Entrypoint]: Database is uninitialized and password option is not specified You need to specify one of the following:
- MYSQL_ROOT_PASSWORD” 按这个意思来说,应该是数据库没有初始化密码,需要对它进行初始化及设置MYSQL_ROOT_PASSWORD参数等。初始化密码,这个要怎么设置就成了一下问题。我参考了一下相关资料,原来是要编写一个部署文件。

4、编写部署文件

根据启动日志的描述,缺少初始化密码。首先在执行部署的时候设置初始密码。执行文件如下:

apiVersion: apps/v1
kind: Deployment # 
metadata:
  name: mysql #名称,全局唯一
  namespace: default # 默认空间
spec:
  replicas: 1 #Pod 副本的期待数量
  selector:
    matchLabels:
      app: mysql # 符合目标的Pod拥有此标签
  template: # 根据此模版创建Pod的副本
    metadata:
      labels:
        app: mysql # Pod副本拥有的标签,对应Selector
    spec:
      containers: # Pod的内容的定义部分
        - name: mysql # 容器的名称
          image: mysql # 容器对应的Docker Image
          ports:
            - containerPort: 3306 # 容器应用监听的端口号
          env:
            - name: MYSQL_ROOT_PASSWORD # 设置mysql的初始化密码
              value: "123456" # 设置mysql的初始化密码
---
apiVersion: v1
kind: Service # 表明是Kubernetes Service
metadata:
  name: mysql # Service 的全局唯一名称
spec:
  type: NodePort
  selector:
    app: mysql
  ports: # Service 提供服务的端口
    - port: 3306 # Service 对应的Pod拥有这里定义的标签

以上就是配置文件需要准备的配置,上面有一些注释,可以详细了解。配置上已经加入“MYSQL_ROOT_PASSWORD”的初始化,可以把它保存为一个yaml的文件,如mysql-k8s.yaml。

5、部署之前,先删除之前部署的mysql

在执行配置文件之前,需要先把之前部署的mysql删除掉.如下:

smy1102@LAPTOP-7HC3FEQ9 C:\\Users\\smy1102>kubectl delete deployment mysql
deployment.apps "mysql" deleted

smy1102@LAPTOP-7HC3FEQ9 C:\\Users\\smy1102>kubectl delete svc mysql
service "mysql" deleted

删除之后,就可以开始部署mysql了。

6、执行部署mysql

进入到存放mysql-k8s.yaml的文件夹下执行“kubectl apply -f mysql-k8s.yaml”,可以参考文章“minikube 快速使用入门 - 命令篇 - 4”,如下:

smy1102@LAPTOP-7HC3FEQ9 D:\\minikube\\source_data>kubectl apply -f mysql-k8s.yaml
deployment.apps/mysql created
service/mysql created

执行完成之后,控制台显示创建成功,需要查看一下pod的状态是否出现异常。

smy1102@LAPTOP-7HC3FEQ9 D:\\minikube\\source_data>kubectl get pods
NAME                          READY   STATUS    RESTARTS      AGE
docker-demo-864cc5fb9-rrd59   1/1     Running   0             27h
mysql-5fd99bf785-pdvhx        1/1     Running   0             2m21s

Pod的状态显示“Running”,说明pod现在是正在运行的状态。然后需要把mysql的端口进行“port-forward”,“port-forward”之后就可以在宿主机上连接测试了。可以参考文章“minikube 快速使用入门 - 命令篇 - 4”

# 把集群内端口映射到节点 把8080端口映射到7080
kubectl port-forward mysql-5fd99bf785-pdvhx 3306:3306

执行之后本地测试一下mysql客户端是否链接正常

提示“连接成功”。
mysql部署已经完成。

ASP.NET Core 快速入门(实战篇)


来源:农码一生

cnblogs.com/zhaopei/p/netcore2.html


上篇讲了。今天我们将做几个小玩意实战一下。用到的技术和工具有mysql、websocket、AngleSharp(爬虫html解析)、nginx多站点部署。


NO1 留言板(mysql的使用)


演示:http://haojima.net


这个功能很简单。就是对数据库的写入和展示。如果在Windows下,相信大家分分钟都可以搞定。而初次接触.net core + mysql可能需要注意些细节。


首先打开vs2017新建一个asp.net core项目(选Web应用程序),然后nuget 导入Microsoft.EntityFrameworkCore.Tools 1.1.1和MySql.Data.EntityFrameworkCore 8.0.8-dmr。


然后新建一个DbContext类。


public class DataContext : DbContext

{

    //【注意】连接字符串一定要加 sslmode=none 

    string str = @"Data Source=;Database=;

     User ID=;Password=;pooling=true;CharSet=utf8;port=3306;sslmode=none";

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>

        optionsBuilder.UseMySQL(str);

    //下面就可以添加要加入数据库的实体了

    //public DbSet<Message> Messages { get; set; }

}


到此为止,我们已经可以利用EF Core直接连接mysql进行增删改查操作了。注意:需要导入命名空间


using Microsoft.EntityFrameworkCore; 

using MySQL.Data.EntityFrameworkCore.Extensions;


当然。你会说,连接字符串不能硬编码到代码里面。我们也可以放配置文件。


appsettings.json

{

  "Logging": {

    "IncludeScopes": false,

    "LogLevel": {

      "Default": "Warning"

    }

  },

  "ConnectionStrings": { "SqlServerConnection": "Data Source=;Database=;User ID=;Password=;pooling=true;CharSet=utf8;port=3306;sslmode=none" }

}


然后把上面的硬编码注释掉。在Startup.cs文件的ConfigureServices方法添加


var connection = Configuration.GetConnectionString("SqlServerConnection");

services.AddDbContext<DataContext>(options => options.UseMySQL(connection));


注意:项目名称和路径最好不要有中文,不然会出现些乱七八糟的问题。


完整代码:

https://github.com/zhaopeiym/BlogDemoCode/tree/master/MessageBoard


NO2 聊天室(WebSocket的使用)


演示:http://socket.haojima.net


WebSocket是Html5新增的一个很酷的技术。下面我们简单讲解下这个很酷的技术


var Socket = new WebSocket(url);//创建 WebSocket 对象


创建了一个WebSocket对象后会触发打开连接事件:


Socket.onopen = function(){  }


除了onopen事件,还有其他三个事件:


Socket.onmessage  //客户端接收服务端数据时触发

Socket.onerror    //通信发生错误时触发

Socket.onclose    //连接关闭时触发


另外还有两个方法:


Socket.send()   //使用连接发送数据

Socket.close()  //关闭连接


最后还有四个连接状态属性:


Socket.readyState

0 - 表示连接尚未建立。

1 - 表示连接已建立,可以进行通信。

2 - 表示连接正在进行关闭。

3 - 表示连接已经关闭或者连接不能打开。


整个WebSocket常用功能知识点就四个事件、两个方法、四种状态。简单吧,下面我们看看asp.net core后台的配合:


后台添加一个SocketHandler类,并添加一个静态方法Map:


/// <summary>

/// 请求

/// </summary>

/// <param name="app"></param>

public static void Map(IApplicationBuilder app)

{

    app.UseWebSockets(); //【注意】需要 nuget   导入 Microsoft.AspNetCore.WebSockets.Server

    app.Use(Acceptor);

}


然后新增对应的Acceptor方法:


/// <summary>

/// 接收请求

/// </summary>

/// <param name="httpContext"></param>

/// <param name="n"></param>

/// <returns></returns>

static async Task Acceptor(HttpContext httpContext, Func<Task> n)

{


需要在Startup.cs类里面的Configure方法里面加入


app.Map("/ws", SocketHandler.Map);   //传入我们刚才新建的静态方法Map


现在为止,基本的类和配置已经完成。


我们主要操作,是在Acceptor方法里面接收和发送消息。


//建立连接

var socket = await httpContext.WebSockets.AcceptWebSocketAsync();

//等待接收数据

await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

//发送消息

await socket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None);


后台关键代码也就这三句,建立连接、等待接收、发送消息。


不过这里有一点需要理解。建立连接后,可以接收任意多次客户端消息。所以ReceiveAsync等待接收这里需要死循环接收消息,直到连接断开。


(不用担心真的死循环,没有消息发送的时候,代码会阻塞在那里等待消息)


完整实现:https://github.com/zhaopeiym/ChatRoom


NO3 找工作(AngleSharp的使用)


演示:http://job.haojima.net


对于爬虫抓包,我相信大家初次接触都非常的热衷于此。我也不例外。

那么在asp.net core下面是否也有这样的插件呢?答案是肯定的。


(感谢博主对.net core的贡献)。不过xpath用起来超级恶心。


之前在.NET下面有一款Jumony:


http://www.cnblogs.com/Ivony/p/3447536.html(博客园大牛写的)。


支持CSS选择和linq查询。简直不要太爽。


可是不支持.net core。(本人试了下迁移.net core,发现很多类在.net core没有实现)


最后还是到了一款支持.net core的解析组件。并可以媲美Jumony,同样支持css选择和linq查询。那就是AngleSharp。


新建项目,nuget 安装 AngleSharp。然后以下简单使用:


using (HttpClient http = new HttpClient())

{

    var htmlString = await http.GetStringAsync(url);

    HtmlParser htmlParser = new HtmlParser();

    var jobInfos = htmlParser.Parse(htmlString)

        .QuerySelectorAll(".newlist_list_content table")

        .Where(t => t.QuerySelectorAll(".zwmc a").FirstOrDefault() != null)

        .Select(t => new JobInfo()

        {

            PositionName = t.QuerySelectorAll(".zwmc a").FirstOrDefault().TextContent,

            CorporateName = t.QuerySelectorAll(".gsmc a").FirstOrDefault().TextContent,

            Salary = t.QuerySelectorAll(".zwyx").FirstOrDefault().TextContent,

            WorkingPlace = t.QuerySelectorAll(".gzdd").FirstOrDefault().TextContent,

        .ToList();

    return jobInfos;

}


看到没有,就像jq一样解析html。如果你说不爽我都不信。


完整实现:https://github.com/zhaopeiym/JobWanted


部署多个站点


以上,这些项目都比较简单。关键技术点和难点都进行的分析。我相信大家都可以动起手练习起来了。


不过有个问题,前面我们只说了部署一个应用程序。如果是多个该怎么部署呢?


首先我们把多个程序发布包放到服务器上。


然后修改nginx的配置文件/etc/nginx/conf.d/default.conf


server {

    listen 80;

    server_name www.haojima.net;           #对应的域名

    root /home/projects/messagBoard;       #程序路径

    location / {

        proxy_pass http://localhost:5000;  #内网端口

        proxy_http_version 1.1; 

        proxy_set_header Upgrade $http_upgrade;

        proxy_set_header Connection keep-alive;

        proxy_set_header Host $host;

        proxy_cache_bypass $http_upgrade;

        proxy_set_header X-real-ip $remote_addr;

        

        proxy_set_header Upgrade $http_upgrade;   

    }

}


有几个程序就添加几个server,不过需要修改你解析到的域名、程序路径和内网对应的端口(看配置里的注释) 。


然后修改supervisor的配置文件/etc/supervisor/conf.d/supervisord.conf


[program:MessageBoard]

command=dotnet MessageBoard.dll        ; 运行程序的命令

directory= /home/projects/messagBoard/ ; 命令执行的目录

autorestart=true                    ; 程序意外退出是否自动重启

stderr_logfile=/var/log/WebApplication1.err.log ; 错误日志文件

stdout_logfile=/var/log/WebApplication1.out.log ; 输出日志文件

environment=ASPNETCORE_ENVIRONMENT=Production ; 进程环境变量

user=root ; 进程执行的用户身份

stopsignal=INT


有几个程序就往下复制几份program。需要修改program名称,只要名称不重复就可以。然后修改 运行程序的命令 对应的dll和命令执行的目录(看配置文件的注释)。


如此就可以部署多个程序了。


开始我还以为是在域名解析的时候,解析IP + 端口。原来是多个域名解析到同一个IP,然后nginx在内部做域名和内网端口分发。


一些其它的细节


部署阿里云


我们在linux的防火墙开放了端口,发现在外面还是访问不了(可以telnet IP 端口 来测试)。有可能是阿里云拦截了。https://help.aliyun.com/document_detail/25471.html 在安全组添加某端口哪些IP可以访问。


mysql的客户端


对于mysql,我们安装好之后总不能每次命令操作吧。在Windows下面有个客户端Navicat可以方便管理mysql。


下载链接:http://pan.baidu.com/s/1nvMEQQt 密码:5eao


获取ip


用了nginx后发现取不到浏览器IP了。那是因为我们程序都是浏览器访问nginx,然后nginx转发内网程序端口。所以取到的IP都是内网本机IP。如果需要取浏览器IP需要在nginx配置


server {

    listen 80;

    server_name www.haojima.net;

    root /home/projects/messagBoard;

    location / {

        proxy_pass http://localhost:5000;

        proxy_http_version 1.1; 

        proxy_set_header Upgrade $http_upgrade;

        proxy_set_header Connection keep-alive;

        proxy_set_header Host $host;

        proxy_cache_bypass $http_upgrade;

        proxy_set_header X-real-ip $remote_addr;     # 新添加

    }

}


然后代码里面取IP:


 var ip = HttpContext.Request.Headers["X-real-ip"].FirstOrDefault();


WebSocket在nginx的配置


上面我们写的WebSocket直接运行发现没有任何问题,可是部署在nginx去跑不起来了。那是因为需要nginx支持WebSocket,需要配置。

http://nginx.org/en/docs/http/websocket.html


server {

    listen 80;

    server_name job.haojima.net;

    root /home/projects/jobWanted;

    location / {

        proxy_pass http://localhost:5002;

        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;

        proxy_set_header Connection keep-alive;

        proxy_set_header Host $host;

        proxy_cache_bypass $http_upgrade;

        proxy_set_header X-real-ip $remote_addr;

        proxy_set_header Upgrade $http_upgrade;     # 新增

        proxy_set_header Connection "upgrade";      # 新增 

    }

}


WebSocket心跳


经过上面的配置,我们的WebSocket在nginx上跑起来了。万分欢喜的我们,发现一分钟不发消息就自动掉线了。郁闷至极到头大。细心的同学通过上面的链接资料其实已经有说明:


By default, the connection will be closed if the proxied server does not transmit any data within 60 seconds. This timeout can be increased with the proxy_read_timeout directive. Alternatively, the proxied server can be configured to periodically send WebSocket ping frames to reset the timeout and check if the connection is still alive.


默认情况下,如果代理的服务器在60秒内没有传输任何数据,则连接将被关闭。可以使用proxy_read_timeout指令增加此超时 。或者,代理服务器可以配置为定期发送WebSocket ping帧以重置超时并检查连接是否仍然存在。


nginx给出了两种解决方案。第一种,修改proxy_read_timeout (默认60秒)。第二种,浏览器客户端定时发送心跳包(时间要短于proxy_read_timeout)。

我使用的是第二种方式。


第一种虽然简单粗暴,但是时间再长也是一个值,还是会有超时的可能。再者,谁能保证浏览器端不会new 很多个WebSocket出来捣蛋。


第二种方式,浏览器定时发送一条消息,内容和后台约定下。如发送“心跳”,然后后台接收消息是,判断如果是“心跳”则不做任何处理。


中文编码


在做“找工作”爬前程无忧的数据时,发现他们使用的GBK编码。而在.net core中默认不支持这种格式,导致取到的数据都是乱码。


我们需要nuget安装System.Text.Encoding.CodePages。然后在Startup.cs的Configure里面注册:


Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);//注册编码提供程序


使用:


var htmlBytes = await http.GetByteArrayAsync(url);

var htmlString = Encoding.GetEncoding("GBK").GetString(htmlBytes);


ASP.NET Core 端口分配


asp.net core 默认端口都是5000。那么我们运行第二个程序的时候就会提示5000端口被占用。这个时候,我们就需要为每个程序分配不同的端口了。

在根目录新建一个json文件hosting.json


{

  "server.urls": "http://*:5002"

}


在Program.cs文件修改


public static void Main(string[] args)

{

    var config = new ConfigurationBuilder()

          .SetBasePath(Directory.GetCurrentDirectory())

          .AddJsonFile("hosting.json", optional: true)

          .Build();


    var host = new WebHostBuilder()

        .UseKestrel()

        .UseConfiguration(config)

        .UseContentRoot(Directory.GetCurrentDirectory())

        .UseIISIntegration()

        .UseStartup<Startup>()

        .UseApplicationInsights()

        .Build();


    host.Run();

}


爬拉勾数据


在爬拉勾网的时候没有搞定,不知道是不是因为https的原因。


using (HttpClient http = new HttpClient())

{

    var url = "https://www.lagou.com/zhaopin/Java/?labelWords=label";

    var htmlString = await http.GetStringAsync(url);

}


在.net core中报错:An unhandled exception occurred while processing the request.


在.net 4.5 中抓到的数据是“页面加载中...”。和浏览器访问的结果不一样。

原因未知。如果有大佬解惑,感激不尽!

 

参考

http://www.runoob.com/html/html5-websocket.html

http://www.cnblogs.com/liguobao/p/6130121.html

http://www.cnblogs.com/linezero/p/5806814.html


演示

http://haojima.net

http://socket.haojima.net

http://job.haojima.net


源码

https://github.com/zhaopeiym/JobWanted

https://github.com/zhaopeiym/ChatRoom

https://github.com/zhaopeiym/BlogDemoCode


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

以上是关于k8s 实战篇 - mysql部署 - 1的主要内容,如果未能解决你的问题,请参考以下文章

云原生|K8s系列第3篇:实战Kubectl创建Deployment部署应用

Flink系列部署篇:Native k8s部署高可用Flink集群实战

云原生之kubernetes实战在k8s集群环境下部署Tomcat应用

云原生之kubernetes实战在k8s环境下部署Discuz论坛系统

高可用集群篇-- K8S部署微服务

K8s完整多节点部署(线网实战!含排错!)