• jvm attach实现一种简单更新rancher上测试环境代码的方法


    前言

    好久没写过博客了,今天突然发现之前的一篇博客能够给到一位老哥帮助,让我很高兴,遂起了继续写博客的想法,说不定自己这些不咋样的文章能够帮助到一些人,也顺便自己记录下学习内容,┑( ̄Д  ̄)┍

    背景

    公司项目中使用k8s集群作为测试、正式环境,在测试阶段,对于很小的改动都会导致我重新推送重启发布,该流程在会有2~3分钟,很是麻烦

    目的

    提升改bug的效率,减少重新推送重启发布的流程

    最终效果

    改动类方法的定义,即修改方法中的一些代码,添加类静态变量,可以本地直接运行main将改动提交到测试环境,rancher中,这样就不需要重新发布重启了
    但是不支持类属性、方法的改动,不能引用新的方法类等(这是我这边使用时会有这些问题)
    在这里插入图片描述

    其他工具

    阿里的jvm调试工具

    代码逻辑

    集成springboot的代码

    说明:代码流程如下
    其中的文件服务器该处用的是我服务器nginx配的,能用就行。该集成springboot的代码应该是能直接用的,毕竟现在这些代码是我目前再用的
    注意 /app.jar 是用来匹配到具体的jvm的,可以在rancher节点执行命令行,通过jps看
    注意 依赖jdk的tools,像我们默认没有加载tools所以在这里载入在这里插入图片描述注意 本地springboot可以载入项目的java环境中的tools

            
            
                com.sun
                tools
                1.8
                system
                ${java.home}/../lib/tools.jar
            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    @GetMapping("/roger/editClass")
        public void editClass(String startApplicationPackageNameAndClassName, String params) {
            JvmAgent.controllerAttach(startApplicationPackageNameAndClassName, params);
        }
    
        static class JvmAgent {
            public static void main(String[] args) throws IOException {
                long l = System.currentTimeMillis();
                Class[] classPath = {ResourceCenterServiceImpl.class};
    //        String s = localPerform(classPath, "http://localhost:8080/media/demo/roger/editClass", "cn.thecover.data.media.MediaApplication");
               //注意,/app.jar 是用来匹配到具体的jvm的,可以在rancher节点执行命令行,通过jps看
                String s = localPerform(classPath, "https://localhost:8080/demo/roger/editClass", "/app.jar", new HashMap(2){{
                    put("Authorization", "eyJhbGciOiJIUzUxMiIsInppcCI6IkRFRiJ9.eNoUjE0OwiAQhe8ya0kYqJT2Bu7dm4GOSWukBgYTY7y70937-d77wiYrzGBdxDB4bxakZAYXo5kSZuMDZhqZ7tZGOEHrSWGnam1NlewPLqZxfXM9QhKYMZwnj3EMVvm918zHv7bChYpcFrX1KTfpVLauedf99fNSDH9_AAAA__8.3aCVmcpfxc43VFuLcew2_5uyfb6Td5fcm_4g6qwpat8-RmgzhZjtJdz2m13FsFeJvHX5WYjvPMQ1r6ZvoLDJsQ");
                }});
                System.out.println(s);
                System.out.println("耗时:"+(System.currentTimeMillis()-l));
    
    
            }
            public static void controllerAttach(String startApplicationPackageNameAndClassName, String params){
                String jarPath = "http://139.9.87.17:30020/file/CommonJvmAgent.jar";
                try {
                    attach(startApplicationPackageNameAndClassName, jarPath, params);
                } catch (IOException | AttachNotSupportedException | AgentLoadException | AgentInitializationException e) {
                    e.printStackTrace();
                }
            }
            public static String localPerform(Class[] clas, String controllerPath, String startApplicationPackageNameAndClassName, Map header) throws IOException {
                List> params = new ArrayList<>();
                for (Class cla : clas) {
                    URL resource = cla.getResource("");
                    String absolutePath = resource.getFile() + cla.getSimpleName() + ".class";
                    absolutePath = absolutePath.substring(1);
                    Map a = new HashMap<>(2);
                    a.put("classUrl", upload(absolutePath));
                    a.put("packageNameAndClassName", cla.getName());
                    params.add(a);
                }
                return doGet(controllerPath + "?startApplicationPackageNameAndClassName=" + startApplicationPackageNameAndClassName + "¶ms=" + URLEncoder.encode(JSON.toJSONString(params), "UTF-8"), header);
            }
    
            private static String doGet(String urlPath, Map header){
                try{
                    // 统一资源
                    URL url = new URL(urlPath);
                    // 连接类的父类,抽象类
                    URLConnection urlConnection = url.openConnection();
                    // http的连接类
                    HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection;
                    //设置超时
                    httpUrlConnection.setConnectTimeout(1000 * 5);
                    if(header != null){
                        header.forEach((x, y)->{
                            httpUrlConnection.setRequestProperty(x, y);
                        });
                    }
                    //设置请求方式,默认是GET
                    httpUrlConnection.setRequestMethod("GET");
                    // 设置字符编码
                    httpUrlConnection.setRequestProperty("Charset", "UTF-8");
                    // 打开到此 URL引用的资源的通信链接(如果尚未建立这样的连接)。
                    httpUrlConnection.connect();
                    // 建立链接从请求中获取数据
                    URLConnection con = url.openConnection();
                    // 读取返回数据
                    StringBuffer strBuf = new StringBuffer();
                    InputStream inputStream = con.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                    String line = null;
                    while ((line = reader.readLine()) != null) {
                        strBuf.append(line).append("\n");
                    }
                    reader.close();
                    inputStream.close();
                    return strBuf.toString();
                }catch (Exception e){
                    e.printStackTrace();
                }
                return "error";
            }
    
    
    
            private static String attach(String startApplicationPackageNameAndClassName,
                                         String jarPath,
                                         String agentArgs) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
                List list = VirtualMachine.list();
                VirtualMachineDescriptor targetDescriptor = null;
                for (VirtualMachineDescriptor virtualMachineDescriptor : list) {
                    System.out.println(virtualMachineDescriptor.displayName());
                    if (virtualMachineDescriptor.displayName().equals(startApplicationPackageNameAndClassName)) {
                        targetDescriptor = virtualMachineDescriptor;
                        break;
                    }else if(virtualMachineDescriptor.displayName().startsWith(startApplicationPackageNameAndClassName)){
                        targetDescriptor = virtualMachineDescriptor;
                        break;
                    }
                }
                if (targetDescriptor == null) {
                    return "not find jvm, the name is " + startApplicationPackageNameAndClassName;
                }
                File file;
                if(jarPath.startsWith("http")){
                    file = new File(Objects.requireNonNull(download(jarPath, "JvmAgent.jar")));
                }else{
                    file = new File(jarPath);
                }
                VirtualMachine vm = VirtualMachine.attach(targetDescriptor.id());
                Objects.requireNonNull(vm).loadAgent(file.getAbsolutePath(), agentArgs);
                vm.detach();
                return "success";
            }
    
            private static String download(String urlPath, String outpath) {
                try {
                    File file = new File(outpath);
                    if(file.exists()){
                        return file.getAbsolutePath();
                    }
                    // 统一资源
                    URL url = new URL(urlPath);
                    // 连接类的父类,抽象类
                    URLConnection urlConnection = url.openConnection();
                    // http的连接类
                    HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection;
                    //设置超时
                    httpUrlConnection.setConnectTimeout(1000 * 5);
                    //设置请求方式,默认是GET
                    httpUrlConnection.setRequestMethod("GET");
                    // 设置字符编码
                    httpUrlConnection.setRequestProperty("Charset", "UTF-8");
                    // 打开到此 URL引用的资源的通信链接(如果尚未建立这样的连接)。
                    httpUrlConnection.connect();
                    // 建立链接从请求中获取数据
                    URLConnection con = url.openConnection();
                    FileOutputStream out = new FileOutputStream(file);
                    BufferedInputStream bin = new BufferedInputStream(con.getInputStream());
    
                    int size;
                    byte[] buf = new byte[2048];
                    while ((size = bin.read(buf)) != -1) {
                        out.write(buf, 0, size);
                    }
                    // 关闭资源
                    bin.close();
                    out.close();
    
                    return file.getAbsolutePath();
                }catch (Exception e){
                    e.printStackTrace();
                }
                return null;
            }
    
            private static String upload(String absolutePath) throws IOException {
                Map fileMap = new HashMap<>();
                fileMap.put("file", absolutePath);
                //调用上传文件的post请求
                String s = formUpload("http://139.9.87.17:30010/live2d/file/upload", null, fileMap);
                JSONObject jsonObject = JSONObject.parseObject(s);
                return jsonObject.getString("datas");
            }
            private static String formUpload(String serviceURL, Map textMap, Map fileMap) throws IOException {
                Map> tempMap = new HashMap<>();
                if (!Objects.isNull(fileMap) && !fileMap.isEmpty()) {
                    Iterator> iter = fileMap.entrySet().iterator();
                    while (iter.hasNext()) {
                        Map.Entry entry = iter.next();
                        String inputName = entry.getKey();
                        String inputValue = entry.getValue();
                        LinkedHashSet values = new LinkedHashSet<>();
                        values.add(inputValue);
                        tempMap.put(inputName, values);
                    }
                }
                return formUploadMulti(serviceURL, textMap, tempMap);
            }
    
            private static String formUploadMulti(String serviceURL, Map textMap, Map> fileMap) throws IOException {
                String res = "";
                HttpURLConnection conn = null;
                OutputStream out = null;
                BufferedReader reader = null;
                // boundary就是request头和上传文件内容的分隔符
                String BOUNDARY = "---------------------------" + System.currentTimeMillis();
                try {
                    URL url = new URL(serviceURL);
                    conn = (HttpURLConnection) url.openConnection();
                    conn.setConnectTimeout(5000);// 30秒连接
                    conn.setReadTimeout(5 * 60 * 1000);// 5分钟读数据
                    conn.setDoOutput(true);
                    conn.setDoInput(true);
                    conn.setUseCaches(false);
                    conn.setRequestMethod("POST");
                    conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
    
                    out = new DataOutputStream(conn.getOutputStream());
                    // text
                    if (!Objects.isNull(textMap) && !textMap.isEmpty()) {
                        StringBuffer strBuf = new StringBuffer();
                        Iterator> iter = textMap.entrySet().iterator();
                        while (iter.hasNext()) {
                            Map.Entry entry = iter.next();
                            String inputName = entry.getKey();
                            String inputValue = entry.getValue();
                            strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
                            strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n");
                            strBuf.append(inputValue);
                        }
                        out.write(strBuf.toString().getBytes());
                    }
    
                    // file
                    if (!Objects.isNull(fileMap) && !fileMap.isEmpty()) {
                        Iterator>> iter = fileMap.entrySet().iterator();
                        while (iter.hasNext()) {
                            Map.Entry> entry = iter.next();
                            String inputName = entry.getKey();
                            LinkedHashSet inputValue = entry.getValue();
                            for (String filePath : inputValue) {
                                File file = new File(filePath);
                                String filename = file.getName();
                                Path path = Paths.get(filePath);
                                String contentType = Files.probeContentType(path);
                                StringBuffer strBuf = new StringBuffer();
                                strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
                                strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename + "\"\r\n");
                                strBuf.append("Content-Type:" + contentType + "\r\n\r\n");
                                out.write(strBuf.toString().getBytes());
    
                                DataInputStream in = new DataInputStream(new FileInputStream(file));
                                int bytes = 0;
                                byte[] bufferOut = new byte[1024];
                                while ((bytes = in.read(bufferOut)) != -1) {
                                    out.write(bufferOut, 0, bytes);
                                }
                                in.close();
                            }
                        }
                    }
    
                    byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
                    out.write(endData);
                    out.flush();
    
                    // 读取返回数据
                    StringBuffer strBuf = new StringBuffer();
                    reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                    String line = null;
                    while ((line = reader.readLine()) != null) {
                        strBuf.append(line).append("\n");
                    }
                    res = strBuf.toString();
                    reader.close();
                    reader = null;
                } catch (IOException e) {
                    throw e;
                } finally {
                    if (!Objects.isNull(out)) {
                        out.close();
                        out = null;
                    }
                    if (!Objects.isNull(reader)) {
                        reader.close();
                        reader = null;
                    }
                    if (conn != null) {
                        conn.disconnect();
                        conn = null;
                    }
                }
                return res;
            }
    
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276

    java agent的代码(附加)

    在这里插入图片描述

    
    import jdk.nashorn.api.scripting.JSObject;
    
    import javax.script.Invocable;
    import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import javax.script.ScriptException;
    import java.lang.instrument.ClassDefinition;
    import java.lang.instrument.Instrumentation;
    import java.lang.instrument.UnmodifiableClassException;
    import java.util.HashMap;
    import java.util.Map;
    
    public class JpAgent {
        public static void premain(String args, Instrumentation inst) {
            System.out.println("premain");
        }
        public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
            ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("nashorn");
            System.out.println("输入传参:"+agentArgs);
            try {
                scriptEngine.eval("function getJson(){return " + agentArgs + "}");
                Invocable in = (Invocable) scriptEngine;
    
                JSObject getJson = (JSObject) in.invokeFunction("getJson");
                Map collect = new HashMap<>();
                for (Object value : getJson.values()) {
                    Map value1 = (Map) value;
                    String packageNameAndClassName = value1.get("packageNameAndClassName");
                    String classUrl = value1.get("classUrl");
                    if (packageNameAndClassName != null && classUrl != null) {
                        collect.put(packageNameAndClassName, classUrl);
                    }
                }
                System.out.println("参数解析:"+collect);
                inst.addTransformer(new JpClassFileTransformer(collect), true);
                for (Class allLoadedClass : inst.getAllLoadedClasses()) {
                    String s = collect.get(allLoadedClass.getName());
                    if(s  != null){
                        System.out.println("重新定义class:"+allLoadedClass.getName());
    //                    ClassDefinition classDefinition = new ClassDefinition(allLoadedClass, JpClassFileTransformer.readUrlFile(s));
    //                    inst.redefineClasses(classDefinition);
                        inst.retransformClasses(allLoadedClass);
                    }
                }
            } catch (ScriptException | NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("nashorn");
            String s = "[{\"classUrl\":\"http://139.9.87.17:30020/file/temp/4d36413c-3de4-4075-859b-fdeb9ebcf1b7.class\",\"packageNameAndClassName\":\"com.Dog\"}]";
            System.out.println(s);
            try {
                scriptEngine.eval("function getJson(){return " + s + "}");
                Invocable in = (Invocable) scriptEngine;
                JSObject getJson = (JSObject) in.invokeFunction("getJson");
                for (Object value : getJson.values()) {
                    Map value1 = (Map) value;
    
                    System.out.println(value1.get("classUrl"));
                }
            } catch (ScriptException | NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    import java.io.*;
    import java.lang.instrument.ClassFileTransformer;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.net.URLConnection;
    import java.security.ProtectionDomain;
    import java.util.Map;
    
    public class JpClassFileTransformer implements ClassFileTransformer {
        private Map classToUrl;
    
        public JpClassFileTransformer(Map classToUrl) {
            this.classToUrl = classToUrl;
        }
    
        @Override
        public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] classfileBuffer) {
    //        System.out.println("loader className: " + className);
            String s = classToUrl.get(className.replaceAll("/", "."));
            if (s != null) {
                if (s.startsWith("http")) {
                    return readUrlFile(s);
                } else {
                    return getBytesFromFile(s);
                }
            }
            return null;
        }
    
        public static byte[] getBytesFromFile(String fileName) {
            File file = new File(fileName);
            try (InputStream is = new FileInputStream(file)) {
                // precondition
    
                long length = file.length();
                byte[] bytes = new byte[(int) length];
    
                // Read in the bytes
                int offset = 0;
                int numRead = 0;
                while (offset < bytes.length
                        && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
                    offset += numRead;
                }
    
                if (offset < bytes.length) {
                    throw new IOException("Could not completely read file "
                            + file.getName());
                }
                is.close();
                return bytes;
            } catch (Exception e) {
                System.out.println("error occurs in _ClassTransformer!"
                        + e.getClass().getName());
                return null;
            }
        }
    
        public static byte[] readUrlFile(String strUrl) {
            try {
                // 统一资源
                URL url = new URL(strUrl);
                // 连接类的父类,抽象类
                URLConnection urlConnection = url.openConnection();
                // http的连接类
                HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
                //设置超时
                httpURLConnection.setConnectTimeout(1000 * 5);
                //设置请求方式,默认是GET
                httpURLConnection.setRequestMethod("POST");
                // 设置字符编码
                httpURLConnection.setRequestProperty("Charset", "UTF-8");
                // 打开到此 URL引用的资源的通信链接(如果尚未建立这样的连接)。
                httpURLConnection.connect();
                // 建立链接从请求中获取数据
                URLConnection con = url.openConnection();
                BufferedInputStream bin = new BufferedInputStream(con.getInputStream());
    
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                int size;
                byte[] buf = new byte[2048];
                while ((size = bin.read(buf)) != -1) {
                    out.write(buf, 0, size);
                }
                // 关闭资源
                bin.close();
                byte[] bytes = out.toByteArray();
                out.close();
                return bytes;
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return null;
        }
    
        public static void main(String[] args) {
            byte[] bytes = readUrlFile("http://139.9.87.17:30020/file/temp/4d36413c-3de4-4075-859b-fdeb9ebcf1b7.class");
            System.out.println(bytes.length);
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    运行效果

    本地:
    在这里插入图片描述
    rancher日志:
    在这里插入图片描述

    总结

    一句话就是,聊胜于无。但是对我而言却是是有一点点帮助,有时候代码有点小错误还是可以直接改的

  • 相关阅读:
    RAID磁盘阵列简单介绍
    同态加密开源框架整理
    金色传说:SAP-QM-周期性检验:MSC1N/MSC2N/MSC3N下一次检验日期逻辑问题
    基于C++的社交应用的数据存储与实现
    无swing,高级javaSE毕业之贪吃蛇游戏(含模块构建,多线程监听服务),已录制视频
    Vue3 - reactive 复杂类型(通俗易懂,详细教程)
    MutationObserver接口(一) 基本用法
    数据库系统的三级模式和二级映射
    初识React -- 一篇文章让你会用react写东西
    22.11.17打卡 mysql学习笔记
  • 原文地址:https://blog.csdn.net/Qinejie1314520/article/details/126721310