標籤: iphone手機收購

  • Gogoro 雲嘉地區電池交換站正式啟用,暢騎基隆到屏東

    Gogoro 雲嘉地區電池交換站正式啟用,暢騎基隆到屏東

    Gogoro 車主們期待已久的雲嘉地區電池交換站21 日正式開通提供服務。未來,Gogoro 的車主將可以騎乘Smartscooter 智慧雙輪縱貫台灣西半部,一路從基隆暢騎到屏東。

    Gogoro 於21 日宣布,雲林嘉義地區首批5 座GoStation 電池交換站正式上線,其中兩站位於雲林斗六、虎尾;另三站則分布於嘉義市區。自從Gogoro 於2015 年在台北市設立首座電池交換站以來,短短兩年的時間,已經建置將近400 座電池交換站,廣布於基隆到屏東的各個縣市,在六都甚至不到一公里就設有一站,累積提供將近500 萬次的電池交換服務,是全世界最穩定的電池交換能源網路系統。

    Gogoro 台灣區行銷總監陳彥揚表示: 「雲嘉地區GoStation 電池交換站的開通有3 個劃時代的意義。首先,我們顛覆消費者對於電動機車續航力不足的刻板印象;兩年前,沒有人會相信,我們可以騎乘電動機車橫跨北高。其次,對Gogoro 車主而言,Gogoro 將不再僅是都會的通勤工具,而是更進一步深入他們的生活,成為跨縣市旅遊的另一種交通選擇;最後,我們則提供雲嘉地區民眾一個能夠改善當地空污,讓生活環境更環保、更健康的解決方案。」

    日前宣布Gogoro 2 系列預購超過13,000 台,震撼市場的Gogoro 在雲林嘉義地區提供電池交換站的服務後,搭配雲嘉地區相對較高的電動機車補助,將加速當地居民的購車意願。目前,雲林縣汰換二行程機車購買電動機車的總補助金額達34,000 元,嘉義市為31,000 元,嘉義縣則為21,000 元。Gogoro 也正規劃在不久的將來於雲嘉地區增設銷售及維修據點,希望很快可以讓雲嘉的居民享有Gogoro 完整的銷售、維修、電池交換等全方位服務。

      (合作媒體:。圖片出處:科技新報)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • MachO文件詳解–逆向開發

    MachO文件詳解–逆向開發

    今天是逆向開發的第5天內容–MachO文件(Mac 和 iOS 平台可執行的文件),在逆向開發中是比較重要的,下面我們着重講解一下MachO文件的基本內容和使用。

    一、MachO概述

    1. 概述

    Mach-O是Mach Object文件格式的縮寫,iOS以及Mac上可執行的文件格式,類似Window的exe格式,Linux上的elf格式。Mach-O是一個可執行文件、動態庫以及目標代碼的文件格式,是a.out格式的替代,提供了更高更強的擴展性。

    2.常見格式

    Mach-O常見格式如下:

    • 目標文件 .o
    • 庫文件
    1. .a
    2. .dylib
    3. .framework
    • 可執行文件
    • dyld
    • .dsym

      通過file文件路徑查看文件類型

    我們通過部分實例代碼來簡單研究一下。

    2.1目標文件.o

    通過test.c 文件,可以使用clang命令將其編譯成目標文件.o

    我們再通過file命令(如下)查看文件類型

    是個Mach-O文件。

    2.2 dylib

    通過cd /usr/lib命令查看dylib

    通過file命令查看文件類型

     

     2.3 .dsym

    下面是一個截圖來說明.dsym是也是Mach-O文件格式

     

    以上只是Mach-O常見格式的某一種,大家可以通過命令來嘗試。

    3. 通用二進制文件

    希望大家在了解App二進制架構的時候,可以先讀一下本人的另一篇博客關於armv7,armv7s以及arm64等的介紹。

    通用二進制文件是蘋果自身發明的,基本內容如下

    下面通過指令查看Macho文件來看下通用二進制文件

     

    然後通過file指令查看文件類型

     

    上面該MachO文件包含了3個架構分別是arm v7,arm v7s 以及arm 64 。

    針對該MachO文件我們做幾個操作,利用lipo命令拆分合併架構

    3.1 利用lipo-info查看MachO文件架構

    3.2 瘦身MachO文件,拆分

    利用lipo-thin瘦身架構

     

     查看一下結果如下,多出來一個新建的MachO_armv7

     

    3.3 增加架構,合併

    利用lipo -create 合併多種架構

    發現多出一種框架,合併成功多出Demo可執行文件。結果如下:

     

    整理出lipo命令如下:

     

    二、MachO文件

    2.1 文件結構

    下面是蘋果官方圖解釋MachO文件結構圖

    MachO文件的組成結構如上,看包括了三個部分

    • Header包含了該二進制文件的一般信息,信息如下:
    1. 字節順序、加載指令的數量以及架構類型
    2. 快速的確定一些信息,比如當前文件是32位或者64位,對應的文件類型和處理器是什麼
    • Load commands 包含很多內容的表
    1. 包括區域的位置、動態符號表以及符號表等
    • Data一般是對象文件的最大部分
    1. 一般包含Segement具體數據

    2.2 Header的數據結構

    在項目代碼中,按下Command+ 空格,然後輸入loader.h  

    然後查看loader.h文件,找到mach_header

    上面是mach_header,對應結構體的意義如下:

    通過MachOView查看Mach64 Header頭部信息

    2.3 LoadCommands

    LoadCommand包含了很多內容的表,通過MachOView查看LoadCommand的信息,圖如下:

     

    但是大家看的可能並不了解內容,下面有圖進行註解,可以看下主要的意思

    2.4 Data

    Data包含Segement,存儲具體數據,通過MachOView查看,地址映射內容

     

    三、DYLD

    3.1 dyld概述

    dyld(the dynamic link editor)是蘋果動態鏈接器,是蘋果系統一個重要的組成部分,系統內核做好準備工作之後,剩下的就會交給了dyld。

    3.2 dyld加載過程

    程序的入口一般都是在main函數中,但是比較少的人關心main()函數之前發生了什麼?這次我們先探索dyld的加載過程。(但是比在main函數之前,load方法就在main函數之前)

    3.2.1 新建項目,在main函數下斷

     

    main()之前有個libdyld.dylib start入口,但是不是我們想要的,根據dyld源碼找到__dyld_start函數

    3.2.2 dyld main()函數

    dyld main()函數是關鍵函數,下面是函數實現內容。(此時的main實現函數和程序App的main 函數是不一樣的,因為dyld也是一個可執行文件,也是具有main函數的

    //
    // Entry point for dyld.  The kernel loads dyld and jumps to __dyld_start which
    // sets up some registers and call this function.
    //
    // Returns address of main() in target program which __dyld_start jumps to
    //
    uintptr_t
    _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
            int argc, const char* argv[], const char* envp[], const char* apple[], 
            uintptr_t* startGlue)
    {
        // Grab the cdHash of the main executable from the environment
        // 第一步,設置運行環境
        uint8_t mainExecutableCDHashBuffer[20];
        const uint8_t* mainExecutableCDHash = nullptr;
        if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
            // 獲取主程序的hash
            mainExecutableCDHash = mainExecutableCDHashBuffer;
    
        // Trace dyld's load
        notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));
    #if !TARGET_IPHONE_SIMULATOR
        // Trace the main executable's load
        notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
    #endif
    
        uintptr_t result = 0;
        // 獲取主程序的macho_header結構
        sMainExecutableMachHeader = mainExecutableMH;
        // 獲取主程序的slide值
        sMainExecutableSlide = mainExecutableSlide;
    
        CRSetCrashLogMessage("dyld: launch started");
        // 設置上下文信息
        setContext(mainExecutableMH, argc, argv, envp, apple);
    
        // Pickup the pointer to the exec path.
        // 獲取主程序路徑
        sExecPath = _simple_getenv(apple, "executable_path");
    
        // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
        if (!sExecPath) sExecPath = apple[0];
    
        if ( sExecPath[0] != '/' ) {
            // have relative path, use cwd to make absolute
            char cwdbuff[MAXPATHLEN];
            if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
                // maybe use static buffer to avoid calling malloc so early...
                char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
                strcpy(s, cwdbuff);
                strcat(s, "/");
                strcat(s, sExecPath);
                sExecPath = s;
            }
        }
    
        // Remember short name of process for later logging
        // 獲取進程名稱
        sExecShortName = ::strrchr(sExecPath, '/');
        if ( sExecShortName != NULL )
            ++sExecShortName;
        else
            sExecShortName = sExecPath;
        
        // 配置進程受限模式
        configureProcessRestrictions(mainExecutableMH);
    
    
        // 檢測環境變量
        checkEnvironmentVariables(envp);
        defaultUninitializedFallbackPaths(envp);
    
        // 如果設置了DYLD_PRINT_OPTS則調用printOptions()打印參數
        if ( sEnv.DYLD_PRINT_OPTS )
            printOptions(argv);
        // 如果設置了DYLD_PRINT_ENV則調用printEnvironmentVariables()打印環境變量
        if ( sEnv.DYLD_PRINT_ENV ) 
            printEnvironmentVariables(envp);
        // 獲取當前程序架構
        getHostInfo(mainExecutableMH, mainExecutableSlide);
        //-------------第一步結束-------------
        
        // load shared cache
        // 第二步,加載共享緩存
        // 檢查共享緩存是否開啟,iOS必須開啟
        checkSharedRegionDisable((mach_header*)mainExecutableMH);
        if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
            mapSharedCache();
        }
        ...
    
        try {
            // add dyld itself to UUID list
            addDyldImageToUUIDList();
    
            // instantiate ImageLoader for main executable
            // 第三步 實例化主程序
            sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
            gLinkContext.mainExecutable = sMainExecutable;
            gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
    
            // Now that shared cache is loaded, setup an versioned dylib overrides
        #if SUPPORT_VERSIONED_PATHS
            checkVersionedPaths();
        #endif
    
    
            // dyld_all_image_infos image list does not contain dyld
            // add it as dyldPath field in dyld_all_image_infos
            // for simulator, dyld_sim is in image list, need host dyld added
    #if TARGET_IPHONE_SIMULATOR
            // get path of host dyld from table of syscall vectors in host dyld
            void* addressInDyld = gSyscallHelpers;
    #else
            // get path of dyld itself
            void*  addressInDyld = (void*)&__dso_handle;
    #endif
            char dyldPathBuffer[MAXPATHLEN+1];
            int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN);
            if ( len > 0 ) {
                dyldPathBuffer[len] = '\0'; // proc_regionfilename() does not zero terminate returned string
                if ( strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0 )
                    gProcessInfo->dyldPath = strdup(dyldPathBuffer);
            }
    
            // load any inserted libraries
            // 第四步 加載插入的動態庫
            if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
                for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
                    loadInsertedDylib(*lib);
            }
            // record count of inserted libraries so that a flat search will look at 
            // inserted libraries, then main, then others.
            // 記錄插入的動態庫數量
            sInsertedDylibCount = sAllImages.size()-1;
    
            // link main executable
            // 第五步 鏈接主程序
            gLinkContext.linkingMainExecutable = true;
    #if SUPPORT_ACCELERATE_TABLES
            if ( mainExcutableAlreadyRebased ) {
                // previous link() on main executable has already adjusted its internal pointers for ASLR
                // work around that by rebasing by inverse amount
                sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
            }
    #endif
            link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
            sMainExecutable->setNeverUnloadRecursive();
            if ( sMainExecutable->forceFlat() ) {
                gLinkContext.bindFlat = true;
                gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
            }
    
            // link any inserted libraries
            // do this after linking main executable so that any dylibs pulled in by inserted 
            // dylibs (e.g. libSystem) will not be in front of dylibs the program uses
            // 第六步 鏈接插入的動態庫
            if ( sInsertedDylibCount > 0 ) {
                for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                    ImageLoader* image = sAllImages[i+1];
                    link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
                    image->setNeverUnloadRecursive();
                }
                // only INSERTED libraries can interpose
                // register interposing info after all inserted libraries are bound so chaining works
                for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                    ImageLoader* image = sAllImages[i+1];
                    image->registerInterposing();
                }
            }
    
            // <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
            for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
                ImageLoader* image = sAllImages[i];
                if ( image->inSharedCache() )
                    continue;
                image->registerInterposing();
            }
            ...
    
            // apply interposing to initial set of images
            for(int i=0; i < sImageRoots.size(); ++i) {
                sImageRoots[i]->applyInterposing(gLinkContext);
            }
            gLinkContext.linkingMainExecutable = false;
            
            // <rdar://problem/12186933> do weak binding only after all inserted images linked
            // 第七步 執行弱符號綁定
            sMainExecutable->weakBind(gLinkContext);
    
            // If cache has branch island dylibs, tell debugger about them
            if ( (sSharedCacheLoadInfo.loadAddress != NULL) && (sSharedCacheLoadInfo.loadAddress->header.mappingOffset >= 0x78) && (sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset != 0) ) {
                uint32_t count = sSharedCacheLoadInfo.loadAddress->header.branchPoolsCount;
                dyld_image_info info[count];
                const uint64_t* poolAddress = (uint64_t*)((char*)sSharedCacheLoadInfo.loadAddress + sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset);
                // <rdar://problem/20799203> empty branch pools can be in development cache
                if ( ((mach_header*)poolAddress)->magic == sMainExecutableMachHeader->magic ) {
                    for (int poolIndex=0; poolIndex < count; ++poolIndex) {
                        uint64_t poolAddr = poolAddress[poolIndex] + sSharedCacheLoadInfo.slide;
                        info[poolIndex].imageLoadAddress = (mach_header*)(long)poolAddr;
                        info[poolIndex].imageFilePath = "dyld_shared_cache_branch_islands";
                        info[poolIndex].imageFileModDate = 0;
                    }
                    // add to all_images list
                    addImagesToAllImages(count, info);
                    // tell gdb about new branch island images
                    gProcessInfo->notification(dyld_image_adding, count, info);
                }
            }
    
            CRSetCrashLogMessage("dyld: launch, running initializers");
            ...
            // run all initializers
            // 第八步 執行初始化方法
            initializeMainExecutable(); 
    
            // notify any montoring proccesses that this process is about to enter main()
            dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_MAIN_DYLD2, 0, 0);
            notifyMonitoringDyldMain();
    
            // find entry point for main executable
            // 第九步 查找入口點並返回
            result = (uintptr_t)sMainExecutable->getThreadPC();
            if ( result != 0 ) {
                // main executable uses LC_MAIN, needs to return to glue in libdyld.dylib
                if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
                    *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
                else
                    halt("libdyld.dylib support not present for LC_MAIN");
            }
            else {
                // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
                result = (uintptr_t)sMainExecutable->getMain();
                *startGlue = 0;
            }
        }
        catch(const char* message) {
            syncAllImages();
            halt(message);
        }
        catch(...) {
            dyld::log("dyld: launch failed\n");
        }
        ...
        
        return result;
    }

    View Code

    摺疊開dyld main函數,步驟總結如下

    對待dyld的講述,是非常不易的,因為本身過程是比較複雜的,上面僅僅是自身的抽出來的。下面再畫一張流程圖,幫助大家理解。

     

    四、總結

    MachO文件對於逆向開發是非常重要的,通過本次講解,希望對大家理解逆向開發有所幫助,也希望大家真正可以提高技術,應對iOS市場的大環境,下一篇我們將講述Hook原理–逆向開發。謝謝!!!

     

     

     

     

     

     

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • Docker基礎與實戰,看這一篇就夠了

    Docker基礎與實戰,看這一篇就夠了

    docker 基礎

    什麼是Docker

    Docker 使用 Google 公司推出的 Go 語言 進行開發實現,基於 Linux 內核的 cgroupnamespace,以及 AUFS 類的 Union FS 等技術,對進程進行封裝隔離,屬於 操作系統層面的虛擬化技術。由於隔離的進程獨立於宿主和其它的隔離的進程,因此也稱其為容器。

    Docker 在容器的基礎上,進行了進一步的封裝,從文件系統、網絡互聯到進程隔離等等,極大的簡化了容器的創建和維護。使得 Docker 技術比虛擬機技術更為輕便、快捷。

    記住最重要的一點,Dokcer實際是宿主機的一個普通的進程,這也是Dokcer與傳統虛擬化技術的最大不同。

    為什麼要使用Docker

    使用Docker最重要的一點就是Docker能保證運行環境的一致性,不會出現開發、測試、生產由於環境配置不一致導致的各種問題,一次配置多次運行。使用Docker,可更快地打包、測試以及部署應用程序,並可減少從編寫到部署運行代碼的周期。

    docker 安裝

    • Docker 要求 CentOS 系統的內核版本高於 3.10 ,查看本頁面的前提條件來驗證你的CentOS 版本是否支持 Docker 。
      uname -r

    • 更新yum,升級到最新版本
      yum update

    • 卸載老版本的docker(若有)
      yum remove docker docker-common docker-selinux docker-engine
      執行該命令只會卸載Docker本身,而不會刪除Docker存儲的文件,例如鏡像、容器、卷以及網絡文件等。這些文件保存在/var/lib/docker 目錄中,需要手動刪除。

    • 查看yum倉庫,查看是否有docker
      ll /etc/yum.repos.d/

      如果用的廠商的服務器(阿里雲、騰訊雲)一般都會有docker倉庫,如果用的是虛擬機或者公司的服務器基本會沒有。

    • 安裝軟件包, yum-util 提供yum-config-manager功能,另外兩個是devicemapper驅動依賴的
      yum install -y yum-utils device-mapper-persistent-data lvm2

    • 安裝倉庫
      yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

    • 查看docker版本
      yum list docker-ce --showduplicates | sort -r

    • 安裝docker
      yum install docker-ce
      以上語句是是安裝最新版本的Docker,你也可以通過yum install docker-ce-<VERSION> 安裝指定版本

    • 啟動docker
      systemctl start docker

    • 驗證安裝是否正確
      dokcer run hello-world

    docker 重要命令

    鏡像相關

    • 搜索鏡像docker search
      docker search nginx Docker就會在Docker Hub中搜索含有“nginx”這個關鍵詞的鏡像倉庫

    • 下載鏡像docker pull
      docker pull nginx Docker就會在Docker Hub中下載含有“nginx”最新版本的鏡像
      當然也可以使用docker pull reg.jianzh5.com/nginx:1.7.9 下載指定倉庫地址標籤的nginx鏡像

    • 列出鏡像docker images

    • 刪除鏡像docker rmi
      docker rmi hello-world刪除我們剛剛下載的hello-world鏡像

    • 構建鏡像docker build
      通過Dockerfile構建鏡像,這個我們等下再拿出來詳細說明。

    容器相關

    • 新建啟動鏡像docker run
      這個命令是我們最常用的命令,主要使用以下幾個選項
      ① -d選項:表示後台運行
      ② -P選項(大寫):隨機端口映射
      ③ -p選項(小寫):指定端口映射,前面是宿主機端口後面是容器端口,如docker run nginx -p 8080:80,將容器的80端口映射到宿主機的8080端口,然後使用localhost:8080就可以查看容器中nginx的歡迎頁了
      ④ -v選項:掛載宿主機目錄,前面是宿主機目錄,後面是容器目錄,如docker run -d -p 80:80 -v /dockerData/nginx/conf/nginx.conf:/etc/nginx/nginx.conf nginx 掛載宿主機的/dockerData/nginx/conf/nginx.conf的文件,這樣就可以在宿主機對nginx進行參數配置了,注意目錄需要用絕對路徑,不要使用相對路徑,如果宿主機目錄不存在則會自動創建。
      ⑤–rm : 停止容器後會直接刪除容器,這個參數在測試是很有用,如docker run -d -p 80:80 --rm nginx
      ⑥–name : 給容器起個名字,否則會出現一長串的自定義名稱如 docker run -name niginx -d -p 80:80 - nginx

    • 列出容器 docker ps
      這個命令可以列出當前運行的容器,使用-a參數后列出所有的容器(包括已停止的)

    • 停止容器docker stop
      docker stop 5d034c6ea010 後面跟的是容器ID,也可以使用容器名稱

    • 啟動停止的容器docker start
      docker run是新建容器並啟動,docker start 是啟動停止的容器,如docker start 5d034c6ea010

    • 重啟容器docker restart
      此命令執行的過程實際是先執行docker stop,然後再執行docker start,如docker restart 5d034c6ea010

    • 進入容器docker exec -it 容器id /bin/bash
      docker exec -it 5d034c6ea010 /bin/bash,就相當於進入了容器本身的操作系統

    • 刪除容器 docker rm
      docker rm 5d034c6ea010 後面跟的是容器ID,刪除容器之前需要先停止容器運行

    • 數據拷貝docker cp
      此命令用於容器與宿主機之間進行數據拷貝,如 docker cp 5d034c6ea010: /etc/nginx/nginx.conf /dockerData/nginx/conf/nginx.conf 將容器的目錄文件拷貝到宿主機指定位置,容器ID可以替換成容器名。

    命令實戰

    如果我們需要一個nginx容器,並且需要在宿主機上直接修改nginx的配置文件、默認主頁,在宿主機可以實時看到容器nginx的日誌。我們可以按照如下的方式一步一步完成。

    • 使用–rm參數啟動容器,方便刪除
      docker run -d -p 8081:80 --name nginx --rm nginx

    • 進入容器,查看容器中配置文件、項目文件、日誌文件的目錄地址
      docker exec -it 9123b67e428e /bin/bash

    • 導出容器的配置文件
      docker cp nginx:/etc/nginx/nginx.conf /dockerData/nginx/conf/nginx.conf導出配置文件 nginx.conf
      docker cp nginx:/etc/nginx/conf.d /dockerData/nginx/conf/conf.d導出配置目錄 conf.d

    • 停止容器docker stop 9123b67e428e,由於加了–rm參數,容器會自動刪除

    • 再以如下命令啟動容器,完成目錄掛載
      shell docker run -d -p 8081:80 --name nginx \ -v /dockerData/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \ -v /dockerData/nginx/conf/conf.d:/etc/nginx/conf.d \ -v /dockerData/nginx/www:/usr/share/nginx/html \ -v /dockerData/nginx/logs:/var/log/nginx nginx
    • 訪問服務器地址http://192.168.136.129:8081/

      訪問報錯,這時候就進入宿主機的日誌目錄/dockerData/nginx/logs查看日誌
      2019/11/23 10:08:11 [error] 6#6: *1 directory index of “/usr/share/nginx/html/” is forbidden, client: 192.168.136.1, server: localhost, request: “GET / HTTP/1.1”, host: “192.168.136.129:8081”
      因為/usr/share/nginx/html/被掛載到了服務器上面的/dockerData/nginx/www目錄下,原來的歡迎頁面在dockerData/nginx/www是沒有的,所有就報錯了,這裏我們隨便建一個。

    • 建立默認主頁
      shell #打開項目文件 cd /dockerData/nginx/www #使用vim 創建並編輯文件 vi index.html #此時我們會進入vim界面,按 i 插入,然後輸入 <h1 align="center">Hello,Welcome to Docker World</h1> #輸入完后,按 esc,然後輸入 :wq
    • 再次訪問瀏覽器地址

    Dockerfile

    我們可以使用Dockfile構建一個鏡像,然後直接在docker中運行。Dockerfile文件為一個文本文件,裡面包含構建鏡像所需的所有的命令,首先我們來認識一下Dockerfile文件中幾個重要的指令。

    指令詳解

    • FROM
      選擇一個基礎鏡像,然後在基礎鏡像上進行修改,比如構建一個SpringBoot項目的鏡像,就需要選擇java這個基礎鏡像,FROM需要作為Dockerfile中的第一條指令
      如:FROM openjdk:8-jdk-alpine 基礎鏡像如果可以的話最好使用alpine版本的,採用alpline版本的基礎鏡像構建出來的鏡像會小很多。

    • RUN
      RUN指令用來執行命令行命令的。它有一下兩種格式:

      • shell 格式:RUN ,就像直接在命令行中輸入的命令一樣。 RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
      • exec 格式:RUN [“可執行文件”, “參數1”, “參數2”],這更像是函數調用中的格式。
    • CMD
      此指令就是用於指定默認的容器主進程的啟動命令的。
      CMD指令格式和RUN相似,也是兩種格式
      • shell 格式:CMD
      • exec 格式:CMD [“可執行文件”, “參數1”, “參數2″…]
      • 參數列表格式:CMD [“參數1”, “參數2″…]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具體的參數。
    • ENTRYPOINT
      ENTRYPOINT 的格式和 RUN 指令格式一樣,分為 exec 格式和 shell 格式。 ENTRYPOINT 的目的和 CMD 一樣,都是在指定容器啟動程序及參數。ENTRYPOINT 在運行時也可以替代,不過比 CMD 要略顯繁瑣,需要通過 docker run 的參數 --entrypoint 來指定。
      當指定了 ENTRYPOINT 后,CMD 的含義就發生了改變,不再是直接的運行其命令,而是將 CMD 的內容作為參數傳給 ENTRYPOINT 指令,換句話說實際執行時,將變為:
      <ENTRYPOINT> "<CMD>"

    • COPY & ADD
      這2個指令都是複製文件,它將從構建上下文目錄中   的文件/目錄 複製到新的一層的鏡像內的   位置。比如: COPY demo-test.jar app.jarADD demo-test.jar app.jar
      ADD指令比 COPY高級點,可以指定一個URL地址,這樣Docker引擎會去下載這個URL的文件,如果 ADD後面是一個 tar文件的話,Dokcer引擎還會去解壓縮。
      我們在構建鏡像時盡可能使用 COPY,因為 COPY 的語義很明確,就是複製文件而已,而 ADD 則包含了更複雜的功能,其行為也不一定很清晰。

    • EXPOSE
      聲明容器運行時的端口,這隻是一個聲明,在運行時並不會因為這個聲明應用就會開啟這個端口的服務。在 Dockerfile 中寫入這樣的聲明有兩個好處,一個是幫助鏡像使用者理解這個鏡像服務的守護端口,以方便配置映射;另一個用處則是在運行時使用隨機端口映射時,也就是 docker run -P 時,會自動隨機映射 EXPOSE 的端口。
      要將 EXPOSE 和在運行時使用 -p <宿主端口>:<容器端口> 區分開來。-p,是映射宿主端口和容器端口,換句話說,就是將容器的對應端口服務公開給外界訪問,而 EXPOSE 僅僅是聲明容器打算使用什麼端口而已,並不會自動在宿主進行端口映射。

    • ENV
      這個指令很簡單,就是設置環境變量,無論是後面的其它指令,如 RUN,還是運行時的應用,都可以直接使用這裏定義的環境變量。它有如下兩種格式:
      • ENV <key> <value>
      • ENV <key1>=<value1> <key2>=<value2>...
    • VOLUME
      該指令使容器中的一個目錄具有持久化存儲的功能,該目錄可被容器本身使用,也可共享給其他容器。當容器中的應用有持久化數據的需求時可以在Dockerfile中使用該指令。如VOLUME /tmp
      這裏的 /tmp 目錄就會在運行時自動掛載為匿名卷,任何向 /tmp 中寫入的信息都不會記錄進容器存儲層,從而保證了容器存儲層的無狀態化。當然,運行時可以覆蓋這個掛載設置。比如:
      docker run -d -v mydata:/tmp xxxx

    • LABEL
      你可以為你的鏡像添加labels,用來組織鏡像,記錄版本描述,或者其他原因,對應每個label,增加以LABEL開頭的行,和一個或者多個鍵值對。如下所示:
      LABEL version="1.0" LABEL description="test"

    Dockerfile實戰

    我們以一個簡單的SpringBoot項目為例構建基於SpringBoot應用的鏡像。
    功能很簡單,只是對外提供了一個say接口,在進入這個方法的時候打印出一行日誌,並將日誌寫入日誌文件。

    @SpringBootApplication
    @RestController
    @Log4j2
    public class DockerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DockerApplication.class, args);
        }
    
        @GetMapping("/say")
        public String say(){
            log.info("get say request...");
            return "Hello,Java日知錄";
        }
        
    }

    我們使用maven將其打包成jar文件,放入一個單獨的文件夾,然後按照下面步驟一步步構建鏡像並執行

    • 在當前文件夾建立Dockerfile文件,文件內容如下:
      properties FROM openjdk:8-jdk-alpine #將容器中的/tmp目錄作為持久化目錄 VOLUME /tmp #暴露端口 EXPOSE 8080 #複製文件 COPY docker-demo.jar app.jar #配置容器啟動后執行的命令 ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
    • 使用如下命令構建鏡像
      docker built -t springboot:v1.0 .

      -t 指定鏡像的名稱及版本號,注意後面需要以 . 結尾。

    • 查看鏡像文件

    • 運行構建的鏡像
      docker run -v /app/docker/logs:/logs -p 8080:8080 --rm --name springboot springboot:v1.0

    • 瀏覽器訪問http://192.168.136.129:8080/say

    • 在宿主機上實時查看日誌
      tail -100f /app/docker/logs/docker-demo-info.log

      請關注個人公眾號:JAVA日知錄

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • this綁定方式總結

    最近在回顧js的一些基礎知識,把《你不知道的js》系列又看了一遍,this始終是重中之重,還是決定把this相關知識做一個系統的總結,也方便自己日後回顧。

    this的四條綁定規則

    1.默認綁定

    這是最常用的函數調用類型:獨立函數調用(即函數是直接使用不帶任何修飾的函數引用進行調用的)。可以把這條規則看作是無法應用其他規則時的默認規則。
    默認綁定的this在非嚴格模式下指向window,嚴格模式下指向undefined,比如下面的函數foo在非嚴格模式下:

    var a = 2;
    function foo(){
        var a = 3;
        console.log(this.a);
    }
    foo(); //2

    這裏的foo()方法內的this指向了window,因此window.a = 2;

    嚴格模式下,this.指向undefined,因此訪問this.a會報錯:

    var a = 2;
    function foo(){
        "use strict";
        var a = 3;
        console.log(this.a);
    }
    foo(); //Uncaught TypeError: Cannot read property 'a' of undefined

    2.隱式綁定

    如果調用位置上有上下文對象,或者說被某個對象“擁有”或者“包
    含”,則使用隱式綁定。

    function foo() {
        console.log( this.a );
    }
    var obj = {
        a: 2,
        foo: foo
    };
    obj.foo(); // 2

    上例中的foo是通過obj.foo()的方式調用的,調用位置會使用obj上下文來引用函數,因此foo中的this指向了obj。
    另外foo是當做引用被加入到obj中的,但是無論是直接在obj 中定義還是先定義再添加為引用屬性,foo嚴格上來說都不屬於obj,因此上述定義裏面的“擁有”與“包含”加上了引號,這樣說是為了方便理解。
    常見的隱式調用場景:
    obj.fn();
    arguments[i]();//其實就是將點的調用方式變為了[]調用
    el.onClick(function(){console.log(this);//this指向el})

    隱式丟失

    先來看一段代碼:

    function foo() {
        console.log( this.a );
    }
    var obj = {
        a: 2,
        foo: foo
    };
    var bar = obj.foo; // 函數別名!
    var a = "global"; // a 是全局對象的屬性
    bar(); // "global"

    上述代碼其實只用看調用的方式:bar(),這其實是一個不帶任何修飾的函數調用,因此應用了默認綁定。
    還有一種參數傳遞的方式也會發生隱式丟失,原理其實跟上述例子一樣:

    function foo() {
        console.log( this.a );
    }
    function doFoo(fn) {
        // fn 其實引用的是foo
        fn(); // <-- 調用位置!
    }
    var obj = {
        a: 2,
        foo: foo
    };
    var a = "global"; // a 是全局對象的屬性
    doFoo( obj.foo ); // "global"

    显示綁定

    使用call,apply和bind方法可以指定綁定函數的this的值,這種綁定方法叫显示綁定。

    function foo() {
        console.log( this.a );
    }
    var obj = {
        a:2
    };
    foo.call( obj ); // 2

    通過foo.call(obj),我們可以在調用foo 時強制把它的this 綁定到obj 上

    new綁定

    new操作符可以基於一個“構造函數”新創建一個對象實例,new的實例化過程如下:

    1. 創建(或者說構造)一個全新的對象。
    2. 這個新對象會被執行[[ 原型]] 連接。
    3. 這個新對象會綁定到函數調用的this。
    4. 如果函數沒有返回其他對象,那麼new 表達式中的函數調用會自動返回這個新對象。
      明確了new的實例化過程后,思考如下代碼:
    function foo(a) {
        this.a = a;
    }
    var bar = new foo(2);
    console.log( bar.a ); // 2

    new foo(2)后新創建了個實例對象bar,然後把這個新對象bar綁定到了foo函數中的this,因此執行this.a = a后其實是把a賦給了bar.a

    優先級

    一般情況下this的綁定會根據上述四條綁定規則來,那麼他們同時出現時,該以怎樣的順序來判斷this的指向?下面是具體的規則:

    1. 函數是否在new 中調用(new 綁定)?如果是的話this 綁定的是新創建的對象( var bar = new foo() )。
    2. 函數是否通過call、apply(顯式綁定)或者硬綁定調用?如果是的話,this 綁定的是指定的對象( var bar = foo.call(obj2) )。
    3. 函數是否在某個上下文對象中調用(隱式綁定)?如果是的話,this 綁定的是那個上下文對象。( var bar = obj1.foo() )
    4. 如果都不是的話,使用默認綁定。如果在嚴格模式下,就綁定到undefined,否則綁定到全局對象。( var bar = foo() )

    綁定例外

    1.使用call,appy,bind這種顯式綁定的方法,參數傳入null或者undefined作為上下文時,函數調用還是會使用默認綁定

    function foo() {
        console.log( this.a );
    }
    var a = 2;
    foo.call( null ); // 2

    什麼情況下需要將上下文傳為null呢?
    1.使用bind函數來實現柯里化

    function foo(a,b) {
        console.log(a,b);
    }
    // 使用 bind(..) 進行柯里化
    var bar = foo.bind( null, 2 );
    bar( 3 ); // 2,3

    2.使用apply(..) 來展開一個數組,併當作參數傳入一個函數

    function foo(a,b) {
        console.log(a,b);
    }
    // 把數組展開成參數
    foo.apply( null, [2, 3] ); // 2,3

    其實上面兩種使用場景其實都不關心call/app/bind第一個參數的值是什麼,只是想傳個佔位值而已。
    但是總是傳入null可能會出現一些難以追蹤的bug,比如說當你在使用的第三方庫中的某個函數中有this時,this會被錯誤的綁定到全局對象上,造成一些難以預料的後果(修改全局變量)

    var a = 1;//全局變量
    const Utils = {
        a: 2,
        changeA: function(a){
            this.a = a;
        }
    }
    Utils.changeA(3);
    Utils.a //3
    a //1
    Utils.changeA.call(null,4);
    Utils.a //3
    a //4,修改了全局變量a!

    更安全的做法:

    var o = Object.create(null);
    Utils.changeA.call(o,6);
    a //1, 全局變量沒有修改
    o.a // 6 改的是變量o

    2.間接引用

    function foo() {
        console.log( this.a );
    }
    var a = 2;
    var o = { a: 3, foo: foo };
    var p = { a: 4 };
    o.foo(); // 3
    (p.foo = o.foo)(); // 2

    賦值表達式p.foo = o.foo 的返回值是目標函數的引用,因此調用位置是foo() 而不是p.foo() 或者o.foo()。根據我們之前說過的,這裡會應用默認綁定。

    this詞法(箭頭函數)

    上述的幾種規則適用於所有的正常函數,但不包括ES6的箭頭函數。箭頭函數不使用this的四種標準規則,而是根據外層(函數或者全局)作用域(詞法作用域)來決定this

    function foo() {
    // 返回一個箭頭函數
        return (a) => {
            //this 繼承自foo()
            console.log( this.a );
        };
    }
    var obj1 = {
        a:2
    };
    var obj2 = {
        a:3
    };
    var bar = foo.call( obj1 );
    bar.call( obj2 ); // 2, 不是3 !

    foo() 內部創建的箭頭函數會捕獲調用時foo() 的this。由於foo() 的this 綁定到obj1,bar(引用箭頭函數)的this 也會綁定到obj1,箭頭函數的綁定無法被修改。(new 也不行!)

    幾個例子加深理解

    this的理論知識講解得差不多了,來幾個例子看看自己有沒有理解全面:
    1.經典面試題:以下輸出結果是什麼

    var length = 10;
    function fn() {
        console.log(this.length);
    }
    var obj = {
      length: 5,
      method: function(fn) {
        fn();
        arguments[0]();
      }
    };
    obj.method(fn, 1);

    obj中method方法裏面調用了兩次fn。第一次是直接調用的“裸露”的fn,因此fn()中this使用默認綁定,this.length為10.第二次調用時通過arguments的方式調用的,arguments[0]其實指向的就是fn,但是是通過obj[fn]這種對象上下文的隱式綁定的,因此this指向arguments,而arguments只有一個一項(method中只有fn一個參數),因此arguments.length為1。因此打印的結果為:

    10
    1

    2.以下輸出什麼

    var obj = {
        birth: 1990,
        getAge: function () {
            var b = this.birth; // 1990
            var fn = function () {
                return new Date().getFullYear() - this.birth; // this指向window或undefined
            };
            return fn();
        }
    };
    obj.getAge();

    答案是嚴格模式下會報錯,非嚴格模式下輸出NaN
    原因也是因為在調用obj.getAge()后,getAge方法內的this使用隱式綁定。但是return fn()的時候用的是“裸露的fn”使用默認綁定,fn裏面的this指向window或者undefined。
    使用箭頭函數來修正this的指向:

    var obj = {
        birth: 1990,
        getAge: function () {
            var b = this.birth; // 1990
            var fn = () => new Date().getFullYear() - this.birth; // this指向obj對象
            return fn();
        }
    };
    obj.getAge(); // 25

    使用箭頭函數后,fn中的this在他的詞法分析階段就已經確定好了(即fn定義的時候),跟調用位置無關。fn的this指向外層的作用域(即getAge中的this)
    3.以下輸出為什麼是’luo’

    var A = function( name ){ 
        this.name = name;
    };
    var B = function(){ 
        A.apply(this,arguments);
    };
    B.prototype.getName = function(){ 
        return this.name;
    };
    var b=new B('sven');  // B {name: "luo"}
    console.log( b.getName() ); // 輸出:  'luo'

    執行new B(‘seven’)後會返回一個新對象b,並且B函數中的this會綁定到新對象b上,B的函數體內執行A.apply(this.arguments)也就是執行b.name = name;這個時候b的值就是{name:’luo’},所以b.getName()就能輸出’luo’啦~

    實際在業務使用中,邏輯會更複雜一些,但是萬變不離其宗,都按照上面寫的規則來代入就好了

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

  • 從零開始搭建前後端分離的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的項目框架之十二Swagger(參數)使用二

    從零開始搭建前後端分離的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的項目框架之十二Swagger(參數)使用二

      引言

      在 中提到了 Swagger 的基本使用,僅限於沒有參數,沒有驗證的那種api文檔生成,那麼這篇就連接上篇繼續,在一般具有安全性、權限等驗證的接口上,

      都會在header/url中加上請求者的秘鑰、簽名等,當然也有可能添加到body等其它地方, Swashbuckle.AspNetCore 都支持這些寫法。

      如何使用 — 下面將介紹兩種使用方式

    兩種方式參數設置到何處都是在  In屬性上,屬性對於值如下:    參考

    • query: 參数字段值對應放在url中
    • header: 參數值對應放在header param中
    • body: 參數對應放到請求體中
    • path: 參數應該對應放到請求路徑  // 具體貌似沒用
    • formData: 參數對應放到請求表單中

      第一種:將一個或多個參數保護API的“securityDefinitions”添加到生成的Swagger中。

    這種是直接在文檔的右上方添加一個 Authorize 按鈕,設置了值后,每一個請求都會在設置的位置上加上相應的值,在 上一篇隨筆中的 ConfigureServices 方法中,

    對應位置 services.AddSwaggerGen(options =>{}) 中的  XmlComments  下 添加代碼如下:

                    options.AddSecurityDefinition("token", new ApiKeyScheme
                    {
                        Description = "token format : {token}",//參數描述
                        Name = "token",//名字
                        In = "header",//對應位置
                        Type = "apiKey"//類型描述
                    });
                    options.AddSecurityDefinition("sid", new ApiKeyScheme
                    {
                        Description = "sid format : {sid}",//參數描述
                        Name = "sid",//名字
                        In = "header",//對應位置
                        Type = "apiKey"//類型描述
                    });
                    //添加Jwt驗證設置 設置為全局的,不然在代碼中取不到
                    options.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> {
                        { "token", Enumerable.Empty<string>() },
                        { "sid", Enumerable.Empty<string>() },
                    });

      添加完成后,運行起來看下效果,效果圖: 

     設置上對應值,調用測試方法,可以在header中取到剛設置到的值,

     這裡能看到,可以取到設置的參數了。這樣一來,在需要驗證的接口上,我們就可以通過接口文檔來測試了。基本不用再藉助postman等接口測試工具了。

    但是,但是,這裡有一個問題,就是只要設置了參數值,每一次訪問都會在請求中帶上參數。

    下面將介紹第二種方式,只給需要驗證用戶的接口上添加驗證參數。

      第二種:使用“filters”擴展Swagger生成器,來實現只在需要添加參數的方法上添加參數。複雜的可以根據自己的需求來添加對應參數

    實現方式就是先新建一個類,名: SwaggerParameter ,實現 IOperationFilter 接口。SwaggerParameter 類代碼如下: 

        /// <summary>
        /// 自定義添加參數
        /// </summary>
        public class SwaggerParameter : IOperationFilter
        {
            /// <summary>
            /// 實現 Apply 方法
            /// </summary>
            /// <param name="operation"></param>
            /// <param name="context"></param>
            public void Apply(Operation operation, OperationFilterContext context)
            {
                if (operation.Parameters == null) operation.Parameters = new List<IParameter>();
                var attrs = context.ApiDescription.ActionDescriptor.AttributeRouteInfo;
                var t = typeof(BaseUserController);
                //先判斷是否是繼承用戶驗證類
                if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor descriptor && context.MethodInfo.DeclaringType?.IsSubclassOf(t) == true)
                {
                    //再驗證是否允許匿名訪問
                    var actionAttributes = descriptor.MethodInfo.GetCustomAttributes(inherit: true);
                    bool isAnonymous = actionAttributes.Any(a => a is AllowAnonymousAttribute);
                    // 需要驗證的方法添加
                    if (!isAnonymous)
                    {
                        operation.Parameters.Add(new NonBodyParameter()
                        {
                            Name = "sid",
                            In = "header", //query header body path formData
                            Type = "string",
                            Required = true,//是否必選
                            Description = "登錄返回的sid"
                        });
                        operation.Parameters.Add(new NonBodyParameter()
                        {
                            Name = "token",
                            In = "header", //query header body path formData
                            Type = "string",
                            Required = true,//是否必選
                            Description = "登錄返回的token"
                        });
                    }
                }
            }
        }

     運行起來后,進入到  文檔頁面,可以看到右上角的 Authorize 按鈕已經不見了,在不需要驗證的方法上,也找不到相應需要設置參數的輸入框。就只有在需要驗證的接口上才有。

    參考Swagger文檔圖如下: 

    參考代碼圖如下:

     

    效果圖: 

      這樣一來設置也就完成了。從上面就能看出,就只有需要用戶驗證的接口才會有相應參數。 

     

    我的設置方式是先定義了用戶驗證控制器類,讓需要用戶驗證的控制器繼承該控制器,然後在該控制器中不需要用戶驗證的接口上加上 AllowAnonymous 屬性 

    設置fitter時就可以根據上面提到的兩個點來進行判斷是否需要加上參數,如果不是這樣實現的,可以根據自己的需求變更fitter類,來控制文檔的生成。 

     

    以上若有什麼不對或可以改進的地方,望各位指出或提出意見,一起探討學習~ 

    有需要源碼的可通過此 鏈接拉取 覺得還可以的給個 start 和點個 下方的推薦哦~~謝謝!

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • pod刪除主要流程源碼解析

    本文以v1.12版本進行分析

    當一個pod刪除時,client端向apiserver發送請求,apiserver將pod的deletionTimestamp打上時間。kubelet watch到該事件,開始處理。

    syncLoop

    kubelet對pod的處理主要都是在syncLoop中處理的。

    func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler) {
    for {
    ...
            if !kl.syncLoopIteration(updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) {
                break
            }
    ...

    與pod刪除主要在syncLoopIteration中需要關注的是以下這兩個。

    func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
        syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
        select {
        case u, open := <-configCh:
    ...
            switch u.Op {
    ...
            case kubetypes.UPDATE:
                handler.HandlePodUpdates(u.Pods)
    ...
        case <-housekeepingCh:
            if !kl.sourcesReady.AllReady() {
            } else {
                if err := handler.HandlePodCleanups(); err != nil {
                    glog.Errorf("Failed cleaning pods: %v", err)
                }
            }
        }

    第一個是由於發送給apiserver的DELETE請求觸發的,增加了deletionTimestamp的事件。這裏對應於kubetypes.UPDATE。也就是會走到HandlePodUpdates函數。

    另外一個與delete相關的是每2s執行一次的來自於housekeepingCh的定時事件,用於清理pod,執行的是handler.HandlePodCleanups函數。這兩個作用不同,下面分別進行介紹。

    HandlePodUpdates

    先看HandlePodUpdates這個流程。只要打上了deletionTimestamp,就必然走到這個流程里去。

    func (kl *Kubelet) HandlePodUpdates(pods []*v1.Pod) {
        for _, pod := range pods {
    ...
            kl.dispatchWork(pod, kubetypes.SyncPodUpdate, mirrorPod, start)
        }
    }

    在HandlePodUpdates中,進而將pod的信息傳遞到dispatchWork中處理。

    func (kl *Kubelet) dispatchWork(pod *v1.Pod, syncType kubetypes.SyncPodType, mirrorPod *v1.Pod, start time.Time) {
        if kl.podIsTerminated(pod) {
            if pod.DeletionTimestamp != nil {
                kl.statusManager.TerminatePod(pod)
            }
            return
        }
        // Run the sync in an async worker.
        kl.podWorkers.UpdatePod(&UpdatePodOptions{
            Pod:        pod,
            MirrorPod:  mirrorPod,
            UpdateType: syncType,
            OnCompleteFunc: func(err error) {
    ...

    這裏首先通過判斷了kl.podIsTerminated(pod)判斷pod是不是已經處於了Terminated狀態。如果是的話,則不進行下面的kl.podWorkers.UpdatePod。

    func (kl *Kubelet) podIsTerminated(pod *v1.Pod) bool {
        status, ok := kl.statusManager.GetPodStatus(pod.UID)
        if !ok {
            status = pod.Status
        }
        return status.Phase == v1.PodFailed || status.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && notRunning(status.ContainerStatuses))
    }

    這個地方特別值得注意的是,並不是由了DeletionTimestamp就會認為是Terminated狀態,而是有DeletionTimestamp且所有的容器不在運行了。也就是說如果是一個正在正常運行的pod,是也會走到kl.podWorkers.UpdatePod中的。UpdatePod通過一系列函數調用,最終會通過異步的方式執行syncPod函數中進入到syncPod函數中。

    func (kl *Kubelet) syncPod(o syncPodOptions) error {
    ...
        if !runnable.Admit || pod.DeletionTimestamp != nil || apiPodStatus.Phase == v1.PodFailed {
            var syncErr error
            if err := kl.killPod(pod, nil, podStatus, nil); err != nil {
    ...

    在syncPod中,調用killPod從而對pod進行停止操作。

    killPod

    killPod是停止pod的主體。在很多地方都會使用。這裏主要介紹下起主要的工作流程。停止pod的過程主要發生在killPodWithSyncResult函數中。

    func (m *kubeGenericRuntimeManager) killPodWithSyncResult(pod *v1.Pod, runningPod kubecontainer.Pod, gracePeriodOverride *int64) (result kubecontainer.PodSyncResult) {
        killContainerResults := m.killContainersWithSyncResult(pod, runningPod, gracePeriodOverride)
    ...
        for _, podSandbox := range runningPod.Sandboxes {
                if err := m.runtimeService.StopPodSandbox(podSandbox.ID.ID); err != nil {
    ...

    killPodWithSyncResult的主要工作分為兩個部分。killContainersWithSyncResult負責將pod中的container停止掉,在停止后再執行StopPodSandbox。

    func (m *kubeGenericRuntimeManager) killContainer(pod *v1.Pod, containerID kubecontainer.ContainerID, containerName string, reason string, gracePeriodOverride *int64) error {
        if err := m.internalLifecycle.PreStopContainer(containerID.ID); err != nil {
            return err
        }
    ...
        err := m.runtimeService.StopContainer(containerID.ID, gracePeriod)

    killContainersWithSyncResult的主要工作是在killContainer中完成的,這裏可以看到,其中的主要兩個步驟是在容器中進行prestop的操作。待其成功后,進行container的stop工作。至此所有的應用容器都已經停止了。下一步是停止pause容器。而StopPodSandbox就是執行這一過程的。將sandbox,也就是pause容器停止掉。StopPodSandbox是在dockershim中執行的。

    func (ds *dockerService) StopPodSandbox(ctx context.Context, r *runtimeapi.StopPodSandboxRequest) (*runtimeapi.StopPodSandboxResponse, error) {
    ...
    if !hostNetwork && (ready || !ok) {
    ...
            err := ds.network.TearDownPod(namespace, name, cID, annotations)
    ...
        }
        if err := ds.client.StopContainer(podSandboxID, defaultSandboxGracePeriod); err != nil {

    StopPodSandbox中主要的部分是先進行網絡卸載,再停止相應的容器。在完成StopPodSandbox后,至此pod的所有容器都已經停止完成。至於volume的卸載,是在volumeManager中進行的。本文不做單獨介紹了。停止后的容器在pod徹底清理后,會被gc回收。這裏也不展開講了。

    HandlePodCleanups

    上面這個流程並不是刪除流程的全部。一個典型的情況就是,如果container都不是running,那麼在UpdatePod的時候都return了,那麼又由誰來處理呢?這裏我們回到最開始,就是那個每2s執行一次的HandlePodCleanups的流程。也就是說比如container處於crash,container正好不是running等情況,其實是在這個流程里進行處理的。當然HandlePodCleanups的作用不僅僅是清理not running的pod,再比如數據已經在apiserver中強制清理掉了,或者由於其他原因這個節點上還有一些沒有完成清理的pod,都是在這個流程中進行處理。

    func (kl *Kubelet) HandlePodCleanups() error {
    ... 
        for _, pod := range runningPods {
            if _, found := desiredPods[pod.ID]; !found {
                kl.podKillingCh <- &kubecontainer.PodPair{APIPod: nil, RunningPod: pod}
            }
        }

    runningPods是從cache中獲取節點現有的pod,而desiredPods則是節點上應該存在未被停止的pod。如果存在runningPods中有而desiredPods中沒有的pod,那麼它應該被停止,所以發送到podKillingCh中。

    func (kl *Kubelet) podKiller() {
    ...
        for podPair := range kl.podKillingCh {
    ...
    
            if !exists {
                go func(apiPod *v1.Pod, runningPod *kubecontainer.Pod) {
                    glog.V(2).Infof("Killing unwanted pod %q", runningPod.Name)
                    err := kl.killPod(apiPod, runningPod, nil, nil)
    ...
                }(apiPod, runningPod)
            }
        }
    }

    在podKiller流程中,會去接收來自podKillingCh的消息,從而執行killPod,上文已經做了該函數的介紹了。

    statusManager

    在最後,statusManager中的syncPod流程,將會進行檢測,通過canBeDeleted確認是否所有的容器關閉了,volume卸載了,cgroup清理了等等。如果這些全部完成了,則從apiserver中將pod信息徹底刪除。

    func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {
    ...
        if m.canBeDeleted(pod, status.status) {
            deleteOptions := metav1.NewDeleteOptions(0)
            deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(pod.UID))
            err = m.kubeClient.CoreV1().Pods(pod.Namespace).Delete(pod.Name, deleteOptions)
    ...

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • 氫燃料沒未來?Honda改拚電動車,明年搶攻歐陸

    氫燃料沒未來?Honda改拚電動車,明年搶攻歐陸

    本田汽車(Honda)計畫於2018年一次推出兩款全電動車,且宣告未來將以電動車作為發展主軸。

    根據《BusinessInsider》報導表示,本田六月就已表示將打造中國專屬電動車,8月29日發佈的聲明稿則指明另一款電動車是為歐洲客戶所設計。

    本田說,新城市電動概念車(Urban EV Concept)將在九月法蘭克福車展上亮相,這也是本田在歐洲第一款電動車,肩負打開歐洲電動車市場的任務。

    目前本田僅有一款純電動車在美國上市,不過本田表示,2030年旗下三分之二的車型都要電動化,達成目標的方法將在法蘭克福車展上對外說明。

    本田與日本同業豐田(Toyota)此前主要開發氫燃料車與混和動力車,但由上述可知本田策略已經轉向發展電動車,可能引領其它日本車廠跟進。

    (本文內容由授權使用。圖片出處:)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • DuckDuckGo切換到蘋果地圖

    DuckDuckGo切換到蘋果地圖

      匿名搜索引擎 DuckDuckGo 宣布地圖和地址相關的搜索將使用蘋果地圖。Google 被認為是一大隱私威脅,顯然以隱私作為主要賣點的 DuckDuckGo 不能也應該使用 Google 地圖。它可以選擇的全球地圖只剩下蘋果地圖、OpenStreetMap 和必應地圖等不多的幾個選擇。Google 地圖在功能和細節上仍然是最出色的,蘋果地圖的美國地圖足夠精細,但在其它國家它的地圖準確性就並不那麼令人滿意了。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 趨勢無法擋!BMW、JLR宣布自2020年起力推電動車

    趨勢無法擋!BMW、JLR宣布自2020年起力推電動車

    Thomson Reuters報導,英國最大汽車製造商Jaguar Land Rover(JLR)7日宣布,2020年起旗下所有新車都將具備電動或油電混合驅動選項。英國跟隨法國以及馬德里、墨西哥城和雅典等城市的抗空汙腳步,7月宣布將自2040年起禁止販售汽油和柴油新車。

    德國車廠BMW 7日宣布將自2020年起開始量產電動車、預估到2025年將有12種純電動車款。BMW執行長Harald Krueger表示,電動車續航力將上看700公里。電池成本約占電動車造價的30到50%。巴克萊(Barclays)預估電池產能的大幅提升將可令電動車、內燃引擎車在2020到2030年間達到「成本平價」。ING預估歐洲有望在2035年成為100%的電動車市場。

    BBC News報導,BMW研發部負責人Klaus Froehlich 日前表示,電動移動趨勢是不可逆轉的,自2020年起BMW所有車款都將擁有電動驅動選項。

    (本文內容由授權使用。)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • 雷諾日產2022目標:推零排放電動車、降低車用鋰電池成本30%

    雷諾日產2022目標:推零排放電動車、降低車用鋰電池成本30%

    特斯拉(Tesla Inc.)讓電動車產業起了大震盪,發展愈來愈快,雷諾日產聯盟(The Renault-Nissan Alliance)也不落人後,聲稱2020年將發表12款零排放電動車,同時還要推出機器人計程車(robo-taxi),提供無人駕駛的叫車服務。

    CNBC報導,雷諾日產聯盟董事長兼執行長、三菱汽車董事長戈恩(Carlos Ghosn)15日在接受專訪時表示,集團擬定的六年新計畫「Alliance 2022」,除了要發表零排放電動車、機器人計程車之外,也會推出40款自駕程度不等的汽車款式。

    戈恩說,機器人計程車主要是商業用途,許多像是Uber等業者,對此都有極高需求。另外,戈恩也打算將雷諾、日產與三菱汽車的合作綜效倍增,預估2022年聯盟的年營收,將從原本的50億歐元拉高至100億歐元;到了2022年,三大車廠的合併汽車銷售量,有望從2016年的1,000萬台、1,050萬台進一步增加至1,400萬台。
    目前聯盟的主要目標,是降低車用電池的成本,希望能在2022年底前,將成本降低30%,單次充電時間壓縮到15分鐘、每次充電可行駛的里程數增加至230公里。戈恩說,集團已經評估過鋰的產能,估計未來5-6年內,應該都不會有瓶頸出現。

    先前已有消息指稱,中國打算禁用、禁產傳統汽油車。新華社9月9日報導,中國工信部副部長辛國斌表示,一些國家已經制定了停止生產銷售傳統能源汽車的時間表。他說,目前工信部也啟動了相關研究、將會同相關部門制定中國的時間表。

    Fortune 11日報導,目前北京當局並未對何時要進行轉換設定時間表。其他國家則打算在2030年左右逐步將汽油車淘汰:印度的目標設定在2030年、但並不是嚴格執行的時間點,至於英國、法國與挪威的預定目標則分別是2040年、2040年與2025年。

    澳洲礦商Orocobre Limited最近才聲稱鋰礦需求強、報價攀高,將擴產碳酸鋰。barron`s.com、The Australian報導,Orocobre執行長Richard Seville 8月底說,全球鋰礦市場的基本面依舊穩健,不但需求強勁、供給吃緊,報價也相當具有吸引力。為了滿足特定電池夥伴的需求,Orocobre會分階段擴充Olaroz鋰礦廠的產能。

    依據Orocobre計畫,位於阿根廷的Olaroz鋰礦廠,產能料將倍增。另外,該公司還會跟豐田通商(Toyota Tsusho Corporation)一同打造一座廠房,預計將年產10,000公噸的氫氧化鋰(lithium hydroxide)。

    (本文內容由授權使用)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益